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 $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);
314 $s = qq|{"t":$ts,"h":$j}|;
315 $last_hour = int($ts/3600)*3600;
316 $last_min = int($ts/60)*60;
319 } elsif ($ts >= $last_min + 60) {
320 my $a = average(@min);
325 if ($loop_count) { # i.e not the first
326 $h{Wind_1m} = sprintf("%0.1f", $a->{w})+0;
327 $h{Dir_1m} = sprintf("%0.0f", $a->{d})+0;
328 $h{Rain_1h} = $rain >= $last_rain_hour ? $rain - $last_rain_hour : $rain; # this is the rate for this hour, so far
329 $h{Rain_1m} = $rain >= $last_rain_min ? $rain - $last_rain_min : $rain;
331 $last_rain_min = $rain;
333 $j = $json->encode(\%h);
334 $s = qq|{"t":$ts,"m":$j}|;
335 $last_min = int($ts/60)*60;
338 my $o = gen_hash_diff($last_reading, \%h);
340 $j = $json->encode($o);
341 $s = qq|{"t":$ts,"r":$j}|;
343 dbg "loop rec not changed" if isdbg 'chan';
346 output_str($s) if $s;
350 dbg "CRC check failed for LOOP data!";
370 while (my ($k, $v) = each %$now) {
371 if ($last->{$k} ne $now->{$k}) {
376 return $count ? \%o : undef;
384 # Using the simplified approximation for dew point
385 # Accurate to 1 degree C for humidities > 50 %
386 # http://en.wikipedia.org/wiki/Dew_point
388 my $dewpoint = $temp - ((100 - $rh) / 5);
390 # this is the more complete one (which doesn't work)
394 #my $ytrh = log(($rh/100) + ($b * $temp) / ($c + $temp));
395 #my $dewpoint = ($c * $ytrh) / ($b - $ytrh);
402 # Expects packed data...
403 my $data_str = shift @_;
406 my @lst = split //, $data_str;
407 foreach my $data (@lst) {
408 my $data = unpack("c",$data);
411 my $index = $crc >> 8 ^ $data;
412 my $lhs = $crc_table[$index];
413 #print "lhs=$lhs, crc=$crc\n";
414 my $rhs = ($crc << 8) & 0xFFFF;
425 return ($_[0] - 32) * 5/9;
430 return $_[0] * 0.44704;
435 return $_[0] * 33.8637526;
444 while (my ($k, $v) = each %$r) {
449 while (my ($k, $v) = each %out) {