File Coverage

blib/lib/Device/Hypnocube.pm
Criterion Covered Total %
statement 15 17 88.2
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 21 23 91.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.900.400';
6 1     1   835033 use 5.010;
  1         3  
7 1     1   4 use strict;
  1         1  
  1         19  
8 1     1   4 use warnings;
  1         6  
  1         29  
9 1     1   221625 use Moo;
  1         2563948  
  1         8  
10 1     1   272791 use Time::HiRes qw( gettimeofday usleep);
  1         1456  
  1         7  
11 1     1   1154 use WebColors;
  0            
  0            
12              
13             # get the crc stuff, this is the function we need
14             use Digest::CRC qw( crcccitt );
15             use Data::Hexdumper;
16              
17             # the bit that does the actual serial comms
18             use Device::Hypnocube::Serial;
19             use Path::Tiny;
20             use YAML::XS qw( Load Dump);
21             use Try::Tiny;
22              
23             use constant HYPNOCUBE_SYNC => 0xc0;
24             use constant HYPNOCUBE_ESC => 0xdb;
25             use constant HYPNOCUBE_LAST_PKT => 0x60;
26             use constant HYPNOCUBE_NEXT_PKT => 0x40;
27             use constant HYPNOCUBE_CHALLENGE => 0xabadc0de;
28             use constant HYPNOCUBE_MAX_PACKET => 50; # max length of a packet to send
29              
30             # these are the commands we can send to the device
31             use constant HYPNOCUBE_LOGIN => 0;
32             use constant HYPNOCUBE_LOGOUT => 1;
33             use constant HYPNOCUBE_RESET => 10;
34             use constant HYPNOCUBE_INFO => 11;
35             use constant HYPNOCUBE_VERS => 12;
36             use constant HYPNOCUBE_ERR => 20;
37             use constant HYPNOCUBE_ACK => 25;
38             use constant HYPNOCUBE_PING => 60;
39             use constant HYPNOCUBE_FLIP => 80;
40             use constant HYPNOCUBE_FRAME => 81;
41             use constant HYPNOCUBE_PIXEL => 81;
42              
43             use constant X_SIZE => 4;
44             use constant Y_SIZE => 4;
45             use constant Z_SIZE => 4;
46             use constant BUFFER_SIZE => X_SIZE * Y_SIZE * Z_SIZE;
47             use constant DEFAULT_COLOR => 'purple';
48              
49             use constant RATE_LIMIT_MSECS => 3333; # 1/30 * 1e6
50              
51             # where we will save the buffer between runs
52             use constant BUFFER_FILE => '/tmp/hypnocube.buffer';
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             my $self = shift;
197             my $args = shift;
198              
199             # add the serial port if it was passed to us
200             if ( $args->{serial} ) {
201              
202             # this should connect too
203             $self->{serial} = Device::Hypnocube::Serial->new($args);
204             }
205             else {
206             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             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             my $self = shift;
225             my $code = shift;
226              
227             my $errmsg = { code => $code, error => $errors{$code} };
228             $self->_set_error_info($errmsg);
229              
230             $self->_debug("error: $errors{$code}");
231             }
232              
233             # ----------------------------------------------------------------------------
234              
235              
236             sub ping {
237             my $self = shift;
238              
239             $self->_debug('ping');
240              
241             # if something is doing something with the serial thats as good as a ping
242             return if ( $self->{serial}->{activity} );
243              
244             # no response possible from a ping, but then again there may be!
245             $self->send_data( HYPNOCUBE_PING, '', 1 );
246             }
247              
248             # ----------------------------------------------------------------------------
249              
250              
251             sub login {
252             my $self = shift;
253              
254             $self->_debug('login');
255              
256             # no need to login again
257             return if ( $self->login_state() );
258              
259             my $resp = $self->send_data( HYPNOCUBE_LOGIN, pack( 'N', HYPNOCUBE_CHALLENGE ) );
260              
261             if ( $resp->{cmd} == HYPNOCUBE_ACK || ( $resp->{cmd} == HYPNOCUBE_ERR && $self->error_info->{code} == 0 ) ) {
262             $self->_set_login_state(1);
263              
264             # $self->info() ; # update the info
265             my $hashref;
266             if ( -f BUFFER_FILE ) {
267             $hashref = Load( path(BUFFER_FILE)->slurp );
268             }
269              
270             # use the buffer otherwise clear to black
271             if ($hashref) {
272             $self->_set_buffer($hashref);
273             $self->update();
274             }
275             else {
276             $self->clear('black');
277             $self->update();
278             }
279             }
280             else {
281             $self->_debug( "resp " . $resp->{cmd} . " " . HYPNOCUBE_ERR . " code " . $self->error_info->{code} );
282             }
283             }
284              
285             # ----------------------------------------------------------------------------
286              
287              
288             sub logout {
289             my $self = shift;
290              
291             $self->_debug('logout');
292              
293             # dont logout if we are not logged in
294             return if ( !$self->login_state() );
295              
296             # don't wait for a response
297             my $resp = $self->send_data( HYPNOCUBE_LOGOUT, '', 1 );
298              
299             $self->_set_login_state(0);
300              
301             # and dump what we know about the device
302             $self->_clear_info();
303             }
304              
305             # ----------------------------------------------------------------------------
306              
307              
308             sub info {
309             my $self = shift;
310             my %info = ();
311              
312             return $self->device_info() if ( $self->has_info() );
313             $self->_debug('info');
314              
315             my $resp = $self->send_data( HYPNOCUBE_INFO, pack( 'CC', 0, 0 ) );
316             $info{name} = $resp->{payload};
317             $resp = $self->send_data( HYPNOCUBE_INFO, pack( 'CC', 0, 1 ) );
318             $info{desc} = $resp->{payload};
319             $resp = $self->send_data( HYPNOCUBE_INFO, pack( 'CC', 0, 2 ) );
320             $info{copyright} = $resp->{payload};
321             $resp = $self->send_data( HYPNOCUBE_VERS, '' );
322              
323             ( $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             $self->_set_device_info( \%info );
327              
328             return \%info;
329             }
330              
331             # ----------------------------------------------------------------------------
332              
333              
334             sub reset {
335             my $self = shift;
336              
337             $self->_debug('reset');
338              
339             $self->send_data( HYPNOCUBE_RESET, '', 1 );
340             }
341              
342             # ----------------------------------------------------------------------------
343              
344              
345             sub last_error {
346             my $self = shift;
347              
348             $self->_debug('last_error');
349              
350             my $resp = $self->send_data( HYPNOCUBE_ERR, 0 );
351              
352             $self->set_error( unpack( 'C', $resp->{payload} ) );
353              
354             # and reset the error
355             $self->send_data( HYPNOCUBE_ERR, -2 );
356             }
357              
358             # ----------------------------------------------------------------------------
359             # _ack
360             # tell the device we got the data
361              
362             sub _ack {
363             my $self = shift;
364              
365             $self->_debug('_ack');
366              
367             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             my $self = shift;
377              
378             # get current time
379             my ( $seconds, $microseconds ) = gettimeofday;
380              
381             # easier to play with as a float
382             my $ftime = $seconds + ( $microseconds / 1000000 );
383             my $lasttime = $self->last_rate_limit();
384              
385             # calc in big microsecs the time elapse since last time
386             my $elapsed = ( $ftime - $lasttime ) * 1000000;
387              
388             # if we need to pause to make up the time, do it now
389             if ( $elapsed < RATE_LIMIT_MSECS ) {
390             my $pause = RATE_LIMIT_MSECS - $elapsed;
391             usleep($pause);
392             }
393              
394             # update the last update with now
395             $self->_set_last_rate_limit($ftime);
396             }
397              
398             # ----------------------------------------------------------------------------
399             # _get_response
400             # read stuff from the device
401              
402             sub _get_response {
403             my $self = shift;
404             my %packet = ();
405              
406             # if something is doing something wait till its over
407             while ( $self->{serial}->{activity} ) {
408             sleep(1);
409             }
410              
411             # we read and discard till we get a sync frame
412             my $tmp = '';
413             while (1) {
414             my $r = $self->{serial}->read(1);
415             if ( !$r ) {
416             sleep 1;
417             }
418             else {
419             my $c = unpack( 'C', $r );
420             if ( $c == HYPNOCUBE_SYNC ) {
421             $packet{sync_head} = $c;
422             last;
423             }
424             $tmp .= $r;
425             }
426             }
427              
428             $packet{type} = unpack( 'C', $self->{serial}->read(1) );
429             $packet{length} = unpack( 'C', $self->{serial}->read(1) );
430             $packet{dest} = unpack( 'C', $self->{serial}->read(1) );
431              
432             # split type into sequence and type
433             $packet{sequence} = $packet{type} & 0x1f;
434             $packet{type} = $packet{type} & 0xe0;
435              
436             my $payload_fmt = 'C' x $packet{length};
437             $packet{cmd} = unpack( $payload_fmt, $self->{serial}->read(1) );
438              
439             # payload is not unpacked the caller will have to do that
440             $packet{payload} = $self->{serial}->read( $packet{length} - 1 );
441             $packet{chksum} = unpack( 'n', $self->{serial}->read(2) );
442             $packet{sync_tail} = unpack( 'C', $self->{serial}->read(1) );
443              
444             if ( $packet{cmd} == HYPNOCUBE_ERR ) {
445             $self->set_error( unpack( 'C', $packet{payload} ) );
446             }
447             else {
448             $self->set_error(0);
449             }
450              
451             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             my $self = shift;
461             my ( $payload, $seq, $type ) = @_;
462             my $sync = pack( 'C', HYPNOCUBE_SYNC );
463              
464             $seq %= 31; # sequence count wraps at 32
465              
466             $self->_debug( "_build_packet\n" . hexdump( data => $payload, suppress_warnings => 1 ) );
467             my $plen = length($payload);
468             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             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             my $crc = crcccitt($out);
479              
480             # add crc onto end
481             $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             my $newdata = '';
486             my $fmt = 'C' x length($out);
487             my $count = 0;
488             for ( my $offset = 0; $offset < length($out); $offset++ ) {
489             my $c = unpack( 'C', substr( $out, $offset, 1 ) );
490             if ( $c == HYPNOCUBE_SYNC ) {
491             $newdata .= pack( 'CC', HYPNOCUBE_ESC, HYPNOCUBE_ESC + 1 );
492             }
493             elsif ( $c == HYPNOCUBE_ESC ) {
494             $newdata .= pack( 'CC', HYPNOCUBE_ESC,, HYPNOCUBE_ESC + 2 );
495             }
496             else {
497             $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             $out = $sync . $newdata . $sync;
504              
505             $self->_debug( "packet $seq\n" . hexdump( data => $out, suppress_warnings => 1 ) );
506              
507             return $out;
508             }
509              
510             # ----------------------------------------------------------------------------
511              
512              
513             sub send_data {
514             my $self = shift;
515             my ( $cmd, $data, $noresp ) = @_;
516             $self->_debug( 'send_data cmd ' . $cmd );
517             $self->_debug( "data\n" . hexdump( data => $data, suppress_warnings => 1 ) ) if ($data);
518              
519             if ( !defined $cmd && !defined $data ) {
520             $self->_debug('no command specified');
521             return {};
522             }
523              
524             $data ||= '';
525             my $seq = 0;
526              
527             # make sure we do not send data too quickly
528             $self->_rate_limit();
529              
530             # add the command to send onto the front of the data
531             $data = pack( 'C', $cmd ) . $data;
532             my $last_packet_flag = 0;
533             while ( !$last_packet_flag ) {
534             my $size = length($data);
535             if ( $size > HYPNOCUBE_MAX_PACKET ) {
536             $size = HYPNOCUBE_MAX_PACKET;
537             }
538             else {
539             $last_packet_flag = 1;
540             }
541              
542             # get bytes to send
543             my $send = substr( $data, 0, $size );
544              
545             # shift data along a bit
546             $data = substr( $data, $size );
547              
548             my $packet = $self->_build_packet( $send, $seq, $last_packet_flag );
549             $self->{serial}->write( $packet, length($packet) );
550             $seq++;
551             }
552              
553             my $resp;
554              
555             # now get the response if we want it
556             $resp = $self->_get_response() if ( !$noresp );
557              
558             return $resp;
559             }
560              
561             # ----------------------------------------------------------------------------
562              
563              
564             sub update {
565             my $self = shift;
566              
567             my @bytes;
568              
569             $self->_debug('update');
570             if ( !$self->login_state() ) {
571             $self->_debug('not possible, login first');
572             return 0;
573             }
574              
575             # get the packed display buffer
576             @bytes = @{ $self->get_bytes() };
577             if ( scalar(@bytes) && defined $bytes[0] ) {
578             my $str = pack( 'C' x scalar(@bytes), @bytes );
579              
580             $self->_debug( "-" x 79 );
581             my $resp = $self->send_data( HYPNOCUBE_FRAME, $str );
582             $self->_debug( "-" x 79 );
583              
584             if ( $resp->{cmd} == HYPNOCUBE_ACK ) {
585              
586             # we send the frame then flip the buffer to 'on'
587             $self->flip();
588              
589             # save the data to a file for later retrieval if needed
590             path(BUFFER_FILE)->spew( Dump( $self->buffer() ) );
591              
592             # just in case different users use this, allow group write too
593             chmod( 0664, BUFFER_FILE );
594             }
595              
596             return $resp->{cmd} == HYPNOCUBE_ACK;
597             }
598             else {
599             return 0;
600             }
601             }
602              
603             # ----------------------------------------------------------------------------
604              
605              
606             sub flip {
607             my $self = shift;
608             $self->_debug('flip');
609             $self->send_data( HYPNOCUBE_FLIP, '', 1 );
610             }
611              
612             # ----------------------------------------------------------------------------
613              
614              
615             sub list_colors {
616             my $self = shift;
617             return @color_names;
618             }
619              
620             # ----------------------------------------------------------------------------
621              
622              
623             sub get_color {
624             my $self = shift;
625             my ( $color, $green, $blue, $default ) = @_;
626              
627             $color //= $default;
628             $color //= DEFAULT_COLOR;
629              
630             if ( $color =~ /^rand(om)?/i ) {
631             my $r = int( rand( scalar(@color_names) ) );
632             ( $color, $green, $blue ) = @{ $colors{ $color_names[$r] } };
633             }
634             # alias for black
635             if ( $color =~ /^(clear|off)$/i ) {
636             ( $color, $green, $blue ) = (0, 0, 0);
637             }
638              
639             my $tc = colorname_to_hex( $color) ;
640             $color = "#$tc" if( $tc) ;
641              
642             if ( $color =~ /^(?:0[xX]|#)([[:xdigit:]]+)$/ && !defined $green && !defined $blue ) {
643             my $c = $1;
644             if ( length($c) == 2 ) {
645             $color = $green = $blue = hex($c);
646             }
647             elsif ( length($c) == 3 ) {
648             $c =~ /([[:xdigit:]])([[:xdigit:]])([[:xdigit:]])/;
649             $color = hex("$1$1");
650             $green = hex("$2$2");
651             $blue = hex("$3$3");
652             }
653             elsif ( length($c) == 6 ) {
654             $c =~ /([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})/;
655             $color = hex($1);
656             $green = hex($2);
657             $blue = hex($3);
658             }
659             else {
660             $self->_debug("bad hex color specified must be like #ab34f0 or 0xab34f0");
661             $color = $default;
662             }
663             }
664              
665             if ( $color =~ /^\d+/ && !defined $green && !defined $blue ) {
666             $green = $blue = $color;
667             }
668             elsif ( !$colors{$color} && !defined $green && !defined $blue ) {
669             $self->_debug( "unknown color $color, using default: " . DEFAULT_COLOR );
670             $color = DEFAULT_COLOR;
671             }
672             ( $color, $green, $blue ) = @{ $colors{$color} } if ( $colors{$color} );
673              
674             return ( $color, $green, $blue );
675             }
676              
677             # ----------------------------------------------------------------------------
678              
679              
680             sub clear {
681             my $self = shift;
682             my ( $color, $green, $blue ) = @_;
683              
684             my @buff = ();
685              
686             ( $color, $green, $blue ) = $self->get_color( $color, $green, $blue, 'black' );
687              
688             foreach my $i ( 0 .. ( BUFFER_SIZE - 1 ) ) {
689             push @buff, [ $color, $green, $blue ];
690             }
691             $self->_set_buffer( \@buff );
692             }
693              
694             # ----------------------------------------------------------------------------
695              
696              
697             sub set_buffer {
698             my $self = shift;
699             my ($buf) = @_;
700              
701             # assume buffer is correct size
702             $self->_set_buffer($buf);
703             }
704              
705             # ----------------------------------------------------------------------------
706              
707              
708             sub get_buffer {
709             my $self = shift;
710              
711             return $self->buffer;
712             }
713              
714             # ----------------------------------------------------------------------------
715              
716              
717             sub buffer_offset {
718             my $self = shift;
719             my ( $x, $y, $z ) = @_;
720              
721             # limit size, wrap around
722             $x %= X_SIZE;
723             $y %= Y_SIZE;
724             $z %= Z_SIZE;
725              
726             # get x other way around
727             $x = X_SIZE - 1 - $x;
728              
729             return ( $y * Z_SIZE * Y_SIZE ) + ( $z * Y_SIZE ) + $x;
730             }
731              
732             # ----------------------------------------------------------------------------
733              
734              
735             sub pixel {
736             my $self = shift;
737             my ( $x, $y, $z, $color, $green, $blue ) = @_;
738              
739             if ( !defined $x || !defined $y || !defined $z || $x < 0 || $y < 0 || $z < 0 ) {
740             $self->_debug('bad pixel args');
741             return 1;
742             }
743              
744             # get the color or default to white
745             ( $color, $green, $blue ) = $self->get_color( $color, $green, $blue, 'white' );
746              
747             # get the colors if we are using a named color
748             if ( defined $color && !defined $green && !defined $blue ) {
749             my $t = $color;
750             ( $color, $green, $blue ) = @{ $colors{$color} };
751             }
752              
753             # make sure we are writing correct things to the buffer
754             if ( int($color) == $color && int($green) == $green && int($blue) == $blue ) {
755              
756             # set the pixel
757             my $offset = $self->buffer_offset( $x, $y, $z );
758             $self->{buffer}[$offset] = [ $color, $green, $blue ];
759             }
760             else {
761             $self->_debug("One of the colors does not evaluate to a number");
762             }
763             return 0;
764             }
765              
766             # ----------------------------------------------------------------------------
767              
768              
769             sub xplane {
770             my $self = shift;
771             my ( $plane, $color, $green, $blue ) = @_;
772              
773             return 2 if ( !defined $plane || $plane < 0 || $plane > Y_SIZE - 1 );
774              
775             try {
776             for ( my $x = 0; $x < X_SIZE; $x++ ) {
777             for ( my $z = 0; $z < Z_SIZE; $z++ ) {
778             $self->pixel( $x, $plane, $z, $color, $green, $blue );
779             }
780             }
781             }
782             catch {};
783             }
784              
785             # ----------------------------------------------------------------------------
786              
787              
788             sub yplane {
789             my $self = shift;
790             my ( $plane, $color, $green, $blue ) = @_;
791              
792             return 2 if ( !defined $plane || $plane < 0 || $plane > X_SIZE - 1 );
793              
794             for ( my $y = 0; $y < Y_SIZE; $y++ ) {
795             for ( my $z = 0; $z < X_SIZE; $z++ ) {
796             $self->pixel( $plane, $y, $z, $color, $green, $blue );
797             }
798             }
799             }
800              
801             # ----------------------------------------------------------------------------
802              
803              
804             sub zplane {
805             my $self = shift;
806             my ( $plane, $color, $green, $blue ) = @_;
807              
808             return 2 if ( !defined $plane || $plane < 0 || $plane > Z_SIZE - 1 );
809              
810             for ( my $x = 0; $x < X_SIZE; $x++ ) {
811             for ( my $y = 0; $y < Y_SIZE; $y++ ) {
812             $self->pixel( $x, $y, $plane, $color, $green, $blue );
813             }
814             }
815             }
816              
817             # ----------------------------------------------------------------------------
818              
819              
820             sub get_bytes {
821             my $self = shift;
822             my $count = 0;
823             my $last_b = 0;
824             my @bytes = ();
825              
826             try {
827             foreach my $pix ( @{ $self->buffer() } ) {
828              
829             # get the rgb values
830             my ( $r, $g, $b ) = @$pix;
831              
832             # we only want the most significant 4 bits of a byte
833             $r = ( $r >> 4 ) & 0xf;
834             $g = ( $g >> 4 ) & 0xf;
835             $b = ( $b >> 4 ) & 0xf;
836              
837             # only save on every other pixel
838             if ( $count & 1 ) {
839              
840             # save next 2 byes of data, b+r then g+b
841             push @bytes, ( $last_b << 4 ) + $r;
842             push @bytes, ( $g << 4 ) + $b;
843             }
844             else {
845             # save first byes of data, r+g
846             push @bytes, ( $r << 4 ) + $g;
847             $last_b = $b;
848             }
849             $count++;
850             last if ( $count >= BUFFER_SIZE );
851             }
852             }
853             catch {
854             $self->_debug($_);
855             @bytes = undef;
856             };
857             return \@bytes;
858             }
859              
860             # ----------------------------------------------------------------------------
861             #_debug
862             # write sa debug msg to STDERR
863              
864             sub _debug {
865             my $self = shift;
866             my ( $msg, $type ) = @_;
867              
868             print STDERR "$msg\n" if ( $self->verbose() );
869             }
870              
871             # -----------------------------------------------------------------------------
872              
873             1;
874              
875             __END__