ruby-xbee 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|