File Coverage

blib/lib/Device/RFXCOM/Decoder/Oregon.pm
Criterion Covered Total %
statement 192 192 100.0
branch 24 24 100.0
condition 10 10 100.0
subroutine 39 39 100.0
pod 31 31 100.0
total 296 296 100.0


line stmt bran cond sub pod time code
1 4     4   3947 use strict;
  4         9  
  4         148  
2 4     4   22 use warnings;
  4         6  
  4         188  
3             package Device::RFXCOM::Decoder::Oregon;
4             $Device::RFXCOM::Decoder::Oregon::VERSION = '1.142010';
5             # ABSTRACT: Device::RFXCOM::Decoder::Oregon decode Oregon RF messages
6              
7              
8 4     4   85 use 5.006;
  4         14  
  4         175  
9 4     4   20 use constant DEBUG => $ENV{DEVICE_RFXCOM_DECODER_OREGON_DEBUG};
  4         7  
  4         234  
10 4     4   21 use Carp qw/croak/;
  4         9  
  4         368  
11 4     4   555 use Device::RFXCOM::Decoder qw/hi_nibble lo_nibble nibble_sum/;
  4         8  
  4         323  
12             our @ISA = qw(Device::RFXCOM::Decoder);
13 4     4   684 use Device::RFXCOM::Response::Sensor;
  4         10  
  4         111  
14 4     4   8400 use Device::RFXCOM::Response::DateTime;
  4         10  
  4         15105  
15              
16             my %types =
17             (
18             type_length_key(0xfa28, 80) =>
19             {
20             part => 'THGR810', checksum => \&checksum2, method => 'common_temphydro',
21             },
22             type_length_key(0xfab8, 80) =>
23             {
24             part => 'WTGR800', checksum => \&checksum2, method => 'alt_temphydro',
25             },
26             type_length_key(0x1a99, 88) =>
27             {
28             part => 'WTGR800', checksum => \&checksum4, method => 'wtgr800_anemometer',
29             },
30             type_length_key(0x1a89, 88) =>
31             {
32             part => 'WGR800', checksum => \&checksum4, method => 'wtgr800_anemometer',
33             },
34             type_length_key(0xda78, 72) =>
35             {
36             part => 'UVN800', checksum => \&checksum7, method => 'uvn800',
37             },
38             type_length_key(0xea7c, 120) =>
39             {
40             part => 'UV138', checksum => \&checksum1, method => 'uv138',
41             },
42             type_length_key(0xea4c, 80) =>
43             {
44             part => 'THWR288A', checksum => \&checksum1, method => 'common_temp',
45             },
46             type_length_key(0xea4c, 68) =>
47             {
48             part => 'THN132N', checksum => \&checksum1, method => 'common_temp',
49             },
50             type_length_key(0x8aec, 104) => { part => 'RTGR328N', },
51             type_length_key(0x9aec, 104) =>
52             {
53             part => 'RTGR328N', checksum => \&checksum3, method => 'rtgr328n_datetime',
54             },
55             type_length_key(0x9aea, 104) =>
56             {
57             part => 'RTGR328N', checksum => \&checksum3, method => 'rtgr328n_datetime',
58             },
59             type_length_key(0x1a2d, 80) =>
60             {
61             part => 'THGR228N', checksum => \&checksum2, method => 'common_temphydro',
62             },
63             type_length_key(0x1a3d, 80) =>
64             {
65             part => 'THGR918', checksum => \&checksum2, method => 'common_temphydro',
66             },
67             type_length_key(0x5a5d, 88) =>
68             {
69             part => 'BTHR918', checksum => \&checksum5,
70             method => 'common_temphydrobaro',
71             },
72             type_length_key(0x5a6d, 96) =>
73             {
74             part => 'BTHR918N', checksum => \&checksum5, method => 'alt_temphydrobaro',
75             },
76             type_length_key(0x3a0d, 80) =>
77             {
78             part => 'WGR918', checksum => \&checksum4, method => 'wgr918_anemometer',
79             },
80             type_length_key(0x3a0d, 88) =>
81             {
82             part => 'WGR918', checksum => \&checksum4, method => 'wgr918_anemometer',
83             },
84             type_length_key(0x2a1d, 84) =>
85             {
86             part => 'RGR918', checksum => \&checksum6, method => 'common_rain',
87             },
88             type_length_key(0x0a4d, 80) =>
89             {
90             part => 'THR128', checksum => \&checksum2, method => 'common_temp',
91             },
92             #type_length_key(0x0a4d,80)=>{ part => 'THR138', method => 'common_temp', },
93              
94             type_length_key(0xca2c, 80) =>
95             {
96             part => 'THGR328N', checksum => \&checksum2, method => 'common_temphydro',
97             },
98              
99             type_length_key(0xca2c, 120) =>
100             {
101             part => 'THGR328N', checksum => \&checksum2, method => 'common_temphydro',
102             },
103              
104             # masked
105             type_length_key(0x0acc, 80) =>
106             {
107             part => 'RTGR328N', checksum => \&checksum2, method => 'common_temphydro',
108             },
109              
110             type_length_key(0x2a19, 92) =>
111             {
112             part => 'PCR800',
113             checksum => \&checksum8,
114             method => 'pcr800_rain',
115             },
116              
117             type_length_key(0xca48, 68) =>
118             {
119             part => 'THWR800', checksum => \&checksum1, method => 'common_temp',
120             },
121              
122             # for testing
123             type_length_key(0xfefe, 80) => { part => 'TEST' },
124             );
125              
126             my $DOT = q{.};
127              
128              
129             sub decode {
130 77     77 1 166 my ($self, $parent, $message, $bytes, $bits, $result) = @_;
131              
132 77 100       173 return unless (scalar @$bytes >= 2);
133              
134 76         261 my $type = ($bytes->[0] << 8) + $bytes->[1];
135 76         196 my $key = type_length_key($type, $bits);
136 76   100     560 my $rec = $types{$key} || $types{$key&0xfffff};
137 76 100       156 unless ($rec) {
138 53         231 return;
139             }
140              
141 23         22066 my @nibbles = map { hex $_ } split //, unpack "H*", $message;
  514         2139  
142             # my @nibbles = map { vec $message, $_ + ($_%2 ? -1 : 1), 4
143             # } 0..(2*length $message);
144 23         108 my $checksum = $rec->{checksum};
145 23 100 100     103 if ($checksum && !$checksum->($bytes, \@nibbles)) {
146 3         19 return;
147             }
148              
149 20         48 my $method = $rec->{method};
150 20 100       41 unless ($method) {
151 1         10 warn "Possible message from Oregon part \"", $rec->{part}, "\"\n";
152 1         9 return;
153             }
154 19         101 my $device = sprintf "%s.%02x", (lc $rec->{part}), $bytes->[3];
155 19         83 $self->$method($device, $bytes, \@nibbles, $result);
156             }
157              
158              
159             sub uv138 {
160 2     2 1 6 my ($self, $device, $bytes, $nibbles, $result) = @_;
161              
162 2         8 uv($device, $bytes, $nibbles, $result);
163 2         6 simple_battery($device, $bytes, $nibbles, $result);
164 2         11 return 1;
165             }
166              
167              
168             sub uvn800 {
169 1     1 1 4 my ($self, $device, $bytes, $nibbles, $result) = @_;
170              
171 1         6 uv2($device, $bytes, $nibbles, $result);
172 1         5 percentage_battery($device, $bytes, $nibbles, $result);
173 1         6 return 1;
174             }
175              
176              
177             sub wgr918_anemometer {
178 1     1 1 4 my ($self, $device, $bytes, $nib, $result) = @_;
179              
180 1         4 my $dir = $nib->[10]*100 + $nib->[11]*10 + $nib->[8];
181 1         5 my $speed = $nib->[15]*10 + $nib->[12] + $nib->[13]/10;
182 1         4 my $avspeed = $nib->[16]*10 + $nib->[17] + $nib->[14]/10;
183             #print "WGR918: $device $dir $speed\n";
184 1         2 push @{$result->{messages}},
  1         9  
185             Device::RFXCOM::Response::Sensor->new(device => $device,
186             measurement => 'speed',
187             value => $speed,
188             units => 'mps',
189             average => $avspeed,
190             ),
191             Device::RFXCOM::Response::Sensor->new(device => $device,
192             measurement => 'direction',
193             value => $dir,
194             units => 'degrees',
195             );
196 1         6 percentage_battery($device, $bytes, $nib, $result);
197 1         6 return 1;
198             }
199              
200              
201             sub wtgr800_anemometer {
202 1     1 1 6 my ($self, $device, $bytes, $nib, $result) = @_;
203              
204 1         4 my $dir = $nib->[8] * 22.5;
205 1         5 my $speed = $nib->[14]*10 + $nib->[12] + $nib->[13]/10;
206 1         5 my $avspeed = $nib->[16]*10 + $nib->[17] + $nib->[14]/10;
207             #print "WTGR800: $device $dir $speed\n";
208 1         4 push @{$result->{messages}},
  1         9  
209             Device::RFXCOM::Response::Sensor->new(device => $device,
210             measurement => 'speed',
211             value => $speed,
212             units => 'mps',
213             average => $avspeed,
214             ),
215             Device::RFXCOM::Response::Sensor->new(device => $device,
216             measurement => 'direction',
217             value => $dir,
218             );
219 1         5 percentage_battery($device, $bytes, $nib, $result);
220 1         7 return 1
221             }
222              
223              
224             sub alt_temphydro {
225 2     2 1 6 my ($self, $device, $bytes, $nibbles, $result) = @_;
226              
227 2         7 temperature($device, $bytes, $nibbles, $result);
228 2         7 humidity($device, $bytes, $nibbles, $result);
229 2         5 percentage_battery($device, $bytes, $nibbles, $result);
230 2         10 return 1;
231             }
232              
233              
234             sub alt_temphydrobaro {
235 1     1 1 3 my ($self, $device, $bytes, $nibbles, $result) = @_;
236              
237 1         6 temperature($device, $bytes, $nibbles, $result);
238 1         5 humidity($device, $bytes, $nibbles, $result);
239 1         4 pressure($device, $bytes, $nibbles, $result, 18, 856);
240 1         4 percentage_battery($device, $bytes, $nibbles, $result);
241 1         12 return 1;
242             }
243              
244              
245             sub rtgr328n_datetime {
246 1     1 1 4 my ($self, $device, $bytes, $nib, $result) = @_;
247              
248 1         5 my $time = $nib->[15].$nib->[12].$nib->[13].$nib->[10].$nib->[11].$nib->[8];
249 1         5 my $day =
250             [ 'Mon', 'Tues', 'Wednes',
251             'Thur', 'Fri', 'Satur', 'Sun' ]->[($bytes->[9]&0x7)-1];
252 1         7 my $date =
253             2000+($nib->[21].$nib->[18]).sprintf("%02d",$nib->[16]).
254             $nib->[17].$nib->[14];
255              
256             #print STDERR "datetime: $date $time $day\n";
257 1         2 push @{$result->{messages}},
  1         12  
258             Device::RFXCOM::Response::DateTime->new(date => $date,
259             time => $time,
260             day => $day.'day',
261             device => $device,
262             );
263 1         7 return 1;
264             }
265              
266              
267             sub common_temp {
268 2     2 1 7 my ($self, $device, $bytes, $nibbles, $result) = @_;
269              
270 2         7 temperature($device, $bytes, $nibbles, $result);
271 2         6 simple_battery($device, $bytes, $nibbles, $result);
272 2         12 return 1;
273             }
274              
275              
276             sub common_temphydro {
277 5     5 1 13 my ($self, $device, $bytes, $nibbles, $result) = @_;
278              
279 5         12 temperature($device, $bytes, $nibbles, $result);
280 5         13 humidity($device, $bytes, $nibbles, $result);
281 5         12 simple_battery($device, $bytes, $nibbles, $result);
282 5         25 return 1;
283             }
284              
285              
286             sub common_temphydrobaro {
287 1     1 1 2 my ($self, $device, $bytes, $nibbles, $result) = @_;
288              
289 1         5 temperature($device, $bytes, $nibbles, $result);
290 1         2 humidity($device, $bytes, $nibbles, $result);
291 1         3 pressure($device, $bytes, $nibbles, $result, 19);
292 1         4 simple_battery($device, $bytes, $nibbles, $result);
293 1         5 return 1;
294             }
295              
296              
297             sub common_rain {
298 1     1 1 4 my ($self, $device, $bytes, $nib, $result) = @_;
299              
300 1         3 my $rain = $nib->[10]*100 + $nib->[11]*10 + $nib->[8];
301 1         4 my $train = $nib->[17]*1000 + $nib->[14]*100 + $nib->[15]*10 + $nib->[12];
302 1         2 my $flip = $nib->[13];
303             #print STDERR "$device rain = $rain, total = $train, flip = $flip\n";
304 1         3 push @{$result->{messages}},
  1         6  
305             Device::RFXCOM::Response::Sensor->new(device => $device,
306             measurement => 'speed',
307             value => $rain,
308             units => 'mm/h',
309             ),
310             Device::RFXCOM::Response::Sensor->new(device => $device,
311             measurement => 'distance',
312             value => $train,
313             units => 'mm',
314             ),
315             Device::RFXCOM::Response::Sensor->new(device => $device,
316             measurement => 'count',
317             value => $flip,
318             units => 'flips',
319             );
320 1         4 simple_battery($device, $bytes, $nib, $result);
321 1         4 return 1;
322             }
323              
324              
325             sub pcr800_rain {
326 1     1 1 3 my ($self, $device, $bytes, $nib, $result) = @_;
327              
328 1         5 my $rain = $nib->[13]*10 + $nib->[10] + $nib->[11]/10 + $nib->[8]/100;
329 1         3 $rain *= 25.4; # convert from inch/hr to mm/hr
330              
331 1         5 my $train = $nib->[19]*100 + $nib->[16]*10 + $nib->[17]
332             + $nib->[14]/10 + $nib->[15]/100 + $nib->[12]/1000;
333 1         1 $train *= 25.4; # convert from inch/hr to mm/hr
334             #print STDERR "$device rain = $rain, total = $train\n";
335 1         2 push @{$result->{messages}},
  1         16  
336             Device::RFXCOM::Response::Sensor->new(device => $device,
337             measurement => 'speed',
338             value => (sprintf "%.2f", $rain),
339             units => 'mm/h',
340             ),
341             Device::RFXCOM::Response::Sensor->new(device => $device,
342             measurement => 'distance',
343             value => (sprintf "%.2f", $train),
344             units => 'mm',
345             );
346 1         6 simple_battery($device, $bytes, $nib, $result);
347 1         7 return 1;
348             }
349              
350              
351             sub checksum1 {
352 7     7 1 18 my $c = $_[1]->[12] + ($_[1]->[15]<<4);
353 7         26 my $s = ( ( nibble_sum(12, $_[1]) + $_[1]->[13] - 0xa) & 0xff);
354 7         34 $s == $c;
355             }
356              
357              
358             sub checksum2 {
359 12     12 1 65 $_[0]->[8] == ((nibble_sum(16,$_[1]) - 0xa) & 0xff);
360             }
361              
362              
363             sub checksum3 {
364 5     5 1 19 $_[0]->[11] == ((nibble_sum(22,$_[1]) - 0xa) & 0xff);
365             }
366              
367              
368             sub checksum4 {
369 5     5 1 20 $_[0]->[9] == ((nibble_sum(18,$_[1]) - 0xa) & 0xff);
370             }
371              
372              
373             sub checksum5 {
374 5     5 1 33 $_[0]->[10] == ((nibble_sum(20,$_[1]) - 0xa) & 0xff);
375             }
376              
377              
378             sub checksum6 {
379 4     4 1 16 $_[1]->[16]+($_[1]->[19]<<4) == ((nibble_sum(16,$_[1]) - 0xa) & 0xff);
380             }
381              
382              
383             sub checksum7 {
384 4     4 1 15 $_[0]->[7] == ((nibble_sum(14,$_[1]) - 0xa) & 0xff);
385             }
386              
387              
388             sub checksum8 {
389 4     4 1 82 my $c = $_[1]->[18] + ($_[1]->[21]<<4);
390 4         14 my $s = ( ( nibble_sum(18, $_[1]) - 0xa) & 0xff);
391 4         17 $s == $c;
392             }
393              
394              
395             sub checksum_tester {
396 3     3 1 2136 my @bytes = ( @{$_[0]}, 0, 0, 0, 0, 0, 0, 0 );
  3         12  
397 3         3 my @nibbles = ( @{$_[1]}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
  3         13  
398 3         4 my $found;
399 3         11 my @fn = (\&checksum1, \&checksum2, \&checksum3, \&checksum4,
400             \&checksum5, \&checksum6, \&checksum7, \&checksum8);
401 3         9 foreach my $i (0..$#fn) {
402 24         30 my $sum = $fn[$i];
403 24 100       46 if ($sum->(\@bytes, \@nibbles)) {
404 2         8 $found .= "Possible use of checksum, checksum".($i+1)."\n";
405             }
406             }
407              
408 3         7 for my $i (4..(scalar @bytes)-2) {
409 37         57 my $c = $nibbles[$i*2] + ($nibbles[$i*2+3]<<4);
410 37         83 my $s = ( ( nibble_sum($i*2, \@nibbles) - 0xa) & 0xff);
411 37 100       72 if ($s == $c) {
412 1         6 $found .= q{($_[1]->[}.($i*2).q{] + ($_[1]->[}.($i*2+3).
413             q{])<<4)) == ( ( nibble_sum(}.($i*2).q{, $_[1]) - 0xa) & 0xff);}."\n";
414             }
415 37 100       93 if ($bytes[$i+1] == ( ( nibble_sum(1+$i*2, \@nibbles) - 0xa) & 0xff)) {
416 1         4 $found .= q{$_[0]->[}.($i+1).q{] == ( ( nibble_sum(}.(1+$i*2).
417             q{, $_[0]) - 0xa) & 0xff)}."\n";
418             }
419 37 100       105 if ($bytes[$i+1] == ( ( nibble_sum(($i+1)*2, \@nibbles) - 0xa) & 0xff)) {
420 1         6 $found .= q{$_[0]->[}.($i+1).q{] == ( ( nibble_sum(}.(($i+1)*2).
421             q{, $_[0]) - 0xa) & 0xff);}."\n";
422             }
423             }
424 3   100     35 die $found || "Could not determine checksum\n";
425             }
426              
427             my @uv_str =
428             (
429             qw/low low low/, # 0 - 2
430             qw/medium medium medium/, # 3 - 5
431             qw/high high/, # 6 - 7
432             'very high', 'very high', 'very high', # 8 - 10
433             );
434              
435              
436             sub uv_string {
437 3 100   3 1 22 $uv_str[$_[0]] || 'dangerous';
438             }
439              
440              
441             sub uv {
442 2     2 1 4 my ($dev, $bytes, $nib, $result) = @_;
443 2         8 my $uv = $nib->[11]*10 + $nib->[8];
444 2         6 my $risk = uv_string($uv);
445             #printf STDERR "%s uv=%d risk=%s\n", $dev, $uv, $risk;
446 2         5 push @{$result->{messages}},
  2         13  
447             Device::RFXCOM::Response::Sensor->new(device => $dev,
448             measurement => 'uv',
449             value => $uv,
450             risk => $risk,
451             );
452 2         5 1;
453             }
454              
455              
456             sub uv2 {
457 1     1 1 3 my ($dev, $bytes, $nib, $result) = @_;
458 1         4 my $uv = $nib->[8];
459 1         5 my $risk = uv_string($uv);
460             #printf STDERR "%s uv=%d risk=%s\n", $dev, $uv, $risk;
461 1         4 push @{$result->{messages}},
  1         8  
462             Device::RFXCOM::Response::Sensor->new(device => $dev,
463             measurement => 'uv',
464             value => $uv,
465             risk => $risk,
466             );
467 1         4 1;
468             }
469              
470              
471             sub temperature {
472 11     11 1 22 my ($dev, $bytes, $nib, $result) = @_;
473 11         41 my $temp = $nib->[10]*10 + $nib->[11] + $nib->[8]/10;
474 11 100       31 $temp *= -1 if ($bytes->[6]&0x8);
475             #printf STDERR "%s temp=%.1f\n", $dev, $temp;
476 11         30 push @{$result->{messages}},
  11         79  
477             Device::RFXCOM::Response::Sensor->new(device => $dev,
478             measurement => 'temp',
479             value => $temp,
480             );
481 11         31 1;
482             }
483              
484              
485             sub humidity {
486 9     9 1 21 my ($dev, $bytes, $nib, $result) = @_;
487 9         18 my $hum = $nib->[15]*10 + $nib->[12];
488 9         30 my $hum_str = ['normal', 'comfortable', 'dry', 'wet']->[$bytes->[7]>>6];
489             #printf STDERR "%s hum=%d%% %s\n", $dev, $hum, $hum_str;
490 9         21 push @{$result->{messages}},
  9         43  
491             Device::RFXCOM::Response::Sensor->new(device => $dev,
492             measurement => 'humidity',
493             value => $hum,
494             string => $hum_str,
495             );
496 9         25 1;
497             }
498              
499              
500             sub pressure {
501 2     2 1 4 my ($dev, $bytes, $nib, $result, $forecast_index, $offset) = @_;
502 2 100       6 $offset = 795 unless ($offset);
503 2         3 my $hpa = $bytes->[8]+$offset;
504 2   100     19 my $forecast = { 0xc => 'sunny',
505             0x6 => 'partly',
506             0x2 => 'cloudy',
507             0x3 => 'rain',
508             }->{$nib->[$forecast_index]} || 'unknown';
509             #printf STDERR "%s baro: %d %s\n", $dev, $hpa, $forecast;
510 2         5 push @{$result->{messages}},
  2         9  
511             Device::RFXCOM::Response::Sensor->new(device => $dev,
512             measurement => 'pressure',
513             value => $hpa,
514             units => 'hPa',
515             forecast => $forecast
516             );
517 2         5 1;
518             }
519              
520              
521             sub simple_battery {
522 12     12 1 23 my ($dev, $bytes, $nib, $result) = @_;
523 12         25 my $battery_low = $bytes->[4]&0x4;
524 12 100       28 my $bat = $battery_low ? 10 : 90;
525 12         14 push @{$result->{messages}},
  12         52  
526             Device::RFXCOM::Response::Sensor->new(device => $dev,
527             measurement => 'battery',
528             value => $bat,
529             units => '%');
530 12         25 $battery_low;
531             }
532              
533              
534             sub percentage_battery {
535 6     6 1 14 my ($dev, $bytes, $nib, $result) = @_;
536 6         11 my $bat = 100-10*$nib->[9];
537 6         12 push @{$result->{messages}},
  6         27  
538             Device::RFXCOM::Response::Sensor->new(device => $dev,
539             measurement => 'battery',
540             value => $bat,
541             units => '%',
542             );
543 6         17 $bat < 20;
544             }
545              
546              
547             sub type_length_key {
548 176     176 1 746 ($_[0] << 8) + $_[1]
549             }
550              
551             1;
552              
553             __END__