ruby-xbee 1.1.0 → 1.2.0
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 +5 -13
- data/README.rdoc +5 -4
- data/bin/apilisten.rb +2 -2
- data/bin/ota_upgrade.rb +213 -0
- data/lib/apimode/frame/explicit_addressing_command.rb +46 -1
- data/lib/apimode/frame/explicit_rx_indicator.rb +17 -0
- data/lib/apimode/frame/frame.rb +63 -23
- data/lib/apimode/frame/modem_status.rb +1 -1
- data/lib/apimode/frame/remote_command_response.rb +1 -1
- data/lib/apimode/xbee_api.rb +63 -23
- data/lib/module_config.rb +17 -0
- data/lib/ruby_xbee.rb +7 -6
- data/test/ruby_xbee_api_frame_eac_test.rb +132 -0
- data/test/ruby_xbee_api_frame_erxi_test.rb +54 -0
- data/test/ruby_xbee_api_frame_test.rb +530 -0
- data/test/ruby_xbee_apimode_test.rb +52 -0
- data/test/ruby_xbee_test.rb +3 -3
- data/test/test_helper.rb +11 -8
- metadata +124 -11
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
MTRhMGYwODc0MTVjN2ZiZTI3MjI0ODJmYTg0MjUwYWQwNjEwODdhNQ==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 69899610a08432193f6c6aa024e12d7d1d1f1307
|
4
|
+
data.tar.gz: 4ad1f0cf2f2f80632767e79b28317d076032071c
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
YWEwOGMyMjE1MTk4MDhmMWE3Mjc5ZGVmZGQ3OTNjNzNhNTkyZmFkNjMzYTYw
|
11
|
-
NzYxZWY1MDlhZmQ1N2MzNGJjMWZiZDg4ZGFiZWUzMDQzYmJkYmE=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
MTVmODU0MzllZjM4ZmNhYWI5ZGM5MDMyMGE1ZjJmYzJiNDJlMTc3YTYxYjhk
|
14
|
-
ZmQ1MmU5Y2QyYjJiN2YxNzdlMzJlM2JjMGQ0NmRmNGVhNmE2MWJjYThiMWNl
|
15
|
-
YmJkZTc3ZmNiYjRmOWMwYjEwYmUzOTY3MTQzZjBmNmFmM2M5Njc=
|
6
|
+
metadata.gz: be955c5442b964912f43af8aa2fa15a3a3ac123adc48d98095b8b929570fe1c78f85f4141b0e2f949ae5cda6ee8ae4bfadf402e64d05e48eca90efca4d969782
|
7
|
+
data.tar.gz: 8347bdc735975f245bcbc5feb0e80efa24a01a3b7e488eebe0ae063ea521846754d09d5878ed47030e768ac95d3b2926b15cc5c2f6dcefa3cede73f20f34ffc5
|
data/README.rdoc
CHANGED
@@ -47,10 +47,11 @@ Example use of the core XBee ruby class can be found in the utilities themselves
|
|
47
47
|
xbeeinfo.rb, xbeeconfigure.rb, xbeedio.rb, xbeelisten.rb, xbeesend.rb
|
48
48
|
|
49
49
|
== Quick Start
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
1. install ruby 1.9.3 or 2.0.0 for your platform (see http://www.ruby-lang.org)
|
51
|
+
2. download the ruby-xbee ( git clone https://github.com/exsilium/ruby-xbee )
|
52
|
+
3. install the dependencies `bundle install`
|
53
|
+
4. determine your /dev string and configure that in bin/ruby-xbee.rb
|
54
|
+
5. try ./bin/xbeeinfo.rb
|
54
55
|
|
55
56
|
== Detailed Quick Start
|
56
57
|
=== clone the ruby-xbee repository.
|
data/bin/apilisten.rb
CHANGED
@@ -6,9 +6,9 @@ require 'ruby-xbee'
|
|
6
6
|
require 'pp'
|
7
7
|
|
8
8
|
@uart_config = XBee::Config::XBeeUARTConfig.new()
|
9
|
-
@xbee = XBee::BaseAPIModeInterface.new(@xbee_usbdev_str, @uart_config
|
9
|
+
@xbee = XBee::BaseAPIModeInterface.new(@xbee_usbdev_str, @uart_config)
|
10
10
|
|
11
11
|
# read XBee output forever
|
12
12
|
while 1
|
13
13
|
@xbee.getresponse true
|
14
|
-
end
|
14
|
+
end
|
data/bin/ota_upgrade.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Programmable XBee module Over the Air (OTA) Upgrade
|
4
|
+
# A sample using ruby-xbee and xmodem.
|
5
|
+
#
|
6
|
+
# Please note, this only works for PROGRAMMABLE XBee modules and for uploading
|
7
|
+
# the *application* firmware, not to be confused with the radio firmware.
|
8
|
+
#
|
9
|
+
# The coordinator must be running in API 1 mode because the programmable XBEE
|
10
|
+
# does not properly in the API mode 2 (with escaped control characters).
|
11
|
+
#
|
12
|
+
# This is specific to Series 2 radios and ZigBee
|
13
|
+
#
|
14
|
+
# This script is not interactive, please define the below CONSTANTS before use
|
15
|
+
#
|
16
|
+
# If you are using broadcast addresses, make sure only one device is there to
|
17
|
+
# answer, concurrent upgrades have not been implemented
|
18
|
+
#
|
19
|
+
# Prerequisites: Install the xmodem gem by: gem install xmodem it's purposely
|
20
|
+
# left out from runtime dependencies.
|
21
|
+
#
|
22
|
+
# Usage: 1) set the constants
|
23
|
+
# 2) make sure the ruby-xbee.rb has the correct device configured
|
24
|
+
# 3) ./ota_upgrade.rb
|
25
|
+
#
|
26
|
+
$: << File.dirname(__FILE__)
|
27
|
+
|
28
|
+
require 'date'
|
29
|
+
require 'ruby-xbee'
|
30
|
+
require 'pp'
|
31
|
+
require 'socket'
|
32
|
+
|
33
|
+
require 'rubygems'
|
34
|
+
gem 'xmodem'
|
35
|
+
require 'xmodem'
|
36
|
+
|
37
|
+
### CONSTANTS - SET BEFORE USE
|
38
|
+
|
39
|
+
# Target Address of the device being upgraded 0x000000000000FFFF for broadcast
|
40
|
+
TARGET_ADDRESS = 0x000000000000FFFF
|
41
|
+
# Target network, 0xFFFE for broadcast
|
42
|
+
TARGET_NETWORK = 0xFFFE
|
43
|
+
# OTA password, nil, if no password used
|
44
|
+
PASSWORD = nil
|
45
|
+
# Firmware file being uploaded, can be full path
|
46
|
+
TRANSFER_FILE = "blink_led_0_0_1_OTA_blank_pass.bin"
|
47
|
+
# If not on unix, this is the port used for XMODEM IO
|
48
|
+
LOCAL_PORT = 2000
|
49
|
+
|
50
|
+
### END OF CONSTANTS
|
51
|
+
|
52
|
+
@uart_config = XBee::Config::XBeeUARTConfig.new()
|
53
|
+
@xbee = XBee::Series2APIModeInterface.new(@xbee_usbdev_str, @uart_config)
|
54
|
+
|
55
|
+
# Create server/socket for XModem transfer
|
56
|
+
@server=nil
|
57
|
+
@unix_socket = "/tmp/xmodem-transfer.sock"
|
58
|
+
|
59
|
+
# State variables
|
60
|
+
@transfer_started = false
|
61
|
+
@transfer_eot = 0
|
62
|
+
|
63
|
+
if !FileTest.exists?(TRANSFER_FILE)
|
64
|
+
puts "Firmware file not found: #{TRANSFER_FILE}"
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
|
68
|
+
if ENV['OS'] != "Windows_NT"
|
69
|
+
File.delete( @unix_socket ) if FileTest.exists?( @unix_socket )
|
70
|
+
@server = UNIXServer.new(@unix_socket)
|
71
|
+
else
|
72
|
+
@server = TCPServer.new(LOCAL_PORT)
|
73
|
+
end
|
74
|
+
|
75
|
+
puts "---------------------------------------------------"
|
76
|
+
puts " Over The Air Upgrade "
|
77
|
+
puts "---------------------------------------------------"
|
78
|
+
puts "Target address: 0x%x" % TARGET_ADDRESS
|
79
|
+
puts "Target network: 0x%x" % TARGET_NETWORK
|
80
|
+
puts "---------------------------------------------------"
|
81
|
+
|
82
|
+
#XMODEM::logger.outputters = Outputter.stdout
|
83
|
+
XMODEM::timeout_seconds = 10.0 # Let's prolong the timeout
|
84
|
+
XMODEM::block_size = 64 # XBEE uses non-standard block size
|
85
|
+
|
86
|
+
puts "XMODEM: Transfer blocksize: #{XMODEM::block_size}"
|
87
|
+
puts "XMODEM: Timeout in seconds: #{XMODEM::timeout_seconds}"
|
88
|
+
|
89
|
+
transfer_thread = Thread.new {
|
90
|
+
session = @server.accept
|
91
|
+
session.sync = true
|
92
|
+
file = File.new(TRANSFER_FILE, "rb")
|
93
|
+
puts "Starting file transfer of: #{TRANSFER_FILE}"
|
94
|
+
XMODEM::send(session, file)
|
95
|
+
session.close
|
96
|
+
puts "<CLOSED>"
|
97
|
+
file.close
|
98
|
+
}
|
99
|
+
|
100
|
+
##
|
101
|
+
# If the target board does not have proper OTA enabled application running
|
102
|
+
# or if the previous upgrades have been unsuccessful to the point where the
|
103
|
+
# module is unable to run and goes to bootloader, the following method
|
104
|
+
# can be used instead of the init_ota_upgrade. If the application is running
|
105
|
+
# the following request does not get any response
|
106
|
+
#
|
107
|
+
# Please note that the following works only if the target is in bootloader and
|
108
|
+
# when explicit addressing is used (broadcast address doesn't work, altough
|
109
|
+
# the target network can be broadcast)!
|
110
|
+
frame_id = @xbee.next_frame_id
|
111
|
+
frame_to_send = XBee::Frame::ExplicitAddressingCommand.new(frame_id, TARGET_ADDRESS, TARGET_NETWORK, 0xE8, 0xE8, 0x0011, 0xC105, 0x00, 0x00, "F")
|
112
|
+
@xbee.xbee_serialport.write(frame_to_send._dump)
|
113
|
+
|
114
|
+
##
|
115
|
+
# The following call is meant for Applications that have the OTA upgrade
|
116
|
+
# enabled. In case of wrong password a "bad password" message is sent in reply
|
117
|
+
#
|
118
|
+
# Let's send a initialization package for starting OTA upgrade
|
119
|
+
@xbee.init_ota_upgrade(PASSWORD, TARGET_ADDRESS, TARGET_NETWORK)
|
120
|
+
|
121
|
+
# main loop
|
122
|
+
while 1
|
123
|
+
begin
|
124
|
+
|
125
|
+
r = @xbee.getresponse true
|
126
|
+
|
127
|
+
if r.kind_of?(XBee::Frame::ExplicitRxIndicator) and r.cluster_id == 0x0011 and r.profile_id == 0xC105
|
128
|
+
if !@transfer_started and r.received_data == "C"
|
129
|
+
puts "XBEE: Got response with frame type: 0x%02x" % r.api_identifier
|
130
|
+
puts "XBEE: Source address of the one waiting for new firmware: %x" % r.source_address
|
131
|
+
# Connect to socket
|
132
|
+
if ENV['OS'] != "Windows_NT"
|
133
|
+
socket = UNIXSocket.open(@unix_socket)
|
134
|
+
else
|
135
|
+
socket = TCPSocket.new('localhost', LOCAL_PORT)
|
136
|
+
end
|
137
|
+
socket.sync=true
|
138
|
+
@transfer_started = true
|
139
|
+
puts "Connected to socket"
|
140
|
+
sleep(0.1)
|
141
|
+
end
|
142
|
+
|
143
|
+
if r.received_data == "C"
|
144
|
+
print "C"
|
145
|
+
elsif r.received_data.ord == XMODEM::ACK
|
146
|
+
print "O"
|
147
|
+
elsif r.received_data.ord == XMODEM::NAK
|
148
|
+
print "X"
|
149
|
+
elsif r.received_data == "F" and !@transfer_started
|
150
|
+
puts "F - Upgrade started from bootloader"
|
151
|
+
next
|
152
|
+
else
|
153
|
+
print "?"
|
154
|
+
end
|
155
|
+
|
156
|
+
unless socket.closed?
|
157
|
+
# As we have the socket open, we can presume the tranmission is ok to
|
158
|
+
# start.
|
159
|
+
if transfer_thread.alive? and @transfer_eot < 1
|
160
|
+
# Send the received payload to local XMODEM socket
|
161
|
+
socket.write(r.received_data)
|
162
|
+
|
163
|
+
# Try to get the block that the XMODEM wants to send
|
164
|
+
# 3 header bytes, data block, 2 bytes checksum
|
165
|
+
# We read the first byte separately in case it's the end of file
|
166
|
+
data_to_send = ""
|
167
|
+
b = XMODEM::receive_getbyte(socket)
|
168
|
+
data_to_send << b
|
169
|
+
|
170
|
+
if data_to_send.ord == XMODEM::SOH
|
171
|
+
(XMODEM::block_size+4).times do
|
172
|
+
b = XMODEM::receive_getbyte(socket)
|
173
|
+
data_to_send << b
|
174
|
+
end
|
175
|
+
elsif data_to_send.ord == XMODEM::EOT
|
176
|
+
print "<END-OF-TRANSMISSION>"
|
177
|
+
@transfer_eot += 1
|
178
|
+
end
|
179
|
+
|
180
|
+
#puts "Inspecting packet to encapsulate: #{data_to_send.inspect}"
|
181
|
+
|
182
|
+
# We have the XMODEM block, we should encapsulate and send it to the radio
|
183
|
+
frame_id = @xbee.next_frame_id
|
184
|
+
frame_to_send = XBee::Frame::ExplicitAddressingCommand.new(frame_id, r.source_address, r.source_network, 0xE8, 0xE8, 0x0011, 0xC105, 0x00, 0x00, data_to_send)
|
185
|
+
@xbee.xbee_serialport.write(frame_to_send._dump)
|
186
|
+
print "."
|
187
|
+
else
|
188
|
+
# Transfer completed
|
189
|
+
socket.close
|
190
|
+
puts "XBEE: Transfer completed!"
|
191
|
+
break
|
192
|
+
end
|
193
|
+
end
|
194
|
+
elsif r.kind_of?(XBee::Frame::ExplicitRxIndicator) and r.cluster_id == 0x1000 and r.profile_id == 0xC105 and r.received_data == "bad password"
|
195
|
+
puts "XBEE: Bad password sent for device: 0x%x. Giving up." % r.source_address
|
196
|
+
break
|
197
|
+
end
|
198
|
+
|
199
|
+
if r.kind_of?(XBee::Frame::TransmitStatus)
|
200
|
+
#puts "XBEE: Got response with frame type: 0x%02x" % r.api_identifier
|
201
|
+
#puts "XBEE: cmd_data: #{r.cmd_data.inspect}"
|
202
|
+
end
|
203
|
+
|
204
|
+
rescue RuntimeError => e
|
205
|
+
puts e
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
unless ENV['OS'] == "Windows_NT"
|
210
|
+
File.delete( @unix_socket ) if FileTest.exists?( @unix_socket )
|
211
|
+
end
|
212
|
+
|
213
|
+
puts "Exiting!"
|
@@ -1,7 +1,52 @@
|
|
1
1
|
module XBee
|
2
2
|
module Frame
|
3
|
+
##
|
4
|
+
# Explicit Addressing ZigBee Command Frame (0x11)
|
5
|
+
#
|
6
|
+
# This frame allows ZigBee Application Layer fields to be specified for
|
7
|
+
# a data transmission. This frame is similar to Zigbee Transmit Request (0x10)
|
8
|
+
# but requires the ZigBee application layer addressing fields to be set.
|
9
|
+
#
|
10
|
+
# This frame is also used for triggering Programmable XBee (S2B) module
|
11
|
+
# Over-the-Air firmware upgrade process.
|
12
|
+
#
|
3
13
|
class ExplicitAddressingCommand < Base
|
4
|
-
def
|
14
|
+
def api_identifier ; 0x11 ; end
|
15
|
+
|
16
|
+
attr_accessor :destination_address, :destination_network, :source_endpoint, :destination_endpoint, :cluster_id, :profile_id, :broadcast_radius, :transmit_options, :payload, :payload_pack_string
|
17
|
+
|
18
|
+
def initialize(frame_id = nil, destination_address = 0x000000000000ffff, destination_network = 0x0000fffe, source_endpoint = nil, destination_endpoint = nil, cluster_id = nil, profile_id = nil, broadcast_radius = 0x00, transmit_options = 0x00, payload = nil, payload_pack_string = "a*")
|
19
|
+
self.frame_id = frame_id
|
20
|
+
self.destination_address = destination_address
|
21
|
+
self.destination_network = destination_network
|
22
|
+
self.source_endpoint = source_endpoint
|
23
|
+
self.destination_endpoint = destination_endpoint
|
24
|
+
self.cluster_id = cluster_id
|
25
|
+
self.profile_id = profile_id
|
26
|
+
self.broadcast_radius = broadcast_radius
|
27
|
+
self.transmit_options = transmit_options
|
28
|
+
self.payload = payload
|
29
|
+
self.payload_pack_string = payload_pack_string
|
30
|
+
end
|
31
|
+
|
32
|
+
def cmd_data=(data_string)
|
33
|
+
# We need to read in the 64-bit destination_address in two 32-bit parts.
|
34
|
+
dest_high = dest_low = 0
|
35
|
+
self.frame_id, dest_high, dest_low, self.destination_network, self.source_endpoint, self.destination_endpoint, self.cluster_id, self.profile_id, self.broadcast_radius, self.transmit_options, self.payload = data_string.unpack("CNNnCCnnCC#{payload_pack_string}")
|
36
|
+
self.destination_address = dest_high << 32 | dest_low
|
37
|
+
end
|
38
|
+
|
39
|
+
def cmd_data
|
40
|
+
# We need to pack the 64-bit destination_address in two 32-bit parts.
|
41
|
+
dest_high = (self.destination_address >> 32) & 0xFFFFFFFF
|
42
|
+
dest_low = self.destination_address & 0xFFFFFFFF
|
43
|
+
if payload.nil?
|
44
|
+
# By default we send a null-byte payload \0
|
45
|
+
[self.frame_id, dest_high, dest_low, self.destination_network, self.source_endpoint, self.destination_endpoint, self.cluster_id, self.profile_id, self.broadcast_radius, self.transmit_options, self.payload].pack("CNNnCCnnCCx")
|
46
|
+
else
|
47
|
+
[self.frame_id, dest_high, dest_low, self.destination_network, self.source_endpoint, self.destination_endpoint, self.cluster_id, self.profile_id, self.broadcast_radius, self.transmit_options, self.payload].pack("CNNnCCnnCC#{payload_pack_string}")
|
48
|
+
end
|
49
|
+
end
|
5
50
|
end
|
6
51
|
end
|
7
52
|
end
|
@@ -1,6 +1,23 @@
|
|
1
1
|
module XBee
|
2
2
|
module Frame
|
3
|
+
##
|
4
|
+
# ZigBee Explicit Rx Indicator (0x91) (AO=1)
|
3
5
|
class ExplicitRxIndicator < ReceivedFrame
|
6
|
+
def api_identifier ; 0x91 ; end
|
7
|
+
attr_accessor :source_address, :source_network, :source_endpoint, :destination_endpoint, :cluster_id, :profile_id, :receive_options, :received_data
|
8
|
+
|
9
|
+
def initialize(data = nil)
|
10
|
+
super(data) && (yield self if block_given?)
|
11
|
+
end
|
12
|
+
|
13
|
+
def cmd_data=(data_string)
|
14
|
+
# We need to read in the 64-bit source_address in two 32-bit parts.
|
15
|
+
src_high = src_low = 0
|
16
|
+
src_high, src_low, self.source_network, self.source_endpoint, self.destination_endpoint, self.cluster_id, self.profile_id, self.receive_options, self.received_data = data_string.unpack("NNnCCnnCa*")
|
17
|
+
self.source_address = src_high << 32 | src_low
|
18
|
+
@cmd_data = data_string
|
19
|
+
end
|
20
|
+
|
4
21
|
end
|
5
22
|
end
|
6
23
|
end
|
data/lib/apimode/frame/frame.rb
CHANGED
@@ -15,21 +15,48 @@ module XBee
|
|
15
15
|
0xFF - (data.unpack("C*").inject(0) { |sum, byte| (sum + byte) & 0xFF })
|
16
16
|
end
|
17
17
|
|
18
|
-
def Frame.new(source_io)
|
18
|
+
def Frame.new(source_io, api_mode = :API1)
|
19
19
|
stray_bytes = []
|
20
|
+
unless api_mode == :API1 or api_mode == :API2
|
21
|
+
raise "XBee api_mode must be either :API1 (non-escaped) or :API2 (escaped, default)"
|
22
|
+
end
|
20
23
|
until (start_delimiter = source_io.readchar.unpack('H*').join.to_i(16)) == 0x7e
|
21
24
|
#puts "Stray byte 0x%x" % start_delimiter
|
22
25
|
print "DEBUG: #{start_delimiter} | " if $DEBUG
|
23
26
|
stray_bytes << start_delimiter
|
24
27
|
end
|
25
|
-
|
26
|
-
|
28
|
+
if $VERBOSE
|
29
|
+
puts "Got some stray bytes for ya: #{stray_bytes.map {|b| "0x%x" % b} .join(", ")}" unless stray_bytes.empty?
|
30
|
+
end
|
31
|
+
if(api_mode == :API1)
|
32
|
+
header = source_io.read(3)
|
33
|
+
elsif(api_mode == :API2)
|
34
|
+
header = source_io.read(2).xb_unescape
|
35
|
+
end
|
27
36
|
print "Reading ... header after start byte: #{header.unpack("C*").join(", ")} | " if $DEBUG
|
28
37
|
frame_remaining = frame_length = api_identifier = cmd_data = ""
|
29
|
-
if
|
30
|
-
|
38
|
+
if api_mode == :API2
|
39
|
+
if header.length == 2
|
40
|
+
frame_length = header.unpack("n").first
|
41
|
+
else
|
42
|
+
read_extra_byte = source_io.readchar
|
43
|
+
if(read_extra_byte == "\175")
|
44
|
+
# We stumbled upon another escaped character, read another byte and unescape
|
45
|
+
read_extra_byte += source_io.readchar
|
46
|
+
read_extra_byte = read_extra_byte.xb_unescape
|
47
|
+
else
|
48
|
+
header += read_extra_byte
|
49
|
+
end
|
50
|
+
frame_length = header.unpack("n")
|
51
|
+
end
|
52
|
+
api_identifier = source_io.readchar
|
53
|
+
if(api_identifier == "\175")
|
54
|
+
api_identifier += source_io.readchar
|
55
|
+
api_identifier = api_identifier.xb_unescape
|
56
|
+
end
|
57
|
+
api_identifier = api_identifier.unpack("C").first
|
31
58
|
else
|
32
|
-
frame_length, api_identifier = header.unpack("
|
59
|
+
frame_length, api_identifier = header.unpack("nC")
|
33
60
|
end
|
34
61
|
#### DEBUG ####
|
35
62
|
if $DEBUG then
|
@@ -38,10 +65,17 @@ module XBee
|
|
38
65
|
end
|
39
66
|
#### DEBUG ####
|
40
67
|
cmd_data_intended_length = frame_length - 1
|
41
|
-
|
42
|
-
|
68
|
+
if api_mode == :API2
|
69
|
+
while ((unescaped_length = cmd_data.xb_unescape.length) < cmd_data_intended_length)
|
70
|
+
cmd_data += source_io.read(cmd_data_intended_length - unescaped_length)
|
71
|
+
end
|
72
|
+
data = api_identifier.chr + cmd_data.xb_unescape
|
73
|
+
else
|
74
|
+
while (cmd_data.length < cmd_data_intended_length)
|
75
|
+
cmd_data += source_io.read(cmd_data_intended_length)
|
76
|
+
end
|
77
|
+
data = api_identifier.chr + cmd_data
|
43
78
|
end
|
44
|
-
data = api_identifier.chr + cmd_data.xb_unescape
|
45
79
|
sent_checksum = source_io.getc.unpack('H*').join.to_i(16)
|
46
80
|
#### DEBUG ####
|
47
81
|
if $DEBUG then
|
@@ -56,15 +90,15 @@ module XBee
|
|
56
90
|
case data[0].unpack('H*')[0].to_i(16)
|
57
91
|
when 0x8A
|
58
92
|
ModemStatus.new(data)
|
59
|
-
when 0x88
|
93
|
+
when 0x88
|
60
94
|
ATCommandResponse.new(data)
|
61
|
-
when 0x97
|
95
|
+
when 0x97
|
62
96
|
RemoteCommandResponse.new(data)
|
63
|
-
when 0x8B
|
97
|
+
when 0x8B
|
64
98
|
TransmitStatus.new(data)
|
65
|
-
when 0x90
|
99
|
+
when 0x90
|
66
100
|
ReceivePacket.new(data)
|
67
|
-
when 0x91
|
101
|
+
when 0x91
|
68
102
|
ExplicitRxIndicator.new(data)
|
69
103
|
when 0x92
|
70
104
|
IODataSampleRxIndicator.new(data)
|
@@ -88,22 +122,29 @@ module XBee
|
|
88
122
|
Array(api_identifier).pack("C") + cmd_data
|
89
123
|
end
|
90
124
|
|
91
|
-
def _dump
|
125
|
+
def _dump(api_mode = :API1)
|
126
|
+
unless api_mode == :API1 or api_mode == :API2
|
127
|
+
raise "XBee api_mode must be either :API1 (non-escaped) or :API2 (escaped, default)"
|
128
|
+
end
|
92
129
|
raise "Too much data (#{self.length} bytes) to fit into one frame!" if (self.length > 0xFFFF)
|
93
|
-
|
130
|
+
|
131
|
+
if (api_mode == :API1)
|
132
|
+
"~" + [length].pack("n") + data + [Frame.checksum(data)].pack("C")
|
133
|
+
elsif (api_mode == :API2)
|
134
|
+
"~" + [length].pack("n").xb_escape + data.xb_escape + [Frame.checksum(data)].pack("C")
|
135
|
+
end
|
94
136
|
end
|
95
137
|
end
|
96
138
|
|
97
139
|
class ReceivedFrame < Base
|
98
140
|
def initialize(frame_data)
|
99
|
-
|
100
|
-
self.api_identifier = frame_data[0]
|
141
|
+
self.api_identifier = frame_data[0].unpack('H*').join.to_i(16) unless frame_data.nil?
|
101
142
|
if $DEBUG then
|
102
|
-
print "Initializing a ReceivedFrame of type 0x%
|
103
|
-
|
104
|
-
puts "Initializing a ReceivedFrame of type 0x%
|
143
|
+
print "Initializing a ReceivedFrame of type 0x%02x | " % self.api_identifier
|
144
|
+
elsif $VERBOSE
|
145
|
+
puts "Initializing a ReceivedFrame of type 0x%02x" % self.api_identifier
|
105
146
|
end
|
106
|
-
self.cmd_data = frame_data[1..-1]
|
147
|
+
self.cmd_data = frame_data[1..-1] unless frame_data.nil?
|
107
148
|
end
|
108
149
|
end
|
109
150
|
end
|
@@ -120,4 +161,3 @@ require 'remote_command_request'
|
|
120
161
|
require 'remote_command_response'
|
121
162
|
require 'transmit_request'
|
122
163
|
require 'transmit_status'
|
123
|
-
|
@@ -22,7 +22,7 @@ module XBee
|
|
22
22
|
when 0..2
|
23
23
|
modem_statuses.assoc(status_byte)
|
24
24
|
else
|
25
|
-
raise "ModemStatus frame appears to include an invalid status value:
|
25
|
+
raise "ModemStatus frame appears to include an invalid status value: 0x%02x" % status_byte
|
26
26
|
end
|
27
27
|
#actually assign and move along
|
28
28
|
@cmd_data = data_string
|
@@ -12,7 +12,7 @@ module XBee
|
|
12
12
|
when 0..4
|
13
13
|
command_statuses[status_byte]
|
14
14
|
else
|
15
|
-
raise "AT Command Response frame appears to include an invalid status: 0x%
|
15
|
+
raise "AT Command Response frame appears to include an invalid status: 0x%02x" % status_byte
|
16
16
|
end
|
17
17
|
#actually assign and move along
|
18
18
|
@cmd_data = data_string
|
data/lib/apimode/xbee_api.rb
CHANGED
@@ -3,11 +3,42 @@ require 'frame/frame'
|
|
3
3
|
require File.dirname(File.dirname(__FILE__)) + '/ruby_xbee'
|
4
4
|
|
5
5
|
module XBee
|
6
|
+
##
|
7
|
+
# This is the main class for API mode for XBee radios.
|
6
8
|
class BaseAPIModeInterface < RFModule
|
7
|
-
|
8
|
-
VERSION = "1.
|
9
|
-
|
10
|
-
|
9
|
+
|
10
|
+
VERSION = "1.2.0" # Version of this class
|
11
|
+
|
12
|
+
##
|
13
|
+
# ==== Attributes
|
14
|
+
# * +xbee_usbdev_str+ - USB Device as a string
|
15
|
+
#
|
16
|
+
# ==== Options
|
17
|
+
# * +uart_config+ - XBeeUARTConfig
|
18
|
+
# * +operation_mode+ - Either :AT or :API for XBee operation mode
|
19
|
+
# * +transmission_mode+ - :SYNC for Synchronous communication or :ASYNC for Asynchonrous communication.
|
20
|
+
#
|
21
|
+
# A note on the asynchronous vs synchronous communication modes - A
|
22
|
+
# simplistic network of a few XBee nodes can pretty much work according to
|
23
|
+
# expected flows where requests and responses are always handled in
|
24
|
+
# synchronous ways. However, if bigger radio networks are being deployed
|
25
|
+
# (real scenarios) you cannot guarantee the synchronous nature of the network.
|
26
|
+
# You will have nodes joining/removing themselves, sleeping, sending data
|
27
|
+
# samples etc. Although the default behaviour is set as :SYNC, if you have a
|
28
|
+
# real network, then by design the network is Asynchronous, use :ASYNC instead.
|
29
|
+
# Otherwise you will most definitely run into "Invalid responses" issues.
|
30
|
+
#
|
31
|
+
# For handling the Asynchronous communication logic, use an external Queue
|
32
|
+
# and database to effectively handle the command/response and other frames
|
33
|
+
# that are concurrently being conversed on the PAN.
|
34
|
+
#
|
35
|
+
# ==== Example
|
36
|
+
# require 'ruby_xbee'
|
37
|
+
# @uart_config = XBee::Config::XBeeUARTConfig.new()
|
38
|
+
# @xbee_usbdev_str = '/dev/tty.usbserial-A101KYF6'
|
39
|
+
# @xbee = XBee::BaseAPIModeInterface.new(@xbee_usbdev_str, @uart_config, :API, :ASYNC)
|
40
|
+
#
|
41
|
+
def initialize(xbee_usbdev_str, uart_config = XBeeUARTConfig.new, operation_mode = :API, transmission_mode = :SYNC)
|
11
42
|
super(xbee_usbdev_str, uart_config, operation_mode, transmission_mode)
|
12
43
|
@frame_id = 1
|
13
44
|
if self.operation_mode == :AT
|
@@ -23,11 +54,14 @@ module XBee
|
|
23
54
|
# Switch to API mode - note that in Series 2 the Operation Mode is defined
|
24
55
|
# by the firmware flashed to the device. Only Series 1 can switch from
|
25
56
|
# AT (Transparent) to API Opearation and back seamlessly.
|
57
|
+
#
|
58
|
+
# API Mode 1 - API Enabled
|
59
|
+
# API Mode 2 - API Enabled, with escaped control characters
|
26
60
|
def start_apimode_communication
|
27
61
|
in_command_mode do
|
28
62
|
puts "Entering api mode"
|
29
63
|
# Set API Mode 2 (include escaped characters)
|
30
|
-
self.xbee_serialport.write("
|
64
|
+
self.xbee_serialport.write("ATAP1\r")
|
31
65
|
self.xbee_serialport.read(3)
|
32
66
|
end
|
33
67
|
end
|
@@ -35,10 +69,10 @@ module XBee
|
|
35
69
|
def get_param(at_param_name, at_param_unpack_string = nil)
|
36
70
|
frame_id = self.next_frame_id
|
37
71
|
at_command_frame = XBee::Frame::ATCommand.new(at_param_name,frame_id,nil,at_param_unpack_string)
|
38
|
-
puts "Sending ... [#{at_command_frame._dump.unpack("C*").join(", ")}]"
|
39
|
-
self.xbee_serialport.write(at_command_frame._dump)
|
72
|
+
puts "Sending ... [#{at_command_frame._dump(self.api_mode.in_symbol).unpack("C*").join(", ")}]" if $VERBOSE
|
73
|
+
self.xbee_serialport.write(at_command_frame._dump(self.api_mode.in_symbol))
|
40
74
|
if self.transmission_mode == :SYNC
|
41
|
-
r = XBee::Frame.new(self.xbee_serialport)
|
75
|
+
r = XBee::Frame.new(self.xbee_serialport, self.api_mode.in_symbol)
|
42
76
|
if r.kind_of?(XBee::Frame::ATCommandResponse) && r.status == :OK && r.frame_id == frame_id
|
43
77
|
if block_given?
|
44
78
|
yield r
|
@@ -60,10 +94,10 @@ module XBee
|
|
60
94
|
def set_param(at_param_name, param_value, at_param_unpack_string = nil)
|
61
95
|
frame_id = self.next_frame_id
|
62
96
|
at_command_frame = XBee::Frame::ATCommand.new(at_param_name,frame_id,param_value,at_param_unpack_string)
|
63
|
-
# puts "Sending ... [#{at_command_frame._dump.unpack("C*").join(", ")}]"
|
64
|
-
self.xbee_serialport.write(at_command_frame._dump)
|
97
|
+
# puts "Sending ... [#{at_command_frame._dump(self.api_mode.in_symbol).unpack("C*").join(", ")}]"
|
98
|
+
self.xbee_serialport.write(at_command_frame._dump(self.api_mode.in_symbol))
|
65
99
|
if self.transmission_mode == :SYNC
|
66
|
-
r = XBee::Frame.new(self.xbee_serialport)
|
100
|
+
r = XBee::Frame.new(self.xbee_serialport, self.api_mode.in_symbol)
|
67
101
|
if r.kind_of?(XBee::Frame::ATCommandResponse) && r.status == :OK && r.frame_id == frame_id
|
68
102
|
if block_given?
|
69
103
|
yield r
|
@@ -79,10 +113,10 @@ module XBee
|
|
79
113
|
def get_remote_param(at_param_name, remote_address = 0x000000000000ffff, remote_network_address = 0xfffe, at_param_unpack_string = nil)
|
80
114
|
frame_id = self.next_frame_id
|
81
115
|
at_command_frame = XBee::Frame::RemoteCommandRequest.new(at_param_name, remote_address, remote_network_address, frame_id, nil, at_param_unpack_string)
|
82
|
-
puts "Sending ... [#{at_command_frame._dump.unpack("C*").join(", ")}]"
|
83
|
-
self.xbee_serialport.write(at_command_frame._dump)
|
116
|
+
puts "Sending ... [#{at_command_frame._dump(self.api_mode.in_symbol).unpack("C*").join(", ")}]"
|
117
|
+
self.xbee_serialport.write(at_command_frame._dump(self.api_mode.in_symbol))
|
84
118
|
if self.transmission_mode == :SYNC
|
85
|
-
r = XBee::Frame.new(self.xbee_serialport)
|
119
|
+
r = XBee::Frame.new(self.xbee_serialport, self.api_mode.in_symbol)
|
86
120
|
if r.kind_of?(XBee::Frame::RemoteCommandResponse) && r.status == :OK && r.frame_id == frame_id
|
87
121
|
if block_given?
|
88
122
|
yield r
|
@@ -98,10 +132,10 @@ module XBee
|
|
98
132
|
def set_remote_param(at_param_name, param_value, remote_address = 0x000000000000ffff, remote_network_address = 0xfffe, at_param_unpack_string = nil)
|
99
133
|
frame_id = self.next_frame_id
|
100
134
|
at_command_frame = XBee::Frame::RemoteCommandRequest.new(at_param_name, remote_address, remote_network_address, frame_id, param_value, at_param_unpack_string)
|
101
|
-
puts "Sending ... [#{at_command_frame._dump.unpack("C*").join(", ")}]"
|
102
|
-
self.xbee_serialport.write(at_command_frame._dump)
|
135
|
+
puts "Sending ... [#{at_command_frame._dump(self.api_mode.in_symbol).unpack("C*").join(", ")}]"
|
136
|
+
self.xbee_serialport.write(at_command_frame._dump(self.api_mode.in_symbol))
|
103
137
|
if self.transmission_mode == :SYNC
|
104
|
-
r = XBee::Frame.new(self.xbee_serialport)
|
138
|
+
r = XBee::Frame.new(self.xbee_serialport, self.api_mode.in_symbol)
|
105
139
|
if r.kind_of?(XBee::Frame::RemoteCommandResponse) && r.status == :OK && r.frame_id == frame_id
|
106
140
|
if block_given?
|
107
141
|
yield r
|
@@ -154,15 +188,15 @@ module XBee
|
|
154
188
|
frame_id = self.next_frame_id
|
155
189
|
# neighbors often takes more than 1000ms to return data
|
156
190
|
node_discover_cmd = XBee::Frame::ATCommand.new("ND",frame_id,nil)
|
157
|
-
#puts "Node discover command dump: #{node_discover_cmd._dump.unpack("C*").join(", ")}"
|
191
|
+
#puts "Node discover command dump: #{node_discover_cmd._dump(self.api_mode.in_symbol).unpack("C*").join(", ")}"
|
158
192
|
tmp = @xbee_serialport.read_timeout
|
159
193
|
@xbee_serialport.read_timeout = Integer(self.node_discover_timeout.in_seconds * 1050)
|
160
|
-
@xbee_serialport.write(node_discover_cmd._dump)
|
194
|
+
@xbee_serialport.write(node_discover_cmd._dump(self.api_mode.in_symbol))
|
161
195
|
responses = []
|
162
196
|
#read_thread = Thread.new do
|
163
197
|
begin
|
164
198
|
loop do
|
165
|
-
r = XBee::Frame.new(self.xbee_serialport)
|
199
|
+
r = XBee::Frame.new(self.xbee_serialport, self.api_mode.in_symbol)
|
166
200
|
# puts "Got a response! Frame ID: #{r.frame_id}, Command: #{r.at_command}, Status: #{r.status}, Value: #{r.retrieved_value}"
|
167
201
|
if r.kind_of?(XBee::Frame::ATCommandResponse) && r.status == :OK && r.frame_id == frame_id
|
168
202
|
if r.retrieved_value.empty?
|
@@ -529,7 +563,7 @@ module XBee
|
|
529
563
|
def reset!
|
530
564
|
@xbee_serialport.write("ATFR\r")
|
531
565
|
end
|
532
|
-
|
566
|
+
|
533
567
|
##
|
534
568
|
# Performs a network reset on one or more modules within a PAN. The module responds
|
535
569
|
# immediately with an "OK" and then restarts the network. All network configuration
|
@@ -586,7 +620,7 @@ module XBee
|
|
586
620
|
# echo is disabled by default
|
587
621
|
def getresponse( echo = false )
|
588
622
|
if echo == true
|
589
|
-
r = XBee::Frame.new(self.xbee_serialport)
|
623
|
+
r = XBee::Frame.new(self.xbee_serialport, self.api_mode.in_symbol)
|
590
624
|
else
|
591
625
|
getresults( @xbee_serialport, echo )
|
592
626
|
end
|
@@ -603,6 +637,12 @@ module XBee
|
|
603
637
|
end # class Series1APIModeInterface
|
604
638
|
|
605
639
|
class Series2APIModeInterface < BaseAPIModeInterface
|
606
|
-
|
640
|
+
##
|
641
|
+
# Initiating the application firmware OTA upgrade for Programmable XBee modules
|
642
|
+
def init_ota_upgrade(password = nil, remote_address = 0x000000000000ffff, remote_network_address = 0xfffe)
|
643
|
+
frame_id = self.next_frame_id
|
644
|
+
command_frame = XBee::Frame::ExplicitAddressingCommand.new(frame_id, remote_address, remote_network_address, 0xE8, 0xE8, 0x1000, 0xC105, 0x00, 0x00, password)
|
645
|
+
self.xbee_serialport.write(command_frame._dump(self.api_mode.in_symbol))
|
646
|
+
end
|
607
647
|
end # class Series2APIModeInterface
|
608
648
|
end
|