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,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'semantic_logger'
|
3
|
+
|
4
|
+
require_relative '../address_16'
|
5
|
+
require_relative '../address_64'
|
6
|
+
require_relative '../exceptions/frame_format_error'
|
7
|
+
require_relative '../exceptions/unknown_frame_type'
|
8
|
+
|
9
|
+
module XBee
|
10
|
+
module Frames
|
11
|
+
class Frame
|
12
|
+
include SemanticLogger::Loggable
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# [<Integer, Frame>] Map of frame type IDs to implementation classes
|
16
|
+
@@frame_types = {}
|
17
|
+
|
18
|
+
# Registers the frame type
|
19
|
+
def api_id(byte)
|
20
|
+
@@frame_types ||= {}
|
21
|
+
raise "Attempted to redefine API ID #{byte.inspect}" if @@frame_types.has_key? byte
|
22
|
+
@@frame_types[byte] = self
|
23
|
+
|
24
|
+
define_singleton_method(:frame_type) do
|
25
|
+
byte
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method(:frame_type) do
|
29
|
+
byte
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def from_packet(packet)
|
35
|
+
raise Exceptions::UnknownFrameType, packet unless @@frame_types.has_key? packet.data[0]
|
36
|
+
@@frame_types[packet.data[0]].new packet: packet
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# [XBee::Packet] if this frame was received, it'll belong to a Packet. (Frames prepped for transmit have no Packet association.)
|
42
|
+
attr_reader :packet
|
43
|
+
|
44
|
+
# Subclasses should shift +@parse_bytes+ as necessary to get their data fields.
|
45
|
+
def initialize(packet: nil)
|
46
|
+
logger.trace 'Initializing...', packet: packet
|
47
|
+
@packet = packet
|
48
|
+
if packet
|
49
|
+
@parse_bytes = packet.data.dup
|
50
|
+
@frame_type = @parse_bytes.shift
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def bytes
|
56
|
+
[frame_type]
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def to_packet
|
61
|
+
@packet = Packet.new bytes
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
# The base class for frames that have an ID for tracking the radio's processing.
|
7
|
+
class IdentifiedFrame < Frame
|
8
|
+
attr_accessor :id
|
9
|
+
|
10
|
+
def initialize(packet: nil)
|
11
|
+
super
|
12
|
+
|
13
|
+
if @parse_bytes
|
14
|
+
@id = @parse_bytes.shift
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def bytes
|
20
|
+
super + [(id || 0x00)]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
class ModemStatus < Frame
|
7
|
+
api_id 0x8A
|
8
|
+
|
9
|
+
|
10
|
+
STATUSES = {
|
11
|
+
0x00 => 'Hardware reset',
|
12
|
+
0x01 => 'Watchdog timer reset',
|
13
|
+
0x02 => 'Joined network',
|
14
|
+
0x03 => 'Disassociated',
|
15
|
+
0x06 => 'Coordinator started',
|
16
|
+
0x07 => 'Network security key was updated',
|
17
|
+
0x0D => 'Voltage supply limit exceeded',
|
18
|
+
0x11 => 'Modem configuration changed while join in progress',
|
19
|
+
}
|
20
|
+
|
21
|
+
attr_accessor :status
|
22
|
+
|
23
|
+
|
24
|
+
def initialize(packet: nil)
|
25
|
+
super
|
26
|
+
|
27
|
+
if @parse_bytes
|
28
|
+
@status = @parse_bytes.shift
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def status_string
|
34
|
+
if status < 0x80
|
35
|
+
STATUSES[status]
|
36
|
+
else
|
37
|
+
'Ember ZigBee stack error'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def bytes
|
43
|
+
super + [status || 0x00]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'unidentified_addressed_frame'
|
3
|
+
require_relative '../bytes'
|
4
|
+
|
5
|
+
module XBee
|
6
|
+
module Frames
|
7
|
+
class NodeIdentificationIndicator < UnidentifiedAddressedFrame
|
8
|
+
api_id 0x95
|
9
|
+
|
10
|
+
attr_accessor :options
|
11
|
+
attr_accessor :source_address16
|
12
|
+
attr_accessor :source_address64
|
13
|
+
attr_accessor :parent_address16
|
14
|
+
attr_accessor :identifier
|
15
|
+
attr_accessor :device_type
|
16
|
+
attr_accessor :source_event
|
17
|
+
attr_accessor :profile_id
|
18
|
+
attr_accessor :manufacturer_id
|
19
|
+
|
20
|
+
|
21
|
+
def initialize(packet: nil)
|
22
|
+
super
|
23
|
+
|
24
|
+
if @parse_bytes
|
25
|
+
@options = @parse_bytes.shift
|
26
|
+
@source_address16 = Address16.new *@parse_bytes.shift(2)
|
27
|
+
@source_address64 = Address64.new *@parse_bytes.shift(8)
|
28
|
+
@identifier = @parse_bytes.shift 2
|
29
|
+
@parent_address16 = Address16.new *@parse_bytes.shift(2)
|
30
|
+
@device_type = @parse_bytes.shift
|
31
|
+
@source_event = @parse_bytes.shift
|
32
|
+
@profile_id = Bytes.unsigned_int_from_array @parse_bytes.shift(2)
|
33
|
+
@manufacturer_id = Bytes.unsigned_int_from_array @parse_bytes.shift(2)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'unidentified_addressed_frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
class OverTheAirFirmwareUpdateStatus < UnidentifiedAddressedFrame
|
7
|
+
api_id 0xA0
|
8
|
+
|
9
|
+
attr_accessor :options
|
10
|
+
attr_accessor :bootloader_message
|
11
|
+
attr_accessor :block_number
|
12
|
+
|
13
|
+
attr_accessor :target_address64
|
14
|
+
|
15
|
+
|
16
|
+
def initialize(packet: nil)
|
17
|
+
super
|
18
|
+
|
19
|
+
if @parse_bytes
|
20
|
+
@options = @parse_bytes.shift
|
21
|
+
@bootloader_message = @parse_bytes.shift
|
22
|
+
@block_number = @parse_bytes.shift
|
23
|
+
@target_address64 = Address64.new *@parse_bytes.shift(8)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
# When a device configured with a standard API Rx Indicator (AO = 0) receives an RF data packet, it
|
7
|
+
# sends it out the serial interface using this message type.
|
8
|
+
class ReceivePacket < Frame
|
9
|
+
api_id 0x90
|
10
|
+
|
11
|
+
|
12
|
+
OPTION_BITS = {
|
13
|
+
0x01 => 'Acknowledged',
|
14
|
+
0x02 => 'Broadcast packet',
|
15
|
+
0x20 => 'Encrypted with APS encryption',
|
16
|
+
0x40 => 'End device',
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
|
20
|
+
attr_accessor :address64
|
21
|
+
attr_accessor :address16
|
22
|
+
attr_accessor :options
|
23
|
+
attr_accessor :data
|
24
|
+
|
25
|
+
|
26
|
+
def initialize(packet: nil)
|
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
|
+
@data = @parse_bytes
|
34
|
+
@parse_bytes = []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def bytes
|
40
|
+
super + (address64 || Address64.from_array([0] * 8)).to_a + (address16 || Address16.new(0, 0)).to_a + [options || 0x00] + (data || [])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'addressed_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 RemoteATCommandRequest < AddressedFrame
|
11
|
+
api_id 0x17
|
12
|
+
|
13
|
+
OPTIONS = {
|
14
|
+
0x01 => :disable_ack,
|
15
|
+
0x40 => :extended_transmission_timeout,
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
attr_accessor :at_command
|
19
|
+
attr_accessor :command_parameter
|
20
|
+
attr_accessor :remote_command_options
|
21
|
+
|
22
|
+
|
23
|
+
def initialize(packet: nil)
|
24
|
+
@address16 = Address16::BROADCAST
|
25
|
+
|
26
|
+
super
|
27
|
+
|
28
|
+
if @parse_bytes
|
29
|
+
@remote_command_options = @parse_bytes.shift
|
30
|
+
@at_command = @parse_bytes.shift 2
|
31
|
+
@command_parameter = @parse_bytes
|
32
|
+
@parse_bytes = []
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# @param value [Array/String] The AT command. Must be two bytes, either an array or a string.
|
38
|
+
def at_command=(value)
|
39
|
+
raise ArgumentError, "AT command must be exactly two bytes (got #{value.inspect})" unless value.length == 2
|
40
|
+
if value.respond_to?(:force_encoding)
|
41
|
+
@at_command = value.dup.force_encoding('ASCII').unpack 'C*'
|
42
|
+
else
|
43
|
+
@at_command = value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def bytes
|
49
|
+
super + [remote_command_options || 0x00] + (at_command || [0] * 2) + (command_parameter || [])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'addressed_frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
# If a device receives this frame in response to a Remote Command Request (0x17) frame, the device
|
7
|
+
# sends an AT Command Response (0x97) frame out the serial interface.
|
8
|
+
# Some commands, such as the ND command, may send back multiple frames.
|
9
|
+
class RemoteATCommandResponse < AddressedFrame
|
10
|
+
api_id 0x97
|
11
|
+
|
12
|
+
attr_accessor :at_command
|
13
|
+
attr_accessor :status
|
14
|
+
attr_accessor :data
|
15
|
+
|
16
|
+
|
17
|
+
# Possible values for +status+
|
18
|
+
STATUS = {
|
19
|
+
0 => :ok,
|
20
|
+
1 => :error,
|
21
|
+
2 => :invalid_command,
|
22
|
+
3 => :invalid_parameter,
|
23
|
+
4 => :transmission_failed,
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
|
27
|
+
def initialize(packet: nil)
|
28
|
+
super
|
29
|
+
|
30
|
+
if @parse_bytes
|
31
|
+
@at_command = @parse_bytes.shift 2
|
32
|
+
@status = @parse_bytes.shift
|
33
|
+
@data = @parse_bytes
|
34
|
+
@parse_bytes = []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def bytes
|
40
|
+
super + (command || [0x00] * 2) + [status || 0x00] + (data || [])
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'unidentified_addressed_frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
class RouteRecordIndicator < UnidentifiedAddressedFrame
|
7
|
+
api_id 0xA1
|
8
|
+
|
9
|
+
attr_accessor :options
|
10
|
+
attr_accessor :addresses
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(packet: nil)
|
14
|
+
@addresses = []
|
15
|
+
|
16
|
+
super
|
17
|
+
|
18
|
+
if @parse_bytes
|
19
|
+
@options = @parse_bytes.shift
|
20
|
+
number_of_addresses = @parse_bytes.shift
|
21
|
+
number_of_addresses.times do
|
22
|
+
@addresses.push Address16.new(*@parse_bytes.shift(2))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'addressed_frame'
|
3
|
+
|
4
|
+
module XBee
|
5
|
+
module Frames
|
6
|
+
# This frame causes the device to send payload data as an RF packet to a specific destination.
|
7
|
+
# * For broadcast transmissions, set the 64-bit destination address to 0x000000000000FFFF.
|
8
|
+
# Address the coordinator by either setting the 64-bit address to all 0x00s and the 16-bit address
|
9
|
+
# to 0xFFFE, or setting the 64-bit address to the coordinator's 64-bit address and the 16-bit
|
10
|
+
# address to 0x0000.
|
11
|
+
# * For all other transmissions, setting the 16-bit address to the correct 16-bit address helps
|
12
|
+
# improve performance when transmitting to multiple destinations. If you do not know a 16-bit
|
13
|
+
# address, set this field to 0xFFFE (unknown). If successful, the Transmit Status frame (0x8B)
|
14
|
+
# indicates the discovered 16-bit address.
|
15
|
+
#
|
16
|
+
# You can set the broadcast radius from 0 up to NH. If set to 0, the value of NH specifies the broadcast
|
17
|
+
# radius (recommended). This parameter is only used for broadcast transmissions.
|
18
|
+
#
|
19
|
+
# You can read the maximum number of payload bytes with the NP command.
|
20
|
+
class TransmitRequest < AddressedFrame
|
21
|
+
api_id 0x10
|
22
|
+
|
23
|
+
attr_accessor :broadcast_radius
|
24
|
+
attr_accessor :options
|
25
|
+
attr_accessor :data
|
26
|
+
|
27
|
+
|
28
|
+
OPTIONS = {
|
29
|
+
0x01 => :disable_retries,
|
30
|
+
0x20 => :enable_aps_encryption,
|
31
|
+
0x40 => :extended_transmission_timeout,
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
class << self
|
35
|
+
# Allocates and returns an instance pre-addressed as a broadcast.
|
36
|
+
def broadcast
|
37
|
+
new.tap do |frame|
|
38
|
+
frame.address64 = Address64::BROADCAST
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# Allocates and returns an instance pre-addressed for the coordinator node.
|
44
|
+
def coordinator
|
45
|
+
new.tap do |frame|
|
46
|
+
frame.address64 = Address64::COORDINATOR
|
47
|
+
frame.address16 = Address16::COORDINATOR
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def initialize(packet: nil)
|
54
|
+
super
|
55
|
+
|
56
|
+
if @parse_bytes
|
57
|
+
@broadcast_radius = @parse_bytes.shift
|
58
|
+
@options = @parse_bytes.shift
|
59
|
+
@data = @parse_bytes
|
60
|
+
@parse_bytes = []
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def bytes
|
66
|
+
super + [broadcast_radius || 0x00] + [options || 0x00] + (data || [])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|