Class: XBee::Packet

Inherits:
Object
  • Object
show all
Defined in:
lib/xbee/packet.rb

Constant Summary collapse

START_BYTE =
0x7E
ESCAPE =
0x7D
XON =
0x11
XOFF =
0x13
ESCAPE_XOR =
0x20
ESCAPE_BYTES =
[
  START_BYTE, ESCAPE, XON, XOFF
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ Packet

Returns a new instance of Packet.

Parameters:

  • data (Array<Integer>)

    Byte array



130
131
132
# File 'lib/xbee/packet.rb', line 130

def initialize(data)
  @data = data
end

Class Method Details

.checksum(bytes) ⇒ Object



23
24
25
# File 'lib/xbee/packet.rb', line 23

def checksum(bytes)
  255 - bytes.reduce(&:+) % 256
end

.escape(bytes, options = {}) ⇒ Array<Integer>

Escapes an array of bytes. Ignores the first byte unless ignore_first_byte is set to false in the options hash.

Parameters:

  • bytes (Array<Integer>)

    The array of bytes to escape.

  • options (Hash) (defaults to: {})

    Options hash.

Options Hash (options):

  • :ignore_first_byte (Boolean)

    If the first byte should be ignored (usually true for handling an entire packet since the first byte is START_BYTE). Default true.

Returns:

  • (Array<Integer>)

    Escaped bytes.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/xbee/packet.rb', line 33

def escape(bytes, options = {})
  ignore_first_byte = options.fetch :ignore_first_byte, true

  prepend = []
  if ignore_first_byte
    bytes = bytes.dup
    prepend = [bytes.shift]
  end

  prepend + bytes.reduce([]) do |escaped, b|
    if ESCAPE_BYTES.include?(b)
      escaped << ESCAPE
      escaped << (ESCAPE_XOR ^ b)
    else
      escaped << b
    end
  end
end

.from_byte_enum(bytes) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/xbee/packet.rb', line 104

def from_byte_enum(bytes)
  begin
    loop until bytes.next == START_BYTE
    length = (next_unescaped_byte(bytes) << 8) + next_unescaped_byte(bytes)
  rescue
    raise IOError, 'Packet is too short, unable to read length fields.'
  end
  begin
    data = (1..length).map { next_unescaped_byte bytes }
  rescue
    raise IOError, "Expected data length to be #{length} but got fewer bytes"
  end
  begin
    crc = next_unescaped_byte bytes
  rescue
    raise IOError, 'Packet is too short, unable to read checksum'
  end
  if crc != checksum(data)
    raise IOError, "Expected checksum to be 0x#{checksum(data).to_s 16} but was 0x#{crc.to_s 16}"
  end
  new data
end

.from_bytes(bytes) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/xbee/packet.rb', line 74

def from_bytes(bytes)
  if bytes.length < 4
    raise ArgumentError, "Packet is too short (only #{bytes.length} bytes)"
  end
  if bytes[0] != START_BYTE
    raise ArgumentError, 'Missing start byte'
  end
  data = [START_BYTE] + unescape(bytes[1..-1])
  length = (data[1] << 8) + data[2]
  if length != data.length - 4
    raise ArgumentError, "Expected data length to be #{length} but was #{data.length - 4}"
  end
  crc = checksum(data[3..-2])
  if crc != data[-1]
    raise ArgumentError, "Expected checksum to be 0x#{crc.to_s 16} but was 0x#{data[-1].to_s 16}"
  end
  new data[3..-2]
end

.next_unescaped_byte(bytes) ⇒ Object



94
95
96
97
98
99
100
101
# File 'lib/xbee/packet.rb', line 94

def next_unescaped_byte(bytes)
  byte = bytes.next
  if byte == ESCAPE
    0x20 ^ bytes.next
  else
    byte
  end
end

.special_byte?(byte) ⇒ Boolean

Parameters:

  • byte (Integer)

Returns:

  • (Boolean)


18
19
20
# File 'lib/xbee/packet.rb', line 18

def special_byte?(byte)
  ESCAPE_BYTES.include? byte
end

.unescape(bytes) ⇒ Array<Integer>

When provided a byte array that has escaped data, this returns a new byte array with just the raw data.

Parameters:

  • bytes (Array<Integer>)

    Array of bytes to unescape.

Returns:

  • (Array<Integer>)

    Array of unescaped bytes.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/xbee/packet.rb', line 56

def unescape(bytes)
  byte_escaped = false
  bytes.reduce([]) do |unescaped, b|
    if byte_escaped
      unescaped << (0x20 ^ b)
      byte_escaped = false
    else
      if b == ESCAPE
        byte_escaped = true
      else
        unescaped << b
      end
    end
    unescaped
  end
end

Instance Method Details

#==(other) ⇒ Object



166
167
168
# File 'lib/xbee/packet.rb', line 166

def ==(other)
  data == other.data
end

#bytesObject



150
151
152
# File 'lib/xbee/packet.rb', line 150

def bytes
  [START_BYTE, length >> 8, length & 0xff] + @data + [checksum]
end

#bytes_escapedObject



155
156
157
158
159
160
161
162
163
# File 'lib/xbee/packet.rb', line 155

def bytes_escaped
  [START_BYTE] + bytes[1..-1].flat_map do |b|
    if self.class.special_byte?(b)
      [ESCAPE, 0x20 ^ b]
    else
      b
    end
  end
end

#checksumObject



145
146
147
# File 'lib/xbee/packet.rb', line 145

def checksum
  Packet.checksum @data
end

#dataObject



135
136
137
# File 'lib/xbee/packet.rb', line 135

def data
  @data
end

#lengthObject



140
141
142
# File 'lib/xbee/packet.rb', line 140

def length
  @data.length
end

#to_sObject



171
172
173
# File 'lib/xbee/packet.rb', line 171

def to_s
  'Packet [' + data.map { |b| "0x#{b.to_s 16}" }.join(', ') + ']'
end