| 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; |