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