tag:blogger.com,1999:blog-44547704156190384362024-03-23T11:22:23.515-07:00My Open Source ProjectsTorchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.comBlogger44125tag:blogger.com,1999:blog-4454770415619038436.post-14746907820726507222014-06-06T09:08:00.000-07:002014-06-06T09:08:52.240-07:00Wire Wrap Introduction VideoThis post is to provide a link to my new video on YouTube and a bit more accompanying explanation.<br />
<br />
First off, here is the video:<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="360" src="//www.youtube.com/embed/kk9sqDO58Vk" width="640"></iframe>
<br />
For those who are curious about some further explanation of the process and purchasing wire wrap supplies, I see that Jameco has a great online article and carries the supplies.<a href="http://www.jameco.com/jameco/workshop/techtip/wirewrap.html" target="_blank">(Check it out here).</a> Wikipedia also has a great article on wire wrapping <a href="http://en.wikipedia.org/wiki/Wire-wrapping" target="_blank">here</a>.<br />
<br />
I recall working for a company in the 1990s that was still using it extensively for building aircraft maintenance simulators because it is reliable, quick and easy and well suited to very small production run products. Early computers used the technique extensively. Check out this picture of a Z80 backplane from 1977:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Computerplatine_Wire-wrap_backplane_detail_Z80_Doppel-Europa-Format_1977.jpg/1280px-Computerplatine_Wire-wrap_backplane_detail_Z80_Doppel-Europa-Format_1977.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Computerplatine_Wire-wrap_backplane_detail_Z80_Doppel-Europa-Format_1977.jpg/1280px-Computerplatine_Wire-wrap_backplane_detail_Z80_Doppel-Europa-Format_1977.jpg" height="480" width="640" /></a></div>
<br />
The main challenge is obviously keeping track of the many point-to-point connections when you are looking at the <i>back </i>of the board! I seem to recall the firm I worked for had some sort of system of tables for keeping track of what went from where to where. Generally, I find that if I am going to have problems with a wire wrap assembly, it will be an error I have made in losing track of which pin is which when I flip the board over!<br />
<br />
That's it for now! Soon I will do a longer post on my first work with the nRF24L01 radios and my DigiX board. I have something almost working and when it's done I will provide more details.<br />
<br />
<br />
<br />
<br />Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-15120658729519684332014-05-14T19:07:00.000-07:002014-05-15T07:40:08.923-07:00Bringing it all together!Over the last four blog posts, I have been working step-by-step on creating a basic sensor network in which my BeagleBone takes information from my BlueLine Innovations PowerCost meter and my DigiX Arduino and presents it in a single web interface. Finally, after around three months, I have this working at a level I feel I can write up.<br />
<br />
The source code is available on GitHub <a href="https://github.com/torchris/powermeter2" target="_blank">here</a>. <br />
<br />
The system diagram for this project has now expanded to include the new components:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtoxqqwWeIegl8mbBDc3WorHfMV8u96jLCAy0HlTJ99c5n85n66PcH2e21xtCFPioXr4Uc1NO56a7Jw3xrQh1mwqbIYFdrdLEWdIKw7aI-b9At_fZFpRGylBxA285MbvHP5TSMtpR4jt0/s1600/Connectivity+diagram2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtoxqqwWeIegl8mbBDc3WorHfMV8u96jLCAy0HlTJ99c5n85n66PcH2e21xtCFPioXr4Uc1NO56a7Jw3xrQh1mwqbIYFdrdLEWdIKw7aI-b9At_fZFpRGylBxA285MbvHP5TSMtpR4jt0/s1600/Connectivity+diagram2.png" height="480" width="640" /></a></div>
<br />
To see how the DigiX is connected to the OneWire sensor, see my previous post <a href="http://opensource-torchris.blogspot.ca/2014/04/integrating-beaglebone-and-digix-part-2.html" target="_blank">here</a>.<br />
<br />
Here is a "family portrait" of the hardware on my desk:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIiXdJ-HW4rSwNsX1LFZrswWY6oVD9p1wJJJd0b4Kch49pu4DTi4JjwK-SdkIY9vE2_ucSYnGohYLWWy0ME2CJZUjnT_YGGigkkbszdSY2PD0yz7eDE0M4BHk-moxgQkYxkFXjkCTaATc/s1600/IMG_20140512_095215.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIiXdJ-HW4rSwNsX1LFZrswWY6oVD9p1wJJJd0b4Kch49pu4DTi4JjwK-SdkIY9vE2_ucSYnGohYLWWy0ME2CJZUjnT_YGGigkkbszdSY2PD0yz7eDE0M4BHk-moxgQkYxkFXjkCTaATc/s1600/IMG_20140512_095215.jpg" height="640" width="360" /></a></div>
<br />
<br />
I have also updated the layout of the web interface in Jade to move a few elements off to a sidebar:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiTxkwKvOGh-nUdMqpG1mcbmi9Y5MZsUP0p9NCDWyN8AmhsUQmmvOdMTwswPYsuW2qKCgrn28z0efiIMmBbGubGfOUEjL3lApuhdAmEeat0Ay8YT7uzcFkPysyFERSgLrzuLKhnRtwSM4/s1600/WebPageRevised.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiTxkwKvOGh-nUdMqpG1mcbmi9Y5MZsUP0p9NCDWyN8AmhsUQmmvOdMTwswPYsuW2qKCgrn28z0efiIMmBbGubGfOUEjL3lApuhdAmEeat0Ay8YT7uzcFkPysyFERSgLrzuLKhnRtwSM4/s1600/WebPageRevised.png" height="436" width="640" /></a></div>
<br />
<br />
Here is the code...<br />
<br />
<h3>
App.js</h3>
<br />
This takes the code I worked up in the last few projects and wraps into one package. Check out GitHub for the full source, but here are a few of the highlights.<br />
<br />
The "getData" function has been updated to include two different Request functions - one for the power and outside temperature from the BlueLine Innovations PowerCost gateway and the other from the DigiX OneWire sensor.<br />
<br />
<br />
<pre class="cpp" name="code">function getData() {
request({
uri: "http://192.168.1.33/pcmconfig.htm"
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
var n = body.search("Present Demand");
usage = body.substr((n + 44), 5);
usage = usage.trim();
console.log("Power Usage from PowerCost" + usage);
var n2 = body.search("Sensor Temp");
temp = body.substr((n2 + 40), 3);
temp = temp.trim();
temp = temp - 2;
console.log("Temp from PowerCost:" + temp);
}
});
request({
uri: "http://192.168.1.7:3010/temp"
}, function(error, response, body) {
if (error) {
//If there is an error or the DigiX is unreachable, set the value to an error.
console.log("Error: DigiX not available");
tempDigiX = 19.99;
DigiXAvailable = "FALSE";
console.log("DigiX status: " + DigiXAvailable);
}
else if (!error && response.statusCode == 200) {
var n = body.search("TEMP:");
tempDigiX = body.substr((n + 6), 5);
tempDigiX = tempDigiX.trim();
console.log("Current temp reading on DigiX: " + tempDigiX);
DigiXAvailable = "TRUE";
console.log("DigiX status: " + DigiXAvailable);
}
});
var readingInfo = new Readings({
temp2: temp,
usage2: usage,
DigiTemp: tempDigiX
});
readingInfo.save(function(err, readingInfo) {
if (err) return console.error(err);
console.dir(readingInfo);
});
}
//run the above function
setInterval(getData, (process.env.REFRESHINT * 1000));
</pre>
<br />
(See my <a href="http://opensource-torchris.blogspot.ca/2014/04/integrating-beaglebone-and-digix-part-2.html" target="_blank">previous post here</a> on the slightly "hacky" way I get the temp info off of the DigiX.)<br />
<br />
The info is them popped into the "readingInfo" object and then into the TingoDB database. Then a 'setInterval" runs the GetData function on the timing set by the REFRESHINT environment variable. If the Request function for the DigiX can't connect then it sets the DigiXAvailable variable to FALSE and also sets the temperature to a nominal "19.99". I played around with various mechanisms to test and flag that the link to the DigiX was up or down and this was the best I could come up with for now. It does have a lot of lag since it won't set the unavailable status on the web page until it has run through a full three minute cycle.<br />
<br />
The other big part of App.js is the Socket.io section:<br />
<br />
<pre class="cpp" name="code">io.sockets.on('connection', function(socket) {
console.log('A new user connected!');
Readings.find({}, {}, {
sort: {
'time': -1
},
limit: process.env.SAMPLES
}, function(err, readings) {
socket.broadcast.emit('readingsData', readings);
console.log(process.env.SAMPLES + ' readings sent over');
console.log('DigiX Status sent over socket = ' + DigiXAvailable);
socket.broadcast.emit('digiXStatus', DigiXAvailable);
});
setInterval(function() {
Readings.find({}, {}, {
sort: {
'time': -1
},
limit: process.env.SAMPLES
}, function(err, readings) {
socket.broadcast.emit('readingsData', readings);
console.log(process.env.SAMPLES + ' readings sent over');
console.log('DigiX Status sent over socket = ' + DigiXAvailable);
socket.broadcast.emit('digiXStatus', DigiXAvailable);
});
}, (process.env.REFRESHINT * 1000));
socket.on('sampleInput', function(sampleInputSetting) {
console.log('setting data = ' + sampleInputSetting);
process.env.SAMPLES = sampleInputSetting;
socket.broadcast.emit('sampleSetting', sampleInputSetting);
console.log('Sending sample rate back out');
});
socket.on('refreshInput', function(refreshInputSetting) {
console.log('setting data = ' + refreshInputSetting);
process.env.REFRESHINT = refreshInputSetting;
socket.broadcast.emit('refreshSetting', refreshInputSetting);
console.log('Sending refresh rate back out');
Readings.find({}, {}, {
sort: {
'time': -1
},
limit: process.env.SAMPLES
}, function(err, readings) {
socket.broadcast.emit('readingsData', readings);
socket.emit('readingsData', readings);
});
});
});
</pre>
This is pretty similar to the original Socket.io version of my project (<a href="http://opensource-torchris.blogspot.ca/2014/03/beaglebone-nodejs-project-updated-with.html" target="_blank">check it out here</a>)it's just that now it also sends over the DigiXAvailable flag to the browser for processing to alert end-users of the status of the DigiX.<br />
<br />
I really must start using the "Promises" framework in Node to simplify some of these nested functions! <br />
<br />
<br />
<h3>
Main.js</h3>
<h3>
</h3>
<h3>
</h3>
On the client side, the codes is again pretty similar to the last version of this project, except now I have included this in the Socket.io section:<br />
<br />
<pre class="cpp" name="code"> socket.on('digiXStatus', function(digiXStatusData) {
console.log('DigiX Status received: ' + digiXStatusData);
if (digiXStatusData == "FALSE"){
$('#DigiStatus').text('Offline');
$('#DigiStatus').css('color', 'red');
} else {
console.log("DigiX online");
$('#DigiStatus').text('Online');
$('#DigiStatus').css('color', 'green');
}
});
</pre>
<br />
This will take the DigiXAvaialble flag from the socket (renamed digiXStatusData when it gets over to the browser) and then write info to the "#DigiStatus" DIV on the webpage using jQuery.<br />
<br />
<br />
<br />
<h3>
Index.Jade</h3>
<h3>
</h3>
I decided to change the layout to put a "status" block and the controls off to the left side to make the layout a bit more horizontal. To do this I created CSS elements in Stylesheet.css for the "Status" on the left side and the "Content" area where the chart data goes:<br />
<br />
<pre class="cpp" name="code">status {
position: absolute;
left: 0;
width: 15em;
}
#content {
margin-left: 15em;
}
</pre>
<br />
I then created a DIV in Jade and put the content where it belonged:<br />
<br />
<pre class="cpp" name="code">extends layout
block content
#status
h2 System Status
table
tr
td
b DigiX:
td
#DigiStatus
tr
td
b BeagleBone:
td
#logger
h2 Settings
p Data to graph:
input#dataSampleInput(value= '#{sampleNum}', type='text')
p Time between refresh (sec)
input#refreshTimeInput(value= '#{refreshRate}', type='text')
button#submitBtn Submit
#content
h1= title
#legendholder(style='margin-left: 110px;')
#placeholder(style='width:700px;height:300px')
h2 Current Readings
#tablediv
table
thead
tr
td
b Timestamp
td
b Outside Temp
td
b Power usage
td
b DigiX Temp
tbody
tr
td
#currTime
td
#currTemp
td
#currUsage
td
#currDigiX
</pre>
<br />
Jade takes some getting used to, but it does allow you to generate a webpage without many lines of code!<br />
<br />
<h3>
DigiX software</h3>
<br />
<br />
This is identical to my post <a href="http://opensource-torchris.blogspot.ca/2014/04/integrating-beaglebone-and-digix-part-2.html" target="_blank">here </a>- the DigiX serves up information depending on which URL is requested. I chose to not implement the digital On/Off controls in this version, but they would be easy enough to include in the future.<br />
<br />
<h3>
Conclusion and next steps</h3>
<br />
Well, that's about it for this phase. There are a few things I still need to track down...<br />
<br />
<ul>
<li>As I said, there is a lot of lag in alerting on the browser if the DigiX goes offline (which it does occasionally) - basically it goes through at least one of it's 180 second cycles. I played around with a "heartbeat" or "ping" function, but I couldn't quite get it to work.</li>
<li>Since the DigiX has an SD card slot, it would be good to be able to buffer readings when there is a network interruption and then send them over once the connection is re-established.</li>
<li>It would be good to throw an LCD display onto the DigiX to display the sensor readings. </li>
<li>I need to add a few more sensors onto the DigiX as well - light level and humidity, for instance!</li>
</ul>
<br />
Next up, I want to exercise the mesh radio features of the DigiX. A few months back, I bought a few of these NRF24L01+ radios with serial modules off of eBay (<a href="http://www.ebay.ca/itm/NRF24L01-Wireless-Serial-Module-WITH-MCU-STC15L204-Wireless-Development-Board-/161236811905?pt=LH_DefaultDomain_0&hash=item258a768081" target="_blank">check them out here</a>):<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga9t_yYzojtvgZn5S8NVAzH0Uf4wF83CKsMuITgwTUs7L76Kas9nxVGuAK-_eR5_LU-_diVeCVJDep2bpnSpp39t1oqJQdNY5UeUIjWF8Pvde9kWzXB4jTBBgjN-IH36f-2QgliJ5Yens/s1600/$_57.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga9t_yYzojtvgZn5S8NVAzH0Uf4wF83CKsMuITgwTUs7L76Kas9nxVGuAK-_eR5_LU-_diVeCVJDep2bpnSpp39t1oqJQdNY5UeUIjWF8Pvde9kWzXB4jTBBgjN-IH36f-2QgliJ5Yens/s1600/$_57.JPG" height="320" width="320" /></a></div>
I can hopefully use one of these with one of my regular Arduinos just using serial! Check back in a few weeks on that!<br />
<br />
<br />
<br />
<br />
<br />Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-1258055433833680212014-04-21T19:19:00.000-07:002014-06-21T18:39:19.976-07:00Integrating the Beaglebone and DigiX - Part 2 of 2This is the second part of my adventures with the Digix! In this blog, I will describe the software and the slightly "hackified" way I got the DigiX and my Beaglebone to talk to one another.<br />
<br />
The software is all posted to GitHub here:<br />
<br />
<a href="https://github.com/torchris/digixbeagle">https://github.com/torchris/digixbeagle</a><br />
<br />
If you missed the first section of this, check it out <a href="http://opensource-torchris.blogspot.ca/2014/04/getting-started-with-digix-part-1-of-2.html" target="_blank">here</a>.<br />
<br />
<h3>
Hardware setup</h3>
<h3>
</h3>
Here is a diagram of the breadboard I did up in Fritzing. Note that I have used an Arduino Due instead of the DigiX because there is not yet a DigiX image for Fritzing and the Dues is a similar size and shape.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTIvAoyWOF_iOIfL941xDLMTY4Qj0MQaDIFOLqaRpPiELa3BzkkSH10QoykuSHAu0kRWffmB8Fsqskq4np9YOhb_n2VtOI_4M-hFGldbNETiEJ-KSy8qaQreNJCZ1L_CC-Nw9Q8CZZaws/s1600/digiXbeagle_bb.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTIvAoyWOF_iOIfL941xDLMTY4Qj0MQaDIFOLqaRpPiELa3BzkkSH10QoykuSHAu0kRWffmB8Fsqskq4np9YOhb_n2VtOI_4M-hFGldbNETiEJ-KSy8qaQreNJCZ1L_CC-Nw9Q8CZZaws/s1600/digiXbeagle_bb.jpg" height="428" width="640" /></a></div>
This is very straightforward and there are many good articles on using the DS18B20 OneWire sensor with the Arduino. <a href="http://tushev.org/articles/arduino/item/52-how-it-works-ds18b20-and-arduino" target="_blank">Here is an excellent article by Simon Tushev.</a> There are also two LEDs. One shows when requests are being processed and the other shows how a digital output on the DigiX can be controlled by the web page off the BeagleBone.<br />
<br />
<h3>
Server side</h3>
<br />
The server code for this running on the BeagleBone is a basic Express site modified to use Socket.io and using the Request library to get information from the DigiX. The server then uses Socket.io to push the out to a browser. It also takes input from the browser and sends it back over to the DigiX.<br />
<br />
The one thing to note here, which I should have remembered from the last time around is that you can't use the Express template with Socket.io right off the bat. <a href="http://stackoverflow.com/questions/10191048/socket-io-js-not-found" target="_blank">This post </a>from Stackoverflow explains the situation. Just to extract the most salient bit:<br />
<blockquote class="tr_bq">
<i>Express 3 requires that you instantiate a <code>http.Server</code> to attach socket.io to first:</i><br />
<br />
meaning - (1) you must create a server instance:<br />
<br />
<pre class="lang-js prettyprint prettyprinted"><code><span class="kwd">var</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">var</span><span class="pln"> http </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'http'</span><span class="pun">).</span><span class="pln">createServer</span><span class="pun">(</span><span class="pln">app</span><span class="pun">);</span></code></pre>
<pre class="lang-js prettyprint prettyprinted"><code><span class="pun"> </span></code></pre>
(2) couple it with the socket.io:<br />
<br />
<pre class="lang-js prettyprint prettyprinted"><code><span class="kwd">var</span><span class="pln"> io </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'socket.io'</span><span class="pun">);</span><span class="pln">
io</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">http</span><span class="pun">);</span></code></pre>
<pre class="lang-js prettyprint prettyprinted"><code><span class="pun"> </span></code></pre>
and ONLY THEN - (3) make the server listen:<br />
<br />
<pre class="lang-js prettyprint prettyprinted"><code><span class="pln">http</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">8080</span><span class="pun">);</span></code></pre>
<pre class="lang-js prettyprint prettyprinted"><code><span class="pun"> </span></code></pre>
<b>make sure you keep this order!</b></blockquote>
I must tattoo that on my forehead so I don't waste time with Socket.io not working!!<br />
<br />
Anyway, the App.js code is basically all stock except for the change above and this function that gets the info from the DigiX using web page calls and the Request library (more on that later on) and then does some simple text processing to extract the temp.:<br />
<br />
<pre class="cpp" name="code">function getData() {
request({
uri: "http://192.168.1.7:3010/temp"
}, function(error, response, body) {
if (error){
//If there is an error or the DigiX is unreachable, set the value to an error.
console.log("Error: DigiX not available");
temp = "DigiX not available";
}
else if (!error && response.statusCode == 200) {
var n = body.search("TEMP:");
temp = body.substr((n + 6), 5);
temp = temp.trim();
console.log("Current temp reading: " + temp);
}
});
setTimeout(getData, 10000);
}
getData();
</pre>
<br />
<br />
The other main part of App.js is the Socket.io section:<br />
<br />
<pre class="cpp" name="code">io.sockets.on('connection', function(socket) {
console.log('A new user connected!');
console.log("Sending over initial LED state: " + ledStatOn);
socket.broadcast.emit('ledStatOn', ledStatOn);
setInterval(function() {
socket.broadcast.emit('tempData', temp);
console.log("Temp sent over sockets to client; " + temp);
}, 10000);
socket.on('ledStatOn', function(ledStatOn) {
if (ledStatOn == "ledOn") {
console.log('LED data = ' + ledStatOn);
request({
uri: "http://192.168.1.7:3010/on"
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
var x = body.search("ledCmded:");
ledOnStat = body.substr((x + 10), 2);
ledOnStat = ledOnStat.trim();
console.log("LED status is " + ledOnStat);
socket.emit('ledStatOn', ledOnStat);
}
});
}
else if (ledStatOn === "ledOff") {
console.log('LED data = ' + ledStatOn);
request({
uri: "http://192.168.1.7:3010/off"
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
var x = body.search("ledCmded:");
ledOnStat = body.substr((x + 10), 3);
ledOnStat = ledOnStat.trim();
console.log("LED status is " + ledOnStat);
socket.emit('ledStatOn', ledOnStat);
}
});
}
});
});
</pre>
<br />
<br />
When the Socket.io connection is established, this sends over the temerature info from the DS18S20to be displayed on the browser. As well, when commands to turn on or off the LED come over from the browser via Socket.io, the Request library is used to trigger these actions on the DigiX.<br />
<br />
<h3>
Browser side</h3>
<h3>
</h3>
The web interface for this is very, very simple. Just a basic Jade-generated page: <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVDHRg832DuUbkLZfiq-Dxr4nf_3j-juIModSSi5f5BCpCvdNdwnk9NJtzbXAAlHyIYwWhjdojoTZkzUq25RVtHQx0kKNXZ6B3U_qgz2SELyJGJCXuq-Rvi3OcUjrW1Yk5l2wosVDq4Mo/s1600/Digixwebpage.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVDHRg832DuUbkLZfiq-Dxr4nf_3j-juIModSSi5f5BCpCvdNdwnk9NJtzbXAAlHyIYwWhjdojoTZkzUq25RVtHQx0kKNXZ6B3U_qgz2SELyJGJCXuq-Rvi3OcUjrW1Yk5l2wosVDq4Mo/s1600/Digixwebpage.jpg" height="490" width="640" /></a></div>
<br />
Here is the JavaScript code for the page:<br />
<br />
<pre class="cpp" name="code">var socket = io.connect();
$(document).ready(function() {
socket.on('ledStatOn', function(ledStatOn) {
console.log('Received LED status ' + ledStatOn);
});
$("input:radio[name=ledStat]").click(function() {
var val = $('input:radio[name=ledStat]:checked').val();
console.log(val);
socket.emit('ledStatOn', val);
});
socket.on('tempData', function(tempData) {
$('#logger').text('Web server connected.');
$('#logger').css('color', 'green');
console.log("Server Connected");
console.log(tempData);
$('#tempData').html(tempData);
socket.on('disconnect', function() {
// visually disconnect
$('#logger').text('Web server disconnected.');
$('#logger').css('color', 'red');
});
});
});
</pre>
<br />
<br />
This reuses the server connected/disconnected status used in the previous project. It also uses "socket.emit" to send over to teh server the user selection for the digital output on the DigiX.<br />
<br />
Index.jade is also pretty simple - just the DIVs to write the info into and the radio buttons for the LED:<br />
<br />
<pre class="cpp" name="code">extends layout
block content
h1= title
p Welcome to #{title}
#logger
br
p Curent temperature from DigiX:
b#tempData
br
br
input(type='radio', name='ledStat', value='ledOff', checked='CHECKED')
| LED Off
input(type='radio', name='ledStat', value='ledOn')
| LED On
</pre>
<br />
<br />
<br />
<h3>
DigiX</h3>
<h3>
</h3>
This is where it maybe gets a bit silly, but it does work. As I explained in my last post, I couldn't figure out how to get Socket.io or even good-old Websockets (which I used four years ago with <a href="http://opensource-torchris.blogspot.ca/2010/05/arduino-email-manager-part-2.html" target="_blank">this project</a> in PHP). I am sure someone with more brains or patience could easily come up with an answer, but I got impatient to get this going, so I came up with this solution.<br />
<br />
I noticed in the DigiX webserver demo application that the DigiX was parsing the value of the page requested (of course it would have to) and I figured that with different pages being called, the sketch could execute different Arduino commands. In other words, to get the application to do something on the on the DigiX, I just called different web addresses. Here is what this sketch implements. When I call these URLs, I get the following responses:<br />
<ul>
<li>http://192.168.1.7/on = turn ON the LED at digital output 8</li>
<li>http://192.168.1.7/off = turn OFF the LED at digital output 8</li>
<li>http://192.168.1.7/temp = print a simple webpage with the DS18B20 OneWire temp reading</li>
</ul>
Nice and simple! Also easy to troubleshoot because I can just use any browser to navigate to those pages and see the results. Is this a basic RESTful API? (I use the LED on pin 9 to show when a web request is being processed.)<br />
<br />
Here is the code:<br />
<br />
<pre class="cpp" name="code">#include <digifi .h="">
#include <onewire .h="">
#include <dallastemperature .h="">
DigiFi wifi;
int ledCmded = 8;
int ledStat = 9;
#define ONE_WIRE_BUS 10
// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
void setup()
{
pinMode(ledCmded, OUTPUT);
pinMode(ledStat, OUTPUT);
digitalWrite(ledCmded, LOW);
digitalWrite(ledStat, LOW);
Serial.begin(9600);
wifi.begin(9600);
sensors.begin();
//DigiX trick - since we are on serial over USB wait for character to be entered in serial terminal
while (!Serial.available()) {
Serial.println("Enter any key to begin");
delay(1000);
}
Serial.println("Starting");
while (wifi.ready() != 1)
{
Serial.println("Error connecting to network");
delay(15000);
}
Serial.println("Connected to wifi!");
Serial.print("Server running at: ");
String address = wifi.server(3010);//sets up server and returns IP
Serial.println(address);
// wifi.close();
}
void loop()
{
if ( wifi.serverRequest()) {
Serial.print("Request for: ");
Serial.println(wifi.serverRequestPath());
if (wifi.serverRequestPath() == "/off") {
digitalWrite(ledStat, HIGH);
digitalWrite(ledCmded, LOW);
Serial.println("ledCmded off");
wifi.serverResponse("<html><body><h1>ledCmded: OFF</h1></body></html>");
digitalWrite(ledStat, LOW);
}
else if (wifi.serverRequestPath() == "/on") {
digitalWrite(ledStat, HIGH);
digitalWrite(ledCmded, HIGH);
Serial.println("ledCmded on");
wifi.serverResponse("<html><body><h1>ledCmded: ON</h1></body></html>");
digitalWrite(ledStat, LOW);
} else if (wifi.serverRequestPath() == "/temp") {
digitalWrite(ledStat, HIGH);
float gotTemp;
gotTemp = getTemp();
Serial.print("Temp = ");
Serial.println(gotTemp);
wifi.println("HTTP/1.1 200 OK");
wifi.println("Content-Type: text/html");
wifi.println("Connection: close"); // the connection will be closed after completion of the response
wifi.println();
wifi.println("");
wifi.print("<html><body><h1>TEMP: ");
wifi.print(gotTemp);
wifi.print("</h1></body></html>");
digitalWrite(ledStat, LOW);
}
else {
wifi.serverResponse("<html><body><h1>Nothing doing</h1></body></html>"); //defaults to 200
}
}
delay(10);
}
float getTemp() {
float currTemp;
sensors.requestTemperatures(); // Send the command to get temperatures
currTemp = sensors.getTempCByIndex(0);
Serial.println(currTemp); // Why "byIndex"? You can have more than one IC on the same bus. 0 refers to the first IC on the wire
return currTemp;
}
</pre>
<br />
<br />
This works as an easy way to get the kind of interaction I wanted for this project, which is just to exchange some simple sensor info in a non-realtime way and test a Server -> Arduino digital output. However, this has some obvious limitations. It would be hard, for instance, to exchange analog information from the server to the Arduino (for instance, to dim an LED using PWM). You could perhaps have the BeagleBone server send over a series of URLs like "http://192.168.1.7/analog789" where "789" is the analog value and the Arduino code parses that out of the request and then sets the PWM value, but I doubt you could do this more than a few times per minute. In other words, this is a nice easy way to get this project working, but I can't see how you could do something like my earlier project where I exchanged realtime movement info over the network (<a href="http://opensource-torchris.blogspot.ca/2010/01/arduino-motion-control-over-ethernet_21.html" target="_blank">see this project</a>).<br />
<br />
That's it for now. Next up will be to include this DigiX sensor data in my previous code so I start to have a real "sensor network". Eventually, I want to use the mesh networking off of the DigiX to get sensor data from several other Arduinos (hopefully the tiny DigiSparks if I can make them work).<br />
<br />
<br />
<br />Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-57886789980997026782014-04-11T08:58:00.001-07:002014-06-13T18:07:33.131-07:00Getting started with the DigiX - Part 1 of 2This is the first part of my project to integrate the Digistump DigiX board with the BeagleBone. In this post, I'll provide a review of the DigiX and some of the good and less good points about working with it. The next section will describe in more detail the software and process I used to build the project.<br />
<br />
First, for your multimedia viewing pleasure, here is a quick video tour of this fairly simple project:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<object class="BLOGGER-youtube-video" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" data-thumbnail-src="https://i1.ytimg.com/s_vi/P4u_T_I0Jnk/default.jpg?sqp=CNCZoJoF&rs=AOn4CLDM-FkY24Gn-gAZfSclBJcaOy36oA" height="266" width="320"><param name="movie" value="https://www.youtube.com/v/P4u_T_I0Jnk?version=3&f=user_uploads&c=google-webdrive-0&app=youtube_gdata" /><param name="bgcolor" value="#FFFFFF" /><param name="allowFullScreen" value="true" /><embed width="640" height="505" src="https://www.youtube.com/v/P4u_T_I0Jnk?version=3&f=user_uploads&c=google-webdrive-0&app=youtube_gdata" type="application/x-shockwave-flash" allowfullscreen="true"></embed></object></div>
<br />
<h3>
Introducing the DigiX </h3>
<h3>
</h3>
The DigiX is produced by <a href="http://digistump.com/" target="_blank">DigiStump </a>which was founded in 2012 by Mr. Erik Kettenburg in the Seattle area. I first became aware of them when I backed his successful Kickstarter for the DigiSpark which is a super-small <a href="http://digistump.com/products/1" target="_blank">$9 Arduino development board</a> - I will include them in a later part of my monitoring system. A few months after the DigiSpark shipped, I saw the Kickstarter come up for the DigiX and decided to go for it.<br />
<br />
The specs on the DigiX are, frankly over the top! This is the Monster Killer Arduino of all Time (at least for now) - check it out: <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://digispark.s3.amazonaws.com/comparison.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://digispark.s3.amazonaws.com/comparison.PNG" height="640" width="510" /></a></div>
<br />
In form factor, it looks a bit similar to the Arduino Due, but it has a double row of IO pins down the one side whereas the Due only has a single row. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
This is a beast!! As well it has BOTH on-board wifi and mesh networking via the popular, low-cost nRF24L01+ wireless module and and a micro SD card slot. All this for only $69.95 USD!<br />
<br />
That being said, given that the BeagleBone Black is only $44.95 and the RaspBerry Pi is only $39.95 (prices from SparkFun) and both of them run full up Linux. Is it worth the extra money to have the DigiX? Here are a few thoughts on pros and cons...<br />
<br />
<h3>
Pros </h3>
<ol>
<li>Rather than having to wrestle with the complexity of managing GPIO in the Linux environment, the DigiX supports the usual Arduino IDE and it's familiar interface. If you're used to stock Arduinos, then there is no ramp up.</li>
<li>The onboard wifi works as advertised and is a breeze to configure according to the DigiX Wiki article (check it out <a href="http://digistump.com/wiki/digix/tutorials/wifi" target="_blank">here</a>).</li>
<li>There are more IO pins than any reasonable person could ever use and there is loads of program memory space (524,288 bytes). </li>
<li>There is a good range of sample programs that exercise the functionality of the board and they all seem to compile and work right away. I was amazed that I had a simple web server up and running in about 15 minutes!</li>
<li>I haven't tried the mesh networking yet, but I am assuming it works and it's awesome to have it on the same board. </li>
<li>This board is a 3.3 VDC board (like the other newer Arduinos), but Digistump sells a level shifting shield so you can reuse older 5 VDC shields, which is very useful if you're an old-timer like me.</li>
<li>There is a reasonable amount of documentation and active forums for help. </li>
</ol>
<br />
<b>Cons</b><br />
<br />
<br />
The main draw back is is it not quite a stock Arduino and a couple of compromises have been made to keep the board affordable. Let's look at two things I have found...<br />
<br />
<h4>
Wifi </h4>
The wifi module on the DigiX is not the same as on the Arduino Yun. The Yun uses the AR9331 while the DigiX uses an Atheros Silicon based embedded UART/WiFi module(source <a href="https://community.freescale.com/thread/309138" target="_blank">here</a>). This means the DigiFi wifi library is <u>mostly</u> compatible with the Arduino Ethernet library, but not 100%.<br />
<br />
Consequently, for the project I just completed, I wanted to use Socket.io or websockets to exchange information between my Beaglebone and the DigiX. I tried various sockets libraries and just could not find a way to make them work with the DigiX! I posted a thread to the DigiX forums, but, since they're somewhat less active than the Arduino boards, I so far haven't found a solution. You will see in my upcoming project that the solution I found works, but is definitely "suboptimal" from a design standpoint.<br />
<br />
<br />
<h4>
USB Serial and programming</h4>
<h4>
</h4>
Mr. Kettenburg provides a good technical description of what happens with USB serial on the DigiX and Due boards <a href="http://digistump.com/wiki/digix/tutorials/programming" target="_blank">here</a>. You can read the detailed explanation, but what it boils down to is that the Arduino Due has two USB ports - one for programming and one for native USB - while the DigiX has only one USB port which has to work for programming and USB. Unfortunately, on the DigiX it seems to sometimes be a coin toss as to which is going to be available when you boot up the board, which caused me some headaches at first.<br />
<br />
When I was first using the board, it would do something like this when doing programming:<br />
<ol>
<li>The Arduino IDE is set on COM16 and you hit the upload button in the IDE.</li>
<li>The program compiles and if there are no errors, then it goes to upload and says "The COM port isn't available".</li>
<li>Restart the DigiX and <u>if you are lucky</u> it comes up with COM15.</li>
<li>While on COM15, hit the upload button and it goes through and programs the board.</li>
<li><u>However</u> when you go to the IDE to select the serial port for the serial monitor, it is back to COM16!</li>
</ol>
This could be a huge hassle sometimes requiring multiple restarts of the board and plugging and unplugging the USB until the right port came up. As well, the IDE serial monitor would sometimes not fire up and that required more restarts.<br />
<br />
At the moment, I seem to have solved this and it is working smoothly. What I did was follow <a href="http://digistump.com/wiki/digix/tutorials/basics" target="_blank">this advice</a> from the DigiX wiki:<br />
<br />
<blockquote class="tr_bq">
<span style="font-family: "Helvetica Neue",Arial,Helvetica,sans-serif;">If the COM port isn't showing in the Arduino IDE - unplug and replug the
board. If that doesn't work - while plugged in, hold down the erase
button on the board for a moment and then unplug and replug - you may
then have to select it from the com port menu as it may be on a
different port - but it is a sure way to get it to respond even if your
sketch crashed the USB stack. </span></blockquote>
<br />
At the same time as doing this, I moved it to another USB port on my system and Windows reloaded the drivers. It works normally now, but it leaves me concerned it will stop working again in the future. I guess I shouldn't complain because it is a bit of an RTFM situation, but perhaps future versions of the board could have the two port solution like the Due or maybe a jumper block to put the USB port into one mode or the other to avoid this confusion? <br />
<br />
<h3>
Summary</h3>
<br />
Despite the minor hassles I would still say the DigiX is a great board and worth the money. It gives you loads of room to grow and build very sophisticated networked projects. If you just want something relatively simple to take some digital/analog inputs and then do some digital.analog outputs, then this board may be too much and you should look at some of the simpler Arduinos on the market.<br />
<br />
As the "Internet of Things" movement develops, it will be <i>de rigueur </i>to be able to to hook your embedded project to the Internet and to other devices via either mesh or Bluetooth Low Energy. The DigiX has you covered with wifi and mesh on board and while it is more expensive than some boards, you save by having all the connectivity integrated and not having to get separate shields and so on.<br />
<br />
Nest up, the software the drives this project!<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-58131738892335773662014-03-26T19:11:00.000-07:002014-05-01T08:59:28.382-07:00BeagleBone Node.js project updated with Socket.ioAfter I finished my last project, I realized I wasn't entirely satisfied with it. While it basically functioned, relying on it doing a whole page refresh every so often just didn't seem "elegant". As well, when you changed the settings in one browser, they didn't change in other browsers, which, again, didn't seem very slick.<br />
<br />
I figured I had to learn to use the Socket.io library anyway to eventually communicated with my Arduino DigiX, so why not update the BeagleBone project to "socketize" it? Naturally, this took a bit of a learning curve, but I like the results.<br />
<br />
The project hardware setup is exactly the same as my <a href="http://opensource-torchris.blogspot.ca/2014/03/creating-nodejs-application-on.html" target="_blank">previous post</a> - using the BeagleBone and the BlueLine Innovations PowerCost Monitor. I have also gotten smart and finally signed up for GitHub and here is a link to the complete source code:<br />
<br />
<ul>
<li><a href="https://github.com/torchris/powermeter2/tree/d5e0af62f13c5c16f72a46009756c215eed5d2f1" target="_blank">GitHub repository with this version of the code</a></li>
</ul>
<br />
Finally, I have done a YouTube overview of the functionality of the project:<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="315" src="//www.youtube.com/embed/hqQ3gvqxspI" width="560"></iframe>
<br />
<br />
In case it isn't clear in the video, here is what the web page looks like:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgITzCfTS4iQksGeGft0NSYKO8KDgIw7lzZ1zK8jSK0jBEBRid9TQjn72vC6XCDOMlOGnHLr6dZsKuUClH9a9n80dCBbmLoaFVpNNR_13nSZK5pHOxChagmK-qEBMJNLQJCAQMNvSjseJ4/s1600/socket_web.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgITzCfTS4iQksGeGft0NSYKO8KDgIw7lzZ1zK8jSK0jBEBRid9TQjn72vC6XCDOMlOGnHLr6dZsKuUClH9a9n80dCBbmLoaFVpNNR_13nSZK5pHOxChagmK-qEBMJNLQJCAQMNvSjseJ4/s1600/socket_web.jpg" height="590" width="640" /></a></div>
<br />
<br />
The software uses all the libraries from the previous version (Request, Flot, jQuery etc.). The only addition is Socket.io, however, I ended up practically rewriting the application from scratch to accommodate the change!<br />
<br />
<h3>
Server side - App.js</h3>
<h3>
</h3>
Some of the elements of App.js from the last version of this project are the same, so I won't go into detail on them such as using the Request library to do the screen scrape and then popping that into the TingoDB/Mongoose database. However, it seems like Socket.io is only "semi compatible" with Express and you need to make several changes from the default Express setup.<br />
<br />
Again, there are many, many good tutorials out there by folks who understand this at a deeper level. Below is just my description of how I got this to work. Here are a couple of good Socket.io tutorials I found along the way that may be helpful:<br />
<br />
<ul>
<li><a href="http://www.williammora.com/2013/03/nodejs-tutorial-building-chatroom-with.html" target="_blank">William Mora - building a chat room</a></li>
<li><a href="http://www.youtube.com/watch?v=JfYRGTvMbBA" target="_blank">James Doyle - broadcasting sensor positions out to multiple browsers. Very cool!</a></li>
</ul>
<br />
<br />
Here is the section of App.js setting up Socket.io, which seems to have to come later in the App.js file than the usual "require" statements at the beginning:<br />
<br />
<pre class="cpp" name="code">var io = require('socket.io').listen(server);
app.set('port', process.env.PORT || 3008);
io.set('log level', 1);
</pre>
<br />
In my last version of this project the "app.get" and "app.post" functions did most of the heavy lifting of rendering information over to web page. Now with Socket.io, the "app.post" section is completely gone since the button pushing is now handled by Socket.io rather than the traditional POST method.<br />
<br />
The main action is now all within this one big Socket.io loop:<br />
<br />
<pre class="cpp" name="code">io.sockets.on('connection', function(socket) {
console.log('A new user connected!');
Readings.find({}, {}, {
sort: {
'time': -1
},
limit: process.env.SAMPLES
}, function(err, readings) {
socket.broadcast.emit('readingsData', readings);
socket.broadcast.emit('sampleSetting', process.env.SAMPLES);
socket.broadcast.emit('refreshSetting', process.env.REFRESHINT);
console.log('Initial data over to browser.');
});
socket.on('sampleInput', function(sampleInputSetting) {
console.log('setting data = ' + sampleInputSetting);
process.env.SAMPLES = sampleInputSetting;
socket.broadcast.emit('sampleSetting', sampleInputSetting);
console.log('Sending sample rate back out');
});
socket.on('refreshInput', function(refreshInputSetting) {
console.log('setting data = ' + refreshInputSetting);
process.env.REFRESHINT = refreshInputSetting;
socket.broadcast.emit('refreshSetting', refreshInputSetting);
console.log('Sending refresh rate back out');
Readings.find({}, {}, {
sort: {
'time': -1
},
limit: process.env.SAMPLES
}, function(err, readings) {
socket.broadcast.emit('readingsData', readings);
socket.emit('readingsData', readings);
});
});
setInterval(function() {
Readings.find({}, {}, {
sort: {
'time': -1
},
limit: process.env.SAMPLES
}, function(err, readings) {
socket.broadcast.emit('readingsData', readings);
console.log(process.env.SAMPLES + ' readings sent over');
});
}, (process.env.REFRESHINT * 1000));
});
</pre>
<br />
At it's heart, when a browser connects and establishes a socket, this sends over an initial chunk of data and then kicks off the setInterval loop. This broadcasts out a chunk of database data to all connected browsers according to the refresh interval environment variable (process.env.REFRESHINT).<br />
<br />
When the "submit" button is hit on the website, this opens the the refreshInput and sampleInput sockets and they in turn send over the revised values to the other browsers connected to the server. <br />
<br />
Overall, this works fine, but really looks ugly to me and I am sure I'm doing something wrong! I have repeated the database query three times. I think this should be put into a separate function, but then this isn't the way Node.js works. I looked at implementing the Promises framework (<a href="http://howtonode.org/promises" target="_blank">more here</a>), but I got lost in it and I decided to just close off where I am. Promises will need to wait until later.<br />
<br />
<h3>
Client side - Main.js</h3>
<br />
Since the client-side JavaScript had gotten rather complicated, I decided to put it off into a separate file which I called "main.js" which lives in the public/javascripts folder of the project. This is referenced in the "Layout.jade" file (more about Jade in a minute).<br />
<br />
At the top is this to load the Socket.io:<br />
<br />
<pre class="cpp" name="code">var socket = io.connect();
</pre>
Then we have the jQuery function started up and the logic for the Settings button:<br />
<br />
<pre class="cpp" name="code">$(document).ready(function() {
console.log('Doc loaded');
$('#submitBtn').click(function() {
var sampleInput = document.getElementById("dataSampleInput").value;
console.log(sampleInput);
var refreshInput = document.getElementById("refreshTimeInput").value;
console.log(refreshInput);
socket.emit('sampleInput', sampleInput);
socket.emit('refreshInput', refreshInput);
});
</pre>
<br />
This is some jQuery that activates when the button is pushed, and picks up the values entered into the two fields with Javascript and then the pushes the values back to the server with socket.emit. You can see the "sampleInput" and "refreshInput" values here that end up over at the server side.<br />
<br />
Then there are two further sockets that take the settings value back from the server when they are rebroadcast out:<br />
<br />
<pre class="cpp" name="code"> socket.on('sampleSetting', function(serverSampleSetting) {
console.log('Received new sample rate ' + serverSampleSetting);
$('#dataSampleInput').val(serverSampleSetting);
});
socket.on('refreshSetting', function(serverRefreshSetting) {
console.log('Received new refresh rate ' + serverRefreshSetting);
$('#refreshTimeInput').val(serverRefreshSetting);
});
</pre>
<br />
This uses jQuery to write these values back to the fields so all browsers show the same settings.<br />
<br />
The socket.on 'readingsData' is what receives the data object over from the server when it is sent over according to the refresh interval. There is then basically the same logic as I used in the last project to break down the data object into two separate arrays and then feed that into Flot to generate the graph. Just check out my previous post on how that works.<br />
<br />
The "Server connected" and "Server disconnected" status line is an idea I got from James Doyle and <a href="https://www.youtube.com/watch?v=JfYRGTvMbBA" target="_blank">this great tutorial on YouTube</a> (GitHub <a href="https://github.com/james2doyle/devicesocket" target="_blank">here</a>). It is very simple and works like this. When the main readingsData socket starts up a couple of lines of jQuery write to a DIV called "logger":<br />
<br />
<pre class="cpp" name="code"> $('#logger').text('Server connected.');
$('#logger').css('color', 'green');
</pre>
<br />
When the socket disconnects, it changes the message:<br />
<br />
<pre class="cpp" name="code"> socket.on('disconnect', function() {
// visually disconnect
$('#logger').text('Server disconnected.');
$('#logger').css('color', 'red');
</pre>
Mr. Doyle used plain JavaScript for his, but I found for whatever reason this didn't work across all browsers, but jQuery does. The only limitation with this is that it will go to "disconnected" if the server is offline and then back to "connected" if it comes back on fairly quickly (let's say 15 min or so), but if the server is down for too long it won't auto-reconnect and you need to reload the web page. It also won't go to a "disconnect" status if the Ethernet cable is pulled out of the BeagleBone, for instance.<br />
<br />
<b>Jade files - Layout.jade and Index.jade</b><br />
<br />
If you check out these files on GitHub, you will see they are much simpler than they were the first time around. I moved the bulk of the scripting into "Main.js" and the original form I used for submission of the settings has been much simplified.<br />
<br />
Hopefully this is interesting to all and sundry! Next up I promise to get on with working with the DigiX and maybe I will even manage a flashing LED using Socket.io!!<br />
<br />Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-12768777221901358022014-03-01T14:28:00.000-08:002014-03-01T15:06:37.601-08:00Creating a Node.js application on the BeagleBone<br />
Well, as usual, it has taken a log time to get to another post! Hopefully it was worth the wait.<br />
<br />
<b>WARNING 1 - </b>I am very much of a beginner with Node.js! Please Google before asking me any questions about the programming on this project. The code in this project more or less works and demonstrates basic concepts, but totally lacks the kind of error handling and security that production code should always incorporate.<br />
<br />
<b>WARNING 2 -</b> I am also NOT an expert on the BeagleBone and this project really does not use any of the GPIO features of the board (in fact, the Node.js could be run on Windows as easily as the BB). Anyone wanting to understand the board should first look at Derek Molloy's <a href="http://derekmolloy.ie/tag/beaglebone-2/" target="_blank">fantastic blog and YouTube videos</a>!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<h2>
Motivation and background</h2>
<h2>
</h2>
I first pre-ordered my BeagleBone way back in 2011 when the product was first announced. At the time I was interested in the BeagleBoard and the RaspBerry Pi wasn't yet released and it seemed to have good specs for the cost. When I got it, I booted it up and played around with it a bit, but there wasn't a ton of documentation available yet and I was very busy with work. Lately I've been coming back around to my embedded electronics work and when I pulled it out I found that now there was tons of new material around on the device. Just to be clear, mine is the original "white" BeagleBone not the newer "Black" model.<br />
<br />
After updating the Angstrom image and booting it up, I saw that the BB provides it's own on-board IDE which is a locally version of normally cloud-based Cloud9 (<a href="https://c9.io/" target="_blank">more on Cloud9</a>) and runs this new and mysterious thing called "Node.js". I had a small amount of JavaScript knowledge, so I bought a text on Node.js (<a href="http://www.amazon.ca/Sams-Teach-Yourself-Node-js-Hours/dp/0672335956" target="_blank">Sams Tech Yourself Node.js</a>) so I could start puzzling it out.<br />
<br />
Rather than plunge into the depths of doing GPIO with the BB right away and introducing too many variables, I decided to basically use the BB as a web server and to come up with a simple goal so I could try to learn some Node.js and prepare for something more substantial later on. I decided something that would be "simple" to do would be figue out how to screen scrape my BlueLine Innovations Wifi PowerCost Monitor (<a href="http://www.bluelineinnovations.com/Products/power-cost-energy-monitor-wifi-gateway/" target="_blank">more details here</a>) to provide a visual indication of my power usage and the outside temp. Since I am already using Plot<br />
<br />
<h2>
Gear and setup</h2>
<h2>
</h2>
No elaborate wiring diagrams or anything here. The equipmentused is:<br />
<ul>
<li>BeagleBone (White, running Angstrom Linux image)</li>
<li>BlueLine Innovations PowerCost Monitor™ WiFi Bridge</li>
<li>BlueLine Innovations Energy Monitor outside meter sensor</li>
</ul>
<br />
Here are the BeagleBone and the PowerCost Monitor:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbEsgud2W45glub_LYaidBgTmdqP53h46wKti9s6BvpLAbwUVGXXq3qdrazVVL8ywwJOY0gKxdmwfIrtEhKbS76AvP7Tt34hKrB9mpqF4EXcaGVXtVWYoEL3gzgrK1-A5LVWpkTvHypF4/s1600/gateway+and+beaglebone.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbEsgud2W45glub_LYaidBgTmdqP53h46wKti9s6BvpLAbwUVGXXq3qdrazVVL8ywwJOY0gKxdmwfIrtEhKbS76AvP7Tt34hKrB9mpqF4EXcaGVXtVWYoEL3gzgrK1-A5LVWpkTvHypF4/s1600/gateway+and+beaglebone.jpg" height="384" width="640" /></a></div>
<br />
Here is the outdoor portion strapped onto my utility power meter. Seems a bit funky, but it has been out there for a few years now.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitpQ6EUIj0K27_VJwS7W7GYvUr9NDrHPOITUvI9ddA0LYh-8bV2p8ssJjKxfd-0gGohp7y99SZRXndCeiUsh6asFXR0GcP7f6EnrOZPoq_wS0J36PjDL-f8ZFMB30gLAOiQSICt4d0HYk/s1600/outdoor+monitor.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitpQ6EUIj0K27_VJwS7W7GYvUr9NDrHPOITUvI9ddA0LYh-8bV2p8ssJjKxfd-0gGohp7y99SZRXndCeiUsh6asFXR0GcP7f6EnrOZPoq_wS0J36PjDL-f8ZFMB30gLAOiQSICt4d0HYk/s1600/outdoor+monitor.jpg" height="400" width="351" /></a></div>
<br />
In this project, the Outdoor monitor communicates with the Wifi gateway over a proprietary protocol and then the Gateway serves up a small web page which looks like this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5Xh9mt28i0g4RP8kbZA2emUIyR7gufmBLJwwm1h1yspE0radcB3QL5A-ypn58F7tQj6q7STNvcWsDc74e5wv-wLdYxR8OYseUKUhhHvoFVnb1Ir7c_Vlk5LZFGehSmHb8eNB7vWkvWuo/s1600/PowerCost+web+interface.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5Xh9mt28i0g4RP8kbZA2emUIyR7gufmBLJwwm1h1yspE0radcB3QL5A-ypn58F7tQj6q7STNvcWsDc74e5wv-wLdYxR8OYseUKUhhHvoFVnb1Ir7c_Vlk5LZFGehSmHb8eNB7vWkvWuo/s1600/PowerCost+web+interface.jpg" height="480" width="640" /></a></div>
<br />
The Node.js running on the BeagleBone captures the webpage at regular intervals, stores the temperature and usage data in a Tingo database (more on that later) and then serves up a web page that shows a table of recent results with a graph. Here is the layout:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTF_yqNKpoLV2pq3MSc_Rx2KRfp1I_kqlZnAg6LMNMiVN0DYomxnT3M5z8pd4agPNqqIc397qwgmUFZlM7GjvUGerQYLnAoh0fPI121syqqrHKI_mgWQktD8EJDZkfLmliG1u9V87vnVA/s1600/Connectivity+diagram.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTF_yqNKpoLV2pq3MSc_Rx2KRfp1I_kqlZnAg6LMNMiVN0DYomxnT3M5z8pd4agPNqqIc397qwgmUFZlM7GjvUGerQYLnAoh0fPI121syqqrHKI_mgWQktD8EJDZkfLmliG1u9V87vnVA/s1600/Connectivity+diagram.jpg" height="480" width="640" /></a></div>
<br />
<h2>
The Web Page </h2>
<br />
This is the resulting power & temp usage monitor web page. It shows:<br />
<ul>
<li>a graph at the top of power use and temp over time</li>
<li>a listing of the data points with their timestamps </li>
<li>a settings area where the number of data samples and the interval between samples can be set.</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9kyDWgTKg-umHrf4w9tjQfms8VGB6eXeXsZ_21Bh00icPo7A8pjc7Fjk_JSMZ4X6Pk0SB-q5aZ04ZHBXLGMnqhI-MyOs09s7sH-iWH2N_evEI8-Qzm4ImtdDX-Fb7h9tqdjVp-RqQNTs/s1600/browser.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9kyDWgTKg-umHrf4w9tjQfms8VGB6eXeXsZ_21Bh00icPo7A8pjc7Fjk_JSMZ4X6Pk0SB-q5aZ04ZHBXLGMnqhI-MyOs09s7sH-iWH2N_evEI8-Qzm4ImtdDX-Fb7h9tqdjVp-RqQNTs/s1600/browser.jpg" height="532" width="640" /></a></div>
<br />
<br />
<h2>
The code</h2>
<h2>
</h2>
This project makes use of many different Node.js and JavaScript library. Specifically:<br />
<br />
<ul>
<li><a href="http://nodejs.org/" target="_blank">Node.js </a>- asynchronous, non-blocking, server-side JavaScript</li>
<li><a href="http://expressjs.com/" target="_blank">Express </a>- web framework for Node.js</li>
<li><a href="http://jade-lang.com/" target="_blank">Jade </a>- HTML templating engine for Node.js</li>
<li><a href="http://www.tingodb.com/" target="_blank">TingoDB</a> - This is a "noSQL" database alternative to MongoDB. (I couldn't get Mongo to run on the BeagleBoard Angstrom release, but TingoDB works fine - probably because it is written entirely in JavaScript.)</li>
<li><a href="https://github.com/sergeyksv/tungus" target="_blank">Tungus </a>- This is a driver to allow use of the Mongoose implements mongoose.js driver API.</li>
<li><a href="http://mongoosejs.com/" target="_blank">Mongoose </a>- object modelling tool for Mongo (in this case being used with TingoDB via Tungus - say that five times fast!) </li>
<li><a href="https://github.com/mikeal/request" target="_blank">Request </a>- A simple HTTP client for Node.js</li>
<li><a href="http://www.flotcharts.org/" target="_blank">Flot </a>- A charting package for jQuery</li>
<li><a href="http://jquery.com/" target="_blank">jQuery</a> - JavaScript library for HTML document manipulation</li>
</ul>
Getting all these elements to work together was a fair challenge given my complete ignorance at the start of this project!<br />
<br />
I won't bother with a complete step-by-step build process here. There are many number of great tutorials on the web that walk through creation of Node.js/Express/Jade websites. Christopher Buecheler <a href="http://cwbuecheler.com/web/tutorials/2013/node-express-mongo/" target="_blank">has a great one over here, for instance.</a> Suffice it to say, first install Node.js, then use npm to install all the dependencies. If you get error messages with the npm installation, Google them and you will pretty much always find an answer. After that, I used Express to create the basic shell of the website.<br />
<br />
This is a comparatively simple, single page Node.js project. Besides the libraries above, there are 4 critical files: <br />
<ul>
<li>app.js</li>
<li>layout.jade</li>
<li>index.jade</li>
<li>style.css</li>
</ul>
<br />
The complete source code files are at the end of the
article and I will highlight critical snippets of code to help understand how it all hangs together. Other code in the files (especially app.js) are set up by Express when you create the website.<br />
<br />
Once I had the basic Express/Jade site built and included all the dependencies, I ended up with a directory structure that looked like:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdStV37OX1Xe7SGPA-wulcqBpxHq9JbZS_tzg6ozZ604-Yh-eK7xE4qpiX672YDibfU7AGoLgKtBwcVGh7QbGkHGSrzn9FDYHXIEy7yCZuBSu205gk0qtd3hbAusRJ5V5jx8XNTcqP8NA/s1600/directory_structure.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdStV37OX1Xe7SGPA-wulcqBpxHq9JbZS_tzg6ozZ604-Yh-eK7xE4qpiX672YDibfU7AGoLgKtBwcVGh7QbGkHGSrzn9FDYHXIEy7yCZuBSu205gk0qtd3hbAusRJ5V5jx8XNTcqP8NA/s1600/directory_structure.jpg" height="400" width="261" /></a></div>
<br />
<h3>
App.js notes:</h3>
<h3>
</h3>
App.js is the server code that runs the whole thing. It also initializes the database, sets environment variables, uses the Request library to extract data from the Wifi PowerCost Monitor website and store that in the TingoDB database. Finally, when the gets a "GET" request from a browser, it renders the data on to the Jade engine.<br />
<br />
Here are the sections that manage the database. This code links to the dependencies:<br />
<br />
<pre class="cpp" name="code">
var tungus = require('tungus');
var Engine = require('tingodb');
var mongoose = require('mongoose');
</pre>
<br />
This code initializes Mongoose with Tingo and names the database:<br />
<br />
<pre class="cpp" name="code">
var db = mongoose.connect('tingodb://readingsdb');
</pre>
<br />
<br />
Then Mongoose connects and prints to the console:
<br />
<br />
<pre class="cpp" name="code">
mongoose.connect('tingodb://readingsdb', function (err){
if (!err) {
console.log('connected to databse');
} else {
throw err;
}
});
</pre>
<br />
<br />
Then we set up the schema for the database:<br />
<br />
<pre class="cpp" name="code">
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var ReadingsSchema = new Schema({
time: { type : Date, default: Date.now },
usage2: Number,
temp2: Number
});
var Readings = mongoose.model('Readings', ReadingsSchema);
</pre>
<br />
<br />
I decided to set the refresh rate (how often the monitor browser reloads) and the interval time (how long between data samples) via environment variables, which I do here:<br />
<br />
<code></code><br />
<pre class="cpp" name="code">
process.env['SAMPLES'] = '15';
process.env['REFRESHINT'] = '120';
</pre>
<br />
The screen scrape and data extraction is done by the getData function which uses the Request library to load the PowerCost Monitor webpage into a variable, then uses simple JavaScript string commands to isolate the two values we want. It loads the values into the database created above and used a JavaScript setInterval function to repeat the process however often has been set for the refresh rate.<br />
<br />
One minor note is I find I have to subract 2 from the temperature I get from the PowerCost Wifi Monitor to get the correct temperature. It just seems to be a slight calibration issue.<br />
<br />
<code></code><br />
<pre class="cpp" name="code">
function getData (){
request({uri: "http://192.168.1.33/pcmconfig.htm"}, function (error, response, body){
if (!error && response.statusCode == 200) {
var n = body.search("Present Demand");
usage = body.substr((n+44),5);
usage = usage.trim();
console.log(usage);
var n2 = body.search("Sensor Temp");
temp = body.substr((n2+40),3);
temp = temp.trim();
temp = temp - 2;
console.log(temp);
var readingInfo = new Readings({
temp2: temp,
usage2: usage
});
readingInfo.save(function(err, readingInfo){
if (err) return console.error(err);
console.dir(readingInfo);
});
setTimeout(getData,(process.env.REFRESHINT * 1000));
}});
}
//run the above function
getData();
</pre>
<br />
<br />
<br />
Now we have our data and stored it into a database so we need to render it over to Jade. When a GET request is received from a browser, this function is run:<br />
<br />
<pre class="cpp" name="code">
app.get('/', function(req, res){
Readings.find({}, {}, { sort: { 'time' : -1}, limit: process.env.SAMPLES }, function(err, readings) {
if (err) return console.error(err);
res.render('index',
{ title: 'Power Usage and Temp from PowerCost Monitor',
refreshRate: process.env.REFRESHINT,
sampleNum: process.env.SAMPLES,
readings: readings
});
}
);
}
);
</pre>
<br />
<br />
This renders the title, the environment variables and an object with the actual temp & power readings over to Jade via the "res.render" function.<br />
<br />
<br />
If the user wants to change the sample interval or the refresh rate, they enter them in the the "Settings" fields and when you hit "Submit" it triggers the app.post function, whichwill set the refresh rate and sample number environment variables and then re-render the page:<br />
<br />
<pre class="cpp" name="code">
app.post('/', function(req, res){
process.env['SAMPLES'] = req.param("samples");
process.env['REFRESHINT'] = req.param("interval");
console.log('Samples is set to: ' + process.env.SAMPLES);
console.log('Refresh is set to: ' + process.env.REFRESHINT);
Readings.find({}, {}, { sort: { 'time' : -1}, limit: process.env.SAMPLES }, function(err, readings) {
if (err) return console.error(err);
res.render('index',
{ title: 'Power Usage and Temp from PowerCost Monitor',
refreshRate: process.env.REFRESHINT,
sampleNum: process.env.SAMPLES,
readings: readings
});
}
);
});
</pre>
<br />
Finally, app.js contains the code to actually run the webserver, which is just as Express generated it:<br />
<br />
<pre class="cpp" name="code">
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
</pre>
<br />
<br />
<h3>
Layout.jade</h3>
<h3>
</h3>
Over the course of development, I went back and forth over whether to include the bulk of the client-side JavaScript in the Layout.jade file (essentially the headers of the webpage) or at the end. After reading a few articles (such as <a href="http://flippinawesome.org/2013/05/06/5-things-you-should-stop-doing-with-jquery/" target="_blank">this one</a>) , I decided to put the main client-side JS at the end of the page (in this case, the Index.jade file) and it does seem to perform better.<br />
<br />
Not really too much to the Layout.jade file, but note this line:<br />
<br />
<pre class="cpp" name="code">
meta(http-equiv="refresh" content="#{refreshRate}" )
</pre>
<br />
It sets the refresh rate based on the environment variable mentioned earlier. I tried to go with having the table and the Flot chart in DIVs and just refreshing the DIVs, but this seemed to not work easily so I will tackle this in the future.<br />
<br />
<h3>
Index.jade</h3>
<h3>
</h3>
This is the other major working part of this project and comes in two parts. First is the Jade portion:<br />
<br />
It took me a while to learn how to get the variables to come across properly and to get used to how finicky Jade is about <u>spaces</u>!
The coding and hierarchy for the resulting HTML page rely entirely on
how you have used spaces to indent, so you have to be very careful! Also note that I have set up the size of the placeholder DIV for the Flot chart. It seems like you need to do this to ensure the chart generates properly. <br />
<br />
extends layout<br />
<br />
<pre class="cpp" name="code">
block content
h1= title
h2 Graph over time
br
#placeholder(style='width:700px;height:300px')
h2 Table of results
#tablediv
table
thead
tr
td
b Timestamp
td
b Temperature
td
b Power usage
tbody
each reading in readings
tr
td #{reading.time}
td #{reading.temp2}
td #{reading.usage2}
</pre>
<br />
<br />
<br />
The section below is the form for doing the settings. The thing to notice here is that this form also picks up the current values of the environment variables for refresh and interval. The defaults are 15 samples and an interval of 180 seconds.<br />
<br />
<pre class="cpp" name="code">
h2 Settings
form#dataSamples(name="dataSamples",method="post",action="/")
label(for "samples") Data samples to graph:
input#setSamplesInput(type="text", placeholder="samples", name="samples", value="#{sampleNum}")
br
label(for "interval") Time between refreshes (seconds):
input#setIntervalInput(type="text",placeholder="interval", name="interval", value="#{refreshRate}")
br
button#btnSubmit(type="submit") submit
</pre>
<br />
Then at the bottom of the Index.jade file is the client-side JavaScript which is really what makes everything happen. This points to the jQuery and Flot libraries (which are in the /public folder in the directory tree).<br />
<br />
When the page finishes loading, this translates the data object of the database readings from the app.js res.render statement into two separate arrays (one for temp and one for power usage) using the JSON.stringify command so they are ready for plotting. It uses the $.plot function in the Flot library to create the plot. It also sets the labeling and axis formatting as well. (Note that to get the time-based X Axis, you need to include the Flot Time library as well as Flot itself.<br />
<br />
<pre class="cpp" name="code">
script(src='jquery/dist/cdn/jquery-2.1.0.js')
script(src='flot/jquery.flot.js')
script(src='flot/jquery.flot.time.js')
script.
window.onload = function(){
if (window.console)console.log("Executing script");
var temp_data = [];
var usage_data = [];
var reading_data = !{JSON.stringify(readings)};
for (var i = 0; i < #{sampleNum}; i++)
temp_data.push([new Date(reading_data[i].time), reading_data[i].temp2]);
for (var i = 0; i < #{sampleNum}; i++)
usage_data.push([new Date(reading_data[i].time), reading_data[i].usage2]);
$.plot($("#placeholder"), [{
label: "Temperature (C)",
data: temp_data,
},{
label: "Power Usage (KW)",
data: usage_data,
yaxis: 2
}], {
xaxes: [{
mode: "time",
timeformat: "%H:%M",
timezone: "browser"
}],
yaxes: [{},{position: "right"}]
});
};
</pre>
<br />
I suspect there is a simpler way to do this that allows Flot to pick up the data directly from the rendered data object without having to use JSON.stringify array process, but I couldn't find it! <br />
<br />
<h3>
Style.css</h3>
<br />
Not really much to note here. Just some basic formatting for the few elements I am using. The complete listing is at the end.<br />
<br />
<h2>
Conclusion and next steps</h2>
<br />
So, that's it for now. As I said at the beginning, this code is certainly not complete! It needs a lot more error checking and testing in failure modes. For instance, what if the network is down or the PowerCost Wifi Monitor is not responding?<br />
<br />
Astute observers will also note that the way I have done the "Settings" feature only really works with one browser window at a time. When I have tested changing settings with multiple browsers I have found that it sometimes doesn't pick up the environment variables properly and the refresh rate gets into an "undefined" state and then it just continually loops without any pause - obviously bad!<br />
<br />
My next steps will be to learn the Socket.io library in Node.js and then to start connecting the BeagleBone to my DigiX board which I got through Kickstarter a few months ago! Check it out here:<br />
<br />
<iframe frameborder="0" height="480" scrolling="no" src="https://www.kickstarter.com/projects/digistump/digix-the-ultimate-arduino-compatible-board-with-w/widget/video.html" width="640"> </iframe>
<br />
<br />
<h2>
Complete source code</h2>
<h2>
</h2>
<h3>
app.js</h3>
<h3>
</h3>
<br />
This is the complete source code for the app.js file discussed above.
<br />
<pre class="cpp" name="code">
/**
* Module dependencies.
*/
//Load Dependencies
var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');
var tungus = require('tungus');
var Engine = require('tingodb');
var mongoose = require('mongoose');
var request = require("request");
//Initialize Mongoose with Tingo
var db = mongoose.connect('tingodb://readingsdb');
//Set up some global variables
var usage;
var temp;
var readings;
//Set environment variables for defaults
process.env['SAMPLES'] = '15';
process.env['REFRESHINT'] = '120';
//Mongoose connects to database
mongoose.connect('tingodb://readingsdb', function (err){
if (!err) {
console.log('connected to databse');
} else {
throw err;
}
});
//Set up schema for database
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var ReadingsSchema = new Schema({
time: { type : Date, default: Date.now },
usage2: Number,
temp2: Number
});
var Readings = mongoose.model('Readings', ReadingsSchema);
//Do the simple screen scrape and pop the results into the database.
//Rinse and repeat according to the environment variable for refresh rate
function getData (){
request({uri: "http://192.168.1.33/pcmconfig.htm"}, function (error, response, body){
if (!error && response.statusCode == 200) {
var n = body.search("Present Demand");
usage = body.substr((n+44),5);
usage = usage.trim();
console.log(usage);
var n2 = body.search("Sensor Temp");
temp = body.substr((n2+40),3);
temp = temp.trim();
temp = temp - 2;
console.log(temp);
var readingInfo = new Readings({
temp2: temp,
usage2: usage
});
readingInfo.save(function(err, readingInfo){
if (err) return console.error(err);
console.dir(readingInfo);
});
setTimeout(getData,(process.env.REFRESHINT * 1000));
}});
}
//run the above function
getData();
//Below are all set by default in Express
var app = express();
// all environments
app.set('port', process.env.PORT || 3008);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
//When the Submit button is pressed, it runs this function and re-renders the page
app.post('/', function(req, res){
process.env['SAMPLES'] = req.param("samples");
process.env['REFRESHINT'] = req.param("interval");
console.log('Samples is set to: ' + process.env.SAMPLES);
console.log('Refresh is set to: ' + process.env.REFRESHINT);
Readings.find({}, {}, { sort: { 'time' : -1}, limit: process.env.SAMPLES }, function(err, readings) {
if (err) return console.error(err);
res.render('index',
{ title: 'Power Usage and Temp from PowerCost Monitor',
refreshRate: process.env.REFRESHINT,
sampleNum: process.env.SAMPLES,
readings: readings
});
}
);
});
//This serves up the page and renders the variables into the Jade template engine.
app.get('/', function(req, res){
Readings.find({}, {}, { sort: { 'time' : -1}, limit: process.env.SAMPLES }, function(err, readings) {
if (err) return console.error(err);
res.render('index',
{ title: 'Power Usage and Temp from PowerCost Monitor',
refreshRate: process.env.REFRESHINT,
sampleNum: process.env.SAMPLES,
readings: readings
});
}
);
}
);
//Start up the server!
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
</pre>
<h3>
Layout.jade</h3>
<br />
<br />
<pre class="cpp" name="code">
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
meta(http-equiv="refresh" content="#{refreshRate}" )
body
block content
</pre>
<br />
<h3>
Index.jade</h3>
<br />
<pre class="cpp" name="code">
extends layout
block content
h1= title
h2 Graph over time
br
#placeholder(style='width:700px;height:300px')
h2 Table of results
#tablediv
table
thead
tr
td
b Timestamp
td
b Temperature
td
b Power usage
tbody
each reading in readings
tr
td #{reading.time}
td #{reading.temp2}
td #{reading.usage2}
h2 Settings
form#dataSamples(name="dataSamples",method="post",action="/")
label(for "samples") Data samples to graph:
input#setSamplesInput(type="text", placeholder="samples", name="samples", value="#{sampleNum}")
br
label(for "interval") Time between refreshes (seconds):
input#setIntervalInput(type="text",placeholder="interval", name="interval", value="#{refreshRate}")
br
button#btnSubmit(type="submit") submit
script(src='jquery/dist/cdn/jquery-2.1.0.js')
script(src='flot/jquery.flot.js')
script(src='flot/jquery.flot.time.js')
script.
window.onload = function(){
if (window.console)console.log("Executing script");
var temp_data = [];
var usage_data = [];
var reading_data = !{JSON.stringify(readings)};
for (var i = 0; i < #{sampleNum}; i++)
temp_data.push([new Date(reading_data[i].time), reading_data[i].temp2]);
for (var i = 0; i < #{sampleNum}; i++)
usage_data.push([new Date(reading_data[i].time), reading_data[i].usage2]);
$.plot($("#placeholder"), [{
label: "Temperature (C)",
data: temp_data,
},{
label: "Power Usage (KW)",
data: usage_data,
yaxis: 2
}], {
xaxes: [{
mode: "time",
timeformat: "%H:%M",
timezone: "browser"
}],
yaxes: [{},{position: "right"}]
});
};
</pre>
<br />
<h3>
Style.css</h3>
<br />
<pre class="cpp" name="code">
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
table
{
border-collapse:collapse;
}
table,th, td
{
border: 1px solid black;
}
td
{
padding:6px;
}
</pre>
<br />
<br />Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-82073523229342853862013-10-23T10:06:00.003-07:002013-10-23T10:07:13.527-07:00Resurecting the Arduino/Xbee/Gumstix sensor networkAbout four years ago, I built a simple sensor network that used a couple of Arduinos, a Gumstix Linux mini computer and Xbee wireless that acted as an indoor-outdoor temperature display. A couple of years back, the outdoor part of the system stopped working. I poked it and prodded it and finally just decided the Arduino clone I was using had died.<br />
<br />
Finally, after it had sat on my shelf for a couple of years, I figured I should start to look at salvaging some of the parts for something new. Just as a last chance, I once again unplugged the processor and juggled the wires and suddenly, it works like a charm!! As with most thing, the threat of imminent destruction got it moving!<br />
<br />
Anyway, in honour of it's miraculous recovery, I have fixed up my blog posts and reinserted the graphics Google lost for me. The posts can be found at:<br />
<br />
<ol>
<li><a href="http://opensource-torchris.blogspot.ca/2009/01/my-arduino-sensor-network.html" target="_blank">Overview of the sensor network</a></li>
<li><a href="http://opensource-torchris.blogspot.ca/2009/01/indoor-outdoor-temp-display.html" target="_blank">Indoor Outdoor temp display</a></li>
<li><a href="http://opensource-torchris.blogspot.ca/2009/01/gumstix-xbee-modem-hardware.html" target="_blank">Xbee -Gumstix hardware</a></li>
<li><a href="http://opensource-torchris.blogspot.ca/2009/01/gumstix-arduino-software.html" target="_blank">Gumstix-Arduino software</a></li>
</ol>
<br />
This project is pretty old now and obviously could be done much more easily with newer models of Arduino like the Yun, but it does show some good basics on getting Zigbee and PHP and Arduino all working together. Apparently, too, it is capable of great longevity!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-43519383444554199782013-10-06T18:12:00.001-07:002013-10-06T18:12:53.726-07:00Apologies and fixes!Hello all,<br />
<br />
It has been a very, very, very long time since I have updated this blog, but I am still around!<br />
<br />
I just wanted to take a moment to apologize to the few of you who might stumble onto this blog and find that the graphics are missing. It seems a while ago Google changed the way it stored embedded graphics in Blogger and it messed up all the links in most of my posts! This happened a while ago and I hadn't visited in a while, but I finally got around to fixing up at least most of the posts.<br />
<br />
I know a few people have gotten to my blog from Arduino-related searches, so I have focused on trying to fix up my Arduino related posts - although most of these are on rather antiquated hardware.<br />
<br />
Hopefully soon I will get to some projects with the Digistump DigiSpark which is a small form factor Arduino compatible board. You can read more about it <a href="http://digistump.com/">here</a>.<br />
<br />
Thanks!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-19197421100875525992011-10-01T19:23:00.000-07:002011-10-01T19:23:22.078-07:00Test Driving the Nissan LeafWell, I suppose once a year or so I should update this blog! Sorry for the long absence.<br />
<br />
Anyway, I had a chance today to drive the new, all-electric Nissan Leaf as part of their promotional tour of Canada with the new car. I'm not much of a car review or automotive expert, but I figured it is still worth recording my impressions since this is such a unique new vehicle. These are just my overall impressions of a quick tour & test drive. You can find loads of more detailed reviews by searching online.<br />
<br />
<b>Exterior </b><br />
<br />
Definitely a nice look to the vehicle. It doesn't scream oddball, or high-tech. If anything, it looks like a slightly bigger version of the Versa. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCOMN4Ubt8sV0_crPPwXkZGdLt6dtoWk8WWHayl57XGdAUMmz9_-_d57UcBtzfMhCDGj1ebhkm-dYOSwMAEPVy3zE1NV31-pzVNAekJyyURfmpOBQc3J2onIGf-cI72LLSkC9Jiix8EW4/s1600/IMG_2546.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCOMN4Ubt8sV0_crPPwXkZGdLt6dtoWk8WWHayl57XGdAUMmz9_-_d57UcBtzfMhCDGj1ebhkm-dYOSwMAEPVy3zE1NV31-pzVNAekJyyURfmpOBQc3J2onIGf-cI72LLSkC9Jiix8EW4/s640/IMG_2546.JPG" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="separator" style="clear: both; text-align: left;"> The main exterior difference? No tailpipe at all!!</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/AVvXsEgRrIZwZx1iEcxYZuNUN4l2JXW7cG1oeX9ZGI9_KSpfgxXGFkNplklRQMtjxP6e9qDS3Yt0AMr7Zra25ipYcMUOWwsFJH-pUS_9tPdWDFOmy8REJpuhOjPR6ftmTQFybIVl9grPuZEP7Zs/s1600/IMG_2560.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRrIZwZx1iEcxYZuNUN4l2JXW7cG1oeX9ZGI9_KSpfgxXGFkNplklRQMtjxP6e9qDS3Yt0AMr7Zra25ipYcMUOWwsFJH-pUS_9tPdWDFOmy8REJpuhOjPR6ftmTQFybIVl9grPuZEP7Zs/s640/IMG_2560.JPG" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"><br />
</div><div class="separator" style="clear: both; text-align: left;">The engine compartment is actually kind of funny since they have mocked it up to look as if it is an internal combustion engine. The guy at Nissan said that under the aluminium cover that looks like a valve cover is just a printed circuit board! On the front of this photo you can see the connector for the charging station. The battery you see there is obviously not the battery that drives the engine! That is just for the accessories like the a/c, lights & heating. You can get an optional solar panel on the spoiler that charges this battery - but not the main big batteries, which are in the back.</div><br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCnuGbXqaCXiXoZMkrYWy0Xfx8__drcLYriBAW-2RwcGcAl51xhi9XOOuF2HWDh-u83-fMh7qghYy4D0vEcRmlmAbbk2gmN-v61s5wRGW0Ueh8xb938mEqwW7BbvBYC6bMpZ43Q8uoprI/s1600/IMG_2549.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCnuGbXqaCXiXoZMkrYWy0Xfx8__drcLYriBAW-2RwcGcAl51xhi9XOOuF2HWDh-u83-fMh7qghYy4D0vEcRmlmAbbk2gmN-v61s5wRGW0Ueh8xb938mEqwW7BbvBYC6bMpZ43Q8uoprI/s640/IMG_2549.JPG" width="640" /></a></div><br />
<b>Interior</b><br />
<br />
Inside the car has a very "deluxe" feel. Nice leather on the steering wheel, pleasant textures everywhere an nice quality plastic on the controls. I wonder how long it would take me to get all that nice pale colored surfaces all grungy! Over time, I think a darker interior might show the wear a bit less, but on a new car it just looks spiffy!<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYehmOdy8wvKWL5RLYba7pDfENxMdMCI0bwSN4_-2ZCnhb9HPlNi2uWn_IhylFOc2Xq14EKIz4sNlN5andjhIx2Fpz2DN8xaA8WPADjTWvfjgCR51me3_wOH9aw6hlVSItP2Q-2qzsb0Q/s1600/IMG_2550.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYehmOdy8wvKWL5RLYba7pDfENxMdMCI0bwSN4_-2ZCnhb9HPlNi2uWn_IhylFOc2Xq14EKIz4sNlN5andjhIx2Fpz2DN8xaA8WPADjTWvfjgCR51me3_wOH9aw6hlVSItP2Q-2qzsb0Q/s640/IMG_2550.JPG" width="640" /></a></div><br />
This also gives you a sense of the control layout, which is very sensible and easy to use. My only quibble is the little shift controller knob which you can see in the lower right of the picture above. You flick it to the left and down to go into "drive" do that twice and you are in "eco mode" and flick it left and up to go into reverse. Cute, but why would it have been hard to just put a good old shift lever labelled "D", "D-E" and "R"? It isn't hard to get used to, but it is one of the few things that doesn't seem ergonomic.<br />
<br />
<b>Interface</b><br />
<br />
I did get a very short test drive, so I didn't get too much time to play with the displays & interface. The main display shows your speed & power level in a sensible way:<br />
<b> </b><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinIeK2jrUq4wtq6ZZLqYkE41yUjCS3LDXJ2NkeuFsObFJB_5ZgT0toUTr6rOv0xq3Os8O1qmCb_Z-ZYDs2ZdvHmJEolZNGx8ZvAp9tmD0W9Y93DF3R5o64vM8evY611Q0T3ZoVa756HVI/s1600/IMG_2553.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinIeK2jrUq4wtq6ZZLqYkE41yUjCS3LDXJ2NkeuFsObFJB_5ZgT0toUTr6rOv0xq3Os8O1qmCb_Z-ZYDs2ZdvHmJEolZNGx8ZvAp9tmD0W9Y93DF3R5o64vM8evY611Q0T3ZoVa756HVI/s400/IMG_2553.JPG" width="400" /></a></div><br />
The main touchscreen panel<b> </b>provides well organized controls - with an obvious focus on power consumption and range.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR2qu66_tNMWRuEJKvA79LyAY084MX4QWFVEcQTLiBQ5wiKbTjiehuZNiJ0RXacl0Ts1-s2oPCAjbhZJ3reayfZcyJmL_kw_Nm0ctpvl7Nja7PYL0tk98FI9M7fhiEEPi3ip7bpdyvCeQ/s1600/IMG_2555.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR2qu66_tNMWRuEJKvA79LyAY084MX4QWFVEcQTLiBQ5wiKbTjiehuZNiJ0RXacl0Ts1-s2oPCAjbhZJ3reayfZcyJmL_kw_Nm0ctpvl7Nja7PYL0tk98FI9M7fhiEEPi3ip7bpdyvCeQ/s640/IMG_2555.JPG" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"></div><br />
Again, I didn't get a chance to play a lot with this system, but my only concern was it seemed rather busy and I wonder about "driver workload" or distraction with so many options.<br />
<br />
<b>The Drive</b><br />
<br />
What was it like to actually drive? I did get a quick drive of about 10 minutes or so which took me on some city streets and a quick jaunt on the highway and I have to say it is very impressive indeed. You start it with the power button you can see on the lower left of the above photo (maybe "boot it up" is more appropriate) and then pop it into drive with the selector and the funny thing is <b></b>it just sits there dead silent! It took me a minute to realize that, of course, it's not like the engine would idle or anything - the motor does <u>nothing</u> until you press on the accelerator!<br />
<br />
Once you do hit the accelerator, the driving experience is really very normal and pleasant. It is certainly the absolute quietest vehicle I have ever driven and the weight of the batteries really gives it a solid feel. I was reminded of the old saying about the Rolls Royce that all you could hear was the noise of the clock ticking on the dashboard! The pickup was very brisk since the electric motor delivers huge torque immediately, but all you hear is just a little whine as it runs up. What impressed me the most was just how comfortable and natural it felt to drive - there was really no feeling of having to transition or re-learn how to drive. I did try the "Eco" mode and that felt like it just put a wet blanket over all the power and acceleration, but then, that is what it is supposed to do - preserve your battery over performance.<br />
<br />
<b>Will I buy one?</b><br />
<br />
Well, sadly, probably not soon. For once thing, there likely won't be real volumes of the vehicles available in Canada until later next year, but at the end of the day the range thing just doesn't quite work for me - although it is pretty close.<br />
<br />
It is rated at 160 kms on a charge and the Nissan people told me that some testing showed under the best conditions it could do almost 190 km, but I really wonder what the real-world range will be in the extremes of the Canadian winter or our hot summers. I live in a smaller city and I often drive to surrounding communities that are 50 or 60 km away, which would start to push the range. That being said, I work from home so the total amount that I drive in a month is pretty limited, so why spend the extra to get an EV which won't be driven when I could get a very efficient gasoline vehicle for less? However, if I was commuting, say 25 kms or less a day, the range would really be quite acceptable and the extra cost justified when gas starts to creep up to $1.50 a liter. As well, the Nissan folks make a very good point that the car is really very simple mechanically and will need little by way of regular servicing compared to an internal combustion engine.<br />
<br />
Ultimately, I don't see this as my next car (I plan to buy in the next six months or so), but if the <u>next</u> model gets up to, say, 220 km, I would definitely think hard about it. Driving away in my manual transmission Honda (which I like a lot) felt rather like climbing into a Model T.Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-75668531897760895962010-08-11T11:27:00.000-07:002010-08-11T11:27:22.745-07:00PRK - 11 weeks post surgeryWell, this is probably my last post on this topic.<br />
<br />
I went to my eye doctor today and he is very pleased with the progress. My left eye is basically perfect, but my right eye has a very slight -0.025 diopter nearsightedness, but this is starting from over -8 in both eyes. He said the right eye is still healing and this is just the tail end of the process. I am now down to only doing the FML steroid drops once a day.<br />
<br />
My observation is that my vision - especially at a distance - is spectacular! Not having minor optical distortions or smudges or scratches from glasses is great and having back all of my peripheral vision is also fantastic. One of the best items of recent progress was that I found I didn't need reading glasses to work on the computer and I could get all my screens back to their regular fonts sizes!!<br />
<br />
There are still some downside. I do have haloing at night which is irritating while driving but should clear up over the next few months. I do have to wear reading glasses now for reading in bed or playing games on my iPhone. Having to remember my reading glasses so I can read menus at restaurants and so on is irritating. Hopefully this will get slightly better, but I expect this is something I may be stuck with at my age. I do need to keep using artificial tears regularly, but my eyes don't seem to feel dry or uncomfortable except at the end of a long day.<br />
<br />
When I was researching the procudure I found several useful diary blogs like this through Google. So, for those who might come upon this blog from the interwebs looking for info on PRK surgery, here are links to all the entries in sequential order:<br />
<ul><li><a href="http://opensource-torchris.blogspot.com/2010/05/prk-laser-srugery-prologue.html">PRK Laser Sugery - Prologue</a></li>
<li><a href="http://opensource-torchris.blogspot.com/2010/05/prk-surgery-day.html">Surgery Day </a></li>
<li><a href="http://opensource-torchris.blogspot.com/2010/05/prk-surgery-day-1-after.html">Post-surgery - Day 1 after</a></li>
<li><a href="http://opensource-torchris.blogspot.com/2010/05/prk-post-surgery-day-2.html">Post surgery Day 2</a></li>
<li><a href="http://opensource-torchris.blogspot.com/2010/05/prk-post-surgery-day-3.html">Post surgery Day 3</a></li>
<li><a href="http://opensource-torchris.blogspot.com/2010/06/prk-post-surgery-day-4.html">Post surgery Day 4</a></li>
<li><a href="http://opensource-torchris.blogspot.com/2010/06/prk-post-surgery-day-5.html">Post surgery Day 5</a></li>
<li><a href="http://opensource-torchris.blogspot.com/2010/06/prk-1-week-post-surgery.html">1 week post surgery</a></li>
<li><a href="http://opensource-torchris.blogspot.com/2010/06/prk-2-weeks-post-surgery.html">2 weeks post surgery</a></li>
<li><a href="http://opensource-torchris.blogspot.com/2010/07/prk-5-weeks-post-surgery.html">5 weeks post-surgery</a></li>
</ul>It's been quite a journey!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-64407374556615764222010-08-05T09:02:00.000-07:002013-03-12T17:51:36.049-07:00Arduino-Twilio Dialer ApplicationWell, it has been a while since I posted an Honest-to-God Arduino project here! What with getting my eye surgery done and one thing and another, this has taken me quite a while. Besides, there were more than a few challenges in getting it all to work along the way!<br />
<br />
This project is a concept for an information kiosk or unit that could be placed in a public area where passers-by could request information. A user enters their mobile phone number into the keypad and they are then immediately called by the Twilio cloud-based telephony system (check out Twilio here) and presented with a simple phone menu that allows them to:<br />
<ol>
<li>Talk to an operator</li>
<li>Leave a voice message</li>
<li>Receive an SMS message</li>
</ol>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-size: small;">Here is the hardware in it’s not very pretty form:</span></span></span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVodzenWCEIrqjYaT0BqPqlp93_5fGopG_AFG9xCTsn3cTjwdttJmSRdNVdlj8j8YDx5tsUDInfpzcv50QWoenDqmzzv5af1b4YFL9V4lj9WJ51Y2VJKaA42wEr0vYEK7Deq2rqTUCpRQ/s1600/Photo+lighter+cropped2.JPG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVodzenWCEIrqjYaT0BqPqlp93_5fGopG_AFG9xCTsn3cTjwdttJmSRdNVdlj8j8YDx5tsUDInfpzcv50QWoenDqmzzv5af1b4YFL9V4lj9WJ51Y2VJKaA42wEr0vYEK7Deq2rqTUCpRQ/s640/Photo+lighter+cropped2.JPG" width="460" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Here is the obligatory video showing how it works:<br />
<br />
<br />
<object height="385" width="480"><param name="movie" value="http://www.youtube.com/v/30x88IaxUaw&hl=en_US&fs=1"></param>
<param name="allowFullScreen" value="true"></param>
<param name="allowscriptaccess" value="always"></param>
<embed src="http://www.youtube.com/v/30x88IaxUaw&hl=en_US&fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></embed></object><br />
<br />
<b>Hardware</b><br />
<br />
The hardware consists of:<br />
<ul>
<li>Arduino Duemilanove</li>
<li>Arduino Ethernetshield</li>
<li>Sparkfun serial enabled 20 x 4 LCD display (<a href="http://www.sparkfun.com/commerce/product_info.php?products_id=9568">LCD-09568</a>)</li>
<li>Sparkfun 12 button keypad (<a href="http://www.sparkfun.com/commerce/product_info.php?products_id=8653">COM-08653</a>)</li>
</ul>
Here is the circuit diagram:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9d16a8HFhap-6Dfv-vQZkSSD-eo2Se5eSIfM7tj2DRj0zwcIELPx9FnljMK-KizW3l0ySDCnqVZVtbmZyZqRWUAvCwtEgGRRh3wvFbY0S-YvJnQNEyZu7dn6m2Aom6Z4MGFvJSxQpVGQ/s1600/twilio_dialer_schem.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9d16a8HFhap-6Dfv-vQZkSSD-eo2Se5eSIfM7tj2DRj0zwcIELPx9FnljMK-KizW3l0ySDCnqVZVtbmZyZqRWUAvCwtEgGRRh3wvFbY0S-YvJnQNEyZu7dn6m2Aom6Z4MGFvJSxQpVGQ/s640/twilio_dialer_schem.png" width="516" /></a></div>
<br />
The hookup of the keypad is based on <a href="http://www.instructables.com/id/Using-the-sparkfun-12-Button-keypad-with-the-ardu/">this article</a> by <a href="http://www.instructables.com/member/amando96/">amando96 </a>on Instructables. You will have to sign up for membership to see the whole article or you can just follow the above diagram. One odd thing I noticed was that the pull down resistors turned out to not be required. Originally I had them included and everything worked fine, then at a certain point in the development the keypresses started looping and I removed the resistors and everything worked fine!<br />
<br />
This just uses the standard “keypad.h” library found on the Arduino website, but slightly modified so the pins do not interfere with the Ethernetshield. There is also an Instructable post on multiplexing the button input to use fewer digital pins, but since I didn’t need any extra digital pins I figured this was simpler.<br />
<br />
The serial LCD is quite straightforward to hookup, but just note that I am using one of the A1 Analog pin as the Tx for the serial since I am not using any analog inputs anyway.<br />
<br />
<b>Software</b><br />
<br />
As with the previous Twilio-Arduino project, this one requires:<br />
<ul>
<li>An account with <a href="http://www.twilio.com/">Twilio</a> </li>
<li>A web-server with PHP</li>
<li> An Ethernetshield</li>
</ul>
The Arduino communicates with one PHP script running on my web-server, this then triggers Twilio to set up a call and Twilio then goes to my web-server and checks for another PHP file on what options to present to the user. It is a reasonably complex mesh of files that need to be setup, but actually the Twilio XML programming was easily the simplest part of the setup.<br />
<br />
This diagram shows the overall flow:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlwN4j4sGIv87KVBJsAbtQzvUlWD9bg1r7OCkObAv1pMuVqQA0YSSoU6_GEiONbxpOinBCQ8dFQfzx8ikorfP8ik8wKWjYitgo8-R9Fcsfhu-dfBq4uJ5LcwkannilACWcO2RSlA_FQ3g/s1600/Twilio_dialer.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlwN4j4sGIv87KVBJsAbtQzvUlWD9bg1r7OCkObAv1pMuVqQA0YSSoU6_GEiONbxpOinBCQ8dFQfzx8ikorfP8ik8wKWjYitgo8-R9Fcsfhu-dfBq4uJ5LcwkannilACWcO2RSlA_FQ3g/s640/Twilio_dialer.jpg" width="606" /></a></div>
<br />
<br />
<br />
<br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-size: small;">The first part is the Arduino software:</span> </span></span><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
</span></span><br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"></span></span><br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
<br />
</span></span><br />
<pre class="cpp" name="code">/*
* Twilio Arduino Information Service Dialer
* When used in conjunction with "dialer_twilio.php PHP script and the Twilio cloud-based
* telephony environment (www.twilio.com) this allows a user to enter a phone number into a keypad
* on an Arduino and have a call placed to their cell phone with various options for information.
*
* Uncomment the //Serial lines for troubleshooting/debug info.
*
* This code is in the public domain. Please provide credit if it is used
* in another project.
*
* written by Chris Armour, August 4th, 2010
*
* Full description psted at http://opensource-torchris.blogspot.com/
*
*/
//=====================Libraries=============/
#include <ethernet .h="">
#include <keypad .h="">
#include <newsoftserial .h="">
#include <textfinder .h="">
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //MAC address for Arduino
byte ip[] = { 192,168,0,34 }; // IP address you wish to assign to Arduino
byte server[] = { 192, 168, 0, 171 }; // IP address of your PHP server
int ServerPort = 55455;
const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};
byte rowPins[ROWS] = {5, 4, 3, 2}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {8, 7, 6}; //connect to the column pinouts of the keypad
boolean ConnectedState = false;
char DialNum[12]={"xxxxxxxxxxx"};
int a;
NewSoftSerial mySerial = NewSoftSerial(14,15); //osft serial running off of Analog Pin 1
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
Client client(server, ServerPort);
TextFinder finder( client);
void setup(){
Ethernet.begin(mac, ip);
// Serial.begin(9600);
mySerial.begin(9600);
mySerial.print(254, BYTE);
delay(100);
mySerial.print(1, BYTE);
delay(100);
mySerial.print("Started - Press any key to conect to the server."); //Display initial message on LCD.
}
void loop(){
char key = keypad.getKey();
if (key != NO_KEY){
if (ConnectedState == false){
if (client.connect()){
if(finder.find("results:") == true){
char c = client.read();
// Serial.print("Value of the connect code: ");
// Serial.println(c);
mySerial.print(254, BYTE);
delay(100);
mySerial.print(1, BYTE);
delay(100);
mySerial.print("Connected. ");
delay(2000);
WelcomeMsg();
//If connection successful, then print Welcome Msg and flip the connected state variable.
ConnectedState = true;
// Serial.println(DialNum);
}
}
else {
ConnectedState == false;
ConnectFailMsg(); //If connection fails, then print an error.
delay(2000);
}
}
if (key == '*') {
//When it picks up the "*" then it starts gathering the digits.
// Serial.println("Got the *");
EnterNumMsg();
do {
key = keypad.getKey();
if (key != NO_KEY){
delay(150);
// Serial.println(key);
mySerial.print(key); //Prints the keypad input to the LCD for user feedback.
DialNum[a] = key; //Puts the key info entered into an array.
a++;
}
} while (key != '#');
}
if (key == '#'){
//When the "#" is pressed, the info is sent over to the PHP script.
delay(250);
mySerial.print(DialNum);
client.println(DialNum); //Dialnum array sent to PHP.
if(finder.find("results:") == true){//PHP script evaluates number & sends back response.
char c = client.read();
// Serial.print("Value of the return code: ");
// Serial.println(c);
if (c == 'Y'){//Y back from the script means it is a valid number & success displayed.
NumAcceptedMsg();
}
if (c == 'N'){//If it is not a valid number, then N returned & error displayed.
NumNotAcceptedMsg();
// client.println(DialNum);
delay(3000);
WelcomeMsg();//Go back to Welcome message.
}
delay(200);
if(finder.find("results:") == true){
char c = client.read();
// Serial.print("Value of the return code: ");
// Serial.println(c);
if (c == 'O'){//If the call was successully placed display success then go back to Welcome.
NumCalledMsg();
delay(4000);
WelcomeMsg();
}
if (c == 'X'){//If the call couldn't be made then error message & return to welcome.
NumNotCalledMsg();
delay(3000);
WelcomeMsg();
}
}
// Serial.println(DialNum);
// Serial.println(a);
a = 0;
}
}
}
}
void WelcomeMsg(){
mySerial.print(254, BYTE);
delay(100);
mySerial.print(1, BYTE);
delay(100);
mySerial.print("Welcome to the Information service.Press the * key to start.");
}
void EnterNumMsg(){
mySerial.print(254, BYTE);
delay(100);
mySerial.print(1, BYTE);
delay(100);
mySerial.print("Enter your 10 digit phone number followed by #: ");
}
void ConnectFailMsg(){
mySerial.print(254, BYTE);
delay(100);
mySerial.print(1, BYTE);
delay(100);
mySerial.print("Connection Failed. Press any key to reconnect.");
}
void NumAcceptedMsg(){
// Serial.println("Number accepted by server");
mySerial.print(254, BYTE);
delay(100);
mySerial.print(1, BYTE);
delay(100);
mySerial.print("Number Accepted. ");
}
void NumNotAcceptedMsg(){
// Serial.println("Number NOT accepted by server");
mySerial.print(254, BYTE);
delay(100);
mySerial.print(1, BYTE);
delay(100);
mySerial.print("Number not valid.");
}
void NumCalledMsg(){
// Serial.println("Number called successfully.");
mySerial.print(254, BYTE);
delay(200);
mySerial.print(1, BYTE);
delay(200);
mySerial.print("Number called successfully.");
}
void NumNotCalledMsg(){
// Serial.println("Number cannot be called.");
mySerial.print(254, BYTE);
delay(100);
mySerial.print(1, BYTE);
delay(100);
mySerial.print("Number cannot be called.");
}</textfinder></newsoftserial></keypad></ethernet></pre>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
</span></span><br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"> This uses the Arduino Ethernet library to set up a client application, the Keypad library to get the keypresses, NewSoftSerial to send information to the LCD and finally the TextFinder library to parse the result codes from the server. I have put all the LCD messages into functions just to clean up the code since each message needs to have a screen clear send and some delay. I’m sure I could further clean that up to just have a function that did the screen clear, but this works.<br />
<br />
The Arduino client in term communicates with the PHP server program (dialer_twilio.php):<br />
<br />
</span></span></span></span></span></span></span></span><br />
<pre class="cpp" name="code"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;">request("/$ApiVersion/Accounts/$AccountSid/Calls",
"POST", array(
"Caller" => "XXXXXXXXXXXXXXX",
"Called" => $dial_num,
"Url" => "http://xxxxx.xxxxx.xxx/twi_test.php"
));
if($response->IsError){
echo "Error: {$response->ErrorMessage}\n";
socket_write($spawn, "results:X\n");
echo "Sent over X\n";
} else {
echo "Started call: {$response->ResponseXml->Call->Sid}\n";
socket_write($spawn, "results:O\n");
echo "Sent over O\n";
}
} else {
echo ("Invalid number\n");
socket_write($spawn, "results:N\n");
echo "Sent over N\n";
}
usleep(5000);
}
}while(true);
?>
</span></span></span></span></span></span></pre>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
<br />
<br />
Anything marked “XXXX” above is info on the account and phone numbers to be used.<br />
<br />
This script does a quick and dirty check if the number is valid (just making sure there are no stray “x” or “#” characters and it is the right length) then uses theTwilio REST API to set up the call. Twilio will go to the “twi_test.php” script for info on how to handle the call.<br />
<br />
So, when Twilio goes to twi_test.php it sees:</span></span></span></span><br />
<br />
</span></span><br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"></span></span><br />
<pre class="cpp" name="code"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"> header("content-type: text/xml");
echo "\n";
?>
<response>
<gather action="twi_action.php" method="POST" numdigits="1">
<say voice="woman">Hello. Welcome to the Arduino Information Service.</say>
<say voice="woman">Press 1 to be connected to an operator.</say>
<say voice="woman">Press 2 to leave a voicemail.</say>
<say voice="woman">Press 3 to receive an SMS message.</say>
<say voice="woman">Press 4 to repeat this menu.</say>
<say voice="woman">Press 5 to hangup.</say></gather></response>
</span></span></span></span></pre>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-size: small;">This sets up the top level menu of the simple IVR and then directs to go to “twi_action.php” once a button is pushed. The XML such as <response> and <gather> are the Twilio “verbs” that defines actions on the Twilio cloud system.</gather></response></span></span></span><span style="font-size: small;"><br />
</span><span style="font-size: small;"><br />
</span><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-size: small;">So here is “twi_action.php";</span></span></span><br />
</span></span><br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"></span></span></span></span></span></span><br />
<pre class="cpp" name="code"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;">$stringData = $_REQUEST['Digits'];
switch($stringData){
case 5:
echo "<response>";
echo "<say voice="woman"> Thank you, Good bye.</say>";
echo "<hangup>";
echo "</hangup></response>";
break;
case 4:
echo "<response>";
echo "<redirect>";
echo "http://xxxxx.xxxxx.xxx/twi_test.php";
echo "</redirect>";
echo "</response>";
break;
case 3:
echo "<response>";
echo "<say voice="woman">You are being sent an SMS message.</say>";
echo "<sms from="XXXXXXXXX">You are receiving this message from the Twilio Arduino dialer application.</sms>";
echo "</response>";
break;
case 2:
echo "<response>";
echo "<say voice="woman">Leave your message after the tone and press # when you are done.</say>";
echo "<record action="voicerecorder.php" transcribe="true">";
echo "</record></response>";
break;
case 1:
echo "<response>";
echo "<dial>XXXXXXX</dial>";
echo "<say voice="woman">Goodbye</say>";
echo "</response>";
break;
}
?>
</span></span></span></span></span></span></pre>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
This parses the digit pressed from the POST command and then takes action on the digits, which is either:<br />
<br />
</span></span><br />
</span></span></span></span></span></span><br />
<ol>
<li><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;">Connect to an operator (in this case it dials through to my Skype account).</span></span></span></span></span></span></span></span></li>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"> <span style="font-style: normal; vertical-align: baseline;"><span style="color: black;">
<li>Leave a voicemail. Currently this records a sound file which you can download from the Twilio website, but it could also have the sound file sent to an email address or transcribe the voice into text and have that sent along.</li>
<li>Receive and SMS message. This sends a canned message that could be some general information to the cell phone that made the call.</li>
<li>Repeat the menu.</li>
<li>Hangup.</li>
</span></span></span></span></span></span></span></span></ol>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;">The final small script is the one that handles the voicemail recording (voicerecorder.php):<br />
<br />
</span></span><br />
</span></span></span></span></span></span><br />
<pre class="cpp" name="code"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"> header("content-type: text/xml");
echo "\n";
?>
<response>
<say voice="woman">Thanks for the message. Here is what you recorded.</say>
<play></play>
<say voice="woman">Goodbye.</say></response>
</span></span></span></span></span></span></span></span></pre>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><br />
This one is very simple. It just plays back the message that was just recorded. That’s all there is to it! :-)<br />
<br />
<b>Conclusion</b><br />
<br />
This project could have some practical uses as a low cost way to place a simple information service in some public place. It does, however, have the obvious limitation that there is no validation that the number entered really does belong to the user’s cell phone and not to some little old lady in Detroit, so in it’s current form it could just be a nuisance generator. This could probably be fixed with an exchange of a PIN via SMS.<br />
<br />
What other things could this kind of project be used for? Here are some ideas:<br />
</span></span><br />
</span></span></span></span></span></span><br />
<ul>
<li><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;">Make a super secure lock by combining this project with an RFID reader lock so that once the card is swiped the user also has to enter a PIN and a code received via SMS at a cell phone that has been pre-registered with the system. This way an intruder would have to steal not only the users RFID badge, but also their phone and their PIN number.</span></span></span></span></span></span></span></span></li>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"> <span style="font-style: normal; vertical-align: baseline;"><span style="color: black;">
<li>This could also be adapted to trigger phone calls or SMS messages for various conditions driven by digital or analog inputs - such as temperature readings or intruder alarms. Conversely, as discussed in my Twilio LED project, a call could be used to control switches, motors or valves remotely using a telephone.</li>
<li>If it used Power Over Ethernet it would eliminate the extra power supply requirement. Even better would be using wi-fi or GSM cellular data to eliminate the need to have it wired to a router.</li>
<li>Clever use of the PROGMEM would probably allow all the response strings to be stored on the Arduino so it might be possible to dispense with using an outside webserver altogether!</li>
</span></span></span></span></span></span></span></span></ul>
<span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;"><span style="font-style: normal; vertical-align: baseline;"><span style="color: black;">This was a pretty challenging project and I hope it provides inspiration for some further cool projects!<br />
<br />
<br />
</span></span></span></span></span></span></span></span>Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-49525672687642807702010-07-09T20:18:00.000-07:002010-07-09T20:18:31.867-07:00PRK - 5 weeks post-surgeryWow, two blog posts in two days! Yes, there is an Arduino project in the works, but it's moving slowly!<br />
<br />
Anyway, today was my five week checkup so it seems like a good time to give an update on my eyes. Things are continuing to go well. My doctor says I am starting to be able to read the 20-20 lines of the charts and everything is healing very nicely,<br />
<br />
From my unscientific perspective, things have been going well especially this week. Lat week I was getting bad headaches almost everyday from eye strain, but this week things have gotten really much clearer and I haven't had a headache in days. I do now wear some store bought reading glasses when working on the computer or reading, but I can do pretty well without them. Maybe the heat wave helped somehow? Basically, the improvements are now on a week-by-week basis rather than daily, but improvements are still happening.<br />
<br />
The best thing was being able to swim in our pool without having to worry about "where did I leave my glasses" and being able to dive and see underwater with a mask! Wow! It really does make me realize how profoundly blind I was without my glasses before.Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-35567759087555266522010-07-08T16:42:00.000-07:002010-07-08T19:38:56.519-07:00Home Coffee RoastingSince this blog is basically about any interesting project or skill I can pass along, I figured people might also be interested in learning about how to roast their own coffee in the comfort of their back deck!<br />
<br />
Over the years we have gotten used to coffee roasting and production being a huge industrial process or else done by specialist boutique-y roasters with special machines and a fancy appreciation for roasts and blending. While it is true that roasting in bulk or hitting very exact roast points does require some specialist skill and machinery, the reality is that coffee roasting really isn't too hard - probably not much more difficult than making pop corn - and the results of really fresh, fresh roasted coffee make up for any imperfection in the home roaster's skills.<br />
<br />
A great book on the subject is "Home Coffee Roasting: Romance and Revival" by Kenneth Davids which is available from Amazon in Canada <a href="http://www.amazon.ca/Home-Coffee-Roasting-Romance-Revival/dp/0312312199/ref=sr_1_2?ie=UTF8&s=books&qid=1278616986&sr=8-2">here</a>.<br />
<br />
Here is a quick video of me roasting a batch of coffee on my barbecue using a big iron frying pan. As usual, I am sorry for the muddy audio!<br />
<br />
<object height="525" width="660"><param name="movie" value="http://www.youtube.com/v/cfZIQ7gAQYM&hl=en_US&fs=1?border=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/cfZIQ7gAQYM&hl=en_US&fs=1?border=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="660" height="525"></embed></object><br />
<br />
With the lid opening and closing cycles, the total process is about 10 or 12 minutes. <br />
<br />
<u>Note:</u> It is important to do this <u>outdoors</u>! The process will generate lots of pungent blue smoke that will fill up your house in no time. Also, it works best with the really high heat from a good gas barbecue.<br />
<br />
<b>Tools and materials:</b><br />
<br />
Here is what you will need at least the way I do it (there are lots of variations):<br />
<ul><li> Green coffee beans - these can be bought from Whole Foods in small batches to play with. For more variety and if you are in Canada I would recommend the<a href="http://www.greenbeanery.ca/bean/home.php"> Green Beanery</a> . They have a very cool store in downtown Toronto and also great online ordering. Some Googling would find other suppliers too. Green beans will store in a cool place for over five years!</li>
<li>A big cast iron frying pan. I believe I got mine from Canadian Tire (<a href="http://www.canadiantire.ca/AST/browse/5/SportsRec/Camping/CookingAccessories/PRD%7E0763416P/Cast%252BIron%252BSkillet%25252C%252B14-in.jsp?locale=en">here)</a>. Often they are sold in the camping section. <u>Do not</u> use a non-stick pan! The high temperatures would ruin the Teflon coating. </li>
<li>A metal colander to hold the beans after they are roasted. It <u>must</u> be metal! The beans will be over 500 degrees Fahrenheit when they are done.</li>
<li>A long handled metal spatula for stirring the beans.</li>
<li>Bowls and containers for the beans</li>
<li>Very stout pot holders - I use silicon oven mitts.</li>
<li>If you want to be fussy, a scale to weigh the beans out.</li>
<li>A coffee grinder to process your roasted beans.</li>
<li>That's about it!!</li>
</ul><b>Process:</b><br />
<br />
It's really pretty simple! <br />
<ol><li>Put the pan on the barbecue and get it as hot as you can! Just leave it dry with no oil or anything in it. Close the lid and let it get up to at least 500 degrees F. </li>
<li>Measure out enough beans to cover the bottom of your pan<b>.</b> For me that's about 1/2 lb (250 gm).</li>
<li>Put the beans in the skillet and make sure they are in an even layer and not bunched up. The idea is to try to roast the beans as evenly as possible.</li>
<li>Close the lid and let the heat build up for a little while - maybe one minute.</li>
<li>Open up the lid and stir the beans around - especially try to flip them over so the tops and bottoms get heated.</li>
<li>Close the lid!</li>
<li>Open it up again and stir.</li>
<li>This cycle will continue for a while until you start to hear a bit of a crackling sound and see the beans start to smoke. This is called "First Crack".</li>
<li>Keep opening and closing and you will see the beans pass from greenish, to yellow to light brown and then start to get darker and darker and bigger and bigger. Interestingly, the smoke doesn't really smell a thing like the "fresh roast coffee" smell we are all used to from cafes and so on - it is more pungent - like burning a piece of toast with motor oil.</li>
<li>When the beans start to darken is when you need to use a bit of judgement - do you like lighter roasts or darker espresso or French roasts? Generally, when you hear an increase in crackling and see more smoke you are at "Second Crack". A this point you can leave them in a few seconds later before dumping them out if you want them to be a darker roast. Remember! The beans have so much internal heat built up, that they will roast a bit even after they are dumped out! You need to stop a bit <u>before</u> the final darkness of roast you want to achieve. This is really the hardest "art" of the whole roast. </li>
<li>When they are ready, dump the beans out into the metal colander and stir them up to move air through them and cool them as quickly as possible. You can also give a quick spritz of water - but not too much!</li>
<li>Once the beans have cooled down you can grind them and their peak flavor is actually about 24 hours after they are roasted and they will keep super fresh for up to two weeks. It is important to not store freshly roasted beans in a tightly sealed container - especially a glass one - since they will continue to out gas for a couple of days after roasting.</li>
</ol>That's about it! Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-15954788805506585182010-06-11T19:17:00.000-07:002010-06-11T19:17:56.115-07:00PRK - 2 weeks post surgeryWell, today is two weeks and I just had a followup with my doctor, so it seems like a good time for a progress report...<br />
<br />
In general, things are proceeding and each day is still slightly better, but then some days are better than others and the improvements are smaller. I do still get blurry vision, dry eyes this gives me a headache sometimes (like right now) - especially since my job requires a lot of computer screen time. At the end of the day it feel like I have had my contacts in for too long and it's time to take them out, but then I remember that isn't an option! On the other hand, I can drive again and when my vision is completely clear it is spectacular.<br />
<br />
My eye doctor says everything is healing perfectly and looks great, but that it will likely take the more like 3 to 6 months for everything to fully clear up. The residual blurriness and so on is apparently because of minute swelling of the cornea as part of the recovery process. Naturally, I would have liked everything to be perfect by the end of two weeks, but my doctor makes the perfectly valid point that I did have 25% of my cornea blasted away by a laser and that takes some time to heal. A friend of mine who had PRK recommends keeping well hydrated, which does seem to help and is just good advice in general!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-33140778901557335812010-06-04T19:52:00.000-07:002010-06-04T19:52:32.454-07:00PRK - 1 week post surgerySo, it has now officially been a week post-surgery and things continue to progress albeit not in a completely linear fashion.<br />
<br />
Today I realized I could once again read small magazine text without reading glasses and I was able to bump down the font sizes on my computer for the first time. Still, it is hard to work at the computer screen for extended periods and moving to focus from close to far objects is tough. I also still have halo'ing. My vision also seems to be clearer when I yawn, which I expect is because of the effect of tearing up slightly when I yawn. I also have to do lots of artificial tears.<br />
<br />
Still, on the positive side, things are getting better each day and I am really liking have peripheral vision again like I did when I wore contacts many years ago! Not having to reach for my glasses in the morning or middle of the night is pretty cool. I am still getting used to my non-glassed face in the mirror when I shave. My wife says it is definitely a transition for her since she has only ever known me with glasses!<br />
<br />
I also wanted to thank the two people who more than anyone (well, besides my surgeon, eye doctor & wife) have contributed to my recovery - Tom and Ray Magliozzi from Car Talk on NPR. I downloaded I don't know how many episodes of the show onto my iPhone and I've listened to them all back-to-back - especially earlier on when I pretty much couldn't do anything! If you aren't familiar with them, check them out at<a href="http://www.cartalk.com/"> cartalk.com</a>. I'm just waiting for my car to make a weird noise so I can call it in and have them laugh at me!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-15914762157110089562010-06-02T18:20:00.000-07:002010-06-02T18:20:13.257-07:00PRK - Post Surgery Day 5Quick update today...<br />
<br />
My doctor (and most of my reading on the surgery) said the recovery would not be linear and I guess today was the non-linear day. Things that were quite clear yesterday are very blurry again today - for instance I totally can't read the house addresses across the street. Even a moderate amount of computer work is very fatiguing and I have had to take some Tylenol for a headache.<br />
<br />
Well, I will keep the faith that this will sort out over the next few days as everyone has promised. Tomorrow I am starting back to work, but I will definitely have to take frequent rests from the computer screen now and then.<br />
<br />
I also picked up a pair of reading glasses today and for the 1st time got to utter the immortal phrase "Honey, have you seen my reading glasses?" Fortunately they weren't balanced on my forehead or something at the time!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-76482058592826562402010-06-01T17:23:00.000-07:002010-06-01T17:23:38.860-07:00PRK - Post surgery Day 4Today has been a big improvement over yesterday. Immediately after getting up, my left eye still had the somewhat painful "eyelash" sensation, but by mid-morning that had gone away and today is the first day I haven't had to take any pain killers - just steroidal drops (which I will be on for months to come). My vision is getting better day-by-day. Today I could read the address numbers on houses next door!<br />
<br />
I went for the last of the daily follow-ups with my doctor and, besides apologizing profusely for the incident yesterday, he said everything is healing up just as expected. The left eye is now a day or so behind the right eye because of tearing the epithelium, but it has healed up and is coming along. I still have a lot of haloing - especially with the left eye, but the improvement is very noticeable now. Amazingly, he said I actually would be legal to drive! Frankly, I still find the idea slightly terrifying and I would give it a few days yet before I will be back to driving.<br />
<br />
So, all-in-all, good progress and now moving more quickly! I will likely stop posting about this every day now and just give periodic updates as my recovery continues.Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-29627067744575611122010-05-31T15:54:00.000-07:002010-06-02T18:21:40.956-07:00PRK - Post Surgery Day 3Well, a bit of a late post today because unfortunately the day hasn't gone so well.<br />
<br />
Initially I had high hopes I would be getting the bandage contacts out today and all would be right with the world and my prescription would be starting to lock in somewhat. The doctor had told me to be sure to hit my eyes with artificial tears every five minutes for an hour before my appointment so that they would be well lubed up to slip out, which I dutifully did.<br />
<br />
When I got there he did the usual microscope routine and pronounced the epithelium healed enough to allow taking the lenses out. He took out the right lens just fine, but when he went for the left lens it unfortunately tore at the new layer of skin. Amazingly, the pain is pretty much what you would expect from ripping freshly grown skin off your eyeball - excruciating.<br />
<br />
For pretty much the whole day I have had to keep my left eye closed as much as possible since the sensation of opening it had been akin to having glass rubbed in my eye. Initially, I came home and had a Percocet which just caused me to have a very weird, hallucinogenic nap then later I had Tylenol and Tylenol with codeine. Now, about 7 hours after it happened I can keep my left eye open a bit while I type this, but it definitely still hurts a lot. Of course, this is pretty discouraging as well as painful since I was hoping today would really be when I turned a corner. I can read the shampoo bottle labels in the shower, which is more than I ever could without my glasses, so there is some progress.<br />
<br />
The right eye does feel quite good, fortunately, but the prescription is still very off. My doctor says it should be starting to improve, but then he's the guy who just ripped the skin off my eyeball!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-40988740886383798242010-05-30T08:00:00.000-07:002010-05-30T08:00:24.877-07:00PRK - Post surgery Day 2OK, this is supposed to be the worst day and it really is. I can barely see what I am writing even with the screen fonts & browser scaling turned up full. My vision today is still somewhat better than my previous uncorrected vision, but lots of blurring and today some slight haziness. Last night it was definitely more painful and I'm getting lots of the "eye lash in the eye" feeling.<br />
<br />
That being said, this is all completely normal and my eye doctor this morning said everything in healing up just fine. My left eye is bugging me the most and he says that is the one that is healing the fastest! So, I will keep a good thought and just be patient today.<br />
<br />
So far I have only had to take two of the Percocets they gave me and a few Extra Strength Tylenols. Again, I would still say this is less painful than a dental procedure, but the deteriorating vision is definitely irritating and interferes with doing a lot of normal things I would do to kill time like gardening, working on the computer or watching TV. Good thing I downloaded a bunch of episodes of "Car Talk" from NPR before the surgery. By the time this is done I will be a master mechanic and/or have a Boston accent.<br />
<br />
Many thanks to my wife for keeping all the drops straight. She really should be a nurse. One thing you do have to watch with this is all the various eye drops you need to be squirting in your eyes!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-32371149403389915722010-05-29T14:36:00.000-07:002010-05-30T07:52:10.054-07:00PRK Post-surgery - Day 1 afterWell, this is my 1st day after the surgery and I will need to keep this quick since it's pretty hard for me to see the screen.<br />
<br />
I had my first follow-up with my local eye doctor and he said everything was healing fine. In fact, since my surgery was quite early yesterday he said I was slightly ahead of the curve. The family all went out for a good breakfast this morning and in general I have felt pretty good. I had a long nap in the afternoon and my energy level seems a bit low.<br />
<br />
<br />
For most of the morning I really couldn't feel the bandage contacts at all and so far I have only had two extra strength Tylenols for the discomfort. I've had a bit of the "eyelash in the eye" feeling in one eye, but only occasionally. Right now later in the day and after having been outside, and with trying to concentrate on my computer screen to write this, my eyes definitely feel tired and a bit scratchy.<br />
<br />
My vision is very poor and probably worse than yesterday. I would describe it as about a third or half of my previous uncorrected -9 diopter prescription, so I can function at most things, but it is still tough - lots of halos and so on.<br />
<br />
The strangest thing is my eyes look perfectly normal - they aren't blood shot or weepy or swollen or anything. As my wife says, if I had sneaked out and had the surgery, it would be impossible to tell! Even the bandage contact lenses aren't all that obvious. Contrast this with a friend of mine who had Lasik who had some bloodshot appearance a week later. I think that's because there is a ring they place on your eye for cutting the flap in Lasik that isn't required for PRK.<br />
<br />
So, that's probably enough for now, but just to mention one last thing. If you are going for this surgery you definitely, absolutely will need a committed partner to help see you through it and the after care. Immediately after the surgery you are really pretty helpless and I still feel pretty helpless while I have the bandage lenses in. There is also a pretty complex regime of eye drops to manage. It gets especially complicated when there are also children to look after! My friends who have had Lasik are generally right back to fairly normal routine the next day, but that just isn't the case with PRK!<br />
<br />
I made the dumb mistake of planning this for when our primary child care helpers (my in-laws) happened to be out of town which has made this somewhat more complicated than it might be normally. My wife has been wonderfully supportive and taken on the extra load without complaint and I appreciate it and love her even more for it! I will definitely make it up to her with some time off for her later in the summer when this is all over.Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-77977978174498841852010-05-28T13:03:00.000-07:002010-05-28T13:03:59.606-07:00PRK Surgery DayJust a quick note today because the nurses warned me that "the doctor can tell if you have been working on your computer!"<br />
<br />
The first part was paperwork & payment then a quick meeting with the doctor. He explained that because of my extreme prescription, they would be taking off nearly a quarter of my cornea, but the actual laser time was only 58 seconds. I have a somewhat elevated chance of needing a touch up later, but fingers crossed. As well there was a briefing on the post-op drop regime.<br />
<br />
The surgery went well and had somewhat of an alien abduction feel. Ahead of the surgery you get a course of drops (more antibiotics and steroidal drops) then the topical anesthetic. The weird thing was being ushered into the room with no glasses on, so effectively blind, and being laid out on the surgical bed.<br />
<br />
The first part of the procedure is getting the ocular speculum in to hold open the eyelid on the first eye (rather uncomfortable) then some measurements and getting everything set up. Then the doctor used what seemed like a mini-electric toothbrush to abrade off the out epithelial layer on my cornea. The topical anesthetic worked fine and it just tickled a bit. but was definitely odd. After scrapping off the residue, then it was laser time!<br />
<br />
For the actual lasering, I had to concentrate on a flashing light, then you could hear the clicking of the laser and the technician counting down the time. Gradually my vision grew cloudier & cloudier and there was a bit of a disconcerting "burning flesh" smell, but it didn't last too long.<br />
<br />
After that the doctor put Mitomecin on my eye, which is a cancer drug that helps prevent hazing and scarring then flushed the eye with water, put on some drops then slipped in the bandage contact lens. All very quick and I'm sure no more than 5 minutes. The second eye was just the same. Definitely a freaky feel and somewhat hard to describe, but not too terrible. Not to give away too much of my medical history, but I found it harder than a dental procedure (like a filling), but much easier than my vasectomy. Everyone at the clinic was very professional and nice.<br />
<br />
Afterwords they check you out one more time and then you're done! I felt reasonably well after the surgery, so we went over to a nearby Chapters so my wife could find a book she needed and I just settled in to listen to my iPod and have a latte. On the long drive home, my eyes started to feel a bit irritated just with so much sun and I guess from drying out a bit.<br />
<br />
At home, we went through a drop treatment and I took a Percocet and slept for a couple of hours. Now I am up and about with sunglasses on and my eyes feel fine. My vision is definitely very distorted, but I would say it is about at half my previous uncorrected myopia blurriness. It is a drag to not just get immediately perfect correction like the Lasik people, but at least I don't feel uncomfortable and I can more or less see well enough to do basic things. Thank heavens I learned touch typing or I would never be able to write this up!!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-54628618495267046192010-05-27T16:47:00.000-07:002010-05-27T16:47:21.711-07:00PRK Laser Srugery - PrologueThis will be a somewhat different set of posts than usual - perhaps this will be more like an actual blog then a set of technical recipes. You see, tomorrow I will be going in for laser eye surgery to get rid of my glasses after wearing them for some 38 years and I thought it might be a good idea to keep a diary of my recovery.<br />
<br />
Normally, for most people these days, getting Lasik surgery is quite routine, but I have...<br />
<br />
<ol><li>A pretty extreme perscription at -9 diopters</li>
<li> Apparently the epithelial layer of cells on my eye ball are somewhat loose (who knew?)</li>
</ol>Because of these, I need to go for PRK surgery (Photorefractive keratectomy, which you can read about <a href="http://en.wikipedia.org/wiki/Photorefractive_keratectomy">here</a>). Since that does away with the Lasik flap, I will have to regrow the epithelium over my cornea, which can take a few days. The only thing that makes me a bit nervous is that sometimes it can be somewhat painful and it can take a few weeks for your vision to completely "lock in" to it's final perscription.<br />
<br />
So, assuming I can even see my computer monitor, I am going to keep a record on my blog so that I can track just how well I am doing and how long it takes to recover. I've read a few other patients journals from their recovery and I found it really helpful to understand the process. Hopefully my journal will be helpful to someone else.<br />
<br />
I do have a very cool Arduino project I will finish once I am up to it!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-73563139638807421162010-05-07T11:51:00.001-07:002013-10-06T17:45:10.362-07:00Arduino Phone Control via TwilioSince Jeff from Twilio was nice enough to leave a comment on my last post, I have put together what must be my quickest project ever! This is just a very simple demonstration proof-of-concept to show the most basic of integration, but it does work. Using the Twilio cloud-based telephony API (<a href="http://www.twilio.com/" id="bzys" title="www.twilio.com">www.twilio.com</a>), this project uses a very basic IVR menu to control an LED on an Arduino. As a digital output, of course, the LED could be a relay controlling an AC current doing home automation or any number of digital control devices. This is a very simplistic project which only shows a small portion of what can be done with Twilio. Hopefully in the next few weeks I can do something more sophisticated when I understand their API better.<br />
<br />
First, here is the obligatory video:<br />
<br />
<object height="525" width="660"><param name="movie" value="http://www.youtube.com/v/RYu_e4P3Y-4&hl=en_GB&fs=1&border=1"></param>
<param name="allowFullScreen" value="true"></param>
<param name="allowscriptaccess" value="always"></param>
<embed src="http://www.youtube.com/v/RYu_e4P3Y-4&hl=en_GB&fs=1&border=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="660" height="525"></embed></object><br />
<br />
The hardware is a very simple Diecimila + Adafruit Ethernet Shield sandwich:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhojhMqoEMXI7OAbp_M9E-Ry58rflF0Vg-PZr-u_2V3DFStJROmpiT9acKeB96jh67McgosXiCeqr4Si5pasWaoPi5UifOciUWD-OmEJ92qzN3Bb3q3kvL99EEeVU2WTBfniAUSnFNVv2A/s1600/tw_ard_2.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhojhMqoEMXI7OAbp_M9E-Ry58rflF0Vg-PZr-u_2V3DFStJROmpiT9acKeB96jh67McgosXiCeqr4Si5pasWaoPi5UifOciUWD-OmEJ92qzN3Bb3q3kvL99EEeVU2WTBfniAUSnFNVv2A/s400/tw_ard_2.JPG" width="400" /></a></div>
<br />
<br />
<div id="v91q" style="text-align: left;">
<br />
I have the LED going to digital output pin 11.</div>
<br />
<div id="bowc" style="text-align: left;">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWmCuLVan4RbShoxZ5fyMAuRBYIbfKYUa0wrkDtEn20zk5Si290bAEGQYUJLoaZwODf61y4tPwr6IS2AkRBjRHZ09lF7cLPCQphpb7XDH8-Fq5W4sXrIkK0oOt8nT-PHYLQv7FRCaR7YU/s1600/twi_ard_crop.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="380" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWmCuLVan4RbShoxZ5fyMAuRBYIbfKYUa0wrkDtEn20zk5Si290bAEGQYUJLoaZwODf61y4tPwr6IS2AkRBjRHZ09lF7cLPCQphpb7XDH8-Fq5W4sXrIkK0oOt8nT-PHYLQv7FRCaR7YU/s640/twi_ard_crop.JPG" width="640" /></a></div>
<br />
<br />
<div id="r9_-" style="text-align: left;">
So, how does it work? The Twilio system is really more geared to allowing web-based control of telephony functions and to quickly build IVRs and messaging systems in with a cloud-based back-end, so using to control physical hardware is a bit of a hack. Fortunately, they have a simple to use API and lots of PHP-based example code available. <br />
<br />
What is involved, however, is having a bunch of scripts/files all working together to pass the info down to the Arduino in a format it can digest. Here is a general flow of how it works:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJm-xt5riXTqDYciD4zeEzd3hoAyvQXTTaW00CTTU2LfL_wkV1X5vEPwIJw3edbi90UVibfTuznvVeG-sCCZlw8CIMaCBImyOMUrDD4ukhnbaOI05d8eD5J1cAgi9RdC3KBGVKZS_Rbdk/s1600/twilio_arduino.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJm-xt5riXTqDYciD4zeEzd3hoAyvQXTTaW00CTTU2LfL_wkV1X5vEPwIJw3edbi90UVibfTuznvVeG-sCCZlw8CIMaCBImyOMUrDD4ukhnbaOI05d8eD5J1cAgi9RdC3KBGVKZS_Rbdk/s640/twilio_arduino.jpg" width="482" /></a></div>
<br /></div>
<br />
<div id="t412" style="text-align: left;">
<div id="uq:i" style="text-align: left;">
</div>
<br />
The first file is the PHP file that generates the XML read by Twilio:<br />
<br />
<pre class="cpp" name="code"><?php
header("content-type: text/xml");
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
?>
<Response>
<Say>Hello</Say>
<Gather action="twi_do.php" numDigits ='1' method="POST">
<Say>Press 1 to turn on the L.E.D. Press 2 to turn it off. Press 3 to repeat this menu and press 4 to hang up.</Say>
</Gather>
</Response>
</pre>
<br />
This file is on my personal webserver and accessible to Twilo. When I call the access number, Twilio goes to this webpage and acts on the XML instructions presented. The XML uses the Twilio verbs "Say" to repeat the menu options and "Gather" to get the keypresses. Here is the twi_do.php script that is called by the above code:<br />
<br />
<pre class="cpp" name="code"><?php
$stringData = $_REQUEST['Digits'];
switch($stringData){
case 4:
echo "<Response>";
echo "<Say> Thank you, Good bye.</Say>";
echo "<hangup/>";
echo "</Response>";
break;
case 3:
echo "<Response>";
echo "<Redirect>";
echo "http://xxx,xxxxxx,xxx/twi_test.php";
echo "</Redirect>";
echo "</Response>";
break;
case 2:
echo "<Response>";
echo "<Say> You have turned the L.E.D. off.</Say>";
echo "<Redirect>";
echo "http://xxx,xxxxxx,xxx/twi_test.php";
echo "</Redirect>";
echo "</Response>";
$myFile = "twi_File.txt";
$fh = fopen($myFile, 'w') or die("can't open file");
fwrite($fh, "NUM" . $stringData);
fclose($fh);
break;
case 1:
echo "<Response>";
echo "<Say> You have turned the L.E.D on.</Say>";
echo "<Redirect>";
echo "http://xxx,xxxxxx,xxx/twi_test.php";
echo "</Redirect>";
echo "</Response>";
$myFile = "twi_File.txt";
$fh = fopen($myFile, 'w') or die("can't open file");
fwrite($fh, "NUM" . $stringData);
fclose($fh);
break;
}
?>
</pre>
<br />
This responds to the keypress value by presenting the different prompts and, in the case of the digits 1 and 2 it writes the value to a small file (twi_File.txt) which just contains either "NUM1" or "NUM2". So, now we have the kepress value stored in a locally accessible file!<br />
<br />
Running along with my webserver is a small PHP sockets server program (Arduino_twilio.php):<br />
<br />
<pre class="cpp" name="code"><?php
$host_ard = "192.168.0.171";
$port_ard = "55455";
//=====================Create Socket to listen for Arduino======================//
ob_implicit_flush();
//Open socket to listen for Arduino
$socket_ard = socket_create(AF_INET, SOCK_STREAM, 0) or die ("Could not bind to socket\n");
$result_ard = socket_bind($socket_ard, $host_ard, $port_ard) or die("Could not bind to socket\n");
// start listening for connections
$result_ard = socket_listen($socket_ard, 3) or die("Could not set up socket listener\n");
// accept incoming connections
// spawn another socket to handle communication
$spawn = socket_accept($socket_ard) or die("Could not accept incoming connection\n");
// read client input
socket_write($spawn,"C\n");
echo "Device connected.\n";
do{
$input = socket_read($spawn, 2048, PHP_NORMAL_READ) or die("Could not read input\n");
echo ("This is the value of input: " . $input . "\n");
if (trim($input[0]) == "C"){
usleep(10000);
$data = file_get_contents("twi_File.txt");
echo ("The value of data is: " . $data . "\n");
socket_write($spawn, "X" . $data . "\n");
}
usleep(10000);
}while(true);
?>
</pre>
<br />
If a client is connected to this server, then the script periodically reads the twi_File.txt file and presents it on to the Arduino client when requested. Given the simplicity of this project, I could probably have dispensed entirely with a "preprocessor" script like this and done all the manipulation on the Arduino, but this was just easier to do since I had the other code lying around.<br />
<br />
Finally, here is the Arduino code:<br />
<br />
<pre class="cpp" name="code">#include <NewSoftSerial.h>
#include <AF_XPort.h>
#define XPORT_RXPIN 2
#define XPORT_TXPIN 3
#define XPORT_RESETPIN 4
#define XPORT_DTRPIN 5
#define XPORT_CTSPIN 6
#define XPORT_RTSPIN 7
#define IPADDR "192.168.0.171" //IP Address of the Arduino Server
#define PORT 55455 //IP port of the server
AF_XPort xport = AF_XPort(XPORT_RXPIN, XPORT_TXPIN, XPORT_RESETPIN, XPORT_DTRPIN, XPORT_RTSPIN, XPORT_CTSPIN);
uint8_t ret;
int ledPin = 11;
char linebuffer[16];
void setup()
{
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
Serial.println("Starting up!");
// set the data rate for the NewSoftSerial port
xport.begin(9600);
}
void loop() // run over and over again
{
xport.connect(IPADDR, PORT);
delay(300);
delay(300);
ret = xport.readline_timeout(linebuffer, 32, 600); // get first line
Serial.println(linebuffer);
if (linebuffer[0] == 'N'){
if(linebuffer[3] == '1'){
digitalWrite(ledPin, HIGH);
}
if(linebuffer[3] == '2'){
digitalWrite(ledPin, LOW);
}
}
}
</pre>
<br />
This is based on the Adafruit AF_Xport library and when the Xport connects it sends out a C plus the IP address and port. When the PHP server receives the "C" it sends the value to the Arduino and this program then either turns the light on or off depending on what it gets back. <br />
<br />
<h3>
So what?</h3>
<br />
Well, like the email projects, this is also a good way to do remote control since simple DTMF telelphony is ubiquitious. If you have a cell phone, you could call into your Arduino from anywhere in the world and control household appliances, monitor temperatures, control a robot - anything in the huge universe of whacky stuff Arduino builders build!<br />
<br />
<h3>
What's next?</h3>
<br />
Of course, this project really just uses the very simplest of features of the Twilio API. I think next up I will looking into how an Arduino could be made to send out an SMS or do a click-to-call implementation.</div>
Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-59195211373841408272010-05-03T18:51:00.001-07:002013-10-06T17:37:17.862-07:00Arduino Email Manager - Part 3 - Arduino CodeNow we come to the last part of the Email Manager - the Arduino firmware. Most of it is reasonably straightforward, but there are a few things to note.<br />
<br />
To drive the LCD I used the LCD4Bit_mod library recommended to go with the DFRobot LCD shield (available from <a href="http://www.robotshop.com/content/ZIP/dfrobot-lcd4bit-mod.zip" id="m357" title="here">here</a>). In general this seems to work fine, but it doesn't seems consistent about one line flowing over to the next which is why I have "lcd.cursorTo" lines to put the cursor on the 1st or 2nd line. <br />
<br />
The buttons actually are run over Analog pin 0 and the "get_key" function determines the analog value returned and lines it up with the appropriate key number. The trickiest thing for me was figuring the various modes for the buttons in different states - especially the Up/Down buttons that can also be used to delete messages, which is why there are a lot of "if-this-key-and-that-mode-do-this" statements. Likely they could be cleaned up further!<br />
<br />
Finding the from/subject info in the data stream proved to be harder than I would have thought as explained in the first post so I ended up using the TextFinder library available from <a href="http://www.arduino.cc/playground/Code/TextFinder" id="r6x2" title="here">here</a>. This allows you to search in the data stream (either serial or Ethernet) for keywords or values. Come to think of it, I could probably have used this to do the whole email parsing job and then dispensed with the PHP script, but I noticed when I ran the "getValue" feature it temporarily blacked out the LCD screen - maybe a short processor lock up? Anyway, if anyone can think of a way to use TexFinder to eliminate the PHP script, let me know!<br />
<br />
So, here is the Arduino code (with lots of comments) in its glory:<br />
<br />
<pre class="cpp" name="code">/*
* Arduino POP Mail Manager
* Uses the DFRobot LCD Shield, Arduino Ethernet Sheild
* and an Arduino Duemilanove to build a simple system for seeing
* how many POP eamils you have and deleting ones you don't want. Separate
* PHP script must be used with it. Complete description at:
* http://opensourceprojects-torchris.blogspot.com/
*
* Uncomment the //Serial lines for troubleshooting/debug info.
*
* This code is in the public domain. Please provide credit if it is used
* in another project.
*
* written by Chris Armour, Arpil 30th, 2010
*
*/
//=====================Libraries=============/
#include <Ethernet.h>
#include <LCD4Bit_mod.h>
#include <TextFinder.h>
//===============Set up variables =============/
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //MAC address for Arduino
byte ip[] = { 192,168,0,34 }; // IP address you wish to assign to Arduino
byte server[] = { 192, 168, 0, 171 }; // IP address of your PHP server
int ServerPort = 12345;
int adc_key_val[5] ={30, 150, 360, 535, 760 };
int NUM_KEYS = 5;
int adc_key_in;
int key=-1;
int oldkey=-1;
char NumRecd[4];
char SubjRecd[32];
boolean ConnectedState = false;
int MsgNumber = 1;
char TotalMsgNumChar[4] = " ";
int MsgTotalNum = 1;
char TotalMsgCharAr[2];
boolean DelMode = false;
unsigned long ButtonMillis = 0;
unsigned long CurrentMillis = 0;
unsigned long interval = 15000;
//===============Setup instances===============/
LCD4Bit_mod lcd = LCD4Bit_mod(2);
Client client(server, ServerPort);
TextFinder finder( client);
//=================Setup========================/
void setup() {
Ethernet.begin(mac, ip);
lcd.init();
lcd.clear();
lcd.cursorTo(1,0);
lcd.printIn("POP Mail Started");
lcd.cursorTo(2,0);
lcd.printIn("Press S to conct");
//Serial.begin(9600);
DelMode = false;
ConnectedState = false;
}
//=================Main program loop ==================/
void loop() {
CurrentMillis = millis();
if (((CurrentMillis - ButtonMillis) > interval) && (ConnectedState == true)){
//If connected to the PHP script, then every 15 seconds display the number of emails.
ButtonMillis = CurrentMillis;
//Serial.println("15 Seconds have passed!");
GetTotalMsgs(); //Get total message count
delay(100);
PrintTotalMsgs(); //Display total messages
}
if (((CurrentMillis - ButtonMillis) > interval) && (ConnectedState != true)){
//If not connected, then just display the not connected error.
ButtonMillis = CurrentMillis;
//Serial.println("15 Seconds have passed!");
NotConnectedError();
}
//The following items are from the original LCD example and they read the value of the key pressed.
adc_key_in = analogRead(0); // read the value from the sensor
key = get_key(adc_key_in); // convert into key press
if (key != oldkey) // if keypress is detected
{
delay(70); // wait for debounce time
adc_key_in = analogRead(0); // read the value from the sensor
key = get_key(adc_key_in); // convert into key press
//Serial.println(key);
if (key != oldkey)
{
oldkey = key;
}
}
if (key == 4) {
//If the Select button is pushed, run the routine to connect to the PHP server.
ButtonMillis = millis();
if (client.connect()) {
char c = client.read();
if (c == 'C') {
//Serial.println("connected");
lcd.clear();
lcd.printIn("Connected");
ConnectedState = true; //Sets ConnectedState to true
delay(200);
}
} else {
//If can't connect, print an error.
//Serial.println("connection failed");
ConnectedState = false; // Set connected state to false
lcd.clear();
lcd.printIn("Connection Fail");
lcd.cursorTo(2, 0); //line=2, x=0
lcd.printIn("Check PHP script");
}
}
if ((key == 3) && (ConnectedState == true)) {
//If Right button pressed and it's connected to the PHP script, then dispaly the toal messages.
ButtonMillis = millis(); //these detect when the button was pushed
GetTotalMsgs();
delay(100);
PrintTotalMsgs();
}
if ((key == 3) && (ConnectedState == false)){
//Serial.println("Not Connected!!");
//If ConnectedState is false, print error message.
NotConnectedError();
}
if ((key == 2) && (ConnectedState == false)){
//Serial.println("Not Connected!!");
NotConnectedError();
}
if ((key == 2)&& (DelMode != true) && (ConnectedState == true)) {
//If it is connected AND not in delete mode, then display email info
ButtonMillis = millis();
GetTotalMsgs();
delay(200);
//Serial.println("DN button push detected");
MsgNumber++; //Increments the messages.
if (MsgNumber >= MsgTotalNum){ //if we reach the max message number, then go to message #1
MsgNumber = 1;
}
CallMsg();
delay(150);
//Serial.print("Total Messages = ");
//Serial.println(MsgTotalNum);
}
if ((key == 2) && (DelMode == true)&& (ConnectedState == true)){
//If it is connected, but in Delete mode, then this is the confirm delete button.
ButtonMillis = millis();
//Serial.println("DN button push detected");
//Serial.print("Deleting message number =");
//Serial.println(MsgNumber);
DelMode = !DelMode;
DelMsg();
lcd.clear();
lcd.printIn("Message Deleted");
MsgNumber--;
delay(250);
CallMsg();
}
if ((key == 1) && (ConnectedState == false)){
//Serial.println("Not Connected!!");
NotConnectedError();
}
if ((key == 1) && (DelMode != true) && (ConnectedState == true)) {
//If we are connected AND not in delete mode, then disply email info
ButtonMillis = millis();
GetTotalMsgs();
delay(200);
//Serial.println("UP button push detected");
MsgNumber--; //decrements the message number
if (MsgNumber <= 1){ //If we decrement down to 1, then go to the highest message number
MsgNumber = MsgTotalNum;
}
//Serial.print("Total Messages = ");
//Serial.println(MsgTotalNum);
delay(150);
CallMsg();
}
if ((key == 1) && (DelMode == true)&& (ConnectedState == true)){
//If we're in delete mode, then this cancels the deletion.
ButtonMillis = millis();
//Serial.println("UP button push detected");
//Serial.print("Cancelling deletion");
//Serial.println(MsgNumber);
//Serial.println("DelMode is set to false");
DelMode = false;
delay(200);
lcd.clear();
lcd.printIn("Delete Cancelled");
CallMsg();
}
if ((key == 0) && (ConnectedState == false)){
//Serial.println("Not Connected!!");
NotConnectedError();
}
if ((key == 0) && (ConnectedState == true)) {
//Finally, if we are connected then this is the delete button.
DelMode = true; //triggers delete mode
//Serial.println("DelMode is set to True");
//prints instructions for deleting or cancelling.
lcd.clear();
lcd.cursorTo(1,0);
lcd.printIn("Delete Message?");
lcd.cursorTo(2,0);
lcd.printIn("UP=N / DN=Y");
}
}
//======================Functions=====================//
// Convert ADC value to key number
int get_key(unsigned int input)
{
int k;
for (k = 0; k < NUM_KEYS; k++)
{
if (input < adc_key_val[k])
{
return k;
}
}
if (k >= NUM_KEYS)
k = -1; // No valid key pressed
return k;
}
//Deletes a message
void DelMsg(){
//Serial.print("Deleting MsgNumber = ");
//Serial.println(MsgNumber);
client.print ("D.");
client.println(MsgNumber);
lcd.printIn("Message Deleted");
delay(150);
}
//Sends the command to the PHP script to get the from/subject info for the current message number
void CallMsg(){
//Serial.print("MsgNumber = ");
//Serial.println(MsgNumber);
client.print ("S.");
client.println(MsgNumber);
delay(150);
GetFrmSubj();
}
void GetFrmSubj(){
//parses out the from/subject info from the PHP return info
if(finder.find("MSG") == true ){
//Uses the TextFinder library to locate the from/subject info.
for (int z = 0; z <= 32; z++) {
delay(20);
char c = client.read();
if ((c > 31) && (c < 128)){
SubjRecd[z] = c;
}
}
Print2LCD();
client.flush();
}
else if (finder.find("MSG") != true) {
//If the from/subj can't be retreived, then display an error.
//Usually a transient authentication error on the PHP side.
//Serial.println("Didn't get it!");
lcd.clear();
lcd.printIn("Error getting Msg");
client.flush();
}
}
void Print2LCD(){
//prints the from/subject info to the LCD
lcd.clear();
lcd.cursorTo(1,0);
for (int y = 0; y <= 15; y++) {
lcd.print(SubjRecd[y]);
//Serial.print(SubjRecd[y]);
}
lcd.cursorTo(2,0);
//Serial.println();
for (int z = 16; z <= 32; z++){
lcd.print(SubjRecd[z]);
//Serial.print(SubjRecd[z]);
}
client.flush();
}
void GetTotalMsgs () {
//Gets the total message number, which is stored as both an array & an integer
client.println("N");
delay(150);
if(finder.find("Total Number of Messages:") == true ) {
for (int w = 0; w <= 2; w++) {
delay(20);
char c = client.read();
if ((c > 31) && (c < 128)){
TotalMsgCharAr[w] = c;
}
}
MsgTotalNum = atoi(TotalMsgCharAr); //Use ASCII to Integer to convert the array.
}
}
void NotConnectedError(){
//Simple error message
lcd.clear();
lcd.cursorTo(1,0);
lcd.printIn("Not Connected");
lcd.cursorTo(2, 0); //line=2, x=0
lcd.printIn("Press S to conct");
}
void PrintTotalMsgs(){
//This takes the total message array & prints it to the LCD.
//Note that is will max out at 99 messages
lcd.clear();
lcd.printIn("Number of emails");
lcd.cursorTo(2, 0); //line=2, x=0
//Serial.print("Total messages = ");
//Serial.println(MsgTotalNum);
for (int d = 0; d <= 1; d++){
if ((TotalMsgCharAr[d] >= 48) && (TotalMsgCharAr[d] <= 57)){
lcd.print(TotalMsgCharAr[d]);
}
}
}
</pre>
<br />
<h2>
So what?</h2>
<br />
Well, why bother doing all this since I can, of course, much more easily delete spam from my Inbox with a regular email client or my iPhone? Well, as with my previous Arduino/POP3 interfacing project, I like to have a running visual indication of my email load, and, of course, the stock answer is "Why not? It's my time to waste!". Actually, email is quite lightweight an ubiquitious and so could be easily used for remote control. We web-based control would be more <i>elegant</i>, but not always accessible via a remote device for whatever reason.<br />
<br />
About 10 years ago I build a device that interfaced with the PC parallel port and then wrote a Visual Basic program that received email and interpreted the subject line to do basic remote control via a couple of solid state relays. It could turn on or off simple AC devices with email and worked like a charm. With an Arduino, you could have a light sensor that then could tell if the light is, in fact, on and send an SMTP message (via the PHP script) to confirm that the action had been taken. <br />
<br />
<h2>
Next steps...</h2>
<br />
Next up, I should get this properly working with Gmail - possibly using the PHP classes available for IMAP rather than POP3.I also hope to get one of those new-fangled Wi-fi WiShield things and try to take this wireless!<br />
<br />
Another future project may be to use the Twilio web-based API for voice-enabling applications to interface to an Arduino (check it out <a href="http://www.twilio.com/" id="ipw5" title="here">here</a>). I have played with Twilio a bit and using a PHP pre-processor should make it quite easy to interface to an Arduino. This would allow a simple IVR-type remote control & monitoring - "Press 1 to turn on your lights, Press 2 to hear the temperature." I will try and not make it five months between posts!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0tag:blogger.com,1999:blog-4454770415619038436.post-24982887384685998702010-05-03T08:45:00.001-07:002013-10-06T17:36:43.164-07:00Arduino Email Manager - Part 2 - The PreprocessorI freely admit that I am not the greatest programmer in the world. Never having been formally trained, my programs tend to be rather loopy and don't include all the error checking they should. This PHP script that I have written is a case in point. It will work with my POP email provider (Rogers in Canada), but it may <b><u>NOT </u></b>work with your provider! It seems like there are minor variations in the way some POP servers will respond to the standard commands and these can throw off the script slightly. The best bet is to log into your POP server via telnet and carefully watch the messages back and forth.<br />
<br />
Also note that this will support SSL (if you have it set up with your PHP installation) just by putting "ssl:/" in front of the host name and using whatever port number has been assigned - usually 995.You can manually log into an SSL-enabled account using the OpenSSL client available from <a href="http://www.openssl.org/" id="i1_m" title="OpenSSL.org">OpenSSL.org</a> instead of telnet. <br />
<br />
At this point, the script <u style="background-color: #ffd966;"><b>does not support </b></u><b style="background-color: #ffd966;"><u>Gmail</u></b>, so don't write to me about that! I have been trying to get it to work and you can definitely log in, but it seems to give inconsistent results and the mail total includes both the inbox and sent folder somehow (which doesn't make sense to me). If anyone out there knows the trick with Gmail, please let me know. I see that there are some PHP classes available for accessing Gmail via IMAP and I will investigate these and post revised code when I can.<br />
<br />
So, how does this script work? The script needs to run on a server with the current version of PHP running. This could be a Mac or a Linux/Windows box or it could be running on a remote webserver - assuming you have all the networking and firewall issues dealt with. There is a new project called Yaler that may help with the networking issues if your script is remotely hosted - check it out <a href="http://yaler.org/" id="gkts" title="here">here</a>. You need to start the script before pressing the "Connect" (or Select) button on the Arduino.<br />
<br />
When running, the Preprocessor is a "host" for the requests from the Arduino, but is in turn acting as a "client" to the POP server. It maintains two separate PHP sockets - one to the Arduino and one to the POP server.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivIYLVfotoOth6HaVhm9aOH2zL7IhmsBFbJlpiVEZGzXbtMfO4oX-6LxvJv5eu_a6Qn-ULnzp4kVUoS9heUSie9qBI-7FYD3oVR40Q5BLvjApLjnrOfY0iXnQebc5ba_fgggqlGUCAKdc/s1600/arduino_email_manager.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="235" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivIYLVfotoOth6HaVhm9aOH2zL7IhmsBFbJlpiVEZGzXbtMfO4oX-6LxvJv5eu_a6Qn-ULnzp4kVUoS9heUSie9qBI-7FYD3oVR40Q5BLvjApLjnrOfY0iXnQebc5ba_fgggqlGUCAKdc/s640/arduino_email_manager.jpg" width="640" /></a></div>
<br />
<div id="hqk:" style="text-align: left;">
<div id="rowe" style="text-align: left;">
<br />
<br />
</div>
</div>
The script implements a very simple protocol with the Arduino:<br />
<br />
<table border="1" bordercolor="#000000" cellpadding="3" cellspacing="0" id="n33e" style="height: 139px; width: 1651px;"><tbody>
<tr><td style="text-align: center;"><b>Arduino Sends...<br />
</b></td><td><b>Preprocessor returns...<br />
</b></td></tr>
<tr><td style="text-align: center;">Arduino connects to IP/port</td><td>the ASCII "C"</td></tr>
<tr><td style="text-align: center;">N</td><td>Current number of emails on the POP server.</td></tr>
<tr><td style="text-align: center;">S.x (where x is an integer)</td><td>The from/subject information for email number "x" prefixed with MSG.</td></tr>
<tr><td style="text-align: center;">D.x (where x is an integer)</td><td>Deletes email number "x".</td></tr>
<tr><td style="text-align: center;">E</td><td>Kills the socket and ends session - currently not implemented.</td></tr>
</tbody></table>
<br />
The script also provides the following error messages back (although I am not currently using these on the Arduino):<br />
<br />
<ul>
<li>X.1 - Could not open socket to POP server</li>
<li>X.2 - Error from server</li>
<li>X.3 - Authentication failure</li>
<li>X.4 - Bad connection</li>
</ul>
<br />
I tend to prefer one letter protocols with the Arduino since it simplifies the text handling on the Arduino side.<br />
<br />
And, without further ado, here is the code...<br />
<br />
<pre class="cpp" name="code"><?php
/*
* PHP Pre-processor script for Arduino POP Mail Manager
*
* this is the pre-processor script for use with the Arduino email
* manager described on my blog. Complete description at:
* http://opensourceprojects-torchris.blogspot.com/
*
* This script can be used with SSL enabled POP services by putting
* "ssl:/" infront of the host name. It does NOT currently work with
* GMail POP service.
*
* This code is in the public domain. Please provide credit if it is used
* in another project.
*
* written by Chris Armour, Arpil 30th, 2010
*
*/
//=============================IP, Port, User Info=================//
//Modify to suit your network.
define("HOST_POP", "your.popserver..com");
define("PORT_POP", "110");
define("USER_POP", "user.name");
define("PASS_POP", "YourPassword");
//Server settings
$host_ard = "192.168.0.171"; //This is the IP of the server running the script.
$port_ard = "12345"; //Port number being used by the Arduino
//==========================Functions===================//
//GetMailCount grabs the number of emails in the mailbox using the STAT command
function GetMailCount()
{
$fp_pop = fsockopen (HOST_POP, PORT_POP, $errno, $errstr);
// if a handle is not returned
ob_implicit_flush();
if (!$fp_pop)
{
echo("Error: could not open socket connection\n");
return("X.1");
}
else
{
// get the welcome message
$welcome = fgets ($fp_pop, 150);
// check for success code
if (substr($welcome, 0, 3) == "+OK")
{
// send username and read response
fputs ($fp_pop, "user " . USER_POP . "\n");
//fgets($fp_pop, 50);
// send password and read response
fputs ($fp_pop, "pass " . PASS_POP . "\n");
$ack = fgets($fp_pop, 50);
// check for success code
if (substr($ack, 0, 3) == "+OK")
{
// send status request and read response
fputs ($fp_pop, "STAT\n");
$status = fgets($fp_pop, 50);
if (substr($status, 0, 3) == "+OK")
{
// shut down connection
fputs ($fp_pop, "QUIT\n");
fclose ($fp_pop);
}
// error getting status
else {
echo ("Error - server said: $status");
return("X.2");
}
}
// auth failure
else {
echo ("Error - server said: $ack");
return("X.3");
}
}
// bad welcome message
else {
echo ("Error - bad connection string\n");
return("X.4");
}
// get status string
// split by spaces
$arr = explode(" ", $status);
// the second element contains the total number of messages
echo $arr[3] . " messages in mailbox\n";
$GotMail = $arr[3];
return $GotMail;
}
}
//GetFromSubj grabs the content of the email MailNumber then passes this back to the main loop for extraction of the From & subject
//You may need to check the output from your POP3 server using telnet to check on the responses. This seems to vary from POP server to POP
//server.
function GetFromSubj($MailNumber){
$fp_pop = fsockopen (HOST_POP, PORT_POP, $errno, $errstr);
// if a handle is not returned
ob_implicit_flush();
if (!$fp_pop)
{
echo("Error: could not open socket connection\n");
return("X.1");
}
else
{
// get the welcome message
$welcome = fgets ($fp_pop, 150);
// check for success code
if (substr($welcome, 0, 3) == "+OK")
{
// send username and read response
fputs ($fp_pop, "user " . USER_POP . "\n");
fgets($fp_pop);
// send password and read response
fputs ($fp_pop, "pass " . PASS_POP . "\n");
$ack = fgets($fp_pop);
// check for success code
if (substr($ack, 0, 3) == "+OK")
{
// send request for email content & read response
fputs ($fp_pop, "retr " . $MailNumber . "\n");
$EmailContent = fread($fp_pop, 4096);
// echo $EmailContent;
if (substr($EmailContent, 0, 3) == "+OK")
{
// shut down connection
return $EmailContent;
fputs ($fp_pop, "QUIT\n");
fclose ($fp_pop);
}
// error getting status
else
{
echo ("Error - Server said: $EmailContent");
return("X.2");
}
}
// auth failure
else
{
echo ("Error - Server said: $ack");
return("X.3");
}
}
// bad welcome message
else
{
echo ("Error - Bad connection string\n");
return("X.4");
}
}
}
//MsgDelete delets message # MailNumber using the dele command
function MsgDelete($MailNumber){
$fp_pop = fsockopen (HOST_POP, PORT_POP, $errno, $errstr);
// if a handle is not returned
ob_implicit_flush();
if (!$fp_pop)
{
echo("Error: could not open socket connection\n");
return("X.1");
}
else
{
// get the welcome message
$welcome = fgets ($fp_pop, 150);
// check for success code
if (substr($welcome, 0, 3) == "+OK")
{
// send username and read response
fputs ($fp_pop, "user " . USER_POP . "\n");
fgets($fp_pop);
// send password and read response
fputs ($fp_pop, "pass " . PASS_POP . "\n");
$ack = fgets($fp_pop);
// check for success code
if (substr($ack, 0, 3) == "+OK")
{
// send delete command
fputs ($fp_pop, "dele " . $MailNumber . "\n");
$DeleteAck = fgets($fp_pop);
// echo $EmailContent;
if (substr($DeleteAck, 0, 3) == "+OK")
{
// shut down connection
echo ("Message " . $MailNumber . " deleted. \n");
fputs ($fp_pop, "QUIT\n");
fclose ($fp_pop);
}
// error getting status
else
{
echo ("Error - Server said: $EmailContent");
return ("X.2");
}
}
// auth failure
else
{
echo ("Error - Server said: $ack");
return("X.3");
}
}
// bad welcome message
else
{
echo ("Error - Bad connection string\n");
return("X.4");
}
}
}
//=====================Create Socket to listen for Arduino======================//
ob_implicit_flush();
//Open socket to listen for Arduino
$socket_ard = socket_create(AF_INET, SOCK_STREAM, 0) or die ("Could not bind to socket\n");
$result_ard = socket_bind($socket_ard, $host_ard, $port_ard) or die("Could not bind to socket\n");
// start listening for connections
$result_ard = socket_listen($socket_ard, 3) or die("Could not set up socket listener\n");
// accept incoming connections
// spawn another socket to handle communication
$spawn = socket_accept($socket_ard) or die("Could not accept incoming connection\n");
// read client input
socket_write($spawn,"C\n");
echo "Device connected.\n";
//=====================Main Socket loop======================//
do {
$input = socket_read($spawn, 2048, PHP_NORMAL_READ) or die("Could not read input\n");
echo $input;
if (trim($input[0]) == "N" || trim($input[0]) == "S" || trim($input[0]) == "D" || trim($input[0]) == "E" || trim($input[0]) == "\r\n") {
//Only do anything if N, S, D or E received.
if (trim($input) == "N"){
// This gets the mailcount using the GetMailCount function.
$MailCount = GetMailCount();
socket_write($spawn, "Total Number of Messages:" . $MailCount . "@");
}
if (trim($input[0]) == "S") {
//This gets the from/subject info is an S.num is received.
$MsgNum = explode (".",$input);
//Use explode to break the info from the Arduino into an array.
$MsgNumber = $MsgNum[1];
//Get the Message number from the array.
echo ("Value of MsgNumber= " . $MsgNumber . "\n");
$MsgFromSubj = GetFromSubj($MsgNumber);
//run the function to get the info from the POP server
if ($MsgFromSubj[0] == "X")
{
//If there's an error, write back the server error text.
socket_write($spawn,$MsgFromSubj);
}
else
{
$MsgFromLocn = strpos($MsgFromSubj, "From: ");
$MsgFromName = substr($MsgFromSubj, ($MsgFromLocn + 6), 14);
print ( "MSG". "F:" . $MsgFromName );
$MsgSubjLocn = strpos($MsgFromSubj, "Subject: ");
$MsgSubj = substr($MsgFromSubj, ($MsgSubjLocn + 9), 14);
echo ("S:" . $MsgSubj );
usleep(20000);
socket_write ($spawn, "MSG" . "F:" . $MsgFromName . "S:" . $MsgSubj . "\n");
}
}
if (trim($input[0]) == "D") {
//This sends the command to delete an email.
$MsgNum = explode (".",$input);
$MsgNumber = $MsgNum[1];
$MsgDelRet = MsgDelete($MsgNumber);
if ($MsgDelRet[0] == "X")
{
socket_write($spawn,$MsgDelRet ."\n");
}
else
{
socket_write($spawn, "D." . $MsgNum[1]);
}
}
if (trim($input) == "E"){
//This isn't actually currently used by the Arduino.
socket_shutdown($spawn, 2);
usleep(1000);
socket_close($spawn);
socket_shutdown($socket_ard, 2);
usleep(1000);
socket_close($socket_ard);
echo "Sockets terminated\n";
// break;
}
}
} while(true);
?></pre>
<br />
Note that this code does not provide good handling of disconnect/reconnect nor can it handle multiple connections simultaneously. In other words, if the Arduino is reset, then the script needs to be restarted. Also, if multiple connection requests are received, it will just die. If anyone can improve on this, please make the suggestions.<br />
<br />
Next up will be the Arduino code!Torchrishttp://www.blogger.com/profile/05149708403435853046noreply@blogger.com0