File Coverage

blib/lib/Device/XBee/API.pm
Criterion Covered Total %
statement 66 460 14.3
branch 0 212 0.0
condition 0 63 0.0
subroutine 22 53 41.5
pod 9 16 56.2
total 97 804 12.0


line stmt bran cond sub pod time code
1             package Device::XBee::API;
2              
3 1     1   7475 use strict;
  1         3  
  1         103  
4              
5             require Exporter;
6             our ( @ISA, @EXPORT_OK, %EXPORT_TAGS );
7              
8             our $VERSION = 0.7;
9              
10 1     1   1042 use IO::Select;
  1         1952  
  1         64  
11 1     1   8 use constant 1.01;
  1         44  
  1         30  
12 1     1   5 use constant XBEE_API_TYPE__MODEM_STATUS => 0x8A;
  1         3  
  1         80  
13 1     1   5 use constant XBEE_API_TYPE__AT_COMMAND => 0x08;
  1         2  
  1         42  
14 1     1   5 use constant XBEE_API_TYPE__AT_COMMAND_QUEUE_PARAMETER_VALUE => 0x09;
  1         2  
  1         52  
15 1     1   5 use constant XBEE_API_TYPE__AT_COMMAND_RESPONSE => 0x88;
  1         2  
  1         59  
16 1     1   6 use constant XBEE_API_TYPE__REMOTE_COMMAND_REQUEST => 0x17;
  1         1  
  1         41  
17 1     1   5 use constant XBEE_API_TYPE__REMOTE_COMMAND_RESPONSE => 0x97;
  1         2  
  1         51  
18 1     1   5 use constant XBEE_API_TYPE__ZIGBEE_TRANSMIT_REQUEST => 0x10;
  1         1  
  1         45  
19 1     1   5 use constant XBEE_API_TYPE__EXPLICIT_ADDRESSING_ZIGBEE_COMMAND_FRAME => 0x11;
  1         2  
  1         52  
20 1     1   5 use constant XBEE_API_TYPE__ZIGBEE_TRANSMIT_STATUS => 0x8B;
  1         2  
  1         38  
21 1     1   5 use constant XBEE_API_TYPE__ZIGBEE_RECEIVE_PACKET => 0x90;
  1         2  
  1         52  
22 1     1   10 use constant XBEE_API_TYPE__ZIGBEE_EXPLICIT_RX_INDICATOR => 0x91;
  1         2  
  1         46  
23 1     1   5 use constant XBEE_API_TYPE__ZIGBEE_IO_DATA_SAMPLE_RX_INDICATOR => 0x92;
  1         2  
  1         51  
24 1     1   4 use constant XBEE_API_TYPE__XBEE_SENSOR_READ_INDICATOR_ => 0x94;
  1         2  
  1         44  
25 1     1   4 use constant XBEE_API_TYPE__NODE_IDENTIFICATION_INDICATOR => 0x95;
  1         2  
  1         83  
26              
27 1         69 use constant XBEE_API_TYPE_TO_STRING => {
28             0x8A => 'MODEM_STATUS',
29             0x08 => 'AT_COMMAND',
30             0x09 => 'AT_COMMAND_QUEUE_PARAMETER_VALUE',
31             0x88 => 'AT_COMMAND_RESPONSE',
32             0x17 => 'REMOTE_COMMAND_REQUEST',
33             0x97 => 'REMOTE_COMMAND_RESPONSE',
34             0x10 => 'ZIGBEE_TRANSMIT_REQUEST',
35             0x11 => 'EXPLICIT_ADDRESSING_ZIGBEE_COMMAND_FRAME',
36             0x8B => 'ZIGBEE_TRANSMIT_STATUS',
37             0x90 => 'ZIGBEE_RECEIVE_PACKET',
38             0x91 => 'ZIGBEE_EXPLICIT_RX_INDICATOR',
39             0x92 => 'ZIGBEE_IO_DATA_SAMPLE_RX_INDICATOR',
40             0x94 => 'XBEE_SENSOR_READ_INDICATOR_',
41             0x95 => 'NODE_IDENTIFICATION_INDICATOR',
42 1     1   5 };
  1         2  
43              
44 1     1   5 use constant XBEE_API_BAUD_RATE_TABLE => [1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200];
  1         2  
  1         40  
45              
46 1     1   5 use constant XBEE_API_BROADCAST_ADDR_H => 0x00;
  1         1  
  1         45  
47 1     1   4 use constant XBEE_API_BROADCAST_ADDR_L => 0xFFFF;
  1         2  
  1         36  
48 1     1   4 use constant XBEE_API_BROADCAST_NA_UNKNOWN_ADDR => 0xFFFE;
  1         2  
  1         6148  
49              
50             {
51             my @xbee_flags = map { /::([^:]+)$/; $1 }
52             grep( /^Device::XBee::API::XBEE_API_/, keys( %constant::declared ) );
53              
54             @ISA = ( 'Exporter' );
55             @EXPORT_OK = ( @xbee_flags );
56              
57             %EXPORT_TAGS = ( 'xbee_flags' => [@xbee_flags], );
58             }
59              
60             =head1 NAME
61              
62             Device::XBee::API - Object-oriented Perl interface to Digi XBee module API
63             mode.
64              
65             =head1 EXAMPLE
66              
67             A basic example:
68              
69             use Device::SerialPort;
70             use Device::XBee::API;
71             use Data::Dumper;
72             $Data::Dumper::Useqq = 1;
73              
74             my $serial_port_device = Device::SerialPort->new( '/dev/ttyU0' ) || die $!;
75             $serial_port_device->baudrate( 9600 );
76             $serial_port_device->databits( 8 );
77             $serial_port_device->stopbits( 1 );
78             $serial_port_device->parity( 'none' );
79             $serial_port_device->read_char_time( 0 ); # don't wait for each character
80             $serial_port_device->read_const_time( 1000 ); # 1 second per unfulfilled "read" call
81              
82             my $api = Device::XBee::API->new( { fh => $serial_port_device } ) || die $!;
83             if ( !$api->tx( { sh => 0, sl => 0 }, 'hello world!' ) ) {
84             die "Transmit failed!";
85             }
86             my $rx = $api->rx();
87             die Dumper( $rx );
88              
89             =head1 SYNOPSIS
90              
91             Device::XBee::API is a module designed to encapsulate the Digi XBee API in
92             object-oriented Perl. This module expects to communicate with an XBee module
93             using the API firmware via a serial (or serial over USB) device.
94              
95             This module is currently a work in progress and thus the API may change in the
96             future.
97              
98             =head1 LICENSE
99              
100             This module is licensed under the same terms as Perl itself.
101              
102             =head1 CONSTANTS
103              
104             A single set of constants, ':xbee_flags', can be imported. These constants
105             all represent various XBee flags, such as packet types and broadcast addresses.
106             See the XBee datasheet for details. The following constants are available:
107              
108             XBEE_API_TYPE__MODEM_STATUS
109             XBEE_API_TYPE__AT_COMMAND
110             XBEE_API_TYPE__AT_COMMAND_QUEUE_PARAMETER_VALUE
111             XBEE_API_TYPE__AT_COMMAND_RESPONSE
112             XBEE_API_TYPE__REMOTE_COMMAND_REQUEST
113             XBEE_API_TYPE__REMOTE_COMMAND_RESPONSE
114             XBEE_API_TYPE__ZIGBEE_TRANSMIT_REQUEST
115             XBEE_API_TYPE__EXPLICIT_ADDRESSING_ZIGBEE_COMMAND_FRAME
116             XBEE_API_TYPE__ZIGBEE_TRANSMIT_STATUS
117             XBEE_API_TYPE__ZIGBEE_RECEIVE_PACKET
118             XBEE_API_TYPE__ZIGBEE_EXPLICIT_RX_INDICATOR
119             XBEE_API_TYPE__ZIGBEE_IO_DATA_SAMPLE_RX_INDICATOR
120             XBEE_API_TYPE__XBEE_SENSOR_READ_INDICATOR_
121             XBEE_API_TYPE__NODE_IDENTIFICATION_INDICATOR
122            
123             XBEE_API_BROADCAST_ADDR_H
124             XBEE_API_BROADCAST_ADDR_L
125             XBEE_API_BROADCAST_NA_UNKNOWN_ADDR
126            
127             XBEE_API_TYPE_TO_STRING
128             XBEE_API_BAUD_RATE_TABLE
129              
130             The above should be self explanatory (with the help of the datasheet). The
131             constant "XBEE_API_TYPE_TO_STRING" is a hashref keyed by the numeric id of the
132             packet type with the value being the constant name, to aid in debugging. The
133             constant XBEE_API_BAUD_RATE_TABLE is the baud rate table used by the BD API
134             command.
135              
136             =head1 METHODS
137              
138             =head2 new
139              
140             Object constructor. Accepts a single parameter, a hashref of options. The
141             following options are recognized:
142              
143             =head3 fh
144              
145             Required. The filehandle to be used to communicate with. This object can be a
146             standard filehandle (that can be accessed via sysread() and syswrite()), or a
147             Device::SerialPort object.
148              
149             =head3 packet_timeout
150              
151             Optional, defaults to 20. Amount of time (in seconds) to wait for a read to
152             complete. Smaller values cause the module to wait less time for a packet to be
153             received by the XBee module. Setting this value too low will cause timeouts to
154             be reported in situations where the network is "slow".
155              
156             When using standard filehandles, the timeout is implemented via select(). When
157             using a Device::SerialPort object, the timeout is done via Device::SerialPort's
158             read() method, and will expect the object to be configured with a
159             read_char_time of 0 and a read_const_time of 1000.
160              
161             =head3 node_forget_time
162              
163             If a node has not been heard from in this time, it will be "forgotten" and
164             removed from the list of known nodes. Defaults to one hour. See L
165             for details.
166              
167             =head3 auto_reuse_frame_id
168              
169             All sent packets need a frame ID to uniquely identify them. There are only 254
170             available IDs and thus there can only be 254 outstanding commands sent to the
171             XBee. Normally frame IDs will be freed and reused once a command reply is
172             received, however there are scenarios where this can not be done (generally
173             those that involve local or remote AT commands, sleeping/offline nodes, etc).
174              
175             Normally, when no frame IDs are available but one is needed, the module will
176             die with an error and the send attempt will be aborted. This condition could be
177             trapped by the caller (via eval) to retry later, or could be treated as fatal.
178              
179             With this flag set, instead of dieing, the oldest frame ID will be reused. This
180             will help work around any issues with frame ID's "leaking", but could cause odd
181             behavior in cases where all outstanding frame IDs are still in use. This option
182             should be used with caution.
183              
184             =head3 alloc_frame_id_func
185             =head3 free_frame_id_func
186              
187             Optional code references to functions used to allocate and free frame IDs. If
188             both are specified they will be called in place of the internal frame ID
189             tracking functions allowing the user more control over how frame IDs are
190             generated. The alloc_frame_id_func will be called when a new frame ID is needed
191             and will be passed as a parameter the reference to the Device:XBee::API object
192             and must return an integer between 1 and 255 inclusive. The free_frame_id_func
193             will be called when the reply frame is received and the frame ID is no longer
194             needed and will be passed as parameters a reference to the Device::XBee::API
195             obect and the frame ID to be freed.
196              
197             See L for details on how this module uses frame IDs.
198              
199             =head3 api_mode_escape
200              
201             Optional. If set to a true value, the module will automatically escape outgoing
202             data and un-escape incoming data for use with XBee API mode 2. Defaults to
203             false.
204              
205             See the XBee datasheet for details on API mode 2 and escaped characters.
206              
207             =cut
208              
209             sub new {
210 0     0 1   my ( $class, $options ) = @_;
211 0           my $self = {};
212              
213 0 0         die "Missing file handle!" unless $options->{'fh'};
214 0           $self->{fh} = $options->{fh};
215 0   0       $self->{packet_wait_time} = $options->{packet_timeout} || 20;
216 0   0       $self->{node_forget_time} = $options->{node_forget_time} || 60 * 60;
217 0 0         $self->{auto_reuse_frame_id} = $options->{auto_reuse_frame_id} ? 1 : 0;
218 0 0         $self->{api_mode_escape} = $options->{api_mode_escape} ? 1 : 0;
219              
220 0 0 0       if ( $options->{alloc_frame_id_func} && $options->{free_frame_id_func} ) {
221 0           $self->{alloc_frame_id_func} = $options->{alloc_frame_id_func};
222 0           $self->{free_frame_id_func} = $options->{free_frame_id_func};
223             }
224              
225 0           $self->{in_flight_uart_frames} = {};
226 0           $self->{known_nodes} = {};
227 0           $self->{rx_queue} = [];
228              
229 0 0 0       if ( ( ref $self->{fh} ne 'Device::SerialPort' )
230             && ( ref $self->{fh} ne 'Win32::SerialPort' ) )
231             {
232 0   0       $self->{fh_sel} = IO::Select->new( $self->{fh} )
233             || die "Failed to initialize IO::Select!";
234             }
235              
236 0 0         if ( $self->{api_mode_escape} ) {
237 0           $self->{api_mode_escape_table} = {};
238 0           $self->{api_mode_unescape_table} = {};
239             # Note the unescape re starts with the escape character.
240 0           $self->{api_mode_escape_re} = "([";
241 0           $self->{api_mode_unescape_re} = "\x7D([";
242             # List of characters taken from XBee datasheet.
243 0           foreach my $e ( 0x7E, 0x7D, 0x11, 0x13 ) {
244 0           my $chr_e = chr( $e );
245 0           my $chr_e_20 = chr( $e ^ 0x20 );
246 0           $self->{api_mode_escape_table}->{$chr_e} = $chr_e_20;
247 0           $self->{api_mode_unescape_table}->{$chr_e_20} = $chr_e;
248 0           $self->{api_mode_escape_re} .= quotemeta( $chr_e );
249 0           $self->{api_mode_unescape_re} .= quotemeta( $chr_e_20 );
250             }
251              
252             # Note the trailing "])" to terminate the character class.
253 0           $self->{api_mode_escape_re} = qr/$self->{api_mode_escape_re}])/;
254 0           $self->{api_mode_unescape_re} = qr/$self->{api_mode_unescape_re}])/;
255             }
256              
257 0           bless $self, $class;
258 0           return $self;
259             }
260              
261             sub read_bytes {
262 0     0 0   my ( $self, $to_read ) = @_;
263 0 0         die unless $to_read;
264 0           my $chars = 0;
265 0           my $buffer = '';
266 0           my $timeout = $self->{packet_wait_time};
267              
268 0 0         if ( !$self->{fh_sel} ) {
269 0           while ( $timeout > 0 ) {
270 0           my ( $count, $saw ) = $self->{fh}->read( $to_read ); # will read _up to_ 255 chars
271 0 0         if ( !defined $count ) {
272 0           die "Error reading from device: $!";
273             }
274 0 0         if ( $count > 0 ) {
275 0           $chars += $count;
276 0           $buffer .= $saw;
277 0 0         if ( $chars >= $to_read ) { return $buffer; }
  0            
278             } else {
279 0           $timeout--;
280             }
281             }
282             } else {
283 0           my $read;
284 0           my $start_ts = time();
285 0           while ( $to_read > 0 ) {
286 0 0         if ( !$self->{fh_sel}->can_read( $timeout ) ) {
287 0           return undef;
288             }
289 0           my $c = sysread( $self->{fh}, $read, $to_read );
290 0 0         if ( $c ) {
291 0           $buffer .= $read;
292 0           $to_read -= $c;
293 0           $timeout = $self->{packet_wait_time} - ( time() - $start_ts );
294 0 0 0       if ( $timeout < 1 && $to_read > 0 ) { return undef; }
  0            
295             } else {
296 0           return undef;
297             }
298             }
299 0           return $buffer;
300             }
301 0           return undef;
302             }
303              
304             sub read_packet {
305 0     0 0   my ( $self ) = @_;
306 0           my $d;
307             my $packet_data_length;
308              
309 0           do {
310 0           $d = $self->read_bytes( 1 );
311 0 0         return undef if !defined $d;
312             } while ( $d ne "\x7E" );
313              
314 0 0         if ( $self->{api_mode_escape} ) {
315 0           ( $packet_data_length, $d ) = $self->read_escaped_packet();
316             } else {
317 0           $d = $self->read_bytes( 2 );
318              
319 0           ( $packet_data_length ) = unpack( 'n', $d );
320              
321 0           $d = $self->read_bytes( $packet_data_length + 1 );
322 0 0         if ( !$d ) {
323 0           return undef;
324             }
325             }
326              
327 0           $packet_data_length--;
328              
329 0           my ( $packet_api_id, $packet_data, $packet_checksum ) = unpack( "Ca[$packet_data_length]C", $d );
330 0           my $validate_checksum = $packet_api_id + $packet_checksum;
331 0           for ( my $i = 0; $i < $packet_data_length; $i++ ) {
332 0           $validate_checksum += unpack( 'c', substr( $packet_data, $i, 1 ) );
333             }
334              
335 0 0         if ( ( $validate_checksum & 0xFF ) != 0xFF ) {
336             #warn "Invalid checksum!";
337 0           return undef;
338             }
339              
340 0           return ( $packet_api_id, $packet_data );
341             }
342              
343             sub read_escaped_packet {
344 0     0 0   my ( $self ) = @_;
345              
346 0           my $l1 = $self->read_bytes( 1 );
347 0 0         return unless defined $l1;
348              
349 0 0         if ( $l1 eq "\x7D" ) {
350 0           $l1 = $self->read_bytes( 1 );
351 0 0         return unless defined $l1;
352 0           $l1 ^= "\x20";
353             }
354              
355 0           my $l2 = $self->read_bytes( 1 );
356 0 0         return unless defined $l2;
357              
358 0 0         if ( $l2 eq "\x7D" ) {
359 0           $l2 = $self->read_bytes( 1 );
360 0 0         return unless defined $l2;
361 0           $l2 ^= "\x20";
362             }
363              
364 0           my $packet_data_length = unpack( 'n', $l1 . $l2 );
365 0           my $data = $self->read_bytes( $packet_data_length + 1 );
366 0 0         return unless defined $data; # includes checksum
367              
368 0 0         if ( $data =~ /\x7D$/ ) { # trailing escape
369 0           my $tail = $self->read_bytes( 1 );
370 0 0         return unless defined $tail;
371 0           $data .= $tail;
372             }
373              
374 0           $data =~ s/$self->{api_mode_unescape_re}/$self->{api_mode_unescape_table}->{$1}/g;
375 0           my $need_a_few_more = $packet_data_length - length( $data ) + 1;
376              
377 0           while ( $need_a_few_more-- ) {
378 0           my $b = $self->read_bytes( 1 );
379 0 0         return unless defined $b;
380 0 0         if ( $b eq "\x7D" ) {
381 0           $b = $self->read_bytes( 1 );
382 0 0         return unless defined $b;
383 0           $b ^= "\x20";
384             }
385 0           $data .= $b;
386             }
387              
388 0           return ( $packet_data_length, $data );
389             }
390              
391             sub free_frame_id {
392 0     0 0   my ( $self, $id ) = @_;
393 0 0         if ( $self->{free_frame_id_func} ) { return $self->{free_frame_id_func}->( $self, $id ); }
  0            
394 0           delete $self->{in_flight_uart_frames}->{$id};
395             }
396              
397             # id 0 is special, don't allocate it. I don't know if we should die here or
398             # return 0 on failure...
399             sub alloc_frame_id {
400 0     0 0   my ( $self ) = @_;
401              
402 0 0         if ( $self->{alloc_frame_id_func} ) { return $self->{alloc_frame_id_func}->( $self ); }
  0            
403              
404 0           my $start_id = int( rand( 255 ) ) + 1;
405 0           my $id = $start_id;
406 0           my $oldest_time = 0xFFFFFFFF;
407 0           my $oldest_id;
408              
409 0           while ( 1 ) {
410 0 0         if ( !exists $self->{in_flight_uart_frames}->{$id} ) {
    0          
411 0           $self->{in_flight_uart_frames}->{$id} = time();
412 0           return $id;
413             } elsif ( $self->{in_flight_uart_frames}->{$id} < $oldest_time ) {
414 0           $oldest_time = $self->{in_flight_uart_frames}->{$id};
415 0           $oldest_id = $id;
416             }
417 0           $id++;
418 0 0         if ( $id > 255 ) { $id = 1; }
  0            
419 0 0         if ( $id == $start_id ) {
420 0 0         if ( $self->{auto_reuse_frame_id} ) {
421 0           $self->{in_flight_uart_frames}->{$oldest_id} = time();
422 0           return $oldest_id;
423             }
424 0           die "Unable to allocate frame id!";
425             }
426             }
427             }
428              
429             sub parse_packet {
430 0     0 0   my ( $self, $api_id, $api_data, $dont_free_id ) = @_;
431 0           my @u;
432             my $r;
433              
434 0 0         if ( $api_id == XBEE_API_TYPE__AT_COMMAND_RESPONSE ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
435 0           $r = __parse_at_command_response( $api_data );
436              
437             } elsif ( $api_id == XBEE_API_TYPE__MODEM_STATUS ) {
438 0           $r = __parse_modem_status( $api_data );
439              
440             } elsif ( $api_id == XBEE_API_TYPE__ZIGBEE_RECEIVE_PACKET ) {
441 0           $r = __parse_zigbee_receive_packet( $api_data );
442              
443             } elsif ( $api_id == XBEE_API_TYPE__ZIGBEE_EXPLICIT_RX_INDICATOR ) {
444 0           $r = __parse_zigbee_explicit_rx_indicator( $api_data );
445              
446             } elsif ( $api_id == XBEE_API_TYPE__ZIGBEE_TRANSMIT_STATUS ) {
447 0           $r = __parse_zigbee_transmit_status( $api_data );
448              
449             } elsif ( $api_id == XBEE_API_TYPE__ZIGBEE_IO_DATA_SAMPLE_RX_INDICATOR ) {
450 0           $r = __parse_zigbee_io_data_sample_rx_indicator( $api_data );
451              
452             } elsif ( $api_id == XBEE_API_TYPE__NODE_IDENTIFICATION_INDICATOR ) {
453 0           $r = __parse_node_identification_indicator( $api_data );
454              
455             } elsif ( $api_id == XBEE_API_TYPE__REMOTE_COMMAND_RESPONSE ) {
456 0           $r = __parse_remote_command_response( $api_data );
457              
458             } elsif ( XBEE_API_TYPE_TO_STRING->{$api_id} ) {
459 0           warn "No code to handle this packet: " . XBEE_API_TYPE_TO_STRING->{$api_id};
460             } else {
461 0           warn "Got unknown packet type: $api_id";
462             }
463              
464 0 0 0       if ( !$dont_free_id && $r->{frame_id} ) {
465 0           $self->free_frame_id( $r->{frame_id} );
466             }
467 0           $r->{api_type} = $api_id;
468 0           $r->{api_data} = $api_data;
469              
470 0           $self->_add_known_node( $r );
471 0           return $r;
472             }
473              
474             sub send_packet {
475 0     0 0   my ( $self, $api_id, $data ) = @_;
476 0           my $xbee_data = pack( 'nC', length( $data ) + 1, $api_id );
477 0           my $checksum = $api_id;
478              
479 0           for ( my $i = 0; $i < length( $data ); $i++ ) {
480 0           $checksum += unpack( 'C', substr( $data, $i, 1 ) );
481             }
482 0           $checksum = pack( 'C', 0xFF - ( $checksum & 0xFF ) );
483 0           $xbee_data = $xbee_data . $data . $checksum;
484              
485 0 0         if ( $self->{api_mode_escape} ) {
486             # Note we insert the \x7D here, it's not part of the table!
487 0           $xbee_data =~ s/$self->{api_mode_escape_re}/\x7D$self->{api_mode_escape_table}->{$1}/g;
488             }
489              
490 0 0         if ( !$self->{fh_sel} ) {
491 0           $self->{fh}->write( "\x7E" . $xbee_data );
492             } else {
493 0           syswrite( $self->{fh}, "\x7E" . $xbee_data );
494             }
495             }
496              
497             =head2 at
498              
499             Send an AT command to the module. Accepts two parameters, the first is the AT
500             command name (as two-character string), and the second is the expected data
501             for that command (if any). See the XBee datasheet for a list of supported AT
502             commands and expected data for each.
503              
504             Returns the frame ID sent for this packet. This method does not wait for a
505             reply from the XBee, as the expected reply is dependent on the AT command sent.
506             To retrieve the reply (if any), call one of the L methods.
507              
508             If no reply is expected, the caller should immediately free the returned frame
509             ID via L to prevent frame ID leaks.
510              
511             =cut
512              
513             sub at {
514 0     0 1   my ( $self, $command, $data ) = @_;
515 0 0         $data = '' unless $data;
516 0           my $frame_id = $self->alloc_frame_id();
517 0           $self->send_packet( XBEE_API_TYPE__AT_COMMAND, pack( 'C', $frame_id ) . $command . $data );
518 0           return $frame_id;
519             }
520              
521             =head2 remote_at
522              
523             Send an AT command to a remote module. Accepts three parameters: a hashref with
524             endpoint addresses, command options, frame_id; the AT command name (as
525             two-character string); and the third as the expected data for that command (if
526             any). See the XBee datasheet for a list of supported AT commands and expected
527             data for each.
528              
529             Endpoint addresses should be specified as a hashref containing the following
530             keys:
531              
532             =over 4
533              
534             =item sh
535              
536             The high 32-bits of the destination address.
537              
538             =item sl
539              
540             The low 32-bits of the destination address.
541              
542             =item na
543              
544             The destination network address.
545              
546             =item disable_ack
547              
548             If included ack is disabled
549              
550             =item apply_changes
551              
552             If included changes applied immediate, if missing an AC command must be sent to
553             apply changes
554              
555             =item extended_xmit_timeout
556              
557             If included the exteded transmission timeout is used
558              
559             =back
560              
561             Returns the frame ID sent for this packet. To retrieve the reply (if any), call
562             one of the L methods. If no reply is expected, the caller should immediately
563             free the returned frame ID via L to prevent frame ID leaks.
564              
565             =cut
566              
567             sub remote_at {
568 0     0 1   my ( $self, $tx, $command, $data ) = @_;
569 0           my @my_rx_queue;
570 0 0         if ( !$command ) { die "Invalid parameters"; }
  0            
571 0 0 0       if ( !$tx && !$data ) { die "Invalid parameters"; }
  0            
572 0 0 0       if ( !defined $tx && defined $data ) {
    0          
573 0           $tx = {};
574             } elsif ( ref $tx ne 'HASH' ) {
575 0           $data = $tx;
576 0           $tx = {};
577             }
578              
579 0 0 0       if ( ( $tx->{sh} && !$tx->{sl} )
      0        
      0        
580             || ( !$tx->{sh} && $tx->{sl} ) )
581             {
582 0           die "Invalid parameters";
583             }
584              
585 0 0         if ( !defined $tx->{na} ) {
586 0           $tx->{na} = XBEE_API_BROADCAST_NA_UNKNOWN_ADDR;
587             }
588 0 0         if ( !defined $tx->{sh} ) {
589 0           $tx->{sh} = XBEE_API_BROADCAST_ADDR_H;
590 0           $tx->{sl} = XBEE_API_BROADCAST_ADDR_L;
591             }
592 0           my ( $ack, $chg, $timeout );
593 0 0         if ( !defined $tx->{disable_ack} ) {
594 0           $ack = 0x00;
595             } else {
596 0           $ack = 0x01;
597             }
598 0 0         if ( defined $tx->{apply_changes} ) {
599 0           $chg = 0x02;
600             } else {
601 0           $chg = 0x00;
602             }
603 0 0         if ( defined $tx->{extended_xmit_timeout} ) {
604 0           $timeout = 0x40;
605             } else {
606 0           $timeout = 0x00;
607             }
608 0           my $options = $ack + $chg + $timeout;
609              
610 0 0         $data = '' unless defined $data;
611 0           my $frame_id = $self->alloc_frame_id();
612 0           my $tx_req = pack( 'CNNnC', $frame_id, $tx->{sh}, $tx->{sl}, $tx->{na}, $options );
613 0           $self->send_packet( XBEE_API_TYPE__REMOTE_COMMAND_REQUEST, $tx_req . $command . $data );
614 0           return $frame_id;
615             }
616              
617             =head2 tx
618              
619             Sends a transmit request to the XBee. Accepts three parameters, the first is the
620             endpoint address, the second is a scalar containing the data to be sent, and the
621             third is an optional flag (known as the async flag) specifying whether or not
622             the method should wait for an acknowledgement from the XBee.
623              
624             Endpoint addresses should be specified as a hashref containing the following
625             keys:
626              
627             =over 4
628              
629             =item sh
630              
631             The high 32-bits of the destination address.
632              
633             =item sl
634              
635             The low 32-bits of the destination address.
636              
637             =item na
638              
639             The destination network address. If this is not specified, it will default to
640             XBEE_API_BROADCAST_NA_UNKNOWN_ADDR.
641              
642             =back
643              
644             If both sh and sl are missing or the parameter is undefined,, they will default
645             to XBEE_API_BROADCAST_ADDR_H and XBEE_API_BROADCAST_ADDR_L.
646              
647             The meaning of these addresses can be found in the XBee datasheet. Note: In
648             the future, a Device::XBee::API::Node object will be an acceptable parameter.
649              
650             If the async flag is not set, the method will wait for an acknowledgement packet
651             from the XBee. Return values depend on calling context. In scalar context, true
652             or false will be returned representing transmission acknowledgement by the
653             remote XBee device. In array context, the first return value is the delivery
654             status (as set in the transmit status packet and documented in the datasheet),
655             and the second is the actual transmit status packet (as a hashref) itself.
656              
657             If the async flag is set, the method will not wait for an acknowledgement packet
658             and the tx frame ID will be returned. The caller will need to then receive the
659             transmit status packet (via one of the L methods) and free the frame ID (via
660             L) manually.
661              
662             No retransmissions will be attempted by this module, but the XBee
663             device itself will likely attempt retransmissions as per its configuration (and
664             subject to whether or not the packet was a "broadcast").
665              
666             =cut
667              
668             # API is goofy here. If called in scalar context, returns true or false if the
669             # packet was transmitted. If called in array context, returns the delivery
670             # status and the transmit status packet as an array. Note: the actual delivery
671             # status uses 0 (or false) to indicate success.
672             sub tx {
673 0     0 1   my ( $self, $tx, $data, $async ) = @_;
674 0           my @my_rx_queue;
675 0 0 0       if ( !$tx && !$data ) { die "Invalid parameters"; }
  0            
676 0 0 0       if ( !defined $tx && defined $data ) {
    0          
677 0           $tx = {};
678             } elsif ( ref $tx ne 'HASH' ) {
679 0           $data = $tx;
680 0           $tx = {};
681             }
682              
683 0 0 0       if ( ( $tx->{sh} && !$tx->{sl} ) || ( !$tx->{sh} && $tx->{sl} ) ) { die "Invalid parameters"; }
  0   0        
      0        
684              
685 0 0         if ( !defined $tx->{na} ) { $tx->{na} = XBEE_API_BROADCAST_NA_UNKNOWN_ADDR; }
  0            
686 0 0         if ( !defined $tx->{sh} ) {
687 0           $tx->{sh} = XBEE_API_BROADCAST_ADDR_H;
688 0           $tx->{sl} = XBEE_API_BROADCAST_ADDR_L;
689             }
690              
691 0           my $frame_id = $self->alloc_frame_id();
692 0 0         my $tx_req = pack( 'CNNnCC', $frame_id, $tx->{sh}, $tx->{sl}, $tx->{na}, 0, ( $tx->{broadcast} ? 0x8 : 0 ) );
693 0           $self->send_packet( XBEE_API_TYPE__ZIGBEE_TRANSMIT_REQUEST, $tx_req . $data );
694              
695 0 0         if ( $async ) { return $frame_id; }
  0            
696              
697             # Wait until we get the send result message.
698 0           my $rx = $self->rx_frame_id( $frame_id );
699 0 0         return undef unless defined $rx;
700              
701             # Wonky return API.
702 0 0         if ( wantarray ) {
703 0           return ( $rx->{delivery_status}, $rx );
704             } else {
705 0 0         if ( $rx->{delivery_status} == 0 ) {
706 0           return 1;
707             } else {
708 0           return 0;
709             }
710             }
711             }
712              
713             sub _unshift_rx {
714 0     0     my ( $self, $rxq ) = @_;
715              
716 0 0         if ( !$rxq ) { return; }
  0            
717 0 0         if ( ref $rxq eq '' ) {
    0          
718 0           unshift @{ $self->{rx_queue} }, $rxq;
  0            
719             } elsif ( ref $rxq eq 'ARRAY' ) {
720 0           unshift @{ $self->{rx_queue} }, @{$rxq};
  0            
  0            
721             } else {
722 0           die "Unknown parameter type";
723             }
724             }
725              
726             sub _rx_no_queue {
727 0     0     my ( $self, $dont_free_id ) = @_;
728              
729 0           my ( $type, $data ) = $self->read_packet();
730 0 0         return unless defined $type;
731 0           return $self->parse_packet( $type, $data, $dont_free_id );
732             }
733              
734             =head2 rx
735              
736             Receives a packet from the XBee module. This packet may be a transmission from
737             a remote XBee node or a control packet from the local XBee module.
738              
739             If no packet is received before the timeout period expires, undef is returned.
740              
741             Returned packets will be as a hashref of the packet data, broken out by key for
742             easy access. Note, as this module is a work in progress, not every XBee packet
743             type is supported. Callers should check the "api_type" key to determine the
744             type of the received packet. When possible, packed integers will be unpacked
745             into the "data_as_int" key. If no packed integer is found this key will not be
746             present. If unpacking is not possible (due to an unknown packet type, etc), the
747             value will be undef.
748              
749             Accepts a single parameter, a flag indicating the received frame ID should NOT
750             be freed automatically. See L for why you might want to use this
751             flag (generally, cases when you expect multiple packets to arrive with the same
752             frame ID).
753              
754             =cut
755              
756             sub rx {
757 0     0 1   my ( $self, $dont_free_id ) = @_;
758              
759 0 0         if ( scalar( @{ $self->{rx_queue} } ) > 0 ) { return shift @{ $self->{rx_queue} }; }
  0            
  0            
  0            
760 0           return $self->_rx_no_queue( $dont_free_id );
761             }
762              
763             =head2 rx_frame_id
764              
765             Like L but only returns the packet with the requested frame ID number and
766             then frees that frame ID. If no packet with the specified frame ID is received
767             within the object's configured packet_timeout time, undef will be returned. Any
768             other packets received will be enqueued for later processing by another rx
769             function call.
770              
771             Accepts two parameters, the first being the desired frame ID and the second a
772             flag denoting that the frame ID should NOT be automatically freed. In cases
773             where multiple frames with the same ID are expected to be returned (such as
774             after an AT ND command), it is preferable to set this flag to a true value and
775             continue to call rx_frame_id until undef is returned, and then free the ID via
776             L.
777              
778             =cut
779              
780             sub rx_frame_id {
781 0     0 1   my ( $self, $frame_id, $dont_free_id ) = @_;
782 0           my @ignored;
783             my $r;
784 0           my $start_time = time();
785              
786 0           while ( 1 ) {
787 0           $r = $self->rx( $dont_free_id );
788 0 0         if ( $r ) {
789 0 0 0       if ( $r->{frame_id} && $r->{frame_id} == $frame_id ) {
790 0           last;
791             } else {
792 0           push @ignored, $r;
793             }
794             }
795 0 0         if ( time() - $start_time >= $self->{packet_wait_time} ) {
796 0           undef $r;
797 0           last;
798             }
799             }
800 0 0         if ( @ignored ) {
801 0           $self->_unshift_rx( \@ignored );
802             }
803 0           return $r;
804             }
805              
806             =head2 discover_network
807              
808             Performs a network node discovery via the ND 'AT' command. Blocks until no
809             replies have been received in packet_timeout seconds.
810              
811             =cut
812              
813             sub discover_network {
814 0     0 1   my ( $self ) = @_;
815 0           my $frame_id = $self->at( 'ND' );
816 0           while ( defined $self->rx_frame_id( $frame_id, 1 ) ) { }
817 0           $self->free_frame_id( $frame_id );
818             }
819              
820             =head2 node_info
821              
822             =cut
823              
824             sub node_info {
825 0     0 1   my ( $self, $node ) = @_;
826 0           my $sn = __node_sn( $node );
827 0 0         if ( !$sn ) { return undef; }
  0            
828 0           $node->{sn} = $sn;
829 0           return $self->{known_nodes}->{$sn};
830             }
831              
832             =head2 known_nodes
833              
834             Returns a hashref of all known nodes indexed by their full serial number (i.e.
835             $node->{sh} . '_' . $node->{sl}). Nodes that haven't been heard from in the
836             configured node_forget_time will be automatically removed from this list if
837             they've not been heard from in that time. Nodes are added to that list when a
838             message is received from them or a discover_network call has been made.
839              
840             Note, the age-out mechanism may be susceptable to stepping of the system clock.
841              
842             =cut
843              
844             sub known_nodes {
845 0     0 1   my ( $self ) = @_;
846 0           $self->_prune_known_nodes();
847 0           return { %{ $self->{known_nodes} } };
  0            
848             }
849              
850             ### Private methods
851              
852             sub _add_known_node {
853 0     0     my ( $self, $node ) = @_;
854              
855 0           my $sn = __node_sn( $node );
856 0 0         if ( !$sn ) { return; }
  0            
857              
858 0           $self->_prune_known_nodes();
859              
860             # Update the node in-place in case someone else is holding onto a
861             # reference.
862 0 0         if ( $self->{known_nodes}->{$sn} ) {
863 0           my $sknsn = $self->{known_nodes}->{$sn};
864             # These are the only known values that should change for a node with a
865             # given serial number. The rest are burned into the chip.
866 0           foreach my $k ( qw/ ni profile_id / ) {
867 0 0 0       if ( $node->{$k}
      0        
868             && ( !$sknsn->{$k} || $sknsn->{$k} ne $node->{$k} ) )
869             {
870 0           $sknsn->{$k} = $node->{$k};
871             }
872             }
873 0   0       $sknsn->{na} = $node->{na} || $node->{my};
874 0           $sknsn->{last_seen_time} = time();
875             } else {
876 0   0       $self->{known_nodes}->{$sn} = {
877             sn => $sn,
878             sh => $node->{sh},
879             sl => $node->{sl},
880             na => $node->{na} || $node->{my},
881             ni => $node->{ni},
882             profile_id => $node->{profile_id},
883             device_type => $node->{device_type},
884             manufacturer_id => $node->{manufacturer_id},
885             last_seen_time => time(),
886             };
887             }
888             }
889              
890             sub _prune_known_nodes {
891 0     0     my ( $self ) = @_;
892 0           my $now = time();
893 0           my @saved_nodes;
894 0           while ( my ( $sn, $node ) = each( %{ $self->{known_nodes} } ) ) {
  0            
895 0 0         if ( $now - $node->{last_seen_time} > $self->{node_forget_time} ) {
896             # Set just in case a caller has held onto the reference for
897             # something.
898 0           $node->{forgotten} = 1;
899 0           delete $self->{known_nodes}->{$sn};
900             }
901             }
902             }
903              
904             ### Private functions
905              
906             sub __node_sn {
907 0     0     my ( $node ) = @_;
908 0 0         if ( $node->{sn} ) { return $node->{sn} }
  0            
909 0 0         if ( !$node->{sh} ) { return undef; }
  0            
910 0           return $node->{sh} . '_' . $node->{sl};
911             }
912              
913             sub __get_bits {
914 0     0     my ( $int ) = @_;
915 0           my $and = 0x80;
916 0           my @list;
917 0           my $any_hits = 0;
918 0           for ( 1 .. 8 ) {
919 0 0         if ( $int & $and ) {
920             # if the bit is set == 1
921 0           push @list, 1;
922 0           $any_hits = 1;
923             } else {
924             # if the bit is not set == 0
925 0           push @list, 0;
926             }
927              
928             # shift the constant using right shift
929 0           $and = $and >> 1;
930             }
931 0           return ( $any_hits, @list );
932             }
933              
934             sub __parse_at_command_response {
935 0     0     my ( $api_data ) = @_;
936              
937 0           my @u = unpack( 'Ca[2]Ca*', $api_data );
938              
939 0           my $r = {
940             frame_id => $u[0],
941             command => $u[1],
942             status => $u[2],
943             data => $u[3],
944             is_ok => $u[2] == 0,
945             is_error => $u[2] == 1,
946             is_invalid_command => $u[2] == 2,
947             is_invalid_parameter => $u[2] == 3,
948             };
949              
950 0 0         if ( $r->{command} eq 'ND' ) {
951             (
952 0           $r->{na}, $r->{sh}, $r->{sl},
953             $r->{ni}, $r->{parent_network_address}, $r->{device_type},
954             $r->{source_event}, $r->{profile_id}, $r->{manufacturer_id},
955             ) = unpack( 'nNNZ*nCCnna*', $r->{data} );
956             # The ND API calls it "my" but it's "na" everywhere else. Provide both
957             # because the user may expect to see "my" after this packet arrives.
958             # This module only uses "na".
959 0           $r->{my} = $r->{na};
960             } else {
961 0           $r->{data_as_int} = __data_to_int( $r->{data} );
962             }
963              
964 0           return $r;
965             }
966              
967             sub __data_to_int {
968 0     0     my ( $data ) = @_;
969              
970 0 0         if ( length( $data ) == 1 ) {
    0          
    0          
    0          
971 0           return unpack( 'C', $data );
972             } elsif ( length( $data ) == 2 ) {
973 0           return unpack( 'n', $data );
974             } elsif ( length( $data ) == 4 ) {
975 0           return unpack( 'N', $data );
976             } elsif ( length( $data ) == 8 ) {
977 0           my ( $h, $l ) = unpack( 'NN', $data );
978 0           return ( $l | ( $h << 32 ) );
979             }
980 0           return undef;
981             }
982              
983             sub __parse_modem_status {
984 0     0     my ( $api_data ) = @_;
985 0           my $u = unpack( 'C', $api_data );
986             return {
987 0           status => $u,
988             is_hardware_reset => $u == 1,
989             is_wdt_reset => $u == 2,
990             is_associated => $u == 3,
991             is_disassociated => $u == 4,
992             is_sync_lost => $u == 5,
993             is_coord_realign => $u == 6,
994             is_coord_start => $u == 7,
995             };
996             }
997              
998             sub __parse_zigbee_receive_packet {
999 0     0     my ( $api_data ) = @_;
1000 0           my @u = unpack( 'NNnCa*', $api_data );
1001             # sh sl and na are named to match the fields in a network discovery AT
1002             # packet response
1003             return {
1004 0 0         sh => $u[0],
1005             sl => $u[1],
1006             na => $u[2],
1007             options => $u[3],
1008             data => $u[4],
1009             is_ack => $u[3] & 0x01,
1010             is_broadcast => ( $u[3] & 0x02 ? 1 : 0 ),
1011             };
1012             }
1013              
1014             sub __parse_zigbee_explicit_rx_indicator {
1015 0     0     my ( $api_data ) = @_;
1016 0           my @u = unpack( 'NNnCCnnCa*', $api_data );
1017              
1018             return {
1019 0 0         sh => $u[0],
    0          
    0          
1020             sl => $u[1],
1021             na => $u[2],
1022             se => $u[3],
1023             de => $u[4],
1024             ci => $u[5],
1025             profile_id => $u[6],
1026             options => $u[7],
1027             data => $u[8],
1028             is_ack => $u[7] & 0x01,
1029             is_broadcast => ( $u[7] & 0x02 ? 1 : 0 ),
1030             is_encrypted => ( $u[7] & 0x20 ? 1 : 0 ),
1031             is_from_end_device => ( $u[7] & 0x40 ? 1 : 0 ),
1032             };
1033             }
1034              
1035             sub __parse_zigbee_transmit_status {
1036 0     0     my ( $api_data ) = @_;
1037 0           my @u = unpack( 'CnCCC', $api_data );
1038             return {
1039 0           frame_id => $u[0],
1040             remote_na => $u[1],
1041             tx_retry_count => $u[2],
1042             delivery_status => $u[3],
1043             discovery_status => $u[4]
1044             };
1045             }
1046              
1047             sub __parse_zigbee_io_data_sample_rx_indicator {
1048 0     0     my ( $api_data ) = @_;
1049 0           my @u = unpack( 'NNnCCCCCa*', $api_data );
1050 0           my $data = $u[8];
1051 0 0         my $r = {
1052             sh => $u[0],
1053             sl => $u[1],
1054             na => $u[2],
1055             options => $u[3],
1056             is_ack => $u[3] & 0x01,
1057             is_broadcast => ( $u[3] & 0x02 ? 1 : 0 ),
1058             number_samples => $u[4],
1059             data => unpack( "h*", $data )
1060             };
1061              
1062 0           my ( $any_d1, $any_d2, $any_a );
1063 0           my @bits;
1064 0           ( $any_d1, @bits ) = __get_bits( $u[5] );
1065 0           $r->{"digital_channel_first"} = [@bits];
1066 0           ( $any_d2, @bits ) = __get_bits( $u[6] );
1067 0           $r->{"digital_channel_second"} = [@bits];
1068 0           ( $any_a, @bits ) = __get_bits( $u[7] );
1069 0           $r->{"analog_channel_bits"} = [@bits];
1070 0           my @digital;
1071              
1072             # do we need grab the digital 16 bits?
1073 0 0         if ( $any_d1 + $any_d2 ) {
1074 0           my ( $d1, $d2 );
1075 0           ( $d1, $d2, $data ) = unpack( "CCa*", $data );
1076 0           my $trash;
1077             my @digital_status;
1078 0           my @digital;
1079 0           ( $trash, @digital_status ) = __get_bits( $d1 );
1080 0 0         if ( $r->{"digital_channel_first"}[3] == 1 ) {
1081 0           $digital[12] = $digital_status[3];
1082             }
1083 0 0         if ( $r->{"digital_channel_first"}[4] == 1 ) {
1084 0           $digital[11] = $digital_status[4];
1085             }
1086 0 0         if ( $r->{"digital_channel_first"}[5] == 1 ) {
1087 0           $digital[10] = $digital_status[5];
1088             }
1089 0           ( $trash, @digital_status ) = __get_bits( $d2 );
1090 0           my $d_number = 7;
1091 0           for ( my $i = 0; $i < 8; $i++ ) {
1092 0 0         if ( $r->{"digital_channel_second"}[$i] == 1 ) {
1093 0           $digital[$d_number] = $digital_status[$i];
1094             }
1095 0           $d_number--;
1096             }
1097 0           $r->{"digital_inputs"} = \@digital;
1098             }
1099              
1100             # now get the analog values, if any
1101 0           my @analog;
1102 0           for ( my $i = 7; $i >= 0; $i-- ) {
1103 0 0         if ( $r->{"analog_channel_bits"}[$i] == 1 ) {
1104 0           ( $analog[7 - $i], $data ) = unpack( 'na*', $data );
1105             }
1106             }
1107 0           $r->{"analog_inputs"} = \@analog;
1108 0           return $r;
1109             }
1110              
1111             sub __parse_node_identification_indicator {
1112 0     0     my ( $api_data ) = @_;
1113 0           my @u = unpack( 'NNnCnNNZ*nCCnn', $api_data );
1114             return {
1115 0 0         source_sh => $u[0],
1116             source_sl => $u[1],
1117             source_na => $u[2],
1118             options => $u[3],
1119             is_ack => $u[3] & 0x01,
1120             is_broadcast => ( $u[3] & 0x02 ? 1 : 0 ),
1121             remote_na => $u[4],
1122             remote_sh => $u[5],
1123             remote_sl => $u[6],
1124             ni => $u[7],
1125             parent_address => $u[8],
1126             device_type => $u[9],
1127             source_event => $u[10],
1128             profile_id => $u[11],
1129             mfg_id => $u[12]
1130             };
1131             }
1132              
1133             sub __parse_remote_command_response {
1134 0     0     my ( $api_data ) = @_;
1135 0           my @u = unpack( 'CNNna[2]Ca*', $api_data );
1136             return {
1137 0           frame_id => $u[0],
1138             sh => $u[1],
1139             sl => $u[2],
1140             na => $u[3],
1141             command => $u[4],
1142             status => $u[5],
1143             data => $u[6],
1144             data_as_int => __data_to_int( $u[6] ),
1145             is_ok => $u[5] == 0,
1146             is_error => $u[5] == 1,
1147             is_invalid_command => $u[5] == 2,
1148             is_invalid_parameter => $u[5] == 3,
1149             is_remote_cmd_xmit_failed => $u[5] == 4,
1150             };
1151             }
1152              
1153             =head1 EXAMPLES
1154              
1155             Miscellaneous code examples follow.
1156              
1157             =head2 Fetch modem baud rage
1158              
1159             use Device::SerialPort;
1160             use Device::XBee::API;
1161            
1162             # From XBee datasheet pg 73.
1163             my @baud_rate_table = (
1164             1200,
1165             2400,
1166             4800,
1167             9600,
1168             19200,
1169             38400,
1170             57600,
1171             115200
1172             );
1173            
1174             # Configure the serial port
1175             my $serial_port_device = Device::SerialPort->new( '/dev/ttyU0' )
1176             || die $!;
1177             $serial_port_device->baudrate( 9600 );
1178             $serial_port_device->databits( 8 );
1179             $serial_port_device->stopbits( 1 );
1180             $serial_port_device->parity( 'none' );
1181             $serial_port_device->read_char_time( 0 );
1182             $serial_port_device->read_const_time( 1000 );
1183            
1184             # Create the API object
1185             my $api = Device::XBee::API->new( { fh => $serial_port_device } )
1186             || die $!;
1187            
1188             # Send the BD API command
1189             my $at_frame_id = $api->at( 'BD' );
1190             die "Transmit failed" unless $at_frame_id;
1191            
1192             # Receive the reply
1193             my $rx = $api->rx_frame_id( $at_frame_id );
1194             die "No reply received" if !$rx;
1195             if ( $rx->{status} != 0 ) {
1196             die "API error" if $rx->{is_error};
1197             die "Invalid command" if $rx->{is_invalid_command};
1198             die "Invalid parameter" if $rx->{is_invalid_parameter};
1199             die "Unknown error";
1200             }
1201            
1202             my $baud_rate = $baud_rate_table[ $rx->{data_as_int} ];
1203             if ( !$baud_rate ) {
1204             $baud_rate = $rx->{data_as_int};
1205             }
1206            
1207             print "Modem baud rate is $baud_rate bps.\n";
1208              
1209              
1210             =head1 CHANGES
1211              
1212             =head2 0.7, 20130330 - jeagle
1213              
1214             Add ability to allow users to specify their own frame allocation routines.
1215              
1216             Update API mode 2 with latest version from jdodgen
1217              
1218             =head2 0.6, 20120624 - jeagle
1219              
1220             Update documentation.
1221              
1222             Add support for API mode 2 escapes. Needs testing.
1223              
1224             Add constant for the "BD" baud rate table.
1225              
1226             =head2 0.5, 20120401 - jeagle
1227              
1228             Add support for Win32::SerialPort to enable Windows support. (Thanks Jerry)
1229              
1230             Fix issue with tx() in async mode. (Thanks Vicente)
1231              
1232             Add support for "explicit rx indicator" packets. (Thanks Vicente)
1233              
1234             =head2 0.4, 20110831 - jeagle
1235              
1236             Fix packet timeout bug reported by Dave S.
1237              
1238             Replace call to die() in __data_to_int with return undef, update docs to
1239             reflect this.
1240              
1241             =head2 0.3, 20110621 - jeagle, jdodgen
1242              
1243             Change from internal Device::SerialPort wrapper to accepting an fh.
1244              
1245             Add asynchronous support to tx and add some helpful methods to support it.
1246              
1247             Handle more command types (remote AT, ZigBee IO, node identification).
1248              
1249             Add an option to re-use frame IDs under high tx load.
1250              
1251             Many more changes!
1252              
1253             =head2 0.2, 20101206 - jeagle
1254              
1255             Initial release to CPAN.
1256              
1257             =cut
1258              
1259             1;