File Coverage

blib/lib/Device/Hypnocube.pm
Criterion Covered Total %
statement 108 380 28.4
branch 0 76 0.0
condition 0 76 0.0
subroutine 36 68 52.9
pod 20 23 86.9
total 164 623 26.3


line stmt bran cond sub pod time code
1             # ABSTRACT: Control a hypnocube
2              
3              
4             package Device::Hypnocube;
5             $Device::Hypnocube::VERSION = '1.9';
6 1     1   26649 use 5.010;
  1         5  
  1         39  
7 1     1   6 use strict;
  1         3  
  1         31  
8 1     1   5 use warnings;
  1         7  
  1         37  
9 1     1   906 use Moo;
  1         23619  
  1         8  
10 1     1   21755 use Time::HiRes qw( gettimeofday usleep);
  1         2043  
  1         5  
11 1     1   1156 use WebColors;
  1         2159  
  1         97  
12              
13             # get the crc stuff, this is the function we need
14 1     1   903 use Digest::CRC qw( crcccitt );
  1         5747  
  1         87  
15 1     1   875 use Data::Hexdumper;
  1         2083  
  1         59  
16              
17             # the bit that does the actual serial comms
18 1     1   680 use Device::Hypnocube::Serial;
  1         4  
  1         38  
19 1     1   1208 use Path::Tiny;
  1         14012  
  1         64  
20 1     1   636 use YAML::XS qw( Load Dump);
  1         2512  
  1         49  
21 1     1   7 use Try::Tiny;
  1         1  
  1         47  
22              
23 1     1   5 use constant HYPNOCUBE_SYNC => 0xc0;
  1         2  
  1         34  
24 1     1   5 use constant HYPNOCUBE_ESC => 0xdb;
  1         1  
  1         32  
25 1     1   3 use constant HYPNOCUBE_LAST_PKT => 0x60;
  1         2  
  1         40  
26 1     1   4 use constant HYPNOCUBE_NEXT_PKT => 0x40;
  1         1  
  1         31  
27 1     1   4 use constant HYPNOCUBE_CHALLENGE => 0xabadc0de;
  1         1  
  1         35  
28 1     1   3 use constant HYPNOCUBE_MAX_PACKET => 50; # max length of a packet to send
  1         2  
  1         41  
29              
30             # these are the commands we can send to the device
31 1     1   3 use constant HYPNOCUBE_LOGIN => 0;
  1         2  
  1         36  
32 1     1   3 use constant HYPNOCUBE_LOGOUT => 1;
  1         2  
  1         34  
33 1     1   4 use constant HYPNOCUBE_RESET => 10;
  1         1  
  1         27  
34 1     1   4 use constant HYPNOCUBE_INFO => 11;
  1         1  
  1         33  
35 1     1   8 use constant HYPNOCUBE_VERS => 12;
  1         2  
  1         32  
36 1     1   4 use constant HYPNOCUBE_ERR => 20;
  1         1  
  1         47  
37 1     1   9 use constant HYPNOCUBE_ACK => 25;
  1         1  
  1         34  
38 1     1   4 use constant HYPNOCUBE_PING => 60;
  1         2  
  1         33  
39 1     1   4 use constant HYPNOCUBE_FLIP => 80;
  1         1  
  1         37  
40 1     1   4 use constant HYPNOCUBE_FRAME => 81;
  1         1  
  1         30  
41 1     1   4 use constant HYPNOCUBE_PIXEL => 81;
  1         1  
  1         33  
42              
43 1     1   3 use constant X_SIZE => 4;
  1         2  
  1         38  
44 1     1   3 use constant Y_SIZE => 4;
  1         6  
  1         28  
45 1     1   4 use constant Z_SIZE => 4;
  1         1  
  1         54  
46 1     1   4 use constant BUFFER_SIZE => X_SIZE * Y_SIZE * Z_SIZE;
  1         1  
  1         30  
47 1     1   4 use constant DEFAULT_COLOR => 'purple';
  1         1  
  1         32  
48              
49 1     1   4 use constant RATE_LIMIT_MSECS => 3333; # 1/30 * 1e6
  1         1  
  1         35  
50              
51             # where we will save the buffer between runs
52 1     1   4 use constant BUFFER_FILE => '/tmp/hypnocube.buffer';
  1         2  
  1         3846  
53              
54             my %errors = (
55             0 => 'no error',
56             1 => 'timeout ‐ too long of a delay between packets',
57             2 => 'missing packet, followed by missing sequence number',
58             3 => 'invalid checksum',
59             4 => 'invalid type (2 and 3 defined for now)',
60             5 => 'invalid sequence counter',
61             6 => 'missing SYNC ‐ SYNC out of order (2 SYNC in a row, for example)',
62             7 => 'invalid packet length',
63             8 => 'invalid command',
64             9 => 'invalid data (valid command)',
65             10 => 'invalid ESC sequence ‐ illegal byte after ESC byte',
66             11 => 'overflow ‐ too much data was fed in with the packets',
67             12 => 'command not implemented (in case command deliberately no allowed)',
68             13 => 'invalid login value'
69             );
70              
71             my %colors = (
72              
73             # colors are generated in BUILDARGS
74             );
75             my @color_names;
76              
77             # ----------------------------------------------------------------------------
78             # instance initialisation
79             # ----------------------------------------------------------------------------
80              
81             has 'error_info' => (
82             is => 'ro'
83              
84             # , isa => 'HashRef'
85             , init_arg => undef # prevent setting this in initialisation
86             , writer => '_set_error_info' # we want to be able to set this in this module only
87             );
88              
89             has 'login_state' => (
90             is => 'ro'
91              
92             # , isa => 'Integer'
93             , init_arg => undef # prevent setting this in initialisation
94             ,
95             default => sub {0},
96             writer => '_set_login_state' # we want to be able to set this in this module only
97             );
98              
99             has 'device_info' => (
100             is => 'ro'
101              
102             # , isa => 'HashRef'
103             , init_arg => undef # prevent setting this in initialisation
104             ,
105             predicate => 'has_info',
106             clearer => '_clear_info',
107             writer => '_set_device_info' # we want to be able to set this in this module only
108             );
109              
110             # get _debug info out
111             has 'verbose' => (
112             is => 'rw'
113              
114             # , isa => 'Integer'
115             ,
116             default => sub {0}
117             );
118              
119             has 'buffer' => (
120             is => 'ro'
121              
122             # , isa => 'ArrayRef'
123             , init_arg => undef # prevent setting this in initialisation
124             ,
125             default => sub { [] },
126             writer => '_set_buffer'
127             );
128              
129             # get the time as a float, including the microseconds
130             has 'last_rate_limit' => (
131             is => 'rw'
132              
133             # , isa => 'Float'
134             , init_arg => undef # prevent setting this in initialisation
135             ,
136             default => sub { my ( $t, $u ) = gettimeofday(); $t + ( $u / 1000000 ); },
137             writer => '_set_last_rate_limit'
138             );
139              
140             # ----------------------------------------------------------------------------
141             # special method called BEFORE the class is properly instanced
142             # we can modify passed params if needed
143             around BUILDARGS => sub {
144             my $orig = shift;
145             my $class = shift;
146             my $opt = @_ % 2 ? die("Odd number of values passed where even is expected.") : {@_};
147              
148             # here we can extract and extra args we want to process but do not have
149             # object variables for
150              
151             # add in the web_colors
152             # we reduce them by 75% as the LEDs are not that accurate
153             foreach my $c ( list_webcolors() ) {
154             my ( $r, $g, $b ) = colorname_to_rgb($c);
155              
156             # reduce them as they are too bright
157             $colors{$c} = [ int( $r / 4 ), int( $g / 4 ), int( $b / 4 ) ];
158             }
159              
160             # add extra color names for primaries and close relatives
161             my @prefix = ( "dark", "mid", "", "bright" );
162             for ( my $i = 0; $i <= 3; $i++ ) {
163             my $c = ( 64 * ( $i + 1 ) );
164             $c = $c >= 256 ? 255 : $c;
165             my $p = $prefix[$i];
166             $colors{ $p . "red" } = [ $c, 0, 0 ];
167             $colors{ $p . "green" } = [ 0, $c, 0 ];
168             $colors{ $p . "blue" } = [ 0, 0, $c ];
169              
170             $colors{ $p . "magenta" } = [ $c, 0, $c ];
171             $colors{ $p . "yellow" } = [ $c, $c, 0 ];
172             $colors{ $p . "cyan" } = [ 0, $c, $c ];
173             $colors{ $p . "white" } = [ $c, $c, $c ];
174             }
175              
176             # define some colors by hand as we want them more vibrant or they
177             # are not quite right in WebColors
178              
179             $colors{lilac} = [ 0xf0, 0, 0xf0 ];
180             $colors{orange} = [ 0xf0, 0x20, 0 ];
181             $colors{amber} = [ 0xf0, 0x20, 0 ];
182             $colors{warmwhite} = [ 0xa0, 0xa0, 0xa0 ];
183             $colors{lightpurple} = [ 0x40, 0, 0x40 ];
184             $colors{pink} = [ 0xf0, 0x00, 0x20 ];
185              
186             @color_names = keys %colors;
187              
188             # now build the class properly
189             return $class->$orig(@_);
190             };
191              
192             # ----------------------------------------------------------------------------
193              
194              
195             sub BUILD {
196 0     0 0   my $self = shift;
197 0           my $args = shift;
198              
199             # add the serial port if it was passed to us
200 0 0         if ( $args->{serial} ) {
201              
202             # this should connect too
203 0           $self->{serial} = Device::Hypnocube::Serial->new($args);
204             }
205             else {
206 0           die "serial argument is required";
207             }
208             }
209              
210             # ----------------------------------------------------------------------------
211             # DEMOLISH
212             # called to as part of destroying the object
213              
214             sub DEMOLISH {
215 0     0 0   my $self = shift;
216             }
217              
218             # ----------------------------------------------------------------------------
219             # instance variables and handlers
220             # some of these are the things being $self->send_data( HYPNOCUBE_ERR, 0)assed to new
221             # ----------------------------------------------------------------------------
222              
223             sub set_error {
224 0     0 0   my $self = shift;
225 0           my $code = shift;
226              
227 0           my $errmsg = { code => $code, error => $errors{$code} };
228 0           $self->_set_error_info($errmsg);
229              
230 0           $self->_debug("error: $errors{$code}");
231             }
232              
233             # ----------------------------------------------------------------------------
234              
235              
236             sub ping {
237 0     0 1   my $self = shift;
238              
239 0           $self->_debug('ping');
240              
241             # if something is doing something with the serial thats as good as a ping
242 0 0         return if ( $self->{serial}->{activity} );
243              
244             # no response possible from a ping, but then again there may be!
245 0           $self->send_data( HYPNOCUBE_PING, '', 1 );
246             }
247              
248             # ----------------------------------------------------------------------------
249              
250              
251             sub login {
252 0     0 1   my $self = shift;
253              
254 0           $self->_debug('login');
255              
256             # no need to login again
257 0 0         return if ( $self->login_state() );
258              
259 0           my $resp = $self->send_data( HYPNOCUBE_LOGIN, pack( 'N', HYPNOCUBE_CHALLENGE ) );
260              
261 0 0 0       if ( $resp->{cmd} == HYPNOCUBE_ACK || ( $resp->{cmd} == HYPNOCUBE_ERR && $self->error_info->{code} == 0 ) ) {
      0        
262 0           $self->_set_login_state(1);
263              
264             # $self->info() ; # update the info
265 0           my $hashref;
266 0 0         if ( -f BUFFER_FILE ) {
267 0           $hashref = Load( path(BUFFER_FILE)->slurp );
268             }
269              
270             # use the buffer otherwise clear to black
271 0 0         if ($hashref) {
272 0           $self->_set_buffer($hashref);
273 0           $self->update();
274             }
275             else {
276 0           $self->clear('black');
277 0           $self->update();
278             }
279             }
280             else {
281 0           $self->_debug( "resp " . $resp->{cmd} . " " . HYPNOCUBE_ERR . " code " . $self->error_info->{code} );
282             }
283             }
284              
285             # ----------------------------------------------------------------------------
286              
287              
288             sub logout {
289 0     0 1   my $self = shift;
290              
291 0           $self->_debug('logout');
292              
293             # dont logout if we are not logged in
294 0 0         return if ( !$self->login_state() );
295              
296             # don't wait for a response
297 0           my $resp = $self->send_data( HYPNOCUBE_LOGOUT, '', 1 );
298              
299 0           $self->_set_login_state(0);
300              
301             # and dump what we know about the device
302 0           $self->_clear_info();
303             }
304              
305             # ----------------------------------------------------------------------------
306              
307              
308             sub info {
309 0     0 1   my $self = shift;
310 0           my %info = ();
311              
312 0 0         return $self->device_info() if ( $self->has_info() );
313 0           $self->_debug('info');
314              
315 0           my $resp = $self->send_data( HYPNOCUBE_INFO, pack( 'CC', 0, 0 ) );
316 0           $info{name} = $resp->{payload};
317 0           $resp = $self->send_data( HYPNOCUBE_INFO, pack( 'CC', 0, 1 ) );
318 0           $info{desc} = $resp->{payload};
319 0           $resp = $self->send_data( HYPNOCUBE_INFO, pack( 'CC', 0, 2 ) );
320 0           $info{copyright} = $resp->{payload};
321 0           $resp = $self->send_data( HYPNOCUBE_VERS, '' );
322              
323 0           ( $info{hw_major}, $info{hw_minor}, $info{sw_major}, $info{sw_minor}, $info{proto_major}, $info{proto_minor} ) = unpack( 'CCCCCC', $resp->{payload} );
324              
325             # set the info
326 0           $self->_set_device_info( \%info );
327              
328 0           return \%info;
329             }
330              
331             # ----------------------------------------------------------------------------
332              
333              
334             sub reset {
335 0     0 1   my $self = shift;
336              
337 0           $self->_debug('reset');
338              
339 0           $self->send_data( HYPNOCUBE_RESET, '', 1 );
340             }
341              
342             # ----------------------------------------------------------------------------
343              
344              
345             sub last_error {
346 0     0 1   my $self = shift;
347              
348 0           $self->_debug('last_error');
349              
350 0           my $resp = $self->send_data( HYPNOCUBE_ERR, 0 );
351              
352 0           $self->set_error( unpack( 'C', $resp->{payload} ) );
353              
354             # and reset the error
355 0           $self->send_data( HYPNOCUBE_ERR, -2 );
356             }
357              
358             # ----------------------------------------------------------------------------
359             # _ack
360             # tell the device we got the data
361              
362             sub _ack {
363 0     0     my $self = shift;
364              
365 0           $self->_debug('_ack');
366              
367 0           my $resp = $self->send_data( HYPNOCUBE_ACK, '' );
368             }
369              
370             # ----------------------------------------------------------------------------
371             # _rate_limit
372             # make sure that we do not send data too quickly, will pause before allowing
373             # more things to be sent
374              
375             sub _rate_limit {
376 0     0     my $self = shift;
377              
378             # get current time
379 0           my ( $seconds, $microseconds ) = gettimeofday;
380              
381             # easier to play with as a float
382 0           my $ftime = $seconds + ( $microseconds / 1000000 );
383 0           my $lasttime = $self->last_rate_limit();
384              
385             # calc in big microsecs the time elapse since last time
386 0           my $elapsed = ( $ftime - $lasttime ) * 1000000;
387              
388             # if we need to pause to make up the time, do it now
389 0 0         if ( $elapsed < RATE_LIMIT_MSECS ) {
390 0           my $pause = RATE_LIMIT_MSECS - $elapsed;
391 0           usleep($pause);
392             }
393              
394             # update the last update with now
395 0           $self->_set_last_rate_limit($ftime);
396             }
397              
398             # ----------------------------------------------------------------------------
399             # _get_response
400             # read stuff from the device
401              
402             sub _get_response {
403 0     0     my $self = shift;
404 0           my %packet = ();
405              
406             # if something is doing something wait till its over
407 0           while ( $self->{serial}->{activity} ) {
408 0           sleep(1);
409             }
410              
411             # we read and discard till we get a sync frame
412 0           my $tmp = '';
413 0           while (1) {
414 0           my $r = $self->{serial}->read(1);
415 0 0         if ( !$r ) {
416 0           sleep 1;
417             }
418             else {
419 0           my $c = unpack( 'C', $r );
420 0 0         if ( $c == HYPNOCUBE_SYNC ) {
421 0           $packet{sync_head} = $c;
422 0           last;
423             }
424 0           $tmp .= $r;
425             }
426             }
427              
428 0           $packet{type} = unpack( 'C', $self->{serial}->read(1) );
429 0           $packet{length} = unpack( 'C', $self->{serial}->read(1) );
430 0           $packet{dest} = unpack( 'C', $self->{serial}->read(1) );
431              
432             # split type into sequence and type
433 0           $packet{sequence} = $packet{type} & 0x1f;
434 0           $packet{type} = $packet{type} & 0xe0;
435              
436 0           my $payload_fmt = 'C' x $packet{length};
437 0           $packet{cmd} = unpack( $payload_fmt, $self->{serial}->read(1) );
438              
439             # payload is not unpacked the caller will have to do that
440 0           $packet{payload} = $self->{serial}->read( $packet{length} - 1 );
441 0           $packet{chksum} = unpack( 'n', $self->{serial}->read(2) );
442 0           $packet{sync_tail} = unpack( 'C', $self->{serial}->read(1) );
443              
444 0 0         if ( $packet{cmd} == HYPNOCUBE_ERR ) {
445 0           $self->set_error( unpack( 'C', $packet{payload} ) );
446             }
447             else {
448 0           $self->set_error(0);
449             }
450              
451 0           return \%packet;
452             }
453              
454             # ----------------------------------------------------------------------------
455             # _build_packet
456             # build a packet to send to the device, the payload should already be in the right
457             # format, ie packed
458              
459             sub _build_packet {
460 0     0     my $self = shift;
461 0           my ( $payload, $seq, $type ) = @_;
462 0           my $sync = pack( 'C', HYPNOCUBE_SYNC );
463              
464 0           $seq %= 31; # sequence count wraps at 32
465              
466 0           $self->_debug( "_build_packet\n" . hexdump( data => $payload, suppress_warnings => 1 ) );
467 0           my $plen = length($payload);
468 0           my $payload_fmt = 'C' x $plen;
469              
470             # create the header, then add the data
471             # top 3 bits show 224 end packet, 128 not last packet
472             # next 5 bits show packet sequence number
473 0 0         my $out = pack( 'C', ( $type ? HYPNOCUBE_LAST_PKT : HYPNOCUBE_NEXT_PKT ) + ( $seq & 0x1f ) ) . pack( 'C', $plen ) # length
474             . pack( 'C', 0 ) # broadcast
475             . $payload; # already packed by caller
476              
477             # get the crc on everything so far
478 0           my $crc = crcccitt($out);
479              
480             # add crc onto end
481 0           $out .= pack( 'n', $crc );
482              
483             # now fixup the data so that it has bits replaced SYNC for ESC
484             # ESC for ESC ESC
485 0           my $newdata = '';
486 0           my $fmt = 'C' x length($out);
487 0           my $count = 0;
488 0           for ( my $offset = 0; $offset < length($out); $offset++ ) {
489 0           my $c = unpack( 'C', substr( $out, $offset, 1 ) );
490 0 0         if ( $c == HYPNOCUBE_SYNC ) {
    0          
491 0           $newdata .= pack( 'CC', HYPNOCUBE_ESC, HYPNOCUBE_ESC + 1 );
492             }
493             elsif ( $c == HYPNOCUBE_ESC ) {
494 0           $newdata .= pack( 'CC', HYPNOCUBE_ESC,, HYPNOCUBE_ESC + 2 );
495             }
496             else {
497 0           $newdata .= pack( 'C', $c );
498             }
499             }
500              
501             # replace out with the fixedup data add in crc then
502             # wrap the sync framing bytes around the packet
503 0           $out = $sync . $newdata . $sync;
504              
505 0           $self->_debug( "packet $seq\n" . hexdump( data => $out, suppress_warnings => 1 ) );
506              
507 0           return $out;
508             }
509              
510             # ----------------------------------------------------------------------------
511              
512              
513             sub send_data {
514 0     0 1   my $self = shift;
515 0           my ( $cmd, $data, $noresp ) = @_;
516 0           $self->_debug( 'send_data cmd ' . $cmd );
517 0 0         $self->_debug( "data\n" . hexdump( data => $data, suppress_warnings => 1 ) ) if ($data);
518              
519 0 0 0       if ( !defined $cmd && !defined $data ) {
520 0           $self->_debug('no command specified');
521 0           return {};
522             }
523              
524 0   0       $data ||= '';
525 0           my $seq = 0;
526              
527             # make sure we do not send data too quickly
528 0           $self->_rate_limit();
529              
530             # add the command to send onto the front of the data
531 0           $data = pack( 'C', $cmd ) . $data;
532 0           my $last_packet_flag = 0;
533 0           while ( !$last_packet_flag ) {
534 0           my $size = length($data);
535 0 0         if ( $size > HYPNOCUBE_MAX_PACKET ) {
536 0           $size = HYPNOCUBE_MAX_PACKET;
537             }
538             else {
539 0           $last_packet_flag = 1;
540             }
541              
542             # get bytes to send
543 0           my $send = substr( $data, 0, $size );
544              
545             # shift data along a bit
546 0           $data = substr( $data, $size );
547              
548 0           my $packet = $self->_build_packet( $send, $seq, $last_packet_flag );
549 0           $self->{serial}->write( $packet, length($packet) );
550 0           $seq++;
551             }
552              
553 0           my $resp;
554              
555             # now get the response if we want it
556 0 0         $resp = $self->_get_response() if ( !$noresp );
557              
558 0           return $resp;
559             }
560              
561             # ----------------------------------------------------------------------------
562              
563              
564             sub update {
565 0     0 1   my $self = shift;
566              
567 0           my @bytes;
568              
569 0           $self->_debug('update');
570 0 0         if ( !$self->login_state() ) {
571 0           $self->_debug('not possible, login first');
572 0           return 0;
573             }
574              
575             # get the packed display buffer
576 0           @bytes = @{ $self->get_bytes() };
  0            
577 0 0 0       if ( scalar(@bytes) && defined $bytes[0] ) {
578 0           my $str = pack( 'C' x scalar(@bytes), @bytes );
579              
580 0           $self->_debug( "-" x 79 );
581 0           my $resp = $self->send_data( HYPNOCUBE_FRAME, $str );
582 0           $self->_debug( "-" x 79 );
583              
584 0 0         if ( $resp->{cmd} == HYPNOCUBE_ACK ) {
585              
586             # we send the frame then flip the buffer to 'on'
587 0           $self->flip();
588              
589             # save the data to a file for later retrieval if needed
590 0           path(BUFFER_FILE)->spew( Dump( $self->buffer() ) );
591              
592             # just in case different users use this, allow group write too
593 0           chmod( 0664, BUFFER_FILE );
594             }
595              
596 0           return $resp->{cmd} == HYPNOCUBE_ACK;
597             }
598             else {
599 0           return 0;
600             }
601             }
602              
603             # ----------------------------------------------------------------------------
604              
605              
606             sub flip {
607 0     0 1   my $self = shift;
608 0           $self->_debug('flip');
609 0           $self->send_data( HYPNOCUBE_FLIP, '', 1 );
610             }
611              
612             # ----------------------------------------------------------------------------
613              
614              
615             sub list_colors {
616 0     0 1   my $self = shift;
617 0           return @color_names;
618             }
619              
620             # ----------------------------------------------------------------------------
621              
622              
623             sub get_color {
624 0     0 1   my $self = shift;
625 0           my ( $color, $green, $blue, $default ) = @_;
626              
627 0   0       $color //= $default;
628 0   0       $color //= DEFAULT_COLOR;
629              
630 0 0         if ( $color =~ /^rand(om)?/i ) {
631 0           my $r = int( rand( scalar(@color_names) ) );
632 0           ( $color, $green, $blue ) = @{ $colors{ $color_names[$r] } };
  0            
633             }
634              
635 0 0 0       if ( $color =~ /^(?:0[xX]|#)([[:xdigit:]]+)$/ && !defined $green && !defined $blue ) {
      0        
636 0           my $c = $1;
637 0 0         if ( length($c) == 2 ) {
    0          
638 0           $color = $green = $blue = hex($c);
639             }
640             elsif ( length($c) == 6 ) {
641 0           $c =~ /([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})/;
642 0           $color = hex($1);
643 0           $green = hex($2);
644 0           $blue = hex($3);
645             }
646             else {
647 0           $self->_debug("bad hex color specified must be like #ab34f0 or 0xab34f0");
648 0           $color = $default;
649             }
650             }
651              
652 0 0 0       if ( $color =~ /^\d+/ && !defined $green && !defined $blue ) {
    0 0        
      0        
653 0           $green = $blue = $color;
654             }
655             elsif ( !$colors{$color} && !defined $green && !defined $blue ) {
656 0           $self->_debug( "unknown color $color, using default: " . DEFAULT_COLOR );
657 0           $color = DEFAULT_COLOR;
658             }
659 0 0         ( $color, $green, $blue ) = @{ $colors{$color} } if ( $colors{$color} );
  0            
660              
661 0           return ( $color, $green, $blue );
662             }
663              
664             # ----------------------------------------------------------------------------
665              
666              
667             sub clear {
668 0     0 1   my $self = shift;
669 0           my ( $color, $green, $blue ) = @_;
670              
671 0           my @buff = ();
672              
673 0           ( $color, $green, $blue ) = $self->get_color( $color, $green, $blue, 'black' );
674              
675 0           foreach my $i ( 0 .. ( BUFFER_SIZE - 1 ) ) {
676 0           push @buff, [ $color, $green, $blue ];
677             }
678 0           $self->_set_buffer( \@buff );
679             }
680              
681             # ----------------------------------------------------------------------------
682              
683              
684             sub set_buffer {
685 0     0 1   my $self = shift;
686 0           my ($buf) = @_;
687              
688             # assume buffer is correct size
689 0           $self->_set_buffer($buf);
690             }
691              
692             # ----------------------------------------------------------------------------
693              
694              
695             sub get_buffer {
696 0     0 1   my $self = shift;
697              
698 0           return $self->buffer;
699             }
700              
701             # ----------------------------------------------------------------------------
702              
703              
704             sub buffer_offset {
705 0     0 1   my $self = shift;
706 0           my ( $x, $y, $z ) = @_;
707              
708             # limit size, wrap around
709 0           $x %= X_SIZE;
710 0           $y %= Y_SIZE;
711 0           $z %= Z_SIZE;
712              
713             # get x other way around
714 0           $x = X_SIZE - 1 - $x;
715              
716 0           return ( $y * Z_SIZE * Y_SIZE ) + ( $z * Y_SIZE ) + $x;
717             }
718              
719             # ----------------------------------------------------------------------------
720              
721              
722             sub pixel {
723 0     0 1   my $self = shift;
724 0           my ( $x, $y, $z, $color, $green, $blue ) = @_;
725              
726 0 0 0       if ( !defined $x || !defined $y || !defined $z || $x < 0 || $y < 0 || $z < 0 ) {
      0        
      0        
      0        
727 0           $self->_debug('bad pixel args');
728 0           return 1;
729             }
730              
731             # get the color or default to white
732 0           ( $color, $green, $blue ) = $self->get_color( $color, $green, $blue, 'white' );
733              
734             # get the colors if we are using a named color
735 0 0 0       if ( defined $color && !defined $green && !defined $blue ) {
      0        
736 0           my $t = $color;
737 0           ( $color, $green, $blue ) = @{ $colors{$color} };
  0            
738             }
739              
740             # make sure we are writing correct things to the buffer
741 0 0 0       if ( int($color) == $color && int($green) == $green && int($blue) == $blue ) {
      0        
742              
743             # set the pixel
744 0           my $offset = $self->buffer_offset( $x, $y, $z );
745 0           $self->{buffer}[$offset] = [ $color, $green, $blue ];
746             }
747             else {
748 0           $self->_debug("One of the colors does not evaluate to a number");
749             }
750 0           return 0;
751             }
752              
753             # ----------------------------------------------------------------------------
754              
755              
756             sub xplane {
757 0     0 1   my $self = shift;
758 0           my ( $plane, $color, $green, $blue ) = @_;
759              
760 0 0 0       return 2 if ( !defined $plane || $plane < 0 || $plane > Y_SIZE - 1 );
      0        
761              
762             try {
763 0     0     for ( my $x = 0; $x < X_SIZE; $x++ ) {
764 0           for ( my $z = 0; $z < Z_SIZE; $z++ ) {
765 0           $self->pixel( $x, $plane, $z, $color, $green, $blue );
766             }
767             }
768             }
769 0     0     catch {};
  0            
770             }
771              
772             # ----------------------------------------------------------------------------
773              
774              
775             sub yplane {
776 0     0 1   my $self = shift;
777 0           my ( $plane, $color, $green, $blue ) = @_;
778              
779 0 0 0       return 2 if ( !defined $plane || $plane < 0 || $plane > X_SIZE - 1 );
      0        
780              
781 0           for ( my $y = 0; $y < Y_SIZE; $y++ ) {
782 0           for ( my $z = 0; $z < X_SIZE; $z++ ) {
783 0           $self->pixel( $plane, $y, $z, $color, $green, $blue );
784             }
785             }
786             }
787              
788             # ----------------------------------------------------------------------------
789              
790              
791             sub zplane {
792 0     0 1   my $self = shift;
793 0           my ( $plane, $color, $green, $blue ) = @_;
794              
795 0 0 0       return 2 if ( !defined $plane || $plane < 0 || $plane > Z_SIZE - 1 );
      0        
796              
797 0           for ( my $x = 0; $x < X_SIZE; $x++ ) {
798 0           for ( my $y = 0; $y < Y_SIZE; $y++ ) {
799 0           $self->pixel( $x, $y, $plane, $color, $green, $blue );
800             }
801             }
802             }
803              
804             # ----------------------------------------------------------------------------
805              
806              
807             sub get_bytes {
808 0     0 1   my $self = shift;
809 0           my $count = 0;
810 0           my $last_b = 0;
811 0           my @bytes = ();
812              
813             try {
814 0     0     foreach my $pix ( @{ $self->buffer() } ) {
  0            
815              
816             # get the rgb values
817 0           my ( $r, $g, $b ) = @$pix;
818              
819             # we only want the most significant 4 bits of a byte
820 0           $r = ( $r >> 4 ) & 0xf;
821 0           $g = ( $g >> 4 ) & 0xf;
822 0           $b = ( $b >> 4 ) & 0xf;
823              
824             # only save on every other pixel
825 0 0         if ( $count & 1 ) {
826              
827             # save next 2 byes of data, b+r then g+b
828 0           push @bytes, ( $last_b << 4 ) + $r;
829 0           push @bytes, ( $g << 4 ) + $b;
830             }
831             else {
832             # save first byes of data, r+g
833 0           push @bytes, ( $r << 4 ) + $g;
834 0           $last_b = $b;
835             }
836 0           $count++;
837 0 0         last if ( $count >= BUFFER_SIZE );
838             }
839             }
840             catch {
841 0     0     $self->_debug($_);
842 0           @bytes = undef;
843 0           };
844 0           return \@bytes;
845             }
846              
847             # ----------------------------------------------------------------------------
848             #_debug
849             # write sa debug msg to STDERR
850              
851             sub _debug {
852 0     0     my $self = shift;
853 0           my ( $msg, $type ) = @_;
854              
855 0 0         print STDERR "$msg\n" if ( $self->verbose() );
856             }
857              
858             # -----------------------------------------------------------------------------
859              
860             1;
861              
862             __END__