File Coverage

blib/lib/UAV/Pilot/WumpusRover/Server.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package UAV::Pilot::WumpusRover::Server;
2 1     1   700 use v5.14;
  1         3  
  1         33  
3 1     1   384 use Moose;
  0            
  0            
4             use namespace::autoclean;
5             use IO::Socket::INET ();
6             use UAV::Pilot::WumpusRover::PacketFactory;
7             use UAV::Pilot::WumpusRover::Server::Backend;
8             use Time::HiRes ();
9             use Errno qw(:POSIX);
10              
11             use constant BUF_LENGTH => 1024;
12             use constant SLEEP_LOOP_US => 1_000_000 / 100; # In microseconds
13              
14             our $VERSION = 0.2;
15              
16              
17             has 'listen_port' => (
18             is => 'ro',
19             isa => 'Int',
20             default => 49_000,
21             );
22             has 'backend' => (
23             is => 'ro',
24             isa => 'UAV::Pilot::WumpusRover::Server::Backend',
25             );
26             has '_socket' => (
27             is => 'rw',
28             isa => 'Maybe[IO::Socket::INET]',
29             );
30             has 'ch1_max' => (
31             is => 'ro',
32             isa => 'Int',
33             default => 100,
34             writer => '_set_ch1_max',
35             );
36             has 'ch1_min' => (
37             is => 'ro',
38             isa => 'Int',
39             default => -100,
40             writer => '_set_ch1_min',
41             );
42             has 'ch2_max' => (
43             is => 'ro',
44             isa => 'Int',
45             default => 90,
46             writer => '_set_ch2_max',
47             );
48             has 'ch2_min' => (
49             is => 'ro',
50             isa => 'Int',
51             default => -90,
52             writer => '_set_ch2_min',
53             );
54             has 'ch3_max' => (
55             is => 'ro',
56             isa => 'Int',
57             default => 100,
58             writer => '_set_ch3_max',
59             );
60             has 'ch3_min' => (
61             is => 'ro',
62             isa => 'Int',
63             default => -100,
64             writer => '_set_ch3_min',
65             );
66             has 'ch4_max' => (
67             is => 'ro',
68             isa => 'Int',
69             default => 100,
70             writer => '_set_ch4_max',
71             );
72             has 'ch4_min' => (
73             is => 'ro',
74             isa => 'Int',
75             default => -100,
76             writer => '_set_ch4_min',
77             );
78             has 'ch5_max' => (
79             is => 'ro',
80             isa => 'Int',
81             default => 100,
82             writer => '_set_ch5_max',
83             );
84             has 'ch5_min' => (
85             is => 'ro',
86             isa => 'Int',
87             default => -100,
88             writer => '_set_ch5_min',
89             );
90             has 'ch6_max' => (
91             is => 'ro',
92             isa => 'Int',
93             default => 100,
94             writer => '_set_ch6_max',
95             );
96             has 'ch6_min' => (
97             is => 'ro',
98             isa => 'Int',
99             default => -100,
100             writer => '_set_ch6_min',
101             );
102             has 'ch7_max' => (
103             is => 'ro',
104             isa => 'Int',
105             default => 100,
106             writer => '_set_ch7_max',
107             );
108             has 'ch7_min' => (
109             is => 'ro',
110             isa => 'Int',
111             default => -100,
112             writer => '_set_ch7_min',
113             );
114             has 'ch8_max' => (
115             is => 'ro',
116             isa => 'Int',
117             default => 100,
118             writer => '_set_ch8_max',
119             );
120             has 'ch8_min' => (
121             is => 'ro',
122             isa => 'Int',
123             default => -100,
124             writer => '_set_ch8_min',
125             );
126              
127             with 'UAV::Pilot::Server';
128             with 'UAV::Pilot::Logger';
129              
130              
131             sub start_listen_loop
132             {
133             my ($self) = @_;
134             $self->_init_socket;
135              
136             my $CONTINUE = 1;
137             while($CONTINUE) {
138             if(! $self->_read_packet ) {
139             # If we didn't read a packet, sleep for a while
140             Time::HiRes::usleep( $self->SLEEP_LOOP_US );
141             }
142             }
143              
144             return 1;
145             }
146              
147             sub process_packet
148             {
149             my ($self, $packet) = @_;
150             my $backend = $self->backend;
151              
152             my $process = sub {
153             if( $backend->process_packet($packet, $self) ) {
154             my $ack = $self->_build_ack_packet( $packet );
155             $self->_send_packet( $ack );
156             }
157             };
158              
159             if(! $backend->started) {
160             if( $packet->isa(
161             'UAV::Pilot::WumpusRover::Packet::RequestStartupMessage' )) {
162             $process->();
163             }
164             else {
165             $self->_logger->warn( 'Recieved packet of type "' . ref( $packet )
166             . '", but we need a RequestStartupMessage first' );
167             }
168             }
169             else {
170             $process->();
171             }
172              
173             return 1;
174             }
175              
176             sub _read_packet
177             {
178             my ($self) = @_;
179             my $logger = $self->_logger;
180             my $return = 1;
181             $logger->info( 'Received packet' );
182              
183             my $buf = undef;
184             my $len = read( $self->_socket, $buf, $self->BUF_LENGTH );
185             if( defined($len) && ($len > 0) ) {
186             my $len = length $buf;
187             $logger->info( "Read $len bytes" );
188             my $packet = eval {
189             my $packet = UAV::Pilot::WumpusRover::PacketFactory
190             ->read_packet( $buf );
191             $logger->info( 'Processing message ID: '
192             . $packet->message_id . ' (type: ' . ref($packet) . ')' );
193             $self->process_packet( $packet );
194             };
195             if( ref($@) ) {
196             if( $@->isa( 'UAV::Pilot::ArdupilotPacketException::Badheader' ) ) {
197             $self->_logger->warn(
198             'Bad header in packet: [' . $@->got_header . ']' );
199             }
200             elsif( $@->isa(
201             'UAV::Pilot::ArdupilotPacketException::BadChecksum'
202             )) {
203             $self->_logger->warn( 'Bad checksum in packet' );
204             $self->_logger->warn( 'Expected checksum: '
205             . $@->expected_checksum1 . ', ' . $@->expected_checksum2 );
206             $self->_logger->warn( 'Got checksum: '
207             . $@->got_checksum1 . ', ' . $@->got_checksum2 );
208             }
209             else {
210             my $is_ref = ref $@;
211             $self->_logger->warn( 'Got exception while processing packet: '
212             . $is_ref ? $is_ref : $@ );
213             $@->rethrow if $is_ref;
214             }
215             }
216             elsif( $@ ) {
217             die "Error processing packet: $@\n";
218             }
219             }
220             elsif(! defined $len) {
221             # Possible error
222             if($!{EAGAIN} || $!{EWOULDBLOCK}) {
223             $logger->info( 'No data to read' );
224             }
225             else {
226             UAV::Pilot::IOException->throw({
227             error => $!,
228             });
229             }
230             }
231             else {
232             $return = 0;
233             $logger->info( "No data to read" );
234             }
235              
236             return $return;
237             }
238              
239             sub _build_ack_packet
240             {
241             my ($self, $packet) = @_;
242              
243             my $ack = UAV::Pilot::WumpusRover::PacketFactory->fresh_packet( 'Ack' );
244             $ack->message_received_id( $packet->message_id );
245             $ack->checksum_received1( $packet->checksum1 );
246             $ack->checksum_received2( $packet->checksum2 );
247              
248             return $ack;
249             }
250              
251             sub _send_packet
252             {
253             my ($self, $packet) = @_;
254             # TODO
255             return 1;
256             }
257              
258             sub _init_socket
259             {
260             my ($self) = @_;
261             $self->_logger->info( 'Starting listener on UDP port '
262             . $self->listen_port );
263              
264             my $socket = IO::Socket::INET->new(
265             Proto => 'udp',
266             LocalPort => $self->listen_port,
267             Blocking => 0,
268             ) or UAV::Pilot::IOException->throw({
269             error => 'Could not open socket: ' . $!,
270             });
271             $self->_socket( $socket );
272              
273             $self->_logger->info( 'Done starting listener' );
274             return 1;
275             }
276              
277             sub _map_value
278             {
279             my ($self, $in_min, $in_max, $out_min, $out_max, $input) = @_;
280             return 0 if $in_max - $in_min == 0; # Avoid divide-by-zero error
281             my $output = ($input - $in_min) / ($in_max - $in_min)
282             * ($out_max - $out_min) + $out_min;
283              
284             return $output;
285             }
286              
287              
288             no Moose;
289             __PACKAGE__->meta->make_immutable;
290             1;
291             __END__
292              
293              
294             =head1 NAME
295              
296             UAV::Pilot::WumpusRover::Server
297              
298             =head1 SYNOPSIS
299              
300             my $backend = UAV::Pilot::WumpusRover::Server::Backend::RaspberryPiI2C->new;
301             my $server = UAV::Pilot::WumpusRover::Server->new({
302             backend => $backend,
303             });
304             $server->start_listen_loop;
305              
306             =head1 DESCRIPTION
307              
308             A server for running the WumpusRover. Listens on specified UDP port,
309             defaulting to C<<UAV::Pilot::WumpusRover->DEFAULT_PORT>>.
310              
311             =head1 METHODS
312              
313             =head2 start_listen_loop
314              
315             Starts listening on the UDP port. Loops indefinitely.
316              
317             =head2 process_packet
318              
319             process_packet( $packet )
320              
321             Does the right thing with C<$packet> (a C<UAV::Pilot::WumpusRover::Packet>
322             object).
323              
324             =head2 ch*_min() and ch*_max()
325              
326             The channel min/max values that you can set. Channels are numbered 1 through 8.
327              
328             Note that these are the min/max values that are input to the server. The
329             values output by the backend is set by the backend.
330              
331             =head1 PROTECTED METHODS
332              
333             =head2 _set_ch*_min( $value ) and _set_ch*_max( $value )
334              
335             Sets the raw min/max value for the associated channel number. Channels are
336             numbered 1 through 8.
337              
338             =head2 _map_value
339              
340             _map_value(
341             $in_min, $in_max,
342             $out_min, $out_max,
343             $input,
344             )
345              
346             Given the input min/max settings, maps the input number to an equivalent
347             output between the output min/max. For instance:
348              
349             $self->_map_value(
350             0, 10,
351             0, 30,
352             5,
353             );
354              
355             Would return 15.
356              
357             Note that this returns 0 if C<$in_max - $in_min == 0>, which avoids a
358             divide-by-zero error. This isn't correct behavior and will be fixed Soon(tm).
359             The output min/max settings don't have this problem.
360              
361             The primary use of this method is for backends to map the channel values held
362             by the Server object into the output needed by the backend connection.
363              
364             =head1 SETTING UP THE RASPBERRY PI CAMERA
365              
366             On Raspbian, follow the instructions below for installing the Raspicam v4l
367             driver:
368              
369             L<http://www.linux-projects.org/modules/sections/index.php?op=viewarticle&artid=16>
370              
371             =cut