{"id":51073,"date":"2012-10-05T22:28:00","date_gmt":"2012-10-05T12:28:00","guid":{"rendered":"http:\/\/riscy.biz\/?p=51073"},"modified":"2012-10-05T22:28:00","modified_gmt":"2012-10-05T12:28:00","slug":"power-monitor-update","status":"publish","type":"post","link":"https:\/\/riscy.biz\/index.php\/2012\/10\/05\/power-monitor-update\/","title":{"rendered":"Power monitor update"},"content":{"rendered":"<p>Well after setting the original system up it only lasted 4 days before the battery went flat. It appears I shouldn&#8217;t leave the radio on all the time on the remote node otherwise it chews through the battery very quickly. So I searched through the JeeNode examples to look for low power options.<\/p>\n<p>One of the issues with low power mode for the ATmega is that it doesn&#8217;t maintain time accuracy, and the JeeNode doesn&#8217;t have a RTC (I&#8217;m thinking of getting one). So the best thing I think I can do is to just turn the radio off when not used. The other option is to tone down update rate, I&#8217;ll probably look at that after I&#8217;ve collected more baseline data.<\/p>\n<p>So after working with the <a title=\"RadioBlip2\" href=\"http:\/\/jeelabs.org\/2012\/05\/29\/its-about-survival\/\">RadioBlip<\/a> code from the Jeelib examples I came up with this:<\/p>\n<blockquote><p>#include &lt;Ports.h&gt;<br \/>\n#include &lt;RF12.h&gt;<br \/>\n#include &lt;avr\/sleep.h&gt;<br \/>\n#include &#8220;kWh.h&#8221;<\/p>\n<p>#define SEND_MODE 1<\/p>\n<p>volatile bool adcDone;<\/p>\n<p>\/\/ for low-noise\/-power ADC readouts, we&#8217;ll use ADC completion interrupts<br \/>\nISR(ADC_vect) { adcDone = true; }<\/p>\n<p>\/\/ this must be defined since we&#8217;re using the watchdog for low-power waiting<br \/>\nISR(WDT_vect) { Sleepy::watchdogEvent(); }<\/p>\n<p>static byte vccRead (byte count =4) {<br \/>\nset_sleep_mode(SLEEP_MODE_ADC);<br \/>\nADMUX = bit(REFS0) | 14; \/\/ use VCC as AREF and internal bandgap as input<br \/>\nbitSet(ADCSRA, ADIE);<br \/>\nwhile (count&#8211; &gt; 0) {<br \/>\nadcDone = false;<br \/>\nwhile (!adcDone)<br \/>\nsleep_mode();<br \/>\n}<br \/>\nbitClear(ADCSRA, ADIE);<br \/>\n\/\/ convert ADC readings to fit in one byte, i.e. 20 mV steps:<br \/>\n\/\/\u00a0 1.0V = 0, 1.8V = 40, 3.3V = 115, 5.0V = 200, 6.0V = 250<br \/>\nreturn (55U * 1024U) \/ (ADC + 1) &#8211; 50;<br \/>\n}<\/p>\n<p>class Port;<br \/>\nPort inputPort(1);<br \/>\nstatic unsigned long last;<br \/>\nstatic unsigned long totalblinks;<\/p>\n<p>void setup() {<br \/>\nSerial.begin(57600);<br \/>\ninputPort.mode2(INPUT); \/\/ Set AIO mode as input<br \/>\ninputPort.digiWrite2(1); \/\/ Activate pull-up resistor for AIO<\/p>\n<p>\/\/rf12_config();<br \/>\nrf12_config(); \/\/ Apparently this is necessary<br \/>\nrf12_easyInit(1); \/\/ Send value at most every 3 seconds<br \/>\nlast = millis();<br \/>\nrf12_control(0xC040); \/\/ set low-battery level to 2.2V i.s.o. 3.1V<br \/>\nrf12_sleep(RF12_SLEEP);<br \/>\n}<\/p>\n<p>void loop () {<\/p>\n<p>static boolean ledOn = false; \/\/ Variable to indicate LED status<br \/>\nint data = inputPort.anaRead();<br \/>\nrf12_easyPoll();<br \/>\nif (!ledOn &amp;&amp; data &gt; 760) {\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\/\/ After testing I found the switching point was 750<br \/>\nledOn = true;<br \/>\n} else if (ledOn &amp;&amp; data &lt; 740) {<br \/>\nledOn = false;<br \/>\nledBlink();<br \/>\ntotalblinks++;<br \/>\n}<br \/>\n}<\/p>\n<p>void ledBlink() {<br \/>\nstatic int nBlinks = 0;<br \/>\nunsigned long time = millis();<br \/>\nunsigned long interval = time &#8211; last;<\/p>\n<p>nBlinks++;<br \/>\nif (interval &lt; 0) { \/\/ millis() overflow<br \/>\nlast = time;<br \/>\nnBlinks = 0;<br \/>\n} else if (interval &gt; 1000) { \/\/ 1+ sec passed<br \/>\n\/\/ Blinks are 1000 per kWh, or 1 Wh each<br \/>\n\/\/ One hour has 3.6M milliseconds<br \/>\nlong watts = nBlinks * 1 * 3.6E6 \/ interval;<\/p>\n<p>byte vcc = vccRead();<br \/>\nwattSend(watts,totalblinks,vcc);<\/p>\n<p>last = time;<br \/>\nnBlinks = 0;<br \/>\n}<br \/>\n}<\/p>\n<p>static void wattSend(long watts,long blinks, long vcc) {<br \/>\nPacket_t packet;<br \/>\npacket.lang = LANG_ELECTRICITY;<br \/>\npacket.mesg = MESG_ELEC_CURRENT;<br \/>\npacket.data = watts;<br \/>\npacket.blinks = blinks;<br \/>\npacket.vcc = vcc;<br \/>\nrf12_sleep(RF12_WAKEUP);<br \/>\nwhile (!rf12_canSend())<br \/>\nrf12_recvDone();<br \/>\nrf12_sendStart(0, &amp;packet, sizeof packet);<br \/>\nrf12_sendWait(SEND_MODE);<br \/>\ndelay(10);<br \/>\nrf12_sleep(RF12_SLEEP);<br \/>\n}<\/p><\/blockquote>\n<p>The code also contains parts which measures the Vcc voltage so I can determine when the battery pack is starting to get low.<\/p>\n<p>The receiver code needed to change too. I wanted to include not just power, but the number of blinks and the new Vcc measurement. Here is the new code:<\/p>\n<blockquote><p>#include &lt;Ports.h&gt;<br \/>\n#include &lt;RF12.h&gt;<br \/>\n#include &#8220;kWh.h&#8221;<\/p>\n<p>void setup() {<br \/>\nSerial.begin(57600);<br \/>\n\/\/rf12_config();<br \/>\nrf12_config();<br \/>\n}<\/p>\n<p>void loop() {<br \/>\nif (rf12_recvDone() &amp;&amp; rf12_crc == 0 &amp;&amp; rf12_len == sizeof (Packet_t)) {<br \/>\nPacket_t packet = *(Packet_t *) rf12_data;<\/p>\n<p>if (packet.lang == LANG_ELECTRICITY) {<br \/>\nif (packet.mesg == MESG_ELEC_CURRENT) {<br \/>\nwattShow(packet.data,packet.blinks,packet.vcc);<br \/>\n}<br \/>\n}<br \/>\n}<br \/>\nelse {<br \/>\ndelay(10);<br \/>\n}<br \/>\n}<\/p>\n<p>static void wattShow(long watts,long blinks,long vcc) {<br \/>\nSerial.print(watts);<br \/>\nSerial.print(&#8220;,&#8221;);<br \/>\nSerial.print(blinks);<br \/>\nSerial.print(&#8220;,&#8221;);<br \/>\nSerial.println(vcc);<br \/>\n}<\/p><\/blockquote>\n<p>I also changed the receiving Perl script to put the data into a MySQL database instead of dumping to a text file.<\/p>\n<blockquote><p>use strict;<br \/>\nuse warnings;<br \/>\nuse DBI;<br \/>\nuse LWP::Simple;<\/p>\n<p>use Win32::SerialPort qw( :STAT 0.19 );<br \/>\nuse POSIX qw\/strftime\/;<\/p>\n<p>my $port = Win32::SerialPort-&gt;new(&#8216;COM9&#8217;);<\/p>\n<p>my $time = time;<\/p>\n<p># MYSQL CONFIG VARIABLES<br \/>\nmy $host = &#8220;192.168.1.101&#8221;;<br \/>\nmy $database = &#8220;powermonitor&#8221;;<br \/>\nmy $tablename = &#8220;data&#8221;;<br \/>\nmy $user = &#8220;powermonitor&#8221;;<br \/>\nmy $pw = [the password];<\/p>\n<p>if( ! defined($port) ) {<br \/>\ndie(&#8220;Can&#8217;t open COM9: $^E\\n&#8221;);<br \/>\n}<\/p>\n<p>my $outfd;<br \/>\nopen ($outfd, &#8220;&gt;&gt;&#8221;, &#8220;log.txt&#8221;) or die &#8220;Failed to open output file &#8211; $!n&#8221;;<\/p>\n<p>my $output = select(STDOUT);<br \/>\n$|++;<br \/>\nselect($outfd);<br \/>\n$|++;<br \/>\nselect $output;<\/p>\n<p>$port-&gt;initialize();<\/p>\n<p>$port-&gt;baudrate(57600);<br \/>\n$port-&gt;parity(&#8216;none&#8217;);<br \/>\n$port-&gt;databits(8);<br \/>\n$port-&gt;stopbits(1);<br \/>\n$port-&gt;write_settings();<br \/>\n$port-&gt;are_match(&#8220;\\n&#8221;);<\/p>\n<p>while(1) {<br \/>\nmy $char = $port-&gt;lookfor();<br \/>\nif ($char) {<br \/>\n$char =~ s\/\\xd\/\/g;<br \/>\nif($char =~ m\/(\\d+),(\\d+),(\\d+)\/){<br \/>\nmy $dbh = DBI-&gt;connect(&#8216;DBI:mysql:&#8217;.$database.&#8217;;host=&#8217;.$host, $user, $pw, { RaiseError =&gt; 1});<br \/>\nmy $thedate=strftime(&#8216;%Y-%m-%d %H:%M:%S&#8217;,localtime);<br \/>\nmy $pvdate = strftime(&#8216;%Y%m%d&#8217;,localtime);<br \/>\nmy $pvtime = strftime(&#8216;%H:%M&#8217;,localtime);<br \/>\nmy $pvoutput = &#8220;get http:\/\/pvoutput.org\/service\/r2\/addoutput.jsp?key=[the api number]&amp;sid=[the sid number]&amp;d=$pvdate&amp;pt=$pvtime&amp;ip=$1&#8221;;<\/p>\n<p>#my $content = get $pvoutput;<\/p>\n<p>my $sth=$dbh-&gt;prepare(&#8220;INSERT INTO $tablename VALUES(&#8216;$thedate&#8217;,&#8217;$1&#8242;,&#8217;$2&#8242;,&#8217;$3&#8242;)&#8221;)<br \/>\nor die &#8220;Can&#8217;t prepare SQL statement: $DBI::errstr\\n&#8221;;<br \/>\n$sth-&gt;execute<br \/>\nor die &#8220;Can&#8217;t execute SQL statement: $DBI::errstr\\n&#8221;;<br \/>\n$dbh-&gt;disconnect();<br \/>\n}<br \/>\nsleep (1);<br \/>\n}<br \/>\n}<br \/>\n$port-&gt;close();<br \/>\nexit(0);<\/p><\/blockquote>\n<p>This is the database structure:<\/p>\n<blockquote><p>\n&#8212; Server version: 5.5.24<br \/>\n&#8212;<br \/>\n&#8212; Database: `powermonitor`<\/p>\n<p>&#8212; Table structure for table `data`<br \/>\n&#8212;<br \/>\nCREATE TABLE IF NOT EXISTS `data` (<br \/>\n`date` datetime NOT NULL,<br \/>\n`watts` int(11) NOT NULL,<br \/>\n`blinks` int(11) NOT NULL,<br \/>\n`vcc` int(11) NOT NULL,<br \/>\nPRIMARY KEY (`date`)<br \/>\n) ENGINE=InnoDB DEFAULT CHARSET=latin1;<\/p><\/blockquote>\n<p>Now finally I wanted to graph the data but I haven&#8217;t found a good graph program for the front-end. So I&#8217;m using PVoutput, I&#8217;m not sure how it will work but I wrote this script to dump usage data for 5 minute intervals. It calculates averages and power usage data for the last 5 minute period, I initiate it using cron.<\/p>\n<blockquote><p>use strict;<br \/>\nuse warnings;<br \/>\nuse DBI;<br \/>\nuse LWP::Simple;<br \/>\nuse LWP::UserAgent;<br \/>\nuse HTTP::Request::Common qw(POST);<\/p>\n<p>use POSIX qw\/strftime\/;<\/p>\n<p># MYSQL CONFIG VARIABLES<br \/>\nmy $host = &#8220;192.168.1.101&#8221;;<br \/>\nmy $database = &#8220;powermonitor&#8221;;<br \/>\nmy $tablename = &#8220;data&#8221;;<br \/>\nmy $user = &#8220;powermonitor&#8221;;<br \/>\nmy $pw = &#8220;powermonitor&#8221;;<\/p>\n<p>my $thetime=int(time() \/ 300) * 300;<br \/>\nmy $thetime_5=$thetime-300;<br \/>\nmy $thetimestring=strftime(&#8216;%Y-%m-%d %H:%M:%S&#8217;,localtime($thetime));<br \/>\nmy $thetime_5string=strftime(&#8216;%Y-%m-%d %H:%M:%S&#8217;,localtime($thetime_5));<\/p>\n<p>my $dbh = DBI-&gt;connect(&#8216;DBI:mysql:&#8217;.$database.&#8217;;host=&#8217;.$host, $user, $pw, { RaiseError =&gt; 1});<\/p>\n<p>my $sth=$dbh-&gt;prepare(&#8220;select DATE_FORMAT(MIN(date), &#8216;%Y-%m-%d&#8217;) as day, max(blinks)-min(blinks) as Wh, round(avg(watts)) as Watts, Date_format(Min(date), &#8216;%H:%i&#8217;) as time from data where date &gt; &#8216;$thetime_5string&#8217; AND date &lt; &#8216;$thetimestring&#8217; GROUP BY ( 12 * HOUR( date ) + FLOOR( MINUTE( date ) \/ 5 ));&#8221;)<br \/>\nor die &#8220;Can&#8217;t prepare SQL statement: $DBI::errstr\\n&#8221;;<br \/>\n$sth-&gt;execute<br \/>\nor die &#8220;Can&#8217;t execute SQL statement: $DBI::errstr\\n&#8221;;<br \/>\nmy @result = $sth-&gt;fetchrow_array();<br \/>\nmy $pvdate= substr($result[0], 0, 4).substr($result[0],5,2).substr($result[0],8,2);<br \/>\nmy $pvwatth= $result[1];<br \/>\nmy $pvwatt= $result[2];<br \/>\nmy $pvtime= $result[3];<br \/>\nprint &#8220;$pvdate $pvwatth $pvwatt $pvtime\\n&#8221;;<br \/>\n$sth-&gt;finish();<br \/>\n$dbh-&gt;disconnect();<\/p>\n<p>my $url = &#8220;http:\/\/pvoutput.org\/service\/r2\/addstatus.jsp&#8221;;<\/p>\n<p>my $ua = LWP::UserAgent-&gt;new( timeout =&gt; 120 );<br \/>\n$ua-&gt;default_header(&#8216;X-Pvoutput-Apikey&#8217;=&gt; &#8220;edaf9593007119df34f393acbd36703b7476457b&#8221;);<br \/>\n$ua-&gt;default_header(&#8216;X-Pvoutput-SystemId&#8217;=&gt; &#8220;12265&#8221;);<\/p>\n<p>my $req = POST $url, [<br \/>\nd =&gt; $pvdate,<br \/>\nt =&gt; $pvtime,<br \/>\nv3=&gt; $pvwatth,<br \/>\nv4=&gt; $pvwatt<br \/>\n];<\/p>\n<p>my $content = $ua-&gt;request($req)-&gt;as_string;<\/p>\n<p>print $content;<\/p><\/blockquote>\n<p>I&#8217;ll post a link to my PVOutput data once it looks ok.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Well after setting the original system up it only lasted 4 days before the battery went flat. It appears I shouldn&#8217;t leave the radio on all the time on the remote node otherwise it chews through the battery very quickly. &hellip; <a href=\"https:\/\/riscy.biz\/index.php\/2012\/10\/05\/power-monitor-update\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[63,1,62],"tags":[],"class_list":["post-51073","post","type-post","status-publish","format-standard","hentry","category-arduino","category-computers","category-jeenode"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/riscy.biz\/index.php\/wp-json\/wp\/v2\/posts\/51073","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/riscy.biz\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/riscy.biz\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/riscy.biz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/riscy.biz\/index.php\/wp-json\/wp\/v2\/comments?post=51073"}],"version-history":[{"count":0,"href":"https:\/\/riscy.biz\/index.php\/wp-json\/wp\/v2\/posts\/51073\/revisions"}],"wp:attachment":[{"href":"https:\/\/riscy.biz\/index.php\/wp-json\/wp\/v2\/media?parent=51073"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/riscy.biz\/index.php\/wp-json\/wp\/v2\/categories?post=51073"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/riscy.biz\/index.php\/wp-json\/wp\/v2\/tags?post=51073"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}