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