File Coverage

blib/lib/Device/Ericsson/AccessoryMenu.pm
Criterion Covered Total %
statement 51 98 52.0
branch 9 38 23.6
condition 6 20 30.0
subroutine 7 14 50.0
pod 7 11 63.6
total 80 181 44.2


line stmt bran cond sub pod time code
1 1     1   825 use strict;
  1         2  
  1         51  
2             package Device::Ericsson::AccessoryMenu;
3 1     1   4 use base 'Class::Accessor::Fast';
  1         2  
  1         751  
4             __PACKAGE__->mk_accessors( qw( states menu port debug callback ) );
5 1     1   3213 use vars qw( $VERSION );
  1         3  
  1         1065  
6             $VERSION = '0.8';
7              
8             =head1 NAME
9              
10             Device::Ericsson::AccessoryMenu - allows use of a T68i as a remote control
11              
12             =head1 SYNOPSIS
13              
14             my $remote = Device::Ericsson::AccessoryMenu->new;
15             $remote->menu( [ 'Remote' => [ pause => sub { ... },
16             Volume => [ up => sub { ... },
17             down => sub { ... },
18             ],
19             ],
20             ] );
21              
22             # on Win32, Win32::SerialPort should be equivalent
23             my $port = Device::SerialPort->new('/dev/rfcomm0')
24             or die "couldn't connect to T68i";
25             $remote->port( $port );
26              
27             $remote->register_menu;
28              
29             while (1) {
30             $remote->control;
31             }
32              
33             =head1 DESCRIPTION
34              
35             Device::Ericsson::AccessoryMenu provides a framework for adding an
36             accessory menu to devices that obey the EAM set of AT commands.
37              
38             This allows you to write programs with similar function to the Romeo
39             and Clicker applications for OSX, only instead of applescript your
40             actions invoke perl subroutines (which of course may invoke
41             applescript events, if that's your desire).
42              
43             =head1 METHODS
44              
45             =head2 new
46              
47             =cut
48              
49             sub new {
50 1     1 1 6933 my $class = shift;
51 1         13 $class->SUPER::new({ menu => [], @_ });
52             }
53              
54             =head2 menu
55              
56             your menus and actions.
57              
58             If your action is a subroutine, it will be invoked with the
59             Device::Ericsson::AccesoryMenu object as its first parameter.
60              
61             If the action returns a scalar, this is sent on to the phone via
62             C
63              
64             If your action is, or returns an array reference, then it's taken as a
65             sub menu.
66              
67             =head2 port
68              
69             The serial port to communicate over.
70              
71             This may be real serial port, or a bluetooth RFCOMM device, just so
72             long as it looks like a Device::SerialPort or Win32::SerialPort.
73              
74             =head2 send( $what )
75              
76             send bytes over the serial port to the phone
77              
78             =cut
79              
80             sub send {
81 4     4 1 10 my $self = shift;
82 4         5 my $what = shift;
83 4         10 my $count = $self->port->write( "$what\r" );
84 4         632 $self->port->write_drain;
85 4 50       43 print "# send '$what'\n" if $self->debug;
86 4         26 return $count == length $what;
87             }
88              
89              
90             # Lifted from Device::Modem
91             sub expect {
92 4     4 0 7 my $self = shift;
93 4         7 my ($expect, $timeout) = @_;
94              
95 4   100     16 $timeout ||= 2000;
96              
97 4         4 my $time_slice = 100; # single cycle wait time
98 4 50       8 $time_slice = 20 if $timeout < 200;
99 4         8 my $max_cycles = $timeout / $time_slice;
100 4         6 my $max_idle_cycles = $max_cycles;
101              
102             # If we expect something, we must first match against serial input
103 4         3 my $done;# = (defined $expect and $expect eq '');
104              
105             # Main read cycle
106 4         5 my ($answer, $cycles, $idle_cycles);
107 4         5 do {
108 4         10 my ($howmany, $what) = $self->port->read($time_slice);
109              
110             # Timeout count incremented only on empty readings
111 4 50 33     47 if ( defined $what && $howmany > 0 ) {
112 4         4 $answer .= $what;
113 4         5 $idle_cycles = 1;
114             #$max_idle_cycles = $max_cycles;
115             }
116             else {
117 0         0 ++$idle_cycles;
118             }
119              
120 4 50 33     65 ++$done if $expect && $answer && $answer =~ $expect;
      33        
121 4 50       22 ++$done if $idle_cycles >= $max_idle_cycles;
122 4 50       8 ++$done if ++$cycles >= $max_cycles;
123 4 50       16 select(undef, undef, undef, $time_slice/1000) unless $done;
124             } while ( not $done );
125              
126             # Flush receive and trasmit buffers
127 4         12 $self->port->purge_all;
128              
129             # Trim result of beginning and ending CR+LF (XXX)
130 4 50       29 if( defined $answer ) {
131 4         10 $answer =~ s/^[\r\n]+//;
132 4         15 $answer =~ s/[\r\n]+$//;
133             }
134              
135 4 50 33     11 print "# got '$answer'\n" if $self->debug && defined $answer;
136 4         161 return $answer;
137             }
138              
139              
140             =head2 register_menu
141              
142             Notify the phone that there's an accessory connected
143              
144             =cut
145              
146             sub register_menu {
147 1     1 1 539 my $self = shift;
148              
149 1         3 $self->states( [] );
150              
151             # Phone, Kree!
152 1         6 $self->send( "ATZ" );
153 1         3 $self->expect( "OK", 5000 );
154             # turn off echo
155 1         3 $self->send( "ATE=0" );
156 1         3 $self->expect( "OK" );
157 1         3 $self->send( 'AT*EAM="'. $self->menu->[0] . '"' );
158 1         3 $self->expect( "OK" );
159 1         2 $self->send( 'AT+CSCS="8859-1"' );
160 1         3 $self->expect( "OK" );
161              
162             }
163              
164             sub enter_state {
165 0     0 0   my $self = shift;
166 0           my $class = shift;
167              
168 0           $class = __PACKAGE__."::$class";
169 0 0         eval "require $class" or die $@;
170              
171 0           my $entering = $class->new( parent => $self, @_ );
172 0           unshift @{ $self->states }, $entering;
  0            
173              
174 0 0         print "entering $entering\n" if $self->debug;
175 0           $entering->on_enter;
176 0           return;
177             }
178              
179             sub exit_state {
180 0     0 0   my $self = shift;
181              
182 0           my $leaving = shift @{ $self->states };
  0            
183 0 0         print "leaving $leaving\n" if $self->debug;
184 0           $leaving->on_exit;
185 0           my ($current) = @{ $self->states };
  0            
186 0 0         $current->on_enter if $current;
187 0           return;
188             }
189              
190             sub current_state {
191 0     0 0   my $self = shift;
192 0           my ($state) = @{ $self->states };
  0            
193 0           return $state;
194             }
195              
196              
197             =head2 send_text( $title, @lines )
198              
199             Send the text as a message dialog and wait for user input.
200              
201             =cut
202              
203             sub send_text {
204 0     0 1   my $self = shift;
205 0           my $title = shift;
206 0 0         @_ = ($title) unless @_;
207              
208 0           $self->enter_state( 'Text', title => $title, lines => \@_ );
209             }
210              
211              
212             =head2 percent_slider( %args )
213              
214             %args = (
215             title => 'Slider',
216             steps => 10, # 1..10
217             value => 50,
218             callback => undef, # a subroutine ref, will be called with the new value
219             );
220              
221             =cut
222              
223             sub percent_slider {
224 0     0 1   my $self = shift;
225 0           my %args = @_;
226              
227 0 0         my $value = defined $args{value} ? $args{value}: 50;
228 0   0       $self->enter_state( 'Slider', ( title => $args{title} || 'Slider',
      0        
229             steps => $args{steps} || 10,
230             value => $value,
231             callback => $args{callback} ) );
232             }
233              
234             =head2 mouse_mode( %args )
235              
236             Put the T68i into a fullscan mode. Returns keyboard events for every
237             key pressed and released.
238              
239             %args = (
240             title => 'Mouse',
241             callback => sub ( $key, $updown ) {}, # will be called with the key and
242             # the updown event (1 = key
243             # down, 0 = key up)
244              
245             );
246              
247             =cut
248              
249             sub mouse_mode {
250 0     0 1   my $self = shift;
251 0           my %args = @_;
252              
253 0   0       $self->enter_state( 'Mouse', ( title => $args{title} || 'Mouse',
254             callback => $args{callback} ) );
255             }
256              
257              
258             =head2 control
259              
260             Respond to what the phone is sending back over the port, invoking
261             callbacks and all that jazz.
262              
263             =cut
264              
265             sub control {
266 0     0 1   my $self = shift;
267 0           my ($timeout) = @_;
268              
269             # $self->port->modemlines; may be the key to 'it's attached, it's
270             # not attached' stuff
271              
272 0           my $line = $self->expect("\r", $timeout);
273 0 0         return unless $line;
274              
275 0 0         print "# control '$line'\n" if $self->debug;
276              
277 0 0         if ( my $state = $self->current_state ) {
278 0           $state->handle( $line );
279 0           return;
280             }
281              
282 0 0         if ($line =~ /EAAI/) { # top level menu
283 0           $self->enter_state( 'Menu', data => $self->menu );
284 0           return;
285             }
286              
287 0           warn "control got unexpected '$line'\n";
288             }
289              
290             1;
291             __END__