File Coverage

blib/lib/Device/MiniLED.pm
Criterion Covered Total %
statement 466 580 80.3
branch 70 146 47.9
condition 15 48 31.2
subroutine 54 60 90.0
pod 5 5 100.0
total 610 839 72.7


line stmt bran cond sub pod time code
1             #
2             package Device::MiniLED;
3 2     2   67117 use Carp;
  2         4  
  2         169  
4 2     2   14 use strict;
  2         4  
  2         69  
5 2     2   10 use warnings;
  2         15  
  2         70  
6 2     2   44 use 5.005;
  2         7  
  2         465  
7             $Device::MiniLED::VERSION="1.03";
8             #
9             # Shared Constants / Globals
10             #
11             our %EFFECTMAP = (
12             "hold" => 0x41, "scroll" => 0x42,
13             "snow" => 0x43, "flash" => 0x44,
14             "hold+flash" => 0x45
15             );
16             #
17             # Use Win32::Serial port on Windows otherwise, use Device::SerialPort
18             #
19             BEGIN
20             {
21 2 50 33 2   25 my $IS_WINDOWS = ($^O eq "MSWin32" or $^O eq "cygwin") ? 1 : 0;
22             #
23 2 50       9 if ($IS_WINDOWS) {
24 0         0 eval "use Win32::SerialPort 0.14";
25 0 0       0 die "$@\n" if ($@);
26             } else {
27 2     2   285 eval "use Device::SerialPort";
  2         3593  
  2         90805  
  2         245  
28 2 50       4078 die "$@\n" if ($@);
29             }
30             }
31              
32             sub new {
33 1     1 1 657 my $that = shift;
34 1   33     13 my $class = ref($that) || $that;
35 1         25 my(%params) = @_;
36 1         4 my $this = {};
37 1         3 bless $this, $class;
38 1 50       6 if (!defined($params{devicetype})) {
39 0         0 croak("Parameter [devicetype] must be present (sign or badge)");
40 0         0 return undef;
41             }
42 1 50 33     6 if ($params{devicetype} ne "sign" and $params{devicetype} ne "badge") {
43 0         0 croak("Invalue value for [devicetype]: \"$params{devicetype}\"");
44 0         0 return undef;
45             }
46 1         10 $this->{imagefactory}=Device::MiniLED::Factory->new(
47             devicetype=> $params{devicetype}
48             );
49 1         6 $this->{msgfactory}=Device::MiniLED::Factory->new(
50             devicetype=> $params{devicetype}
51             );
52 1         3 $this->{device} = $params{device};
53 1         13 $this->{devicetype} = $params{devicetype};
54 1         3 $this->{refcount}=0;
55 1         5 return $this;
56             }
57             sub _msgfactory {
58 14     14   33 my($this) = shift;
59 14         84 return $this->{msgfactory};
60             }
61             sub _imagefactory {
62 6     6   11 my($this) = shift;
63 6         34 return $this->{imagefactory};
64             }
65             sub addPix {
66 1     1 1 8 my($this) = shift;
67 1         4 my(%params)=@_;
68 1 50       5 if (defined($params{clipart})) {
69 1         9 my $ca=Device::MiniLED::Clipart->new(
70             name => $params{clipart},
71             type => "pix"
72             );
73 1         6 $params{data}=$ca->data();
74 1         4 $params{width}=$ca->width();
75 1         3 $params{height}=$ca->height();
76             }
77 1 50       5 if (!defined($params{data})) {
78 0         0 croak("Parameter [data] must be present");
79 0         0 return undef;
80             }
81 1         5 my $pixobj=$this->_imagefactory->pixmap(
82             data => $params{data},
83             height => $params{height},
84             width => $params{width},
85             devicetype => $this->{devicetype}
86             );
87 1         4 my $pixtag=$pixobj->get_pixtag;
88 1         7 $pixobj->loaddata;
89 1         5 return $pixtag;
90             }
91             sub addIcon {
92 1     1 1 6 my($this) = shift;
93 1         3 my(%params)=@_;
94 1 50       11 if (defined($params{clipart})) {
95 1         9 my $ca=Device::MiniLED::Clipart->new(
96             name => $params{clipart},
97             type => "icon"
98             );
99 1         3 $params{data}=$ca->data();
100             }
101 1 50       4 if (!defined($params{data})) {
102 0         0 croak("Parameter [data] must be present");
103 0         0 return undef;
104             }
105 1         4 my $iconobj=$this->_imagefactory->icon(
106             data => $params{data},
107             devicetype => $this->{devicetype}
108             );
109 1         3 my $icontag=$iconobj->get_icontag;
110 1         6 $iconobj->loaddata;
111 1         6 return $icontag;
112             }
113              
114             sub addMsg {
115 3     3 1 16 my($this) = shift;
116 3         13 my(%params)=@_;
117 3 50       9 if ($this->_msgfactory->{msgcount} >= 8) {
118 0         0 carp("Maximum message count of 8 is already".
119             " reached, discarding new message");
120 0         0 return undef;
121             }
122 3 50       11 if (!defined($params{data})) {
123 0         0 croak("Parameter [data] must be present");
124 0         0 return undef;
125             }
126 3 50       7 if (!defined($params{speed})) {
127 0         0 $params{speed}=4;
128             }
129 3 50       17 if ($params{speed} !~ /^[1-5]$/) {
130 0         0 croak("Parameter [speed] must be between 1 (slowest) and 5 (fastest)");
131 0         0 return undef;
132             }
133             # effect
134 3 50       9 if (!defined($params{effect})) {
135 0         0 $params{effect}="scroll";
136             } else {
137 3         22 my @effects=keys(%Device::MiniLED::EFFECTMAP);
138 3 50       80 if (!grep(/^$params{effect}$/,@effects)) {
139 0         0 croak("Invalid effect value [$params{effect}]");
140 0         0 return undef;
141             }
142             }
143 3 50       10 if (exists($params{slot})) {
144 0 0       0 if ($params{slot} !~ /^[1-8]$/) {
145 0         0 croak("Parameter [slot] must be a value from 1 to 8");
146             } else {
147 0         0 $this->_msgfactory->slot($params{slot});
148             }
149             } else {
150 3         7 $params{slot}=$this->_msgfactory->slot;
151             }
152 3         9 my $mobj=$this->_msgfactory->msg(
153             %params,
154             devicetype => $this->{devicetype},
155             imagefactory =>$this->_imagefactory,
156             );
157 3         13 return $this->_msgfactory->{msgcount};
158             }
159             sub _connect {
160 0     0   0 my $this=shift;
161 0         0 my(%params)=@_;
162 0         0 my $serial;
163 0         0 my $port=$params{device};
164 0         0 my $baudrate=$params{baudrate};
165 0 0 0     0 my $IS_WINDOWS = ($^O eq "MSWin32" or $^O eq "cygwin") ? 1 : 0;
166 0 0       0 if ($IS_WINDOWS) {
167 0         0 $serial = new Win32::SerialPort ($port, 1);
168             } else {
169 0         0 $serial = new Device::SerialPort ($port, 1);
170             }
171 0 0       0 croak("Can't open serial port $port: $^E\n") unless ($serial);
172             # set serial parameters
173 0         0 $serial->baudrate($baudrate);
174 0         0 $serial->parity('none');
175 0         0 $serial->databits(8);
176 0         0 $serial->stopbits(1);
177 0         0 $serial->handshake('none');
178 0         0 $serial->write_settings();
179 0         0 return $serial;
180             }
181             sub send {
182 1     1 1 736 my $this=shift;
183 1         6 my(%params)=@_;
184 1 50       7 if (!defined($params{device})) {
185 0         0 croak("Must supply the device name.");
186 0         0 return undef;
187             }
188 1         3 my $baudrate;
189 1 50       5 if (defined($params{baudrate})) {
190 0         0 my @validrates = qw( 0 50 75 110 134 150 200 300 600
191             1200 1800 2400 4800 9600 19200 38400 57600
192             115200 230400 460800 500000 576000 921600 1000000
193             1152000 2000000 2500000 3000000 3500000 4000000
194             );
195 0 0       0 if (! grep {$_ eq $params{baudrate}} @validrates) {
  0         0  
196 0         0 croak('Invalid baudrate ['.$params{baudrate}.']');
197             } else {
198 0         0 $baudrate=$params{baudrate};
199             }
200             } else {
201 1         3 $baudrate="38400";
202             }
203             # packetdelay is the # of seconds to sleep between sending packets over
204             # the serial port. Can be a floating point number. Default is 0.2 seconds
205 1         2 my $packetdelay;
206 1 50       4 if (defined($params{packetdelay})) {
207 0 0 0     0 if ($params{packetdelay} > 0 &&
208             $params{packetdelay} =~ m#^\d*\.{0,1}\d*$#) {
209 0         0 $packetdelay=$params{packetdelay};
210             } else {
211 0         0 croak('Invalid value ['.$params{packetdelay}
212             . '] for parameter packetdelay');
213             }
214             } else {
215             # anything below this seems to overrun the sign
216 1         2 $packetdelay=0.20;
217             }
218 1         2 my $serial;
219 1 50       14 if (defined $params{debug}) {
220 1         8 $serial=Device::MiniLED::SerialTest->new();
221             } else {
222 0         0 $serial=$this->_connect(
223             device => $params{device},
224             baudrate => $baudrate
225             );
226             }
227             # send an initial null, wakes up the sign
228 1         7 $serial->write(pack("C",0x00));
229             # sleep a short while to avoid overrunning sign
230 1         200540 select(undef,undef,undef,$packetdelay);
231 1         14 my $count=0;
232 1         4 foreach my $msgobj (@{$this->_msgfactory->objects}) {
  1         65  
233             # get the data
234 3         15 $count++;
235 3         73 my @packets=$msgobj->encode(devicetype => $params{devicetype});
236 3         18 foreach my $data (@packets) {
237 12         127 $serial->write($data);
238             # logic so we don't sleep after the last packet
239 12         2407302 select(undef,undef,undef,$packetdelay);
240             # sleep a short while to avoid overrunning sign
241             }
242             }
243 1         14 foreach my $data ($this->_imagefactory->packets()) {
244 2         18 $serial->write($data);
245             # sleep a short while to avoid overrunning sign
246 2         400772 select(undef,undef,undef,$packetdelay);
247             }
248 1         31 my %BITVAL = (
249             1 => 1, 2 => 2,
250             3 => 4, 4 => 8,
251             5 => 16, 6 => 32,
252             7 => 64, 8 => 128
253             );
254 1         4 my $bits=0;
255 1         4 my @slots;
256 1 50       13 if (exists($params{showslots})) {
257             # strip spaces
258 0         0 $params{showslots} =~ s#\s##g;
259 0         0 foreach my $one (split(/\,/,$params{showslots})) {
260 0 0       0 if ($one !~ /^[1-8]$/) {
261 0         0 croak("Invalid value [$one] in parameter [showslots]");
262             } else {
263 0         0 push(@slots,$one);
264             }
265             }
266             } else {
267 1         4 @slots = keys %{$this->_msgfactory->{'msgslots'}};
  1         12  
268             }
269 1         6 foreach my $num (@slots) {
270 3         14 $bits += $BITVAL{$num};
271             }
272 1 50       12 if ($bits != 0) {
273 1         11 my $runit=pack("C*",(0x02,0x33,$bits));
274             #select(undef,undef,undef,$packetdelay);
275 1         10 $serial->write($runit);
276             }
277 1 50       43 if (defined $params{debug}) {
278 1         8 return $serial->dump();
279             }
280             }
281             package Device::MiniLED::Factory;
282 2     2   26 use Carp;
  2         4  
  2         3106  
283             our @CARP_NOT = qw(Device::MiniLED);
284             sub new {
285 2     2   3 my $that = shift;
286 2   33     10 my $class = ref($that) || $that;
287 2         6 my(%params) = @_;
288 2         4 my $this = {};
289 2         4 bless $this, $class;
290 2         7 foreach my $key (keys(%params)) {
291 2         12 $this->{$key}=$params{$key};
292             }
293 2         5 $this->{msgcount}=0;
294 2         4 $this->{imgcount}=0;
295 2         3 $this->{chunkcount}=0;
296 2         5 $this->{chunkcache}={};
297 2         4 $this->{chunks}=[];
298 2         3 $this->{msgslots}=();
299 2         6 $this->{objects}=[];
300 2         11 return $this;
301             }
302             sub slot {
303 3     3   7 my $this=shift;
304 3         4 my $slot;
305 3         3 $slot=shift;
306 3         5 my $return;
307 3 50       7 if (!defined($slot)) {
308             # a slot wasn't specified, so issue the next available
309 3         8 for (1..8) {
310 6 100       21 if (!exists($this->{msgslots}{$_})) {
311 3         8 $this->{msgslots}->{$_}=1;
312 3         5 $return=$_;
313 3         6 last;
314             }
315             }
316             } else {
317 0 0       0 if (exists($this->{msgslots}->{$slot})) {
318 0         0 croak("Slot [$slot] already in use\n");
319             } else {
320 0         0 $this->{msgslots}->{$slot}=1;
321 0         0 $return=$slot;
322             }
323             }
324 3         10 return $return;
325             }
326              
327             sub msg {
328 3     3   5 my $this=shift;
329 3         13 my(%params) = @_;
330 3         14 my $msg=Device::MiniLED::Msg->new(%params, factory => $this);
331 3         8 push(@{$this->{objects}},$msg);
  3         5  
332 3         7 $this->{msgcount}++;
333 3         5 my $msgcount=$this->{msgcount};
334 3         8 $msg->{number}=$this->{msgcount};
335 3         11 return $msg;
336             }
337             sub objects {
338 1     1   221 my $this=shift;
339 1         12 return $this->{objects};
340             }
341              
342             sub pixmap {
343 1     1   2 my $this=shift;
344 1         4 my(%params) = @_;
345 1         11 my $pixmap=Device::MiniLED::Pixmap->new(
346             %params,
347             devicetype => $this->{devicetype},
348             factory => $this
349             );
350 1         1 push(@{$this->{pixobjects}},$pixmap);
  1         3  
351 1         2 $this->{imgcount}++;
352 1         6 $pixmap->{number}=$this->{imgcount};
353 1         8 return $pixmap;
354             }
355             sub icon {
356 1     1   2 my $this=shift;
357 1         3 my(%params) = @_;
358 1         9 my $icon=Device::MiniLED::Icon->new(
359             %params,
360             devicetype => $this->{devicetype},
361             factory => $this
362             );
363 1         2 push(@{$this->{iconobjects}},$icon);
  1         2  
364 1         2 $this->{imgcount}++;
365 1         2 $icon->{number}=$this->{imgcount};
366 1         3 return $icon;
367             }
368             sub store_icontag {
369 1     1   3 my $this=shift;
370 1         2 my $icontag=shift;
371 1         2 my $msgref=shift;
372 1         8 $this->{icontag}{$icontag}=$msgref;
373             }
374             sub icontag_data {
375 1     1   4 my $this=shift;
376 1         3 my $icontag=shift;
377 1 50       9 if (defined($this->{icontag}{$icontag})) {
378 1         6 return $this->{icontag}{$icontag};
379             } else {
380 0         0 return '';
381             }
382             }
383              
384             sub pixobjects {
385 0     0   0 my $this=shift;
386 0         0 return $this->{pixobjects};
387             }
388             sub iconobjects {
389 0     0   0 my $this=shift;
390 0         0 return $this->{iconobjects};
391             }
392             sub add_chunk {
393 2     2   3 my $this=shift;
394 2         15 my %params=@_;
395 2         4 my $chunk = $params{chunk};
396 2         3 my $type = $params{type};
397 2         4 my $return;
398             # if we've seen a chunk like this before, pass back the existing
399             # reference instead of storing a new image
400 2 50       7 if (exists($this->{chunkcache}{$chunk})) {
401 0         0 $return=$this->{chunkcache}{$chunk};
402             } else {
403 2         4 my $sequence=0;
404 2         3 foreach my $thing (@{$this->{chunks}}) {
  2         6  
405 1         2 my $len=length($thing);
406 1 50       6 if ($len > 32) {
407 0         0 $sequence+=2;
408             } else {
409 1         4 $sequence+=1;
410             }
411             }
412 2         4 push(@{$this->{chunks}},$chunk);
  2         6  
413 2         3 my $msgref;
414 2 100       9 if ($type eq "pixmap") {
    50          
415 1         2 $msgref=0x8000+$sequence;
416             } elsif ($type eq "icon") {
417 1         3 $msgref=0xc000+$sequence;
418             } else {
419 0         0 die("argh!\n");
420             }
421 2         6 $return=pack("n",$msgref);
422 2         8 $this->{chunkcache}{$chunk}=$return;
423             }
424 2         11 return($return);
425             }
426             sub packets {
427 1     1   5 my $this=shift;
428 1         3 my $blob=join('',@{$this->{chunks}});
  1         7  
429 1         4 my $length=length($blob);
430             # pad out to an even multiple of 64 bytes
431 1 50       9 if ($length % 64) {
432 1         5 my $paddedsize=$length+64-($length % 64);
433 1         10 $blob=pack("a$paddedsize",$blob);
434             }
435 1         3 my $new=length($blob);
436             # now split into 64 byte pieces, each one it's own packet
437 1         2 my $i;
438             my @packets;
439 1         3 my $count=0x0E00;
440 1         9 foreach my $chunk (unpack("(a64)*",$blob)) {
441 2         5 my $len=length($chunk);
442 2         3 my @tosend;
443 2         4 push(@tosend,0x02,0x31);
444 2         12 my $hcount=sprintf("%04x",$count);
445 2         211 my($start,$end)=(unpack("(a2)*",sprintf("%04x",$count)));
446 2         6 $start=hex($start); $end=hex($end);
  2         4  
447 2         6 push(@tosend,$start,$end);
448 2         23 foreach my $char (split(//,$chunk)) {
449 128         178 push(@tosend,ord($char));
450             }
451 2         30 my @slice=@tosend[1..$#tosend];
452 2         8 my $total;
453 2         4 foreach my $one (@slice) {
454 134         164 $total+=$one;
455             # my $hextotal = sprintf("0x%x",$total);
456             }
457 2         5 my $mod=$total % 256;
458 2         3 push(@tosend,$mod);
459 2         9 my $packed=pack("C*",@tosend);
460 2         5 push(@packets,$packed);
461 2         12 $count+=64;
462             }
463 1         5 return @packets;
464             }
465             sub store_pixtag {
466 1     1   3 my $this=shift;
467 1         2 my $pixtag=shift;
468 1         2 my $msgref=shift;
469 1         7 $this->{pixtag}{$pixtag}=$msgref;
470             }
471             sub pixtag_data {
472 1     1   6 my $this=shift;
473 1         3 my $pixtag=shift;
474 1 50       26 if (defined($this->{pixtag}{$pixtag})) {
475 1         10 return $this->{pixtag}{$pixtag};
476             } else {
477 0         0 return '';
478             }
479             }
480             #
481             # object to hold a message and it's associated data and parameters
482             #
483             package Device::MiniLED::Msg;
484 2     2   15 use Carp;
  2         4  
  2         1991  
485             our @CARP_NOT = qw(Device::MiniLED);
486             sub new {
487 3     3   5 my $that = shift;
488 3   33     31 my $class = ref($that) || $that;
489 3         24 my(%params) = @_;
490 3         6 my $this = {};
491 3         7 bless $this, $class;
492 3         10 foreach my $key (keys(%params)) {
493 21         47 $this->{$key}=$params{$key};
494             }
495 3         11 return $this;
496             }
497             sub factory {
498 0     0   0 my $this = shift;
499 0         0 return $this->{factory};
500             }
501             sub processTags {
502 3     3   9 my $this = shift;
503 3         11 my $type=$this->{devicetype};
504 3         13 my $msgdata=$this->{data};
505             # font tags
506            
507 3         17 my ($normal,$flash);
508 3 50       25 if ($type eq "badge") {
509 0         0 $normal=pack("C*",0xff,0x80);
510 0         0 $flash=pack("C*",0xff,0x81);
511             } else {
512 3         12 $normal=pack("C*",0xff,0x8f);
513 3         9 $flash=pack("C*",0xff,0x8f);
514             }
515 3         24 $msgdata =~ s//$normal/g;
516 3         9 $msgdata =~ s//$flash/g;
517             # icon tags
518 3         9 my $factory=$this->{imagefactory};
519 3         41 while ($msgdata =~ /()/g) {
520 1         8 my $icontag=$1;
521 1         10 my $substitute=$factory->icontag_data($icontag);
522 1         37 $msgdata=~s/$icontag/$substitute/g;
523             }
524             # pix tags
525 3         31 while ($msgdata =~ /()/g) {
526 1         9 my $pixtag=$1;
527 1         8 my $substitute=$factory->pixtag_data($pixtag);
528 1         39 $msgdata=~s/$pixtag/$substitute/g;
529             }
530 3         16 $this->{data}=$msgdata;
531 3         303 return $msgdata;
532             }
533             sub encode {
534 3     3   13 my $this = shift;
535 3         35 my(%params)=@_;
536 3         19 my $number=$this->{number};
537 3         20 my $msgdata=$this->processTags();
538 3         36 my %SPMAP = (
539             1 => 0x31, 2 => 0x32, 3 => 0x33,
540             4 => 0x34, 5 => 0x35
541             );
542 3         17 my $effect=$Device::MiniLED::EFFECTMAP{$this->{effect}};
543              
544 3 50       15 if (! $effect ) {
545 0         0 $effect=0x35;
546             }
547 3         12 my $speed=$SPMAP{$this->{speed}};
548 3 50       15 if (! $speed ) {
549 0         0 $speed=0x35;
550             }
551 3         9 my $alength=length($msgdata);
552 3         27 $msgdata=pack("Z255",$msgdata);
553 3         9 my @encoded;
554             my $end;
555 3         21 my @endmem=(0x00,0x40,0x80,0xc0);
556 3         13 my $slot=$this->{slot};
557 3         15 foreach my $i (0..3) {
558 12         36 my $start=0x06+($slot-1);
559             #my $start=0x06+($number-1);
560 12         17 my $chunk;
561 12 100       37 if ($i == 0) {
562 3         17 $chunk=substr($msgdata,0,60);
563             } else {
564 9         36 my $offset=60+(64*($i-1));
565 9         29 $chunk=substr($msgdata,$offset,64);
566             }
567 12         28 $end=$endmem[$i];
568 12         27 my $csize=length($chunk)+2;
569 12         33 my(@tosend)=(0x02,0x31,$start,$end);
570 12 100       40 if ($i == 0) {
571 3         11 push(@tosend,($speed,0x31,$effect,$alength));
572             }
573 12         193 foreach my $char (split(//,$chunk)) {
574 756         2297 push(@tosend,ord($char));
575             }
576 12         102 my $aend=$#tosend;
577 12         165 my @slice=@tosend[1..$#tosend];
578 12         43 my $total;
579 12         83 foreach my $one (@slice) {
580 804         1721 $total+=$one;
581 804         2430 my $hextotal = sprintf("0x%x",$total);
582             }
583 12         35 my $mod=$total % 256;
584 12         31 my $hmod=sprintf("0x%x",$mod);
585              
586 12         24 push(@tosend,$mod);
587 12         61 my $packed=pack("C*",@tosend);
588 12         98 push(@encoded,$packed);
589             }
590 3         203 return @encoded;
591             }
592             #
593             # parent object for Pixmap and Icon to derive from
594             #
595             #
596             package Device::MiniLED::Image;
597 2     2   15 use Carp;
  2         5  
  2         177  
598 2     2   14 use POSIX qw (ceil);
  2         3  
  2         22  
599             sub new {
600 2     2   4 my $that = shift;
601 2   33     7 my $class = ref($that) || $that;
602 2         5 my(%params) = @_;
603 2         3 my $this = {};
604 2         4 bless $this, $class;
605 2         5 foreach my $key (keys(%params)) {
606 8         18 $this->{$key}=$params{$key};
607             }
608 2         7 return $this;
609             }
610             sub factory {
611 2     2   3 my $this = shift;
612 2         5 return $this->{factory};
613             }
614             sub loaddata {
615 2     2   3 my $this=shift();
616 2         3 my $devicetype=$this->{devicetype};
617 2         4 my $data=$this->{data};
618 2         15 $data=~s/[^01]//g;
619             # set tilesize, width, and height
620 2         2 my $tilesize;my $width;my $height;
  0         0  
621 2 100       14 if ($this->{objtype} eq "pixmap") {
    50          
622 1         2 $width=$this->{width};
623 1         2 $height=$this->{height};
624 1 50       3 if ($devicetype eq "sign") {
    0          
625 1         2 $tilesize=16;
626 1         3 $this->{packformat}="a32"
627             } elsif ($devicetype eq "badge") {
628 0         0 $tilesize=12;
629 0         0 $this->{packformat}="a24"
630             }
631             } elsif ($this->{objtype} eq "icon") {
632 1 50       4 if ($devicetype eq "sign") {
    0          
633 1         2 $tilesize=16;$width=32;$height=16;
  1         1  
  1         2  
634 1         2 $this->{packformat}="a64"
635             } elsif ($devicetype eq "badge") {
636 0         0 $tilesize=12;$width=24;$height=12;
  0         0  
  0         0  
637 0         0 $this->{packformat}="a48"
638             }
639             }
640 2         3 my $length=length($data);
641 2         4 my $expected=$width*$height;
642 2 50       4 if ($length < $width * $height) {
643 0         0 carp("Expected [$expected] bits, got [$length] bits...padding ".
644             "data with zeros");
645 0         0 $data.="0"x($expected-$length);
646             }
647 2 50       7 my $padding=$width%$tilesize?$tilesize-($width % $tilesize):0;
648 2         2 my $newwidth=$width+$padding;
649             # pad the image width to an equal multiple of the tilesize
650 2         52 my $tiles=ceil($width/$tilesize);
651 2         5 my $final;
652 2         6 foreach my $tile (1..$tiles) {
653 3         5 foreach my $row (1..$tilesize) {
654 48         55 my $rowstart=($row-1)*($width);
655 48         54 my $offset=$rowstart+(($tile-1)*$tilesize);
656 48         40 my $chunk;
657 48         48 my $chunkstart=(($tile-1) * $tilesize);
658 48         47 my $chunkend=$chunkstart+($tilesize);
659 48 50       66 if ($row <= $height) {
660 48 50       65 if ($chunkend <= $width) {
661 48         68 $chunk=substr($data,$offset,$tilesize);
662             } else {
663 0         0 $chunk=substr($data,$offset,$width-$chunkstart);
664 0         0 $chunk.="0"x($tilesize-length($chunk));
665             }
666             } else {
667 0         0 $chunk="0"x($tilesize);
668             }
669             #print "chunk [$chunk]\n";
670 48         117 $final.=pack("B16",$chunk);
671             }
672             }
673 2         17 $this->setmsg(data => $final);
674             }
675             sub setmsg {
676 2     2   4 my $this = shift;
677 2         6 my %params = @_;
678 2         5 my $data=$params{data};
679 2         5 my $devicetype=$this->{devicetype};
680 2         4 my $objtype=$this->{objtype};
681 2         4 my $msgref;
682 2         58 my $factory=$this->factory;
683 2         7 my $format=$this->{packformat};
684 2         10 foreach my $chunk (unpack("($format)*",$data)) {
685 2         8 $msgref.=$factory->add_chunk(chunk => $chunk, type => $objtype);
686             }
687 2 100       10 if ($objtype eq "pixmap") {
    50          
688 1         4 $factory->store_pixtag($this->get_pixtag,$msgref);
689             } elsif ($objtype eq "icon") {
690 1         4 $factory->store_icontag($this->get_icontag,$msgref);
691             }
692             }
693             #
694             # object to hold a pixmap and it's associated data and parameters
695             #
696             package Device::MiniLED::Pixmap;
697 2     2   2197 use Carp;
  2         4  
  2         207  
698             our @ISA= qw (Device::MiniLED::Image);
699             our @CARP_NOT = qw(Device::MiniLED);
700 2     2   14 use Carp;
  2         3  
  2         1290  
701             sub new {
702 1     1   2 my $that = shift;
703 1   33     5 my $class = ref($that) || $that;
704 1         4 my %params=@_;
705 1         15 my $this = Device::MiniLED::Image->new(%params);
706 1         3 $this->{'objtype'}='pixmap';
707             # fix - my(%params) = @_;
708 1         2 foreach my $key (keys(%params)) {
709 5         8 $this->{$key}=$params{$key};
710             }
711 1 50       4 if (!defined($this->{height})) {
712 0         0 croak("Height must exist,and be 1 or greater");
713 0         0 return undef;
714             }
715 1 50 33     10 if (defined($this->{height}) && $this->{height} < 1 ) {
716 0         0 croak("Height must be greater than 1");
717 0         0 return undef;
718             }
719 1 50       3 if (!defined($this->{width})) {
720 0         0 croak("Width must exist,and be between 1 and 256");
721 0         0 return undef;
722             }
723 1 50 33     8 if (defined($this->{width}) && ( $this->{width} < 1 or $this->{width} > 256)) {
      33        
724 0         0 croak("Width must be between 1 and 256");
725 0         0 return undef;
726             }
727 1 50       4 if (!defined($this->{data})) {
728 0         0 croak("Parameter [data] must be present");
729 0         0 return undef;
730             }
731 1 50       4 if (!defined($this->{devicetype})) {
732 0         0 croak("Parameter [devicetype] must be present");
733 0         0 return undef;
734             }
735 1         5 return (bless($this,$class));
736             }
737             sub get_pixtag {
738 2     2   4 my $this=shift;
739 2         3 my $number=$this->{number};
740 2         8 return "";
741             }
742             #
743             # object to hold a icon and it's associated data and parameters
744             #
745             package Device::MiniLED::Icon;
746 2     2   15 use Carp;
  2         5  
  2         588  
747             our @ISA= qw (Device::MiniLED::Image);
748             our @CARP_NOT = qw(Device::MiniLED);
749             sub new {
750 1     1   2 my $that = shift;
751 1   33     6 my $class = ref($that) || $that;
752 1         4 my (%params)=@_;
753 1         4 my $this = Device::MiniLED::Image->new(%params);
754 1         3 $this->{'objtype'}='icon';
755 1         2 bless $this, $class;
756 1 50       8 if (!defined($this->{data})) {
757 0         0 croak("Parameter [data] must be present");
758 0         0 return undef;
759             }
760 1 50       3 if (!defined($this->{devicetype})) {
761 0         0 croak("Parameter [devicetype] must be present");
762 0         0 return undef;
763             }
764 1         4 return (bless($this,$class));
765             }
766             sub get_icontag {
767 2     2   3 my $this=shift;
768 2         4 my $number=$this->{number};
769 2         9 return "";
770             }
771             package Device::MiniLED::Clipart;
772 2     2   12 use Carp;
  2         4  
  2         2632  
773             our @CARP_NOT = qw(Device::MiniLED);
774             sub new {
775 3     3   12089 my $that = shift;
776 3   33     18 my $class = ref($that) || $that;
777 3         10 my(%params) = @_;
778 3         5 my $this = {};
779 3         9 bless $this, $class;
780 3 50       16 if (!defined($params{type})) {
781 0         0 croak("Parameter [type] must be supplied, valid values are [pix] or [icon]");
782 0         0 return undef;
783             }
784 3 50 66     15 if ($params{type} ne "pix" and $params{type} ne "icon") {
785 0         0 croak("Parameter [type] invalid, valid values are [pix] or [icon]");
786 0         0 return undef;
787             }
788 3         11 $this->{type}=$params{type};
789 3 100       10 if (defined($params{name})) {
790 2         3 $this->{name}=$params{name};
791 2 50       12 if ( $this->{hashref}=$this->set(name => $params{name}) ) {
792 2         6 return $this;
793             } else {
794 0         0 croak("No clipart named [$params{name}] exists");
795 0         0 return undef;
796             }
797             } else {
798 1         5 return $this;
799             }
800             }
801             sub data {
802 3     3   9 my $this=shift;
803 3         5 my $hashref=$this->{hashref};
804 3         8 my $data=$$hashref{'data'};
805 3         3 my $bits;
806 3         44 foreach my $one (unpack("(A2)*",$data)) {
807 128         276 $bits.=unpack("B8",pack("C",hex($one)));
808             }
809 3         15 my $len=length($bits);
810 3         14 return $bits;
811             }
812             sub width {
813 1     1   2 my $this=shift;
814 1         2 my $hashref=$this->{hashref};
815 1         3 return $$hashref{'width'};
816             }
817             sub height {
818 1     1   2 my $this=shift;
819 1         2 my $hashref=$this->{hashref};
820 1         11 return $$hashref{'height'};
821             }
822             sub hash {
823 3     3   6 my $this=shift;
824 3         6 my %params=@_;
825 3         5 my $name=$params{name};
826 3         163 my %CLIPART_PIX = (
827             zen16 => {
828             width => 16,
829             height => 16,
830             data =>
831             '07e00830100820045c067e02733273327f027f863ffc1ff80ff007e000000000'
832             },
833             zen12 => {
834             width => 12,
835             height => 12,
836             data =>
837             '0e00318040404040f120f860dfe07fc07fc03f800e000000'
838             },
839             cross16 => {
840             width => 16,
841             height => 16,
842             data =>
843             '0100010001000100010002800440f83e04400280010001000100010001000100'
844             },
845             circle16 => {
846             width => 16,
847             height => 16,
848             data =>
849             '07e00ff01ff83ffc7ffe7ffe7ffe7ffe7ffe7ffe3ffc1ff80ff007e000000000'
850             },
851             questionmark12 => {
852             width => 12,
853             height => 12,
854             data =>
855             '1f003f8060c060c061800300060006000600000006000600'
856             },
857             smile12 => {
858             width => 12,
859             height => 12,
860             data =>
861             '0e003180404051408020802091204e40404031800e000000'
862             },
863             phone16 => {
864             width => 16,
865             height => 16,
866             data =>
867             '000000003ff8fffee00ee44ee44e0fe0183017d017d037d8600c7ffc00000000'
868             },
869             rightarrow12 => {
870             width => 12,
871             height => 12,
872             data =>
873             '000000000000010001807fc07fe07fc00180010000000000'
874             },
875             heart12 => {
876             width => 12,
877             height => 12,
878             data =>
879             '000071c08a208420802080204040208011000a0004000000'
880             },
881             heart16 => {
882             width => 16,
883             height => 16,
884             data =>
885             '00000000000000000c6012902108202820081010101008200440028001000000'
886             },
887             square12 => {
888             width => 12,
889             height => 12,
890             data =>
891             'fff0fff0fff0fff0fff0fff0fff0fff0fff0fff0fff0fff0'
892             },
893             handset16 => {
894             width => 16,
895             height => 16,
896             data =>
897             '00003c003c003e0006000600060c065006a0075006503e603c003c0000000000'
898             },
899             leftarrow16 => {
900             width => 16,
901             height => 16,
902             data =>
903             '00000000000004000c001c003ff87ff83ff81c000c0004000000000000000000'
904             },
905             circle12 => {
906             width => 12,
907             height => 12,
908             data =>
909             '0e003f807fc07fc0ffe0ffe0ffe07fc07fc03f800e000000'
910             },
911             questionmark16 => {
912             width => 16,
913             height => 16,
914             data =>
915             '000000000fc01fe0303030303030006000c00180030003000000030003000000'
916             },
917             smile16 => {
918             width => 16,
919             height => 16,
920             data =>
921             '07c01830200840044c648c62800280028002882247c440042008183007c00000'
922             },
923             leftarrow12 => {
924             width => 12,
925             height => 12,
926             data =>
927             '000000000000080018003fe07fe03fe01800080000000000'
928             },
929             rightarrow16 => {
930             width => 16,
931             height => 16,
932             data =>
933             '000000000000008000c000e07ff07ff87ff000e000c000800000000000000000'
934             },
935             music16 => {
936             width => 16,
937             height => 16,
938             data =>
939             '000001000180014001200110011001200100010007000f000f000e0000000000'
940             },
941             phone12 => {
942             width => 12,
943             height => 12,
944             data =>
945             '00007fc0ffe0c060c060ca601f0031802e806ec0c060ffe0'
946             },
947             music12 => {
948             width => 12,
949             height => 12,
950             data =>
951             '000008000c000a0009000880088039007800780070000000'
952             },
953             cross12 => {
954             width => 12,
955             height => 12,
956             data =>
957             '04000400040004000a00f1e00a0004000400040004000000'
958             },
959             handset12 => {
960             width => 12,
961             height => 12,
962             data =>
963             'f000f80018001800180018201b401c801940f880f0000000'
964             },
965             square16 => {
966             width => 16,
967             height => 16,
968             data =>
969             'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
970             },
971             );
972 3         170 my %CLIPART_ICONS=(
973             cross16 => {
974             width => 32,
975             height => 16,
976             data =>
977             '01000100010001000100010001000100010002800280044004400820F83EF01E'.
978             '0440082002800440010002800100010001000100010001000100010001000100'
979             },
980             heart16 => {
981             width => 32,
982             height => 16,
983             data =>
984             '000000000000000000001C70000022880C604104129040242108402420284004'.
985             '2008200810102008101010100820082004400440028002800100010000000000'
986             },
987             leftarrow16 => {
988             width => 32,
989             height => 16,
990             data =>
991             '000000000000000000000000040000000C0004001C000C003FF81C007FF83FF8'.
992             '3FF87FF81C003FF80C001C0004000C0000000400000000000000000000000000'
993             },
994             rightarrow16 => {
995             width => 32,
996             height => 16,
997             data =>
998             '0000000000000000000000000080000000C0008000E000C07FF000E07FF87FF0'.
999             '7FF07FF800E07FF000C000E0008000C000000080000000000000000000000000'
1000             },
1001             handset16 => {
1002             width => 32,
1003             height => 16,
1004             data =>
1005             '000000003C003C003C003C003E003E000600060006000600060C06000650064C'.
1006             '06A006B007500748065006503E603E203C003C003C003C000000000000000000'
1007             },
1008             phone16 => {
1009             width => 32,
1010             height => 16,
1011             data =>
1012             '0000000000003FF83FF8FFFEFFFEE00EE00EE00EE44EE44EE44E04400FE00FE0'.
1013             '1830183017D017D017D017D037D837D8600C600C7FFC7FFC0000000000000000'
1014             },
1015             smile16 => {
1016             width => 32,
1017             height => 16,
1018             data =>
1019             '07C007C01830183020082008400440044C644C648C628C628002800280028002'.
1020             '800290128822983247C44C64400447C4200820081830183007C007C000000000'
1021             },
1022             circle16 => {
1023             width => 32,
1024             height => 16,
1025             data =>
1026             '07E000000FF007E01FF80FF03FFC1FF87FFE3FFC7FFE3FFC7FFE3FFC7FFE3FFC'.
1027             '7FFE3FFC7FFE3FFC3FFC1FF81FF80FF00FF007E007E000000000000000000000'
1028             },
1029             zen16 => {
1030             width => 32,
1031             height => 16,
1032             data =>
1033             '07E00000083007E010080830200410085C0620047E025C0673327E0273327332'.
1034             '7F0273327F867F023FFC7F861FF83FFC0FF01FF807E00FF0000007E000000000'
1035             },
1036             music16 => {
1037             width => 32,
1038             height => 16,
1039             data =>
1040             '0000000001000000018001000140018001200140011001200110011001200110'.
1041             '0100012001000100070007000F000F000F000F000E000E000000000000000000'
1042             },
1043             questionmark16 => {
1044             width => 32,
1045             height => 16,
1046             data =>
1047             '00000000000000000FC000001FE00FC030301FE0303030303030303000600060'.
1048             '00C000C001800180030003000300030000000000030003000300030000000000'
1049             },
1050             square16 => {
1051             width => 32,
1052             height => 16,
1053             data =>
1054             'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'.
1055             'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
1056             },
1057             cross12 => {
1058             width => 24,
1059             height => 12,
1060             data =>
1061             '0400400400400400400400a00a0110f1ee0e'.
1062             '0a01100400a0040040040040040040000000'
1063             },
1064             heart12 => {
1065             width => 24,
1066             height => 12,
1067             data =>
1068             '00000071c0008a200084271c8028a2802842'.
1069             '4044042082081101100a00a0040040000000'
1070             },
1071             leftarrow12 => {
1072             width => 24,
1073             height => 12,
1074             data =>
1075             '0000000000000001000803001807fc3feffc'.
1076             '7fe7fc3fe300180100080000000000000000'
1077             },
1078             rightarrow12 => {
1079             width => 24,
1080             height => 12,
1081             data =>
1082             '000000000000000020010030018ff87fcffc'.
1083             '7feff87fc030018020010000000000000000'
1084             },
1085             handset12 => {
1086             width => 24,
1087             height => 12,
1088             data =>
1089             'f00f00f80f80180180180180180182182194'.
1090             '1b41a81c81d4194188f88f80f00f00000000'
1091             },
1092             phone12 => {
1093             width => 24,
1094             height => 12,
1095             data =>
1096             '0000007fc000ffe7fcc06ffec06c06ca6ca6'.
1097             '1f01f03183182e82e86ec6ecc06c06ffeffe'
1098             },
1099             smile12 => {
1100             width => 24,
1101             height => 12,
1102             data =>
1103             '0e00e0318318404404514514802802802802'.
1104             '9129b24e44444044043183180e00e0000000'
1105             },
1106             circle12 => {
1107             width => 24,
1108             height => 12,
1109             data =>
1110             '0e00003f80e07fc3f87fc3f8ffe7fcffe7fc'.
1111             'ffe7fc7fc3f87fc3f83f80e00e0000000000'
1112             },
1113             zen12 => {
1114             width => 24,
1115             height => 12,
1116             data =>
1117             '0e00003180e0404318404404f12404f86f12'.
1118             'dfef867fcdfe7fc7fc3f87fc0e03f80000e0'
1119             },
1120             music12 => {
1121             width => 24,
1122             height => 12,
1123             data =>
1124             '0000000801000c01800a0140090120088120'.
1125             '088120390740780f00780f00700e00000000'
1126             },
1127             questionmark12 => {
1128             width => 24,
1129             height => 12,
1130             data =>
1131             '1f00003f81e060c3f060c618618618030630'.
1132             '0600600600c00600c00000000600c00600c0'
1133             },
1134             square12 => {
1135             width => 24,
1136             height => 12,
1137             data =>
1138             'ffffffffffffffffffffffffffffffffffff'.
1139             'ffffffffffffffffffffffffffffffffffff'
1140             }
1141            
1142             );
1143 3 100       20 if ($this->{type} eq "icon") {
    50          
1144 1         19 return %CLIPART_ICONS;
1145             } elsif ($this->{type} eq "pix") {
1146 2         53 return %CLIPART_PIX;
1147             }
1148             }
1149             sub list {
1150 0     0   0 my $this=shift;
1151 0         0 my %HASH=$this->hash;
1152 0         0 return keys(%HASH);
1153             }
1154             sub set {
1155 3     3   9 my $this=shift;
1156 3         6 my %params=@_;
1157 3         5 my $name=$params{name};
1158 3         5 my $type=$this->{type};
1159 3         10 my %HASH=$this->hash;
1160 3 50       25 if (exists($HASH{$name})) {
1161 3         44 $this->{hashref}=$HASH{$name};
1162             }
1163             }
1164              
1165             package Device::MiniLED::SerialTest;
1166 2     2   17 use Carp;
  2         12  
  2         759  
1167             sub new {
1168 1     1   3 my $that = shift;
1169 1   33     7 my $class = ref($that) || $that;
1170 1         3 my(%params) = @_;
1171 1         2 my $this = {};
1172 1         2 bless $this, $class;
1173 1         9 $this->{data}='';
1174 1         4 return $this;
1175             }
1176             sub connect {
1177 0     0   0 my $this=shift;
1178 0         0 $this->{data}='';
1179             }
1180             sub write {
1181 16     16   60 my $this=shift;
1182 16         79 for (@_) {
1183 16         182 $this->{data}.=$_;
1184             }
1185             }
1186             sub dump {
1187 1     1   5 my $this=shift;
1188 1         36 return $this->{data};
1189             }
1190              
1191             1;
1192              
1193             =head1 NAME
1194              
1195             Device::MiniLED - send text and graphics to small LED badges and signs
1196            
1197             =head1 VERSION
1198              
1199             Version 1.03
1200              
1201             =head1 SYNOPSIS
1202              
1203             use Device::MiniLED;
1204             my $sign=Device::MiniLED->new(devicetype => "sign");
1205             #
1206             # add a text only message
1207             #
1208             $sign->addMsg(
1209             data => "Just a normal test message",
1210             effect => "scroll",
1211             speed => 4
1212             );
1213             #
1214             # create a picture and an icon from built-in clipart
1215             #
1216             my $pic=$sign->addPix(clipart => "zen16");
1217             my $icon=$sign->addIcon(clipart => "heart16");
1218             #
1219             # add a message with the picture and animated icon we just created
1220             #
1221             $sign->addMsg(
1222             data => "Message 2 with a picture: $pic and an icon: $icon",
1223             effect => "scroll",
1224             speed => 3
1225             );
1226             $sign->send(device => "COM3");
1227              
1228             =head1 DESCRIPTION
1229              
1230             Device::MiniLED is used to send text and graphics via RS232 to our smaller set of LED Signs and badges.
1231              
1232             =head1 CONSTRUCTOR
1233              
1234             =head2 new
1235              
1236             my $sign=Device::MiniLED->new(
1237             devicetype => $devicetype
1238             );
1239             # $devicetype can be either:
1240             # sign - denoting a device with a 16 pixel high display
1241             # badge - denoting a device with a 12 pixel high display
1242              
1243             =head1 METHODS
1244              
1245             =head2 $sign->addMsg
1246              
1247             This family of devices support a maximum of 8 messages that can be sent to the sign. These messages can consist of three different types of content, which can be mixed together in the same message..plain text, pixmap images, and 2-frame anmiated icons.
1248              
1249             The $sign->addMsg method has one required argument, data, It also has three optional arguments: effect, speed, and slot.
1250              
1251             =over 4
1252              
1253             =item
1254             B: (required) The data to be sent to the sign. Plain text, optionally with tags for fonts and $variables that reference pixmap images or animated icons.
1255              
1256             =over
1257              
1258             =item
1259             B: You can insert a font tag to create flashing text. The supported tags are <f:normal> and <f:flash>. On the badges, these tags work as expected. On the signs, either flag is actually just a toggle back and forth from flashing to normal. If you use them in the right order, you won't notice. For example:
1260             $sign->addMsg(
1261             data => "Some flashing text. Neat, right?"
1262             );
1263              
1264             =back
1265              
1266             =item
1267             B: (optional, defaults to "scroll") One of "hold", "scroll", "snow", "flash" or "hold+flash"
1268              
1269             =item
1270             B: (optional, defaults to "4") An integer from 1 to 5, where 1 is the slowest and 5 is the fastest
1271              
1272             =item
1273             B: (optional) An integer from 1 to 8, representing the message slots in the sign. If you don't supply this, it will assign slot numbers automatically, in ascending order.
1274              
1275             =back
1276              
1277             The addMsg method returns a number that indicates how many messages have been created. This may be helpful to ensure you don't try to add a 9th message, which will fail:
1278              
1279             my $sign=Device::MiniLED->new(devicetype => "sign");
1280             for (1..9) {
1281             my $number=$sign->addMsg(
1282             data => "Message number $_",
1283             effect => "scroll",
1284             speed => 5
1285             );
1286             # on the ninth loop, $number will be undef, and a warning will be
1287             # generated
1288             }
1289              
1290             Assigning slots manually
1291              
1292             my $sign=Device::MiniLED->new(devicetype => "sign");
1293             $sign->addMsg(
1294             data => "A msg in slot 3",
1295             slot => 3
1296             );
1297             $sign->addMsg(
1298             data => "A msg in slot 1",
1299             slot => 1
1300             );
1301             $sign->addMsg(
1302             data => "A msg in slot 5",
1303             slot => 5
1304             );
1305             # even though we loaded a message in slot 3, the use of "showslots"
1306             # below means that only the messages in slots 1 and 5 will be displayed
1307             $sign->send(
1308             device => "/dev/ttyUSB0",
1309             showslots => "1,5"
1310             );
1311             # sleep for a minute...
1312             sleep(60);
1313             # now we'll have the sign show just what's in slot number 3.
1314             $sign->send(
1315             device => "/dev/ttyUSB0",
1316             showslots => "3"
1317             );
1318             #
1319             # note: if the sign already has messages in a slot, you can have a script
1320             # that does nothing other than $sign->send (with the showslots parameter)
1321             # to select which of them to display on the sign.
1322             #
1323             # for example, you could preload messages in slots 1 through 7, with message
1324             # 1 being "Happy Monday", 2 being "Happy Tuesday", and so forth.
1325             # Then, on monday morning, your script could send:
1326             # run $sign->send(device => 'COM1', showslots => "1"), and just the monday
1327             # message would display on the sign
1328             #
1329             # For unknown reasons, however, the message slot selection buttons on the
1330             # sign itself won't show stored messages. They are there, and will be
1331             # displayed if you use the showslots parameter in $sign->send.
1332             #
1333              
1334             =head2 $sign->addPix
1335              
1336             The addPix method allow you to create simple, single color pixmaps that can be inserted into a message. There are two ways to create a picture.
1337              
1338             B
1339              
1340             #
1341             # load the built-in piece of clipart named phone16
1342             # the "16" is hinting that it's 16 pixels high, and thus better suited to
1343             # a 16 pixel high device, and not a 12 pixel high device
1344             #
1345             my $pic=$sign->addPix(
1346             clipart => "phone16"
1347             );
1348             # now use that in a message
1349             $sign->addMsg(
1350             data => "here is a phone: $pic",
1351             );
1352              
1353             B
1354              
1355             To supply your own pictures, you need to supply 3 arguments:
1356              
1357             B: height of the picture in pixels
1358              
1359             B: width of the picture in pixels (max is 256)
1360              
1361             B : a string of 1's and 0's, where the 1 will light up the pixel and
1362             a 0 won't. You might find Image::Pbm and it's $image->as_bitstring method
1363             helpful in generating these strings.
1364              
1365             # make a 5x5 pixel outlined box
1366             my $pic=$sign->addPix(
1367             height => 5,
1368             width => 5,
1369             data =>
1370             "11111".
1371             "10001".
1372             "10001".
1373             "10001".
1374             "11111"
1375             );
1376             # now use that in a message
1377             $sign->addMsg(
1378             data => "here is a 5 pixel box outline: $pic",
1379             );
1380              
1381              
1382             =head2 $sign->addIcon
1383              
1384             The $sign->addIcon method is almost identical to the $sign->addPix method.
1385             The addIcon method accepts either a 16x32 pixel image (for signs), or a
1386             12x24 pixel image (for badges). It internally splits the image down the middle
1387             into a left and right halves, each one being 16x16 (or 12x12) pixels.
1388              
1389             Then, when displayed on the sign, it alternates between the two, in place,
1390             creating a simple animation.
1391              
1392             # make an icon using the built-in heart16 clipart
1393             my $icon=$sign->addIcon(
1394             clipart => "heart16"
1395             );
1396             # now use that in a message
1397             $sign->addMsg(
1398             data => "Animated heart icon: $icon",
1399             );
1400              
1401             You can "roll your own" icons as well.
1402              
1403             # make an animated icon that alternates between a big box and a small box
1404             my $sign=Device::MiniLED->new(devicetype => "sign");
1405             my $icon16x32=
1406             "XXXXXXXXXXXXXXXX" . "----------------" .
1407             "X--------------X" . "----------------" .
1408             "X--------------X" . "--XXXXXXXXXXX---" .
1409             "X--------------X" . "--X---------X---" .
1410             "X--------------X" . "--X---------X---" .
1411             "X--------------X" . "--X---------X---" .
1412             "X--------------X" . "--X---------X---" .
1413             "X--------------X" . "--X---------X---" .
1414             "X--------------X" . "--X---------X---" .
1415             "X--------------X" . "--X---------X---" .
1416             "X--------------X" . "--X---------X---" .
1417             "X--------------X" . "--X---------X---" .
1418             "X--------------X" . "--X---------X---" .
1419             "X--------------X" . "--XXXXXXXXXXX---" .
1420             "X--------------X" . "----------------" .
1421             "XXXXXXXXXXXXXXXX" . "----------------";
1422             # translate X to 1, and - to 0
1423             $icon16x32=~tr/X-/10/;
1424             # no need to specify width or height, as
1425             # it assumes 16x32 if $sign is devicetype "sign",
1426             # and assumes 12x24 if $sign
1427             my $icon=$sign->addIcon(
1428             data => $icon16x32
1429             );
1430             $sign->addMsg(
1431             data => "Flashing Icon: [$icon]"
1432             );
1433              
1434             =head2 $sign->send
1435              
1436             The send method connects to the sign over RS232 and sends all the data accumulated from prior use of the $sign->addMsg method. The only mandatory argument is 'device', denoting which serial device to send to.
1437              
1438             It supports three optional arguments, showslots, baudrate and packetdelay:
1439              
1440             =over 4
1441              
1442             =item
1443             B:
1444             A string that is a comma separated list of the slot numbers that you want to display on the sign. If you omit this, it will display the messages you just added with addMsg. If you supply a null string, then the sign will continue to display whatever slots it is currently displaying.
1445              
1446             =item
1447             B:
1448             defaults to 38400, no real reason to use something other than the default, but it's there if you feel the need. Must be a value that Device::Serialport or Win32::Serialport thinks is valid
1449              
1450             =item
1451             B: An amount of time, in seconds, to wait, between sending packets to the sign. The default is 0.20. If you see "XX" displayed on your sign while sending data, increasing this value may help. Must be greater than zero.
1452              
1453             =over
1454              
1455             =item
1456             B: For reference, each text message generates 3 packets, and each 16x32 portion of an image sends one packet. There's also an additional, short, packet sent after all message and image packets are delivered. So, if you make packetdelay a large number...and have lots of text and/or images, you may be waiting a while to send all the data. Similarly, you may get some milage out of using a number smaller than the default, provided you don't see 'XX' displayed on the sign while sending.
1457              
1458             =back
1459              
1460             =back
1461              
1462             Examples of using $sign->send
1463              
1464             # typical use on a windows machine
1465             $sign->send(
1466             device => "COM4"
1467             );
1468             # typical use on a unix/linux machine
1469             $sign->send(
1470             device => "/dev/ttyUSB0"
1471             );
1472             # using optional arguments, set baudrate to 9600, and sleep 1/2 a second
1473             # between sending packets.
1474             $sign->send(
1475             device => "COM8",
1476             baudrate => "9600",
1477             packetdelay => 0.5
1478             );
1479              
1480             Note that if you have multiple connected signs, you can send to them without creating a new object:
1481              
1482             # send to the first sign
1483             $sign->send(device => "COM4");
1484             # send to another sign
1485             $sign->send(device => "COM6");
1486             # send to a badge connected on COM7
1487             # this works fine for plain text, but won't work well for
1488             # pictures and icons...you'll have to create a new
1489             # sign object with devicetype "badge" for them to render correctly
1490             $sign->send(device => "COM7");
1491            
1492             Using the showslots parameter. Also see the "slot" parameter under L<< /"$sign->addMsg" >>.
1493              
1494              
1495             #
1496             # showslots is a string, with the numbers of the messages you want
1497             # displayed separated by commas
1498             #
1499             $sign->send(device => "/dev/ttyUSB0",
1500             showslots => "1,5,7"
1501             );
1502              
1503             =head1 AUTHOR
1504              
1505             Kerry Schwab, C<< >>
1506              
1507             =head1 SUPPORT
1508              
1509             You can find documentation for this module with the perldoc command.
1510            
1511             perldoc Device::MiniSign
1512            
1513             Other links that may be helpful:
1514              
1515             =over
1516              
1517             =item *
1518             Our website: L
1519              
1520             =item *
1521             Our L
1522              
1523             =item *
1524             The signs that work with this api are L. They are the first three shown, the badge, the "micro sign" and the "mini sign".
1525              
1526             =back
1527            
1528             =head1 BUGS
1529              
1530             Please report any bugs or feature requests to
1531             C, or through the web interface at
1532             L. I will be notified, and then you'll automatically be
1533             notified of progress on your bug as I make changes.
1534              
1535             =head1 TODO
1536              
1537             =over
1538              
1539             =item
1540             B: The signs only natively support one line of text, but they do support uploading and replacing the native font. The native font that comes with the sign is 12 pixels tall, I suppose to allow for the effect that outlines the text in a box. A 15 or 16 pixel font, however, would be much more visible.
1541              
1542             =item
1543             B: If we provided a way to render smaller fonts, like a standard 5x7 LED font, into a pixmap, you could present two lines of text on the sign, abeit, only as a picture via addPix.
1544              
1545             =item
1546             B: Need a better module structure that supports other models of LED signs that use a different protocol. Like LEDSign::Mini LEDSign:OtherModel, etc.
1547              
1548             =item
1549             B: I'm much better with Perl, but it's not as popular as it used to be. Porting to python might open up a wider user base for the signs.
1550              
1551             =back
1552              
1553             =head1 ACKNOWLEDGEMENTS
1554              
1555             I was able to leverage some existing work I found, though none of these examples reverse engineered the protocol to the same degree that we've done in this API. Here's what I found:
1556              
1557             =over 4
1558              
1559             =item * L - Some code samples for different types of LED badges. The last one, "Badge Three", uses the same protocol we're targeting here.
1560              
1561             =item * L - Ruby library that appears to be targeting led badges with a very similar protocol.
1562              
1563             =item * L - A game, written in C#, that uses LED badges. Also has some protocol information and C# code. Targeting the same type of signs/badges.
1564              
1565             =item * L - Python code, again, using the same protocol.
1566              
1567             =back
1568              
1569             Other Cpan modules related to Led Signs
1570              
1571             =over
1572              
1573             =item * L - The only other CPAN perl module I could find that does something similar, albeit for a different type of sign.
1574              
1575             =back
1576              
1577             =head1 LICENSE AND COPYRIGHT
1578              
1579             Copyright 2013 Kerry Schwab.
1580              
1581             This program is free software; you can redistribute it and/or modify it
1582             under the terms of the the Artistic License (2.0). You may obtain a
1583             copy of the full license at:
1584              
1585             L
1586              
1587             Aggregation of this Package with a commercial distribution is always
1588             permitted provided that the use of this Package is embedded; that is,
1589             when no overt attempt is made to make this Package's interfaces visible
1590             to the end user of the commercial distribution. Such use shall not be
1591             construed as a distribution of this Package.
1592              
1593             The name of the Copyright Holder may not be used to endorse or promote
1594             products derived from this software without specific prior written
1595             permission.
1596              
1597             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
1598             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
1599             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1600              
1601              
1602             =cut