ruby-xbee 1.1.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 +15 -0
- data/LICENSE +11 -0
- data/README.rdoc +232 -0
- data/Rakefile +38 -0
- data/agpl.txt +661 -0
- data/bin/apicontrol.rb +37 -0
- data/bin/apilisten.rb +14 -0
- data/bin/ruby-xbee.rb +27 -0
- data/bin/xbeeconfigure.rb +263 -0
- data/bin/xbeedio.rb +89 -0
- data/bin/xbeeinfo.rb +110 -0
- data/bin/xbeelisten.rb +114 -0
- data/bin/xbeesend.rb +81 -0
- data/lib/apimode/at_commands.rb +28 -0
- data/lib/apimode/frame/at_command.rb +34 -0
- data/lib/apimode/frame/at_command_response.rb +33 -0
- data/lib/apimode/frame/explicit_addressing_command.rb +7 -0
- data/lib/apimode/frame/explicit_rx_indicator.rb +6 -0
- data/lib/apimode/frame/frame.rb +123 -0
- data/lib/apimode/frame/modem_status.rb +32 -0
- data/lib/apimode/frame/receive_packet.rb +6 -0
- data/lib/apimode/frame/remote_command_request.rb +34 -0
- data/lib/apimode/frame/remote_command_response.rb +22 -0
- data/lib/apimode/frame/transmit_request.rb +7 -0
- data/lib/apimode/frame/transmit_status.rb +6 -0
- data/lib/apimode/xbee_api.rb +608 -0
- data/lib/legacy/command_mode.rb +553 -0
- data/lib/module_config.rb +77 -0
- data/lib/ruby_xbee.rb +134 -0
- data/test/ruby_xbee_test.rb +20 -0
- data/test/test_helper.rb +22 -0
- metadata +110 -0
data/bin/xbeesend.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# == Synposis
|
3
|
+
# xbeesend.rb - A Ruby utility for sending raw data to and through an XBee
|
4
|
+
#
|
5
|
+
# :title: xbeesend.rb - A Ruby utility for sending raw data to and through an XBee
|
6
|
+
#
|
7
|
+
# == Copyright
|
8
|
+
# Copyright (C) 2008-2009 360VL, Inc. and Landon Cox
|
9
|
+
#
|
10
|
+
# == License
|
11
|
+
# This program is free software: you can redistribute it and/or modify
|
12
|
+
# it under the terms of the GNU Affero General Public License version 3 as
|
13
|
+
# published by the Free Software Foundation.
|
14
|
+
#
|
15
|
+
# This program is distributed in the hope that it will be useful,
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
+
# GNU Affero General Public License version 3 for more details.
|
19
|
+
#
|
20
|
+
# You should have received a copy of the GNU Affero General Public License along with this program.
|
21
|
+
# If not, see http://www.gnu.org/licenses/
|
22
|
+
#
|
23
|
+
# You can learn more about ruby for XBee and other projects at http://sawdust.see-do.org
|
24
|
+
#
|
25
|
+
# == Usage
|
26
|
+
# xbeesend.rb [message]
|
27
|
+
#
|
28
|
+
# example:
|
29
|
+
# ./xbeesend.rb 'this is some data to send' 'some more data' 'last bits'
|
30
|
+
#
|
31
|
+
# each of the 3 parameters above is sent in succession. You can put as many messages on the command
|
32
|
+
# line as will be accomodated by the shell.
|
33
|
+
#
|
34
|
+
# example
|
35
|
+
# ./xbeesend.rb
|
36
|
+
#
|
37
|
+
# this form of the command will wait for you to input data from the keyboard...it reads stdin input
|
38
|
+
# and every line you type will be sent to/through the XBee when you hit enter
|
39
|
+
#
|
40
|
+
# this utility can be used also for just setting up an XBee with raw AT commands.
|
41
|
+
# it doesn't interpret anything in or out of the XBee. You can put the XBee into attention by:
|
42
|
+
# ./xbeesend.rb '+++'
|
43
|
+
# if you monitor it with the xbeelisten.rb utility, you'd see an 'OK' in response
|
44
|
+
#
|
45
|
+
# See conf/xbeeconfig.rb for configuration defaults
|
46
|
+
#
|
47
|
+
# this code is for the following XBee modules:
|
48
|
+
# IEEE® 802.15.4 OEM RF Modules by Digi International
|
49
|
+
#
|
50
|
+
# == See Also
|
51
|
+
# xbeeinfo.rb, xbeeconfigure.rb, xbeedio.rb, xbeelisten.rb, xbeesend.rb
|
52
|
+
#
|
53
|
+
# == Learn more
|
54
|
+
# You can learn more about Ruby::XBee and other projects at http://sawdust.see-do.org
|
55
|
+
#
|
56
|
+
# see Digi product manual: "Product Manual v1.xCx - 802.15.4 Protocol"
|
57
|
+
# for details on the operation of XBee series 1 modules.
|
58
|
+
#
|
59
|
+
|
60
|
+
$: << File.dirname(__FILE__)
|
61
|
+
|
62
|
+
require 'date'
|
63
|
+
require 'getoptlong'
|
64
|
+
|
65
|
+
require 'ruby-xbee'
|
66
|
+
|
67
|
+
# start a connection to the XBee
|
68
|
+
@xbee = XBee.new( @xbee_usbdev_str, @xbee_baud, @data_bits, @stop_bits, @parity )
|
69
|
+
|
70
|
+
if ( ARGV.size > 0 )
|
71
|
+
ARGV.each do | message |
|
72
|
+
puts "Sending: #{message}"
|
73
|
+
@xbee.send! message
|
74
|
+
end
|
75
|
+
else # take input from STDIN to make it interactive
|
76
|
+
while true
|
77
|
+
message = gets
|
78
|
+
@xbee.send! message
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module XBee
|
2
|
+
module ATCommands
|
3
|
+
class ParameterDescriptor
|
4
|
+
def initialize
|
5
|
+
yield self if block_given?
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class CommandDescriptor
|
10
|
+
attr_reader :command, :command_name, :command_description, :parameter
|
11
|
+
|
12
|
+
def initialize (command, command_name, command_description = nil, parameter = nil)
|
13
|
+
@command = command
|
14
|
+
@command_name = command_name
|
15
|
+
@command_description = command_description
|
16
|
+
@parameter = parameter
|
17
|
+
yield self if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_parameter?
|
21
|
+
parameter.nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
AP_PARAM_DESCRIPTOR = ParameterDescriptor.new
|
26
|
+
AP = CommandDescriptor.new("AP","API Mode","0 = off; 1 = on, unescaped; 2 = on, escaped", AP_PARAM_DESCRIPTOR)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module XBee
|
2
|
+
module Frame
|
3
|
+
class ATCommand < Base
|
4
|
+
def api_identifier ; 0x08 ; end
|
5
|
+
|
6
|
+
attr_accessor :at_command, :parameter_value, :parameter_pack_string
|
7
|
+
|
8
|
+
def initialize(at_command, frame_id = nil, parameter_value = nil, parameter_pack_string = "a*")
|
9
|
+
self.frame_id = frame_id
|
10
|
+
self.at_command = at_command # TODO: Check for valid AT command codes here
|
11
|
+
self.parameter_value = parameter_value
|
12
|
+
self.parameter_pack_string = parameter_pack_string
|
13
|
+
yield self if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
def cmd_data=(data_string)
|
17
|
+
self.frame_id, self.at_command, self.parameter_value = data_string.unpack("ca2#{parameter_pack_string}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def cmd_data
|
21
|
+
if parameter_value.nil?
|
22
|
+
[frame_id, at_command].pack("ca2")
|
23
|
+
else
|
24
|
+
[frame_id, at_command, parameter_value].pack("ca2#{parameter_pack_string}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class ATCommandQueueParameterValue < ATCommand
|
30
|
+
def api_identifier ; 0x09 ; end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module XBee
|
2
|
+
module Frame
|
3
|
+
class ATCommandResponse < ReceivedFrame
|
4
|
+
attr_accessor :frame_id, :at_command, :status, :retrieved_value
|
5
|
+
|
6
|
+
def initialize(data = nil)
|
7
|
+
super(data) && (yield self if block_given?)
|
8
|
+
end
|
9
|
+
|
10
|
+
def command_statuses
|
11
|
+
[:OK, :ERROR, :Invalid_Command, :Invalid_Parameter]
|
12
|
+
end
|
13
|
+
|
14
|
+
def cmd_data=(data_string)
|
15
|
+
self.frame_id, self.at_command, status_byte, self.retrieved_value = data_string.unpack("Ca2Ca*")
|
16
|
+
self.status = case status_byte
|
17
|
+
when 0..3
|
18
|
+
command_statuses[status_byte]
|
19
|
+
else
|
20
|
+
raise "AT Command Response frame appears to include an invalid status: 0x%x" % status_byte
|
21
|
+
end
|
22
|
+
#actually assign and move along
|
23
|
+
@cmd_data = data_string
|
24
|
+
#### DEBUG ####
|
25
|
+
if $DEBUG then
|
26
|
+
print "Retrieved Value: #{self.retrieved_value.unpack('C*').join(', ')} | "
|
27
|
+
print "Retrieved Value: #{self.retrieved_value.unpack('a*')} | "
|
28
|
+
end
|
29
|
+
#### DEBUG ####
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
$: << File.dirname(__FILE__)
|
2
|
+
|
3
|
+
class String
|
4
|
+
def xb_escape
|
5
|
+
self.gsub(/[\176\175\021\023]/) { |c| [0x7D, c[0].ord ^ 0x20].pack("CC")}
|
6
|
+
end
|
7
|
+
def xb_unescape
|
8
|
+
self.gsub(/\175./) { |ec| [ec.unpack("CC").last ^ 0x20].pack("C")}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module XBee
|
13
|
+
module Frame
|
14
|
+
def Frame.checksum(data)
|
15
|
+
0xFF - (data.unpack("C*").inject(0) { |sum, byte| (sum + byte) & 0xFF })
|
16
|
+
end
|
17
|
+
|
18
|
+
def Frame.new(source_io)
|
19
|
+
stray_bytes = []
|
20
|
+
until (start_delimiter = source_io.readchar.unpack('H*').join.to_i(16)) == 0x7e
|
21
|
+
#puts "Stray byte 0x%x" % start_delimiter
|
22
|
+
print "DEBUG: #{start_delimiter} | " if $DEBUG
|
23
|
+
stray_bytes << start_delimiter
|
24
|
+
end
|
25
|
+
puts "Got some stray bytes for ya: #{stray_bytes.map {|b| "0x%x" % b} .join(", ")}" unless stray_bytes.empty?
|
26
|
+
header = source_io.read(3).xb_unescape
|
27
|
+
print "Reading ... header after start byte: #{header.unpack("C*").join(", ")} | " if $DEBUG
|
28
|
+
frame_remaining = frame_length = api_identifier = cmd_data = ""
|
29
|
+
if header.length == 3
|
30
|
+
frame_length, api_identifier = header.unpack("nC")
|
31
|
+
else
|
32
|
+
frame_length, api_identifier = header.unpack("n").first, source_io.readchar
|
33
|
+
end
|
34
|
+
#### DEBUG ####
|
35
|
+
if $DEBUG then
|
36
|
+
print "Frame length: #{frame_length} | "
|
37
|
+
print "Api Identifier: #{api_identifier} | "
|
38
|
+
end
|
39
|
+
#### DEBUG ####
|
40
|
+
cmd_data_intended_length = frame_length - 1
|
41
|
+
while ((unescaped_length = cmd_data.xb_unescape.length) < cmd_data_intended_length)
|
42
|
+
cmd_data += source_io.read(cmd_data_intended_length - unescaped_length)
|
43
|
+
end
|
44
|
+
data = api_identifier.chr + cmd_data.xb_unescape
|
45
|
+
sent_checksum = source_io.getc.unpack('H*').join.to_i(16)
|
46
|
+
#### DEBUG ####
|
47
|
+
if $DEBUG then
|
48
|
+
print "Sent checksum: #{sent_checksum} | "
|
49
|
+
print "Received checksum: #{Frame.checksum(data)} | "
|
50
|
+
print "Payload: #{cmd_data.unpack("C*").join(", ")} | "
|
51
|
+
end
|
52
|
+
#### DEBUG ####
|
53
|
+
unless sent_checksum == Frame.checksum(data)
|
54
|
+
raise "Bad checksum - data discarded"
|
55
|
+
end
|
56
|
+
case data[0].unpack('H*')[0].to_i(16)
|
57
|
+
when 0x8A
|
58
|
+
ModemStatus.new(data)
|
59
|
+
when 0x88
|
60
|
+
ATCommandResponse.new(data)
|
61
|
+
when 0x97
|
62
|
+
RemoteCommandResponse.new(data)
|
63
|
+
when 0x8B
|
64
|
+
TransmitStatus.new(data)
|
65
|
+
when 0x90
|
66
|
+
ReceivePacket.new(data)
|
67
|
+
when 0x91
|
68
|
+
ExplicitRxIndicator.new(data)
|
69
|
+
when 0x92
|
70
|
+
IODataSampleRxIndicator.new(data)
|
71
|
+
else
|
72
|
+
ReceivedFrame.new(data)
|
73
|
+
end
|
74
|
+
rescue EOFError
|
75
|
+
# No operation as we expect eventually something
|
76
|
+
end
|
77
|
+
|
78
|
+
class Base
|
79
|
+
attr_accessor :api_identifier, :cmd_data, :frame_id
|
80
|
+
|
81
|
+
def api_identifier ; @api_identifier ||= 0x00 ; end
|
82
|
+
|
83
|
+
def cmd_data ; @cmd_data ||= "" ; end
|
84
|
+
|
85
|
+
def length ; data.length ; end
|
86
|
+
|
87
|
+
def data
|
88
|
+
Array(api_identifier).pack("C") + cmd_data
|
89
|
+
end
|
90
|
+
|
91
|
+
def _dump
|
92
|
+
raise "Too much data (#{self.length} bytes) to fit into one frame!" if (self.length > 0xFFFF)
|
93
|
+
"~" + [length].pack("n").xb_escape + data.xb_escape + [Frame.checksum(data)].pack("C")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class ReceivedFrame < Base
|
98
|
+
def initialize(frame_data)
|
99
|
+
# raise "Frame data must be an enumerable type" unless frame_data.kind_of?(Enumerable)
|
100
|
+
self.api_identifier = frame_data[0]
|
101
|
+
if $DEBUG then
|
102
|
+
print "Initializing a ReceivedFrame of type 0x%x | " % self.api_identifier.unpack('H*').join.to_i(16)
|
103
|
+
else
|
104
|
+
puts "Initializing a ReceivedFrame of type 0x%x" % self.api_identifier.unpack('H*').join.to_i(16)
|
105
|
+
end
|
106
|
+
self.cmd_data = frame_data[1..-1]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
require 'at_command'
|
113
|
+
require 'at_command_response'
|
114
|
+
require 'explicit_addressing_command'
|
115
|
+
require 'explicit_rx_indicator'
|
116
|
+
require 'io_data_sample_rx_indicator'
|
117
|
+
require 'modem_status'
|
118
|
+
require 'receive_packet'
|
119
|
+
require 'remote_command_request'
|
120
|
+
require 'remote_command_response'
|
121
|
+
require 'transmit_request'
|
122
|
+
require 'transmit_status'
|
123
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module XBee
|
2
|
+
module Frame
|
3
|
+
class ModemStatus < ReceivedFrame
|
4
|
+
attr_accessor :status
|
5
|
+
|
6
|
+
def initialize(data = nil)
|
7
|
+
super(data) && (yield self if block_given?)
|
8
|
+
end
|
9
|
+
|
10
|
+
def modem_statuses
|
11
|
+
[
|
12
|
+
[0, :Hardware_Reset],
|
13
|
+
[1, :Watchdog_Timer_Reset],
|
14
|
+
[2, :Associated],
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
def cmd_data=(data_string)
|
19
|
+
status_byte = data_string.unpack("c")[0]
|
20
|
+
# update status ivar for later use
|
21
|
+
self.status = case status_byte
|
22
|
+
when 0..2
|
23
|
+
modem_statuses.assoc(status_byte)
|
24
|
+
else
|
25
|
+
raise "ModemStatus frame appears to include an invalid status value: #{data_string}"
|
26
|
+
end
|
27
|
+
#actually assign and move along
|
28
|
+
@cmd_data = data_string
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'at_command'
|
2
|
+
|
3
|
+
module XBee
|
4
|
+
module Frame
|
5
|
+
class RemoteCommandRequest < ATCommand
|
6
|
+
def api_identifier ; 0x17 ; end
|
7
|
+
|
8
|
+
attr_accessor :destination_address, :destination_network
|
9
|
+
|
10
|
+
def initialize(at_command, destination_address = 0x000000000000ffff, destination_network = 0x0000fffe, frame_id = nil, parameter_value = nil, parameter_pack_string = "a*")
|
11
|
+
self.destination_address = destination_address
|
12
|
+
self.destination_network = destination_network
|
13
|
+
super(at_command, frame_id, parameter_value, parameter_pack_string)
|
14
|
+
yield self if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def cmd_data=(data_string)
|
18
|
+
dest_high = dest_low = 0
|
19
|
+
self.frame_id, dest_high, dest_low, self.destination_network, self.at_command, self.parameter_value = data_string.unpack("CNNnxa2#{parameter_pack_string}")
|
20
|
+
self.destination_address = dest_high << 32 | dest_low
|
21
|
+
end
|
22
|
+
|
23
|
+
def cmd_data
|
24
|
+
dest_high = (self.destination_address >> 32) & 0xFFFFFFFF
|
25
|
+
dest_low = self.destination_address & 0xFFFFFFFF
|
26
|
+
if parameter_value.nil?
|
27
|
+
[self.frame_id, dest_high, dest_low, self.destination_network, 0x00, self.at_command].pack("CNNnCa2")
|
28
|
+
else
|
29
|
+
[self.frame_id, dest_high, dest_low, self.destination_network, 0x02, self.at_command, self.parameter_value].pack("CNNnCa2#{parameter_pack_string}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'at_command_response'
|
2
|
+
|
3
|
+
module XBee
|
4
|
+
module Frame
|
5
|
+
class RemoteCommandResponse < ATCommandResponse
|
6
|
+
attr_accessor :destination_address, :destination_network
|
7
|
+
def cmd_data=(data_string)
|
8
|
+
dest_high = dest_low = 0
|
9
|
+
self.frame_id, dest_high, dest_low, self.destination_network, self.at_command, status_byte, self.retrieved_value = data_string.unpack("CNNna2Ca*")
|
10
|
+
self.destination_address = dest_high << 32 | dest_low
|
11
|
+
self.status = case status_byte
|
12
|
+
when 0..4
|
13
|
+
command_statuses[status_byte]
|
14
|
+
else
|
15
|
+
raise "AT Command Response frame appears to include an invalid status: 0x%x" % status_byte
|
16
|
+
end
|
17
|
+
#actually assign and move along
|
18
|
+
@cmd_data = data_string
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|