File Coverage

blib/lib/HiPi/Interface/MicroDotPHAT.pm
Criterion Covered Total %
statement 27 226 11.9
branch 0 58 0.0
condition 0 45 0.0
subroutine 9 32 28.1
pod 0 20 0.0
total 36 381 9.4


line stmt bran cond sub pod time code
1             #########################################################################################
2             # Package HiPi::Interface::MicroDotPHAT
3             # Description : Interface to Pimoroni Micro Dot pHAT
4             # Copyright : Perl Port Copyright (c) 2018 Mark Dootson
5             # License : This is free software; you can redistribute it and/or modify it under
6             # the same terms as the Perl 5 programming language system itself.
7             #
8             # This is a port of the Pimoroni Python code to Perl
9             #
10             # https://github.com/pimoroni/microdot-phat
11             #
12             #########################################################################################
13             # Pimoroni Copyright Notice
14             #########################################################################################
15             # MIT License
16             #
17             # Copyright (c) 2017 Pimoroni Ltd.
18             #
19             # Permission is hereby granted, free of charge, to any person obtaining a copy
20             # of this software and associated documentation files (the "Software"), to deal
21             # in the Software without restriction, including without limitation the rights
22             # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23             # copies of the Software, and to permit persons to whom the Software is
24             # furnished to do so, subject to the following conditions:
25             #
26             # The above copyright notice and this permission notice shall be included in all
27             # copies or substantial portions of the Software.
28             #
29             # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30             # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31             # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32             # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33             # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34             # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35             # SOFTWARE.
36             #
37             #########################################################################################
38              
39             package HiPi::Interface::MicroDotPHAT;
40              
41             #########################################################################################
42 1     1   1026 use strict;
  1         2  
  1         31  
43 1     1   5 use warnings;
  1         2  
  1         25  
44 1     1   5 use parent qw( HiPi::Interface );
  1         3  
  1         5  
45 1     1   56 use HiPi qw( :i2c :rpi :fl3730);
  1         3  
  1         465  
46 1     1   9 use HiPi::Interface::IS31FL3730;
  1         1  
  1         47  
47 1     1   7 use HiPi::Utils::BitBuffer;
  1         2  
  1         36  
48 1     1   594 use HiPi::Interface::MicroDotPHAT::Font qw( :font );
  1         4  
  1         128  
49 1     1   10 use Try::Tiny;
  1         5  
  1         50  
50 1     1   14 use Carp;
  1         2  
  1         2588  
51              
52             our $VERSION ='0.81';
53              
54             __PACKAGE__->create_ro_accessors( qw( _hat_width _hat_height ) );
55              
56             __PACKAGE__->create_accessors( qw(
57             controllers buffer
58             _rotate180
59             _scrollx
60             _scrolly
61             _mirror
62             _decimal
63             _clear_on_exit
64             ) );
65              
66             my $matrixconfig = [
67             { control => 0, type => 'B' },
68             { control => 0, type => 'A' },
69             { control => 1, type => 'B' },
70             { control => 1, type => 'A' },
71             { control => 2, type => 'B' },
72             { control => 2, type => 'A' },
73             ];
74              
75             sub new {
76 0     0 0   my ($class, %userparams) = @_;
77            
78 0           my %params = (
79             brightness => 127,
80             width => 30,
81             height => 7,
82             _hat_width => 30,
83             _hat_height => 7,
84             _rotate180 => 0,
85             _scrollx => 0,
86             _scrolly => 0,
87             _mirror => 0,
88             _clear_on_exit => 1,
89             _decimal => [ 0, 0, 0, 0, 0, 0 ],
90             );
91            
92             # get user params
93 0           foreach my $key( keys (%userparams) ) {
94 0           my $paramkey = $key;
95 0           $paramkey =~ s/^_+//;
96 0           $params{$paramkey} = $userparams{$key};
97             }
98            
99             # initialise
100 0           my @controllers = ();
101            
102 0           my $devicename = $params{devicename};
103            
104 0           for my $address ( 0x63, 0x62, 0x61 ) {
105 0 0         my %is31params = ( $devicename ) ? ( address => $address, devicename => $devicename ) : ( address => $address );
106 0           my $control = HiPi::Interface::IS31FL3730->new( %is31params );
107 0           $control->reset;
108 0           $control->configure( FL3730_SSD_NORMAL | FL3730_DM_MATRIX_BOTH | FL3730_AEN_OFF | FL3730_ADM_8X8 );
109 0           $control->lighting_effect( FL3730_AGS_0_DB | FL3730_CS_35_MA );
110 0           $control->brightness( $params{brightness} );
111 0           push @controllers, $control;
112             }
113            
114 0           $params{controllers} = \@controllers;
115            
116             $params{buffer} = HiPi::Utils::BitBuffer->new(
117             width => $params{width},
118             height => $params{height},
119 0           autoresize => 1,
120             );
121            
122 0           my $self = $class->SUPER::new(%params);
123 0           HiPi->register_exit_method( $self, '_exit');
124 0           return $self;
125             }
126              
127 0     0 0   sub width { $_[0]->buffer->width; }
128              
129 0     0 0   sub height { $_[0]->buffer->height; }
130              
131             sub clear {
132 0     0 0   my $self = shift;
133            
134 0           $self->buffer (
135             HiPi::Utils::BitBuffer->new(
136             width => $self->_hat_width,
137             height => $self->_hat_height,
138             autoresize => 1,
139             )
140             );
141            
142 0           $self->_scrollx(0);
143 0           $self->_scrolly(0);
144 0           $self->_decimal([0,0,0,0,0,0]);
145 0           return;
146             }
147              
148             sub fill {
149 0     0 0   my ( $self, $val ) = @_;
150 0           $self->buffer->fill( $val );
151             }
152              
153             sub set_rotate180 {
154 0     0 0   my($self, $value) = @_;
155 0 0         $self->_rotate180( $value ? 1 : 0 );
156             }
157              
158             sub set_mirror {
159 0     0 0   my($self, $value) = @_;
160 0 0         $self->_mirror( $value ? 1 : 0 );
161             }
162              
163             sub set_clear_on_exit {
164 0     0 0   my($self, $value) = @_;
165 0 0         $self->_clear_on_exit( $value ? 1 : 0 );
166             }
167              
168             sub set_brightness {
169 0     0 0   my($self, $val) = @_;
170            
171 0   0       $val ||= 1.0;
172 0 0 0       if( $val > 1.0 || $val < 0.0 ) {
173 0           carp q(brightness value must be between 0.0 and 1.0 );
174             }
175            
176 0           my $brightness = int($val * 127);
177 0 0         $brightness = 127 if $brightness > 127;
178            
179 0           $_->brightness( $brightness ) for ( @{ $self->controllers } );
  0            
180             }
181              
182             sub set_col {
183 0     0 0   my($self, $x, $col) = @_;
184            
185 0           for (my $y = 0; $y < 7; $y++) {
186 0           $self->set_pixel($x, $y, ($col & (1 << $y)) > 0);
187             }
188             }
189              
190             sub set_pixel {
191 0     0 0   my($self, $x, $y, $c) = @_;
192 0 0         $c = $c ? 1 : 0;
193 0           $self->buffer->set_bit($x, $y, $c);
194             }
195              
196             sub write_char {
197 0     0 0   my($self, $char, $offset_x, $offset_y) = @_;
198 0   0       $offset_x ||= 0;
199 0   0       $offset_y ||= 0;
200            
201 0           my $charbits = _get_char($char);
202 0           for ( my $x = 0; $x < 5; $x++ ) {
203 0           for ( my $y = 0; $y < 7; $y ++ ) {
204 0 0         my $p = (($charbits->[$x] & (1 << $y)) > 0) ? 1 : 0;
205 0           $self->set_pixel($offset_x + $x, $offset_y + $y, $p );
206             }
207             }
208             }
209              
210             sub _get_char {
211 0     0     my $char = shift;
212 0   0       $char //= ' ';
213 0           my $char_ordinal;
214              
215 0     0     try { $char_ordinal = ord($char); };
  0            
216            
217 0 0 0       unless( $char_ordinal && exists(phat_font->{$char_ordinal}) ) {
218 0           carp qq(Unsupported char $char);
219 0           $char_ordinal = 32;
220             }
221              
222             # ? override
223            
224 0 0         $char_ordinal = 12316 if $char_ordinal == 65374;
225            
226 0           return phat_font->{$char_ordinal};
227             }
228              
229              
230             sub set_decimal {
231 0     0 0   my($self, $index, $state) = @_;
232            
233 0 0 0       unless(defined($index)
234             && $index =~ /^0|1|2|3|4|5$/
235             ) {
236 0           return;
237             }
238            
239 0 0         $self->_decimal->[$index] = $state ? 1 : 0;
240            
241             }
242              
243             sub write_string {
244 0     0 0   my($self, $string, $offset_x, $offset_y, $kerning ) = @_;
245 0   0       $string //= '';
246 0   0       $offset_x ||= 0;
247 0   0       $offset_y ||= 0;
248 0   0       $kerning //= 1;
249            
250 0           my $pixels = 0;
251 0           for my $char ( split(//, $string) ) {
252              
253 0           my $char_data = _get_char($char);
254            
255 0           my @pixelcols = ();
256 0           my ($maxX, $minX);
257            
258 0           for (my $x = 0; $x < 5; $x ++ ) {
259 0           my @pixelrows = ();
260 0           for ( my $y = 0; $y < 7; $y++ ) {
261            
262 0 0         my $val = (($char_data->[$x] & (1 << $y)) > 0) ? 1 : 0;
263            
264 0 0         if( $val ) {
265 0 0         $minX = $x unless(defined($minX));
266 0           $maxX = $x;
267             }
268            
269 0           push @pixelrows, [ $offset_x + $x, $offset_y + $y, $val ];
270             }
271            
272 0           push @pixelcols, \@pixelrows;
273             }
274            
275 0 0         if( $kerning ) {
276 0 0         if(defined($minX)) {
277 0           my $shiftcount = $minX;
278 0           for (my $x = 0; $x < $shiftcount; $x ++) {
279 0           shift @pixelcols;
280             }
281 0           for (my $x = $maxX; $x < 4; $x++) {
282              
283 0           pop @pixelcols;
284             }
285            
286             # adjust x values
287 0           for my $col( @pixelcols ) {
288 0           for my $row ( @$col ) {
289 0           $row->[0] -= $shiftcount;
290             }
291             }
292            
293             # add gap
294 0           my @pixelrows = ();
295 0           my $gapoffset = scalar @pixelcols;
296 0           for ( my $y = 0; $y < 7; $y++ ) {
297 0           push @pixelrows, [ $offset_x + $gapoffset + 1, $offset_y + $y, 0 ];
298             }
299 0           push @pixelcols, \@pixelrows;
300             } else {
301             # a space - 2 rows - get rid of final 3
302 0           pop @pixelcols;
303 0           pop @pixelcols;
304 0           pop @pixelcols;
305             }
306             }
307            
308 0           my $charpixels = scalar @pixelcols;
309            
310 0           $offset_x += $charpixels;
311            
312 0           $pixels += $charpixels;
313            
314 0           for my $col ( @pixelcols ) {
315 0           for my $row ( @$col ) {
316 0           $self->set_pixel( @$row );
317             }
318             }
319             }
320            
321 0           return $pixels;
322             }
323              
324             sub show {
325 0     0 0   my $self = shift;
326            
327 0           my $databuf = $self->buffer->clone_buffer;
328            
329             # scroll it etc
330 0           $databuf->scroll_x_y( $self->_scrollx, $self->_scrolly);
331            
332 0 0         $databuf->mirror($self->_hat_width, $self->_hat_height) if $self->_mirror;
333            
334 0 0         $databuf->flip($self->_hat_width, $self->_hat_height) if $self->_rotate180;
335            
336             # write it
337 0           for (my $matrix = 0; $matrix < 6; $matrix++) {
338 0           my $mconf = $matrixconfig->[$matrix];
339 0           my $control = $self->controllers->[$mconf->{control}];
340 0           my $offset_x = $matrix * 5;
341            
342 0           my @buffer = ( 0 ) x 8;
343            
344 0           for ( my $x = 0; $x < 5; $x++) {
345 0           for ( my $y = 0; $y < 7; $y++) {
346 0           my $val = $databuf->get_bit( $offset_x + $x, $y );
347 0 0         if($mconf->{type} eq 'B') {
348 0           $buffer[$x] += ( $val << $y );
349             } else {
350 0           $buffer[$y] += ( $val << $x );
351             }
352             }
353             }
354            
355 0 0         if($mconf->{type} eq 'B') {
356 0 0         if( $self->_decimal->[$matrix] ) {
357 0           $buffer[7] |= 0b01000000;
358             } else {
359 0           $buffer[7] &= 0b10111111;
360             }
361 0           $control->matrix_2_data( @buffer );
362             } else {
363 0 0         if( $self->_decimal->[$matrix] ) {
364 0           $buffer[6] |= 0b10000000;
365             } else {
366 0           $buffer[6] &= 0b01111111;
367             }
368 0           $control->matrix_1_data( @buffer );
369             }
370             }
371            
372 0           for my $control ( @{ $self->controllers } ) {
  0            
373 0           $control->update;
374             }
375             }
376              
377             sub scroll {
378 0     0 0   my($self, $amount_x, $amount_y) = @_;
379 0   0       $amount_x //= 0;
380 0   0       $amount_y //= 0;
381            
382 0 0 0       if($amount_x == 0 && $amount_y == 0 ) {
383 0           $amount_x = 1;
384             }
385            
386 0           my $scroll_x = $self->_scrollx;
387 0           my $scroll_y = $self->_scrolly;
388              
389 0           $scroll_x += $amount_x;
390 0           $scroll_y += $amount_y;
391 0           $scroll_x %= $self->width;
392 0           $scroll_y %= $self->height;
393            
394 0           $self->_scrollx( $scroll_x );
395 0           $self->_scrolly( $scroll_y );
396 0           return;
397             }
398              
399             sub scroll_to {
400 0     0 0   my($self, $position_x, $position_y) = @_;
401 0   0       $position_x //= 0;
402 0   0       $position_y //= 0;
403            
404 0           my $scroll_x = $position_x % $self->width;
405 0           my $scroll_y = $position_y % $self->height;
406            
407 0           $self->_scrollx( $scroll_x );
408 0           $self->_scrolly( $scroll_y );
409            
410 0           return;
411             }
412              
413             sub scroll_horizontal {
414 0     0 0   my($self, $amount) = @_;
415 0   0       $amount //= 1;
416 0           $self->scroll( $amount, 0 );
417             }
418              
419             sub scroll_vertical {
420 0     0 0   my($self, $amount) = @_;
421 0   0       $amount //= 1;
422 0           $self->scroll( 0, $amount );
423             }
424              
425             sub draw_tiny {
426 0     0 0   my($self, $display, $text) = @_;
427 0   0       $text //= '';
428            
429 0 0 0       return unless( defined($display) && $display =~ /^0|1|2|3|4|5$/ );
430 0 0         return unless length($text);
431            
432 0 0         unless( $text =~ /^\d+$/ ) {
433 0           carp qq(text should contain only numbers: '$text');
434 0           return;
435             }
436            
437 0           my @buf = ();
438 0           for my $char ( split(//, $text) ) {
439 0           my $num = int($char);
440 0           push @buf, @{ phat_tiny_numbers->[$num] };
  0            
441             # space
442 0           push @buf, 0;
443             }
444            
445 0           my $rowcount = scalar @buf;
446 0 0         $rowcount = 7 if $rowcount > 7;
447              
448 0           for ( my $row = 0; $row < $rowcount; $row ++ ) {
449 0           my $offset_x = $display * 5;
450 0           my $offset_y = 6-($row % 7);
451 0           for ( my $d = 0; $d < 5; $d++ ) {
452 0           $self->set_pixel($offset_x+(4-$d), $offset_y, ($buf[$row] & (1 << $d)) > 0);
453             }
454             }
455            
456 0           return;
457             }
458              
459             sub _exit {
460 0     0     my $self = shift;
461 0 0         if( $self->_clear_on_exit ) {
462 0           for my $control ( @{ $self->controllers } ) {
  0            
463 0           $control->configure(FL3730_SSD_SHUTDOWN);
464             }
465             }
466             }
467              
468             1;
469              
470             __END__