File Coverage

blib/lib/Linux/IRPulses.pm
Criterion Covered Total %
statement 102 116 87.9
branch 27 34 79.4
condition 10 15 66.6
subroutine 16 16 100.0
pod 4 7 57.1
total 159 188 84.5


line stmt bran cond sub pod time code
1             package Linux::IRPulses;
2             $Linux::IRPulses::VERSION = '0.7';
3 4     4   43902 use v5.14;
  4         12  
4 4     4   16 use warnings;
  4         9  
  4         107  
5 4     4   2246 use Moose;
  4         1276536  
  4         24  
6 4     4   20064 use namespace::autoclean;
  4         8  
  4         32  
7 4     4   247 use Moose::Exporter;
  4         6  
  4         22  
8              
9 4     4   136 use constant DEBUG => 0;
  4         4  
  4         4571  
10              
11             # ABSTRACT: Parse LIRC pulse data
12              
13             Moose::Exporter->setup_import_methods(
14             as_is => [ 'pulse', 'space', 'pulse_or_space' ],
15             );
16 7     7 0 2971 sub pulse ($) {[ 'pulse', $_[0] ]}
17 7     7 0 112 sub space ($) {[ 'space', $_[0] ]}
18 2     2 0 49 sub pulse_or_space ($) {[ 'either', $_[0] ]}
19              
20              
21             has 'fh' => (
22             is => 'ro',
23             );
24             has 'header' => (
25             traits => ['Array'],
26             is => 'ro',
27             isa => 'ArrayRef[ArrayRef[Str]]',
28             required => 1,
29             handles => {
30             header_length => 'count',
31             },
32             );
33             has 'zero' => (
34             traits => ['Array'],
35             is => 'ro',
36             isa => 'ArrayRef[ArrayRef[Str]]',
37             required => 1,
38             handles => {
39             zero_length => 'count',
40             },
41             );
42             has 'one' => (
43             traits => ['Array'],
44             is => 'ro',
45             isa => 'ArrayRef[ArrayRef[Str]]',
46             required => 1,
47             handles => {
48             one_length => 'count',
49             },
50             );
51             has 'bit_count' => (
52             is => 'ro',
53             isa => 'Int',
54             required => 1,
55             );
56             has '_bits' => (
57             is => 'rw',
58             isa => 'Int',
59             default => 0,
60             );
61             has 'tolerance' => (
62             is => 'ro',
63             isa => 'Num',
64             required => 1,
65             default => 0.20,
66             );
67             has 'callback' => (
68             is => 'ro',
69             isa => 'CodeRef',
70             required => 1,
71             );
72             has '_do_close_file' => (
73             is => 'ro',
74             isa => 'Bool',
75             default => 0,
76             );
77             has '_do_end_loop' => (
78             is => 'rw',
79             isa => 'Bool',
80             default => 0,
81             );
82             has '_did_see_header' => (
83             is => 'rw',
84             isa => 'Bool',
85             default => 0,
86             );
87             has '_header_index' => (
88             traits => ['Number'],
89             is => 'rw',
90             isa => 'Int',
91             default => 0,
92             handles => {
93             _add_header_index => 'add',
94             },
95             );
96             has '_bit_count' => (
97             traits => ['Number'],
98             is => 'rw',
99             isa => 'Int',
100             default => 0,
101             handles => {
102             _add_bit_count => 'add',
103             },
104             );
105             has '_is_maybe_zero' => (
106             is => 'rw',
107             isa => 'Bool',
108             default => 1,
109             );
110             has '_is_maybe_one' => (
111             is => 'rw',
112             isa => 'Bool',
113             default => 1,
114             );
115             has '_zero_index' => (
116             traits => ['Number'],
117             is => 'rw',
118             isa => 'Int',
119             default => 0,
120             handles => {
121             _add_zero_index => 'add',
122             },
123             );
124             has '_one_index' => (
125             traits => ['Number'],
126             is => 'rw',
127             isa => 'Int',
128             default => 0,
129             handles => {
130             _add_one_index => 'add',
131             },
132             );
133              
134              
135             sub BUILDARGS
136             {
137 3     3 1 7 my ($class, $args) = @_;
138              
139 3 50       15 if( exists $args->{dev_file} ) {
140 0         0 my $file = delete $args->{dev_file};
141              
142 0 0       0 open( my $in, '<', $file ) or die "Can't open file '$file': $!\n";
143 0         0 $args->{fh} = $in;
144 0         0 $args->{'_do_close_file'} = 1;
145             }
146              
147 3         69 return $args;
148             }
149              
150              
151             sub run
152             {
153 2     2 1 13 my ($self) = @_;
154 2         60 my $in = $self->fh;
155              
156 2   66     59 while(
157             (! $self->_do_end_loop)
158             && (my $line = readline($in))
159             ) {
160 75         77 chomp $line;
161 75         97 $self->handle_line( $line );
162             }
163              
164 2 50       49 close $in if $self->_do_close_file;
165 2         6 return;
166             }
167              
168             sub end
169             {
170 2     2 1 1667 my ($self) = @_;
171 2         56 $self->_do_end_loop( 1 );
172 2         2 return;
173             }
174              
175              
176             sub handle_line
177             {
178 142     142 1 292 my ($self, $line) = @_;
179 142         99 warn "Matching: $line\n" if DEBUG;
180            
181 142 100       3116 if( $self->_did_see_header ) {
182 135         113 my $is_matched = 0;
183              
184 135 50       2831 if( $self->_is_maybe_zero() ) {
185 135 100       2704 if( $self->_match_line( $line, $self->zero->[$self->_zero_index] ) ) {
186 68         1875 $self->_add_zero_index(1);
187 68 100       1474 if( $self->_zero_index >= $self->zero_length ) {
188 4         3 warn "\tWe have a complete zero signal\n" if DEBUG;
189 4         90 $self->_zero_index(0);
190 4         87 $self->_one_index(0);
191 4         86 $self->_is_maybe_zero(1);
192 4         84 $self->_is_maybe_one(1);
193 4         108 $self->_add_bit_count(1);
194 4         94 $self->_bits( $self->_bits() << 1 | 0 );
195 4         4 $is_matched = 1;
196             }
197             else {
198 64         53 warn "\tWe might have a zero, but we're not sure so sit tight\n"
199             if DEBUG;
200             }
201             }
202             else {
203 67         48 warn "\tIt's definately not a zero\n" if DEBUG;
204 67         1510 $self->_is_maybe_zero( 0 );
205             }
206             }
207              
208 135 100 66     2955 if( (! $is_matched) && $self->_is_maybe_one() ) {
209 131 50       2518 if( $self->_match_line( $line, $self->one->[$self->_one_index] ) ) {
210 131         3558 $self->_add_one_index(1);
211 131 100       2755 if( $self->_one_index >= $self->one_length ) {
212             # We have a complete one signal, reset state
213 67         40 warn "\tWe have a complete one signal\n" if DEBUG;
214 67         1418 $self->_zero_index(0);
215 67         1406 $self->_one_index(0);
216 67         1409 $self->_is_maybe_zero(1);
217 67         1416 $self->_is_maybe_one(1);
218 67         1829 $self->_add_bit_count(1);
219 67         1360 $self->_bits( $self->_bits() << 1 | 1 );
220 67         72 $is_matched = 1;
221             }
222             else {
223             # Might be a one, but we're not sure yet, so sit tight
224 64         52 warn "\tWe might have a one, but we're not sure so sit tight\n"
225             if DEBUG;
226             }
227             }
228             else {
229 0         0 warn "\tIt's definately not a one\n" if DEBUG;
230 0         0 $self->_is_maybe_one( 0 );
231             }
232             }
233              
234 135 100 33     2787 if( $self->_bit_count >= $self->bit_count ) {
    50          
235 3         45 warn "\tWe met our bit count, so call the callback\n" if DEBUG;
236 3         70 $self->callback->({
237             pulse_obj => $self,
238             code => $self->_bits
239             });
240              
241 3         899 $self->_zero_index(0);
242 3         69 $self->_one_index(0);
243 3         67 $self->_is_maybe_zero(1);
244 3         70 $self->_is_maybe_one(1);
245 3         68 $self->_bit_count(0);
246 3         71 $self->_did_see_header(0);
247 3         65 $self->_bits(0);
248              
249             }
250             elsif( (! $self->_is_maybe_zero) && (! $self->_is_maybe_one) ) {
251 0         0 warn "\tWe've gotten to a bad state where nothing looks right. Resetting.\n"
252             if DEBUG;
253 0         0 $self->_zero_index(0);
254 0         0 $self->_one_index(0);
255 0         0 $self->_is_maybe_zero(1);
256 0         0 $self->_is_maybe_one(1);
257 0         0 $self->_bit_count(0);
258 0         0 $self->_did_see_header(0);
259 0         0 $self->_bit_count(0);
260             }
261             }
262             else {
263 7 100       169 if( $self->_match_line( $line, $self->header->[$self->_header_index] ) ) {
264 6         183 $self->_add_header_index(1);
265              
266 6 100       129 if( $self->_header_index >= $self->header_length ) {
267 3         4 warn "\tWe have a complete, valid header\n" if DEBUG;
268 3         69 $self->_did_see_header( 1 );
269 3         74 $self->_header_index( 0 );
270             }
271             else {
272 3         5 warn "\tHave a partial header, sit tight for now\n" if DEBUG;
273             }
274             }
275             else {
276 1         2 warn "\tThis isn't the part of the header we were expecting. Reset.\n" if DEBUG;
277 1         23 $self->_did_see_header( 0 );
278 1         22 $self->_header_index( 0 );
279             }
280             }
281              
282 142         1745 return;
283             }
284              
285             sub _match_line
286             {
287 273     273   270 my ($self, $line, $expect) = @_;
288 273         189 my ($expect_type, $expect_num) = @{ $expect };
  273         289  
289 273         171 warn "\tMatching '$line', expecting '$expect_type $expect_num'\n" if DEBUG;
290 273         606 my ($type, $num) = $line =~ /\A (pulse|space) \s+ (\d+) /x;
291 273 100       419 $expect_type = $type if $expect_type eq 'either';
292              
293             return (
294 273 100 66     310 $self->_is_value_in_range( $num, $expect_num )
295             && ($expect_type eq $type)
296             ) ? 1 : 0;
297             }
298              
299             sub _is_value_in_range
300             {
301 273     273   220 my ($self, $val, $target_val) = @_;
302 273         5473 my $tolerance = $self->tolerance;
303 273         350 my $min = $target_val - ($target_val * $tolerance);
304 273         215 my $max = $target_val + ($target_val * $tolerance);
305 273         197 warn "\tMatching $min <= $val <= $max\n" if DEBUG;
306 273 100 100     1535 return (($min <= $val) && ($val <= $max)) ? 1 : 0;
307             }
308              
309              
310 4     4   29 no Moose;
  4         7  
  4         22  
311             __PACKAGE__->meta->make_immutable;
312             1;
313             __END__
314              
315              
316             =encoding utf8
317              
318             =head1 NAME
319              
320             Linux::IRPulses - Parse IR data from LIRC
321              
322             =head1 SYNOPSIS
323              
324             use Linux::IRPulses; # exports pulse(), space(), and pulse_or_space()
325            
326             open( my $in, '-|', 'mode2' ) or die "Can't exec mode2: $!\n";
327            
328             my $ir = Linux::IRPulses->new({
329             fh => $in,
330             header => [ pulse 9000, space 4500 ],
331             zero => [ pulse 563, space 563 ],
332             one => [ pulse 563, space 1688 ],
333             bit_count => 32,
334             callback => sub {
335             my ($args) = @_;
336             my $ir = $args->{pulse_obj};
337             my $code = $args->{code};
338             ...
339             },
340             });
341             $ir->run;
342              
343             =head1 DESCRIPTION
344              
345             Parses the pulse/space data coming from LIRC. Note that this works at a little lower
346             level down the LIRC stack than usual. LIRC usually works by translating the pulses on
347             its own, mapping that to a button on a remote, and then mapping that to a command to
348             execute. If you want that, then look at L<Lirc::Client>.
349              
350             This module grabs the pulse data coming out of LIRC and then translates that to binary.
351             That lets you manipulate the raw encoding.
352              
353             =head1 HOW IR REMOTES WORK
354              
355             Perhaps not surprisingly, every company has their own weird way of encoding IR data.
356             This usually breaks down to sending a header followed by zeros and ones that are
357             encoded through sending pulses of different lengths. Everyone also has their own
358             frequency for sending data, although 36KHz is common. Your IR receiver module needs
359             to be set to the same frequency.
360              
361             The length for encoding pulses has to deal with the fact that in the real world,
362             the IR emitter and receiver won't shut off at exactly the right time. The pulse will
363             tend to be a bit longer than specified; I've seen as high as 18%. Parsing must therefore
364             allow a fudge factor in the exact numbers.
365              
366             =head1 EXPORTS
367              
368             The exports are to help you build a datastructure that the parser can use. In general,
369             remotes tend to start with a long header, then a space, then a series of pulses and
370             spaces.
371              
372             For example, NEC remotes start with a header that pulses (voltage high) for 9000μs,
373             followed by a space (voltage low) for 4500μs. After that, there are 32 bits. A zero is
374             sent by a pulse of 563μs followed by a space of 563μs. A one is sent by a pulse of
375             563μs followed by a space of 1688μs. We can build this in C<Linux::IRPulses> with:
376              
377             my $ir = Linux::IRPulses->new({
378             header => [ pulse 9000, space 4500 ],
379             zero => [ pulse 563, space 563 ],
380             one => [ pulse 563, space 1688 ],
381             ...
382             });
383              
384             Notice that the C<pulse()> and C<space()> exports help you to specify the datastructure.
385              
386             Another example is EasyRaceLapTimer, which is an IR-based timing system for quadcopter
387             FPV racing. To save on message time length, it encodes by the time of either the
388             pulses or the spaces. For example, a 0110 would be sent by a pulse of 300μs, a space
389             of 600μs, a pulse of 600μs, and a space of 300μs. That is, spaces and pulses always
390             alternate, and the time of the space or pulse tells you if it's a one or zero.
391              
392             To handle this, we use C<pulse_or_space()>:
393              
394             my $ir = Linux::IRPulses->new({
395             header => [ pulse 300, space 300 ],
396             zero => [ pulse_or_space 300 ],
397             one => [ pulse_or_space 600 ],
398             ...
399             });
400              
401             Which doesn't care if it comes across as a pulse or space, as long as the length is correct.
402              
403             =head1 METHODS
404              
405             =head2 new
406              
407             new({
408             fh => $fh,
409             header => [ pulse 9000, space 4500 ],
410             zero => [ pulse 563, space 563 ],
411             one => [ pulse 563, space 1688 ],
412             bit_count => 32,
413             callback => sub {
414             my ($args) = @_;
415             my $ir = $args->{pulse_obj};
416             my $code = $args->{code};
417             ...
418             },
419             });
420              
421             Constructor. The C<fh> argument is a filehandle that will be read for pulse data. In
422             general, this should be a filehandle open for reading that's piped from LIRC's C<mode2>
423             program. The C<bit_count> argument is the expected length of each message.
424              
425             The C<header>, C<zero>, and C<one> arguments are arrayrefs that specify the format of
426             the respective datapoint. The first data would match the first entry in C<header>, and
427             then matching each subsequent entry in turn. Once we reach the end of the C<headers>
428             list, we start matching C<zero> and C<one> in the same way. We continue matching
429             zeros and ones until we hit C<bit_count>. At that point, we consider the message complete
430             and pass the data to the subref in C<callback>.
431              
432             =head2 run
433              
434             Starts reading the data from the filehandle. The callback will be hit during this
435             process.
436              
437             =head2 handle_line
438              
439             handle_line( $line );
440              
441             Processes a single line of the forms:
442              
443             pulse 1000
444             space 2000
445              
446             Hits the callback if we gathered enough lines to get a full code. Be sure to C<chomp> the
447             line before passing.
448              
449             =head2 end
450              
451             Stops the process of reading from the filehandle, returning to normal execution flow
452             after the place C<run()> was called.
453              
454             =head1 LICENSE
455              
456             Copyright (c) 2016 Timm Murray
457             All rights reserved.
458              
459             Redistribution and use in source and binary forms, with or without modification, are
460             permitted provided that the following conditions are met:
461              
462             * Redistributions of source code must retain the above copyright notice, this list of
463             conditions and the following disclaimer.
464             * Redistributions in binary form must reproduce the above copyright notice, this list of
465             conditions and the following disclaimer in the documentation and/or other materials
466             provided with the distribution.
467              
468             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
469             OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
470             MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
471             COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
472             EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
473             SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
474             HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
475             TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
476             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
477              
478              
479             =cut