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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +13 -0
  3. data/.gitignore +25 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +49 -0
  8. data/examples/check_and_set_destination_address.rb +161 -0
  9. data/examples/read_frames.rb +45 -0
  10. data/examples/response_parser_throughput.rb +35 -0
  11. data/lib/xbee.rb +5 -0
  12. data/lib/xbee/address.rb +23 -0
  13. data/lib/xbee/address_16.rb +39 -0
  14. data/lib/xbee/address_64.rb +40 -0
  15. data/lib/xbee/bytes.rb +21 -0
  16. data/lib/xbee/exceptions/exception.rb +9 -0
  17. data/lib/xbee/exceptions/frame_format_error.rb +9 -0
  18. data/lib/xbee/exceptions/unknown_frame_type.rb +9 -0
  19. data/lib/xbee/frames/addressed_frame.rb +27 -0
  20. data/lib/xbee/frames/at_command.rb +35 -0
  21. data/lib/xbee/frames/at_command_queue_parameter_value.rb +21 -0
  22. data/lib/xbee/frames/at_command_response.rb +31 -0
  23. data/lib/xbee/frames/create_source_route.rb +15 -0
  24. data/lib/xbee/frames/data/sample.rb +72 -0
  25. data/lib/xbee/frames/data_sample_rx_indicator.rb +51 -0
  26. data/lib/xbee/frames/explicit_addressing_command.rb +68 -0
  27. data/lib/xbee/frames/explicit_rx_indicator.rb +60 -0
  28. data/lib/xbee/frames/frame.rb +65 -0
  29. data/lib/xbee/frames/identified_frame.rb +24 -0
  30. data/lib/xbee/frames/many_to_one_route_request_indicator.rb +11 -0
  31. data/lib/xbee/frames/modem_status.rb +47 -0
  32. data/lib/xbee/frames/node_identification_indicator.rb +38 -0
  33. data/lib/xbee/frames/over_the_air_firmware_update_status.rb +28 -0
  34. data/lib/xbee/frames/receive_packet.rb +44 -0
  35. data/lib/xbee/frames/remote_at_command_request.rb +53 -0
  36. data/lib/xbee/frames/remote_at_command_response.rb +45 -0
  37. data/lib/xbee/frames/route_record_indicator.rb +28 -0
  38. data/lib/xbee/frames/transmit_request.rb +70 -0
  39. data/lib/xbee/frames/transmit_status.rb +63 -0
  40. data/lib/xbee/frames/unidentified_addressed_frame.rb +26 -0
  41. data/lib/xbee/frames/xbee_sensor_read_indicator.rb +50 -0
  42. data/lib/xbee/packet.rb +175 -0
  43. data/lib/xbee/version.rb +5 -0
  44. data/lib/xbee/xbee.rb +109 -0
  45. data/xbee.gemspec +40 -0
  46. 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
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XBee
4
+ module Exceptions
5
+ # Base class for all XBee exception types, for convenience when consumer code wants to rescue anything XBee raises.
6
+ class Exception < RuntimeError
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'exception'
3
+
4
+ module XBee
5
+ module Exceptions
6
+ class FrameFormatError < XBee::Exceptions::Exception
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'exception'
3
+
4
+ module XBee
5
+ module Exceptions
6
+ class UnknownFrameType < ::XBee::Exceptions::Exception
7
+ end
8
+ end
9
+ 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