File Coverage

blib/lib/Device/WebIO/RaspberryPi.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # Copyright (c) 2014 Timm Murray
2             # All rights reserved.
3             #
4             # Redistribution and use in source and binary forms, with or without
5             # modification, are permitted provided that the following conditions are met:
6             #
7             # * Redistributions of source code must retain the above copyright notice,
8             # this list of conditions and the following disclaimer.
9             # * Redistributions in binary form must reproduce the above copyright
10             # notice, this list of conditions and the following disclaimer in the
11             # documentation and/or other materials provided with the distribution.
12             #
13             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23             # POSSIBILITY OF SUCH DAMAGE.
24             package Device::WebIO::RaspberryPi;
25             $Device::WebIO::RaspberryPi::VERSION = '0.008';
26             # ABSTRACT: Device::WebIO implementation for the Rapsberry Pi
27 1     1   491 use v5.12;
  1         2  
  1         32  
28 1     1   500 use Moo;
  1         11312  
  1         7  
29 1     1   1894 use namespace::clean;
  1         11464  
  1         6  
30 1     1   357 use HiPi::Wiring qw( :wiring );
  0            
  0            
31             use HiPi::Device::I2C;
32             use HiPi::Device::SPI qw( :spi );
33             use GStreamer1;
34             use Glib qw( TRUE FALSE );
35             use AnyEvent;
36              
37             use constant {
38             TYPE_REV1 => 0,
39             TYPE_REV2 => 1,
40             TYPE_MODEL_B_PLUS => 2,
41             };
42              
43             use constant {
44             # maps of Rpi Pin -> Wiring lib pin
45             PIN_MAP_REV1 => {
46             0 => 8,
47             1 => 9,
48             4 => 7,
49             7 => 11,
50             8 => 10,
51             9 => 13,
52             10 => 12,
53             11 => 14,
54             14 => 15,
55             15 => 16,
56             17 => 0,
57             18 => 1,
58             21 => 2,
59             22 => 3,
60             23 => 4,
61             24 => 5,
62             25 => 6,
63             },
64             PIN_MAP_REV2 => {
65             2 => 8,
66             3 => 9,
67             4 => 7,
68             7 => 11,
69             8 => 10,
70             9 => 13,
71             10 => 12,
72             11 => 14,
73             14 => 15,
74             15 => 16,
75             17 => 0,
76             18 => 1,
77             27 => 2,
78             28 => 17,
79             22 => 3,
80             23 => 4,
81             24 => 5,
82             25 => 6,
83             30 => 19,
84             29 => 18,
85             31 => 20,
86             },
87             PIN_MAP_MODEL_B_PLUS => {
88             2 => 8,
89             3 => 9,
90             4 => 7,
91             7 => 11,
92             8 => 10,
93             9 => 13,
94             10 => 12,
95             11 => 14,
96             14 => 15,
97             15 => 16,
98             17 => 0,
99             18 => 1,
100             27 => 2,
101             28 => 17,
102             22 => 3,
103             23 => 4,
104             24 => 5,
105             25 => 6,
106             30 => 19,
107             29 => 18,
108             31 => 20,
109             },
110             };
111              
112             my %ALLOWED_VIDEO_TYPES = (
113             'video/H264' => 1,
114             'video/x-msvideo' => 1,
115              
116             # mp4mux doesn't seem to like the stream-format that comes out of rpicamsrc.
117             # Converting might be too slow on the Rpi. For reference, try to get this
118             # pipeline to work (which won't link up as written):
119             #
120             # gst-launch-1.0 -v rpicamsrc ! h264parse ! \
121             # 'video/x-h264,width=800,height=600,fps=30,stream-format=avc' ! \
122             # mp4mux ! filesink location=/tmp/output.mp4
123             #
124             # 'video/mp4' => 1,
125             );
126              
127              
128             has 'pin_desc', is => 'ro';
129             has '_type', is => 'ro';
130             has '_pin_mode' => (
131             is => 'ro',
132             );
133             has '_pin_map' => (
134             is => 'ro',
135             );
136             # Note that _output_pin_value should be mapped by the Wiring library's
137             # pin number, *not* the Rpi's numbering
138             has '_output_pin_value' => (
139             is => 'ro',
140             );
141              
142             has '_is_gstreamer_inited' => (
143             is => 'rw',
144             default => sub { 0 },
145             );
146              
147              
148             my $CALLED_WIRING_SETUP = 0;
149              
150              
151             sub BUILDARGS
152             {
153             my ($class, $args) = @_;
154             my $rpi_type = delete($args->{type}) // $class->TYPE_REV1;
155              
156             $args->{pwm_bit_resolution} = 10;
157             $args->{pwm_max_int} = 2 ** $args->{pwm_bit_resolution};
158              
159             if( TYPE_REV1 == $rpi_type ) {
160             $args->{input_pin_count} = 26;
161             $args->{output_pin_count} = 26;
162             $args->{pwm_pin_count} = 0;
163             $args->{pin_desc} = $class->_pin_desc_rev1;
164             $args->{'_pin_map'} = $class->PIN_MAP_REV1;
165             }
166             elsif( TYPE_REV2 == $rpi_type ) {
167             $args->{input_pin_count} = 26;
168             $args->{output_pin_count} = 26;
169             $args->{pwm_pin_count} = 1;
170             $args->{pin_desc} = $class->_pin_desc_rev2;
171             $args->{'_pin_map'} = $class->PIN_MAP_REV2;
172             }
173             elsif( TYPE_MODEL_B_PLUS == $rpi_type ) {
174             $args->{input_pin_count} = 26;
175             $args->{output_pin_count} = 26;
176             $args->{pwm_pin_count} = 1;
177             $args->{pin_desc} = $class->_pin_desc_model_b_plus;
178             $args->{'_pin_map'} = $class->PIN_MAP_MODEL_B_PLUS;
179             }
180             else {
181             die "Don't know what to do with Rpi type '$rpi_type'\n";
182             }
183              
184             $args->{'_pin_mode'} = [ ('IN') x $args->{input_pin_count} ];
185             $args->{'_output_pin_value'} = [ (0) x $args->{output_pin_count} ];
186              
187             HiPi::Wiring::wiringPiSetup() unless $CALLED_WIRING_SETUP;
188             $CALLED_WIRING_SETUP = 1;
189              
190             return $args;
191             }
192              
193              
194             has 'input_pin_count', is => 'ro';
195             with 'Device::WebIO::Device::DigitalInput';
196              
197             sub set_as_input
198             {
199             my ($self, $rpi_pin) = @_;
200             my $pin = $self->_rpi_pin_to_wiring( $rpi_pin );
201             return undef if $pin < 0;
202             $self->{'_pin_mode'}[$pin] = 'IN';
203             HiPi::Wiring::pinMode( $pin, WPI_INPUT );
204             return 1;
205             }
206              
207             sub input_pin
208             {
209             my ($self, $rpi_pin) = @_;
210             my $pin = $self->_rpi_pin_to_wiring( $rpi_pin );
211             return undef if $pin < 0;
212             my $in = HiPi::Wiring::digitalRead( $pin );
213             return $in;
214             }
215              
216             sub is_set_input
217             {
218             my ($self, $rpi_pin) = @_;
219             my $pin = $self->_rpi_pin_to_wiring( $rpi_pin );
220             return undef if $pin < 0;
221             return 1 if $self->_pin_mode->[$pin] eq 'IN';
222             return 0;
223             }
224              
225              
226             has 'output_pin_count', is => 'ro';
227             with 'Device::WebIO::Device::DigitalOutput';
228              
229             sub set_as_output
230             {
231             my ($self, $rpi_pin) = @_;
232             my $pin = $self->_rpi_pin_to_wiring( $rpi_pin );
233             return undef if $pin < 0;
234             $self->{'_pin_mode'}[$pin] = 'OUT';
235             HiPi::Wiring::pinMode( $pin, WPI_OUTPUT );
236             return 1;
237             }
238              
239             sub output_pin
240             {
241             my ($self, $rpi_pin, $value) = @_;
242             my $pin = $self->_rpi_pin_to_wiring( $rpi_pin );
243             return undef if $pin < 0;
244             $self->_output_pin_value->[$rpi_pin] = $value;
245             HiPi::Wiring::digitalWrite( $pin, $value ? WPI_HIGH : WPI_LOW );
246             return 1;
247             }
248              
249             sub is_set_output
250             {
251             my ($self, $rpi_pin) = @_;
252             my $pin = $self->_rpi_pin_to_wiring( $rpi_pin );
253             return undef if $pin < 0;
254             return 1 if $self->_pin_mode->[$pin] eq 'OUT';
255             return 0;
256             }
257              
258              
259             has 'pwm_pin_count', is => 'ro';
260             has 'pwm_bit_resolution', is => 'ro';
261             has 'pwm_max_int', is => 'ro';
262             with 'Device::WebIO::Device::PWM';
263              
264             {
265             my %did_set_pwm;
266             sub pwm_output_int
267             {
268             my ($self, $rpi_pin, $val) = @_;
269             my $pin = $self->_rpi_pin_to_wiring( $rpi_pin );
270             return undef if $pin < 0;
271             HiPi::Wiring::pinMode( $pin, WPI_PWM_OUTPUT )
272             if ! exists $did_set_pwm{$pin};
273             $did_set_pwm{$pin} = 1;
274              
275             HiPi::Wiring::pwmWrite( $pin, $val );
276             return 1;
277             }
278             }
279              
280             has '_img_width' => (
281             is => 'rw',
282             default => sub {[
283             1024
284             ]},
285             );
286             has '_img_height' => (
287             is => 'rw',
288             default => sub {[
289             768
290             ]},
291             );
292             has '_img_quality' => (
293             is => 'rw',
294             default => sub {[
295             100
296             ]},
297             );
298             with 'Device::WebIO::Device::StillImageOutput';
299              
300             my %IMG_CONTENT_TYPES = (
301             'image/jpeg' => 'jpeg',
302             'image/gif' => 'gif',
303             'image/png' => 'png',
304             );
305              
306             sub img_width
307             {
308             my ($self, $channel) = @_;
309             return $self->_img_width->[$channel];
310             }
311              
312             sub img_height
313             {
314             my ($self, $channel) = @_;
315             return $self->_img_height->[$channel];
316             }
317              
318             sub img_quality
319             {
320             my ($self, $channel) = @_;
321             return $self->_img_quality->[$channel];
322             }
323              
324             sub img_set_width
325             {
326             my ($self, $channel, $width) = @_;
327             $self->_img_width->[$channel] = $width;
328             return 1;
329             }
330              
331             sub img_set_height
332             {
333             my ($self, $channel, $height) = @_;
334             $self->_img_height->[$channel] = $height;
335             return 1;
336             }
337              
338             sub img_set_quality
339             {
340             my ($self, $channel, $quality) = @_;
341             $self->_img_quality->[$channel] = $quality;
342             return 1;
343             }
344              
345             sub img_channels
346             {
347             my ($self) = @_;
348             return 1;
349             }
350              
351             sub img_allowed_content_types
352             {
353             my ($self) = @_;
354             return [ keys %IMG_CONTENT_TYPES ];
355             }
356              
357             sub img_stream
358             {
359             my ($self, $channel, $mime_type) = @_;
360             my $imager_type = $IMG_CONTENT_TYPES{$mime_type};
361              
362             my $width = $self->img_width( $channel );
363             my $height = $self->img_height( $channel );
364             my $quality = $self->img_quality( $channel );
365              
366             $self->_init_gstreamer;
367              
368             my $loop = Glib::MainLoop->new( undef, FALSE );
369             my $pipeline = GStreamer1::Pipeline->new( 'pipeline' );
370              
371             my $rpi = GStreamer1::ElementFactory::make( rpicamsrc => 'and_who' );
372             my $h264parse = GStreamer1::ElementFactory::make( h264parse => 'are_you' );
373             my $capsfilter = GStreamer1::ElementFactory::make(
374             capsfilter => 'the_proud_lord_said' );
375             my $avdec_h264 = GStreamer1::ElementFactory::make(
376             avdec_h264 => 'that_i_should_bow_so_low' );
377             my $jpegenc = GStreamer1::ElementFactory::make( jpegenc => 'only_a_cat' );
378             my $appsink = GStreamer1::ElementFactory::make(
379             appsink => 'of_a_different_coat' );
380              
381             my $caps = GStreamer1::Caps::Simple->new( 'video/x-h264',
382             width => 'Glib::Int' => 800,
383             height => 'Glib::Int' => 600,
384             );
385             $capsfilter->set( caps => $caps );
386              
387             $appsink->set( 'max-buffers' => 20 );
388             $appsink->set( 'emit-signals' => TRUE );
389             $appsink->set( 'sync' => FALSE );
390              
391              
392             my @link = (
393             $rpi, $h264parse, $capsfilter, $avdec_h264, $jpegenc, $appsink );
394             $pipeline->add( $_ ) for @link;
395             foreach my $i (0 .. ($#link - 1)) {
396             my $this = $link[$i];
397             my $next = $link[$i+1];
398             $this->link( $next );
399             }
400              
401             $pipeline->set_state( "playing" );
402             my $jpeg_sample = $appsink->pull_sample;
403             $pipeline->set_state( "null" );
404              
405             my $jpeg_buf = $jpeg_sample->get_buffer;
406             my $size = $jpeg_buf->get_size;
407             my $buf = $jpeg_buf->extract_dup( 0, $size, undef, $size );
408              
409             my $scalar_buf = pack 'C*', @$buf;
410             open( my $jpeg_fh, '<', \$scalar_buf )
411             or die "Could not open ref to scalar: $!\n";
412              
413             return $jpeg_fh;
414             }
415              
416              
417             with 'Device::WebIO::Device::I2CProvider';
418              
419             sub i2c_channels { 2 }
420              
421             sub i2c_read
422             {
423             my ($self, $channel, $addr, $register, $len) = @_;
424             my $dev = $self->_get_i2c_device_by_channel( $channel );
425             my $hipi = HiPi::Device::I2C->new(
426             devicename => $dev,
427             address => $addr,
428             );
429              
430             my @data = $hipi->bus_read( $register, $len );
431             return @data;
432             }
433              
434             sub i2c_write
435             {
436             my ($self, $channel, $addr, $register, @data) = @_;
437             my $dev = $self->_get_i2c_device_by_channel( $channel );
438             my $hipi = HiPi::Device::I2C->new(
439             devicename => $dev,
440             address => $addr,
441             );
442              
443             $hipi->bus_write( $register, @data );
444             return 1;
445             }
446              
447             sub _get_i2c_device_by_channel
448             {
449             my ($self, $channel) = @_;
450             return
451             $channel == 0 ? '/dev/i2c-0' :
452             $channel == 1 ? '/dev/i2c-1' :
453             undef;
454             }
455              
456              
457             has '_vid_width' => (
458             is => 'rw',
459             default => sub {[
460             1920
461             ]},
462             );
463             has '_vid_height' => (
464             is => 'rw',
465             default => sub {[
466             1080
467             ]},
468             );
469             has '_vid_fps' => (
470             is => 'rw',
471             default => sub {[
472             30
473             ]},
474             );
475             has '_vid_bitrate' => (
476             is => 'rw',
477             default => sub {[
478             8000
479             ]},
480             );
481             has '_vid_stream_callbacks' => (
482             is => 'rw',
483             default => sub {[]},
484             );
485             has '_vid_stream_callback_types' => (
486             is => 'rw',
487             default => sub {[]},
488             );
489             has 'cv' => (
490             is => 'rw',
491             default => sub { AnyEvent->condvar },
492             );
493             with 'Device::WebIO::Device::VideoOutputCallback';
494              
495             sub vid_channels
496             {
497             return 1;
498             }
499              
500             sub vid_height
501             {
502             my ($self, $pin) = @_;
503             return $self->_vid_height->[$pin];
504             }
505              
506             sub vid_width
507             {
508             my ($self, $pin) = @_;
509             return $self->_vid_width->[$pin];
510             }
511              
512             sub vid_fps
513             {
514             my ($self, $pin) = @_;
515             return $self->_vid_fps->[$pin];
516             }
517              
518             sub vid_kbps
519             {
520             my ($self, $pin) = @_;
521             return $self->_vid_bitrate->[$pin];
522             }
523              
524             sub vid_set_width
525             {
526             my ($self, $pin, $val) = @_;
527             return $self->_vid_width->[$pin] = $val;
528             }
529              
530             sub vid_set_height
531             {
532             my ($self, $pin, $val) = @_;
533             return $self->_vid_height->[$pin] = $val;
534             }
535              
536             sub vid_set_fps
537             {
538             my ($self, $pin, $val) = @_;
539             return $self->_vid_fps->[$pin] = $val;
540             }
541              
542             sub vid_set_kbps
543             {
544             my ($self, $pin, $val) = @_;
545             $val *= 1024;
546             return $self->_vid_bitrate->[$pin] = $val;
547             }
548              
549             sub vid_allowed_content_types
550             {
551             return keys %ALLOWED_VIDEO_TYPES;
552             }
553              
554             sub vid_stream
555             {
556             my ($self, $pin, $type) = @_;
557             die "Do not support type '$type'" unless exists $ALLOWED_VIDEO_TYPES{$type};
558             $self->_init_gstreamer;
559             return 1;
560             }
561              
562             sub vid_stream_callback
563             {
564             my ($self, $pin, $type, $callback) = @_;
565             die "Do not support type '$type'" unless exists $ALLOWED_VIDEO_TYPES{$type};
566             $self->_vid_stream_callbacks->[$pin] = $callback;
567             $self->_vid_stream_callback_types->[$pin] = $type;
568             return 1;
569             }
570              
571             sub vid_stream_begin_loop
572             {
573             my ($self, $channel) = @_;
574             my $width = $self->vid_width( $channel );
575             my $height = $self->vid_height( $channel );
576             my $fps = $self->vid_fps( $channel );
577             my $bitrate = $self->vid_kbps( $channel );
578             my $callback = $self->_vid_stream_callbacks->[$channel];
579             my $type = $self->_vid_stream_callback_types->[$channel];
580              
581              
582             $self->_init_gstreamer;
583             my $cv = $self->cv;
584             my $pipeline = GStreamer1::Pipeline->new( 'pipeline' );
585              
586             my $rpi = GStreamer1::ElementFactory::make( rpicamsrc => 'and_who' );
587             my $h264parse = GStreamer1::ElementFactory::make( h264parse => 'are_you' );
588             my $capsfilter = GStreamer1::ElementFactory::make(
589             capsfilter => 'the_proud_lord_said' );
590             my $sink = GStreamer1::ElementFactory::make(
591             fakesink => 'that_i_should_bow_so_low' );
592              
593             my $muxer = ($type ne 'video/H264')
594             ? $self->_get_vid_mux_by_type( $type )
595             : undef;
596              
597             $rpi->set( bitrate => $bitrate );
598              
599             my $caps = GStreamer1::Caps::Simple->new( 'video/x-h264',
600             width => 'Glib::Int' => $width,
601             height => 'Glib::Int' => $height,
602             fps => 'Glib::Int' => $fps,
603             );
604             $capsfilter->set( caps => $caps );
605              
606             $sink->set( 'signal-handoffs' => TRUE );
607             $sink->signal_connect(
608             'handoff' => $self->_get_vid_stream_callback( $pipeline, $cv, $callback )
609             );
610              
611             my @link = ( $rpi, $h264parse, $capsfilter );
612             push @link, $muxer if defined $muxer;
613             push @link, $sink;
614             $pipeline->add( $_ ) for @link;
615             foreach my $i (0 .. ($#link - 1)) {
616             my $this = $link[$i];
617             my $next = $link[$i+1];
618             $this->link( $next );
619             }
620              
621             $pipeline->set_state( "playing" );
622             $cv->recv;
623             $pipeline->set_state( "null" );
624              
625             return 1;
626             }
627              
628              
629             sub _get_vid_stream_callback
630             {
631             my ($self, $pipeline, $cv, $callback) = @_;
632              
633             my $full_callback = sub {
634             my ($sink, $data_buf, $pad) = @_;
635             my $size = $data_buf->get_size;
636             my $buf = $data_buf->extract_dup( 0, $size, undef, $size );
637              
638             $callback->( $buf );
639              
640             return 1;
641             };
642              
643             return $full_callback;
644             }
645              
646             my %MUXER_BY_TYPE = (
647             'video/x-msvideo' => [
648             'avimux', {},
649             ],
650             # 'video/mp4' => [
651             # 'mp4mux', {
652             # streamable => TRUE,
653             # },
654             # ],
655             );
656             sub _get_vid_mux_by_type
657             {
658             my ($self, $type) = @_;
659             my ($muxer_name, $properties) = @{ $MUXER_BY_TYPE{$type} };
660             my $muxer = GStreamer1::ElementFactory::make( $muxer_name => 'muxer' );
661              
662             for (keys %$properties) {
663             $muxer->set( $_ => $properties->{$_} );
664             }
665              
666             return $muxer;
667             }
668              
669             sub _pin_desc_rev1
670             {
671             return [qw{
672             V33 V50 2 V50 3 GND 4 14 GND 15 17 18 27 GND 22 23 V33 24 10 GND 9 25
673             11 8 GND 7
674             }];
675             }
676              
677             sub _pin_desc_rev2
678             {
679             return [qw{
680             V33 V50 2 V50 3 GND 4 14 GND 15 17 18 27 GND 22 23 V33 24 10 GND 9 25
681             11 8 GND 7
682             }];
683             }
684              
685             sub _pin_desc_model_b_plus
686             {
687             return [qw{
688             V33 V50 2 V50 3 GND 4 14 GND 15 17 18 27 GND 22 23 V33 24 10 GND 9 25
689             11 8 GND 7 GND GND 5 GND 6 12 13 GND 19 16 26 20 GND 21
690             }];
691             }
692              
693              
694             sub _rpi_pin_to_wiring
695             {
696             my ($self, $rpi_pin) = @_;
697             my $pin = $self->_pin_map->{$rpi_pin} // -1;
698             return $pin;
699             }
700              
701              
702             sub all_desc
703             {
704             my ($self) = @_;
705             my $pin_count = $self->input_pin_count;
706              
707             my %data = (
708             UART => 0,
709             SPI => 0,
710             I2C => 0,
711             ONEWIRE => 0,
712             GPIO => {
713             map {
714             my $function = $self->is_set_input( $_ ) ? 'IN'
715             : $self->is_set_output( $_ ) ? 'OUT'
716             : 'UNSET';
717             my $value = $function eq 'IN'
718             ? $self->input_pin( $_ )
719             : $self->_output_pin_value->[$_];
720             (defined $value)
721             ? (
722             $_ => {
723             function => $function,
724             value => $value,
725             }
726             )
727             : ();
728             } 0 .. ($pin_count - 1)
729             },
730             );
731              
732             return \%data;
733             }
734              
735              
736             sub _init_gstreamer
737             {
738             my ($self) = @_;
739             return 1 if $self->_is_gstreamer_inited;
740             GStreamer1::init([ $0, @ARGV ]);
741             $self->_is_gstreamer_inited( 1 );
742             return 1;
743             }
744              
745              
746             with 'Device::WebIO::Device::SPI';
747              
748             my @CHANNEL_NAMES = sort HiPi::Device::SPI->get_device_list;
749             has '_spi_channel_devs' => (
750             is => 'ro',
751             default => sub {{}},
752             );
753              
754              
755             sub spi_channels { scalar @CHANNEL_NAMES }
756              
757             sub spi_set_speed
758             {
759             my ($self, $channel, $speed) = @_;
760             my $dev = $self->_spi_get_dev( $channel, $speed );
761             $dev->set_bus_maxspeed( $speed );
762             return 1;
763             }
764              
765             sub spi_read
766             {
767             my ($self, $channel, $len) = @_;
768             my $dev = $self->_spi_get_dev( $channel );
769             my $buf = pack 'C*', ((0x00) x $len);
770             my $recv = $dev->transfer( $buf );
771             return [ unpack 'C*', $recv ];
772             }
773              
774             sub spi_write
775             {
776             my ($self, $channel, $data) = @_;
777             my $dev = $self->_spi_get_dev( $channel );
778             $dev->transfer( $data );
779             return 1;
780             }
781              
782             sub _spi_get_dev
783             {
784             my $self = shift;
785             my $channel = shift;
786             my $channel_name = $CHANNEL_NAMES[$channel];
787             return $self->_spi_channel_devs->{$channel_name}
788             if exists $self->_spi_channel_devs->{$channel_name};
789              
790             my $speed = @_
791             ? shift
792             : SPI_SPEED_KHZ_500;
793              
794             my $dev = HiPi::Device::SPI->new(
795             devicename => $channel_name,
796             speed => $speed,
797             );
798             $self->_spi_channel_devs->{$channel_name} = $dev;
799             return $dev;
800             }
801              
802              
803             # TODO
804             #with 'Device::WebIO::Device::Serial';
805              
806             1;
807             __END__