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