File Coverage

blib/lib/Device/PiGlow.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Device::PiGlow;
2              
3 2     2   29885 use strict;
  2         6  
  2         83  
4 2     2   12 use warnings;
  2         3  
  2         192  
5              
6             our $VERSION = '1.1';
7              
8 2     2   2514 use Moose;
  0            
  0            
9              
10              
11             use Device::SMBus;
12              
13              
14             # These are all the register numbers defined by the device
15             use constant CMD_ENABLE_OUTPUT => 0x00;
16             use constant CMD_ENABLE_LEDS => 0x13;
17             use constant CMD_ENABLE_LEDS_1 => 0x13;
18             use constant CMD_ENABLE_LEDS_2 => 0x14;
19             use constant CMD_ENABLE_LEDS_3 => 0x15;
20             use constant CMD_SET_PWM_VALUES => 0x01;
21             use constant CMD_SET_PWM_VALUE_1 => 0x01;
22             use constant CMD_SET_PWM_VALUE_2 => 0x02;
23             use constant CMD_SET_PWM_VALUE_3 => 0x03;
24             use constant CMD_SET_PWM_VALUE_4 => 0x04;
25             use constant CMD_SET_PWM_VALUE_5 => 0x05;
26             use constant CMD_SET_PWM_VALUE_6 => 0x06;
27             use constant CMD_SET_PWM_VALUE_7 => 0x07;
28             use constant CMD_SET_PWM_VALUE_8 => 0x08;
29             use constant CMD_SET_PWM_VALUE_9 => 0x09;
30             use constant CMD_SET_PWM_VALUE_10 => 0x0A;
31             use constant CMD_SET_PWM_VALUE_11 => 0x0B;
32             use constant CMD_SET_PWM_VALUE_12 => 0x0C;
33             use constant CMD_SET_PWM_VALUE_13 => 0x0D;
34             use constant CMD_SET_PWM_VALUE_14 => 0x0E;
35             use constant CMD_SET_PWM_VALUE_15 => 0x0F;
36             use constant CMD_SET_PWM_VALUE_16 => 0x10;
37             use constant CMD_SET_PWM_VALUE_17 => 0x11;
38             use constant CMD_SET_PWM_VALUE_18 => 0x12;
39             use constant CMD_UPDATE => 0x16;
40             use constant CMD_RESET => 0x17;
41             =head1 NAME
42              
43             Device::PiGlow - Interface to the PiGlow board using i2c
44              
45             =head1 SYNOPSIS
46              
47             use Device::PiGlow;
48              
49             my $pg = Device::PiGlow->new();
50              
51             my $values = [0x01,0x02,0x04,0x08,0x10,0x18,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0xA0,0xC0,0xE0,0xFF];
52              
53             $pg->enable_output();
54             $pg->enable_all_leds();
55              
56             $pg->write_all_leds($values);
57             sleep 10;
58             $pg->reset();
59              
60              
61             See the L<examples> directory for more ways of using this.
62              
63             =head1 DESCRIPTION
64              
65             The PiGlow from Pimoroni (http://shop.pimoroni.com/products/piglow) is
66             a small board that plugs in to the Raspberry PI's GPIO header
67             with 18 LEDs on that can be addressed individually via i2c.
68              
69             This module uses L<Device::SMBus> to abstract the interface to the device
70             so that it can be controlled from a Perl programme.
71              
72             It is assumed that you have installed the OS packages required to make
73             i2c work and have configured and tested the i2c appropriately. The only
74             difference that seems to affect the PiGlow device is that it only seems
75             to be reported by C<i2cdetect> if you use the "quick write" probe flag:
76              
77             sudo i2cdetect -y -q 1
78              
79             (assuming you have a Rev B. Pi - if not you should supply 0 instead of 1.)
80             I have no way of knowing the compatibility of the "quick write" with any
81             other devices you may have plugged in to the Pi, so I wouldn't recommend
82             doing this with any other devices unless you know that they won't be adversely
83             affected by "quick write". The PiGlow has a fixed address anyway so the
84             information isn't that useful.
85              
86             =head2 METHODS
87              
88             =over 4
89              
90             =item new
91              
92             The constructor. This takes two optional attributes which are passed on
93             directly to the L<Device::SMBus> constructor:
94              
95             =over 4
96              
97             =item I2CBusDevicePath
98              
99             This sets the device path, it defaults to /dev/i2c-1 (assuming a newer
100             Raspberry PI,) You will want to set this if you are using an older PI or
101             an OS that creates a different device.
102              
103             =cut
104              
105             has I2CBusDevicePath => (
106             is => 'rw',
107             isa => 'Str',
108             default => '/dev/i2c-1',
109             );
110              
111             =item I2CDeviceAddress
112              
113             This sets the i2c device address, this defaults to 0x54. Unless you have
114             somehow altered the address you shouldn't need to change this.
115              
116             =cut
117              
118             has I2CDeviceAddress => (
119             is => 'rw',
120             isa => 'Num',
121             default => 0x54,
122             );
123              
124             =back
125              
126             =item device_smbus
127              
128             This is the L<Device::SMBus> object we will be using to interact with i2c.
129             It will be initialised with the attributes described above. You may want
130             this if you need to do something to the PiGlow I haven't thought of.
131              
132             =cut
133              
134             has device_smbus => (
135             is => 'ro',
136             isa => 'Device::SMBus',
137             lazy => 1,
138             builder => '_get_device_smbus',
139             handles => {
140             i2c_file => 'I2CBusFilenumber',
141             _write_byte => 'writeByteData',
142             },
143             );
144              
145             sub _get_device_smbus
146             {
147             my ( $self ) = @_;
148              
149             my $smbus = Device::SMBus->new(
150             I2CBusDevicePath => $self->I2CBusDevicePath,
151             I2CDeviceAddress => $self->I2CDeviceAddress
152             );
153             return $smbus;
154             }
155              
156             =item update
157              
158             This updates the values set to the LED registers to the LEDs and changes
159             the display.
160              
161             =cut
162              
163             sub update
164             {
165             my ( $self ) = @_;
166            
167             return $self->_write_byte(CMD_UPDATE, 0xFF);
168             }
169              
170             =item enable_output
171              
172             This sets the state of the device to active.
173              
174             =cut
175              
176             sub enable_output
177             {
178             my ( $self ) = @_;
179             return $self->_write_byte(CMD_ENABLE_OUTPUT, 0x01);
180             }
181              
182             has '_led_bank_enable_registers' => (
183             is => 'ro',
184             isa => 'ArrayRef',
185             lazy => 1,
186             auto_deref => 1,
187             default => sub {
188             return [
189             CMD_ENABLE_LEDS_1,
190             CMD_ENABLE_LEDS_2,
191             CMD_ENABLE_LEDS_3,
192             ];
193             },
194             );
195              
196             =item enable_all_leds
197              
198             This turns on all three banks of LEDs.
199              
200             =cut
201              
202             sub enable_all_leds
203             {
204             my ( $self ) = @_;
205             return $self->write_block_data(CMD_ENABLE_LEDS, [0xFF, 0xFF, 0xFF]);
206             }
207              
208             =item write_all_leds
209              
210             This writes the PWM values supplied as an Array Reference and immediately
211             calls C<update> to apply the values to the LEDs.
212              
213             The array must be exactly 18 elements long.
214              
215             The optional second argument will cause the gamma correction to be applied
216             if the value is true.
217              
218             =cut
219              
220             sub write_all_leds
221             {
222             my ( $self, $values, $fix ) = @_;
223              
224             if ( @{$values} == 18 )
225             {
226             if ( $fix )
227             {
228             $values = $self->gamma_fix_values($values);
229             }
230             $self->write_block_data(CMD_SET_PWM_VALUES, $values);
231             $self->update();
232             }
233             }
234              
235             =item all_off
236              
237             This is convenience to turn off (set to brightness 0) all the LEDs at
238             once. It calls C<update> immediately.
239              
240             =cut
241              
242             sub all_off
243             {
244             my ( $self ) = @_;
245              
246             my $vals = [];
247             @{$vals} = (0) x 18;
248              
249             $self->write_all_leds($vals);
250             }
251              
252             =item set_leds
253              
254             This sets the leds specified in the array reference in the first argument
255             ( values 0 - 17 to index the LEDs ) all to the single value specified.
256              
257             Gamma adjustment is applied.
258              
259             This does not call update, this should be done afterwards in order to
260             update the LED values.
261              
262             =cut
263              
264             sub set_leds
265             {
266             my ( $self, $leds, $value ) = @_;
267              
268             if (defined $leds && ( $value >= 0 && $value <= 255 ))
269             {
270             $value = $self->map_gamma($value);
271             foreach my $led ( @{$leds} )
272             {
273             if ( $led >= 0 && $led <= 17 )
274             {
275             $self->_write_byte($self->get_led_register($led), $value);
276             }
277             }
278             }
279             }
280              
281             =item led_table
282              
283             This provides a mapping between the logical order of the leds (indexed
284             0 - 17 ) to the registers that control them.
285              
286             =cut
287              
288             has led_table => (
289             is => 'ro',
290             isa => 'ArrayRef',
291             traits => [qw(Array)],
292             handles => {
293             get_led_register => 'get',
294             },
295             auto_deref => 1,
296             lazy => 1,
297             builder => '_get_led_table',
298             );
299              
300             # "0x07", "0x08", "0x09", "0x06", "0x05", "0x0A", "0x12", "0x11",
301             # "0x10", "0x0E", "0x0C", "0x0B", "0x01", "0x02", "0x03", "0x04", "0x0F", "0x0D"
302             sub _get_led_table
303             {
304             my ( $self ) = @_;
305              
306             return [
307             CMD_SET_PWM_VALUE_7,
308             CMD_SET_PWM_VALUE_8,
309             CMD_SET_PWM_VALUE_9,
310             CMD_SET_PWM_VALUE_6,
311             CMD_SET_PWM_VALUE_5,
312             CMD_SET_PWM_VALUE_10,
313             CMD_SET_PWM_VALUE_18,
314             CMD_SET_PWM_VALUE_17,
315             CMD_SET_PWM_VALUE_16,
316             CMD_SET_PWM_VALUE_14,
317             CMD_SET_PWM_VALUE_12,
318             CMD_SET_PWM_VALUE_11,
319             CMD_SET_PWM_VALUE_1,
320             CMD_SET_PWM_VALUE_2,
321             CMD_SET_PWM_VALUE_3,
322             CMD_SET_PWM_VALUE_4,
323             CMD_SET_PWM_VALUE_15,
324             CMD_SET_PWM_VALUE_13,
325             ];
326             }
327              
328             =item ring_table
329              
330             The arrangement of the LEDs can be thought of as being arrange logically
331             as 6 "rings". This provides access to the rings indexed 0-5.
332              
333             =cut
334              
335             has ring_table => (
336             is => 'ro',
337             isa => 'ArrayRef',
338             traits => [qw(Array)],
339             handles => {
340             get_ring_leds => 'get',
341             },
342             auto_deref => 1,
343             lazy => 1,
344             builder => '_get_ring_table',
345             );
346              
347             sub _get_ring_table
348             {
349             my ( $self ) = @_;
350              
351             my $rings = [];
352              
353             foreach my $led ( 0 .. 5 )
354             {
355             $rings->[$led] = [];
356              
357             foreach my $arm ( 0 .. 2 )
358             {
359             my $led_no = $self->get_arm_leds($arm)->[$led];
360             push @{$rings->[$led]}, $led_no;
361             }
362             }
363              
364             return $rings;
365             }
366              
367             =item set_ring
368              
369             Sets all of the LEDs in the logical ring indexed 0 - 5 to the value
370             specified. Gamma correction is applied to the value.
371              
372             This isn't immediately applied to the LEDs, C<update> should be called
373             after all the changes have been applied.
374              
375             =cut
376              
377             sub set_ring
378             {
379             my ( $self, $ring, $value ) = @_;
380              
381             if ($ring >= 0 && $ring <= 5 )
382             {
383             if( defined( my $ring_leds = $self->get_ring_leds($ring) ))
384             {
385             $self->set_leds($ring_leds, $value);
386             }
387             else
388             {
389             warn "no ring defined for $ring";
390             }
391             }
392             else
393             {
394             warn "No ring $ring";
395             }
396             }
397              
398             =item arm_table
399              
400             This returns an Array Ref of Array references that reference the LEDs in
401             each "arm" of the PiGlow.
402              
403             =cut
404              
405             has arm_table => (
406             is => 'ro',
407             isa => 'ArrayRef',
408             traits => [qw(Array)],
409             handles => {
410             get_arm_leds => 'get',
411             },
412             auto_deref => 1,
413             lazy => 1,
414             builder => '_get_arm_table',
415             );
416              
417             sub _get_arm_table
418             {
419             my ( $self ) = @_;
420              
421             return [
422             [0,1,2,3,4,5],
423             [6,7,8,9,10,11],
424             [12,13,14,15,16,17]
425             ];
426             }
427              
428             =item set_arm
429              
430             Sets the LEDs in the specified "arm" of the PiGlow to the specified value.
431              
432             Value has gamma correction applied.
433              
434             Update isn't applied and the update method should be called when all the
435             required updates have been performed.
436             =cut
437              
438              
439             sub set_arm
440             {
441             my ( $self, $arm, $value ) = @_;
442              
443             if ( defined $arm && ($arm >= 0 && $arm <= 2))
444             {
445             my $arm_leds = $self->get_arm_leds($arm);
446             $self->set_leds($arm_leds, $value);
447             }
448             }
449              
450             =item colour_table
451              
452             This returns a Hash reference mapping the names of the coloured LEDs
453             to the groups of LEDs of that colour.
454              
455             The delegate colours returns the keys, get_colour_leds returns the
456             list of LEDs
457              
458             =cut
459              
460              
461             has colour_table => (
462             is => 'ro',
463             isa => 'HashRef',
464             traits => [qw(Hash)],
465             handles => {
466             get_colour_leds => 'get',
467             colours => 'keys',
468             },
469             auto_deref => 1,
470             lazy => 1,
471             builder => '_get_colour_table',
472             );
473              
474             sub _get_colour_table
475             {
476             return {
477             white => [5,11,17],
478             blue => [4,10,16],
479             green => [3,9,15],
480             yellow => [2,8,14],
481             orange => [1,7,13],
482             red => [0,6,12] ,
483             };
484             }
485              
486             =item set_colour
487              
488             Sets the LEDs in the specified "colour" of the PiGlow to the specified value.
489              
490             Value has gamma correction applied.
491              
492             Update isn't applied and the update method should be called when all the
493             required updates have been performed.
494              
495             =cut
496              
497              
498             sub set_colour
499             {
500             my ( $self, $colour, $value ) = @_;
501              
502             if ( defined $colour)
503             {
504             if ( defined (my $colour_leds = $self->get_colour_leds($colour) ))
505             {
506             $self->set_leds($colour_leds, $value);
507             }
508             }
509             }
510              
511             =item gamma_table
512              
513             This is a map of input PWM values (0 - 255) to gamma corrected values
514             that produce a more even range of brightness in the LEDs.
515              
516             The values were lifted from the piglow library for Node.js which in turn
517             borrowed them from elsewhere.
518              
519             =cut
520              
521             has gamma_table => (
522             is => 'ro',
523             isa => 'ArrayRef',
524             traits => [qw(Array)],
525             auto_deref => 1,
526             lazy => 1,
527             builder => '_get_gamma_table',
528             handles => {
529             map_gamma => 'get',
530             },
531             );
532              
533             sub _get_gamma_table
534             {
535             my ($self) = @_;
536              
537             return [
538             0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
539             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
540             1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
541             2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
542             3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
543             4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6,
544             6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8,
545             8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 11,
546             11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15,
547             15, 15, 16, 16, 16, 17, 17, 18, 18, 18, 19, 19, 20, 20,
548             20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 26, 26, 27, 27,
549             28, 29, 29, 30, 31, 31, 32, 33, 33, 34, 35, 36, 36, 37,
550             38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48, 50, 51,
551             52, 53, 54, 55, 57, 58, 59, 60, 62, 63, 64, 66, 67, 69,
552             70, 72, 74, 75, 77, 79, 80, 82, 84, 86, 88, 90, 91, 94,
553             96, 98, 100, 102, 104, 107, 109, 111, 114, 116, 119, 122, 124, 127,
554             130, 133, 136, 139, 142, 145, 148, 151, 155, 158, 161, 165, 169, 172,
555             176, 180, 184, 188, 192, 196, 201, 205, 210, 214, 219, 224, 229, 234,
556             239, 244, 250, 255
557             ];
558             }
559              
560             =item gamma_fix_values
561              
562             This applies the gamma adjustment mapping to the supplied array ref.
563              
564             =cut
565              
566             sub gamma_fix_values
567             {
568             my ( $self, $values ) = @_;
569              
570             my @values = map { $self->map_gamma($_) } @{$values};
571              
572             return \@values;
573             }
574              
575             =item reset
576              
577             Resets the device to its default state. That is to say all LEDs off.
578              
579             It will be necessary to re-enable the groups of LEDs again after calling
580             this.
581              
582             =cut
583              
584             sub reset
585             {
586             my ( $self) = @_;
587             return $self->_write_byte(CMD_RESET, 0xFF);
588             }
589              
590              
591             =item write_block_data
592              
593             $self->writeBlockData($register_address, $values)
594              
595             Writes a maximum of 32 bytes in a single block to the i2c device.
596             The supplied $values should be an array ref containing the bytes to
597             be written.
598              
599             The register address supplied should be the first of a consecutive set
600             of addresses equal to the number of values supplied. Supplying an
601             address that doesn't fit that description is unlikely to work well and
602             will almost certainly result in undefined behaviour in the device.
603              
604             =cut
605              
606             # Device::SMBus seems to have the XS part of this but not the perl.
607             # I'll use this one if it doesn't
608              
609             sub write_block_data
610             {
611             my ( $self, $register_address, $values ) = @_;
612            
613             my $value = pack "C*", @{$values};
614              
615             my $retval = Device::SMBus::_writeI2CBlockData($self->i2c_file,$register_address, $value);
616             return $retval;
617             }
618              
619             =back
620              
621             =head2 CONSTANTS
622              
623             These define the command registers used by the SN3218 IC used in PiGlow
624              
625             =over 4
626              
627             =cut
628              
629              
630             =item CMD_ENABLE_OUTPUT
631              
632             If set to 1 the device will be ready for operation, if 0 then it will
633             be "shutdown"
634              
635             =cut
636              
637              
638             =item CMD_ENABLE_LEDS
639              
640             This should be used for a block write to enable (or disable) all three
641             groups of LEDs in one go. The values are a 6 bit mask, one bit for each
642             LED in the group.
643              
644             =cut
645              
646              
647             =item CMD_ENABLE_LEDS_1
648              
649             A bit mask to enable the LEDs in group 1
650              
651             =cut
652              
653              
654             =item CMD_ENABLE_LEDS_2
655              
656             A bit mask to enable the LEDs in group 2
657              
658             =cut
659              
660              
661             =item CMD_ENABLE_LEDS_3
662              
663             A bit mask to enable the LEDs in group three.
664              
665             =cut
666              
667              
668             =item CMD_SET_PWM_VALUES
669              
670             This should be used in a block write to set the PWM values of all 18 LEDs
671             at once. The values should be 8 bit values.
672              
673             There are also CMD_SET_PWN_VALUE_[1 .. 18] to set the LEDs individually.
674              
675             =cut
676              
677              
678              
679             =item CMD_UPDATE
680              
681             The written LED values are stored in a temporary register and are not
682             applied to the LEDs until an 8 bit value is written to this register/
683              
684             =cut
685              
686              
687             =item CMD_RESET
688              
689             Writing a value to this register will restore the device to its power
690             on default (i.e. all LEDs blank)
691              
692             =back
693              
694             =head1 AUTHOR
695              
696             Jonathan Stowe <jns@gellyfish.co.uk>
697              
698             =head1 COPYRIGHT
699              
700             This is licensed under the same terms as Perl itself. Please see the
701             LICENSE file in the distribution files for the full details.
702              
703             =head1 SUPPORT
704              
705             I wrote this because I had the device and I prefer to use Perl. It
706             probably does everything I would like it to do. If you want it to do
707             something else or find a bug or infelicity, please feel free to fork
708             the code at github and send me a pull request:
709              
710             https://github.com/jonathanstowe/Device-PiGlow
711              
712             bug reports without patches are likely to be ignored unless you want to
713             do do something with it that I think is fun and interesting.
714              
715             =head1 CREDIT WHERE IT'S DUE
716              
717             This was largely a no brainer. The author of L<Device::SMBus> did all the
718             hard work on the Perl side and the implementation details were largely
719             translated from the PyGlow library. https://github.com/benleb/PyGlow/
720              
721              
722             =head1 SEE ALSO
723              
724             L<Device::SMBus>
725              
726             =cut
727              
728             1;