tag:blogger.com,1999:blog-77247737687430218132024-03-16T00:49:41.368-05:00Dan Malec's BlogGardening | Programming | TinkeringDan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.comBlogger19125tag:blogger.com,1999:blog-7724773768743021813.post-72851012646396682482016-04-23T09:10:00.002-05:002016-04-23T09:10:36.595-05:00Partial Intents and Conversational ToneAs I read through the <a href="https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-voice-interface-and-user-experience-testing">Alexa UX testing guidelines</a> prior to submitting my new skill for certification, I realized I had not handled partial intents. The challenge I had was that two different intents each specified a numeric slot type and, based on the state of the skill, the user might have said either prior to saying a number.<br />
<br />
<h2>
The First Attempt</h2>
<div class="p1">
<span class="s1">After some playing around, I settled on three core intents:</span><br />
<span class="s1"><i><br /></i></span>
<span class="s1"><i>FooIntent foo {MyNumber}</i></span><br />
<i>BarIntent bar {MyNumber}</i></div>
<div class="p1">
<span class="s1"><i>NumberIntent {MyNumber}</i></span></div>
<div class="p1">
<br /></div>
<div class="p1">
This allowed me to catch the user filling in the numeric slot while saying <i>foo</i> or <i>bar.</i> It also allowed me to ask the user to provide a number if they did not when saying <i>foo</i> or <i>bar. </i>I thought this was fairly solid and updated the intent implementations to handle the user leaving out a number:</div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div class="p1">
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;">exports.handleFooAction = function(intent, session) {</span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> var myNumber = parseMyNumber(intent);</span></span></div>
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="s1"></span><br /></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> if (typeof myNumber === 'undefined') {</span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"></span> setPriorAction(session, 'FOO');</span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"><span class="Apple-tab-span"></span> return { 'view': 'Error_FooToUnspecifiedNumberView', 'data': data };</span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> }</span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;"> ...</span></span></div>
<span class="s1" style="font-family: Courier New, Courier, monospace;">
</span><br />
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace;">}</span></span></div>
<div class="p1">
<span class="s1"><br /></span></div>
</div>
<div class="p1">
<span class="s1">I added code for the new intent:</span></div>
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">exports.handleNumberAction = function(intent, session) {</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> var priorAction = getPriorAction(session);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> if (priorAction === 'FOO') {</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> clearPriorAction(session);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> return exports.handleFooAction(intent, session);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> } else if (priorAction === 'BAR') {</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> clearPriorAction(session);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> return exports.handleBarAction(intent, session);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">
</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> } else {</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">
</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> return { 'view': 'OK_HelpView', 'data': data};</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> }</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">}</span></div>
<div class="p1">
<br /></div>
<div class="p1">
In the case of no prior action, the code sends help text to the user, so they can understand how to use the application (we'll come back to this shortly). After running through a number of tests, I was confident that it behaved as expected and submitted the skill for certification.<br />
<br /></div>
<div class="p1">
<h2>
Getting Feedback</h2>
I did not pass certification (<i><span style="color: #999999;">"Alexa, Sad Trombone"</span></i>) due to the <i>NumberIntent</i> triggering the help text when invoked directly.<br />
<br />
I'll admit, my first thought was, "<i>Well, yes, if the user does not understand how to give the skill commands, it tries to give them help.</i>"<br />
<br />
My second thought was, "<i>Forcing the user to say things in an arbitrary order is not good UX, I should fix this.</i>"<br />
<br /></div>
<div class="p1">
<h2>
Back to the Drawing Board</h2>
</div>
<div class="p1">
I reworked the code so that, if there is no possibility the user meant <i>bar</i>, the skill assumes the user meant <i>foo. </i>Otherwise, the user is asked if they would like <i>foo </i>or<i> bar:</i></div>
<div class="p1">
<i><br /></i></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">exports.handleNumberAction = function(intent, session) {</span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace;"> ...</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"></span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> } else {</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"></span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> if (!isBarAllowed(session)) {</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> return exports.handleFooAction(intent, session);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> } else {</span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace;"><span class="s1"> </span><span class="s1">var my</span>Number = parseMyNumber(intent);</span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace;"> ...</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> setPriorNumber(session, <span class="s1">my</span>Number);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> return { 'view': 'Error_FooOrBarRequired', 'data': data };</span></div>
<div class="p1">
</div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> }</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> }</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">}</span></div>
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span class="s1">Both the foo and bar action handlers were updated to account for prior numbers having been provided:</span></div>
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">// Check if a prior number was given.</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">var <span class="s1">my</span>Number = getPriorNumber(session);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">if (typeof <span class="s1">my</span>Number !== 'undefined') {</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> clearPriorNumber(session);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">} else {</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;"> <span class="s1">my</span>Number = parseMyNumber(intent);</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">
</span></div>
<div class="p1">
<span class="s1" style="font-family: Courier New, Courier, monospace;">}</span></div>
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span class="s1">After some play testing and debugging, the skill appears stable and the interactions feel more natural for having made this change.</span><br />
<span class="s1"><br /></span>
<h2>
Conclusion</h2>
<span class="s1">I think I'm ready for my next attempt at passing certification. The lesson I took away is that there is no substitute for user testing when building user experiences.</span></div>
Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-78404611154458778312015-02-16T17:57:00.002-05:002015-02-16T17:57:43.502-05:00OctoGlow - a PiGlow plugin for OctoPrintOriginally I worked out how to <a href="http://danmalec.blogspot.com/2015/02/octoglow.html">use a PiGlow to display OctoPrint progress</a>. Based on <a href="http://octoprint.org/">OctoPrint</a>'s suggestion I took a look at <a href="http://docs.octoprint.org/en/devel/plugins/gettingstarted.html">the tutorial on writing a plugin</a> and revisited the code. If you are interested in writing a plugin, I definitely recommend checking out the tutorial; I was able to have the hello world example up and running in no time.<br />
<br />
A big advantage of running as a plugin is that I can spawn a thread to update the LEDs independently of the event callbacks. This enables me to enhance the display of status and progress by animating the LEDs. I put together a short video demonstrating how this looks:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/duX3tedMOqg/0.jpg" src="http://www.youtube.com/embed/duX3tedMOqg?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div>
<br />
<br />
I am still testing the code and working out if I want to handle additional status changes. However, you can check out <a href="https://github.com/dmalec/OctoPrint-OctoGlow">the source code on GitHub</a> and install the plugin if you are interested in using a PiGlow with OctoPrint.Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-21365830909751538632015-02-02T19:24:00.001-05:002015-02-16T11:07:52.612-05:00How To Display OctoPrint Progress on a PiGlowI've been using <a href="http://octoprint.org/">OctoPrint/OctoPi</a> and a Raspberry Pi to drive my <a href="http://printrbot.com/shop/simple-makers-kit-2/">PrintrBot 1405</a> and it's been working well. There are times when being able to glance over and see the progress of the print without using a web browser would be nice, so I added a <a href="http://shop.pimoroni.com/products/piglow">Pimoroni PiGlow</a>.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzgHmhw12JQPI7GhZbUQAJAzieZvOcZRM49aWSwm2g7mFE7681v_NW9DafNrfnJQebfwLogWnk2iNWZakCxW87DCuvtfP_pwYfMfsXljCNdqGRwChp6d7VPxjB1lAGZPemBK7qo3AQNTbP/s1600/OctoGlow_Intro.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzgHmhw12JQPI7GhZbUQAJAzieZvOcZRM49aWSwm2g7mFE7681v_NW9DafNrfnJQebfwLogWnk2iNWZakCxW87DCuvtfP_pwYfMfsXljCNdqGRwChp6d7VPxjB1lAGZPemBK7qo3AQNTbP/s1600/OctoGlow_Intro.jpg" height="426" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">PrintrBot with PiGlow</td></tr>
</tbody></table>
<h3>
<a name='more'></a>Getting the PiGlow Running</h3>
The first step was enabling i2c on the Pi. I followed the <a href="https://github.com/pimoroni/piglow">Pimoroni supplied instructions for enabling support for the PiGlow</a>. Running 'sudo python piglow-example.py' resulted in the following error message:
<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">Traceback (most recent call last):</span><br />
<span style="font-family: Courier New, Courier, monospace;"> File "piglow-example.py", line 52, in</span><br />
<span style="font-family: Courier New, Courier, monospace;"> piglow = PiGlow(1)</span><br />
<span style="font-family: Courier New, Courier, monospace;"> File "piglow-example.py", line 21, in __init__</span><br />
<span style="font-family: Courier New, Courier, monospace;"> self.bus = SMBus(i2c_bus)</span><br />
<span style="font-family: Courier New, Courier, monospace;">IOError: [Errno 2] No such file or directory</span><br />
<br />
Hmmm... that's not quite right... after much fumbling about, I read the source and found out that my Version 1 Model B required the i2c port to change from 1 to 0:<br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">piglow = PiGlow(1)</span><br />
<br />
became:<br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">piglow = PiGlow(0)</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
And blinking LED goodness ensued:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR2YIyqarOnl7xtra1EcMKVuPobP3pyc2fZd46lxA_HstWb_cbLSb2C3xFy3-CVZzqcW8nGQkRYGoyCHm5npaQ0zgxrm19im7fpw7LxC0Z2WNZNgDBMMJTqfzo9zQ8n1kl3fCrbRH_J5jY/s1600/OctoGlow_LedTest.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR2YIyqarOnl7xtra1EcMKVuPobP3pyc2fZd46lxA_HstWb_cbLSb2C3xFy3-CVZzqcW8nGQkRYGoyCHm5npaQ0zgxrm19im7fpw7LxC0Z2WNZNgDBMMJTqfzo9zQ8n1kl3fCrbRH_J5jY/s1600/OctoGlow_LedTest.jpg" height="426" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">PiGlow Running Test Program</td></tr>
</tbody></table>
<br />
Awesome! At this point, a test print using OctoPi insured I hadn't messed up anything during my fumbling about. So far, so good.<br />
<br />
<h3>
Reporting Progress</h3>
Reading through the <a href="http://docs.octoprint.org/en/master/events/index.html">OctoPrint documentation on EventHooks</a> I decided to start with just tracking the ZChange and using the progress variable to show the percentage complete. This allows for a very simple python script that takes the percentage complete as a command line argument and updates the PiGlow accordingly.<br />
<br />
<a href="https://github.com/Boeeerb/PiGlow">Jason Barnett's excellent PiGlow library</a> abstracts away a lot of the details of working with the PiGlow as well as dealing with the version of the Raspberry Pi itself. Jason's library and my script will reside in ~/octoglow/progress.py:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">cd ~</span><br />
<span style="font-family: Courier New, Courier, monospace;">mkdir octoglow</span><br />
<span style="font-family: Courier New, Courier, monospace;">cd octoglow</span><br />
<span style="font-family: Courier New, Courier, monospace;">wget https://raw.github.com/Boeeerb/PiGlow/master/piglow.py</span><br />
<span style="font-family: Courier New, Courier, monospace;">nano progress.py</span><br />
<br />
The following script will light up the spiral from the inside to the outside as the print progresses:<br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">from piglow import PiGlow</span><br />
<span style="font-family: Courier New, Courier, monospace;">import sys</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">piglow = PiGlow()</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">def updateProgress(progress):</span><br />
<span style="font-family: Courier New, Courier, monospace;"> piglow.white(10)</span><br />
<span style="font-family: Courier New, Courier, monospace;"> if progress > 20:</span><br />
<span style="font-family: Courier New, Courier, monospace;"> piglow.blue(10)</span><br />
<span style="font-family: Courier New, Courier, monospace;"> if progress > 40:</span><br />
<span style="font-family: Courier New, Courier, monospace;"> piglow.green(10)</span><br />
<span style="font-family: Courier New, Courier, monospace;"> if progress > 60:</span><br />
<span style="font-family: Courier New, Courier, monospace;"> piglow.yellow(10)</span><br />
<span style="font-family: Courier New, Courier, monospace;"> if progress > 80:</span><br />
<span style="font-family: Courier New, Courier, monospace;"> piglow.orange(10)</span><br />
<span style="font-family: Courier New, Courier, monospace;"> if progress >= 100:</span><br />
<span style="font-family: Courier New, Courier, monospace;"> piglow.red(10)</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">piglow.all(0);</span><br />
<span style="font-family: Courier New, Courier, monospace;">if len(sys.argv) > 1:</span><br />
<span style="font-family: Courier New, Courier, monospace;"> updateProgress(float(sys.argv[1]))</span><br />
<br />
You can then test the script from the command line:<br />
<br />
<div>
<span style="font-family: 'Courier New', Courier, monospace;">sudo python ~/octoglow/progress.py 100</span></div>
<div>
<span style="font-family: 'Courier New', Courier, monospace;"><br /></span></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihrul1Ajq6TQmGs425BCG8uJVP3FPzK_-yiulOe8MKkMEGy8z4McShNKBWn0xX9Azt_WRFRgZNSlixJJavMTTvI1JAXo_vFzmtK9-S1e-l5Lj4yJllBkRxG3JejVyR52_r0Gna9hEyM81_/s1600/OctoGlow_AllOn.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihrul1Ajq6TQmGs425BCG8uJVP3FPzK_-yiulOe8MKkMEGy8z4McShNKBWn0xX9Azt_WRFRgZNSlixJJavMTTvI1JAXo_vFzmtK9-S1e-l5Lj4yJllBkRxG3JejVyR52_r0Gna9hEyM81_/s1600/OctoGlow_AllOn.jpg" height="426" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">PiGlow Showing OctoPrint Progress</td></tr>
</tbody></table>
<h3>
Adding the Event Hooks to OctoPrint</h3>
Next up is editing '.octoprint/config.yaml' and adding the following snippet:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">events:</span><br />
<span style="font-family: Courier New, Courier, monospace;"> enabled: True</span><br />
<span style="font-family: Courier New, Courier, monospace;"> subscriptions:</span><br />
<span style="font-family: Courier New, Courier, monospace;"> - event: PrintStarted</span><br />
<span style="font-family: Courier New, Courier, monospace;"> command: sudo python ~/octoglow/progress.py {__progress}</span><br />
<span style="font-family: Courier New, Courier, monospace;"> type: system</span><br />
<span style="font-family: Courier New, Courier, monospace;"> - event: ZChange</span><br />
<span style="font-family: Courier New, Courier, monospace;"> command: sudo python ~/octoglow/progress.py {__progress}</span><br />
<span style="font-family: Courier New, Courier, monospace;"> type: system</span><br />
<span style="font-family: Courier New, Courier, monospace;"> - event: PrintDone</span><br />
<span style="font-family: Courier New, Courier, monospace;"> command: sudo python ~/octoglow/progress.py {__progress}</span><br />
<span style="font-family: Courier New, Courier, monospace;"> type: system</span><br />
<br />
Then, via the web interface, I restarted OctoPrint. Things looked good initially; but, unfortunately, the progress never got beyond zero. After some debugging, I traced it back to a mismatch in hash keys that looks like it has been fixed in the development branch <a href="https://github.com/foosel/OctoPrint/commit/47cfcd7b122464fa8d895295b193e1bff3983a91">courtesy of a pull request by Salandora</a>. A few small updates to the local copy of events.py, another restart of OctoPrint and the progress bar is up and running!<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/gMtqndar2dM/default.jpg?sqp=CLCowKYF&rs=AOn4CLAFqHVYYVrkdseWXuvYE3lqqk38Sg" frameborder="0" height="266" src="http://www.youtube.com/embed/gMtqndar2dM?feature=player_embedded" width="320"></iframe></div>
<span style="font-family: Times, Times New Roman, serif;"><br /></span>
<br />
<h2>
</h2>
Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-73735800158808451572012-03-20T23:13:00.001-05:002012-03-20T23:13:49.404-05:00Adafruit IoT Printer Box Drawing CheatsheetI realized I wanted a cheat sheet for working with box drawing on <a href="https://www.adafruit.com/products/717">Adafruit's Internet of Things Printer</a> while I was playing around with <a href="http://danmalec.blogspot.com/2012/03/pachube-on-adafruits-iot-printer.html">making the Pachube data feed look swanky</a>. This is my attempt to capture the character codes for the various types of possible boxes. This was gathered from a combination of the printer's test printout, Anders's work on this <a href="http://forums.adafruit.com/viewtopic.php?f=19&t=26807">forum thread</a>, and the Unicode section of <a href="http://en.wikipedia.org/wiki/Box-drawing_character">Wikipedia's entry on box drawing</a>. If you can think of additional characters or a different breakdown in the information that would make the cheat sheet more useful, please feel free to leave feedback in the comments section<br/><br/>
<style>.tblGenFixed td {padding:0 3px;overflow:hidden;white-space:normal;letter-spacing:0;word-spacing:0;background-color:#fff;z-index:1;border-top:0px none;border-left:0px none;border-bottom:1px solid #CCC;border-right:1px solid #CCC;} .dn {display:none} .tblGenFixed td.s0 {background-color:white;font-family:arial,sans,sans-serif;font-size:100.0%;font-weight:bold;font-style:normal;color:#000000;text-decoration:none;text-align:left;vertical-align:bottom;direction:auto-ltr;white-space:normal;overflow:hidden;border-top:1px solid #CCC;border-right:;border-bottom:;border-left:1px solid #CCC;} .tblGenFixed td.s2 {background-color:white;font-family:arial,sans,sans-serif;font-size:100.0%;font-weight:normal;font-style:normal;color:#000000;text-decoration:none;text-align:left;vertical-align:bottom;direction:auto-ltr;white-space:normal;overflow:hidden;border-right:;border-bottom:;border-left:1px solid #CCC;} .tblGenFixed td.s1 {background-color:white;font-family:arial,sans,sans-serif;font-size:100.0%;font-weight:bold;font-style:normal;color:#000000;text-decoration:none;text-align:left;vertical-align:bottom;direction:auto-ltr;white-space:normal;overflow:hidden;border-top:1px solid #CCC;border-right:;border-bottom:;} .tblGenFixed td.s3 {background-color:white;font-family:arial,sans,sans-serif;font-size:100.0%;font-weight:normal;font-style:normal;color:#000000;text-decoration:none;text-align:left;vertical-align:bottom;direction:auto-ltr;white-space:normal;overflow:hidden;border-right:;border-bottom:;} </style>
<span style="font-size: large;">Single Lines</span><br />
<table border=0 cellpadding=0 cellspacing=0 class='tblGenFixed'>
<tr class='rShim'><td class='rShim' style='width:120px;'><td class='rShim' style='width:120px;'><td class='rShim' style='width:120px;'><td class='rShim' style='width:602px;'></tr>
<tr><td class='s0'>Unicode<td class='s1'>Char<td class='s1'>Unicode<td class='s1'>Description</tr>
<tr><td class='s2'>┌<td class='s3'>0xDA<td class='s3'>U+250C<td class='s3'>Top Left Corner</tr>
<tr><td class='s2'>┐<td class='s3'>0xBF<td class='s3'>U+2510<td class='s3'>Top Right Corner</tr>
<tr><td class='s2'>└<td class='s3'>0xC0<td class='s3'>U+2514<td class='s3'>Bottom Left Corner</tr>
<tr><td class='s2'>┘<td class='s3'>0xD9<td class='s3'>U+2518<td class='s3'>Bottom Right Corner</tr>
<tr><td class='s2'>├<td class='s3'>0xC3<td class='s3'>U+251C<td class='s3'>Right Facing Tee</tr>
<tr><td class='s2'>┤<td class='s3'>0xB4<td class='s3'>U+2524<td class='s3'>Left Facing Tee</tr>
<tr><td class='s2'>┬<td class='s3'>0xC2<td class='s3'>U+252C<td class='s3'>Bottom Facing Tee</tr>
<tr><td class='s2'>┴<td class='s3'>0xC1<td class='s3'>U+2534<td class='s3'>Top Facing Tee</tr>
<tr><td class='s2'>┼<td class='s3'>0xC5<td class='s3'>U+253C<td class='s3'>Plus</tr>
<tr><td class='s2'>━<td class='s3'>0xC4<td class='s3'>U+2501<td class='s3'>Horizontal Line</tr>
<tr><td class='s2'>│<td class='s3'>0xB3<td class='s3'>U+2502<td class='s3'>Vertical Line</tr>
</table>
<br/>
<span style="font-size: large;">Double Lines</span><br />
<table border=0 cellpadding=0 cellspacing=0 class='tblGenFixed'>
<tr class='rShim'><td class='rShim' style='width:120px;'><td class='rShim' style='width:120px;'><td class='rShim' style='width:120px;'><td class='rShim' style='width:602px;'></tr>
<tr><td class='s0'>Unicode<td class='s1'>Char<td class='s1'>Unicode<td class='s1'>Description</tr>
<tr><td class='s2'>╔<td class='s3'>0xC9<td class='s3'>U+2554<td class='s3'>Top Left Corner</tr>
<tr><td class='s2'>╗<td class='s3'>0xBB<td class='s3'>U+2557<td class='s3'>Top Right Corner</tr>
<tr><td class='s2'>╚<td class='s3'>0xC8<td class='s3'>U+255A<td class='s3'>Bottom Left Corner</tr>
<tr><td class='s2'>╝<td class='s3'>0xBC<td class='s3'>U+255D<td class='s3'>Bottom Right Corner</tr>
<tr><td class='s2'>╠<td class='s3'>0xCC<td class='s3'>U+2560<td class='s3'>Right Facing Tee</tr>
<tr><td class='s2'>╣<td class='s3'>0xB9<td class='s3'>U+2563<td class='s3'>Left Facing Tee</tr>
<tr><td class='s2'>╦<td class='s3'>0xCB<td class='s3'>U+2566<td class='s3'>Bottom Facing Tee</tr>
<tr><td class='s2'>╩<td class='s3'>0xCA<td class='s3'>U+2569<td class='s3'>Top Facing Tee</tr>
<tr><td class='s2'>╬<td class='s3'>0xCE<td class='s3'>U+256C<td class='s3'>Plus</tr>
<tr><td class='s2'>═<td class='s3'>0xCD<td class='s3'>U+2550<td class='s3'>Horizontal Line</tr>
<tr><td class='s2'>║<td class='s3'>0xBA<td class='s3'>U+2551<td class='s3'>Vertical Line</tr>
</table>
<br />
<span style="font-size: large;">Double Vertical / Single Horizontal Lines</span><br />
<table border=0 cellpadding=0 cellspacing=0 class='tblGenFixed'>
<tr class='rShim'><td class='rShim' style='width:120px;'><td class='rShim' style='width:120px;'><td class='rShim' style='width:120px;'><td class='rShim' style='width:602px;'></tr>
<tr><td class='s0'>Unicode<td class='s1'>Char<td class='s1'>Unicode<td class='s1'>Description</tr>
<tr><td class='s2'>╓<td class='s3'>0xD6<td class='s3'>U+2553<td class='s3'>Top Left Corner</tr>
<tr><td class='s2'>╖<td class='s3'>0xB7<td class='s3'>U+2556<td class='s3'>Top Right Corner</tr>
<tr><td class='s2'>╙<td class='s3'>0xD3<td class='s3'>U+2559<td class='s3'>Bottom Left Corner</tr>
<tr><td class='s2'>╜<td class='s3'>0xBD<td class='s3'>U+255C<td class='s3'>Bottom Right Corner</tr>
<tr><td class='s2'>╟<td class='s3'>0xC7<td class='s3'>U+255F<td class='s3'>Right Facing Tee</tr>
<tr><td class='s2'>╢<td class='s3'>0xB6<td class='s3'>U+2562<td class='s3'>Left Facing Tee</tr>
<tr><td class='s2'>╥<td class='s3'>0xD2<td class='s3'>U+2565<td class='s3'>Bottom Facing Tee</tr>
<tr><td class='s2'>╨<td class='s3'>0xD0<td class='s3'>U+2568<td class='s3'>Top Facing Tee</tr>
<tr><td class='s2'>╫<td class='s3'>0xD7<td class='s3'>U+256B<td class='s3'>Plus</tr>
</table>
<br />
<span style="font-size: large;">Single Vertical / Double Horizontal Lines</span><br />
<table border=0 cellpadding=0 cellspacing=0 class='tblGenFixed'>
<tr class='rShim'><td class='rShim' style='width:120px;'><td class='rShim' style='width:120px;'><td class='rShim' style='width:120px;'><td class='rShim' style='width:602px;'></tr>
<tr><td class='s0'>Unicode<td class='s1'>Char<td class='s1'>Unicode<td class='s1'>Description</tr>
<tr><td class='s2'>╒<td class='s3'>0xD5<td class='s3'>U+2552<td class='s3'>Top Left Corner</tr>
<tr><td class='s2'>╕<td class='s3'>0xB8<td class='s3'>U+2555<td class='s3'>Top Right Corner</tr>
<tr><td class='s2'>╘<td class='s3'>0xD4<td class='s3'>U+2558<td class='s3'>Bottom Left Corner</tr>
<tr><td class='s2'>╛<td class='s3'>0xBE<td class='s3'>U+255B<td class='s3'>Bottom Right Corner</tr>
<tr><td class='s2'>╞<td class='s3'>0xC6<td class='s3'>U+255E<td class='s3'>Right Facing Tee</tr>
<tr><td class='s2'>╡<td class='s3'>0xB5<td class='s3'>U+2561<td class='s3'>Left Facing Tee</tr>
<tr><td class='s2'>╤<td class='s3'>0xD1<td class='s3'>U+2564<td class='s3'>Bottom Facing Tee</tr>
<tr><td class='s2'>╧<td class='s3'>0xCF<td class='s3'>U+2567<td class='s3'>Top Facing Tee</tr>
<tr><td class='s2'>╪<td class='s3'>0xD8<td class='s3'>U+256A<td class='s3'>Plus</tr>
</table>
<br />
Here's some sample code that prints out each style of box line:
<script class="brush: cpp;" type="syntaxhighlighter">
#include "SoftwareSerial.h"
#include "Adafruit_Thermal.h"
const int printer_RX_Pin = 5;
const int printer_TX_Pin = 6;
Adafruit_Thermal printer(printer_RX_Pin, printer_TX_Pin);
uint8_t h1v1_test[5][5] = {{ 0xDA, 0xC4, 0xC2, 0xC4, 0xBF },
{ 0xB3, 0x20, 0xB3, 0x20, 0xB3 },
{ 0xC3, 0xC4, 0xC5, 0xC4, 0xB4 },
{ 0xB3, 0x20, 0xB3, 0x20, 0xB3 },
{ 0xC0, 0xC4, 0xC1, 0xC4, 0xD9 }};
uint8_t h2v2_test[5][5] = {{ 0xC9, 0xCD, 0xCB, 0xCD, 0xBB },
{ 0xBA, 0x20, 0xBA, 0x20, 0xBA },
{ 0xCC, 0xCD, 0xCE, 0xCD, 0xB9 },
{ 0xBA, 0x20, 0xBA, 0x20, 0xBA },
{ 0xC8, 0xCD, 0xCA, 0xCD, 0xBC }};
uint8_t h1v2_test[5][5] = {{ 0xD6, 0xC4, 0xD2, 0xC4, 0xB7 },
{ 0xBA, 0x20, 0xBA, 0x20, 0xBA },
{ 0xC7, 0xC4, 0xD7, 0xC4, 0xB6 },
{ 0xBA, 0x20, 0xBA, 0x20, 0xBA },
{ 0xD3, 0xC4, 0xD0, 0xC4, 0xBD }};
uint8_t h2v1_test[5][5] = {{ 0xD5, 0xCD, 0xD1, 0xCD, 0xB8 },
{ 0xB3, 0x20, 0xB3, 0x20, 0xB3 },
{ 0xC6, 0xCD, 0xD8, 0xCD, 0xB5 },
{ 0xB3, 0x20, 0xB3, 0x20, 0xB3 },
{ 0xD4, 0xCD, 0xCF, 0xCD, 0xBE }};
void print_test(uint8_t test[5][5]) {
for (int i=0; i<5; i++) {
for (int j=0; j<5; j++) {
printer.write(test[i][j]);
}
printer.println();
}
}
void setup() {
Serial.begin(9600);
printer.begin();
printer.setLineHeight(24);
print_test(h1v1_test);
print_test(h2v2_test);
print_test(h1v2_test);
print_test(h2v1_test);
printer.sleep();
printer.wake();
printer.setDefault();
}
void loop() {}
</script><br />
And the resulting printout:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj76bD3EGpGMmXHGIxW5eTItpdymEqhRc6in0XJmE8TV-AEGZhHSoK4fVqWjLKRXJjKsZdLLP454h1gHeL6gX_EJE2rP6keq6fF9MRWynGM1uFdl7aiq0uChe0izhBazkyDjksTH_0FKfcZ/s1600/test_boxes.jpg" imageanchor="1" style=""><img border="0" height="320" width="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj76bD3EGpGMmXHGIxW5eTItpdymEqhRc6in0XJmE8TV-AEGZhHSoK4fVqWjLKRXJjKsZdLLP454h1gHeL6gX_EJE2rP6keq6fF9MRWynGM1uFdl7aiq0uChe0izhBazkyDjksTH_0FKfcZ/s320/test_boxes.jpg" /></a></div>Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-58272723282005198252012-03-14T20:06:00.000-05:002012-03-14T20:06:38.469-05:00PWM Backlight Workaround for LCD BackpackI really like the <a href="https://www.adafruit.com/products/292">Adafruit LCD Backpack</a>, but I've been wanting to play around with adjusting the LCD backlight brightness programatically. After much mulling and reading of forum posts, I decided to just operate the backlight using one extra wire to a PWM pin on the Arduino. To try and keep things tidy, I ordered some extra <a href="http://www.adafruit.com/products/725">3-pin terminal blocks</a> so I could make a 6-pin block instead of the normal 5-pin block. Just to up the stakes, I'm doing this with my brand new <a href="http://www.adafruit.com/products/198">20x4 LCD</a>.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGJ7VjlnfiPy1BBoTF4AT8ntauQROb4nGu4VwiWwe2PMjBWS-jvfGBRBWMt9m4YAFX-iANjsdrC3F6KWDl6RAQeHAHfqw6F-OwGkgx81hHW0ncwKVSl4Hol3-WNFBhOoEXIEENSNwGx2I_/s1600/IMG_4372.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGJ7VjlnfiPy1BBoTF4AT8ntauQROb4nGu4VwiWwe2PMjBWS-jvfGBRBWMt9m4YAFX-iANjsdrC3F6KWDl6RAQeHAHfqw6F-OwGkgx81hHW0ncwKVSl4Hol3-WNFBhOoEXIEENSNwGx2I_/s640/IMG_4372.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">All the Bits and Pieces</td></tr>
</tbody></table>
<br />
First up, I broke off 15 header pins instead of 16 and soldered them into openings 1-15, leaving opening 16 empty; this is labeled "K" on my LCD. According to the <a href="http://www.ladyada.net/learn/lcd/charlcd.html">tutorial at Adafruit</a>, this is the backlight ground connection. I then cut a length of wire, stripped one end and soldered it through opening 16.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvyBHS_3no8QNT5kk-Ya-5y7dm3DDSxnWuXL_Ntgz1Yfy-jMkBjwlg74QBOhv43zJHSIg7FNeKbKm4H_jT6C1uLIHncNirLfApCK52gzFLyEe2e8UMfL0zuTle_XaDCf4TZ2qb-79DmkJu/s1600/IMG_4373.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvyBHS_3no8QNT5kk-Ya-5y7dm3DDSxnWuXL_Ntgz1Yfy-jMkBjwlg74QBOhv43zJHSIg7FNeKbKm4H_jT6C1uLIHncNirLfApCK52gzFLyEe2e8UMfL0zuTle_XaDCf4TZ2qb-79DmkJu/s640/IMG_4373.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Backpack with Pins and Wire Soldered</td></tr>
</tbody></table>
<br />
I "folded" over the wire end and soldered it to the pad on the LCD. This required a little bit of finagling with my third hand's alligator clips, but wasn't too rough.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil36Csnw6GBd9IP2uqiS-p215oki6ilJJQbzEqHRlxFqfTDsii3KaUYY48h7I7M580542QB0c450AvHBuHxyWgL5TvzmEjjYnrQZV8S_51RDyis2NOk_kMbJ8spI34WxcuM7Cj2SR3dWWZ/s1600/IMG_4374.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil36Csnw6GBd9IP2uqiS-p215oki6ilJJQbzEqHRlxFqfTDsii3KaUYY48h7I7M580542QB0c450AvHBuHxyWgL5TvzmEjjYnrQZV8S_51RDyis2NOk_kMbJ8spI34WxcuM7Cj2SR3dWWZ/s640/IMG_4374.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Backlight Ground Wire Soldered to LCD</td></tr>
</tbody></table>
<br />
I swapped two 3-pin terminal blocks for the 2-pin and 3-pin terminal block that came with the LCD backpack<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvBM1RPWB2twm3Rr6iPjDXaYMb-VvbF3OuWQ_z4U7WYc1K33VZxqVuNTyGaU_WA92hqxKNvh-IY_LQerggy98IummhMtzO_4AVBBRCAg8E4_UNhN3OXfV6z77GQ5CEvh4GOZmSn5TTmZAx/s1600/IMG_4375.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvBM1RPWB2twm3Rr6iPjDXaYMb-VvbF3OuWQ_z4U7WYc1K33VZxqVuNTyGaU_WA92hqxKNvh-IY_LQerggy98IummhMtzO_4AVBBRCAg8E4_UNhN3OXfV6z77GQ5CEvh4GOZmSn5TTmZAx/s640/IMG_4375.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Terminal Blocks</td></tr>
</tbody></table>
<br />
I soldered the resulting 6-pin terminal block to the backpack with the extra pin overhanging the PCB on the side.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9j23VqnIDnCvmt45kvjCYWejh220bAZbQT8BKZf2dn1yuCWWUs1Svgt86FHg4jjudlaofK14OEFv9wHoOYtSZvCQCPpV3oOSzsYS_BBztjgC5qhog-yKeHCVf18FByl2cBxWUYx87S4j2/s1600/IMG_4377.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9j23VqnIDnCvmt45kvjCYWejh220bAZbQT8BKZf2dn1yuCWWUs1Svgt86FHg4jjudlaofK14OEFv9wHoOYtSZvCQCPpV3oOSzsYS_BBztjgC5qhog-yKeHCVf18FByl2cBxWUYx87S4j2/s640/IMG_4377.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Terminal Block Soldered in Place</td></tr>
</tbody></table>
<br />
Next up was trimming the wire to fit the length of the backpack PCB and soldering it to the extra pin. This proved to be a bit of a pain, and I ended up with the LCD in a panavise, the backpack held by the third hands, a soldering iron in one hand, and needlenose pliers in the other. I'm sure there's an easier approach...<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrYSW50zALRnQRFxP8K-jPuDQArvPMzaZsD3epeWoZav0ZJEg3o9EuzGXlPWWLFBo0nntsadgwNyPnv5X-lc9oqvImXoTDEDe-LvWu2gkN-Nu6A5DB9PT2spcxt7g9wuyfNoerWixk6hTv/s1600/IMG_4378.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrYSW50zALRnQRFxP8K-jPuDQArvPMzaZsD3epeWoZav0ZJEg3o9EuzGXlPWWLFBo0nntsadgwNyPnv5X-lc9oqvImXoTDEDe-LvWu2gkN-Nu6A5DB9PT2spcxt7g9wuyfNoerWixk6hTv/s640/IMG_4378.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Backlight Ground Wire Soldered to Backpack</td></tr>
</tbody></table>
Finally, I soldered the regular pins in place; overall, I'm happy with the way this turned out. I am thinking of putting some insulation on the exposed extra pin / wire end.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZ2Hj4DMSCJvWOPs-G9-kB1dFqfQ27Iv_bsYFWyPXb4_Cz1W7x-b-beYzzCPPkPU7MLQr0JTxZvcHqfCusqQoU28WbT97DqCvqWOGINEh2iW9as0uMfdwxzANsw3E2HwOcQ1bagpLKo1Ws/s1600/IMG_4380.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZ2Hj4DMSCJvWOPs-G9-kB1dFqfQ27Iv_bsYFWyPXb4_Cz1W7x-b-beYzzCPPkPU7MLQr0JTxZvcHqfCusqQoU28WbT97DqCvqWOGINEh2iW9as0uMfdwxzANsw3E2HwOcQ1bagpLKo1Ws/s640/IMG_4380.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Finished Backpack in Place</td></tr>
</tbody></table>
The code I used to test this is just the Arduino Basics/Fade example sketch and Adafruit's LiquidCrystal/HelloWorld_i2c example sketch mashed together:<br />
<script class="brush: c;" type="syntaxhighlighter">
<![CDATA[
#include "Wire.h"
#include "LiquidCrystal.h"
LiquidCrystal lcd(0);
int brightness = 0;
int fadeAmount = 5;
void setup() {
pinMode(9, OUTPUT);
lcd.begin(20, 4);
lcd.print("hello, world!");
}
void loop() {
lcd.setCursor(0, 1);
lcd.print(millis()/1000);
analogWrite(9, brightness);
brightness = brightness + fadeAmount;
if (brightness == 0 || brightness == 255) {
fadeAmount = -fadeAmount ;
}
delay(300);
}
]]>
</script><br />Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com3tag:blogger.com,1999:blog-7724773768743021813.post-90421994109393280902012-03-11T00:29:00.000-05:002012-03-11T00:29:50.513-05:00Turning Off the LCD at Night<div class="separator" style="clear: both; text-align: left;">
I like the brightness level of <a href="http://www.adafruit.com/products/181">the LCD I'm using</a> on my vivarium project; but, it was giving the living room a distinct glow at night. It turned out to be a quick fix to turn the LCD off when the ambient light level was low. I ordered a <a href="https://www.adafruit.com/products/161">Photocell from Adafruit</a> and followed <a href="http://www.ladyada.net/learn/sensors/cds.html">the tutorial</a> on wiring it up along with a pull-down resistor (the concept of pull-down and pull-up resistors is something I'm just finally getting my head around).</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4EzezHhbAKVwISFEOKj3MpCHxP9TWNOS6aRgMJV5OMbvK0XUZmziiP8WM7-hENmD0RwnguPCQg_X-v1uHU1YTXDgASPIvzdnNBD9LU3NfIegknnwUoHSMdvz3nkFLNHZndCMAVENPLhyM/s1600/photo_cell.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4EzezHhbAKVwISFEOKj3MpCHxP9TWNOS6aRgMJV5OMbvK0XUZmziiP8WM7-hENmD0RwnguPCQg_X-v1uHU1YTXDgASPIvzdnNBD9LU3NfIegknnwUoHSMdvz3nkFLNHZndCMAVENPLhyM/s640/photo_cell.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Photocell and 10K Pulldown Resistor on Pin A3</td></tr>
</tbody></table>
<br />
Then I added a simple check in the loop to test the brightness level:
<script class="brush: c; highlight:[1,7,9,10,15]" type="syntaxhighlighter">
<![CDATA[
#define PHOTOCELL_PIN 3
...
void loop() {
DateTime now = rtc.now();
float humidity = dht.readHumidity();
float temperature = dht.readTemperature() * 1.8 + 32.0;
int ambient_light = analogRead(PHOTOCELL_PIN);
if (ambient_light > 512) {
lcd.setBacklight(HIGH);
print_date_time(&now);
print_humidity(humidity);
print_temperature(temperature);
} else {
lcd.setBacklight(LOW);
}
]]>
</script><br />
For my environment, simply splitting the range in half has worked well.Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-54711147048139186032012-03-08T20:53:00.001-05:002012-03-17T16:08:41.680-05:00Pachube on Adafruit's IoT PrinterI've been meaning to check out <a href="https://pachube.com/">pachube</a> for a while now and it seemed like fiddling with <a href="https://www.adafruit.com/products/717">Adafruit's Internet of Things Printer</a> was a perfect chance. The JSON parsing code from the <a href="https://github.com/adafruit/Adafruit-Tweet-Receipt">Gutenbird sketch</a> lent itself nicely to tweaking for the pachube datastream JSON feed. Pachube was also very easy to get up and running with between the <a href="https://pachube.com/docs/">developer documentation</a>, easy API key generation, and each feed page having buttons to see the various formats (XML, CSV, JSON). The end result is that I can get a print out of air quality data for Boston:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwzvkB1Rv43aVp8lXkw9KeltbFAFXbAuvFhuD_n3HPEnVqKmKPzSb7ioh9lVHdZIe480YMH_7s8vz1BrGFRDgt8Wt6VRgaju-sZEUxNFzxtDXpMLyHQD8J94NgeM3RS23VqoExrh5MuZi-/s1600/pachube_receipt.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwzvkB1Rv43aVp8lXkw9KeltbFAFXbAuvFhuD_n3HPEnVqKmKPzSb7ioh9lVHdZIe480YMH_7s8vz1BrGFRDgt8Wt6VRgaju-sZEUxNFzxtDXpMLyHQD8J94NgeM3RS23VqoExrh5MuZi-/s320/pachube_receipt.jpg" width="272" /></a></div>
(<a href="https://pachube.com/feeds/23716">https://pachube.com/feeds/23716</a> - <i>"Air Quality Index and pollutant data for Boston, Massachusetts webscraped from http://www.airnow.gov/. AIRNow observational data are not fully verified or validated; these data are subject to change and should be considered preliminary. If observational data are used for analyses, displayed on web pages, or used for other programs or products, the analysis results, displays, or products must indicate that these data are preliminary."</i>)<br />
<br />
It's still definitely a work in progress - I'd like to format the dates a bit and there seems to be an intermittent bug with the fetch/parse process. Nonetheless, I've put <a href="https://github.com/dmalec/PatchPress">the code up on github</a> for anyone interested in playing around with it. There are a couple section of code that I think are a bit interesting.<br />
<br />
Adding the pachube API key is as simple as setting one additional header in the HTTP request.
<script class="brush: c; highlight:[6,7]" type="syntaxhighlighter">
<![CDATA[
Serial.print("OK\r\nIssuing HTTP request...");
client->print("GET /v2/feeds/");
client->print(feedId);
client->print(".json");
client->print(" HTTP/1.1\r\nHost: api.pachube.com");
client->print("\r\nX-PachubeApiKey: ");
client->print(apiKey);
client->println("\r\nConnection: close\r\n");
]]>
</script><br />
The pachube feed does contain a last updated field for the feed; unfortunately, it is the last field in the feed. I wanted to stick with (for lack of a better term) the SAX style parsing of the JSON that the original Gutenbird sketch uses rather than do a DOM style load of the data. Each entry does have a timestamp, but I was looking for something that would tell me when the feed as a whole had been updated.<br />
<br />
I ended up looking at the HTTP response header for Last-Modified (I'm considering whether to refactor the code to just store a hash of the value since I'm only using it to decided whether to print the feed or not). If the value has changed, I save it and return true to indicate the feed should be printed.
<script class="brush: c; highlight:[4,7,13,20,23]" type="syntaxhighlighter">
<![CDATA[
boolean PatchPress::checkLastModifiedHeader() {
boolean parsing = true;
int i = 0, c;
char tstamp[25]; // ddd,DDMMMYYYYHH:MM:SSGMT
memset(tstamp, 0, sizeof(tstamp));
if (client->findUntil("Last-Modified:", "\r\n\r\n")) {
while (parsing) {
c = client->peek();
if (c == '\r' || i > 24) {
parsing = false;
} else if (!isspace(c)) {
tstamp[i++] = (char)client->read();
} else {
client->read(); // skip whitespace
}
}
}
if (!strncasecmp(tstamp, lastTstamp, 25)) {
return false;
} else {
strncpy(lastTstamp, tstamp, sizeof(lastTstamp)-1);
return true;
}
}
</script><br />
Looking through the pachube API feed, the structuring of the interesting data is close enough to the twitter feed that reusing the Gutenbird parsing code made a lot of sense to me. As an aside, Jenkins build feeds seem to follow this same "object which contains a single array of interesting shallow objects" (JSON-SAISO anyone? no?):
<script class="brush: javascript; highlight:[4,5,6,7,8,10,11,12,13,14,15,16,17]" type="syntaxhighlighter">
{
"status":"frozen",
...,
"datastreams":[
{
"max_value":"108.0",
"min_value":"1.0",
"at":"2012-03-09T00:38:01.573729Z",
"tags":["air quality index", "pollution"],
"current_value":"36",
"id":"aqi"
},{
"max_value":"106.0",
"min_value":"0.0",
...
}
]
...
"updated":"2012-03-09T00:38:01.609268Z"
}
</script><br />
It took me a couple reads to understand the original code from Gutenbird; but, once I grokked it, I think it's a really elegant approach. It's looking in the JSON for a given name, which will have an array as the value. When in each object, it looks for specific names and saves the values. Finally, at the end of each object, it handles the data for that object.<br />
<br />
I tweaked it as below for the name/value pairs I'm interested in (and I'm using a callback function for printing). Again, very few modifications were needed to switch from parsing twitter to parsing pachube.
<script class="brush: c; highlight:[2,6,7,8,9,10,19,20,28,29,30,31,32,33,34,35,36,37,38]" type="syntaxhighlighter">
<![CDATA[
...
if(depth == datastreamsDepth) { // End of object in results list
// Notify callback of entry data
if (datastreamEntryCallback != NULL) {
(*datastreamEntryCallback)(dataStreamId,
readAt,
minValue,
maxValue,
curValue);
}
// Clear values
memset(readAt, '\0', sizeof(readAt));
memset(dataStreamId, '\0', sizeof(dataStreamId));
minValue = maxValue = curValue = 0.0;
}
} else if(c == '[') { // Array follows
if((!datastreamsDepth) && (!strcasecmp(name, "datastreams")))
datastreamsDepth = depth + 1;
if(!jsonParse(depth + 1,']')) return false;
} else if(c == '"') { // String follows
if(readName) { // Name-reading mode
if(!readString(name, sizeof(name)-1)) return false;
} else { // Value-reading mode
if(!readString(value, sizeof(value)-1)) return false;
// Process name and value strings:
if (!strcasecmp(name, "id")) {
strncpy(dataStreamId, value, sizeof(dataStreamId)-1);
} else if(!strcasecmp(name, "at")) {
strncpy(readAt, value, sizeof(readAt)-1);
} else if(!strcasecmp(name, "min_value")) {
minValue = atof(value);
} else if(!strcasecmp(name, "max_value")) {
maxValue = atof(value);
} else if(!strcasecmp(name, "current_value")) {
curValue = atof(value);
}
...
</script><br />
I'm curious to see what data feeds other IoT owners will find interesting. I'm guessing that as time passes, there'll be more and more sketches available to track different data.<br />
<br />
<b>[Updated]</b><br />
Welcome Adafruit and Flickr readers - here's a little bonus content :)<br />
<br />
The same feed as above, but with swanky outlining:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFKaMtVoAYRXhSaLNqufVizOi6RAIi8RXfREybnTfvCis0urnskWszo9K7xxCIbJjrV7JLDgQ4kDwsw2AViUc37s-PHnWERNtTbYjspckO60tzfbuakdQxOtb634nlcoRw0ssqm6bUe5qS/s1600/bonus_receipt.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFKaMtVoAYRXhSaLNqufVizOi6RAIi8RXfREybnTfvCis0urnskWszo9K7xxCIbJjrV7JLDgQ4kDwsw2AViUc37s-PHnWERNtTbYjspckO60tzfbuakdQxOtb634nlcoRw0ssqm6bUe5qS/s320/bonus_receipt.jpg" width="204" /></a></div>
<br />
And the relevant code changes to printEntryToPrinter :<br />
<br />
<script class="brush: c;" type="syntaxhighlighter">
<![CDATA[
void printEntryToPrinter(char *dataStreamId, char *tstamp, double minValue, double maxValue, double curValue) {
int i, len;
char buffer[12];
// Output to printer
printer.wake();
delay(1000);
printer.write(0xC9);
for (i=0; i<30; i++) printer.write(0xCD);
printer.write(0xBB);
printer.write(0xBA);
printer.write(' ');
printer.print(dataStreamId);
for(i=strlen(dataStreamId); i<29; i++) printer.write(' ');
printer.write(0xBA);
printer.write(0xC7);
for (i=0; i<30; i++) printer.write(0xC4);
printer.write(0xB6);
printer.write(0xBA);
printer.write(' ');
printer.print(tstamp);
for(i=strlen(tstamp); i<29; i++) printer.write(' ');
printer.write(0xBA);
printer.write(0xC7);
for (i=0; i<30; i++) printer.write(0xC4);
printer.write(0xB6);
len = 9;
printer.write(0xBA);
printer.write(' ');
dtostrf(curValue, 4, 2, buffer);
len += strlen(buffer);
printer.print(buffer);
printer.print(" (");
dtostrf(minValue, 4, 2, buffer);
len += strlen(buffer);
printer.print(buffer);
printer.print(" - ");
dtostrf(maxValue, 4, 2, buffer);
len += strlen(buffer);
printer.print(buffer);
printer.write(')');
for (i=len; i<32; i++) printer.write(' ');
printer.write(0xBA);
printer.write(0xC8);
for (i=0; i<30; i++) printer.write(0xCD);
printer.write(0xBC);
printer.feed(1);
printer.sleep();
}
</script>
<br />
And setup :<br />
<br />
<script class="brush: c; highlight:[8]" type="syntaxhighlighter">
<![CDATA[
void setup() {
...
// Set up the printer
pinMode(printer_Ground, OUTPUT);
digitalWrite(printer_Ground, LOW); // Just a reference ground, not power
printer.begin();
printer.setLineHeight(24);
printer.sleep();
...
}
</script>Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-37170129444108744372012-03-03T09:53:00.000-05:002012-03-08T13:04:32.762-05:00Arduino NG upgrade<div class="separator" style="clear: both; text-align: left;">
I've updated my Arduino NG by adding auto reset and an Atmega328P chip; it definitely breathed new life into an old board that was just gathering dust. The <a href="http://www.adafruit.com/products/123">Atmega328P from Adafruit</a> was a direct swap as advertised. While the main Arduino site's hacking section has <a href="http://arduino.cc/en/Hacking/NGAutoReset">instructions on adding auto-reset</a> by soldering on a 0.1uF capacitor, many people have had better luck using the two upper pins. A good overview of the difference is presented in <a href="http://www.libelium.com/squidbee/index.php?title=Adding_autoreset_feature_to_Arduino_NG_rev.c">this wiki entry</a> (the short form is that it depends on how your Arduino IDE / computer handles DTR and RTS when opening a serial connection). OSX 10.6 / Arduino 1.0 seems fine with the upper pins. I now have an Arduino NG capable of running an Ethernet Shield and an LCD Shield (and I don't need to pry off shields to upload new code) for about 7 dollars.</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJHJ5G1ridr19Hgrc-B5biNcM6lp3BwAtVIk-Ex5pn8HEgY3b89Q7YmN0OvHOkskY6lUfWpWXzsbnfiBfJHQrwxyf7i9aI2VLujD9oCxNpfBmMmvht3DGxvc90PkGbV2dxbZLKBkIXryGL/s1600/IMG_4350.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="446" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJHJ5G1ridr19Hgrc-B5biNcM6lp3BwAtVIk-Ex5pn8HEgY3b89Q7YmN0OvHOkskY6lUfWpWXzsbnfiBfJHQrwxyf7i9aI2VLujD9oCxNpfBmMmvht3DGxvc90PkGbV2dxbZLKBkIXryGL/s640/IMG_4350.jpg" width="640" /></a></div>
<br />Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-39277160051861713482012-02-26T23:09:00.000-05:002012-03-12T10:33:12.319-05:00Weather Ticker Using Adafruit's Internet of Things Printer<div class="" style="clear: both; text-align: left;">
We built the <a href="http://www.adafruit.com/products/717">Adafruit Internet of Things Printer</a> last week and, after playing around with it a bit, we settled on tracking a <a href="http://twitter.com/#!/boston_weather">twitter feed of local weather</a> (this has lent itself to all sorts of jokey comments about it not really being whatever outside until the twitter feed through the printer confirms it).</div>
<div class="" style="clear: both; text-align: left;">
<br /></div>
<div class="" style="clear: both; text-align: left;">
However, it also turns out to be a surprisingly clever UI. We correlated people getting headaches with the barometric pressure changing over the weekend. Could we see this by looking at the twitter feed on a computer? Sure - but we've never tracked the weather that way before. There's something very natural feeling about a length of paper with points in time along it (blame it on growing up with timelines in classrooms).</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsbwhMWFazdV4iRhj0g_6GEWIZMjjBPe1K6rAFzr6V0PIhFEDJqcyMzoPmpn0rGSwEhXQJza_RCGaUnfgjci7fk3rOphQI5Mu_LpLX0JdoMHnFMoXfG_CSrngDakkatdfX-rlJcaGX6kfk/s1600/iot-weather.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="539" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsbwhMWFazdV4iRhj0g_6GEWIZMjjBPe1K6rAFzr6V0PIhFEDJqcyMzoPmpn0rGSwEhXQJza_RCGaUnfgjci7fk3rOphQI5Mu_LpLX0JdoMHnFMoXfG_CSrngDakkatdfX-rlJcaGX6kfk/s640/iot-weather.jpg" width="640" /></a></div>Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-61423175454103166552012-02-22T22:49:00.001-05:002012-03-03T12:37:48.482-05:00Graphing Vivarium Humidity and Temperature Using RRDTool<style type="text/css">
div.codewalk pre {
font-weight: bold;
}
</style>
The temperature and humidity monitoring of my vivarium is really coming together. I'm recording a timestamp, humidity, and temperature to an SD card every minute using <a href="http://www.adafruit.com/products/254">Adafruit Industries' SD card breakout board</a>:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKzh5LXtHYGlUBI9BWPfZu8lHJkmTr6PXzlp1wPE4fEPD6_1Bc5TITbQt1WHQUwl6orcBsmqPis5z-JQAynY41QHmdKPAhNNbwrd8GCuEqN-ZpBCNpjEOSi9Ii_iWIFkl6xIO6hs49nq1T/s1600/BoardCapture.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="414" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKzh5LXtHYGlUBI9BWPfZu8lHJkmTr6PXzlp1wPE4fEPD6_1Bc5TITbQt1WHQUwl6orcBsmqPis5z-JQAynY41QHmdKPAhNNbwrd8GCuEqN-ZpBCNpjEOSi9Ii_iWIFkl6xIO6hs49nq1T/s640/BoardCapture.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Components on breadboard</td></tr>
</tbody></table>
<br />
The SD card is sneaker netted to a computer running <a href="http://oss.oetiker.ch/rrdtool/">RRDtool</a> and used to produce graphs to give a sense of how the environment looks across the day. The temperature nicely lines up with what I'd expect given the way the thermostat on the house heating is set up:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhduafowOZZSZ598bAj174vUkP1VUFNVhNBuS_QXzEzzZ1b7G5kSWsNlbL9oxlEWMjrER_uReLKjnlQuFCX_UyNd6o__4u63hyphenhyphenVbebGnkEAb2Ilx22ifad-1DnQhyphenhyphenkTa-FAs5h_mksbnTUo/s1600/vivarium-temp.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhduafowOZZSZ598bAj174vUkP1VUFNVhNBuS_QXzEzzZ1b7G5kSWsNlbL9oxlEWMjrER_uReLKjnlQuFCX_UyNd6o__4u63hyphenhyphenVbebGnkEAb2Ilx22ifad-1DnQhyphenhyphenkTa-FAs5h_mksbnTUo/s640/vivarium-temp.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">24 Hours of Temperature Data</td></tr>
</tbody></table>
<br />
<a name='more'></a><br />
<br />
On the other hand, the humidity seems to wander quite a bit more than I'd have expected. I'll be tuning my RRD settings and generally playing around with the set up to make sure this isn't being introduced by how I'm collecting data:<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAYBNKk7ZknBnAtOUhwc2C6UMXSHU0GTCiZH3Lrm-y3w9XmTYAJ-MXWjVvULSGNvy152JBPncawxbjIou_vMitlWBrkZuNlaKlTNf3Nef2tnIvacVXyoHlL-wgJCvPlU1tw5cOXGIu3eTF/s1600/vivarium-humidity.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAYBNKk7ZknBnAtOUhwc2C6UMXSHU0GTCiZH3Lrm-y3w9XmTYAJ-MXWjVvULSGNvy152JBPncawxbjIou_vMitlWBrkZuNlaKlTNf3Nef2tnIvacVXyoHlL-wgJCvPlU1tw5cOXGIu3eTF/s640/vivarium-humidity.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">24 Hours of Humidity Data</td></tr>
</tbody></table>
And all together:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOCjSjAzZSS_9Qnz4mgeST6wlgVxzcJJclHP3TZZx-v0uwP7en_ghBmT3vQ_J6BB2Ixh-8h4LNg3kDdkk6xnw9taMbYsMkabzHlnvpADdvXPexYiqvV_RwYyDLkXed4fMsBi6HNXmFuN48/s1600/vivarium-temp-humid.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="370" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOCjSjAzZSS_9Qnz4mgeST6wlgVxzcJJclHP3TZZx-v0uwP7en_ghBmT3vQ_J6BB2Ixh-8h4LNg3kDdkk6xnw9taMbYsMkabzHlnvpADdvXPexYiqvV_RwYyDLkXed4fMsBi6HNXmFuN48/s640/vivarium-temp-humid.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">24 Hours of Humidity and Temperature Data</td></tr>
</tbody></table>
<span style="font-size: x-large;">Wiring</span><br />
I did shuffle the Boarduino to the center section of my breadboard to get some space on the side for the SD card breakout board. The design of this breakout is really nice for adding to an Arduino or Boarduino as the pins line up 1-to-1. I haven't played around with the Card Detect pin yet, but at some point I plan to try it out.<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisnXwYUblWdhTxYmPvvFz3kF5KzqMZD5iAkHWo4hGkDnPfaeneT6w4Bv_M9s8p2iIl5Z_N1b3eMUPpSPmgKSm1DWJE9g70ldaDnaSvd4JG9jeImIX4uk6mfGW7zaIPID2f6RR1rjz4n8U1/s1600/SDCardZoom.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="273" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisnXwYUblWdhTxYmPvvFz3kF5KzqMZD5iAkHWo4hGkDnPfaeneT6w4Bv_M9s8p2iIl5Z_N1b3eMUPpSPmgKSm1DWJE9g70ldaDnaSvd4JG9jeImIX4uk6mfGW7zaIPID2f6RR1rjz4n8U1/s640/SDCardZoom.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Zoomed View of the Connection Between the Boarduino and Adafruit SD Card Breakout Board</td></tr>
</tbody></table>
<br />
<div>
<span style="font-size: large;"></span><br />
<span style="font-size: large;"></span><br />
<span style="font-size: large;"><br /></span><br />
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfGenbq4WxSrfaqf-XFaVxtpCUXVuhRbiYgcBHoB_2bi-Oq_rBV9BOCcKpcQiPjD1X7flgvnEFwEKuiwFrFtfOMq4X9IKgxlwi6UFnr5ZZkJ6mCc2AIL_LGK89H4fTKYhddaU4JzOdL82c/s1600/ssd_card.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="458" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfGenbq4WxSrfaqf-XFaVxtpCUXVuhRbiYgcBHoB_2bi-Oq_rBV9BOCcKpcQiPjD1X7flgvnEFwEKuiwFrFtfOMq4X9IKgxlwi6UFnr5ZZkJ6mCc2AIL_LGK89H4fTKYhddaU4JzOdL82c/s640/ssd_card.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Components with Logical Connections</td></tr>
</tbody></table>
<span style="font-size: large;"><br /></span><br />
<span style="font-size: x-large;">Arduino Code</span><br />
<br />
At startup, configure the SD card pins and set up related variables
<script class="brush: c" type="syntaxhighlighter">
<![CDATA[
#define DEFAULT_SD_CARD_CS_PIN 10
#define ACTUAL_SD_CARD_CS_PIN 10
void setup() {
...
// Initialize the SD Card if possible and set a
// flag to track success.
pinMode(DEFAULT_SD_CARD_CS_PIN, OUTPUT);
if (SD.begin(ACTUAL_SD_CARD_CS_PIN)) {
sd_available = true;
} else {
sd_available = false;
}
// Initialize a variable to track if anything
// has been logged this minute.
last_log_minute = 0;
}
]]>
</script><br />
Once a minute, log the timestamp and the current sensor readings
<script class="brush: c" type="syntaxhighlighter">
<![CDATA[
void loop() {
DateTime now = rtc.now();
float humidity = dht.readHumidity();
float temperature = dht.readTemperature() * 1.8 + 32.0;
...
// If the SD card is available, and we haven't logged anything
// yet this minute, log data to the SD card and update the minute.
if (sd_available && now.minute() != last_log_minute) {
log_rrd_data(&now, temperature, humidity);
last_log_minute = now.minute();
}
delay(100);
}
]]>
</script><br />
Log the readings and timestamp to a file on the SD card
<script class="brush: c" type="syntaxhighlighter">
<![CDATA[
#define DATA_FILE_NAME "vivarium.txt"
...
void log_rrd_data(DateTime *now, float temperature, float humidity) {
char buffer[10];
File dataFile = SD.open(DATA_FILE_NAME, FILE_WRITE);
dataFile.print("t0:h0 ");
dataFile.print(now->unixtime());
dataFile.print(':');
dtostrf(temperature, 3, 2, buffer);
dataFile.print(String(buffer));
dataFile.print(':');
dtostrf(humidity, 3, 2, buffer);
dataFile.println(String(buffer));
dataFile.close();
}
]]>
</script><br />
<br />
<span style="font-size: large;">Full source code is available on github:</span><br />
<span style="font-size: large;"><a href="http://draft.blogger.com/goog_733998079">https://github.com/dmalec/Project-Masdev/tree/master/masdev_sdcard</a></span><a href="https://github.com/dmalec/Project-Masdev/tree/master/masdev_sdcard"> </a><br />
<br />
<span style="font-size: x-large;">RRDtool</span><br />
It's worth noting that there are much more sophisticated ways to use RRDtool than what follows and that the setup below only tracks one day worth of data (E.G. it's very likely that during import later data from the SD card will overwrite earlier data from the SD card, since it's unlikely one will be meticulous about dumping the card data to RRD after exactly 24 hours). All that said, this is a first iteration, and the future of the vivarium will hopefully include a more advanced setup.<br />
<br />
<span style="font-size: large;">Creating the RRDtool Database</span>
<script class="brush: shell" type="syntaxhighlighter">
<![CDATA[
rrdtool create vivarium.rrd \
--start 1329597420 \
--step 60 \
DS:h0:GAUGE:300:0:100 \
DS:t0:GAUGE:300:0:100 \
RRA:AVERAGE:0.5:1:1440
]]>
</script>
<br />
<div class="codewalk">
<pre>rrdtool create vivarium.rrd \</pre>
Create the database in a file named vivarium.rrd
<br />
<pre>--start 1329597420 \</pre>
Specify that the first time point in the database is 1329597420. This is in <a href="http://en.wikipedia.org/wiki/Unix_time">unix time</a> which, broadly, is the number of seconds which have elapsed between midnight on January 1st, 1970 and the time you want to specify as the earliest potential point in the database.<br />
<pre>--step 60 \</pre>
Specify the number of seconds expected between individual data readings. In this case, I'm expecting to take a reading every minute from the two sensors.
<br />
<pre>DS:h0:GAUGE:300:0:100 \</pre>
Set up the humidity data source. It will be called h0 and is of type GAUGE, which is good for values that vary over time. If 300 seconds pass without receiving a new value, RRD will consider the value to be unknown (if less time passes, RRD will generate interpolated data to cover the gap). 0 is the minimum expected value. 100 is the maximum expected value.
<br />
<pre>DS:t0:GAUGE:300:0:100 \</pre>
Set up the temperature data source. It will be called t0 and is otherwise a straight copy of the humidity sensor. This could cause issues if the reading goes below 0 or above 100, but in those cases I'll probably have bigger concerns :).
<br />
<pre>RRA:AVERAGE:0.5:1:1440</pre>
Set up the archive for the above datasources to track the average value. 0.5 specifies that half of the recorded data points may be marked as unknown before the stored value is marked unknown (I'm pretty sure this is currently irrelevant since I'm inputting data at the same rate that RRD is storing it - if I start reading at a higher rate than once a minute, and tweak the 300 above to be something less than 60, it should start to be relevant -- but take this with a grain of salt as it's pure speculation). Use 1 recorded data point to generate 1 stored data point. Store 1440 seconds (1 day) worth of data.
</div>
<br />
<br />
<span style="font-size: large;">Importing the Sensor Data into the RRDtool Database</span><br />
My thinking when writing the code for the Arduino was that I could walk the file line by line and append it to an rrdtool update command. This kept producing graphs which were off by 5 hours... it took an embarrassingly amount of time for me to realize that was suspiciously the same as my UTC offset. The Arduino has no concept of local vs. UTC time - so unixtime on my Arduino and on my computer are not the same thing. There's some magic going on below to split the line up, add the appropriate offset, and then join the line. This relies on the computer which originally set the time on the Arduino and the computer running RRD to have the same UTC offset. It also suggests I should rethink my approach.<script class="brush: ruby" type="syntaxhighlighter">
<![CDATA[
#!/usr/bin/ruby
# Save the local UTC offset
utc_offset = Time.now.utc_offset
# Walk the Arduino data file line by line
dataFile = File.new("VIVARIUM.TXT")
dataFile.each do |line|
# Split the file line into the sensor names and the timestamp/data section
fields = line.split(' ')
# Split the timestamp/data section into individual data fields
data = fields[1].split(':')
# Correct the UTC timestamp field
utc_time = data[0].to_i - utc_offset
data[0] = utc_time.to_s
# Rebuild the line with the needed delimeters
line = fields[0] + ' ' + data.join(':')
# Invoke rrdtool update, appending the full line
`rrdtool update vivarium.rrd -t #{line.strip}`
end
]]>
</script><br />
<br />
<span style="font-size: large;">Creating an RRDtool Graph</span>
<script class="brush: shell;" type="syntaxhighlighter">
<![CDATA[
#!/bin/sh
# Get the timestamp of the earliest datapoint in the RRD file
START=`/opt/rrdtool/bin/rrdtool first vivarium.rrd`
rrdtool graph vivarium-humidity.png \
--width 1024 --height 512 \
--start $START --end start+24h --step 60 \
--title "Vivarium Humidity" \
--vertical-label "Relative Humidity"
--watermark "Generated on `date`" \
--color CANVAS#FFFCED \
--color BACK#E8E1CC \
--color ARROW#000000 \
DEF:my_humid=vivarium.rrd:h0:AVERAGE:step=60 \
VDEF:max_humid=my_humid,MAXIMUM \
VDEF:min_humid=my_humid,MINIMUM \
CDEF:smooth_humid=my_humid,300,TREND \
LINE6:smooth_humid#1A1A40:"Avg" \
LINE4:smooth_humid#39398C \
LINE2:smooth_humid#5353CC \
LINE4:max_humid#66281A::dashes \
LINE2:max_humid#B2462E:"Max":dashes \
LINE4:min_humid#2A616B::dashes \
LINE2:min_humid#48A5B8:"Min":dashes \
COMMENT:"\n" \
GPRINT:my_humid:AVERAGE:"Avg\: \t%3.2lf%s\n" \
GPRINT:my_humid:MAX:"Max\: \t%3.2lf%s\n" \
GPRINT:my_humid:MIN:"Min\: \t%3.2lf%s\n"
]]>
</script>
<br />
<div class="codewalk">
<pre>rrdtool graph vivarium-humidity.png \</pre>
Specify the filename of the image to create.
<br />
<pre>--width 1024 --height 512 \</pre>
Set the area in pixels of the graphing area in the image.
<br />
<pre>--start $START --end start+24h --step 60 \</pre>
The first time point in the graph is set to the earliest timestamp in the RRD file.<br />
The end time point in the graph is set to the first time point plus 24 hours.<br />
Specify 60 seconds between datapoints.
<br />
<pre>--title "Vivarium Humidity"</pre>
Set the title at the top of the image.
<br />
<pre>--vertical-label "Relative Humidity" \</pre>
Set the text to draw along the left of the image
<br />
<pre>--watermark "Generated on `date`" \</pre>
Set text at the bottom of the image and include the current system time
<br />
<pre>--color CANVAS#FFFCED \</pre>
Set the background color of the graphing area to #FFFCED.
<br />
<pre>--color BACK#E8E1CC \</pre>
Set the background color of the border around the graphing area to #E8E1CC.
<br />
<pre>--color ARROW#000000 \</pre>
Set the color of the arrows at the ends of the axes to #000000
<br />
<pre>DEF:my_humid=vivarium.rrd:h0:AVERAGE:step=60 \</pre>
Define a variable, which varies over time, my_humid from the file name 'vivarium.rrd' and template name 'h0'. 'AVERAGE' specifies that data should be averaged if needed to match the graph. The granularity of the data is specified as 60 seconds.
<br />
<pre>VDEF:max_humid=my_humid,MAXIMUM \</pre>
Define a single value variable max_humid based on the maximum value of my_humid.
<br />
<pre>VDEF:min_humid=my_humid,MINIMUM \</pre>
Define a single value variable min_humid based on the minimum value of my_humid.
<br />
<pre>CDEF:smooth_humid=my_humid,300,TREND \</pre>
Define a variable, which varies over time, smooth_humid based on a sliding window average of my_humid across a 5 minute window.
<br />
<pre>LINE6:smooth_humid#1A1A40:"Avg" \
LINE4:smooth_humid#39398C \
LINE2:smooth_humid#5353CC \</pre>
Draw three lines, one on top the other, to produce a "single" line with a gradient effect. The first 6 pixel line is drawn with the darkest color #1A1A40 to get a strong outline and appears in the legend as "Avg". The later lines are, respectively, 4 and 2 pixels thick.
<br />
<pre>LINE4:max_humid#66281A::dashes \
LINE2:max_humid#B2462E:"Max":dashes \</pre>
Draw two lines, one on top the other, to produce a "single" line with an outline effect. The first 4 pixel line is drawn with the darkest color #66281A to get a strong outline. Both lines are drawn as dashed lines to differentiate from the variable line.
<br />
<pre>LINE4:min_humid#2A616B::dashes \
LINE2:min_humid#48A5B8:"Min":dashes \</pre>
Very similar to the maximum line, but tracking the min_humid.
<br />
<pre>COMMENT:"\n" \</pre>
Force a line break after the color labels in the legend.
<br />
<pre>GPRINT:my_humid:AVERAGE:"Avg\: \t%3.2lf%s\n" \
GPRINT:my_humid:MAX:"Max\: \t%3.2lf%s\n" \
GPRINT:my_humid:MIN:"Min\: \t%3.2lf%s\n"</pre>
Print min/max/avg for my_humid in the legend. The format string escapes : with \: so it's not interpreted as a field delimiter. \t forces the right hand side to line up at the next tab stop. %3.2lf specifies that the floating values should be printed with 3 digits, a decimal point, and then 2 digits. \n forces a line break after each field.
</div>
<div>
<span style="font-size: large;"><br /></span></div>
<span style="font-size: x-large;">Bill of Materials</span><br />
<table border="1" cellpadding="5px" style="background-color: #eeeeff; border-collapse: collapse; margin: auto; width: 100%;">
<thead>
<tr>
<th>Description</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td style="background-color: #fffbce; font-weight: bold;"><a href="http://www.adafruit.com/products/254">Adafruit MicroSD Card Breakout Board</a></td>
<td style="background-color: #fffbce; font-weight: bold;">$15.00</td>
</tr>
<tr>
<td style="background-color: #fffbce; font-weight: bold;"><a href="http://www.amazon.com/gp/product/B00181BHQ6/">Kingston 2GB MicroSD Flash Card</a></td>
<td style="background-color: #fffbce; font-weight: bold;">$8.99</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/393">Adafruit AM2302 (Wired DHT22) Temperature-Humidity Sensor</a></td>
<td>$15.00</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/72">Adafruit Boarduino</a></td>
<td>$17.50</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/284">Adafruit FTDI To USB Adapter</a></td>
<td>$14.75</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/181">16x2 LCD</a></td>
<td>$9.95</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/292">Adafruit LCD Backpack</a></td>
<td>$10.00</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/264">Adafruit Real Time Clock (DS1307) Breakout Board</a></td>
<td>$9.00</td>
</tr>
<tr>
<td><a href="http://www.amazon.com/gp/product/B000VE7GQQ">12V 6A Power Supply</a></td>
<td>$8.62</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td>$108.81</td>
</tr>
</tfoot>
</table>
<br />
<div>
<span style="font-size: large;"><br /></span></div>Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-85064535103630956012012-02-12T22:50:00.000-05:002012-02-26T22:15:24.488-05:00Tracking Humidity and Temperature with an ArduinoHaving <a href="http://danmalec.blogspot.com/2012/02/building-arduino-lcd-clock.html">added a realtime clock to my Boarduino,</a> I'm now on to measuring the humidity and temperature inside the vivarium. This iteration displays the current reading along with the time on the LCD. Eventually, I plan to capture the data so I can graph how these are changing over the course of the day.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7CFgGD0TkB5fCblWYV8ZetsxLWvLECU7SQEw8ggTU5gM-hY8rkyIrZeTPA6Avgh-d100mX2RufZbuPPCC3TZ1mke2_g8Q_fGrtzDsUQsKEE80wwACQc4qTeON6ushREKXsqthfvGE3a43/s1600/masdev_sensor.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="332" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7CFgGD0TkB5fCblWYV8ZetsxLWvLECU7SQEw8ggTU5gM-hY8rkyIrZeTPA6Avgh-d100mX2RufZbuPPCC3TZ1mke2_g8Q_fGrtzDsUQsKEE80wwACQc4qTeON6ushREKXsqthfvGE3a43/s640/masdev_sensor.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Masdevallia with AM2302 Temperature / Humidity Sensor</td></tr>
</tbody></table>
<span style="font-size: large;">Incremental Bill of Materials</span><br />
New to this step is a temperature / humidity sensor from <a href="http://www.adafruit.com/">Adafruit Industries</a>. I went with the AM2302 since I think the DHT22 will give a reasonable amount of accuracy and I liked the larger plastic housing. I did end up soldering a scrap length of CAT5 cable to the sensor's wires to make the wires long enough to get out of the vivarium to the board.<br />
<br />
<table border="1" cellpadding="5px" style="background-color: #eeeeff; border-collapse: collapse; margin: auto; width: 100%;">
<thead>
<tr>
<th>Description</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://www.adafruit.com/products/393">AM2302 (Wired DHT22) Temperature-Humidity Sensor</a></td>
<td>$15.00</td></tr>
</tbody></table>
<span style="font-size: x-small;">(The full bill of materials is provided at the end of this post)</span><br />
<br />
<span style="font-size: large;">Wiring</span><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2tEUiD3G7UlVI3H7fBbI2XySb3UoRAw2H8qLGlBxyZnJXqdtyIOLGeFxSO_PU9OGzwn0GprXQwqvqgc15aNKPxN0ETyKAEjOzEWPL5SBkS7Yzc0qU9gDL6NaMuJ7d3yo536ipDmRb7faM/s1600/am2302.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="545" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2tEUiD3G7UlVI3H7fBbI2XySb3UoRAw2H8qLGlBxyZnJXqdtyIOLGeFxSO_PU9OGzwn0GprXQwqvqgc15aNKPxN0ETyKAEjOzEWPL5SBkS7Yzc0qU9gDL6NaMuJ7d3yo536ipDmRb7faM/s640/am2302.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Components with Logical Connections</td></tr>
</tbody></table>
<br />
<ol>
<li>The AM2302 runs on 5V along with the LCD and RTC.</li>
<li>The ground line is connected to common ground.</li>
<li>The data line is connected to digital pin 2 on the Boarduino.</li>
</ol>
<br />
<span style="font-size: large;">Code</span><br />
Initial setup of the sensor involves creating a new DHT object and passing in the data pin and sensor type as parameters.
<script class="brush: c" type="syntaxhighlighter">
<![CDATA[
#define DHT_SENSOR_PIN 2
DHT dht(DHT_SENSOR_PIN, DHT22);
void setup() {
// initialize the DHT
dht.begin();
}
]]>
</script><br />
Each pass through the loop, read the values from the sensor. The sensor only produces readings every two seconds, so this might be better reduced to only reading every 5 seconds or some other arbitrary interval.
<script class="brush: c" type="syntaxhighlighter">
<![CDATA[
void loop() {
print_humidity(dht.readHumidity());
print_temperature(dht.readTemperature());
delay(100);
}
]]>
</script><br />
Full source code is available on github:<br />
<a href="https://github.com/dmalec/Project-Masdev/tree/master/masdev_temp_humid">https://github.com/dmalec/Project-Masdev/tree/master/masdev_temp_humid</a><br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZ_EuJ9ZeIh0xOLU69K1fgpCVUSfog3JbG6enHQIgst6Vo2d4tnAp1BfnIF-3RTdjyJyEyTvFmuWzta_VYTrgafmZP7SGQqcwFFx-rNJJItpCXi5DtxZO776y4kWCBoPtagYOtmA9ns_fH/s1600/clock_temp_humid.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="286" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZ_EuJ9ZeIh0xOLU69K1fgpCVUSfog3JbG6enHQIgst6Vo2d4tnAp1BfnIF-3RTdjyJyEyTvFmuWzta_VYTrgafmZP7SGQqcwFFx-rNJJItpCXi5DtxZO776y4kWCBoPtagYOtmA9ns_fH/s640/clock_temp_humid.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">LCD Showing Time, Humidity, & Temperature</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: center;">
</div>
<span style="font-size: large;"><a href="http://draft.blogger.com/blogger.g?blogID=7724773768743021813" name="full_bom">Full Bill of Materials</a></span><br />
<table border="1" cellpadding="5px" style="background-color: #eeeeff; border-collapse: collapse; margin: auto; width: 100%;">
<thead>
<tr>
<th>Description</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td style="background-color: #fffbce; font-weight: bold;"><a href="http://www.adafruit.com/products/393">AM2302 (Wired DHT22) Temperature-Humidity Sensor</a></td>
<td style="background-color: #fffbce; font-weight: bold;">$15.00</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/72">Boarduino</a></td>
<td>$17.50</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/284">FTDI To USB Adapter</a></td>
<td>$14.75</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/181">16x2 LCD</a></td>
<td>$9.95</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/292">LCD Backpack</a></td>
<td>$10.00</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/264">Real Time Clock (DS1307)</a></td>
<td>$9.00</td>
</tr>
<tr>
<td><a href="http://www.amazon.com/gp/product/B000VE7GQQ">12V 6A Power Supply</a></td>
<td>$8.62</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td>$84.82</td>
</tr>
</tfoot>
</table>
<br/><br/>
The next step is <a href="http://danmalec.blogspot.com/2012/02/recording-humidity-and-temperature-in.html">recording the temperature and humidity readings to an SD card</a> and graphing the data using RRDTool.Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com2tag:blogger.com,1999:blog-7724773768743021813.post-61860530344039244902012-02-01T23:38:00.000-05:002012-02-22T22:47:19.361-05:00Building an Arduino LCD ClockThe first step in my Arduino monitored / controlled orchid vivarium is giving my Arduino the ability to track time with reasonable accuracy and display information on an LCD. I went with a Boarduino for ease of prototyping, but the final build will probably use an Uno or Mega.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRcZrJkd6NHh0bx3hxd5D6WrmruaDqTtohfZnISavkldokJQJBpuoECjyGy7hgV60lsRR55NL-PliehUPaY6ZXhzGnNOj1n4DgSz3kMuhKB04eHPYu5GemrydqgkGBwewV0uL-GFEKEs0U/s1600/Masdev_LCD_Clock_Proto.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="281" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRcZrJkd6NHh0bx3hxd5D6WrmruaDqTtohfZnISavkldokJQJBpuoECjyGy7hgV60lsRR55NL-PliehUPaY6ZXhzGnNOj1n4DgSz3kMuhKB04eHPYu5GemrydqgkGBwewV0uL-GFEKEs0U/s640/Masdev_LCD_Clock_Proto.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Components as built on a breadboard</td></tr>
</tbody></table>
<span style="font-size: large;">Bill of Materials</span><br />
A lot of my supplies were bought from <a href="http://www.adafruit.com/">Adafruit Industries</a>. I've been very pleased with everything I've ordered from them and the tutorials/instructions are excellent. I bought an LCD power supply on Amazon because eventually I'll want to run 12V computer case fans.<br />
<br />
<table border="1" cellpadding="5px" style="background-color: #eeeeff; border-collapse: collapse; margin: auto; width: 100%;">
<thead>
<tr>
<th>Description</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://www.adafruit.com/products/72">Boarduino</a></td>
<td>$17.50</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/284">FTDI To USB Adapter</a></td>
<td>$14.75</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/181">16x2 LCD</a></td>
<td>$9.95</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/292">LCD Backpack</a></td>
<td>$10.00</td>
</tr>
<tr>
<td><a href="http://www.adafruit.com/products/264">Real Time Clock (DS1307)</a></td>
<td>$9.00</td>
</tr>
<tr>
<td><a href="http://www.amazon.com/gp/product/B000VE7GQQ">12V 6A Power Supply</a></td>
<td>$8.62</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td>$69.82</td>
</tr>
</tfoot>
</table>
<br />
<div>
<span style="font-size: large;">Wiring</span><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhX09ilGbASh5LPaRKRmQ0lTeK5YFhF935AigLxXLqx53H1PNAcahz2YAXaf6NJnD30lkZb0g1TjGn9gJmUD2ocD1ZjobXyOXRev5r5xIHHwuz77SF1MfcNL8YoSu5VRC8u6_zOv3RAuBr5/s1600/lcd_rtc.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="420" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhX09ilGbASh5LPaRKRmQ0lTeK5YFhF935AigLxXLqx53H1PNAcahz2YAXaf6NJnD30lkZb0g1TjGn9gJmUD2ocD1ZjobXyOXRev5r5xIHHwuz77SF1MfcNL8YoSu5VRC8u6_zOv3RAuBr5/s640/lcd_rtc.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Components with logical connections </td></tr>
</tbody></table>
<br />
<ol>
<li>The 5V pin of the Boarduino is connected to the 5v pin of the DS1307 breakout and the 5v terminal of the LCD backpack.</li>
<li>The ground pin of the Boarduino is connected to the ground pin of the DS1307 breakout and the ground terminal of the LCD backpack.</li>
<li>The A4 pin of the Boarduino is connected to the SDA pin of the DS1307 breakout and the DAT terminal of the LCD backpack.</li>
<li>The A5 pin of the Boarduino is connected to the SCL pin of the DS1307 breakout and the CLK terminal of the LCD backpack.</li>
</ol>
<br />
<span style="font-size: large;">Code</span><br />
I've created a github repo to track the project in general:<br />
<a href="https://github.com/dmalec/Project-Masdev">https://github.com/dmalec/Project-Masdev</a><br />
<br />
The sketch specific to the LCD RTC clock is available here:<br />
<a href="https://github.com/dmalec/Project-Masdev/tree/master/masdev_lcd_clock">https://github.com/dmalec/Project-Masdev/tree/master/masdev_lcd_clock</a><br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpB_cXRHXiuLH1xTZr8PJqVucHxcHztqJjDLXU0i9tc0p3OkhbRuLnCynCh_lzZ2n7o-VESTCSAnMsg5Ys_V_XeWMIDqVNYbdavzXcrLtbD0fj7D-tyNTytLMKaSOrR1tAtkG40a8im1Yt/s1600/lcd_rtc_time.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpB_cXRHXiuLH1xTZr8PJqVucHxcHztqJjDLXU0i9tc0p3OkhbRuLnCynCh_lzZ2n7o-VESTCSAnMsg5Ys_V_XeWMIDqVNYbdavzXcrLtbD0fj7D-tyNTytLMKaSOrR1tAtkG40a8im1Yt/s640/lcd_rtc_time.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Clock pictured mid second change</td></tr>
</tbody></table>
</div>
<div>
<span style="font-size: large;"><br /></span></div>
<div>
The next step is <a href="http://danmalec.blogspot.com/2012/02/tracking-humidity-and-temperature-with.html">adding a temperature and humidity sensor</a> to the circuit and replacing the banner output with those readings.</div>
<div>
<br /></div>Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com4tag:blogger.com,1999:blog-7724773768743021813.post-69116851217032192982012-02-01T18:30:00.000-05:002012-02-16T21:09:52.891-05:00Forcing an Editor Resize During a GXT EditorGrid ResizeWe were having some issues with GXT's EditorGrid during resizes. If a user was actively editing, then started resizing the grid, the cell editor remained at the original location instead of moving along with the grid cell. There may be better solutions, but the one I came up with is adding a resize listener that forces the editor to realign to the new cell location, like so:
<br />
<script class="brush: java" type="syntaxhighlighter">
<![CDATA[
editorGrid.addWidgetListener(new WidgetListener() {
public void widgetResized(ComponentEvent ce) {
CellEditor cellEditor = editorGrid.getActiveEditor();
if (cellEditor != null) {
cellEditor.realign();
}
}
});
]]>
</script>Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-26408129108095372572012-01-26T21:42:00.000-05:002012-01-26T21:42:11.705-05:00Masdevallia MadnessI'm, at best, a dabbler in growing any one kind of plant. So, when I saw an unusual orchid on the sale table at the local nursery, I really had no idea what I was getting myself into, "How different could it really be?". Several months later, I've finally had to admit the answer is "Very".<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiONzyHnanvIBpMMUyE8fg7lfRT5DiS0mEfLMogo7QHi8kSZR5QXySy6oMiy5Gbgpanv1cCVL02dgwTtIC6G2IiZRM8z3LbTOhqyEt4gwbkNmmVodEEXqjQpMCh3oOLoeBnIsiPXVfLTsyG/s1600/Masdev_alone.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiONzyHnanvIBpMMUyE8fg7lfRT5DiS0mEfLMogo7QHi8kSZR5QXySy6oMiy5Gbgpanv1cCVL02dgwTtIC6G2IiZRM8z3LbTOhqyEt4gwbkNmmVodEEXqjQpMCh3oOLoeBnIsiPXVfLTsyG/s320/Masdev_alone.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Orchidus Angrius</td></tr>
</tbody></table>
<br />
All the orchids are watered together - within days, this one resembles a rhododendron in winter; clearly not happy with the humidity levels. Off to the bookcase to dig out Cullina's <i><a href="http://www.amazon.com/Understanding-Orchids-Uncomplicated-Growing-Worlds/dp/0618263268/">Understanding Orchids</a></i> and look up Masdevallia [<i>Masdevallia Heathii (veitchiana 4N x ignea 4N)</i> to be specific]. This is not going to be easy, but it looks like it will be fun.<br />
<br />
<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCDA8OOND62O_5EhCG89rluB68hcWG3ZnhU_Mdtuttm9F_7pqNICO8bEOKFFqbo4z0cUFWPXPeTxxHy3N8dFukW3gqr_10yysKRZ11o8Pt-JgKaGiUsvsStVEVt6jR_s2Xg8RBwKfo6gsw/s1600/vivarium_empty.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCDA8OOND62O_5EhCG89rluB68hcWG3ZnhU_Mdtuttm9F_7pqNICO8bEOKFFqbo4z0cUFWPXPeTxxHy3N8dFukW3gqr_10yysKRZ11o8Pt-JgKaGiUsvsStVEVt6jR_s2Xg8RBwKfo6gsw/s320/vivarium_empty.jpg" width="270" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">But will the locks hold in the orchids?</td></tr>
</tbody></table>
<br />
First step was getting some way to keep the humidity around the orchid at a significantly higher level than I've had it. Shopping around, I decided to give the <a href="http://www.amazon.com/gp/product/B000OQW98Q/">Exo Terra 12 x 12 x 18 Vivarium</a> a try. Too soon to say if it's big enough or the right pick for the job, but I liked a number of features:<br />
<br />
<ul>
<li>Front access via double doors.</li>
<li>Good airflow (my thinking is that it will be easier to reduce airflow than to try and give better airflow to a more sealed design).</li>
<li>Openings at the top back designed for routing wires (should be handy as I add sensors, fans, etc.).</li>
<li>Water tight base.</li>
</ul>
<br />
<br /><br /><br /><table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGX8iM5oe2Ar4RsIvV9TGLJyDZSlft5S8MBwb4rxlXMGrbfAhBzf7Y48lpvhrLDfTqP2lzifbBOVx4hbSlxrp32XSYcXAaE6pqvdS3F8tw0vLKSq9mhW-bn3dUvcx7DRE_TOHq9_ykrTGu/s1600/false_bottom.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGX8iM5oe2Ar4RsIvV9TGLJyDZSlft5S8MBwb4rxlXMGrbfAhBzf7Y48lpvhrLDfTqP2lzifbBOVx4hbSlxrp32XSYcXAaE6pqvdS3F8tw0vLKSq9mhW-bn3dUvcx7DRE_TOHq9_ykrTGu/s320/false_bottom.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Sometimes it turns out a sale isn't really a sale</td></tr>
</tbody></table>
<br />
I use egg crate lighting diffuser trimmed to fit in the water trays for my other orchids. Turns out that this, plus some PVC pipe as spacers, is a standard solution for lifting plants above the water line in vivariums. After dumping a couple inches of water into the base and closing the doors, I'm getting humidity in the mid 70s, which is a big improvement.<br />
<br />
Next up is getting an arduino doing some monitoring of the environment inside the case.Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-69308392383947179302010-08-01T17:23:00.000-05:002012-02-16T21:10:40.299-05:00Solving java.lang.SecurityException when adding Hamcrest in EclipseAdded the full version of Hamcrest 1.2 to my Eclipse project today and got a java.lang.SecurityException as soon as I tried to run my unit tests:<br />
<blockquote>
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><span class="Apple-style-span" style="font-size: small;"><br />
java.lang.SecurityException: class "org.hamcrest.Matchers"'s signer information does not match signer information of other classes in the same package at java.lang.ClassLoader.checkCerts(ClassLoader.java:807)<br />
</span></span></blockquote>
<br />
After some fruitless googling, I finally came across <a href="http://old.nabble.com/SecurityException-when-using-ClassImposteriser-td24304562.html">this post</a>. Don't entirely follow the reference to plugins, but the intent seems pretty clear. After editing my project's build order so Hamcrest is before JUnit4, the problem went away. Definitely looking forward to trying out more Matcher style asserts in my unit tests.Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com1tag:blogger.com,1999:blog-7724773768743021813.post-63327409992874763262010-07-22T21:15:00.001-05:002012-02-16T21:11:46.837-05:00Style a GXT Grid Row Using GridViewConfigAt work, we're using Sencha's Ext GWT library for much of our GWT based application. A few weeks back, I needed to implement some functionality to style rows in the grid based on information in the model data. After struggling through a number of poor approaches, I found GridViewConfig, which sorted things out nicely. While the logic for determining style in our app is more complex, this simple case demonstrates the principle:<br />
<script class="brush: java" type="syntaxhighlighter">
<![CDATA[
grid.getView().setViewConfig(new GridViewConfig() {
public String getRowStyle(ModelData model,
int rowIndex,
ListStore<modeldata> ds) {
if (rowIndex % 3 == 0) {
return "highlight";
} else {
return "";
}
}
});
]]>
</script><br />
<br />
Tho I've found the CSS side of things to be more finicky than I would have hoped:<br />
<script class="brush: css" type="syntaxhighlighter">
<![CDATA[
.highlight td {
font-weight: bold !important;
color: #FF0000 !important;
}
]]>
</script>Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com2tag:blogger.com,1999:blog-7724773768743021813.post-48495267058209079302007-04-26T17:26:00.000-05:002007-04-26T17:34:52.960-05:00Java IntrospectionAbout two weeks ago at work we got into a classic "works fine in development / doesn't work in staging" situation. I finally tracked it down to the fact that development and staging are running slightly different versions of Java. This surfaces because one of the Struts forms doesn't follow the standard for indexed properties. Evidently, version A of Java 1.3 handles introspection differently than version B. After working this out, I was able to jam the JRE from WebSphere in staging into the appropriate place in WSAD and now development breaks just like staging. Apparently this isn't an uncommon issue in Java 1.3 land, but from newgroups it sounds like it generally surfaces with an InvalidArgumentException rather than silently failing.Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-4931616424897771612007-02-14T11:18:00.001-05:002007-02-19T12:25:51.771-05:00Pipes RevisitedFinally put together some working pipes on Yahoo:<br /><A href="http://pipes.yahoo.com/pipes/XhNX0z282xGDiJSwZYQMOQ/">Blogspot as an RSS feed</a><br /><A href="http://pipes.yahoo.com/pipes/tBlgYz682xGnbuOxZYQMOQ/">Google Group as an RSS feed</a><br /><br />And the two together form:<br /><a href="http://pipes.yahoo.com/pipes/osnOCTa82xGO0otcFG_cUw/">The Java Posse Tracker</a> :)Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0tag:blogger.com,1999:blog-7724773768743021813.post-30806435771178406192007-02-08T22:47:00.000-05:002007-02-08T22:52:17.428-05:00The Internet is a Series of... Pipes?I spent some time playing around with <a href="http://pipes.yahoo.com/">Yahoo Pipes</a> tonight and I have to say it's a blast! I didn't build a particularly revolutionary pipe, but it was fun and intuitive. To Yahoo's credit, they managed to distract me from my GWT experiment :) which is the other technology I'm playing with that is surprisingly fun.Dan Malechttp://www.blogger.com/profile/05794361645750586521noreply@blogger.com0