File Coverage

blib/lib/Device/Osram/Lightify/Hub.pm
Criterion Covered Total %
statement 12 80 15.0
branch 0 14 0.0
condition 0 5 0.0
subroutine 4 11 36.3
pod 4 4 100.0
total 20 114 17.5


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3            
4             Device::Osram::Lightify::Hub - Communicate with an Osram Lightify Hub
5            
6             =head1 DESCRIPTION
7            
8             This module allows basic operation of the Osram lightify bulbs,
9             via connections to the Osram hub.
10            
11             =cut
12              
13             =head1 SYNOPSIS
14            
15             use Device::Osram::Lightify;
16            
17             my $tmp = Device::Osram::Lightify::Hub->new( host => "1.2.3.4" );
18            
19             # Turn all devices on
20             $tmp->all_on();
21            
22             # Turn all devices off
23             $tmp->all_of();
24            
25             =cut
26              
27             =head1 DESCRIPTION
28            
29             This module will connect to an Osram Lightify hub, allowing the
30             control of lights via Perl.
31            
32             The communication with the hub is carried out by sending/receiving
33             binary messages to the hub on port 4000. The specific bytes set
34             have been documented in the L<Osram::Lightify::API> module.
35            
36             This module is responsible for:
37            
38             =over 8
39            
40             =item Identifying lights:
41            
42             We can initiate a discovery of all the available lights, and create
43             a suitable L<Osram::Lightify::Light> object for each discovered light.
44            
45             =item Sending broadcast events:
46            
47             We can send a broadcast event, which applies to all known lights,
48             to instruct them to go on or off.
49            
50             =back
51            
52             =cut
53              
54             =head1 METHODS
55            
56             =cut
57              
58 1     1   2598 use strict;
  1         1  
  1         23  
59 1     1   3 use warnings;
  1         1  
  1         27  
60              
61             package Device::Osram::Lightify::Hub;
62              
63 1     1   411 use IO::Socket::INET;
  1         15992  
  1         4  
64 1     1   682 use Device::Osram::Lightify::Light;
  1         2  
  1         538  
65              
66              
67             =head2 new
68            
69             Create a new hub-object, it is mandatory to provide a C<host> parameter
70             which will give the IP (and optional port) of the Osram hub.
71            
72             =cut
73              
74             sub new
75             {
76 0     0 1       my ( $proto, %supplied ) = (@_);
77 0   0           my $class = ref($proto) || $proto;
78              
79 0               my $self = {};
80 0               bless( $self, $class );
81              
82              
83 0   0           $self->{ 'host' } = $supplied{ 'host' } || die "Missing host parameter";
84              
85 0               return $self;
86             }
87              
88              
89             =head2 all_on
90            
91             Broadcast an "on" event to all lights.
92            
93             =cut
94              
95             sub all_on
96             {
97 0     0 1       my ($self) = (@_);
98              
99             # Get the open socket
100 0 0             $self->_connect() unless ( $self->{ '_socket' } );
101 0               my $sock = $self->{ '_socket' };
102              
103             # Send the magic to initiate "All On"
104 0               my $x = "";
105 0               foreach my $char (
106                     qw! 0x0f 0x00 0x00 0x32 0x01 0x00 0x00 0x00 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x01 !
107                   )
108                 {
109 0                   $x .= chr( hex($char) );
110                 }
111 0               syswrite( $sock, $x, length($x) );
112              
113             # Read 8-byte header + 12-byte reply
114 0               my $buffer = $self->_read(20);
115             }
116              
117              
118             =head2 all_off
119            
120             Broadcast an "off" event to all lights.
121            
122             =cut
123              
124             sub all_off
125             {
126 0     0 1       my ($self) = (@_);
127              
128             # Get the open socket
129 0 0             $self->_connect() unless ( $self->{ '_socket' } );
130 0               my $sock = $self->{ '_socket' };
131              
132             # Send the magic to initiate "All Off"
133 0               my $x = "";
134 0               foreach my $char (
135                     qw! 0x0f 0x00 0x00 0x32 0x01 0x00 0x00 0x00 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x00 !
136                   )
137                 {
138 0                   $x .= chr( hex($char) );
139                 }
140 0               syswrite( $sock, $x, length($x) );
141              
142             # Read 8-byte header + 12-byte reply
143 0               my $buffer = $self->_read(20);
144              
145             }
146              
147              
148              
149             =head2 lights
150            
151             Return a new C<Osram::Lightify::Light> object for each of the lights
152             that could be discovered.
153            
154             =cut
155              
156             sub lights
157             {
158 0     0 1       my ($self) = (@_);
159              
160 0               my @ret;
161              
162             # Get the open socket
163 0 0             $self->_connect() unless ( $self->{ '_socket' } );
164 0               my $sock = $self->{ '_socket' };
165              
166             # Send the magic to initiate a scan.
167 0               my $x = "";
168 0               foreach my $char (
169                      qw! 0x0B 0x00 0x00 0x13 0x00 0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x00 !)
170                 {
171 0                   $x .= chr( hex($char) );
172                 }
173 0               syswrite( $sock, $x, length($x) );
174              
175             # Read 8-byte header + 3 bytes reply
176 0               my $buffer = $self->_read(11);
177              
178             # Eight byte header we ignore.
179             # 0 = ??
180             # 1 = ??
181             # ... ??
182             # 8 = ??
183             # 9 = Number of bulbs
184             # 10 = ??
185              
186             # The number of devices.
187 0               my $count = ord( substr( $buffer, 9, 1 ) );
188              
189             # For each one.
190 0               while ($count)
191                 {
192             # Read 8 byte header + 42 bytes for each light.
193 0                   my $buffer = $self->_read(50);
194 0                   $count = $count - 1;
195              
196 0                   push( @ret,
197                           Device::Osram::Lightify::Light->new( hub => $self,
198                                                                binary => $buffer
199                                                              ) );
200                 }
201              
202 0               return (@ret);
203             }
204              
205              
206              
207             =begin doc _connect
208            
209             Private and internal-method.
210            
211             Connect to the hub, via the C<host> parameter we were given in our constructor.
212            
213             =end doc
214            
215             =cut
216              
217             sub _connect
218             {
219 0     0         my ($self) = (@_);
220              
221 0               my $host = $self->{ 'host' };
222 0               my $port = 4000;
223              
224 0 0             if ( $host =~ /^(.*):([0-9]+)$/ )
225                 {
226 0                   $host = $1;
227 0                   $port = $2;
228                 }
229              
230 0               $self->{ '_socket' } =
231                   IO::Socket::INET->new( Proto => "tcp",
232                                          Type => SOCK_STREAM,
233                                          Blocking => 1,
234                                          PeerAddr => $host,
235                                          PeerPort => $port,
236                                        );
237              
238 0 0             die "Failed to connect to $host:$port" unless ( $self->{ '_socket' } );
239 0               binmode( $self->{ '_socket' } );
240             }
241              
242              
243             =begin doc
244            
245             Private and internal-method.
246            
247             Read N-bytes from the open socket. We do this a byte at a time,
248             and return only when we've read as much as we should.
249            
250             =end doc
251            
252             =cut
253              
254             sub _read
255             {
256 0     0         my ( $self, $count ) = (@_);
257              
258 0               my $out;
259              
260 0               while ($count)
261                 {
262 0                   my $buf;
263 0                   my $c = sysread( $self->{ '_socket' }, $buf, 1, 0 );
264 0 0                 if ($c)
265                     {
266 0                       $count -= $c;
267 0                       $out .= $buf;
268              
269                     }
270              
271                 }
272 0               return ($out);
273             }
274              
275              
276             =begin doc _session_token
277            
278             Private and internal-method.
279            
280             Generate and return a four-byte token. All the mutator commands
281             for the light-devices take such a thing. It doesn't seem to matter
282             what we set, so I've encoded the seconds-past-the-epoch.
283            
284             Setting a value avoids commands being dropped as "old"/"reused".
285            
286             =end doc
287            
288             =cut
289              
290             sub _session_token()
291             {
292 0     0         my $time = time();
293 0               $time = sprintf( "%x", time );
294              
295 0               my $t = "";
296              
297 0 0             if ( $time =~ /^(..)(..)(..)(..)$/ )
298                 {
299 0                   $t .= chr( hex($1) );
300 0                   $t .= chr( hex($2) );
301 0                   $t .= chr( hex($3) );
302 0                   $t .= chr( hex($4) );
303                 }
304                 else
305                 {
306              
307 0                   $t .= chr( hex("0x00") );
308 0                   $t .= chr( hex("0x00") );
309 0                   $t .= chr( hex("0x00") );
310 0                   $t .= chr( hex("0x00") );
311                 }
312 0               return ($t);
313             }
314              
315              
316             1;
317              
318              
319              
320             =head1 AUTHOR
321            
322             Steve Kemp <steve@steve.org.uk>
323            
324             =cut
325              
326             =head1 COPYRIGHT AND LICENSE
327            
328             Copyright (C) 2016 Steve Kemp <steve@steve.org.uk>.
329            
330             This library is free software. You can modify and or distribute it under
331             the same terms as Perl itself.
332            
333             =cut
334