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.
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,7 @@
1
+ module XBee
2
+ module Frame
3
+ class ExplicitAddressingCommand < Base
4
+ def api_identifier ; 0x11 ; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module XBee
2
+ module Frame
3
+ class ExplicitRxIndicator < ReceivedFrame
4
+ end
5
+ end
6
+ 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,6 @@
1
+ module XBee
2
+ module Frame
3
+ class ReceivePacket < ReceivedFrame
4
+ end
5
+ end
6
+ 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
@@ -0,0 +1,7 @@
1
+ module XBee
2
+ module Frame
3
+ class TransmitRequest < Base
4
+ def api_identifier ; 0x10 ; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module XBee
2
+ module Frame
3
+ class TransmitStatus < ReceivedFrame
4
+ end
5
+ end
6
+ end