File Coverage

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


line stmt bran cond sub pod time code
1 5     5   5971 use strict;
  5         13  
  5         149  
2 5     5   25 use warnings;
  5         13  
  5         275  
3             package Device::CurrentCost;
4             $Device::CurrentCost::VERSION = '1.232151';
5             # ABSTRACT: Perl modules for Current Cost energy monitors
6              
7              
8 5     5   30 use constant DEBUG => $ENV{DEVICE_CURRENT_COST_DEBUG};
  5         19  
  5         392  
9              
10 5     5   30 use Carp qw/croak carp/;
  5         9  
  5         323  
11 5     5   871 use Device::CurrentCost::Constants;
  5         12  
  5         41  
12 5     5   2372 use Device::CurrentCost::Message;
  5         13  
  5         146  
13 5     5   3448 use Device::SerialPort;
  5         96832  
  5         352  
14 5     5   43 use Fcntl;
  5         12  
  5         1402  
15 5     5   577 use IO::Handle;
  5         6190  
  5         173  
16 5     5   2796 use IO::Select;
  5         8456  
  5         253  
17 5     5   38 use Symbol qw(gensym);
  5         11  
  5         268  
18 5     5   2645 use Time::HiRes;
  5         7416  
  5         22  
19              
20              
21             sub new {
22 13     13 1 9887 my ($pkg, %p) = @_;
23             my $self = bless {
24             buf => '',
25             discard_timeout => 1,
26             type => CURRENT_COST_ENVY,
27       0     history_callback => sub {},
28 13         203 %p
29             }, $pkg;
30             croak $pkg.q{->new: 'device' parameter is required}
31 13 100 100     248 unless (exists $p{device} or exists $p{filehandle});
32 12         61 $self->open();
33 11         115 $self;
34             }
35              
36              
37 3     3 1 5 sub device { shift->{device} }
38              
39              
40 14     14 1 100 sub type { shift->{type} }
41              
42              
43             sub baud {
44 6     6 1 13 my $self = shift;
45             defined $self->{baud} ? $self->{baud} :
46 6 100       21 $self->type == CURRENT_COST_CLASSIC ? 9600 : 57600;
    50          
47             }
48              
49              
50 40     40 1 116 sub filehandle { shift->{filehandle} }
51              
52              
53 2     2 1 633 sub serial_port { shift->{serial_port} }
54              
55              
56             sub open {
57 12     12 1 28 my $self = shift;
58              
59 12         29 my $fh = $self->filehandle;
60 12 100       41 unless ($fh) {
61 3         9 my $dev = $self->device;
62 3         6 print STDERR 'Opening serial port: ', $dev, "\n" if DEBUG;
63 3         10 my $fh = gensym();
64 3 100       62 my $s = tie *$fh, 'Device::SerialPort', $dev or
65             croak "Could not tie serial port, $dev, to file handle: $!";
66 2         20 foreach my $setting ([ baudrate => $self->baud ],
67             [ databits => 8 ],
68             [ parity => 'none' ],
69             [ stopbits => 1 ],
70             [ datatype => 'raw' ]) {
71 10         64 my ($setter, @v) = @$setting;
72 10         42 $s->$setter(@v);
73             }
74 2         22 $s->write_settings();
75 2 50       95 sysopen($fh, $dev, O_RDWR|O_NOCTTY|O_NDELAY) or
76             croak "sysopen of '$dev' failed: $!";
77 2         9 $self->{serial_port} = $s;
78 2         5 $self->{filehandle} = $fh;
79             }
80 11         35 $self->filehandle;
81             }
82              
83              
84             sub read {
85 21     21 1 354 my ($self, $timeout) = @_;
86 21         64 my $res = $self->read_one(\$self->{buf});
87 21 100       88 return $res if (defined $res);
88 17         58 $self->_discard_buffer_check();
89 17         48 my $fh = $self->filehandle;
90 17         111 my $sel = IO::Select->new($fh);
91 17         1032 do {
92 18         41 my $start = $self->_time_now;
93 18 100       59 $sel->can_read($timeout) or return;
94 17         145211 my $bytes = sysread $fh, $self->{buf}, 2048, length $self->{buf};
95 17         78 $self->{_last_read} = $self->_time_now;
96 17 100       60 $timeout -= $self->{_last_read} - $start if (defined $timeout);
97 17 100       43 unless ($bytes) {
98 3 50       319 croak((ref $self).'->read: '.(defined $bytes ? 'closed' : 'error: '.$!));
99             }
100 14         21 print STDERR 'Read ', $bytes, "bytes\n" if DEBUG;
101 14         39 $res = $self->read_one(\$self->{buf});
102 14 100       203 return $res if (defined $res);
103             } while (1);
104             }
105              
106              
107             sub read_one {
108 35     35 1 84 my ($self, $rbuf) = @_;
109 35 100       111 return unless ($$rbuf);
110 24         32 print STDERR 'Read one from !', $$rbuf, "!\n" if DEBUG;
111 24 100       336 if ($$rbuf =~ s!^.*?(.*?)[\r\n ]*!!s) {
112 17         149 my $msg = Device::CurrentCost::Message->new(message => $1);
113 17         43 my $t = $self->_time_now;
114 17 100       72 if ($msg->has_history) {
115 11         48 my $new = $msg->history;
116 11   100     43 my $our= $self->{history} || ($self->{history} = {});
117 11         140 foreach my $sensor (sort keys %$new) {
118 101         139 foreach my $interval (sort keys %{$new->{$sensor}}) {
  101         239  
119 104         141 foreach my $age (keys %{$new->{$sensor}->{$interval}}) {
  104         236  
120             $our->{$sensor}->{$interval}->{pending}->{$age} =
121 400         904 0+$new->{$sensor}->{$interval}->{$age};
122             }
123 104 100 66     376 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         36 my $entries = keys %{$our->{$sensor}->{$interval}->{pending}};
  24         56  
128 24 100 100     104 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         107 %{$our->{$sensor}->{$interval}->{current}} =
133 14         22 %{$our->{$sensor}->{$interval}->{pending}};
  14         45  
134 14         36 $our->{$sensor}->{$interval}->{time} = $t;
135             $self->{history_callback}->($sensor, $interval,
136 14         36 $our->{$sensor}->{$interval}->{current});
137             }
138 24         123 $our->{$sensor}->{$interval}->{pending} = {};
139             }
140             }
141             }
142             }
143 17         57 return $msg;
144             } else {
145 7         18 return;
146             }
147             }
148              
149              
150             sub sensor_history {
151 3     3 1 11 my ($self, $sensor, $interval) = @_;
152 3 100       20 return unless (exists $self->{history}->{$sensor}->{$interval}->{current});
153             return {
154             time => $self->{history}->{$sensor}->{$interval}->{time},
155             data => $self->{history}->{$sensor}->{$interval}->{current}
156 1         5 };
157             }
158              
159             sub _discard_buffer_check {
160 17     17   30 my $self = shift;
161 17 100 100     99 if ($self->{buf} ne '' &&
162             $self->{_last_read} < ($self->_time_now - $self->{discard_timeout})) {
163 1         79 carp "Discarding '", $self->{buf}, "'";
164 1         1138 $self->{buf} = '';
165             }
166             }
167              
168             sub _time_now {
169 58     58   193 Time::HiRes::time;
170             }
171              
172             1;
173              
174             __END__