8 use Mojo::IOLoop::Stream;
9 #use Mojo::JSON qw(decode_json encode_json);
13 use Math::Round qw(nearest);
15 use constant pi => 3.14159265358979;
17 my $devname = "/dev/davis";
18 my $rain_mult = 0.2; # 0.1 or 0.2 mm or 0.01 inches
27 my $ser; # the serial port Mojo::IOLoop::Stream
30 our $json = JSON->new->canonical(1);
32 our $last_min = int(time/60)*60;
40 our $loop_count; # how many LOOPs we have done, used as start indicator
43 0x0, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
44 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
45 0x1231, 0x210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
46 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
47 0x2462, 0x3443, 0x420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
48 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
49 0x3653, 0x2672, 0x1611, 0x630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
50 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
51 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x840, 0x1861, 0x2802, 0x3823,
52 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
53 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0xa50, 0x3a33, 0x2a12,
54 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
55 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0xc60, 0x1c41,
56 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
57 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0xe70,
58 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
59 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
60 0x1080, 0xa1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
61 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
62 0x2b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
63 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
64 0x34e2, 0x24c3, 0x14a0, 0x481, 0x7466, 0x6447, 0x5424, 0x4405,
65 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
66 0x26d3, 0x36f2, 0x691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
67 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
68 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x8e1, 0x3882, 0x28a3,
69 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
70 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0xaf1, 0x1ad0, 0x2ab3, 0x3a92,
71 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
72 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0xcc1,
73 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
74 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0xed1, 0x1ef0
79 $bar_trend{-60} = "Falling Rapidly";
80 $bar_trend{196} = "Falling Rapidly";
81 $bar_trend{-20} = "Falling Slowly";
82 $bar_trend{236} = "Falling Slowly";
83 $bar_trend{0} = "Steady";
84 $bar_trend{20} = "Rising Slowly";
85 $bar_trend{60} = "Rising Rapidly";
89 $SIG{TERM} = $SIG{INT} = sub {++$ending; Mojo::IOLoop->stop;};
99 dbg "*** starting $0";
102 dbg scalar gmtime($last_min);
103 dbg scalar gmtime($last_hour);
105 my $dlog = SMGLog->new("day");
106 $did = Mojo::IOLoop->recurring(1 => sub {$dlog->flushall});
120 $d =~ s/([\%\x00-\x1f\x7f-\xff])/sprintf("%%%02X", ord($1))/eg;
121 dbg "read added '$d' buf lth=" . length $buf if isdbg 'raw';
122 if ($state eq 'waitnl' && $buf =~ /[\cJ\cM]+/) {
123 dbg "Got \\n" if isdbg 'state';
124 Mojo::IOLoop->remove($tid) if $tid;
128 $ser->write("LPS 1 1\n");
129 chgstate("waitloop");
130 } elsif ($state eq "waitloop") {
131 if ($buf =~ /\x06/) {
132 dbg "Got ACK 0x06" if isdbg 'state';
133 chgstate('waitlooprec');
136 } elsif ($state eq 'waitlooprec') {
137 if (length $buf >= 99) {
138 dbg "got loop record" if isdbg 'chan';
149 dbg "start_loop writing $nlcount \\n" if isdbg 'state';
151 Mojo::IOLoop->remove($tid) if $tid;
153 $tid = Mojo::IOLoop->recurring(0.6 => sub {
154 if (++$nlcount > 10) {
155 dbg "\\n count > 10, closing connection" if isdbg 'chan';
159 dbg "writing $nlcount \\n" if isdbg 'state';
167 dbg "state '$state' -> '$_[0]'" if isdbg 'state';
174 dbg "do reopen on '$name' ending $ending";
176 $ser = do_open($name);
180 Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
191 my $ob = Serial->new($name, 19200) || die "$name $!\n";
192 dbg "streaming $name fileno(" . fileno($ob) . ")" if isdbg 'chan';
194 my $ser = Mojo::IOLoop::Stream->new($ob);
195 $ser->on(error=>sub {dbg "serial $_[1]"; do_reopen($name) unless $ending});
196 $ser->on(close=>sub {dbg "serial closing"; do_reopen($name) unless $ending});
197 $ser->on(timeout=>sub {dbg "serial timeout";});
198 $ser->on(read=>sub {on_read(@_)});
201 Mojo::IOLoop->remove($tid) if $tid;
203 Mojo::IOLoop->remove($rid) if $rid;
205 $rid = Mojo::IOLoop->recurring(2.5 => sub {
206 start_loop() if !$state;
220 my $loo = substr $blk,0,3;
221 unless ( $loo eq 'LOO') {
222 dbg "Block invalid loo -> $loo" if isdbg 'chan'; return;
229 my $crc_calc = CRC_CCITT($blk);
234 $tmp = unpack("s", substr $blk,7,2) / 1000;
235 $h{Pressure} = nearest(1, in2mb($tmp));
237 $tmp = unpack("s", substr $blk,9,2) / 10;
238 $h{Temp_In} = nearest(0.1, f2c($tmp));
240 $tmp = unpack("s", substr $blk,12,2) / 10;
241 $h{Temp_Out} = nearest(0.1, f2c($tmp));
243 $tmp = unpack("C", substr $blk,14,1);
244 $h{Wind} = nearest(0.1, mph2mps($tmp));
245 $h{Dir} = unpack("s", substr $blk,16,2)+0;
247 my $wind = {w => $h{Wind}, d => $h{Dir}};
250 $h{Humidity_Out} = unpack("C", substr $blk,33,1)+0;
251 $h{Humidity_In} = unpack("C", substr $blk,11,1)+0;
253 $tmp = unpack("C", substr $blk,43,1)+0;
254 $h{UV} = $tmp unless $tmp >= 255;
255 $tmp = unpack("s", substr $blk,44,2)+0; # watt/m**2
256 $h{Solar} = $tmp unless $tmp >= 32767;
258 # $h{Rain_Rate} = nearest(0.1,unpack("s", substr $blk,41,2) * $rain_mult);
259 $rain = $h{Rain_Day} = nearest(0.1, unpack("s", substr $blk,50,2) * $rain_mult);
260 my $delta_rain = $h{Rain} = nearest(0.1, ($rain >= $last_rain ? $rain - $last_rain : $rain)) if $loop_count;
263 # what sort of packet is it?
264 my $sort = unpack("C", substr $blk,4,1);
268 $tmp = unpack("C", substr $blk,18,2);
269 # $h{Wind_Avg_10} = nearest(0.1,mph2mps($tmp/10));
270 $tmp = unpack("C", substr $blk,20,2);
271 # $h{Wind_Avg_2} = nearest(0.1,mph2mps($tmp/10));
272 $tmp = unpack("C", substr $blk,22,2);
273 # $h{Wind_Gust_10} = nearest(0.1,mph2mps($tmp/10));
275 # $h{Dir_Avg_10} = unpack("C", substr $blk,24,2)+0;
276 $tmp = unpack("C", substr $blk,30,2);
277 $h{Dew_Point} = nearest(0.1, f2c($tmp));
282 $tmp = unpack("C", substr $blk,15,1);
283 # $h{Wind_Avg_10} = nearest(0.1,mph2mps($tmp));
284 $h{Dew_Point} = nearest(0.1, dew_point($h{Temp_Out}, $h{Humidity_Out}));
285 $h{Rain_Month} = nearest(0.1, unpack("s", substr $blk,52,2) * $rain_mult);
286 $h{Rain_Year} = nearest(0.1, unpack("s", substr $blk,54,2) * $rain_mult);
291 if ($ts >= $last_hour + 3600) {
292 $h{Pressure_Trend} = unpack("C", substr $blk,3,1);
293 $h{Pressure_Trend_txt} = $bar_trend{$h{Pressure_Trend}};
294 $h{Batt_TX_OK} = (unpack("C", substr $blk,86,1)+0) ^ 1;
295 $h{Batt_Console} = nearest(0.01, unpack("s", substr $blk,87,2) * 0.005859375);
296 $h{Forecast_Icon} = unpack("C", substr $blk,89,1);
297 $h{Forecast_Rule} = unpack("C", substr $blk,90,1);
298 $h{Sunrise} = sprintf( "%04d", unpack("S", substr $blk,91,2) );
299 $h{Sunrise} =~ s/(\d{2})(\d{2})/$1:$2/;
300 $h{Sunset} = sprintf( "%04d", unpack("S", substr $blk,93,2) );
301 $h{Sunset} =~ s/(\d{2})(\d{2})/$1:$2/;
303 if ($loop_count) { # i.e not the first
304 my $a = wind_average(scalar @hour ? @hour : {w => $h{Wind}, d => $h{Dir}});
306 $h{Wind_1h} = nearest(0.1, $a->{w});
307 $h{Dir_1h} = nearest(0.1, $a->{d});
309 $a = wind_average(@min);
310 $h{Wind_1m} = nearest(0.1, $a->{w});
311 $h{Dir_1m} = nearest(1, $a->{d});
313 ($h{Rain_1m}, $h{Rain_1h}, $h{Rain_24h}) = calc_rain($rain);
315 $last_rain_min = $last_rain_hour = $rain;
317 $s = genstr($ts, 'h', \%h);
319 $last_hour = int($ts/3600)*3600;
320 $last_min = int($ts/60)*60;
323 } elsif ($ts >= $last_min + 60) {
324 my $a = wind_average(@min);
329 if ($loop_count) { # i.e not the first
332 $h{Wind_1m} = nearest(0.1, $a->{w});
333 $h{Dir_1m} = nearest(1, $a->{d});
334 ($h{Rain_1m}, $h{Rain_1h}, $h{Rain_24h}) = calc_rain($rain);
336 $last_rain_min = $rain;
338 $s = genstr($ts, 'm', \%h);
340 $last_min = int($ts/60)*60;
343 my $o = gen_hash_diff($last_reading, \%h);
345 $s = genstr($ts, 'r', $o);
348 dbg "loop rec not changed" if isdbg 'chan';
351 output_str($s) if $s;
355 dbg "CRC check failed for LOOP data!";
366 my $j = $json->encode($h);
367 my ($sec,$min,$hr) = (gmtime $ts)[0,1,2];
368 my $tm = sprintf "%02d:%02d:%02d", $hr, $min, $sec;
370 return qq|{"tm":"$tm","t":$ts,"$let":$j}|;
388 while (my ($k, $v) = each %$now) {
389 if ($last->{$k} ne $now->{$k}) {
394 return $count ? \%o : undef;
402 # Using the simplified approximation for dew point
403 # Accurate to 1 degree C for humidities > 50 %
404 # http://en.wikipedia.org/wiki/Dew_point
406 my $dewpoint = $temp - ((100 - $rh) / 5);
408 # this is the more complete one (which doesn't work)
412 #my $ytrh = log(($rh/100) + ($b * $temp) / ($c + $temp));
413 #my $dewpoint = ($c * $ytrh) / ($b - $ytrh);
420 # Expects packed data...
421 my $data_str = shift @_;
424 my @lst = split //, $data_str;
425 foreach my $data (@lst) {
426 my $data = unpack("c",$data);
429 my $index = $crc >> 8 ^ $data;
430 my $lhs = $crc_table[$index];
431 #print "lhs=$lhs, crc=$crc\n";
432 my $rhs = ($crc << 8) & 0xFFFF;
443 return ($_[0] - 32) * 5/9;
448 return $_[0] * 0.44704;
453 return $_[0] * 33.8637526;
458 my ($sindir, $cosdir, $wind);
463 $sindir += sin(d2r($r->{d})) * $r->{w};
464 $cosdir += cos(d2r($r->{d})) * $r->{w};
468 my $avhdg = r2d(atan2($sindir, $cosdir));
469 $avhdg += 360 if $avhdg < 0;
470 return {w => $wind / $count, d => $avhdg};
477 return ($n / pi) * 180;
484 return ($n / 180) * pi;
491 my $Rain_1h = nearest(0.1, $rain >= $last_rain_hour ? $rain - $last_rain_hour : $rain); # this is the rate for this hour, so far
492 my $rm = $rain >= $last_rain_min ? $rain - $last_rain_min : $rain;
493 my $Rain_1m = nearest(0.1, $rm);
496 while (@rain24 > 24*60) {
497 $rain24 -= shift @rain24;
499 my $Rain_24h = nearest(0.1, $rain24);
500 return ($Rain_1m, $Rain_1h, $Rain_24h);