tag:blogger.com,1999:blog-75499260554346900012024-03-05T16:42:50.327+01:00Raspberry pi experimentsCedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.comBlogger20125tag:blogger.com,1999:blog-7549926055434690001.post-48538067539395637022017-07-24T09:46:00.000+02:002017-08-01T09:46:37.295+02:00Cloud detectorHow to detect clouds to aid astronomical observations.<br />
<br />
<a name='more'></a>Sorry, this is in French from another of my blogs: <a href="http://astro.raguenaud.fr/2017/07/detecteur-de-nuages.html" target="_blank">http://astro.raguenaud.fr/2017/07/detecteur-de-nuages.html</a>Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-893773803996229922017-07-21T09:46:00.000+02:002017-08-01T09:47:49.351+02:00Automatic focuser for telescope<span id="goog_863808990"></span><a href="https://www.blogger.com/"></a><span id="goog_863808991"></span>How to create a focuser that can be controlled remotely to focus a telescope.<br />
<br />
<a name='more'></a>Sorry, this is French from another of my blogs: <a href="http://astro.raguenaud.fr/2017/07/focuser-automatique.html" target="_blank">http://astro.raguenaud.fr/2017/07/focuser-automatique.html</a>Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-70939672993403775242017-02-06T12:17:00.004+01:002017-11-02T23:22:33.291+01:00RIP MetPi #1We had 2 storms passing over us at the weekend. One casualty: the MetPi.<br />
<br />
<a name='more'></a>It had been running pretty well for 6 months. It didn't have any protection other than the <a href="http://pi.raguenaud.fr/2016/07/metpi-5-outdoor-housing.html">meteo housing</a>. Corrosion was always a worry, but it was holding up ok. At the weekend, the strong winds might have pushed water inside the housing and onto the pi. It no longer boots using any sd card. RIP.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHAxVMunTqqJHwXKhhgeBKDq22rObDEjL_ORX1OpEPCTt9MTC0Ps1lEjifuaKBIyTx-fQWPamPXLUfM8aoDH1AJ7BJmS8nGFHe3bbIQoPqvu8V7j7JSvpSQrZBNnFFD4O4AU7ICov0OxXE/s1600/rip+pi-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="199" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHAxVMunTqqJHwXKhhgeBKDq22rObDEjL_ORX1OpEPCTt9MTC0Ps1lEjifuaKBIyTx-fQWPamPXLUfM8aoDH1AJ7BJmS8nGFHe3bbIQoPqvu8V7j7JSvpSQrZBNnFFD4O4AU7ICov0OxXE/s320/rip+pi-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEge-BTSultTZfhZHm-EfaPd6T-5pXy2_nI_Qf6E5HGcez_e3Jn5an5oEQ36qCQYrAK4Dd-dBGuv9ciYF9L96bDiIox0KlTpXJOqrlBxHMm8paxDBCacoGCCoWch7A5FvwNp5FhqxiG0HPLp/s1600/pi+plus+weather.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="101" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEge-BTSultTZfhZHm-EfaPd6T-5pXy2_nI_Qf6E5HGcez_e3Jn5an5oEQ36qCQYrAK4Dd-dBGuv9ciYF9L96bDiIox0KlTpXJOqrlBxHMm8paxDBCacoGCCoWch7A5FvwNp5FhqxiG0HPLp/s320/pi+plus+weather.png" width="320" /></a></div>
<br />Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-52916921497214633812016-11-16T16:07:00.001+01:002016-11-16T16:32:25.498+01:00Raspberry pi + Amazon Dash buttonAmazon has launched its Dash button which allows you to order something (e.g. toothpaste) with a single click through Amazon Prime. You can put these buttons anywhere you like (e.g. in the bathroom in case of toothpaste emergency during the upcoming zombie apocalypse). What's great about them is that you can hack them.<br />
<br />
<a name='more'></a>First things first, you need to set it up. Follow the instructions, but don't select a product to order at the end. Just exit the app. This will setup the wifi but leave the button unable to order anything.<br />
<br />
Next, find out the IP of the button. Mk1 versions of the button used to do an ARP request when switched on, which could be intercepted so tell when it was activated. With Mk2 this has been changed, and it now makes a bootp request to get an IP first. So get to your router and grab that IP. Personnally, I've reserved a small block for these buttons and cut off their internet access (you never know; they have no business chatting outside the house).<br />
<br />
Now your button is ready to play with.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ5-ElRqpR_YEaXoPdStV0H7cCcjv9iFOlo242-rHK2mMFymRwV9zruJgj-zxNLrguQHGsPb_R2K3DQYM3w_z2IG1jdNUexru5t0aR6gR_KvuPkkG_oFtIOGES48xqRUKQjcjdmXeaJD1x/s1600/IMG_20161116_145648-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ5-ElRqpR_YEaXoPdStV0H7cCcjv9iFOlo242-rHK2mMFymRwV9zruJgj-zxNLrguQHGsPb_R2K3DQYM3w_z2IG1jdNUexru5t0aR6gR_KvuPkkG_oFtIOGES48xqRUKQjcjdmXeaJD1x/s320/IMG_20161116_145648-blog.jpg" width="320" /></a></div>
<br />
You now have 2 options: intercept the bootp from the button (using its MAC address) or just ping it until it replies. Option #1 has the advantage of not generating additional network traffic. However, when you have a slightly weird network that combines wifi, CPL, and ethernet, bootp packets don't always travel well (for example some wifi routers don't like to route bootp to machines behind them, e.g. some old d-link ones). So for me, ping is the best option.<br />
<br />
The concept is simple: ping your dash buttons until one answers. When it does, trigger an action. Then disable that button for 10 seconds or so to avoid dupes or double triggers. Loop. I wrote a small PHP script for that (it's rough but trivial):<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><?php</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">include_once("config.inc");</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">include_once("debug.inc");</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">$lastaction = Array();</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">function ping($host, $timeout = 1) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> /* ICMP ping packet with a pre-calculated checksum */</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> $package = "\x08\x00\x7d\x4b\x00\x00\x00\x00PingHost";</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> $socket = socket_create(AF_INET, SOCK_RAW, 1);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $timeout, 'usec' => 0));</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> socket_connect($socket, $host, null);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> $ts = microtime(true);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> socket_send($socket, $package, strLen($package), 0);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if (socket_read($socket, 255))</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> $result = microtime(true) - $ts;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> else $result = false;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> socket_close($socket);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> return $result;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">function processbutton($ip) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> global $buttons;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> $action = $buttons[$ip];</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if ($action!="") {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> debug(file_get_contents($action));</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> } else {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> debug("No action found for ".$ip);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> }</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">while(true) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> foreach($buttons as $ip => $action) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> debug("Testing ".$ip);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> $time = ping($ip);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if ($time>0) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> debug($ip." is up");</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if (($time>0) && (($lastaction[$ip]+$delay) < time())) {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> debug("Processing ".$ip);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> $lastaction[$ip] = time();</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> processbutton($ip, $action);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> } else {</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> debug("Activity on ".$ip." is too recent");</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> }</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> }</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> }</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> sleep(2);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">?></span><br />
<div>
<br /></div>
<div>
The config declares the buttons and the action to trigger (in my case hit a page that switches the heating on/off using the <a href="http://pi.raguenaud.fr/2016/06/radpi-genesys.html">radpi</a>).</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><?php</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">$buttons = Array(</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "192.168.0.225" => "http://blah.com/wemoswitch.php",</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "192.168.0.226" => "http://blah.com/wemoswitch.php",</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">);</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">$delay = 10;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">$debug = true;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">?></span></div>
</div>
<div>
<br /></div>
<div>
Now run the script as a daemon at startup and you're good to go for as many buttons as you want with the caveat that the more buttons you have, the more of a delay you might have between push and action up to the point actions could be missed. If you have that many buttons you should switch to a parallel pinging of the buttons or fall back on the interception of the bootp packets.</div>
<div>
<br /></div>
Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-66995888999854171522016-08-08T13:15:00.000+02:002016-08-08T13:39:36.099+02:00Logpi: prototype #2After having got my hands on a new camera (the new 8MP Raspberry cam v2.1 not IR), I had to start the design of the Logpi from scratch: cables and sizes are different, software is different.<br />
<div>
<a name='more'></a>The Raspberry cam is smaller than the original webcam sensor I used. On the plus side, there is no need for a massive lense in front of it so it should integrate better with the housing.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDG9Arx8lsxY49t9POFHhvnLy2I2rarsLpkpKcPuzWrf4V7-BMWhi4gDZolmL-2DlpvNot3eGArO_u1Kn909zaagU_iQmOGUCysOPJlN_EPM9eHFtB6Gxs-LmzQBZFsPOhWnrB7Hy3qIKN/s1600/IMG_20160808_104251-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDG9Arx8lsxY49t9POFHhvnLy2I2rarsLpkpKcPuzWrf4V7-BMWhi4gDZolmL-2DlpvNot3eGArO_u1Kn909zaagU_iQmOGUCysOPJlN_EPM9eHFtB6Gxs-LmzQBZFsPOhWnrB7Hy3qIKN/s320/IMG_20160808_104251-blog.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
One of the issues is that the camera is connected through a flat cable to the camera port of the pi. That cable is short and I haven't found any other provider for the pi zero (one end is narrower than for the normal-sized pi). So that means I'll need to house the pi zero and the camera in the housing instead of externalising everything and keeping only the sensor in the wearable part of the Logpi as in <a href="http://pi.raguenaud.fr/2016/08/logpi-prototype-1.html">prototype #1</a>.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHbw8T1b9Y6xQSzDY0sjnNgPZ9VgCCi3C5aqQ0z-dRsoQq4Ea95uACpVcPla4woky-QJlRAqlHZbpkL51iltIBidp841ACePyCCRcWXtOb6S-WZ1mxisC-HDeavtCx4dN2oMuQU7B_XKX9/s1600/IMG_20160808_104313-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHbw8T1b9Y6xQSzDY0sjnNgPZ9VgCCi3C5aqQ0z-dRsoQq4Ea95uACpVcPla4woky-QJlRAqlHZbpkL51iltIBidp841ACePyCCRcWXtOb6S-WZ1mxisC-HDeavtCx4dN2oMuQU7B_XKX9/s320/IMG_20160808_104313-blog.jpg" width="320" /></a></div>
<div style="text-align: center;">
<span style="font-size: xx-small;">(the camera port is on the right)</span></div>
<div>
<br /></div>
<div>
As I'll need to house more in the wearable port of the Logpi, I might as well try to include the battery so there is no cable sticking out. The battery is a bit big so I'll need a biggish box for it.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCNKo5FKzd3c0jTiXO5gkTEUJ57RH67bXVE_fdrJbK84yMp5BsbZDF_9pD3oyQAUs8wTwPa7flhpKxipPUSg9GFN7Tw0pjfk_b0eIrbXZrbJmqvBWWs_9YBFi8F2gzSVWLeRy3-IVjS1Kb/s1600/IMG_20160808_104401-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCNKo5FKzd3c0jTiXO5gkTEUJ57RH67bXVE_fdrJbK84yMp5BsbZDF_9pD3oyQAUs8wTwPa7flhpKxipPUSg9GFN7Tw0pjfk_b0eIrbXZrbJmqvBWWs_9YBFi8F2gzSVWLeRy3-IVjS1Kb/s320/IMG_20160808_104401-blog.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
It took again a long time to find a suitable box. Eventually, I thought of a glasses box. One of the many that live in my drawers is nearly exactly the size of the battery,</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJA57J3kvWr0TjFblKhqRsGaMa-_qzEf4753Ynv8WvbJPxxjOZwgASbhnCWh-kuwVqCiycbB2mwnB9BJh4duCU2-tieyl8iQp3Alh-inhq2pGc4GbkTQiGskwsg9GfQ0eC1feBZCR-vSDk/s1600/IMG_20160808_104450-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJA57J3kvWr0TjFblKhqRsGaMa-_qzEf4753Ynv8WvbJPxxjOZwgASbhnCWh-kuwVqCiycbB2mwnB9BJh4duCU2-tieyl8iQp3Alh-inhq2pGc4GbkTQiGskwsg9GfQ0eC1feBZCR-vSDk/s320/IMG_20160808_104450-blog.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
I drilled a hole for the camera lens (7mm) which fits snuggly. No need for glue to keep it there. I had a few problems with the USB cable that connects the battery with the pi: the connectors were too long to fit well in the box so I had to cut out the base of the cables and remove the protecting plastic.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitXEc8TZw5GW0l3C_ode-1AyF_YEB6RUPE7sArkU3VmED5zqH6u78sNoi6XteUcQ0z91aWcTq4Pm8ZrROOr8apso_ZpqEFguX-B_40Uoo-poL-IszOkkiPeChAH4BKSZWTCM_0HKgytTgZ/s1600/IMG_20160808_104416-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitXEc8TZw5GW0l3C_ode-1AyF_YEB6RUPE7sArkU3VmED5zqH6u78sNoi6XteUcQ0z91aWcTq4Pm8ZrROOr8apso_ZpqEFguX-B_40Uoo-poL-IszOkkiPeChAH4BKSZWTCM_0HKgytTgZ/s320/IMG_20160808_104416-blog.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
With everything in the box, it doesn't look to tight.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOLmxL-9Cy5G3tF_CLY3SpPywBu0mm7K4-4x__HtAEpIXNWMQiq9Sdd9IleH42eDFX9uQvLJsGYXunKfLN7xnnz4WVE3lmHZB-v2cctCS3ugmAbxGg7xEl43lDDbdZOXC-IDwJzBOuajlS/s1600/IMG_20160808_111819-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOLmxL-9Cy5G3tF_CLY3SpPywBu0mm7K4-4x__HtAEpIXNWMQiq9Sdd9IleH42eDFX9uQvLJsGYXunKfLN7xnnz4WVE3lmHZB-v2cctCS3ugmAbxGg7xEl43lDDbdZOXC-IDwJzBOuajlS/s320/IMG_20160808_111819-blog.jpg" width="320" /></a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9iuyzGxD-Bs2DZ3DpaS8mCgIFrPO2nDQSJN7WNCBK-Oyn2oV-8gLtaD3nGL4vnO6b89ddX3G06WTYcjXXI4fIXf102KB4eAk1feJ-YX2dXPz6jDS1rYQ5SW_bIhNok8VIhj79ACdDS-zg/s1600/IMG_20160808_111908-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9iuyzGxD-Bs2DZ3DpaS8mCgIFrPO2nDQSJN7WNCBK-Oyn2oV-8gLtaD3nGL4vnO6b89ddX3G06WTYcjXXI4fIXf102KB4eAk1feJ-YX2dXPz6jDS1rYQ5SW_bIhNok8VIhj79ACdDS-zg/s320/IMG_20160808_111908-blog.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
I'll hang it around my neck instead of using a safety pin (it's a bit heavy for that).</div>
<div>
<br /></div>
<div>
For the software, I simply wrote a script that takes a photo every 30 seconds and starts with the system. One thing that complicated things a little bit: the pi doesn't have a persistent clock so when it's running offline it will start without time. I had to add a persistent counter to add to the file names so that there is no clash after multiple restarts.</div>
<div>
<br /></div>
<div>
<div>
<i>#!/bin/bash</i></div>
<div>
<i><br /></i></div>
<div>
<i>INTERVAL=30 # Time between captures, in seconds</i></div>
<div>
<i>WIDTH=3280 # Image width in pixels</i></div>
<div>
<i>HEIGHT=2464 # Image height in pixels</i></div>
<div>
<i>QUALITY=85 # JPEG image quality (0-100)</i></div>
<div>
<i>DEST=/home/cedric/webcam # Destination directory (MUST NOT CONTAIN NUMBERS)</i></div>
<div>
<i>PREFIX=img # Image prefix (MUST NOT CONTAIN NUMBERS)</i></div>
<div>
<i><br /></i></div>
<div>
<i>COUNT=`cat count.dat`</i></div>
<div>
<i><br /></i></div>
<div>
<i>mkdir -p $DEST # Create destination directory (if not present)</i></div>
<div>
<br /></div>
<div>
<i><br /></i></div>
<div>
<i>while : # Forever</i></div>
<div>
<i>do</i></div>
<div>
<i> echo $COUNT</i></div>
<div>
<i> OUTFILE=`printf "$DEST/$PREFIX%05d-$COUNT.jpg"`</i></div>
<div>
<i> # echo $OUTFILE</i></div>
<div>
<i> raspistill -n -w $WIDTH -h $HEIGHT -q $QUALITY -th none -t 250 -o $OUTFILE</i></div>
<div>
<i> COUNT=$(($COUNT + 1))</i></div>
<div>
<i> echo $COUNT > count.dat</i></div>
<div>
<i> sleep $INTERVAL</i></div>
<div>
<i>done</i></div>
</div>
<div>
<br /></div>
<div>
A few test photos:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-vetP4Td_Imhkeyjx3we1KDoUJEgSeqvLAsG9PMeQANxL1Vy5VBJ3sC95QlRX1l0HFsCsyEyae_GyxrZ_rlkDmfseNJsmZEaqArP4ocb1bW5yHiP7iePk3Fl8JsJT3ER1PveT2THsjFVR/s1600/img00006-26-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-vetP4Td_Imhkeyjx3we1KDoUJEgSeqvLAsG9PMeQANxL1Vy5VBJ3sC95QlRX1l0HFsCsyEyae_GyxrZ_rlkDmfseNJsmZEaqArP4ocb1bW5yHiP7iePk3Fl8JsJT3ER1PveT2THsjFVR/s320/img00006-26-blog.jpg" width="320" /></a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMxJl4vLJzhuY_JglKpa7ZO9wui0PlkKcf1k_pNz7K6G7SufZQkWwLt2iA2VQozKZeJP9J2p7rwrhlMBL8SbJb_8ZCqzvg9WSnntNsch4ZPBPrZIepb7bvWjHLrZV7aJ3p3eqqALVlbH6W/s1600/img00013-33-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMxJl4vLJzhuY_JglKpa7ZO9wui0PlkKcf1k_pNz7K6G7SufZQkWwLt2iA2VQozKZeJP9J2p7rwrhlMBL8SbJb_8ZCqzvg9WSnntNsch4ZPBPrZIepb7bvWjHLrZV7aJ3p3eqqALVlbH6W/s320/img00013-33-blog.jpg" width="320" /></a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKe_uGCadd3wRoAF44ooRUl-EokI7bvAVCj1X1gLl5XiilwTSdmJR4sJL5CGA9zwsG0EyFN7P6_xwuu56szYyxZcsa_a6Aiog_AoZPUcoaPAo_Dg2K1nmJ2Lz0NbOMPJDQoReyusfo3Wd4/s1600/img00011-31-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKe_uGCadd3wRoAF44ooRUl-EokI7bvAVCj1X1gLl5XiilwTSdmJR4sJL5CGA9zwsG0EyFN7P6_xwuu56szYyxZcsa_a6Aiog_AoZPUcoaPAo_Dg2K1nmJ2Lz0NbOMPJDQoReyusfo3Wd4/s320/img00011-31-blog.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
Known issues:</div>
<div>
- Shutdown can damage the card. Ideally, it would be good to wire a switch.</div>
<div>
- There is no way to know the battery is flat. Ideally, porting the activity light of the pi to the edge of the case would solve the problem.</div>
<div>
- The battery is a bit large. Switching to a 3.7v drone one with power up would help reduce the size of the case needed. Lifetime of the battery could become an issue since I've only found 1000mA or less ones.</div>
<div>
<br /></div>
<div>
These might be solved in prototype #3.</div>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGAH39goRr2WpUsaBIlGlHI2W1mqDsA1G1GmuaTXJ98QNEcVykwrS1ADYa2MOfJ7vSc8HLpMprBa8pqpzJJ3OS_8dwDhJWjPn8ADFYP5lOnh15XudSReHwy6M9JxoO0QfJaiX7KsE0rLGZ/s1600/pi+plus+camera.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="101" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGAH39goRr2WpUsaBIlGlHI2W1mqDsA1G1GmuaTXJ98QNEcVykwrS1ADYa2MOfJ7vSc8HLpMprBa8pqpzJJ3OS_8dwDhJWjPn8ADFYP5lOnh15XudSReHwy6M9JxoO0QfJaiX7KsE0rLGZ/s320/pi+plus+camera.jpg" width="320" /></a></div>
<div>
<br /></div>
Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-86844150948364725182016-08-01T09:41:00.000+02:002016-08-08T13:15:39.082+02:00Logpi: prototype #1I've been wondering about <a href="https://en.wikipedia.org/wiki/Lifelog" target="_blank">lifeloggers</a> for a while. I'm not convinced they're useful outside specific events, but as my niece is getting baptised next month, I thought I'd create one and wear it.<br />
<br />
<a name='more'></a>I finally managed to get my hands on a pi zero a short while back. I was thinking of using it to replace the pi 2 in the <a href="http://pi.raguenaud.fr/2016/06/metpi-1-genesys.html">Metpi</a>, but I'll play with it first. It's ideal for lifelogger given its size.<br />
<br />
To keep costs down, I went through drawers and dug out an ancient webcam (circa 1999) that I haven't used in years. At the time, Linux didn't have drivers for it and the manufacturer didn't release the specs, so it wasn't that useful to me given that the only Windows machines in the house are my work ones. But I discovered that they now exist and are included in the default kernel (surprising for an old sensor). So, suspecting the sensor is probably a fairly small and simple one, and given that it's already connected to USB (so no need to write code for it), I opened it.<br />
<br />
There was no easy way to do it, it was obviously not designed to be opened since everything in it was glued, so the dremel was called in to the rescue.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho4dH9ELGrQ3jTWD2Vqe01nFjN5AwUY2rnahDpqXOAed3vW_9PzuiZSvEadp9B-Xq1gD-xxz4MN6tZMugThuhTaDLIbvbEAdzx6b8kwXnXNEAP3uhJAu91af6aYbQe9NiBOva23Lt3dXSo/s1600/IMG_20160728_130105-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho4dH9ELGrQ3jTWD2Vqe01nFjN5AwUY2rnahDpqXOAed3vW_9PzuiZSvEadp9B-Xq1gD-xxz4MN6tZMugThuhTaDLIbvbEAdzx6b8kwXnXNEAP3uhJAu91af6aYbQe9NiBOva23Lt3dXSo/s320/IMG_20160728_130105-blog.jpg" width="320" /></a></div>
<br />
What I was after was this tiny thing from which the IR leds have been cut out:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZD0ZMuyNmWQC0VpkDeRBINYvB-O3DJLCWp8XEKIvx0nBlGY7tk37pmggVz5Nw_59_LM2WKs6zBHAJd2cnVRNcbKfP7QG1f3pGab8tIruM62byUzt-fo27guArIOhti-iDy-xzIbjuo6my/s1600/IMG_20160727_141636-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZD0ZMuyNmWQC0VpkDeRBINYvB-O3DJLCWp8XEKIvx0nBlGY7tk37pmggVz5Nw_59_LM2WKs6zBHAJd2cnVRNcbKfP7QG1f3pGab8tIruM62byUzt-fo27guArIOhti-iDy-xzIbjuo6my/s320/IMG_20160727_141636-blog.jpg" width="320" /></a></div>
<br />
It was even smaller than I was expecting, though the lens part was a bit long.<br />
<br />
I tested it on the pi by running the usb camera capture tool:<br />
<i>fswebcam -r 640x480 --no-banner webcam.jpg</i><br />
It worked. The resolution isn't great, it's a very old webcam, but it captured images.<br />
<br />
The next step was to dress it up a bit if it was going to be worn. I looked for a suitable box for a long time and eventually found what I needed in the make up department of the local supermarket: eye shadow boxes from Maybelline. I made a hole on one side (square-ish) and added a safety pin.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHm763WdXG2Ca6axrHY5KU8K3GPDJFTNvb9f9NYR39t3zh9oy63kZyC0fdkr2mweEvO7J6LWwYuvjTQZiplwGizPe9eWWqsTbkYBm4tEmubnTrbWIgCQAMrH77PdiEM5YnvYeJLMC5Ugkf/s1600/IMG_20160730_153353-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHm763WdXG2Ca6axrHY5KU8K3GPDJFTNvb9f9NYR39t3zh9oy63kZyC0fdkr2mweEvO7J6LWwYuvjTQZiplwGizPe9eWWqsTbkYBm4tEmubnTrbWIgCQAMrH77PdiEM5YnvYeJLMC5Ugkf/s320/IMG_20160730_153353-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi92JpFP6WTCrllL8fB8eYsBsAnzRYUO5fNSDuIWTV9AzGU_7cBAWgN8d9aHCtKi2DFoZs0hpAKASCCa2ETc87nNUgTlA8i1VGGG9U-S5gk8C0eAx8plV8w2j0MN5x_7C65CUdRhghTlln0/s1600/IMG_20160730_153401-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi92JpFP6WTCrllL8fB8eYsBsAnzRYUO5fNSDuIWTV9AzGU_7cBAWgN8d9aHCtKi2DFoZs0hpAKASCCa2ETc87nNUgTlA8i1VGGG9U-S5gk8C0eAx8plV8w2j0MN5x_7C65CUdRhghTlln0/s320/IMG_20160730_153401-blog.jpg" width="320" /></a></div>
<br />
I kept the power source external to the casing because there was just no way you could get more than an hour or two from a small enough battery (from a phone). Instead, I went for an external charging battery holding 3,600mAh that is about 1.5 pi sero in size. With the power consumption from the pi, it should hold a good part of the day. I haven't tested that theory yet.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6CAxGdV0RwlCGGleYcbVIF05tAXTqivJ0-ajTZ-wLkZ5lAohs6QWFVAG6Z1KFekciuzcUqCxW2SjT3npCK52qFrD1xblKbY2m-hWAYtfgsEkNxzn7kQg3YfhByolGbPQHTQvh9cPwEZlo/s1600/IMG_20160801_091758-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6CAxGdV0RwlCGGleYcbVIF05tAXTqivJ0-ajTZ-wLkZ5lAohs6QWFVAG6Z1KFekciuzcUqCxW2SjT3npCK52qFrD1xblKbY2m-hWAYtfgsEkNxzn7kQg3YfhByolGbPQHTQvh9cPwEZlo/s320/IMG_20160801_091758-blog.jpg" width="320" /></a></div>
<br />
I then had the camera in its box attached to my clothes, a usb cable running under my clothes to the pi and its battery pack in my pocket. A good compromise. I added a script that autostarts when the pi boots up and takes a photo every 30 seconds, and I took it for a spin:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwH7OZtLlOTdlC8hqsQNfxDnO9TfPKVGhZ8faugagBC_ojgCXji2ZvGP4U9bsjg0jXTVMkkq7Jh29JU9TR5FfAFjCAoKPMitzMc6ec4e6fV9jsSEdFMJaVmBMAW6itLwpx3M_-AgyMa4IR/s1600/2016-07-30_14151469880930-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwH7OZtLlOTdlC8hqsQNfxDnO9TfPKVGhZ8faugagBC_ojgCXji2ZvGP4U9bsjg0jXTVMkkq7Jh29JU9TR5FfAFjCAoKPMitzMc6ec4e6fV9jsSEdFMJaVmBMAW6itLwpx3M_-AgyMa4IR/s320/2016-07-30_14151469880930-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlL00xT2MeXSQ-zClueMFm2oj5TJNuwGDsgh5dTxtmV_dBxfaJEgSD8GTlZOeSKyY5sAClCRtFOKCJRApnwP6LsCXz0JoB4BBAvblCAPCP_VP6-sbm_Z-JY1W15ljxUVLScPkL9UI94Zo1/s1600/2016-07-30_14161469880961-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlL00xT2MeXSQ-zClueMFm2oj5TJNuwGDsgh5dTxtmV_dBxfaJEgSD8GTlZOeSKyY5sAClCRtFOKCJRApnwP6LsCXz0JoB4BBAvblCAPCP_VP6-sbm_Z-JY1W15ljxUVLScPkL9UI94Zo1/s320/2016-07-30_14161469880961-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS4GriHTMqeQIaOfdQ25KO5cV1aPhEntYZCCGGytDGqsjtGc3SGztv3YbH0OgYWspwZJ4kIh-yBByB35by807bTxT-oHj-TFfGwFTuKL3DhQT7m7MvIyxf7LlS5y7AtwNGvgnNqnj7F2UJ/s1600/2016-07-30_14191469881146-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS4GriHTMqeQIaOfdQ25KO5cV1aPhEntYZCCGGytDGqsjtGc3SGztv3YbH0OgYWspwZJ4kIh-yBByB35by807bTxT-oHj-TFfGwFTuKL3DhQT7m7MvIyxf7LlS5y7AtwNGvgnNqnj7F2UJ/s320/2016-07-30_14191469881146-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4Jc_KtjILe15PIf-QEeSIzNzxFMoR_dCCiFVtkmDP7RPHPd20eZwveB6lHzPgg_f7rt7wq5n-ZSpNQKuUCuHiL01R0Rg8DQpmHxmCH9tFC1EXQSEaDligpou6fwa_TTsC6NsS94NYekyK/s1600/2016-07-30_14251469881517-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4Jc_KtjILe15PIf-QEeSIzNzxFMoR_dCCiFVtkmDP7RPHPd20eZwveB6lHzPgg_f7rt7wq5n-ZSpNQKuUCuHiL01R0Rg8DQpmHxmCH9tFC1EXQSEaDligpou6fwa_TTsC6NsS94NYekyK/s320/2016-07-30_14251469881517-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKp5QefxeUXR5NQUMPWe3p5fO03AZwVgowLx6pQyouOBv851p7JbDfcarjAEQ_i5iL3rAAAfYwq7AhP2NlmbjaBLT9zaO55lK_zzmBOd9o3tuat3qDCY6D6EJ1pRd4Lj68QX3EbgT4HuT4/s1600/2016-07-30_14251469881548-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKp5QefxeUXR5NQUMPWe3p5fO03AZwVgowLx6pQyouOBv851p7JbDfcarjAEQ_i5iL3rAAAfYwq7AhP2NlmbjaBLT9zaO55lK_zzmBOd9o3tuat3qDCY6D6EJ1pRd4Lj68QX3EbgT4HuT4/s320/2016-07-30_14251469881548-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTAUjLrx0Fk7qaLbHcxnQLic73PYUzxFhSwaNnrA3xmrqk1b0kWmp1qKwc9kKzSFqvr_yVGy7IZOZbOXJb4RNd1zf6P1RAJGzbUYd0pcNr5OSjYm2iSobytLUTkSwH7VEwtfb31J0nGnh9/s1600/2016-07-30_14301469881856-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTAUjLrx0Fk7qaLbHcxnQLic73PYUzxFhSwaNnrA3xmrqk1b0kWmp1qKwc9kKzSFqvr_yVGy7IZOZbOXJb4RNd1zf6P1RAJGzbUYd0pcNr5OSjYm2iSobytLUTkSwH7VEwtfb31J0nGnh9/s320/2016-07-30_14301469881856-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGCuhOLQR43V4Bl1ADTR1WTmmccYy3ogWEM81IYUCXOTT2qxIj8MhAxBnMZ-vRq7Sigvqk0o_p_r-iZKcdk6ZPPuebJbXmK1putufmyyCZ3I6bsle5iujkBLKWmxSKpp9hGEOfm-m2-Psw/s1600/2016-07-30_14341469882073-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGCuhOLQR43V4Bl1ADTR1WTmmccYy3ogWEM81IYUCXOTT2qxIj8MhAxBnMZ-vRq7Sigvqk0o_p_r-iZKcdk6ZPPuebJbXmK1putufmyyCZ3I6bsle5iujkBLKWmxSKpp9hGEOfm-m2-Psw/s320/2016-07-30_14341469882073-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhURKKiT4-HerJehjWGWE5cXzaochw_kcimVakYuduHDDqv0VMPq-DnzQScphCAvNjBL5on-Bg8YD0yl333Cnk7hnqap4_bRAPSmzOqKL7qM9_xiC-nowEFKlLq2ZHrj-8WNCZEwA0Bh4Dy/s1600/2016-07-30_14351469882104-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhURKKiT4-HerJehjWGWE5cXzaochw_kcimVakYuduHDDqv0VMPq-DnzQScphCAvNjBL5on-Bg8YD0yl333Cnk7hnqap4_bRAPSmzOqKL7qM9_xiC-nowEFKlLq2ZHrj-8WNCZEwA0Bh4Dy/s320/2016-07-30_14351469882104-blog.jpg" width="320" /></a></div>
<br />
The photos aren't that interesting, but it worked. And the point of these things is that you forget about them and you never know what will be taken.<br />
<br />
I now have a raspberry pi camera v2.1, which produces much better photos (8MP sensor). So back to the drawing board for ptototype #2.<br />
<br />
Next step: <a href="http://pi.raguenaud.fr/2016/08/logpi-prototype-2.html">prototype #2</a><br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGKDHqkB1TW-T5QAGYi12VfbeloYTOrbFdY6AJhHCgAzfXfKlJU4rHCH2Egey7xQ9_a1I-AHWebVijYoX68QSyRTlz8VGYutMxbmm0qLG65ZgY4DycDUEh_LSNGCWBMce0jE1fXM6wW1TI/s1600/pi+plus+camera.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="101" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGKDHqkB1TW-T5QAGYi12VfbeloYTOrbFdY6AJhHCgAzfXfKlJU4rHCH2Egey7xQ9_a1I-AHWebVijYoX68QSyRTlz8VGYutMxbmm0qLG65ZgY4DycDUEh_LSNGCWBMce0jE1fXM6wW1TI/s320/pi+plus+camera.jpg" width="320" /></a></div>
<br />Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-79763934197180534842016-07-09T16:53:00.001+02:002016-07-09T19:08:45.136+02:00Metpi #5: outdoor housingOnce the Metpi <a href="http://pi.raguenaud.fr/2016/06/metpi-3-software.html">setup </a>and <a href="http://pi.raguenaud.fr/2016/07/metpi-4-tweaks.html">adjusted</a>, it was time to put it outside. I looked around at how to build a suitable housing and even though the Stevenson box would look nice, it would be too difficult to build to a high standard. So I went low tech and built one with plant pot plates.<br />
<br />
<a name='more'></a>I got 10 24cm diameter plates from the local garden center. I cut the inside of 8 of them and kept 2 full ones for the top and bottom plates. There are ways to cut nicely, e.g. with a heated tin can, but I couldn't find one large enough. So I cut (read melted) with a Dremel.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitAEtNJRJsE8ayRocCnkKnM0uM0aRtuugG2s7sklg_FZO5Rhw09PJR3X_1WJfiOaWpkNOdCMnJai3BaLdm6EDM8gP9KZtxyvVaii4nPZn4Kd9frwrXB_3bLaRfM6SdI0i59atFvpqHZOm9/s1600/IMG_20160707_090737-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitAEtNJRJsE8ayRocCnkKnM0uM0aRtuugG2s7sklg_FZO5Rhw09PJR3X_1WJfiOaWpkNOdCMnJai3BaLdm6EDM8gP9KZtxyvVaii4nPZn4Kd9frwrXB_3bLaRfM6SdI0i59atFvpqHZOm9/s320/IMG_20160707_090737-blog.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKpmvjv4GXOmnN8pCe-jFXpCD7iUJ3uXAHBErTdxAbg9xFNWt-ZsVYXW7vg0xdKuLdNKAG22Fa6CXc5q1q8mhNFULXbM9vbPhpU5NjrDl4M6C_FbDr_UMyWerYR8GvCtUe9hVZigOJA4v8/s1600/IMG_20160707_090722-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKpmvjv4GXOmnN8pCe-jFXpCD7iUJ3uXAHBErTdxAbg9xFNWt-ZsVYXW7vg0xdKuLdNKAG22Fa6CXc5q1q8mhNFULXbM9vbPhpU5NjrDl4M6C_FbDr_UMyWerYR8GvCtUe9hVZigOJA4v8/s320/IMG_20160707_090722-blog.jpg" width="320" /></a></div>
<br />
The next step was to paint them white to reflect the heat and the light. Notice the holes. Each plate needs 4 wholes for structure. If you get plates with a lip, remember to drill them too to avoid water accumulation.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHdFSttJR5qAiGXD09ATctTTZgwLOrYTc9ZHkBfPJDGKpRZlWdOiv_A3cd2jqkpccINuKiEt_Usbv_vw83G7N6a_PrbzcfYy2qcucms2S7bhMD4Zlq6PACQdJWFJ6jgeRNodigXfuVed7y/s1600/IMG_20160707_110350-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHdFSttJR5qAiGXD09ATctTTZgwLOrYTc9ZHkBfPJDGKpRZlWdOiv_A3cd2jqkpccINuKiEt_Usbv_vw83G7N6a_PrbzcfYy2qcucms2S7bhMD4Zlq6PACQdJWFJ6jgeRNodigXfuVed7y/s320/IMG_20160707_110350-blog.jpg" width="320" /></a></div>
<br />
I used a PVC paint undercoat and then concrete outdoor paint.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7KVx1O9ibul3pi0WLdBzXKZUqlk50aqrTOM-IG52xTevITGAKu66-f0xnq1WUiNw4TCtctNkVS9xcrZNEGVNZkexFQ_ZSbs91wbra_sthX4gwC9hB2-069wwSJ0hs4pXTL9iz3bIRvQzw/s1600/IMG_20160709_095719-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7KVx1O9ibul3pi0WLdBzXKZUqlk50aqrTOM-IG52xTevITGAKu66-f0xnq1WUiNw4TCtctNkVS9xcrZNEGVNZkexFQ_ZSbs91wbra_sthX4gwC9hB2-069wwSJ0hs4pXTL9iz3bIRvQzw/s320/IMG_20160709_095719-blog.jpg" width="320" /></a></div>
<br />
I then cut long cut-to-size 6mm diameter bolts to run them through the plates.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-ifAH1wImlQe7oeqPXJR-jIwMHbljM4qy1J2bhSZEGwIVojnL2utGacDxFipvfIhB8F2YXfw5z6xWf3HmBjt77sByy_81jBgrnr11Ch3LD67isoU3QXisvTfoiqR0b2YgZs3PdZGHdL8T/s1600/IMG_20160709_095755-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-ifAH1wImlQe7oeqPXJR-jIwMHbljM4qy1J2bhSZEGwIVojnL2utGacDxFipvfIhB8F2YXfw5z6xWf3HmBjt77sByy_81jBgrnr11Ch3LD67isoU3QXisvTfoiqR0b2YgZs3PdZGHdL8T/s320/IMG_20160709_095755-blog.jpg" width="320" /></a></div>
<br />
Countersunk nuts at the bottom and pile up the plates separated with 2.5cm of 10mm diameter pipe for spacing. You don't want the space to be too wide but it needs to let the air in.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVwMjTvwkQk4guMpJXjlm2TYcgU4NLW32R6iRzVjv7uGIXig5sPenkyHpCIjOzOHnHyAvxZSk_F5wxynl22d52EWoYnBkEvW1KnSEXySl4wseUbBOLryoz5iyRBFBAvtwpYwtmec97x3Qn/s1600/IMG_20160709_100902-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVwMjTvwkQk4guMpJXjlm2TYcgU4NLW32R6iRzVjv7uGIXig5sPenkyHpCIjOzOHnHyAvxZSk_F5wxynl22d52EWoYnBkEvW1KnSEXySl4wseUbBOLryoz5iyRBFBAvtwpYwtmec97x3Qn/s320/IMG_20160709_100902-blog.jpg" width="240" /></a></div>
<br />
Half way up, insert the Raspberry pi + Sensor Hat + DS18B20 temperature sensor (for comparison). I attached the pi to the tubing with tin wire. Not perfect but will do for now. In time I need to figure out a way to make sure no water can run down the wires.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGbUohbFQe6ndT_UfSwv2ZO9F2RqxaQLoQEa3urpP0TFqsLit6zFd1qoqNgWakcj2fh4XoCBTcqjvooQOwfwoB0qUmiEDBHMBAQOjm14rZDODajFRUsYfT3kBUgb7rfwkZ5Kfxk7NP7IyH/s1600/IMG_20160709_110414-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGbUohbFQe6ndT_UfSwv2ZO9F2RqxaQLoQEa3urpP0TFqsLit6zFd1qoqNgWakcj2fh4XoCBTcqjvooQOwfwoB0qUmiEDBHMBAQOjm14rZDODajFRUsYfT3kBUgb7rfwkZ5Kfxk7NP7IyH/s320/IMG_20160709_110414-blog.jpg" width="320" /></a></div>
<br />
Finally at the top, a full plate.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEzBdoMDl9GcLTMhOYA8gnpGM-O7ilI03LlN_Xv-GJQEsYnzmRXc1ZpRIawIWd9IsRnHKQ6d_u9QUKKXg50epcsZbK83cTu_L2iGCPYd74TdCeS19KKyLhV3h9fdFqNA-Muzs0TuyIYdTj/s1600/IMG_20160709_111101-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEzBdoMDl9GcLTMhOYA8gnpGM-O7ilI03LlN_Xv-GJQEsYnzmRXc1ZpRIawIWd9IsRnHKQ6d_u9QUKKXg50epcsZbK83cTu_L2iGCPYd74TdCeS19KKyLhV3h9fdFqNA-Muzs0TuyIYdTj/s320/IMG_20160709_111101-blog.jpg" width="320" /></a></div>
<br />
I used a bracket to hang it to a small post and a waterproof junction box to house the power supply. The location isn't ideal, too close to gravel which will reflect the heat. In time I'll move it to grass.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5xDZZEl0g_cjoIsIjygqZoqmvf8KBGwHFqB1_CL5Rg9YqJHMrArWwGpQ7xUJXov_TB__PQ94VxVcmyCEUTzGzuo6-u-rXNZcK68xPIALScZ2dGtKASDyG1FWvwKyPL1X4amLGpKiV1FI8/s1600/IMG_20160709_152825-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5xDZZEl0g_cjoIsIjygqZoqmvf8KBGwHFqB1_CL5Rg9YqJHMrArWwGpQ7xUJXov_TB__PQ94VxVcmyCEUTzGzuo6-u-rXNZcK68xPIALScZ2dGtKASDyG1FWvwKyPL1X4amLGpKiV1FI8/s320/IMG_20160709_152825-blog.jpg" width="320" /></a></div>
<br />
I don't know yet how the pi will behave outside. I worry about corrosion. We'll see over time.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlQzZDFIBCOKWVfSEIgBcqEqW0pVIHXeAK_EPiPWt2b3suABfJkX5e0bMR9y5rCTaIQPeICyZGesWat4bEPgUvRgUr5fysAMY7CP3frTuKpt7Bh_y1oaImL59eZu-08dnygPKU3l5M8XLX/s1600/pi+plus+weather.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="101" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlQzZDFIBCOKWVfSEIgBcqEqW0pVIHXeAK_EPiPWt2b3suABfJkX5e0bMR9y5rCTaIQPeICyZGesWat4bEPgUvRgUr5fysAMY7CP3frTuKpt7Bh_y1oaImL59eZu-08dnygPKU3l5M8XLX/s320/pi+plus+weather.png" width="320" /></a></div>
<br />Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-17206249337131443762016-07-06T11:55:00.000+02:002016-07-06T11:56:55.313+02:00Metpi #2: hardwareI've been using <a href="https://www.wunderground.com/" target="_blank">Wu</a> (Weather Underground) for a while to get local weather readings (temperature, humidity, wind, etc). With all the Raspberry pis in the house, I thought I'd use one to measure weather data at home.<br />
<a name='more'></a><br />
For the pi, I selected a pi 2 that had been replaced by a pi 3 to play videos (the pi 3 just about copes with software x265 decoding at 720p).<br />
<br />
I didn't want to do anything complicated for measurements given that I could get local data easily by using one of the neighbouring stations. So I orientated myself toward the <a href="https://www.raspberrypi.org/products/sense-hat/" target="_blank">Sense Hat</a> (aka Astro Pi). It measures temperature, humidity, atmospheric pressure, the magnetic field, and position in space. That gave me atmospheric pressure on top of what I could already measure with a <a href="http://pi.raguenaud.fr/2016/06/radpi-temperature.html">DHT22</a> so I went for that.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://cdn.shopify.com/s/files/1/0176/3274/products/101720_1024x1024.jpg?v=1456169912" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="264" src="https://cdn.shopify.com/s/files/1/0176/3274/products/101720_1024x1024.jpg?v=1456169912" width="320" /></a></div>
<br />
The Sense Hat is great in terms of hardware integration: all you need is to plug it in and it runs. Then it's a matter of reading the values and doing something with them.<br />
<br />
Next: <a href="http://pi.raguenaud.fr/2016/06/metpi-3-software.html">software</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMwmXtIZeb-9RPEBV91qWiUPKNHRD9g65Kd1m4j_DslC12mXYthhJ-e1zMsnhSQ2R-9zzo9aQzTs1c5Zsy6abv87bsZzd9nGSa3FDULyc5gF2OJJLuMT8H4_DYgRxNE10zzyF0bRGwwL34/s1600/pi+plus+weather.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="101" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMwmXtIZeb-9RPEBV91qWiUPKNHRD9g65Kd1m4j_DslC12mXYthhJ-e1zMsnhSQ2R-9zzo9aQzTs1c5Zsy6abv87bsZzd9nGSa3FDULyc5gF2OJJLuMT8H4_DYgRxNE10zzyF0bRGwwL34/s320/pi+plus+weather.png" width="320" /></a></div>
<br />Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-14304158152761497382016-07-05T10:11:00.000+02:002016-07-09T16:54:24.324+02:00Metpi #4: tweaksAfter a few days monitoring the metpi, some tweaks are necessary.<br />
<br />
<a name='more'></a>The main problem is the proximity of the Sense Hat to the pi board and USB ports. Both of these generate heat (especially the USB ports) and it affects the sensors on the Sense Hat. It is a known problem and it was a problem on the space station, but they were using a different version of the pi (a B+ instead of a 2B) and a different configuration, which made it easy to resolve using a <a href="https://github.com/astro-pi/watchdog/blob/master/watchdog.py#L2399" target="_blank">simple formula</a>. I tried many formulas over a few days, and it was clear that the effect wasn't linear (maybe heat accumulation, maybe some other external event I'm not aware of outside).<br />
<br />
So I gave up and decided to start again and tackle the heat problem differently. First, I raised the Sense Hat to keep it further from the pi board and to let more air in.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLXgfe6bZLIf-YAB4vhTZb2dApIgFLEAKLyspStVHv1Nd9mvXgqoQ-UqU5z5DnFcnJFSBS3M2MdxnFmKHTRPdY4tNd1klhhA3OK87xWNSX_KSOaOHgMMqmAqv7xQ3CW8c-DJEMglf2VhNw/s1600/IMG_20160705_095043.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLXgfe6bZLIf-YAB4vhTZb2dApIgFLEAKLyspStVHv1Nd9mvXgqoQ-UqU5z5DnFcnJFSBS3M2MdxnFmKHTRPdY4tNd1klhhA3OK87xWNSX_KSOaOHgMMqmAqv7xQ3CW8c-DJEMglf2VhNw/s320/IMG_20160705_095043.jpg" width="320" /></a></div>
<br />
It made a huge difference. The Sense Hat was much less affected by the heat of the pi and the temperature was more consistent with that of other sensors.<br />
<br />
I also tried to put a fan next to the pi to force air into it to cool down the whole thing and avoid any heat accumulation. That didn't work since it lowered the temperature read by the Hat's sensors. I can only presume that the fan, by compressing the air, lowered its temperature a bit and it was enough to read a couple of degrees lower temperatures.<br />
<br />
I also adjusted the formulas. I eventually abandonned trying to use an average of all 3 temperature sensors on the Hat since 2 were always giving exactly the same temperature (which makes me think that the temperature is read from the humidity sensor, not from a separate sensor) and the pressure sensure always reports about 2 degrees above the expected temperature (it might be the one closest to another component on the board and is affected by it). I'm now using only the humidity sensor's readings. They seem mostly consistent with the expected temperature.<br />
<br />
Finally, I smooth out the temperature recorded to avoid local peaks due to wind and other external factors (at the moment the pi is exposed at the corner of a building; a proper housing is being built). For that, I just use a rotating buffer and average the last 3 readings (I experimented with 5 and 10 also). The curves are more sensible now and look less like discrete instant temperatures.<br />
<br />
I'll keep monitoring and I'll move the pi to its new housing in the next few days.<br />
<br />
Next step: <a href="http://pi.raguenaud.fr/2016/07/metpi-5-outdoor-housing.html">outdoor housing</a>.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlQzZDFIBCOKWVfSEIgBcqEqW0pVIHXeAK_EPiPWt2b3suABfJkX5e0bMR9y5rCTaIQPeICyZGesWat4bEPgUvRgUr5fysAMY7CP3frTuKpt7Bh_y1oaImL59eZu-08dnygPKU3l5M8XLX/s1600/pi+plus+weather.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="101" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlQzZDFIBCOKWVfSEIgBcqEqW0pVIHXeAK_EPiPWt2b3suABfJkX5e0bMR9y5rCTaIQPeICyZGesWat4bEPgUvRgUr5fysAMY7CP3frTuKpt7Bh_y1oaImL59eZu-08dnygPKU3l5M8XLX/s320/pi+plus+weather.png" width="320" /></a></div>
<br />Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-33972137777547546972016-06-29T12:26:00.000+02:002016-07-05T10:12:17.229+02:00Metpi #3: softwareFor the software side of the Metpi, I wanted to do two things:<br />
- Log the data with the other pis from the <a href="http://pi.raguenaud.fr/2016/06/radpi-genesys.html">Radpi system</a>.<br />
- Publish the data to the Weather Underground platform.<br />
<a name='more'></a><br />
I've therefore split the work into two programs: one reads the data and makes it available to the Radpi server, the other sends the results to the Wu server.<br />
<br />
Reading the sensor data is easiest done in python. I don't know python, but it's easily done with a few lines of code. The following reads the sensor data, displays it on the led screen of the Sensor Hat, and makes it available to the Radpi server (see <a href="http://pi.raguenaud.fr/2016/06/radpi-temperature.html">Radpi setup</a> to build the pi for that purpose):<br />
<br />
<i>from sense_hat import SenseHat</i><br />
<i>sense = SenseHat()</i><br />
<i><br /></i>
<i># Read the core's temperature</i><br />
<i>tFile = open('/sys/class/thermal/thermal_zone0/temp')</i><br />
<i>temp = float(tFile.read())</i><br />
<i>tempC = temp/1000</i><br />
<i></i><br />
<i>print "tempC=" + str(tempC)</i><br />
<div>
<br /></div>
<div>
<i># Read the Sense Hat's sensors</i></div>
<i>t1 = sense.get_temperature()</i><br />
<i>t2 = sense.get_temperature_from_pressure();</i><br />
<i>t3 = sense.get_temperature_from_humidity()</i><br />
<i>p = sense.get_pressure()</i><br />
<i>h = sense.get_humidity()</i><br />
<i><br /></i>
<i>print "t1="+str(t1)</i><br />
<i>print "t2="+str(t2)</i><br />
<i>print "t3="+str(t3)</i><br />
<i>print "p="+str(p)</i><br />
<i></i><br />
<i>print "h="+str(h)</i><br />
<div>
<i><br /></i></div>
<div>
<i># Do some adjustments</i></div>
<div>
<div>
<i>avgtemp = (t1+t2+t3)/3</i></div>
<div>
<i>calctemp = avgtemp - ((tempC-avgtemp)/10)</i></div>
</div>
<div>
<i><br /></i></div>
<div>
<div>
<i>calchum = h + 4</i></div>
<div>
<i>calcp = p + 6</i></div>
<div>
<i><br /></i></div>
<div>
<i>print "calctemp=" + str(calctemp)</i></div>
<div>
<i>print "calchum=" + str(calchum)</i></div>
<div>
<i>print "calcp=" + str(calcp)</i></div>
</div>
<div>
<i><br /></i></div>
<div>
<i># Save the data for the Radpi server</i></div>
<div>
<div>
<i>f = open('/var/www/html/temp.html', 'w')</i></div>
<div>
<i>f.write(str(calctemp)+'!'+str(calchum)+'!'+str(calcp))</i></div>
<div>
<i>f.close()</i></div>
<div>
<i><br /></i></div>
<div>
<i># Display on the Sense Hat's LCD because we can</i></div>
<div>
<i>msg = "Temperature = %s, Pressure=%s, Humidity=%s" % (calctemp,calcp,calchum)</i></div>
<div>
<i><br /></i></div>
<div>
<i>sense.show_message(msg, scroll_speed=0.05)</i></div>
</div>
<div>
<br /></div>
Note that the Sense Hat design has a major flaw: some of the sensors are very close to the pi's CPU and therefore are <a href="https://github.com/astro-pi/watchdog/blob/master/watchdog.py#L2399" target="_blank">affected</a> by it. It is mainly noticeable for the temperature, but humidity should also be affected. This is why the script contains a few adjustments based on values provided by another station nearby. As the effect of the CPU heat on the temperature sensor will be proportional to how busy/hot the pi is, we take all temperature sensors' readings, and substract a proportion of the difference with the pi's CPU temperature.<br />
<i><br /></i>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ2_b9CbMtKDsizJR2bk9YhvxkD3PH8yBrCJHvn4oOuHV3zivb7qOYQyKL1ESEoTNst-4XmfzPUJ5I6K2wkLAOux6aFDd9oaFX5ioEoQfQNJhGSvsqG2_uyyLipA3iW_wghfrW5C5wbWZn/s1600/metpi-on.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ2_b9CbMtKDsizJR2bk9YhvxkD3PH8yBrCJHvn4oOuHV3zivb7qOYQyKL1ESEoTNst-4XmfzPUJ5I6K2wkLAOux6aFDd9oaFX5ioEoQfQNJhGSvsqG2_uyyLipA3iW_wghfrW5C5wbWZn/s320/metpi-on.jpg" width="240" /></a></div>
<i><br /></i>
<i><br /></i>
<br />
<div>
The script is scheduled to run every minute in crontab. I simply added another target to the Radpi central server to collect that data and store it in a table with temperature, humidity, and pressure fields. I also added the graph on the home temperature page.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcv-6nWgl9oA5Sa62DSHFK9jrb7ZDKDD_lEtgQU4FNgR-1VxvT1OINI7W7E6GbZ5ZS4kq3r2vL-EwvRnkpr_xyADRuixc-Sf4idz0mlPnw41YZ1y2XW-HbCn-9X_fP3DAemHshmIJ0uhx5/s1600/home+graphs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcv-6nWgl9oA5Sa62DSHFK9jrb7ZDKDD_lEtgQU4FNgR-1VxvT1OINI7W7E6GbZ5ZS4kq3r2vL-EwvRnkpr_xyADRuixc-Sf4idz0mlPnw41YZ1y2XW-HbCn-9X_fP3DAemHshmIJ0uhx5/s320/home+graphs.png" width="288" /></a></div>
<div>
<br /></div>
<div>
The other script, in PHP this time since everything else is, reads the file created by the python script, and sends the information to the Wu server:</div>
<br />
<i><?php</i><br />
<i><br /></i>
<i>$data = file_get_contents("/var/www/html/temp.html");</i><br />
<i>$a = explode("!", $data);</i><br />
<i>$dew = $a[0] - ((100 - $a[1])/5);</i><br />
<i><br /></i>
<i>$response = file_get_contents("http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?action=updateraw&ID=YOURIDHERE&PASSWORD=YOURPASSWORD&dateutc=".urlencode(gmdate("Y-M-d H:i:s"))."&humidity=".$a[1]."&tempf=".($a[0]*1.8+32)."&baromin=".($a[2]/1000*29.5)."&dewptf=".($dew*1.8+32));</i><br />
<i>print($response);</i><br />
<i><br /></i>
<i>?></i><br />
<div>
<br /></div>
<div>
The dew point is approximated* but that should be sufficiently accurate. All units are converted to imperial on the fly as that's what Wu requires. The script is scheduled to run every 5 minutes in crontab.</div>
<div>
<br /></div>
<div>
The data generated and uploaded by the station is now <a href="https://www.wunderground.com/personal-weather-station/dashboard?ID=IAQUITAI178" target="_blank">available on Wu</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8z778jOinBEdIP_gARS9_mYYFuQtFl3eK3Ljn6Js6dKaTqlLH6szdeaNS8Dxchtn_ekZtJV8gteLMIs0TliDbX9rtNNIjlbfEEbeHhICwAoAzUCWB5Rh-J8njYiT9Bzxz-Fra1h7kAMMJ/s1600/wu+IAQUIATA178.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="169" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8z778jOinBEdIP_gARS9_mYYFuQtFl3eK3Ljn6Js6dKaTqlLH6szdeaNS8Dxchtn_ekZtJV8gteLMIs0TliDbX9rtNNIjlbfEEbeHhICwAoAzUCWB5Rh-J8njYiT9Bzxz-Fra1h7kAMMJ/s320/wu+IAQUIATA178.jpg" width="320" /></a></div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<span style="font-family: inherit;">* Simple dew point approximation: <i><span style="line-height: 19.2px;">Lawrence, Mark G., 2005: The relationship between relative humidity and the dewpoint temperature in moist air: A simple conversion and applications. </span><span style="line-height: 19.2px;">Bull. Amer. Meteor. Soc.</span><span style="line-height: 19.2px;">, </span><span style="line-height: 19.2px;">86</span><span style="line-height: 19.2px;">, 225-233. doi: http;//dx.doi.org/10.1175/BAMS-86-2-225</span></i></span><br />
<span style="font-family: inherit;"><span style="line-height: 19.2px;"><br /></span></span>
<span style="font-family: inherit;"><span style="line-height: 19.2px;">Next step: <a href="http://pi.raguenaud.fr/2016/07/metpi-4-tweaks.html">tweaks</a></span></span>
<span style="font-family: inherit;"><i><span style="line-height: 19.2px;"><br /></span></i></span>
<br />
<span style="font-family: inherit;"><i><span style="line-height: 19.2px;"><br /></span></i></span>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlQzZDFIBCOKWVfSEIgBcqEqW0pVIHXeAK_EPiPWt2b3suABfJkX5e0bMR9y5rCTaIQPeICyZGesWat4bEPgUvRgUr5fysAMY7CP3frTuKpt7Bh_y1oaImL59eZu-08dnygPKU3l5M8XLX/s1600/pi+plus+weather.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="101" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlQzZDFIBCOKWVfSEIgBcqEqW0pVIHXeAK_EPiPWt2b3suABfJkX5e0bMR9y5rCTaIQPeICyZGesWat4bEPgUvRgUr5fysAMY7CP3frTuKpt7Bh_y1oaImL59eZu-08dnygPKU3l5M8XLX/s320/pi+plus+weather.png" width="320" /></a></div>
<span style="font-family: inherit;"><i><span style="line-height: 19.2px;"><br /></span></i></span>Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-68310621083364990102016-06-29T12:25:00.001+02:002016-07-06T11:56:29.150+02:00Metpi #1: genesysI've been using <a href="https://www.wunderground.com/" target="_blank">Wu</a> (Weather Underground) for a while to get local weather readings (temperature, humidity, wind, etc). With all the Raspberry pis in the house, I thought I'd use one to measure weather data at home.<br />
<a name='more'></a><br />
<div>
I was tempted at first to get a commercially available weasther station that publishes its data, such as netatmo. But where's the fun in that? So I looked into sensors. I already have a bunch of DHT22s and DS18B20s. The DHT22 would get me temperature and humidity. The DS18B20 only temperature. But I also wanted at least atmospheric pressure. So I went for the Sense Hat (aka Astro pi) which provides a number of sensors including temperature, humidity, and atmospheric pressure.</div>
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgT2jDPho4myuWc4r1kz8zXhAbvACTF40gu5vq0Da8CU_KbsRlEG76ty5ETzDl7WDNAC9O_nJsLqgnKoMTJdFTpbpiw5vg2VOKraQW8yNUiQ-Hq11m8BNtcsZE7XBJ_jRzfDcTXdfVePgs/s1600/metpi.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgT2jDPho4myuWc4r1kz8zXhAbvACTF40gu5vq0Da8CU_KbsRlEG76ty5ETzDl7WDNAC9O_nJsLqgnKoMTJdFTpbpiw5vg2VOKraQW8yNUiQ-Hq11m8BNtcsZE7XBJ_jRzfDcTXdfVePgs/s320/metpi.jpg" width="240" /></a></div>
<br />
I originally wanted to have both the Sense Hat and a DHT22 as reference, but it's not easily done since the Sense Hat hijacks all the GIPO pins. For now I'll rey only on the Sense Hat and see how reliable it is.<br />
<br />
<br /></div>
<div>
Next step: <a href="http://pi.raguenaud.fr/2016/07/metpi-2-hardware.html">hardware</a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlQzZDFIBCOKWVfSEIgBcqEqW0pVIHXeAK_EPiPWt2b3suABfJkX5e0bMR9y5rCTaIQPeICyZGesWat4bEPgUvRgUr5fysAMY7CP3frTuKpt7Bh_y1oaImL59eZu-08dnygPKU3l5M8XLX/s1600/pi+plus+weather.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="101" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlQzZDFIBCOKWVfSEIgBcqEqW0pVIHXeAK_EPiPWt2b3suABfJkX5e0bMR9y5rCTaIQPeICyZGesWat4bEPgUvRgUr5fysAMY7CP3frTuKpt7Bh_y1oaImL59eZu-08dnygPKU3l5M8XLX/s320/pi+plus+weather.png" width="320" /></a></div>
<div>
<br /></div>
Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-39412239908114826872016-06-16T13:36:00.000+02:002016-06-16T13:40:18.772+02:00Radpi #4: boiler controlWith the <a href="http://pi.raguenaud.fr/2016/06/radpi-boiler.html">ability to control the boiler remotely</a> and the <a href="http://pi.raguenaud.fr/2016/06/radpi-temperature.html">temperature sensor data stored in a database</a>, I could start adjusting the temperature in the house.<br />
<a name='more'></a>Back to the boiler, we need to control when to start/stop it. I've decided to make that decision based on the temperature of 2 rooms (but more would be easy to use, the other rooms I monitor being separate from the house they weren't relevant). Periodically, a script runs that load the last minute of data from the pis of interest, and based of their values and a program, decides to start/stop the boiler.<br />
<br />
I also wanted the controller pi to tell me what's happening. So it will send emails when it starts/stops the boiler and when it can't do anything for some reason (e.g. no recent temperature readings, or the script is disabled).<br />
<br />
First, the boiler schedule. I created a file with time ranges and temperature targets. My strategy was that the livingroom is the main room, and the bedroom upstairs would act as a moderator (you don't want the temperature to be too high there). The configuration of targets is as follows:<br />
<br />
<i>$ranges = Array(</i><br />
<i> Array("start" => "00:00", "end" => "06:00", "target" => 18.5, "bedroommax" => 21.5, "bedroommin" => 19),</i><br />
<i> Array("start" => "06:00", "end" => "07:30", "target" => 21, "bedroommax" => 21.5, "bedroommin" => 19),</i><br />
<i> Array("start" => "07:30", "end" => "16:00", "target" => 20.5, "bedroommax" => 21.5, "bedroommin" => 19),</i><br />
<i> Array("start" => "16:00", "end" => "17:30", "target" => 21, "bedroommax" => 21.5, "bedroommin" => 19),</i><br />
<i> Array("start" => "17:30", "end" => "23:59:59", "target" => 18.5, "bedroommax" => 21.5, "bedroommin" => 19)</i><br />
<i> );</i><br />
<i><br /></i>
<i>$maxdeviation = 0.5;</i><br />
<i>$adjust = -0.0;</i><br />
<br />
The target temperature applies to the main room (the livingroom) and the bedroom as a fuse that can't be exceeded. The $maxdeviation value refers to the leeway allowed around the target temperature in the livingroom (we don't want to start/stop every minute, so we allow things to vary a bit). $adjust can be used in case the temperature sensor (they're not all very accurate) or its location (e.g. in a corner) distort the temperature and it needs to be adjusted to reflect reality better.<br />
<br />
Now, here are the boiler control script. You'll recognise the wemo control code and the DB access.<br />
<i><?php</i><br />
<i><br /></i>
<i>include_once("config.inc");</i><br />
<i>include_once('SMTPconfig.php');</i><br />
<i>include_once('SMTPClass.php');</i><br />
<i><br /></i>
<i>$portindex = 0;</i><br />
<i><br /></i>
<i>// Find the target range that applies</i><br />
<i>function findRange() {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>global $ranges;</i><br />
<i><br /></i>
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>foreach($ranges as $range) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>if (time() >= strtotime(date("d-M-Y ".$range["start"])) && time() <= strtotime(date("d-M-Y ".$range["end"]))) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>return $range;</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>}</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>}</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Read the livingroom temperature from the DB</i><br />
<i>function readTemperature() {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>global $dbhost, $dbuser, $dbpass, $dbdb;</i><br />
<i><br /></i>
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>$res1 = mysql_connect($dbhost, $dbuser, $dbpass); </i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>mysql_select_db($dbdb); </i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>$result = mysql_query("select ComputerTime,Temperature,Humidity from TempHumid where ComputerTime >= (unix_timestamp(now())-(60*3)) order by ComputerTime desc limit 1", $res1); </i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>if ($result) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>$row = mysql_fetch_assoc($result);</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>if ($row["Temperature"] < 40) { </i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>return $row;</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>}</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>} else {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>print("No data read\n");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>}</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>mysql_close($res1); </i><br />
<i>}</i><br />
<i><br /></i>
<i>// Read the bedroom temperature from the DB</i><br />
<i>function readBedroomTemperature() {</i><br />
<i> global $dbhost, $dbuser, $dbpass, $dbdb;</i><br />
<i><br /></i>
<i> $res1 = mysql_connect($dbhost, $dbuser, $dbpass);</i><br />
<i> mysql_select_db($dbdb);</i><br />
<i> $result = mysql_query("select ComputerTime,Temperature from Bedroom where ComputerTime >= (unix_timestamp(now())-(60*3)) order by ComputerTime desc limit 1", $res1);</i><br />
<i> if ($result) {</i><br />
<i> $row = mysql_fetch_assoc($result);</i><br />
<i> if ($row["Temperature"] < 40) {</i><br />
<i> return $row;</i><br />
<i> }</i><br />
<i> } else {</i><br />
<i> print("No data read\n");</i><br />
<i> }</i><br />
<i> mysql_close($res1);</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Turn wemo off</i><br />
<i>function wemooff() {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>global $portindex, $server, $ports;</i><br />
<i><br /></i>
<i> $headers = array(</i><br />
<i> "Accept: ",</i><br />
<i> "Content-type: text/xml;charset=\"utf-8\"",</i><br />
<i> "SOAPACTION: \"urn:Belkin:service:basicevent:1#SetBinaryState\""</i><br />
<i> );</i><br />
<i><br /></i>
<i> $post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"</i><br />
<i> s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState</i><br />
<i> xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>0</BinaryState></u:SetBinaryState></s:Body></s:Envelope>';</i><br />
<i><br /></i>
<i> $url1 = $server.":".$ports[$portindex]."/upnp/control/basicevent1";</i><br />
<i><br /></i>
<i><br /></i>
<i> $defaults = array(</i><br />
<i> CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,</i><br />
<i> CURLOPT_USERAGENT => '',</i><br />
<i> CURLOPT_POSTFIELDS => $post,</i><br />
<i> CURLOPT_HTTPHEADER => $headers,</i><br />
<i> CURLOPT_POST => 1,</i><br />
<i> CURLOPT_HEADER => 0,</i><br />
<i> CURLOPT_URL => $url1,</i><br />
<i> CURLOPT_FRESH_CONNECT => 1,</i><br />
<i> CURLOPT_RETURNTRANSFER => 1,</i><br />
<i> CURLOPT_FORBID_REUSE => 1,</i><br />
<i> CURLOPT_TIMEOUT => 4,</i><br />
<i><br /></i>
<i> );</i><br />
<i> $ch = curl_init();</i><br />
<i> curl_setopt_array($ch, $defaults);</i><br />
<i> $result = curl_exec($ch);</i><br />
<i> if( ! $result = curl_exec($ch))</i><br />
<i> {</i><br />
<i> trigger_error(curl_error($ch));</i><br />
<i> }</i><br />
<i><br /></i>
<i> curl_close($ch);</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Turn wemo on</i><br />
<i>function wemoon() {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>global $portindex, $server, $ports;</i><br />
<i><br /></i>
<i> $headers = array(</i><br />
<i> "Accept: ",</i><br />
<i> "Content-type: text/xml;charset=\"utf-8\"",</i><br />
<i> "SOAPACTION: \"urn:Belkin:service:basicevent:1#SetBinaryState\""</i><br />
<i> );</i><br />
<i><br /></i>
<i> $post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>1</BinaryState></u:SetBinaryState></s:Body></s:Envelope>';</i><br />
<i><br /></i>
<i> $url1 = $server.":".$ports[$portindex]."/upnp/control/basicevent1";</i><br />
<i><br /></i>
<i> $defaults = array(</i><br />
<i> CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,</i><br />
<i> CURLOPT_USERAGENT => '',</i><br />
<i> CURLOPT_POSTFIELDS => $post,</i><br />
<i> CURLOPT_HTTPHEADER => $headers,</i><br />
<i> CURLOPT_POST => 1,</i><br />
<i> CURLOPT_HEADER => 0,</i><br />
<i> CURLOPT_URL => $url1,</i><br />
<i> CURLOPT_FRESH_CONNECT => 1,</i><br />
<i> CURLOPT_RETURNTRANSFER => 1,</i><br />
<i> CURLOPT_FORBID_REUSE => 1,</i><br />
<i> CURLOPT_TIMEOUT => 4,</i><br />
<i><br /></i>
<i> );</i><br />
<i> $ch = curl_init();</i><br />
<i> curl_setopt_array($ch, $defaults);</i><br />
<i> $result = curl_exec($ch);</i><br />
<i> if( ! $result = curl_exec($ch))</i><br />
<i> {</i><br />
<i> trigger_error(curl_error($ch));</i><br />
<i> }</i><br />
<i><br /></i>
<i> curl_close($ch);</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Get the state of the wemo</i><br />
<i>function wemostate() {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>global $portindex, $server, $ports;</i><br />
<i><br /></i>
<i> $headers = array(</i><br />
<i> "Accept: ",</i><br />
<i> "Content-type: text/xml;charset=\"utf-8\"",</i><br />
<i> "SOAPACTION: \"urn:Belkin:service:basicevent:1#GetBinaryState\""</i><br />
<i> );</i><br />
<i><br /></i>
<i> $post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>1</BinaryState></u:GetBinaryState></s:Body></s:Envelope>';</i><br />
<i><br /></i>
<i> $powerstate = "";</i><br />
<i><br /></i>
<i> $portindex = 0;</i><br />
<i> while (trim($powerstate)=="" && $portindex<count($ports)) {</i><br />
<i><br /></i>
<i> $url1 = $server.":".$ports[$portindex]."/upnp/control/basicevent1";</i><br />
<i><br /></i>
<i><br /></i>
<i> $defaults = array(</i><br />
<i> CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,</i><br />
<i> CURLOPT_USERAGENT => '',</i><br />
<i> CURLOPT_POSTFIELDS => $post,</i><br />
<i> CURLOPT_HTTPHEADER => $headers,</i><br />
<i> CURLOPT_POST => 1,</i><br />
<i> CURLOPT_HEADER => 0,</i><br />
<i> CURLOPT_URL => $url1,</i><br />
<i> CURLOPT_FRESH_CONNECT => 1,</i><br />
<i> CURLOPT_RETURNTRANSFER => 1,</i><br />
<i> CURLOPT_FORBID_REUSE => 1,</i><br />
<i> CURLOPT_TIMEOUT => 4,</i><br />
<i> );</i><br />
<i><br /></i>
<i><br /></i>
<i> $ch = curl_init();</i><br />
<i> curl_setopt_array($ch, $defaults);</i><br />
<i><br /></i>
<i> $powerstate = curl_exec($ch);</i><br />
<i> if (trim($powerstate)=="") {</i><br />
<i> $portindex++;</i><br />
<i> }</i><br />
<i> }</i><br />
<i><br /></i>
<i>// isolate the status byte</i><br />
<i> $power = substr($powerstate, stripos($powerstate,"BinaryState>") + strlen("BinaryState>"),1);</i><br />
<i><br /></i>
<i> $status = "unknown" ;</i><br />
<i> if ( $power == "0" )</i><br />
<i> { $status = "off" ; }</i><br />
<i> else if ( $power == "1" )</i><br />
<i> { $status = "on" ; };</i><br />
<i><br /></i>
<i> if( ! $powerstate == curl_exec($ch))</i><br />
<i> {</i><br />
<i> trigger_error(curl_error($ch));</i><br />
<i> }</i><br />
<i><br /></i>
<i> curl_close($ch);</i><br />
<i> return $status;</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Adjust the temperature reading for error inaccuracy.</i><br />
<i>function adjustForError($temp) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>global $adjust;</i><br />
<i><br /></i>
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>return $temp + $adjust;</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Send notification email.</i><br />
<i>function notification($message) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>global $SmtpServer, $SmtpPort, $SmtpUser, $SmtpPass;</i><br />
<i><br /></i>
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>$to = "cedric@raguenaud.org";</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>$from = "boiler@raguenaud.org";</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>$subject = "Boiler update";</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>$body = $message;</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>$SMTPMail = new SMTPClient ($SmtpServer, $SmtpPort, $SmtpUser, $SmtpPass, $from, $to, $subject, $body);</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>$SMTPChat = $SMTPMail->SendMail();</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Let's do magic!</i><br />
<i>$range = findRange();</i><br />
<i>if (!isset($range)) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>// Couldnt' find a temperature target. This is wrong. Notify and exit.</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>notification("No applicable range found.");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>die ("No applicable range found.");</i><br />
<i>}</i><br />
<i>print("Target is ".$range["target"]."\n");</i><br />
<i><br /></i>
<i>// Get temperatures.</i><br />
<i>$temp = readTemperature();</i><br />
<i>$bedroom = readBedroomTemperature();</i><br />
<i><br /></i>
<i>// If we can't find a recent temperature, notify and exit</i><br />
<i>if (!isset($temp)) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>notificationn("No temperature found.");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>die ("No temperature found.");</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Log the data to file</i><br />
<i>print("At ".date("d-M-Y H:i:s", $temp["ComputerTime"]).", temperature was ".adjustForError($temp["Temperature"])." and humidity was ".$temp["Humidity"].". Bedroom was ".$bedroom["Temperature"].".\n");</i><br />
<i><br /></i>
<i>// What is the state of the wemo/boiler?</i><br />
<i>$status = wemostate();</i><br />
<i>print("Boiler is ".$status."\n");</i><br />
<i>if ($status == "unknown") {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>die("Can't read boiler status.\n");</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Bad temperature values were stored. Ignore and exit.</i><br />
<i>if ($temp["Temperature"]==0 || $temp["Temperature"]>40) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>notification("No decent temperature found. th it probably not running.");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>die();</i><br />
<i>}</i><br />
<i><br /></i>
<i>if (</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>(</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>adjustForError($temp["Temperature"] < ($range["target"]-$maxdeviation)</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span> || $bedroom["Temperature"] < ($range["bedroommin"]-$maxdeviation)</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>) </i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>&& $bedroom["Temperature"] < $range["bedroommax"] && $status == "off") && !file_exists("/tmp/donothing.lock")) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>// start boiler</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>print("Starting boiler.\n");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>notification("Starting boiler. Temperature was ".adjustForError($temp["Temperature"]).". Humidity was ".$temp["Humidity"].". Bedroom was ".$bedroom["Temperature"].".\n");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>wemoon();</i><br />
<i>} else</i><br />
<i>if ((</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>(adjustForError($temp["Temperature"]) >= $range["target"] && $bedroom["Temperature"] > $range["bedroommin"]) || $bedroom["Temperature"] >= $range["bedroommax"]) && $status == "on" && !file_exists("/tmp/donothing.lock")) {</i><br />
<i> // stop boiler</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>print("Stopping boiler.\n");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>notification("Stopping boiler. Temperature was ".adjustForError($temp["Temperature"]).". Humidity was ".$temp["Humidity"].". Bedroom was ".$bedroom["Temperature"].".\n");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>wemooff();</i><br />
<i>} else {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>//notification("Nothing to do. Temperature was ".adjustForError($temp["Temperature"]).". Humidity was ".$temp["Humidity"].".\n");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>print("Nothing to do.\n");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>if (file_exists("/tmp/donothing.lock")) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>print("DONOTHING is on.\n");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>if (rand(0, 9)==0) {</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>notification("DONOTHING is on.\n");</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>}</i><br />
<i><span class="Apple-tab-span" style="white-space: pre;"> </span>}</i><br />
<i>}</i><br />
<i><br /></i>
<i>?> </i><br />
<br />
By creating a file called /tmp/donothing.lock you disable the boiler. It's useful when the temperature is too variable (e,g, open windows in the winter) or when you want to run the boiler manually. In that case I receive an email on average every 10 minutes (10% chance of an email each time the script runs) to remind me of it and so that I reactivate it eventually.<br />
<div>
<br /></div>
<div>
That's all there is to it. This system has run all winter in our house and it's been warmer and has used less fuel than previous years.<br />
<br />
The code could be made more generic to map temperature targets and locations to methods to load their data, and the decision function could be changed to accomodate more location readings. That's not relevant to me at the moment so I haven't done it.</div>
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdIVQUNPFCwkjDbdzs80F6Pu_sh1TPG576nDJsEZst0-ofzLE-i7KZuPiKO-goQ73Zyq9EgJTkfnNzeJzEopK_lhNZYgC7XzCzR8PAD7rYdeILoHvsc0SJyAnpYYLTO0ZYwyr7kqab-1FD/s1600/pi+plus+radiator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="75" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdIVQUNPFCwkjDbdzs80F6Pu_sh1TPG576nDJsEZst0-ofzLE-i7KZuPiKO-goQ73Zyq9EgJTkfnNzeJzEopK_lhNZYgC7XzCzR8PAD7rYdeILoHvsc0SJyAnpYYLTO0ZYwyr7kqab-1FD/s320/pi+plus+radiator.png" width="320" /></a></div>
<br /></div>
Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-3922640685411605352016-06-16T13:24:00.000+02:002016-06-16T13:39:07.610+02:00Radpi #3: temperatureOnce I could <a href="http://pi.raguenaud.fr/2016/06/radpi-boiler.html">control the boiler</a>, I could turn my attention to reading and storing the temperature coming from the various Raspberry pis in the house.<br />
<br />
<a name='more'></a>I bought two different kinds of temperature sensors for the pis: the <a href="https://learn.adafruit.com/adafruits-raspberry-pi-lesson-11-ds18b20-temperature-sensing/ds18b20" target="_blank">DS18B20</a> and the <a href="https://www.adafruit.com/products/385" target="_blank">DHT22</a>. The DS18B20 has the advantage of being waterproof (so usable outside) and the DHT22 also provides humidity readings. I have used the DHT22 on one of the Raspbery pis, but the humidity being irrelevant to my goal and the DHT being more complicated, I used the DS18B20 on most of the pis. I'll only consider that one here.<br />
<br />
The DS18B20 sensor is really easy to use. All you need is a GPIO connector, a resistor, and a soldering iron. There are good <a href="https://leanpub.com/RPiMRE/read" target="_blank">tutorials</a> around so I'll only summarise that part here.<br />
<br />
First, link the yellow and red wires with a 10k resistor to create a pull-up circuit as shown below.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSOOoslOngpQiupXEVxku2iYfvXjpbfbawLPmWf5jBT3eRWVxbd7GZI414uhLmslBJQZxbhmmfTl9trMjjykiDMy87rTCc0sGc6iHrb4LVJhrbD_52guufLG0za-HorRnLRVGpnxcNwnh0/s1600/singleTemp-01.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSOOoslOngpQiupXEVxku2iYfvXjpbfbawLPmWf5jBT3eRWVxbd7GZI414uhLmslBJQZxbhmmfTl9trMjjykiDMy87rTCc0sGc6iHrb4LVJhrbD_52guufLG0za-HorRnLRVGpnxcNwnh0/s320/singleTemp-01.png" width="320" /></a></div>
<br />
Then solder to the GPIO connector as shown below (for the DHT22 which already containted the resistor).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfct-UF0os3wUN5ox_iDroNN7GjJOqR3uor_fAMjnfPjI0HWaO7w3LI4_jRq8QdhStg7fFJIfwa56a5wZoAA9PZMGb15WaGIfDr9C2bYzHl3E65YWKbxkk1Uds5NzL1P0azV6XrSvhQ6mv/s1600/IMG_20160310_121432.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfct-UF0os3wUN5ox_iDroNN7GjJOqR3uor_fAMjnfPjI0HWaO7w3LI4_jRq8QdhStg7fFJIfwa56a5wZoAA9PZMGb15WaGIfDr9C2bYzHl3E65YWKbxkk1Uds5NzL1P0azV6XrSvhQ6mv/s320/IMG_20160310_121432.jpg" width="320" /></a></div>
<br />
The hardware bit is now done. Plug in and switch the pi on.<br />
<br />
On recent kernels (most likely yours), you'll need to add the following line to your /boot/config.txt:<br />
<i>dtoverlay=w1-gpio</i><br />
<br />
In /etc/modules, add the following modules:<br />
<i style="color: #757575; font-family: Monaco, Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; font-size: 14px; line-height: 15.4px; white-space: pre;"><code class="bash plain" style="background-attachment: initial !important; background-clip: initial !important; background-image: none !important; background-origin: initial !important; background-position: initial !important; background-repeat: initial !important; background-size: initial !important; border-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; color: black !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace !important; font-size: 1em !important; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;">w1-therm</code></i><br />
<i style="color: #757575; font-family: Monaco, Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; font-size: 14px; line-height: 15.4px; white-space: pre;"><code class="bash plain" style="background-attachment: initial !important; background-clip: initial !important; background-image: none !important; background-origin: initial !important; background-position: initial !important; background-repeat: initial !important; background-size: initial !important; border-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; color: black !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace !important; font-size: 1em !important; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;">w1-gpio pullup=1</code></i><br />
<div class="line number10 index9 alt1" style="background: none white !important; border-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; color: #757575; direction: ltr !important; float: none !important; font-family: Monaco, Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; font-size: 14px; height: auto !important; left: auto !important; line-height: 15.4px; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px 1em !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; white-space: pre !important; width: auto !important;">
<br /></div>
Reboot. Now check that you can read the temperature from the sensor. Go to <i>/sys/bus/w1/devices</i>. In there there should be a folder whose name starts with "28-". It is the serial number of the sensor when its data is stored. In that folder there should be a file called <i>w1_slave</i> that should contain something like the following:<br />
<br />
31 01 4b 46 7f ff 0c 10 4a : crc=4a YES<br />
31 01 4b 46 7f ff 0c 10 4a t=19062<br />
<div>
<br /></div>
<div>
If that's the case, everything is good and that's where we'll read the temperature on the pi. The temperature is stored in thousands of a degree Celsius so we'll convert it in time.</div>
<div>
<br /></div>
<div>
Now, what we need is to collect the temperature from several pis (4 in my case). We have 2 alternatives: make the pis all write to a central place, or have the central place poll the pis. Writing to a central place has the advantage that data is stored only when it's available (e.g. if no temperature is read for a while you don't end up storing the last good temperature). But the downside is that you might have gaps in the data and also it could make the central place work harder because the pis won't be synchronised. So I decided to do a mix of the two: the pis collect their local data and make it available, and the central pi polls them when needed.</div>
<div>
<br /></div>
<div>
We therefore need to install two things: apache and php by running.</div>
<div>
<i>apt-get install apache2 php5</i></div>
<div>
<br /></div>
<div>
Then we need a script that will read the temperature from the sensor and make it available:</div>
<div>
<div>
<i><?php</i></div>
<div>
<br /></div>
<div>
<i>$file = file_get_contents("/sys/bus/w1/devices/<b>YOURDS18B20SERIALHERE</b>/w1_slave");</i></div>
<div>
<i>$lines = explode("\n", $file);</i></div>
<div>
<i>$l2 = $lines[1];</i></div>
<div>
<i>$a = explode(" ", $l2);</i></div>
<div>
<i>$temp = str_replace("t=", "", $a[9]);</i></div>
<div>
<i>$temp = ($temp/1000)+0;</i></div>
<div>
<i><br /></i></div>
<div>
<i>file_put_contents("/var/www/html/temp.html", $temp);</i></div>
<div>
<i><br /></i></div>
<div>
<i>?></i></div>
</div>
<div>
The script will make a static file available through apache for clients to read.</div>
<div>
<br /></div>
<div>
Finally, we need to run it regularly by adding the follwing to crontab:</div>
<div>
<i>* * * * * /usr/bin/php /root/temp.php 2>&1 > /dev/null</i></div>
<div>
<br /></div>
<div>
All the pis involved in the temperature monitoring scheme need that script running so that the controlling pi can collect the temperatures.</div>
<div>
<br /></div>
<div>
On the controlling pi, we need to install mysql to store the data:</div>
<div>
<i>apt-get install mysql php5-mysql</i></div>
<div>
<br /></div>
<div>
We'll need to create a database and table to store the data. Connect to mysql (command line or phpMyAdmin) and create the database and table:</div>
<div>
<i>create database Monitoring;</i></div>
<div>
<i>use Monitoring;</i></div>
<div>
<i>create table Bedroom(ComputerTime INTEGER UNSIGNED,Temperature DECIMAL(5,1), Humidity DECIMAL(5,1));</i></div>
<div>
(note that the humidity field is only useful if you have humidity readings. I my case I have a mix so I use it)</div>
<div>
<br /></div>
<div>
I created a table like the above for each of the pis having a sensor. An alternative would have been to add a field setting where the temperature came from in a single table storing everything.</div>
<div>
<br /></div>
<div>
We then need a script that will read the remote temperatures and store them in the datbase. That's simply done with a script as follows:</div>
<div>
<br /></div>
<div>
<div>
<i><?php</i></div>
<div>
<i><br /></i></div>
<div>
<i>include_once("config.inc");</i></div>
<div>
<i><br /></i></div>
<div>
<i>function readBedroomTemperature() {</i></div>
<div>
<i> return file_get_contents("http://<b>IPOFTHEPI:PORTOFTHEPI/</b>temp.html");</i></div>
<div>
<i>}</i></div>
<div>
<i><br /></i></div>
<div>
<i>function saveBedroomTemperature($temp) {</i></div>
<div>
<i> global $dbhost, $dbuser, $dbpass, $dbdb;</i></div>
<div>
<i><br /></i></div>
<div>
<i> $res1 = mysql_connect($dbhost, $dbuser, $dbpass);</i></div>
<div>
<i> if (mysql_select_db($dbdb)) {</i></div>
<div>
<i> $result = mysql_query("insert into Bedroom (ComputerTime, Temperature) VALUES (".time().", ".$temp.")");</i></div>
<div>
<i> if (!$result) {</i></div>
<div>
<i> print(mysql_error());</i></div>
<div>
<i> }</i></div>
<div>
<i> } else {</i></div>
<div>
<i> print("Couldn't select database\n");</i></div>
<div>
<i> print(mysql_error());</i></div>
<div>
<i> }</i></div>
<div>
<i> mysql_close($res1);</i></div>
<div>
<i>}</i></div>
<div>
<i><br /></i></div>
<div>
<i>$temp = readBedroomTemperature();</i></div>
<div>
<i>saveBedroomTemperature($temp);</i></div>
<div>
<i><br /></i></div>
<div>
<i>?></i></div>
</div>
<div>
<br /></div>
<div>
The config.inc file included contains static settings such as username and password for the database server.</div>
<div>
<br /></div>
<div>
Schedule the script as often as you want in crontab:</div>
<div>
<i>* * * * * /usr/bin/php /root/Boiler/updateBedroom.php 2>&1 > /dev/null</i></div>
<div>
<br /></div>
<div>
And repeat for each of the pis you have. It's cleaner to create a single script that will poll all known pis instead of running an independent script for each pi, which is a trivial change.</div>
<div>
<br /></div>
<div>
We now have a database that contains all the temperatures for all the pis in the house. We can do two things: draw fancy graphs, and make decisions on how to run the boiler.</div>
<div>
<br /></div>
<div>
For fancy graphs I used phpGraph and fed it the raw data. The script gets the data out of the database, and creates the graphs for each pi. This isn't needed to control the heating, but it's nice to see what's happening around the house:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgprAS6aeO5iE-K5bMUMZiuW-0QRhRv6EFzrOVcKb9xin8G7rMeiYsy1Yl3sI3W2ua74S7iWP4QdlkN_-DT0OIKj3OBBtPSUkD8Q_45kAvLLfZtybp-ai-cTWf7xKS-NLiCuPFB8SxdXyyH/s1600/temp+monitoring.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgprAS6aeO5iE-K5bMUMZiuW-0QRhRv6EFzrOVcKb9xin8G7rMeiYsy1Yl3sI3W2ua74S7iWP4QdlkN_-DT0OIKj3OBBtPSUkD8Q_45kAvLLfZtybp-ai-cTWf7xKS-NLiCuPFB8SxdXyyH/s320/temp+monitoring.png" width="314" /></a></div>
<div>
I added a link to switch on/off the heating manually. I leave to your imagination what to do for that page.</div>
<div>
<br /></div>
<div>
Next step: <a href="http://pi.raguenaud.fr/2016/06/radpi-boiler-control.html">control the boiler</a> using the temperature readings</div>
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdIVQUNPFCwkjDbdzs80F6Pu_sh1TPG576nDJsEZst0-ofzLE-i7KZuPiKO-goQ73Zyq9EgJTkfnNzeJzEopK_lhNZYgC7XzCzR8PAD7rYdeILoHvsc0SJyAnpYYLTO0ZYwyr7kqab-1FD/s1600/pi+plus+radiator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="75" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdIVQUNPFCwkjDbdzs80F6Pu_sh1TPG576nDJsEZst0-ofzLE-i7KZuPiKO-goQ73Zyq9EgJTkfnNzeJzEopK_lhNZYgC7XzCzR8PAD7rYdeILoHvsc0SJyAnpYYLTO0ZYwyr7kqab-1FD/s320/pi+plus+radiator.png" width="320" /></a></div>
<br /></div>
Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-17863464427318489982016-06-16T11:32:00.000+02:002016-06-16T13:38:51.653+02:00Radpi #2: boilerThe first step of the <a href="http://pi.raguenaud.fr/2016/06/radpi-genesys.html" target="_blank">Radpi project</a> was to figure out how to control the boiler. I knew I could switch on/off the boiler with a <a href="https://www.amazon.com/WeMo-Switch-Enabled-Control-Appliances/dp/B00BB2MMNE/ref=sr_1_1?ie=UTF8&qid=1466062640&sr=8-1&keywords=wemo" target="_blank">wemo wifi socket</a> (connected to a wifi repeater due to the thickness of the walls and the distance to the main AP; one of the joys of 150 year old houses). I assumed I could figure out how to control the wemo without using my phone and then use that to control the boiler from a Raspberry pi.<br />
<br />
<a name='more'></a>I looked around for information and reverse engineering the wemo app didn't pause any problem: I reversed engineer lots of programs in my youth (in the 80s that's how you learned programming and how things work, including MS-DOS) and decompiling Java apps rates among the easiest things to do and analyse.<br />
<br />
I discovered that the communication between the phone on the local network and the wemo is done via XML over HTTP (aka SOAP) on 2 possible ports (49153 and 49154). To talk to it I just needed to send the right packet to the known IP of the wemo and voila.<br />
<br />
To switch on the wemo, you just need to send:<br />
<br />
<i><?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>1</BinaryState></u:SetBinaryState></s:Body></s:Envelope></i><br />
For action <i>Belkin:service:basicevent:1#SetBinaryState</i>.<br />
<br />
I used PHP to program the pis because it's the fastest for me when I want to have web interfaces, so to switch on the wemo I used the following code:<br />
<br />
<i>$server = "http://192.168.0.35";</i><br />
<i>$ports = Array(49153,49154);</i><br />
<div>
<i><br /></i></div>
<i>$headers = array(</i><br />
<i> "Accept: ",</i><br />
<i> "Content-type: text/xml;charset=\"utf-8\"",</i><br />
<i> "SOAPACTION: \"urn:Belkin:service:basicevent:1#SetBinaryState\""</i><br />
<i>);</i><br />
<i><br /></i>
<i>$post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState><b>1</b></BinaryState></u:SetBinaryState></s:Body></s:Envelope>';</i><br />
<i><br /></i>
<i>$url = $server.":".$ports[$_REQUEST["port"]]."/upnp/control/basicevent1";</i><br />
<i><br /></i>
<i>$defaults = array(</i><br />
<i> CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,</i><br />
<i> CURLOPT_USERAGENT => '',</i><br />
<i> CURLOPT_POSTFIELDS => $post,</i><br />
<i> CURLOPT_HTTPHEADER => $headers,</i><br />
<i> CURLOPT_POST => 1,</i><br />
<i> CURLOPT_HEADER => 0,</i><br />
<i> CURLOPT_URL => $url,</i><br />
<i> CURLOPT_FRESH_CONNECT => 1,</i><br />
<i> CURLOPT_RETURNTRANSFER => 1,</i><br />
<i> CURLOPT_FORBID_REUSE => 1,</i><br />
<i> CURLOPT_TIMEOUT => 4,</i><br />
<i>);</i><br />
<i><br /></i>
<i>$ch = curl_init();</i><br />
<i>curl_setopt_array($ch, $defaults);</i><br />
<i>$result = curl_exec($ch);</i><br />
<i>// Deal with results here</i><br />
<i><br /></i>
<i>curl_close($ch);</i><br />
<div>
<br /></div>
<div>
Dead easy heh? To switch it off, you just need to change the value set BinaryState value to 0:</div>
<div>
<br /></div>
<div>
<i>$server = "http://192.168.0.35";</i><br />
<i>$ports = Array(49153,49154);</i><br />
<div>
<i><br /></i></div>
<i>$headers = array(</i><br />
<i> "Accept: ",</i><br />
<i> "Content-type: text/xml;charset=\"utf-8\"",</i><br />
<i> "SOAPACTION: \"urn:Belkin:service:basicevent:1#SetBinaryState\""</i><br />
<i>);</i><br />
<i><br /></i>
<i>$post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState><b>0</b></BinaryState></u:SetBinaryState></s:Body></s:Envelope>';</i><br />
<i><br /></i>
<i>$url = $server.":".$ports[$_REQUEST["port"]]."/upnp/control/basicevent1";</i><br />
<i><br /></i>
<i>$defaults = array(</i><br />
<i> CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,</i><br />
<i> CURLOPT_USERAGENT => '',</i><br />
<i> CURLOPT_POSTFIELDS => $post,</i><br />
<i> CURLOPT_HTTPHEADER => $headers,</i><br />
<i> CURLOPT_POST => 1,</i><br />
<i> CURLOPT_HEADER => 0,</i><br />
<i> CURLOPT_URL => $url,</i><br />
<i> CURLOPT_FRESH_CONNECT => 1,</i><br />
<i> CURLOPT_RETURNTRANSFER => 1,</i><br />
<i> CURLOPT_FORBID_REUSE => 1,</i><br />
<i> CURLOPT_TIMEOUT => 4,</i><br />
<i>);</i><br />
<i><br /></i>
<i>$ch = curl_init();</i><br />
<i>curl_setopt_array($ch, $defaults);</i><br />
<i>$result = curl_exec($ch);</i><br />
<i>// Deal with results here</i><br />
<i><br /></i>
<i>curl_close($ch);</i><br />
<i><br /></i>
The port to use comes from the HTTP request (not sanitised, you need to make sure you don't accept any old crap from the interwebs). It is selected after running a test to see which port is currently used by the wemo (it changes regularly). To do that, you can request the BinaryState of your wemo using action <i>Belkin:service:basicevent:1#GetBinaryState</i> and change the port in case of failure:<br />
<br />
<i>$server = "http://192.168.0.35";</i><br />
<i>$ports = Array(49153,49154);</i><br />
<i><br /></i>
<i>$headers = array(</i><br />
<i> "Accept: ",</i><br />
<i> "Content-type: text/xml;charset=\"utf-8\"",</i><br />
<i> "SOAPACTION: \"urn:Belkin:service:basicevent:1#GetBinaryState\""</i><br />
<i>);</i><br />
<i><br /></i>
<i>$post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>1</BinaryState></u:GetBinaryState></s:Body></s:Envelope>';</i><br />
<i><br /></i>
<i>$powerstate = "";</i><br />
<i><br /></i>
<i>$portindex = 0;</i><br />
<i>while (trim($powerstate)=="" && $portindex<count($ports)) {</i><br />
<i><br /></i>
<i> $url1 = $server.":".$ports[$portindex]."/upnp/control/basicevent1";</i><br />
<br />
<i> $defaults = array(</i><br />
<i> CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,</i><br />
<i> </i><i> CURLOPT_USERAGENT => '',</i><br />
<i> </i><i> CURLOPT_POSTFIELDS => $post,</i><br />
<i> </i><i> CURLOPT_HTTPHEADER => $headers,</i><br />
<i> </i><i> CURLOPT_POST => 1,</i><br />
<i> </i><i> CURLOPT_HEADER => 0,</i><br />
<i> </i><i> CURLOPT_URL => $url1,</i><br />
<i> </i><i> CURLOPT_FRESH_CONNECT => 1,</i><br />
<i> </i><i> CURLOPT_RETURNTRANSFER => 1,</i><br />
<i> </i><i> CURLOPT_FORBID_REUSE => 1,</i><br />
<i> </i><i> CURLOPT_TIMEOUT => 4,</i><br />
<i> );</i><br />
<i><br /></i>
<i><br /></i>
<i> $ch = curl_init();</i><br />
<i> curl_setopt_array($ch, $defaults);</i><br />
<i><br /></i>
<i> $powerstate = curl_exec($ch);</i><br />
<i> if (trim($powerstate)=="") {</i><br />
<i> $portindex++;</i><br />
<i> }</i><br />
<i>}</i><br />
<i><br /></i>
<i>// Now we've tested the ports until we found a working one or failed on all of them. We salso know that if $portindex is outside the $port array we failed to communicate. We can take action.</i><br />
<i><br /></i>
<i></i><br />
<i>$power = substr($powerstate, stripos($powerstate,"BinaryState>") + strlen("BinaryState>"),1);</i></div>
<div>
<br /></div>
<div>
At this point, $power==0 of the wemo is off, 1 if it's on, empty if we failed to communicate with the wemo (but we already know that from the value of <i>$portindex</i>). It's up to you to take action and communicate to the user.</div>
<div>
<br /></div>
<div>
With that sorted, I could read the state, start, and stop my boiler as I wanted. Now I just needed to know when to take action.</div>
<div>
<br /></div>
<div>
Next step: <a href="http://pi.raguenaud.fr/2016/06/radpi-temperature.html">reading and storing temperatures</a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdIVQUNPFCwkjDbdzs80F6Pu_sh1TPG576nDJsEZst0-ofzLE-i7KZuPiKO-goQ73Zyq9EgJTkfnNzeJzEopK_lhNZYgC7XzCzR8PAD7rYdeILoHvsc0SJyAnpYYLTO0ZYwyr7kqab-1FD/s1600/pi+plus+radiator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="75" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdIVQUNPFCwkjDbdzs80F6Pu_sh1TPG576nDJsEZst0-ofzLE-i7KZuPiKO-goQ73Zyq9EgJTkfnNzeJzEopK_lhNZYgC7XzCzR8PAD7rYdeILoHvsc0SJyAnpYYLTO0ZYwyr7kqab-1FD/s320/pi+plus+radiator.png" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-29821335213346778132016-06-16T09:38:00.001+02:002016-06-16T13:38:33.567+02:00Radpi #1: genesysAfter the <a href="http://pi.raguenaud.fr/2016/02/carpi-genesys.html" target="_blank">carpi project</a>, the number of Raspberry pis in the house increased to the point where I nearly had one per room performing one of more tasks (TV boxes, wifi AP, network balancers to deal with the fact that we have both ADSL and satellite internet). Overtime, I wondered what I could use them for since they weren't doing much most of the time.<br />
<br />
As our heating system doesn't have a thermostat (it relies entirely on the temperature of the water coming back to the boiler from the radiator circuit; needless to say it's vague and hard to control accurately), I though that maybe if I could know the temperature in each room I could manage it better. The Radpi project was born.<br />
<br />
<a name='more'></a>I started listing the requirements of the project:<br />
- It had to control the boiler.<br />
- It had to not disturb the existing tasks of the house pis.<br />
- It had to be able to sense the temperature in a number of rooms, and adjust the temperature of the house.<br />
- It had to make "smart" decisions so that the house temperature could be different at different times of the day.<br />
- It had to be flexible enough so that I could change the rules whenever I wanted (e.g. depending on the season).<br />
<br />
The first requirement wasn't an easy one: the boiler being old, it had no sensor, and no way to control it. I could however switch it on and off with a <a href="https://www.amazon.com/WeMo-Switch-Enabled-Control-Appliances/dp/B00BB2MMNE/ref=sr_1_1?ie=UTF8&qid=1466062640&sr=8-1&keywords=wemo" target="_blank">wemo wifi socket</a> (I have 2 of those in the house, I could scavenge one). I had to use my phone of course to do so, but if my phone can do it, some reverse engineering of the app should tell me how to do it myself. That would be job #1.<br />
<br />
Sensing the temperature in various rooms isn't difficult in itself with a temperature sensor connected to the pis, but I had to centralise that information and store it somewhere. That would be job #2.<br />
<br />
Finally, the design of the command centre using the temperature information to control the boiler would be job #3.<br />
<br />
Next step: <a href="http://pi.raguenaud.fr/2016/06/radpi-boiler.html">boiler control</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9os44rF7ABEaXalMbHNyJ3mpU_uy-1WKLgeBWpcpD3VgCetkHkXgezJx2d8H3tTzbBvbNoZQPR5NhIV9WL_lLMv8XiUh9TywbejrCTc1NynrlYZbDRurpQd4KpbXa65jguaZBsrdj3Ouq/s1600/pi+plus+radiator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="75" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9os44rF7ABEaXalMbHNyJ3mpU_uy-1WKLgeBWpcpD3VgCetkHkXgezJx2d8H3tTzbBvbNoZQPR5NhIV9WL_lLMv8XiUh9TywbejrCTc1NynrlYZbDRurpQd4KpbXa65jguaZBsrdj3Ouq/s320/pi+plus+radiator.png" width="320" /></a></div>
<br />Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-74509931163052211982016-02-29T18:39:00.001+01:002016-02-29T18:39:26.649+01:00Raspberry Pi 3<p dir="ltr">The Raspberry Pi 3 is out: https://www.raspberrypi.org/blog/raspberry-pi-3-on-sale/</p>
<p dir="ltr">I've ordered mine.</p>
Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-10377225856453609712016-02-05T09:14:00.000+01:002016-06-16T13:38:20.195+02:00Carpi #3: networkFor the software aspect of the <i>Carpi</i>, I had these constraints in mind:<br />
- Control of the player should be done through phones to avoid loging remotes and removing the need to access the pi's display.<br />
- The <i>Carpi</i> needs internet access once in a while for updates and library rescan.<br />
- The phones connected to the <i>Carpi</i> will need internet access while talking to the <i>Carpi</i>.<br />
<br />
<a name='more'></a>I therefore opted for the following solution:<br />
- 1 wifi interface for the phones to connect to.<br />
- 1 wifi interface to connect to a wifi provider (mobile or house).<br />
- Once in a while I might connect via ethernet to the home nerwork.<br />
- Kodi setup to be controlled via the web/remote interface.<br />
- <i>Carpi</i> acting as a firewall/router.<br />
<br />
The external wifi interface is easily setup as standard. I called it wlan0. The internal interface needs to run DHCP and i need some routing. So let's install the software needed:<br />
<br />
Install the DHCP server and hostapd:<br />
<blockquote class="tr_bq">
sudo apt-get update<br />
sudo apt-get install hostapd isc-dhcp-server</blockquote>
<br />
Setup the DHCP daemon:<br />
<blockquote class="tr_bq">
sudo nano /etc/dhcp/dhcpd.conf</blockquote>
<br />
Make sure "authoritative;" is NOT commented out. Now create a DHCP block:<br />
<blockquote class="tr_bq">
subnet 192.168.200.0 netmask 255.255.255.0 {<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>range 192.168.200.100 192.168.200.200;<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>option broadcast-address 192.168.200.255;<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>option routers 192.168.200.1;<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>default-lease-time 600;<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>max-lease-time 7200;<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>option domain-name "local";<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>option domain-name-servers 8.8.8.8, 8.8.4.4;<br />
}</blockquote>
The <i>Carpi</i>'s internal interface will be 192.168.200.1.<br />
<br />
Then edit the DHCP server options:<br />
<blockquote class="tr_bq">
sudo nano /etc/default/isc-dhcp-server</blockquote>
<br />
Change the line "INTERFACES=" to<br />
<blockquote class="tr_bq">
INTERFACES="wlan1"</blockquote>
<br />
Now make wlan1's IP address static.<br />
<blockquote class="tr_bq">
sudo nano /etc/network/interfaces</blockquote>
<br />
Find the wlan1 section and channge it to:<br />
<blockquote class="tr_bq">
auto wlan1<br />
allow-hotplug wlan1<br />
iface wlan1 inet static<br />
address 192.168.200.1<br />
netmask 255.255.255.0<br />
network 192.168.200.0<br />
broadcast 192.168.200.255</blockquote>
<br />
At the end of the file, add:<br />
<blockquote class="tr_bq">
up iptables-restore < /etc/iptables.ipv4.nat</blockquote>
(you'll see why later)<br />
<br />
Bring down the wlan1 interface and bring it back up:<br />
<blockquote class="tr_bq">
sudo ifdown wlan1<br />
sudo ifconfig wlan1 192.168.200.1</blockquote>
<br />
Now we need to setup hostapd:<br />
<blockquote class="tr_bq">
sudo nano /etc/hostapd/hostapd.conf</blockquote>
<br />
Type in:<br />
<blockquote class="tr_bq">
ctrl_interface=/var/run/hostapd<br />
driver=rtl871xdrv<br />
ieee80211n=1<br />
ctrl_interface_group=0<br />
beacon_int=100<br />
interface=wlan1<br />
ssid=OnTheMove<br />
hw_mode=g<br />
channel=6<br />
auth_algs=1<br />
wmm_enabled=1<br />
eap_reauth_period=360000000<br />
macaddr_acl=0<br />
ignore_broadcast_ssid=0<br />
wpa=2<br />
wpa_passphrase=00000000<br />
wpa_key_mgmt=WPA-PSK<br />
wpa_pairwise=TKIP<br />
rsn_pairwise=CCMP</blockquote>
<br />
Change the SSID to your liking (for me it's OnTheMove) and the password to yours (here it's 00000000).<br />
<br />
Now setup hostapd's options:<br />
<blockquote class="tr_bq">
sudo nano /etc/default/hostapd</blockquote>
<br />
Change the DAEMON_CONF line (normally commented out) to:<br />
<blockquote class="tr_bq">
DAEMON_CONF="/etc/hostapd/hostapd.conf"</blockquote>
<br />
Now we need to setup the IP forwarding in the kernel:<br />
<blockquote class="tr_bq">
sudo nano /etc/sysctl.conf</blockquote>
<br />
Find the line with net.ipv4.ip_forward and set it to 1:<br />
<blockquote class="tr_bq">
net.ipv4.ip_forward=1</blockquote>
<br />
Also activate it right now. Run:<br />
<blockquote class="tr_bq">
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"</blockquote>
<br />
We now need a bit of network filtering magic. As I explained, I need interfaces eth0 and wlan0 to face the outside world, and wlan1 to be the internal interface where phones connect. I therefore setup routing that way and I open a few ports:<br />
<blockquote class="tr_bq">
# Clear the tables<br />
sudo iptables -F<br />
# Make eth0 and wlan0 masquerading interfaces<br />
sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE<br />
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE<br />
# Setup forward between interfaces<br />
sudo iptables -A FORWARD -i wlan0 -o wlan1 -m state --state RELATED,ESTABLISHED -j ACCEPT<br />
sudo iptables -A FORWARD -i wlan1 -o wlan0 -j ACCEPT<br />
sudo iptables -A FORWARD -i eth0 -o wlan1 -m state --state RELATED,ESTABLISHED -j ACCEPT<br />
sudo iptables -A FORWARD -i wlan1 -o eth0 -j ACCEPT<br />
# Accept all pings<br />
sudo iptables -A INPUT -p icmp -m state --state NEW --icmp-type 8 -j ACCEPT<br />
# Open a few ports<br />
sudo iptables -A INPUT -i wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT<br />
sudo iptables -A INPUT -i wlan0 -p tcp --dport ssh -j ACCEPT<br />
sudo iptables -A INPUT -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT<br />
sudo iptables -A INPUT -i eth0 -p tcp --dport ssh -j ACCEPT<br />
# On outside interfaces we drop by default<br />
sudo iptables -A INPUT -i wlan0 -j DROP<br />
sudo iptables -A INPUT -i eth0 -j DROP<br />
# Save the rules so they can be reloaded at startup<br />
sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"</blockquote>
<br />
Now, things might work for you out of the box. But with the wifi dongles I've used (TP-Link and Edimax), the default hostapd provided with Raspbian can't handle being an access point. We therefore need to update it:<br />
<blockquote class="tr_bq">
wget http://adafruit-download.s3.amazonaws.com/adafruit_hostapd_14128.zip<br />
unzip adafruit_hostapd_14128.zip<br />
sudo mv /usr/sbin/hostapd /usr/sbin/hostapd.old<br />
sudo mv hostapd /usr/sbin<br />
sudo chmod 755 /usr/sbin/hostapd</blockquote>
<br />
Now let's test it:<br />
<blockquote class="tr_bq">
sudo /usr/sbin/hostapd /etc/hostapd/hostapd.conf</blockquote>
<br />
Everything should be ok. You should see your wifi network appear from other devices (though don't try to connect to it yet, the DHCP daemon isn't running). To finalise, let's start the daemons:<br />
<blockquote class="tr_bq">
sudo systemctl start hostapd<br />
sudo systemctl start isc-dhcp-server</blockquote>
<br />
And enable the services:<br />
<blockquote class="tr_bq">
sudo systemctl enable hostapd<br />
sudo systemctl enable isc-dhcp-server </blockquote>
<br />
Now, we can setup the outside wifi interface to connect to the house wifi network or another network (e.g. 3G mobile router as I did).<br />
<br />
Back in /etc/network/interfaces, edit the wlan0 section:<br />
<blockquote class="tr_bq">
sudo nano /etc/network/interfaces</blockquote>
<br />
Change the wlan0 section for this:<br />
<blockquote class="tr_bq">
auto wlan0<br />
allow-hotplug wlan0<br />
iface wlan0 inet dhcp<br />
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf</blockquote>
<br />
Now edit the wpa options:<br />
<blockquote class="tr_bq">
sudo nano /etc/wpa_supplicant/wpa_supplicant.conf</blockquote>
<br />
Input your wifi network's options (the following lines are just an example):<br />
<blockquote class="tr_bq">
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev<br />
update_config=1<br />
network={<br />
ssid="Home"<br />
scan_ssid=1<br />
key_mgmt=NONE<br />
wep_key0=12345678901234567890123456<br />
wep_tx_keyidx=0<br />
priority=5<br />
}</blockquote>
<br />
Now the <i>Carpi</i> should connect to the home wifi when in range.<br />
<br />
The <i>Carpi</i> is ready to go network-wise.<br />
<br />
Optional:<br />
- Create a script that connects to the house's wifi to download the new files from the central sound file repository (especially useful for podcasts). Sheduled for phase 2.<br />
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhChUgIyJY0vdxjU8FfkFgaqZ5efjaMI4ZM9WyDmatzzleOrMJ8LmrZD31tCVxc5ehI1m8eP8LQKq1iHkX-V3pV9o1H1VxkOpSIsh_2p-jgykAzkC38jM_BsWc_MfCU0JYELLwLapuk30W8/s1600/pi+plus+ds3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhChUgIyJY0vdxjU8FfkFgaqZ5efjaMI4ZM9WyDmatzzleOrMJ8LmrZD31tCVxc5ehI1m8eP8LQKq1iHkX-V3pV9o1H1VxkOpSIsh_2p-jgykAzkC38jM_BsWc_MfCU0JYELLwLapuk30W8/s320/pi+plus+ds3.jpg" width="320" /></a></div>
<br /></div>
Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-83880392955676741172016-02-04T10:09:00.000+01:002016-06-16T13:38:10.722+02:00Carpi #2: hardwareOnce the <i>Carpi</i> project was on the way, I started to collect the hardware I needed.<br />
<br />
<a name='more'></a>To have a mobile raspberry pi with large storage in the car, I needed: Raspberry pi with wifi capability, power supply, hard disk, optional 3G capability.<br />
I had most of it at home except the Raspberry pi and power supply which needed to be pretty powerful to provide 2A to the pi, 1A to the disk, and power the other peripherals.<br />
<br />
The total cost of the project is as follows (including what was already in the house):<br />
- <a href="https://www.amazon.fr/gp/product/B00T2U7R7I/ref=oh_aui_detailpage_o05_s02?ie=UTF8&psc=1" rel="nofollow" target="_blank">Raspberry pi 2 B</a>: 41.59€<br />
- <a href="https://www.amazon.fr/gp/product/B008UR8UCU/ref=oh_aui_detailpage_o05_s02?ie=UTF8&psc=1" rel="nofollow" target="_blank">8GB memory card</a>: 4.99€<br />
- <a href="https://www.amazon.fr/gp/product/B00ILK6DMA/ref=oh_aui_detailpage_o05_s00?ie=UTF8&psc=1" rel="nofollow" target="_blank">Radiators</a>: 3.99€<br />
- 2 <a href="https://www.amazon.fr/gp/product/B003MTTJOY/ref=oh_aui_detailpage_o05_s01?ie=UTF8&psc=1" rel="nofollow" target="_blank">wifi USB dongles</a>: 20.96€<br />
- <a href="https://www.amazon.fr/gp/product/B0000B0DL7/ref=oh_aui_detailpage_o09_s00?ie=UTF8&psc=1" rel="nofollow" target="_blank">Powered USB hub</a>: 18.99€<br />
- <a href="https://www.amazon.fr/gp/product/B009JXJITS/ref=oh_aui_detailpage_o01_s00?ie=UTF8&psc=1" rel="nofollow" target="_blank">Power cable for USB hub</a>: 5.99€<br />
- <a href="https://www.amazon.fr/gp/product/B008PABFX8/ref=oh_aui_detailpage_o05_s02?ie=UTF8&psc=1" rel="nofollow" target="_blank">Hard disk 1 TB</a>: 55.30€<br />
- <a href="https://www.amazon.fr/gp/product/B0122P1WFW/ref=oh_aui_detailpage_o04_s00?ie=UTF8&psc=1" rel="nofollow" target="_blank">12v->5v power supply</a> 42W: 19.99€<br />
- 3G/wifi dongle: free<br />
Total = 171.80€<br />
<br />
I didn't go for the cheapest option every time, I often chose the available or fast delivered one.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVulBNy50FrrOcPd056frr18qbIlpdv-beLl6G0d_XmqDd6QvgO_Ti5v5KfiwLG1zabiX4tm17hCndBxmZLJGU5HOC7OQQcZSjrkv1HZzF8ZxgnWODGcf1UozuTtA30_5_RnQS9B5POa_J/s1600/IMG_20160203_143038-blog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVulBNy50FrrOcPd056frr18qbIlpdv-beLl6G0d_XmqDd6QvgO_Ti5v5KfiwLG1zabiX4tm17hCndBxmZLJGU5HOC7OQQcZSjrkv1HZzF8ZxgnWODGcf1UozuTtA30_5_RnQS9B5POa_J/s320/IMG_20160203_143038-blog.jpg" width="320" /></a></div>
<br />
The components connect according to the following diagram:<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/AVvXsEgex6Co3Al0pTlsCEmQGLb2MQe0Nwj-Ml7eC4Q66BcR65YsFwsnrB_QzsocaZcjCOBCRKVD0O-jOMFhoKs6ArsxtlIiCycY0R_msC6hlJxTVhWZnF2R3j20wFh_om-46zCaccGdEzxqGfhy/s1600/Carpi+diagram.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="269" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgex6Co3Al0pTlsCEmQGLb2MQe0Nwj-Ml7eC4Q66BcR65YsFwsnrB_QzsocaZcjCOBCRKVD0O-jOMFhoKs6ArsxtlIiCycY0R_msC6hlJxTVhWZnF2R3j20wFh_om-46zCaccGdEzxqGfhy/s320/Carpi+diagram.jpg" width="320" /></a></div>
<br />
<br />
Optional: to avoid having to shutdown the Carpi before cutting the power and avoid potentially corupting the memory card, a small battery system with GPIO notification on the pi would be needed. This is scheduled for phase 2.<br />
<br />
Next step: <a href="http://pi.raguenaud.fr/2016/02/carpi-network.html">networking</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW3lR7ip0JEZ0TQNyuDIbyfUrpc6N0PixNXRORT35lu975hcTGPOo9VURO2S5O9p-jEst25m01kOFkzcnSVCSZPiYOuLnQwQAWmpusvi9sosNW6LahdpyH23x-b9E1C1Iqvip0VZKozHrl/s1600/pi+plus+ds3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW3lR7ip0JEZ0TQNyuDIbyfUrpc6N0PixNXRORT35lu975hcTGPOo9VURO2S5O9p-jEst25m01kOFkzcnSVCSZPiYOuLnQwQAWmpusvi9sosNW6LahdpyH23x-b9E1C1Iqvip0VZKozHrl/s320/pi+plus+ds3.jpg" width="320" /></a></div>
<br />Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com1tag:blogger.com,1999:blog-7549926055434690001.post-28943704352429219302016-02-03T14:47:00.000+01:002016-06-16T13:38:00.876+02:00Carpi #1: genesysThe reason why I started this blog was recount for the <i>Carpi</i> project: taking the Raspberry pi to the car.<br />
<br />
<a name='more'></a>Our car stereo isn't too fancy. It doesn't have a Bluetooth input (only output) or even a USB reader capability. So we currently use a portable mp3 player that we connect to the stereo. But it has several drawbacks:<br />
- It doesn't hold that much stuff (4GB, when you listen to podcasts at the speed my wife does it isn't much).<br />
- It requires you to know what you're going to listen to in advance, which is a pain for long trips.<br />
- It needs to be updated, but then it's in the house next time you want it in the car and vice versa.<br />
- It's not great for searching or storing large amounts of files.<br />
And many more.<br />
<br />
Yes, we could upgrade the stereo, but where's the fun in that? So after having discovered the joy of pis, I though "why not get one in the car?". This is how the project <i>Carpi</i> was born.<br />
<br />
I decided on a number of constraints:<br />
- It has to be controlled from a phone to avoid losing remotes or having to install a display (I don't want to use the GPS' one).<br />
- It needs to live in the glove box or under a seat so we forget about it.<br />
- It can't require modifications to the car (my other half won't allow it).<br />
- It needs to be able to update itself if needed.<br />
- It needs to be modular, i.e. I can replace the storage whenever I want.<br />
<br />
For the hardware, I mostly wanted to use what I had at hand around the house, with a few other bits. The goal being to keep costs down. For software, the basis will be what I have experience with on the Raspberry pi: Raspbian + Kodi + whatever else I need to write to glue things together and do small jobs.<br />
<br />
Next step: <a href="http://pi.raguenaud.fr/2016/02/carpi-hardware.html">hardware hunt</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5mnRXzKRvIzyVNXNivwz-WAqM5iYQ5Kh3WNz-rr4ZSXBpMWELSOefbwJycLL76RNV9suAFGzYvrvB0oIlOvVmihHPUlHI-M8pmEUaFqfLxjrLuLifWuM6H5tFHQ97QsyCPMimKkuEsHgQ/s1600/pi+plus+ds3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5mnRXzKRvIzyVNXNivwz-WAqM5iYQ5Kh3WNz-rr4ZSXBpMWELSOefbwJycLL76RNV9suAFGzYvrvB0oIlOvVmihHPUlHI-M8pmEUaFqfLxjrLuLifWuM6H5tFHQ97QsyCPMimKkuEsHgQ/s320/pi+plus+ds3.jpg" width="320" /></a></div>
<br />Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0tag:blogger.com,1999:blog-7549926055434690001.post-58603166853236213792016-02-02T14:34:00.000+01:002016-02-02T14:34:46.758+01:00Pis aheadI came late to the Raspberry pi movement. For a long time I didn't have any use for them so didn't get any. I was more used to desktop and server machines (my office is full of them) and I didn't see the point of the small under-powered wee things.<br />
<br />
<a name='more'></a>Things changed late 2015 when one of our media players died. We had several (one for each tv) connected to the home network with massive NAS storage. The setup allowed us to watch videos, listen to music and podcasts, etc from anywhere in the house (with a little bit of automation, one of the servers was downloading new podcasts overnight for listening the next day without having to hunt for them or subscribe to an online service).<br />
<br />
One day before Christmas, our most used media player (10 year old Asus O!Player because it runs Linux and you can have fun with it) kind of died. It was working most of the time, but then would freeze for a minute or more. It was really irritating. The options were:<br />
- Swap it with a less used one in the house.<br />
- Replace it with a newer version.<br />
- Do something else.<br />
<br />
Swapping it with another one was the least interesting option. It would have solved the problem short term, but we would have still had a crippled media player in the house. Replacing it with a new one meant hunting for a good option. There are many out there, but it's boring to select the right one and these things are expensive. That left "do something else".<br />
<br />
That's when I thought of the <a href="https://www.raspberrypi.org/" target="_blank">Raspberry pi</a>. Friends of mine have been using them for a while and doing electronics projects with them. Electronics aren't my thing, I'm a software guy, but I could do something with the software: install xbmc as I recalled (now Kodi ir turns out) and nearly out of the box it would replace the media player.<br />
<br />
I therefore got one, installed OSMC (yes, the easy solution for a first attempt), connected to the remote storage over the network via wifi, and there you go, new media player in a couple of hours.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiADs6W0l-zPOY4h4RIdre1fzqn_C6HuWr1llcGaO7FCptZts0s4gQTpAZ-fEL9nuQ-KakEedVZeVs3CJHRUTYkQGlvugXpJfEQr4zOkQjyKpL1jdkwy6Vi5u6fBkpVxrEzjj8muy2ighvD/s1600/raspberrypi2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiADs6W0l-zPOY4h4RIdre1fzqn_C6HuWr1llcGaO7FCptZts0s4gQTpAZ-fEL9nuQ-KakEedVZeVs3CJHRUTYkQGlvugXpJfEQr4zOkQjyKpL1jdkwy6Vi5u6fBkpVxrEzjj8muy2ighvD/s320/raspberrypi2.jpg" width="320" /></a></div>
<br />
It worked so well, that I replaced another O!Player with a second pi. The pi family was already growing and it was only the start.Cedric Raguenaudhttp://www.blogger.com/profile/14538148038109025280noreply@blogger.com0