xbee 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|