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