File Coverage

blib/lib/Device/RadioThermostat.pm
Criterion Covered Total %
statement 27 111 24.3
branch 0 30 0.0
condition 0 6 0.0
subroutine 9 29 31.0
pod 18 18 100.0
total 54 194 27.8


line stmt bran cond sub pod time code
1             package Device::RadioThermostat;
2              
3 1     1   30719 use strict;
  1         2  
  1         43  
4 1     1   8 use warnings;
  1         2  
  1         32  
5              
6 1     1   33 use 5.008_001;
  1         9  
  1         59  
7             our $VERSION = '0.03';
8              
9 1     1   7 use Carp;
  1         2  
  1         105  
10 1     1   1184 use JSON;
  1         19264  
  1         7  
11 1     1   1293 use Socket 'inet_aton';
  1         4781  
  1         248  
12 1     1   1118 use Time::HiRes 'usleep';
  1         1883  
  1         5  
13 1     1   1491 use LWP::UserAgent;
  1         96916  
  1         35  
14 1     1   1064 use IO::Socket::INET;
  1         25069  
  1         8  
15              
16             sub new {
17 0     0 1   my ( $class, %args ) = @_;
18 0           my $self = {
19             address => $args{address},
20             ua => LWP::UserAgent->new() };
21 0 0         croak 'Must pass address to new.' unless $self->{address};
22              
23 0           return bless $self, $class;
24             }
25              
26             sub find_all {
27 0     0 1   my ( $class, $low, $high ) = @_;
28 0 0 0       croak 'Must pass two addresses to find_all.' unless $low && $high;
29              
30 0           my $lowint = unpack( "N", inet_aton($low) );
31 0           my $highint = unpack( "N", inet_aton($high) );
32              
33 0   0       my $s = IO::Socket::INET->new(Proto => 'udp') || croak @$;
34              
35 0           for ( 0 .. 4 ) { # retry 5 times
36 0           for ( my $addr = $lowint; $addr <= $highint; $addr++ ) {
37 0           my $hissockaddr = sockaddr_in( 1900, pack( "N", $addr ) );
38 0           $s->send(
39             "TYPE: WM-DISCOVER\r\n"
40             . "VERSION: 1.0\r\n\r\n"
41             . "services: com.marvell.wm.system*\r\n\r\n",
42             0, $hissockaddr
43             );
44 0           usleep 10000;
45             }
46              
47 0           my $rin = '';
48 0           vec($rin, $s->fileno, 1) = 1;
49 0           my ($rout, %result);
50 0           while (select($rout = $rin, undef, undef, 1)) {
51 0           my $response = '';
52 0           my $hispaddr = $s->recv( $response, 1024, 0 );
53 0           my ( $port, $hisiaddr ) = sockaddr_in($hispaddr);
54 0           my ($hisaddr) = $response =~ m!location:\s*http://([0-9.]+)/sys!i;
55 0 0         next unless $hisaddr;
56              
57 0           my $tstat = Device::RadioThermostat->new(
58             address => 'http://' . $hisaddr );
59 0           my $uuid = $tstat->get_uuid();
60 0 0         next unless $uuid;
61              
62 0           $result{$uuid} = $tstat;
63             }
64              
65 0 0         return \%result if %result;
66             }
67              
68 0           return;
69             }
70              
71             sub tstat {
72 0     0 1   my $self = shift;
73 0           return $self->_ua_get('/tstat');
74             }
75              
76             sub sys {
77 0     0 1   my $self = shift;
78 0           return $self->_ua_get('/sys');
79             }
80              
81             sub get_uuid {
82 0     0 1   my $self = shift;
83 0           return $self->sys()->{uuid};
84             }
85              
86             sub set_mode {
87 0     0 1   my ( $self, $mode ) = @_;
88 0           return $self->_ua_post( '/tstat', { tmode => $mode } );
89             }
90              
91             sub get_target {
92 0     0 1   my ($self) = @_;
93 0           my $mode = $self->tstat->{tmode};
94 0 0         return if $mode == 0;
95 0           my $targets = $self->get_targets();
96 0 0         if ( $mode == 1 ) {
    0          
97 0           return $targets->{t_heat};
98             }
99             elsif ( $mode == 2 ) {
100 0           return $targets->{t_cool};
101             }
102             else {
103 0           return [ $targets->{t_cool}, $targets->{t_heat} ];
104             }
105             }
106              
107             sub get_targets {
108 0     0 1   my ($self) = @_;
109 0           return $self->_ua_get('/tstat/ttemp');
110             }
111              
112             sub temp_heat {
113 0     0 1   my ( $self, $temp ) = @_;
114 0           return $self->_ua_post( '/tstat', { t_heat => $temp } );
115             }
116              
117             sub temp_cool {
118 0     0 1   my ( $self, $temp ) = @_;
119 0           return $self->_ua_post( '/tstat', { t_cool => $temp } );
120             }
121              
122             sub remote_temp {
123 0     0 1   my ($self) = @_;
124 0           return $self->_ua_get('/tstat/remote_temp');
125             }
126              
127             sub disable_remote_temp {
128 0     0 1   my ($self) = @_;
129 0           return $self->_ua_post( '/tstat/remote_temp', { rem_mode => 0 } );
130             }
131              
132             sub set_remote_temp {
133 0     0 1   my ( $self, $temp ) = @_;
134 0           return $self->_ua_post( '/tstat/remote_temp', { rem_temp => 0 + sprintf("%d", $temp) } );
135             }
136              
137             sub lock {
138 0     0 1   my ($self, $mode) = @_;
139 0 0         if ($mode) {
140 0 0         return unless $self->_ua_post( '/tstat/lock', { lock_mode => $mode } );
141             }
142 0           return $self->_ua_get('/tstat/lock');
143             }
144              
145             sub user_message {
146 0     0 1   my ( $self, $line, $message ) = @_;
147 0           return $self->_ua_post( '/tstat/uma', { line => $line, message => $message } );
148             }
149              
150             sub price_message {
151 0     0 1   my ( $self, $line, $message ) = @_;
152 0           return $self->_ua_post( '/tstat/pma', { line => $line, message => $message } );
153             }
154              
155             sub clear_message {
156 0     0 1   my ($self) = @_;
157 0           return $self->_ua_post( '/tstat/pma', { mode => 0 } );
158             }
159              
160             sub datalog {
161 0     0 1   my ($self) = @_;
162 0           return $self->_ua_get('/tstat/datalog');
163             }
164              
165             sub _ua_post {
166 0     0     my ( $self, $path, $data ) = @_;
167 0           my $response
168             = $self->{ua}->post( $self->{address} . $path, content => encode_json $data );
169 0 0         if ( $response->is_success ) {
170 0           my $result = decode_json $response->decoded_content();
171              
172             # return $result;
173 0 0         return exists( $result->{success} ) ? 1 : 0;
174             }
175             else {
176 0           my ($code, $err) = ($response->code, $response->message);
177 0 0         carp $code ? "$code response: $err" : "Connection error: $err";
178 0           return;
179             }
180             }
181              
182             sub _ua_get {
183 0     0     my ( $self, $path ) = @_;
184 0           my $response = $self->{ua}->get( $self->{address} . $path );
185 0 0         if ( $response->is_success ) {
186 0           return decode_json $response->decoded_content();
187             }
188             else {
189 0           my ($code, $err) = ($response->code, $response->message);
190 0 0         carp $code ? "$code response: $err" : "Connection error: $err";
191 0           return;
192             }
193             }
194              
195             1;
196             __END__