File Coverage

blib/lib/Device/CurrentCost/Message.pm
Criterion Covered Total %
statement 115 115 100.0
branch 47 48 97.9
condition 2 2 100.0
subroutine 30 30 100.0
pod 21 21 100.0
total 215 216 99.5


line stmt bran cond sub pod time code
1 5     5   100 use strict;
  5         11  
  5         144  
2 5     5   24 use warnings;
  5         11  
  5         288  
3             package Device::CurrentCost::Message;
4             $Device::CurrentCost::Message::VERSION = '1.232151';
5             # ABSTRACT: Perl modules for Current Cost energy monitor messages
6              
7              
8 5     5   41 use constant DEBUG => $ENV{DEVICE_CURRENT_COST_DEBUG};
  5         13  
  5         354  
9              
10 5     5   33 use Carp qw/croak carp/;
  5         10  
  5         225  
11 5     5   36 use Device::CurrentCost::Constants;
  5         22  
  5         55  
12 5     5   28 use List::Util qw/min/;
  5         11  
  5         9267  
13              
14              
15             sub new {
16 18     18 1 124 my ($pkg, %p) = @_;
17 18 100       225 croak $pkg.'->new: message parameter is required' unless (exists $p{message});
18 17         93 my $self = bless { %p }, $pkg;
19 17         63 $self;
20             }
21              
22              
23             sub device_type {
24 5     5 1 12 my $self = shift;
25 5 100       30 return $self->{device_type} if (exists $self->{device_type});
26             $self->{device_type} =
27 4 100       11 $self->message =~ m!! ? CURRENT_COST_CLASSIC : CURRENT_COST_ENVY;
28             }
29              
30              
31             sub device {
32 19     19 1 38 my $self = shift;
33 19 100       68 return $self->{device}->[0] if (exists $self->{device});
34 12         26 $self->_find_device->[0]
35             }
36              
37              
38             sub device_version {
39 19     19 1 38 my $self = shift;
40 19 100       69 return $self->{device}->[1] if (exists $self->{device});
41 3         9 $self->_find_device->[1]
42             }
43              
44             sub _find_device {
45 15     15   24 my $self = shift;
46 15         34 my $name = $self->_parse_field('name');
47             $self->{device} =
48 15 100       67 defined $name ?
49             [ $name, 'v'.$self->_parse_field('sver')] :
50             [ split /-/, $self->_parse_field('src'), 2 ];
51             }
52              
53              
54 187     187 1 2142 sub message { shift->{message} }
55              
56             sub _parse_field {
57 191     191   370 my ($self, $field, $default) = @_;
58 191 100       651 return $self->{$field} if (exists $self->{$field});
59 92 100       185 if ($self->message =~ m!<$field>(.*?)!s) {
    100          
60 58         180 my $v = $1;
61 58 100       553 $self->{$field} =
62             ($v =~ m!<([^>]+)>(.*?)!s) ? { value => $2, units => $1 } : $v;
63             } elsif (defined $default) {
64 17         102 return $default;
65             } else {
66             return
67 17         97 }
68             }
69              
70              
71 5     5 1 25 sub dsb { shift->_parse_field('dsb') }
72              
73              
74              
75 5     5 1 13 sub days_since_boot { shift->dsb }
76              
77              
78             sub time {
79 4     4 1 24 my $self = shift;
80 4         31 my $time = $self->_parse_field('time');
81 4 100       19 return $time if (defined $time);
82             $self->{time} =
83 2         6 $self->_parse_field('hr').':'.
84             $self->_parse_field('min').':'.
85             $self->_parse_field('sec')
86             }
87              
88              
89             sub time_in_seconds {
90 5     5 1 9 my $self = shift;
91 5 100       17 return $self->{time_in_seconds} if (exists $self->{time_in_seconds});
92 4         12 my ($h, $m, $s) = split /:/, $self->time, 3;
93 4         34 $self->{time_in_seconds} = $h*3600 + $m*60 + $s;
94             }
95              
96              
97             sub boot_time {
98 5     5 1 13 my $self = shift;
99 5         12 $self->days_since_boot * 86400 + $self->time_in_seconds
100             }
101              
102              
103 9     9 1 25 sub sensor { shift->_parse_field('sensor', 0) }
104              
105              
106 8     8 1 21 sub id { shift->_parse_field('id') }
107              
108              
109 5     5 1 12 sub type { shift->_parse_field('type') }
110              
111              
112 3     3 1 10 sub tmpr { shift->_parse_field('tmpr') }
113              
114              
115 3     3 1 13 sub temperature { shift->tmpr }
116              
117              
118 59     59 1 4713 sub has_history { shift->message =~ // }
119              
120              
121 21     21 1 45 sub has_readings { shift->message =~ // }
122              
123              
124             sub units {
125 76     76 1 114 my $self = shift;
126 76 100       160 my $ch1 = $self->_parse_field('ch1') or return;
127             $ch1->{units}
128 74         213 }
129              
130              
131             sub value {
132 56     56 1 126 my ($self, $channel) = @_;
133 56 100       105 $self->units || return; # return if no units can be found - historic only
134 55 100       120 if ($channel) {
135 45         142 return $self->_parse_field('ch'.$channel, { value => undef })->{value};
136             }
137 10 100       30 return $self->{total} if (exists $self->{total});
138 7         33 foreach (1 .. 3) {
139 21   100     75 $self->{total} += $self->value($_)||0;
140             }
141             $self->{total}
142 7         60 }
143              
144              
145             sub summary {
146 15     15 1 3238 my ($self, $prefix) = @_;
147 15 100       49 $prefix = '' unless (defined $prefix);
148 15         50 my $str = $prefix.'Device: '.$self->device.' '.$self->device_version."\n";
149 15         33 $prefix .= ' ';
150 15 100       37 if ($self->has_readings) {
151 5         18 $str .= $prefix.'Sensor: '.$self->sensor;
152 5         14 $str .= (' ['.$self->id.','.$self->type."]\n".
153             $prefix.'Total: '.$self->value.' '.$self->units."\n");
154 5         17 foreach my $phase (1..3) {
155 15         30 my $v = $self->value($phase);
156 15 100       38 next unless (defined $v);
157 11         42 $str .= $prefix.'Phase '.$phase.': '.($v+0)." ".$self->units."\n";
158             }
159             }
160 15 100       33 if ($self->has_history) {
161 11         27 $str .= $prefix."History\n";
162 11         30 my $hist = $self->history;
163 11         76 foreach my $sensor (sort keys %$hist) {
164 101         169 $str .= $prefix.' Sensor '.$sensor."\n";
165 101         135 foreach my $span (sort keys %{$hist->{$sensor}}) {
  101         212  
166 104         136 foreach my $age (sort { $a <=> $b } keys %{$hist->{$sensor}->{$span}}) {
  549         801  
  104         270  
167             $str .= $prefix.' -'.$age.' '.$span.': '.
168 400         1032 (0+$hist->{$sensor}->{$span}->{$age})."\n";
169             }
170             }
171             }
172             }
173             $str
174 15         91 }
175              
176              
177             sub history {
178 24     24 1 44 my $self = shift;
179 24 100       107 return $self->{history} if (exists $self->{history});
180 12         24 my %hist = ();
181 12         26 $self->{history} = \%hist;
182 12 100       24 return $self->{history} unless ($self->has_history);
183 11         24 my $xml = $self->message;
184 11 100       39 if ($xml =~ //) {
185             # envy
186 10         105 foreach my $data (split qr!!, $xml) {
187 100 50       523 my ($sensor) = ($data =~ /(\d+)
188 100         192 my %rec = ();
189 100         176 $hist{$sensor} = _parse_history($data);
190             }
191             } else {
192             # classic
193 1         3 $hist{$self->sensor} = _parse_history($xml);
194             }
195 11         52 \%hist;
196             }
197              
198             sub _parse_history {
199 101     101   162 my $string = shift;
200 101         147 my %rec = ();
201 101         180 foreach my $span (qw/hours days months years/) {
202 404         834 my $first = substr $span, 0, 1;
203 404         6071 while ($string =~ m!<$first(\d+)>([^<]+)!mg) {
204 400         2668 $rec{$span}->{0+$1} = 0+$2;
205             }
206             }
207 101         395 \%rec;
208             }
209              
210             1;
211              
212             =pod
213              
214             =encoding UTF-8
215              
216             =head1 NAME
217              
218             Device::CurrentCost::Message - Perl modules for Current Cost energy monitor messages
219              
220             =head1 VERSION
221              
222             version 1.232151
223              
224             =head1 SYNOPSIS
225              
226             use Device::CurrentCost::Message;
227             my $msg = Device::CurrentCost::Message->new(message => '...');
228             print 'Device: ', $msg->device, ' ', $msg->device_version, "\n";
229             if ($msg->has_readings) {
230             print 'Sensor: ', $msg->sensor, '.', $msg->id, ' (', $msg->type, ")\n";
231             print 'Total: ', $msg->value, ' ', $msg->units, "\n";
232             foreach my $phase (1..3) {
233             print 'Phase ', $phase, ': ',
234             $msg->value($phase)+0, " ", $msg->units, "\n";
235             }
236             }
237              
238             use Data::Dumper;
239             print Data::Dumper->Dump([$msg->history]) if ($msg->has_history);
240              
241             # or
242             print $msg->summary, "\n";
243              
244             =head1 DESCRIPTION
245              
246             =head1 METHODS
247              
248             =head2 C
249              
250             This constructor returns a new Current Cost message object.
251             The supported parameters are:
252              
253             =over
254              
255             =item message
256              
257             The message data. Usually a string like 'C<< ... >>'.
258             This parameter is required.
259              
260             =back
261              
262             =head2 C
263              
264             Returns the type of the device that created the message.
265              
266             =head2 C
267              
268             Returns the name of the device that created the message.
269              
270             =head2 C
271              
272             Returns the version of the device that created the message.
273              
274             =head2 C
275              
276             Returns the raw data of the message.
277              
278             =head2 C
279              
280             Returns the days since boot field of the message.
281              
282             =head2 C
283              
284             Returns the days since boot field of the message.
285              
286             =head2 C
287              
288             Returns the time field of the message in C format.
289              
290             =head2 C
291              
292             Returns the time field of the message in seconds.
293              
294             =head2 C
295              
296             Returns the time since boot reported by the message in seconds.
297              
298             =head2 C
299              
300             Returns the sensor number field of the message. A classic monitor
301             supports only one sensor so 0 is returned.
302              
303             =head2 C
304              
305             Returns the id field of the message.
306              
307             =head2 C
308              
309             Returns the sensor type field of the message.
310              
311             =head2 C
312              
313             Returns the tmpr/temperature field of the message.
314              
315             =head2 C
316              
317             Returns the temperature field of the message.
318              
319             =head2 C
320              
321             Returns true if the message contains history data.
322              
323             =head2 C
324              
325             Returns true if the message contains current data.
326              
327             =head2 C
328              
329             Returns the units of the current data readings in the message.
330              
331             =head2 C
332              
333             Returns the value of the current data reading for the given channel
334             (phase) in the message. If no channel is given then the total of all
335             the current data readings for all channels is returned.
336              
337             =head2 C
338              
339             Returns the string summary of the data in the message. Each line of the
340             string is prefixed by the given prefix or the empty string if the prefix
341             is not supplied.
342              
343             =head2 C
344              
345             Returns a data structure contain any history data from the message.
346              
347             =head1 AUTHOR
348              
349             Mark Hindess
350              
351             =head1 COPYRIGHT AND LICENSE
352              
353             This software is copyright (c) 2014 by Mark Hindess.
354              
355             This is free software; you can redistribute it and/or modify it under
356             the same terms as the Perl 5 programming language system itself.
357              
358             =cut
359              
360             __END__