File Coverage

blib/lib/Device/CurrentCost.pm
Criterion Covered Total %
statement 114 115 99.1
branch 35 38 92.1
condition 17 20 85.0
subroutine 24 25 96.0
pod 10 10 100.0
total 200 208 96.1


line stmt bran cond sub pod time code
1 5     5   4707 use strict;
  5         11  
  5         164  
2 5     5   25 use warnings;
  5         8  
  5         296  
3             package Device::CurrentCost;
4             $Device::CurrentCost::VERSION = '1.142240';
5             # ABSTRACT: Perl modules for Current Cost energy monitors
6              
7              
8 5     5   26 use constant DEBUG => $ENV{DEVICE_CURRENT_COST_DEBUG};
  5         8  
  5         279  
9              
10 5     5   25 use Carp qw/croak carp/;
  5         10  
  5         336  
11 5     5   1071 use Device::CurrentCost::Constants;
  5         11  
  5         36  
12 5     5   6877 use Device::CurrentCost::Message;
  5         12  
  5         173  
13 5     5   6058 use Device::SerialPort qw/:PARAM :STAT 0.07/;
  5         134961  
  5         1632  
14 5     5   58 use Fcntl;
  5         9  
  5         1939  
15 5     5   1102 use IO::Handle;
  5         8370  
  5         243  
16 5     5   5575 use IO::Select;
  5         8847  
  5         271  
17 5     5   33 use Symbol qw(gensym);
  5         10  
  5         287  
18 5     5   4871 use Time::HiRes;
  5         9580  
  5         24  
19              
20              
21             sub new {
22 13     13 1 14418 my ($pkg, %p) = @_;
23             my $self = bless {
24             buf => '',
25             discard_timeout => 1,
26             type => CURRENT_COST_ENVY,
27 0     0   0 history_callback => sub {},
28 13         196 %p
29             }, $pkg;
30 13 100 100     305 croak $pkg.q{->new: 'device' parameter is required}
31             unless (exists $p{device} or exists $p{filehandle});
32 12         41 $self->open();
33 11         37 $self;
34             }
35              
36              
37 3     3 1 7 sub device { shift->{device} }
38              
39              
40 14     14 1 128 sub type { shift->{type} }
41              
42              
43             sub baud {
44 6     6 1 12 my $self = shift;
45 6 100       31 defined $self->{baud} ? $self->{baud} :
    50          
46             $self->type == CURRENT_COST_CLASSIC ? 9600 : 57600;
47             }
48              
49              
50 40     40 1 145 sub filehandle { shift->{filehandle} }
51              
52              
53 2     2 1 592 sub serial_port { shift->{serial_port} }
54              
55              
56             sub open {
57 12     12 1 40 my $self = shift;
58              
59 12         42 my $fh = $self->filehandle;
60 12 100       70 unless ($fh) {
61 3         10 my $dev = $self->device;
62 3         5 print STDERR 'Opening serial port: ', $dev, "\n" if DEBUG;
63 3         16 my $fh = gensym();
64 3 100       68 my $s = tie *$fh, 'Device::SerialPort', $dev or
65             croak "Could not tie serial port, $dev, to file handle: $!";
66 2         23 foreach my $setting ([ baudrate => $self->baud ],
67             [ databits => 8 ],
68             [ parity => 'none' ],
69             [ stopbits => 1 ],
70             [ datatype => 'raw' ]) {
71 10         62 my ($setter, @v) = @$setting;
72 10         60 $s->$setter(@v);
73             }
74 2         28 $s->write_settings();
75 2 50       114 sysopen($fh, $dev, O_RDWR|O_NOCTTY|O_NDELAY) or
76             croak "sysopen of '$dev' failed: $!";
77 2         6 $self->{serial_port} = $s;
78 2         4 $self->{filehandle} = $fh;
79             }
80 11         35 $self->filehandle;
81             }
82              
83              
84             sub read {
85 21     21 1 291 my ($self, $timeout) = @_;
86 21         96 my $res = $self->read_one(\$self->{buf});
87 21 100       67 return $res if (defined $res);
88 17         57 $self->_discard_buffer_check();
89 17         42 my $fh = $self->filehandle;
90 17         165 my $sel = IO::Select->new($fh);
91 17         864 do {
92 18         43 my $start = $self->_time_now;
93 18 100       57 $sel->can_read($timeout) or return;
94 17         138938 my $bytes = sysread $fh, $self->{buf}, 2048, length $self->{buf};
95 17         47 $self->{_last_read} = $self->_time_now;
96 17 100       61 $timeout -= $self->{_last_read} - $start if (defined $timeout);
97 17 100       46 unless ($bytes) {
98 3 50       294 croak((ref $self).'->read: '.(defined $bytes ? 'closed' : 'error: '.$!));
99             }
100 14         16 print STDERR 'Read ', $bytes, "bytes\n" if DEBUG;
101 14         45 $res = $self->read_one(\$self->{buf});
102 14 100       146 return $res if (defined $res);
103             } while (1);
104             }
105              
106              
107             sub read_one {
108 35     35 1 55 my ($self, $rbuf) = @_;
109 35 100       94 return unless ($$rbuf);
110 24         65 print STDERR 'Read one from !', $$rbuf, "!\n" if DEBUG;
111 24 100       358 if ($$rbuf =~ s!^.*?(.*?)[\r\n ]*!!s) {
112 17         138 my $msg = Device::CurrentCost::Message->new(message => $1);
113 17         46 my $t = $self->_time_now;
114 17 100       69 if ($msg->has_history) {
115 11         107 my $new = $msg->history;
116 11   100     56 my $our= $self->{history} || ($self->{history} = {});
117 11         79 foreach my $sensor (sort keys %$new) {
118 101         107 foreach my $interval (sort keys %{$new->{$sensor}}) {
  101         263  
119 104         98 foreach my $age (keys %{$new->{$sensor}->{$interval}}) {
  104         315  
120 400         1158 $our->{$sensor}->{$interval}->{pending}->{$age} =
121             0+$new->{$sensor}->{$interval}->{$age};
122             }
123 104 100 66     570 if (exists $our->{$sensor}->{$interval}->{pending}->{1} ||
      66        
      66        
124             ($interval eq 'hours' &&
125             (exists $our->{$sensor}->{$interval}->{pending}->{4} ||
126             exists $our->{$sensor}->{$interval}->{pending}->{2}))) {
127 24         27 my $entries = keys %{$our->{$sensor}->{$interval}->{pending}};
  24         61  
128 24 100 100     169 if ($entries == { years => 4, months => 21, # envy
129             days => 90, hours => 372 }->{$interval} ||
130             $entries == { years => 4, months => 12, # classic
131             days => 31, hours => 13 }->{$interval}) {
132 14         100 %{$our->{$sensor}->{$interval}->{current}} =
  14         57  
133 14         16 %{$our->{$sensor}->{$interval}->{pending}};
134 14         40 $our->{$sensor}->{$interval}->{time} = $t;
135 14         46 $self->{history_callback}->($sensor, $interval,
136             $our->{$sensor}->{$interval}->{current});
137             }
138 24         190 $our->{$sensor}->{$interval}->{pending} = {};
139             }
140             }
141             }
142             }
143 17         58 return $msg;
144             } else {
145 7         17 return;
146             }
147             }
148              
149              
150             sub sensor_history {
151 3     3 1 7 my ($self, $sensor, $interval) = @_;
152 3 100       22 return unless (exists $self->{history}->{$sensor}->{$interval}->{current});
153             return {
154 1         7 time => $self->{history}->{$sensor}->{$interval}->{time},
155             data => $self->{history}->{$sensor}->{$interval}->{current}
156             };
157             }
158              
159             sub _discard_buffer_check {
160 17     17   27 my $self = shift;
161 17 100 100     84 if ($self->{buf} ne '' &&
162             $self->{_last_read} < ($self->_time_now - $self->{discard_timeout})) {
163 1         92 carp "Discarding '", $self->{buf}, "'";
164 1         1403 $self->{buf} = '';
165             }
166             }
167              
168             sub _time_now {
169 58     58   241 Time::HiRes::time;
170             }
171              
172             1;
173              
174             __END__