xbee 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +13 -0
- data/.gitignore +25 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +49 -0
- data/examples/check_and_set_destination_address.rb +161 -0
- data/examples/read_frames.rb +45 -0
- data/examples/response_parser_throughput.rb +35 -0
- data/lib/xbee.rb +5 -0
- data/lib/xbee/address.rb +23 -0
- data/lib/xbee/address_16.rb +39 -0
- data/lib/xbee/address_64.rb +40 -0
- data/lib/xbee/bytes.rb +21 -0
- data/lib/xbee/exceptions/exception.rb +9 -0
- data/lib/xbee/exceptions/frame_format_error.rb +9 -0
- data/lib/xbee/exceptions/unknown_frame_type.rb +9 -0
- data/lib/xbee/frames/addressed_frame.rb +27 -0
- data/lib/xbee/frames/at_command.rb +35 -0
- data/lib/xbee/frames/at_command_queue_parameter_value.rb +21 -0
- data/lib/xbee/frames/at_command_response.rb +31 -0
- data/lib/xbee/frames/create_source_route.rb +15 -0
- data/lib/xbee/frames/data/sample.rb +72 -0
- data/lib/xbee/frames/data_sample_rx_indicator.rb +51 -0
- data/lib/xbee/frames/explicit_addressing_command.rb +68 -0
- data/lib/xbee/frames/explicit_rx_indicator.rb +60 -0
- data/lib/xbee/frames/frame.rb +65 -0
- data/lib/xbee/frames/identified_frame.rb +24 -0
- data/lib/xbee/frames/many_to_one_route_request_indicator.rb +11 -0
- data/lib/xbee/frames/modem_status.rb +47 -0
- data/lib/xbee/frames/node_identification_indicator.rb +38 -0
- data/lib/xbee/frames/over_the_air_firmware_update_status.rb +28 -0
- data/lib/xbee/frames/receive_packet.rb +44 -0
- data/lib/xbee/frames/remote_at_command_request.rb +53 -0
- data/lib/xbee/frames/remote_at_command_response.rb +45 -0
- data/lib/xbee/frames/route_record_indicator.rb +28 -0
- data/lib/xbee/frames/transmit_request.rb +70 -0
- data/lib/xbee/frames/transmit_status.rb +63 -0
- data/lib/xbee/frames/unidentified_addressed_frame.rb +26 -0
- data/lib/xbee/frames/xbee_sensor_read_indicator.rb +50 -0
- data/lib/xbee/packet.rb +175 -0
- data/lib/xbee/version.rb +5 -0
- data/lib/xbee/xbee.rb +109 -0
- data/xbee.gemspec +40 -0
- metadata +245 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'address'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
class Address64 < Address
|
6
|
+
def initialize(b1, b2, b3, b4, b5, b6, b7, b8)
|
7
|
+
@bytes = [b1, b2, b3, b4, b5, b6, b7, b8]
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def from_string(string)
|
13
|
+
if (matcher = /^(\h\h)[^\h]*(\h\h)[^\h]*(\h\h)[^\h]*(\h\h)[^\h]*(\h\h)[^\h]*(\h\h)[^\h]*(\h\h)[^\h]*(\h\h)$/.match(string))
|
14
|
+
new *(matcher[1..8].map &:hex)
|
15
|
+
else
|
16
|
+
raise ArgumentError, "#{string.inspect} is not a valid 64-bit address string"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def from_array(array)
|
22
|
+
if array.length == 8 && array.all? { |x| (0..255).cover? x }
|
23
|
+
new *array
|
24
|
+
else
|
25
|
+
raise ArgumentError, "#{array.inspect} is not a valid 64-bit address array"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
('%02x' * 8) % @bytes
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
BROADCAST = new(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff).freeze
|
38
|
+
COORDINATOR = new(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00).freeze
|
39
|
+
end
|
40
|
+
end
|
data/lib/xbee/bytes.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module XBee
|
2
|
+
# Starting to accumulate code to manipulate arrays of bytes in one place. Maybe eventually this will be a subclass of Array or...?
|
3
|
+
class Bytes
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def array_from_unsigned_int(int, _num_bytes = 2)
|
8
|
+
# TODO: Make this more generic than the 16-bit case
|
9
|
+
[(int >> 8) & 0xff, int & 0xff]
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def unsigned_int_from_array(array)
|
14
|
+
index = -1
|
15
|
+
array.reverse.reduce(0) do |result, byte|
|
16
|
+
result + (byte << 8 * (index += 1))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'identified_frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
class AddressedFrame < IdentifiedFrame
|
7
|
+
|
8
|
+
attr_accessor :address16
|
9
|
+
attr_accessor :address64
|
10
|
+
|
11
|
+
|
12
|
+
def initialize(packet: nil)
|
13
|
+
super
|
14
|
+
|
15
|
+
if @parse_bytes
|
16
|
+
@address64 = Address64.new *@parse_bytes.shift(8)
|
17
|
+
@address16 = Address16.new *@parse_bytes.shift(2)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def bytes
|
23
|
+
super + (address64 || Address64.from_array([0] * 8)).to_a + (address16 || Address16.new(0, 0)).to_a
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'identified_frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
# Use this frame to query or set device parameters on the local device. This API command applies
|
7
|
+
# changes after running the command. You can query parameter values by sending the 0x08 AT
|
8
|
+
# Command frame with no parameter value field (the two-byte AT command is immediately followed by
|
9
|
+
# the frame checksum).
|
10
|
+
class ATCommand < IdentifiedFrame
|
11
|
+
api_id 0x08
|
12
|
+
|
13
|
+
attr_accessor :command_bytes
|
14
|
+
# attr_accessor :command
|
15
|
+
attr_accessor :parameter_bytes
|
16
|
+
# attr_accessor :parameter
|
17
|
+
|
18
|
+
|
19
|
+
def initialize(packet: nil)
|
20
|
+
super
|
21
|
+
|
22
|
+
if @parse_bytes
|
23
|
+
@command_bytes = @parse_bytes.shift 2
|
24
|
+
@parameter_bytes = @parse_bytes
|
25
|
+
@parse_bytes = []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def bytes
|
31
|
+
super + command_bytes + (parameter_bytes || [])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'at_command'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
# This frame allows you to query or set device parameters. In contrast to the AT Command (0x08)
|
7
|
+
# frame, this frame queues new parameter values and does not apply them until you issue either:
|
8
|
+
# * The AT Command (0x08) frame (for API type)
|
9
|
+
# * The AC command
|
10
|
+
#
|
11
|
+
# When querying parameter values, the 0x09 frame behaves identically to the 0x08 frame. The device
|
12
|
+
# returns register queries immediately and not does not queue them. The response for this command is
|
13
|
+
# also an AT Command Response frame (0x88).
|
14
|
+
#
|
15
|
+
# Send a command to change the baud rate (BD) to 115200 baud, but do not apply changes yet. The
|
16
|
+
# module continues to operate at the previous baud rate until you apply the changes.
|
17
|
+
class ATCommandQueueParameterValue < ATCommand
|
18
|
+
api_id 0x09
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'identified_frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
class ATCommandResponse < IdentifiedFrame
|
7
|
+
api_id 0x88
|
8
|
+
|
9
|
+
attr_accessor :command
|
10
|
+
attr_accessor :status
|
11
|
+
attr_accessor :data
|
12
|
+
|
13
|
+
|
14
|
+
def initialize(packet: nil)
|
15
|
+
super
|
16
|
+
|
17
|
+
if @parse_bytes
|
18
|
+
@command = @parse_bytes.shift 2
|
19
|
+
@status = @parse_bytes.shift
|
20
|
+
@data = @parse_bytes
|
21
|
+
@parse_bytes = []
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def bytes
|
27
|
+
super + (command || [0x00] * 2) + [status || 0x00] + (data || [])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'addressed_frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
class CreateSourceRoute < AddressedFrame
|
7
|
+
api_id 0x21
|
8
|
+
|
9
|
+
# TODO: Not implemented
|
10
|
+
def initialize(packet: nil)
|
11
|
+
raise 'Not implemented'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module XBee
|
4
|
+
module Frames
|
5
|
+
module Data
|
6
|
+
class Sample
|
7
|
+
# Bit-mapping of digital channel names
|
8
|
+
DIGITAL_CHANNELS = [
|
9
|
+
:na,
|
10
|
+
:na,
|
11
|
+
:na,
|
12
|
+
:DIO12,
|
13
|
+
:DIO11,
|
14
|
+
:DIO10,
|
15
|
+
:na,
|
16
|
+
:na,
|
17
|
+
:DIO7,
|
18
|
+
:DIO6,
|
19
|
+
:DIO5,
|
20
|
+
:DIO4,
|
21
|
+
:DIO3,
|
22
|
+
:DIO2,
|
23
|
+
:DIO1,
|
24
|
+
:DIO0,
|
25
|
+
]
|
26
|
+
|
27
|
+
# Bit-mapping of analog channel names
|
28
|
+
ANALOG_CHANNELS = [
|
29
|
+
:supply_voltage,
|
30
|
+
:na,
|
31
|
+
:na,
|
32
|
+
:na,
|
33
|
+
:AD3,
|
34
|
+
:AD2,
|
35
|
+
:AD1,
|
36
|
+
:AD0,
|
37
|
+
]
|
38
|
+
|
39
|
+
|
40
|
+
attr_reader :digital_values
|
41
|
+
attr_reader :analog_values
|
42
|
+
|
43
|
+
|
44
|
+
# Parses the input bytes into @digital_values and @analog_values.
|
45
|
+
# Consumes the bytes read, so the next sample can be constructed.
|
46
|
+
# @param parse_bytes [Array<Integer>] Input data.
|
47
|
+
def initialize(parse_bytes:)
|
48
|
+
input = parse_bytes
|
49
|
+
@digital_channel_mask = (input.shift << 8) + input.shift
|
50
|
+
@analog_channel_mask = input.shift
|
51
|
+
|
52
|
+
@digital_values = {}
|
53
|
+
if @digital_channel_mask > 0
|
54
|
+
raw = (input.shift << 8) + input.shift
|
55
|
+
DIGITAL_CHANNELS.reverse.each_with_index do |channel, index|
|
56
|
+
if (@digital_channel_mask & (1 << index)) > 0
|
57
|
+
@digital_values[channel] = (raw & (1 << index)) > 0 ? 1 : 0
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
@analog_values = {}
|
63
|
+
ANALOG_CHANNELS.reverse.each_with_index do |channel, index|
|
64
|
+
if (@analog_channel_mask & (1 << index)) > 0
|
65
|
+
@analog_values[channel] = (input.shift << 8) + input.shift
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'frame'
|
3
|
+
require_relative 'data/sample'
|
4
|
+
|
5
|
+
module XBee
|
6
|
+
module Frames
|
7
|
+
class DataSampleRXIndicator < Frame
|
8
|
+
api_id 0x92
|
9
|
+
|
10
|
+
attr_reader :address16
|
11
|
+
attr_reader :address64
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
# [Array<Data::DataSample>] Array of sample objects
|
15
|
+
attr_reader :samples
|
16
|
+
|
17
|
+
|
18
|
+
OPTION_BITS = {
|
19
|
+
0x01 => :acknowledged,
|
20
|
+
0x02 => :broadcast,
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
|
24
|
+
def initialize(packet: nil)
|
25
|
+
@samples = []
|
26
|
+
|
27
|
+
super
|
28
|
+
|
29
|
+
if @parse_bytes
|
30
|
+
@address64 = Address64.new *@parse_bytes.shift(8)
|
31
|
+
@address16 = Address16.new *@parse_bytes.shift(2)
|
32
|
+
@options = @parse_bytes.shift
|
33
|
+
@number_of_samples = @parse_bytes.shift
|
34
|
+
@number_of_samples.times do
|
35
|
+
@samples << Data::Sample.new(parse_bytes: @parse_bytes)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def bytes
|
42
|
+
super +
|
43
|
+
(address64 || Address64::COORDINATOR).to_a +
|
44
|
+
(address16 || Address16::COORDINATOR).to_a +
|
45
|
+
[options || 0x00] +
|
46
|
+
[(samples || []).length] +
|
47
|
+
samples.map(&:to_a).reduce(:+)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'addressed_frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
# This frame is similar to Transmit Request (0x10), but it also requires you to specify the application-
|
7
|
+
# layer addressing fields: endpoints, cluster ID, and profile ID.
|
8
|
+
#
|
9
|
+
# This frame causes the device to send payload data as an RF packet to a specific destination, using
|
10
|
+
# specific source and destination endpoints, cluster ID, and profile ID.
|
11
|
+
#
|
12
|
+
# * For broadcast transmissions, set the 64-bit destination address to 0x000000000000FFFF .
|
13
|
+
# Address the coordinator by either setting the 64-bit address to all 0x00s and the 16-bit address
|
14
|
+
# to 0xFFFE, or setting the 64-bit address to the coordinator's 64-bit address and the 16-bit
|
15
|
+
# address to 0x0000.
|
16
|
+
#
|
17
|
+
# * For all other transmissions, setting the 16-bit address to the correct 16-bit address helps
|
18
|
+
# improve performance when transmitting to multiple destinations. If you do not know a 16-bit
|
19
|
+
# address, set this field to 0xFFFE (unknown). If successful, the Transmit Status frame (0x8B)
|
20
|
+
# indicates the discovered 16-bit address.
|
21
|
+
#
|
22
|
+
# You can set the broadcast radius from 0 up to NH to 0xFF. If set to 0, the value of NH specifies the
|
23
|
+
# broadcast radius (recommended). This parameter is only used for broadcast transmissions.
|
24
|
+
#
|
25
|
+
# You can read the maximum number of payload bytes with the NP command.
|
26
|
+
class ExplicitAddressingCommand < AddressedFrame
|
27
|
+
api_id 0x11
|
28
|
+
|
29
|
+
attr_accessor :source_endpoint
|
30
|
+
attr_accessor :destination_endpoint
|
31
|
+
attr_accessor :cluster_id # 2 bytes
|
32
|
+
attr_accessor :profile_id # 2 bytes
|
33
|
+
attr_accessor :broadcast_radius
|
34
|
+
attr_accessor :transmission_options
|
35
|
+
attr_accessor :data
|
36
|
+
|
37
|
+
|
38
|
+
OPTIONS = {
|
39
|
+
0x01 => :disable_retries,
|
40
|
+
0x08 => :multicast_addressing,
|
41
|
+
0x20 => :enable_aps_encryption,
|
42
|
+
0x40 => :extended_transmission_timeout,
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
def initialize(packet: nil)
|
48
|
+
super
|
49
|
+
|
50
|
+
if @parse_bytes
|
51
|
+
@source_endpoint = @parse_bytes.shift
|
52
|
+
@destination_endpoint = @parse_bytes.shift
|
53
|
+
@cluster_id = @parse_bytes.shift 2
|
54
|
+
@profile_id = @parse_bytes.shift 2
|
55
|
+
@broadcast_radius = @parse_bytes.shift
|
56
|
+
@transmission_options = @parse_bytes.shift
|
57
|
+
@data = @parse_bytes
|
58
|
+
@parse_bytes = []
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def bytes
|
64
|
+
super + [options || 0x00] + (data || [])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'frame'
|
3
|
+
require_relative '../bytes'
|
4
|
+
|
5
|
+
module XBee
|
6
|
+
module Frames
|
7
|
+
# When a device configured with explicit API Rx Indicator (AO = 1) receives an RF packet, it sends it out
|
8
|
+
# the serial interface using this message type.
|
9
|
+
class ExplicitRXIndicator < Frame
|
10
|
+
api_id 0x91
|
11
|
+
|
12
|
+
|
13
|
+
OPTION_BITS = {
|
14
|
+
0x01 => 'Acknowledged',
|
15
|
+
0x02 => 'Broadcast packet',
|
16
|
+
0x20 => 'Encrypted with APS encryption',
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
|
20
|
+
attr_accessor :address64
|
21
|
+
attr_accessor :address16
|
22
|
+
attr_accessor :source_endpoint # 8-bit scalar
|
23
|
+
attr_accessor :destination_endpoint # 8-bit scalar
|
24
|
+
attr_accessor :cluster_id # 16-bit scalar
|
25
|
+
attr_accessor :profile_id # 16-bit scalar
|
26
|
+
attr_accessor :options # 8-bit scalar
|
27
|
+
attr_accessor :data # 0-n bytes
|
28
|
+
|
29
|
+
|
30
|
+
def initialize(packet: nil)
|
31
|
+
super
|
32
|
+
|
33
|
+
if @parse_bytes
|
34
|
+
@address64 = Address64.new *@parse_bytes.shift(8)
|
35
|
+
@address16 = Address16.new *@parse_bytes.shift(2)
|
36
|
+
@source_endpoint = @parse_bytes.shift
|
37
|
+
@destination_endpoint = @parse_bytes.shift
|
38
|
+
@cluster_id = Bytes.unsigned_int_from_array @parse_bytes.shift(2)
|
39
|
+
@profile_id = Bytes.unsigned_int_from_array @parse_bytes.shift(2)
|
40
|
+
@options = @parse_bytes.shift
|
41
|
+
@data = @parse_bytes
|
42
|
+
@parse_bytes = []
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def bytes
|
48
|
+
super +
|
49
|
+
(address64 || Address64.from_array([0] * 8)).to_a +
|
50
|
+
(address16 || Address16.new(0, 0)).to_a +
|
51
|
+
[source_endpoint || 0x00] +
|
52
|
+
[destination_endpoint || 0x00] +
|
53
|
+
Bytes.array_from_unsigned_int(cluster_id || 0) +
|
54
|
+
Bytes.array_from_unsigned_int(profile_id || 0) +
|
55
|
+
[options || 0x00] +
|
56
|
+
(data || [])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|