8 use Mojo::IOLoop::Stream;
9 #use Mojo::JSON qw(decode_json encode_json);
14 my $devname = "/dev/davis";
15 my $rain_mult = 0.2; # 0.1 or 0.2 mm or 0.01 inches
24 my $ser; # the serial port Mojo::IOLoop::Stream
27 our $json = JSON->new->canonical(1);
29 our $last_min = int(time/60)*60;
35 our $loop_count; # how many LOOPs we have done, used as start indicator
38 0x0, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
39 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
40 0x1231, 0x210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
41 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
42 0x2462, 0x3443, 0x420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
43 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
44 0x3653, 0x2672, 0x1611, 0x630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
45 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
46 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x840, 0x1861, 0x2802, 0x3823,
47 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
48 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0xa50, 0x3a33, 0x2a12,
49 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
50 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0xc60, 0x1c41,
51 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
52 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0xe70,
53 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
54 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
55 0x1080, 0xa1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
56 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
57 0x2b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
58 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
59 0x34e2, 0x24c3, 0x14a0, 0x481, 0x7466, 0x6447, 0x5424, 0x4405,
60 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
61 0x26d3, 0x36f2, 0x691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
62 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
63 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x8e1, 0x3882, 0x28a3,
64 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
65 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0xaf1, 0x1ad0, 0x2ab3, 0x3a92,
66 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
67 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0xcc1,
68 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
69 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0xed1, 0x1ef0
74 $bar_trend{-60} = "Falling Rapidly";
75 $bar_trend{196} = "Falling Rapidly";
76 $bar_trend{-20} = "Falling Slowly";
77 $bar_trend{236} = "Falling Slowly";
78 $bar_trend{0} = "Steady";
79 $bar_trend{20} = "Rising Slowly";
80 $bar_trend{60} = "Rising Rapidly";
84 $SIG{TERM} = $SIG{INT} = sub {++$ending; Mojo::IOLoop->stop;};
94 dbg "*** starting $0";
97 dbg scalar gmtime($last_min);
98 dbg scalar gmtime($last_hour);
100 my $dlog = SMGLog->new("day");
101 $did = Mojo::IOLoop->recurring(1 => sub {$dlog->flushall});
115 $d =~ s/([\%\x00-\x1f\x7f-\xff])/sprintf("%%%02X", ord($1))/eg;
116 dbg "read added '$d' buf lth=" . length $buf if isdbg 'raw';
117 if ($state eq 'waitnl' && $buf =~ /[\cJ\cM]+/) {
118 dbg "Got \\n" if isdbg 'state';
119 Mojo::IOLoop->remove($tid) if $tid;
123 $ser->write("LPS 1 1\n");
124 chgstate("waitloop");
125 } elsif ($state eq "waitloop") {
126 if ($buf =~ /\x06/) {
127 dbg "Got ACK 0x06" if isdbg 'state';
128 chgstate('waitlooprec');
131 } elsif ($state eq 'waitlooprec') {
132 if (length $buf >= 99) {
133 dbg "got loop record" if isdbg 'chan';
144 dbg "start_loop writing $nlcount \\n" if isdbg 'state';
146 Mojo::IOLoop->remove($tid) if $tid;
148 $tid = Mojo::IOLoop->recurring(0.6 => sub {
149 if (++$nlcount > 10) {
150 dbg "\\n count > 10, closing connection" if isdbg 'chan';
154 dbg "writing $nlcount \\n" if isdbg 'state';
162 dbg "state '$state' -> '$_[0]'" if isdbg 'state';
169 dbg "do reopen on '$name' ending $ending";
171 $ser = do_open($name);
175 Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
186 my $ob = Serial->new($name, 19200) || die "$name $!\n";
187 dbg "streaming $name fileno(" . fileno($ob) . ")" if isdbg 'chan';
189 my $ser = Mojo::IOLoop::Stream->new($ob);
190 $ser->on(error=>sub {dbg "serial $_[1]"; do_reopen($name) unless $ending});
191 $ser->on(close=>sub {dbg "serial closing"; do_reopen($name) unless $ending});
192 $ser->on(timeout=>sub {dbg "serial timeout";});
193 $ser->on(read=>sub {on_read(@_)});
196 Mojo::IOLoop->remove($tid) if $tid;
198 Mojo::IOLoop->remove($rid) if $rid;
200 $rid = Mojo::IOLoop->recurring(2.5 => sub {
201 start_loop() if !$state;
215 my $loo = substr $blk,0,3;
216 unless ( $loo eq 'LOO') {
217 dbg "Block invalid loo -> $loo" if isdbg 'chan'; return;
225 $tmp = unpack("s", substr $blk,7,2) / 1000;
226 $h{Pressure} = sprintf("%.0f",in2mb($tmp))+0;
228 $tmp = unpack("s", substr $blk,9,2) / 10;
229 $h{Temp_In} = sprintf("%.1f", f2c($tmp))+0;
231 $tmp = unpack("s", substr $blk,12,2) / 10;
232 $h{Temp_Out} = sprintf("%.1f", f2c($tmp))+0;
234 $tmp = unpack("C", substr $blk,14,1);
235 $h{Wind} = sprintf("%.1f",mph2mps($tmp))+0;
236 $h{Dir} = unpack("s", substr $blk,16,2)+0;
238 my $wind = {w => $h{Wind}, d => $h{Dir}};
241 $h{Humidity_Out} = unpack("C", substr $blk,33,1)+0;
242 $h{Humidity_In} = unpack("C", substr $blk,11,1)+0;
244 $tmp = unpack("C", substr $blk,43,1)+0;
245 $h{UV} = $tmp unless $tmp >= 255;
246 $tmp = unpack("s", substr $blk,44,2)+0; # watt/m**2
247 $h{Solar} = $tmp unless $tmp >= 32767;
249 # $h{Rain_Rate} = sprintf("%0.1f",unpack("s", substr $blk,41,2) * $rain_mult)+0;
250 $rain = $h{Rain_Day} = sprintf("%0.1f", unpack("s", substr $blk,50,2) * $rain_mult)+0;
251 my $delta_rain = $h{Rain} = ($rain >= $last_rain ? $rain - $last_rain : $rain) if $loop_count;
254 # what sort of packet is it?
256 my $sort = unpack("C", substr $blk,4,1);
260 $tmp = unpack("C", substr $blk,18,2);
261 # $h{Wind_Avg_10} = sprintf("%.1f",mph2mps($tmp/10))+0;
262 $tmp = unpack("C", substr $blk,20,2);
263 # $h{Wind_Avg_2} = sprintf("%.1f",mph2mps($tmp/10))+0;
264 $tmp = unpack("C", substr $blk,22,2);
265 # $h{Wind_Gust_10} = sprintf("%.1f",mph2mps($tmp/10))+0;
267 # $h{Dir_Avg_10} = unpack("C", substr $blk,24,2)+0;
268 $tmp = unpack("C", substr $blk,30,2);
269 $h{Dew_Point} = sprintf("%0.1f", f2c($tmp))+0;
274 $tmp = unpack("C", substr $blk,15,1);
275 # $h{Wind_Avg_10} = sprintf("%.1f",mph2mps($tmp))+0;
276 $h{Dew_Point} = sprintf("%0.1f", dew_point($h{Temp_Out}, $h{Humidity_Out}))+0;
277 $h{Rain_Month} = sprintf("%0.1f", unpack("s", substr $blk,52,2) * $rain_mult)+0;
278 $h{Rain_Year} = sprintf("%0.1f", unpack("s", substr $blk,54,2) * $rain_mult)+0;
282 my $crc_calc = CRC_CCITT($blk);
288 if ($ts >= $last_hour + 3600) {
289 $h{Pressure_Trend} = unpack("C", substr $blk,3,1);
290 $h{Pressure_Trend_txt} = $bar_trend{$h{Pressure_Trend}};
291 $h{Batt_TX_OK} = (unpack("C", substr $blk,86,1)+0) ^ 1;
292 $h{Batt_Console} = sprintf("%0.2f", unpack("s", substr $blk,87,2) * 0.005859375)+0;
293 $h{Forecast_Icon} = unpack("C", substr $blk,89,1);
294 $h{Forecast_Rule} = unpack("C", substr $blk,90,1);
295 $h{Sunrise} = sprintf( "%04d", unpack("S", substr $blk,91,2) );
296 $h{Sunrise} =~ s/(\d{2})(\d{2})/$1:$2/;
297 $h{Sunset} = sprintf( "%04d", unpack("S", substr $blk,93,2) );
298 $h{Sunset} =~ s/(\d{2})(\d{2})/$1:$2/;
300 if ($loop_count) { # i.e not the first
301 my $a = average(scalar @hour ? @hour : {w => $h{Wind}, d => $h{Dir}});
303 $h{Wind_1h} = sprintf("%0.1f", $a->{w})+0;
304 $h{Dir_1h} = sprintf("%0.0f", $a->{d})+0;
305 $h{Rain_1h} = $rain >= $last_rain_hour ? $rain - $last_rain_hour : $rain;
307 $h{Wind_1m} = sprintf("%0.1f", $a->{w})+0;
308 $h{Dir_1m} = sprintf("%0.0f", $a->{d})+0;
309 $h{Rain_1m} = $rain >= $last_rain_min ? $rain - $last_rain_min : $rain;
311 $last_rain_min = $last_rain_hour = $rain;
313 $j = $json->encode(\%h);
315 $s = qq|{"t":$ts,"h":$j}|;
316 $last_hour = int($ts/3600)*3600;
317 $last_min = int($ts/60)*60;
320 } elsif ($ts >= $last_min + 60) {
321 my $a = average(@min);
326 if ($loop_count) { # i.e not the first
327 $h{Wind_1m} = sprintf("%0.1f", $a->{w})+0;
328 $h{Dir_1m} = sprintf("%0.0f", $a->{d})+0;
329 $h{Rain_1h} = $rain >= $last_rain_hour ? $rain - $last_rain_hour : $rain; # this is the rate for this hour, so far
330 $h{Rain_1m} = $rain >= $last_rain_min ? $rain - $last_rain_min : $rain;
332 $last_rain_min = $rain;
334 $j = $json->encode(\%h);
336 $s = qq|{"t":$ts,"m":$j}|;
337 $last_min = int($ts/60)*60;
340 my $o = gen_hash_diff($last_reading, \%h);
342 $j = $json->encode($o);
343 $s = qq|{"t":$ts,"r":$j}|;
345 dbg "loop rec not changed" if isdbg 'chan';
348 output_str($s) if $s;
352 dbg "CRC check failed for LOOP data!";
372 while (my ($k, $v) = each %$now) {
373 if ($last->{$k} ne $now->{$k}) {
378 return $count ? \%o : undef;
386 # Using the simplified approximation for dew point
387 # Accurate to 1 degree C for humidities > 50 %
388 # http://en.wikipedia.org/wiki/Dew_point
390 my $dewpoint = $temp - ((100 - $rh) / 5);
392 # this is the more complete one (which doesn't work)
396 #my $ytrh = log(($rh/100) + ($b * $temp) / ($c + $temp));
397 #my $dewpoint = ($c * $ytrh) / ($b - $ytrh);
404 # Expects packed data...
405 my $data_str = shift @_;
408 my @lst = split //, $data_str;
409 foreach my $data (@lst) {
410 my $data = unpack("c",$data);
413 my $index = $crc >> 8 ^ $data;
414 my $lhs = $crc_table[$index];
415 #print "lhs=$lhs, crc=$crc\n";
416 my $rhs = ($crc << 8) & 0xFFFF;
427 return ($_[0] - 32) * 5/9;
432 return $_[0] * 0.44704;
437 return $_[0] * 33.8637526;
446 while (my ($k, $v) = each %$r) {
451 while (my ($k, $v) = each %out) {