ruby-xbee 1.1.0

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