levinalex-serial_interface 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ == 0.3.0 / 2008-10-12
2
+ * initial release
3
+
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/protocol/rca2006.rb
6
+ lib/serial_interface.rb
7
+ lib/serial_packet.rb
8
+ serial_interface.gemspec
9
+ test/test_serial_interface.rb
10
+ test/test_serial_io.rb
11
+ test/test_serial_packets.rb
data/README.txt ADDED
@@ -0,0 +1,54 @@
1
+ serial_interface
2
+ by Levin Alexander
3
+ http://levinalex.net/
4
+
5
+ == DESCRIPTION:
6
+
7
+ serial_interface intends to be a small library that makes it easy
8
+ to define packet based protocols over a serial link (RS232) in a
9
+ declarative fashion.
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ * remove dependency on traits
14
+ * fix all the warnings
15
+ * add some tests that test the actual functionality
16
+ * improve the interface
17
+ * improve the architecture
18
+
19
+ == SYNOPSIS:
20
+
21
+ nothing written yet
22
+
23
+ == REQUIREMENTS:
24
+
25
+ * traits 0.8.1
26
+
27
+ == INSTALL:
28
+
29
+ * not yet written
30
+
31
+ == LICENSE:
32
+
33
+ (The MIT License)
34
+
35
+ Copyright (c) 2006-2008 Levin Alexander
36
+
37
+ Permission is hereby granted, free of charge, to any person obtaining
38
+ a copy of this software and associated documentation files (the
39
+ 'Software'), to deal in the Software without restriction, including
40
+ without limitation the rights to use, copy, modify, merge, publish,
41
+ distribute, sublicense, and/or sell copies of the Software, and to
42
+ permit persons to whom the Software is furnished to do so, subject to
43
+ the following conditions:
44
+
45
+ The above copyright notice and this permission notice shall be
46
+ included in all copies or substantial portions of the Software.
47
+
48
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
49
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
50
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
51
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
52
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
53
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
54
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require 'spec/rake/spectask'
4
+
5
+ require './lib/serial_interface.rb'
6
+
7
+ Hoe.new('serial_interface', SerialInterface::VERSION) do |p|
8
+ p.rubyforge_name = 'serial_interface'
9
+ p.summary = "abstracts protocols on a serial link"
10
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
11
+ p.url = "http://levinalex.net/src/serial_interface"
12
+ p.developer('Levin Alexander', 'mail@levinalex.net')
13
+
14
+ p.extra_deps = ["traits"]
15
+ end
16
+
17
+ task :cultivate do
18
+ system "touch Manifest.txt; rake check_manifest | grep -v \"(in \" | patch"
19
+ system "rake debug_gem | grep -v \"(in \" > `basename \\`pwd\\``.gemspec"
20
+ end
@@ -0,0 +1,197 @@
1
+ module SerialProtocol
2
+
3
+ # Serial protocol Roboterclub Aachen 2006
4
+ # http://www.roboterclub.rwth-aachen.de/
5
+ #
6
+ # Packet format:
7
+ # [0x65, 0xEB, <type:8>, <counter:8>, <length:8>, data:length>, <crc:16>]
8
+ #
9
+ # <type> is one of the following:
10
+ # 0b00000000 --> Data packet, discard on checksum mismatch
11
+ # 0b00011111 --> Data packet, resend on checksum mismatch
12
+ # 0b11100011 --> ACK packet
13
+ # 0b11111100 --> NACK packet
14
+ #
15
+ class RCA2006
16
+
17
+ STARTBYTES = "\x65\xeb"
18
+ TYPE = {
19
+ :data_no_crc => 0,
20
+ :data => 0b00011111,
21
+ :ack => 0b11100011,
22
+ :nack => 0b11111100 }
23
+
24
+ def initialize(send_callback, receive_callback, options = {})
25
+ @rec_queue = Queue.new
26
+ @state = :first_startbyte
27
+ @send_callback = send_callback
28
+ @receive_callback = receive_callback
29
+ end
30
+
31
+ # Set callbacks whenever a packet is sent or received.
32
+ #
33
+ def on_raw_receive(&block)
34
+ @raw_receive_callback = block
35
+ end
36
+ def on_raw_send(&block)
37
+ @raw_send_callback = block
38
+ end
39
+
40
+ # Wrap a string into a packet
41
+ #
42
+ # the options-hash can be used to override the default packet format
43
+ #
44
+ def send_packet(data, options = {})
45
+ str = data.to_s
46
+ type = TYPE[ options[:type] || :data_no_crc].chr
47
+ counter = options[:counter] || 0
48
+ checksum = options[:checksum] || ("" << counter << str.length << str).crc_xmodem
49
+
50
+ @raw_send_callback.call(type, counter, data, checksum) if @raw_send_callback
51
+
52
+ p = "" << STARTBYTES << type << counter << str.length << str << [checksum].pack("S").reverse
53
+
54
+ # send the packet, using the callback
55
+ #
56
+ @send_callback.call(p)
57
+ end
58
+
59
+ def receive_handler(type, counter, data, checksum)
60
+ @raw_receive_callback.call(type,counter,data,checksum) if @raw_receive_callback
61
+
62
+ case type
63
+ when :ack
64
+ when :nack
65
+ when :data
66
+ @receive_callback.call(data)
67
+ when :data_no_crc
68
+ @receive_callback.call(data)
69
+ end
70
+ end
71
+
72
+ # Big and ugly state machine that does most of the work
73
+ #
74
+ def add_char_to_packet(char)
75
+ @state = :first_checksum if (@state == 0)
76
+ case @state
77
+ when :first_startbyte
78
+ @data = ""
79
+ @state = ((char == STARTBYTES[0]) ? :second_startbyte : :first_startbyte)
80
+ when :second_startbyte
81
+ @state = (char == STARTBYTES[1]) ? :type :
82
+ # special case: first startbyte is repeated
83
+ (char == STARTBYTES[0] ? :second_startbyte : :first_startbyte)
84
+ when :type
85
+ @type = TYPE.invert[char]
86
+ @state = :counter
87
+ when :counter
88
+ @counter = char
89
+ @state = :length
90
+ when :length
91
+ @length = char
92
+ @state = @length
93
+ when Integer
94
+ @data << char
95
+ @state -= 1
96
+ when :first_checksum
97
+ @checksum = (char << 8)
98
+ @state = :second_checksum
99
+ when :second_checksum
100
+ @checksum = @checksum + char
101
+ @state = :first_startbyte
102
+
103
+ crc = ("" << @counter << @length << @data).crc_xmodem
104
+ # received a valid packet
105
+
106
+ if @type == :data || @type == :data_no_crc
107
+ if @checksum == crc
108
+
109
+ # send ACK
110
+ send_packet(nil, :type => :ack, :counter => @counter)
111
+ receive_handler(@type, @counter, @data,@checksum)
112
+ else
113
+ # send NACK and discard packet
114
+ send_packet(nil, :type => :nack, :counter => @counter)
115
+ raise ChecksumMismatch, "ChecksumMismatch, expected #{crc}, was #{@checksum}"
116
+ end
117
+ else
118
+ # the packet is ACK, NACK or unknown, call receive-handler
119
+ # data may be mangled since the checksum is not checked
120
+ #
121
+ receive_handler(@type, @counter, @data, @checksum)
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ class RCA2006Simple < RCA2006
128
+ def initialize(send_callback, receive_callback, options = {})
129
+ @type = :data_no_crc
130
+ @counter = 0
131
+ super
132
+ end
133
+
134
+ # Wrap a string into a packet
135
+ #
136
+ # the options-hash can be used to override the default packet format
137
+ #
138
+ def send_packet(data, options = {})
139
+ str = data.to_s
140
+ checksum = options[:checksum] || ("" << str.length << str).crc_xmodem
141
+
142
+ @raw_send_callback.call(:data_no_crc, 0, data, checksum) if @raw_send_callback
143
+
144
+ p = "" << STARTBYTES << str.length << str << [checksum].pack("S").reverse
145
+
146
+ # send the packet, using the callback
147
+ #
148
+ @send_callback.call(p)
149
+ end
150
+
151
+ # Big and ugly state machine that does most of the work
152
+ #
153
+ def add_char_to_packet(char)
154
+ @state = :first_checksum if (@state == 0)
155
+ case @state
156
+ when :first_startbyte
157
+ @data = ""
158
+ @state = ((char == STARTBYTES[0]) ? :second_startbyte : :first_startbyte)
159
+ when :second_startbyte
160
+ @state = (char == STARTBYTES[1]) ? :length :
161
+ # special case: first startbyte is repeated
162
+ (char == STARTBYTES[0] ? :second_startbyte : :first_startbyte)
163
+ when :length
164
+ @length = char
165
+ @state = @length
166
+ when Integer
167
+ @data << char
168
+ @state -= 1
169
+ when :first_checksum
170
+ @checksum = (char << 8)
171
+ @state = :second_checksum
172
+ when :second_checksum
173
+ @checksum = @checksum + char
174
+ @state = :first_startbyte
175
+
176
+ crc = ("" << @length << @data).crc_xmodem
177
+ # received a valid packet
178
+
179
+ if @type == :data || @type == :data_no_crc
180
+ if @checksum == crc
181
+
182
+ receive_handler(@type, @counter, @data,@checksum)
183
+ else
184
+ # send NACK and discard packet
185
+ raise ChecksumMismatch, "ChecksumMismatch, expected #{crc}, was #{@checksum}"
186
+ end
187
+ else
188
+ # the packet is ACK, NACK or unknown, call receive-handler
189
+ # data may be mangled since the checksum is not checked
190
+ #
191
+ receive_handler(@type, @counter, @data, @checksum)
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+
@@ -0,0 +1,224 @@
1
+ #!usr/bin/ruby -w
2
+
3
+ require 'thread'
4
+ require 'enumerator'
5
+ require 'timeout'
6
+
7
+ require File.join(File.dirname(__FILE__),'serial_packet.rb')
8
+
9
+ # FIXME: this should probably be put in a separate file
10
+ #
11
+ class String
12
+ def crc_xmodem
13
+ self.to_enum(:each_byte).inject(0) { |crc,byte|
14
+ crc = (crc ^ (byte << 8)) % 0x10000
15
+ 8.times {
16
+ crc <<= 1
17
+ crc ^= 0x1021 if crc[16] == 1
18
+ }
19
+ crc
20
+ } % 0x10000
21
+ end
22
+ end
23
+
24
+ module SerialInterface
25
+ VERSION = '0.2.3'
26
+ end
27
+
28
+ # PacketIO is used to wrap data in packets and send them
29
+ # over a serial port or some other IO
30
+ #
31
+ class PacketIO
32
+ attr_accessor :protocol_handler
33
+
34
+ # Takes two IO-Objects (uses "readchar" and "<<") to read and write from
35
+ #
36
+ def initialize(protocol, read, write = read, options = {})
37
+ @read, @write = read, write
38
+ @on_receive = nil
39
+
40
+ # Hashes contain SerialPackets that can be sent and received
41
+ #
42
+ @sendable_packets = {}
43
+ @receivable_packets = []
44
+
45
+ @waiting_threads = []
46
+
47
+ @protocol_handler = protocol.new(method(:send_callback),method(:receive_callback), options)
48
+
49
+ # Create the receiver thread, but do not start it yet
50
+ #
51
+ @receiver_thread = Thread.new do
52
+ Thread.abort_on_exception = true
53
+ Thread.stop
54
+
55
+ loop do
56
+ begin
57
+ char = @read.readchar
58
+ @protocol_handler.add_char_to_packet(char) if char
59
+ rescue EOFError
60
+ Thread.pass # there is currently nothing to read
61
+ end
62
+ end if @read # no need to loop, if there is nothing to read from
63
+ end
64
+ end
65
+
66
+ # suspends the current thread, until +num+ packets have been received
67
+ # the thread will be resumed after all callbacks were called
68
+ #
69
+ def wait_for_packet(num_packets = 1, timeout = 10)
70
+ begin
71
+ @waiting_threads << {:num => num_packets, :thread => Thread.current}
72
+ sleep timeout
73
+ raise Timeout::Error, "Timeout"
74
+ rescue SerialProtocol::PacketReceived => e
75
+ ensure
76
+ # delete all occurrences of the current thread from the list of waiting threads,
77
+ # as we are obviously not waiting anymore
78
+ @waiting_threads.delete_if { |h| h[:thread] == Thread.current }
79
+ end
80
+ end
81
+
82
+ # The block given to this method is called with every received string
83
+ #
84
+ def on_receive(&block)
85
+ @on_receive = block
86
+ end
87
+
88
+ def add_sender(hash = {})
89
+ hash.each { |k,v|
90
+ @sendable_packets[k] = v
91
+ }
92
+ self
93
+ end
94
+
95
+ # Add a type of packet, that should be checked for in the interface
96
+ #
97
+ # If a packet is received
98
+ #
99
+ def add_receiver(hash = {}, &block)
100
+ hash.each { |k,v|
101
+ @receivable_packets << {:packet => v, :block => block}
102
+ }
103
+ self
104
+ end
105
+
106
+ # Data to be wrapped in a packet
107
+ #
108
+ # there are different ways of using this method:
109
+ #
110
+ # send_packet(sym, *data)
111
+ # send_packet(sym, options = {}, *data)
112
+ # looks for a packet-class named sym and creates a new instance of this type
113
+ # of packet
114
+ # the optional hash is passed to the protocol layer
115
+ #
116
+ # send_packet(string, options = {})
117
+ # sends a raw string
118
+ # the optional hash is passed to the protocol layer
119
+ #
120
+ #
121
+ def send_packet(data, *args)
122
+ options = (Hash === args.first) ? options = args.shift : {}
123
+ data = (Symbol === data) ? @sendable_packets[data].new(*args) : data
124
+
125
+ @protocol_handler.send_packet(data.to_str, options)
126
+ self
127
+ end
128
+
129
+ # starts the receiver thread
130
+ #
131
+ def run
132
+ @receiver_thread.wakeup
133
+ self
134
+ end
135
+
136
+ def join
137
+ @receiver_thread.join
138
+ self
139
+ end
140
+
141
+ private
142
+
143
+ # this method is called, when a packet should be sent
144
+ #
145
+ def send_callback(str)
146
+ @write << str if @write
147
+ end
148
+
149
+ def receive_callback(packet_str)
150
+ # call the on_receive event handler for every packet
151
+ @on_receive.call(packet_str) if @on_receive
152
+
153
+ # try to match the packet-string against the list of known packets
154
+ @receivable_packets.each { |h|
155
+ if h[:packet].matches?(packet_str)
156
+ h[:block].call( h[:packet].from_str(packet_str) )
157
+ end
158
+ }
159
+
160
+ # check if there are threads to wake up
161
+ #
162
+ @waiting_threads.each { |h|
163
+ h[:num] -= 1 # decrease the number of packets, this thread waits for
164
+ h[:thread].raise SerialProtocol::PacketReceived if h[:num] == 0
165
+ }
166
+ end
167
+
168
+ end
169
+
170
+ module SerialProtocol
171
+ class ChecksumMismatch < RuntimeError
172
+ end
173
+
174
+ class PacketReceived < Exception
175
+ end
176
+ end
177
+
178
+ module SerialProtocol
179
+
180
+ # The classes in this section implement wrappers for specific protocols
181
+ # to be used on a serial port
182
+ #
183
+ # They need to implement the following methods:
184
+ #
185
+ # initialize(send_callback, receive_callback, option_hash = {})
186
+ # creates a new instance of the protocol object
187
+ # it gets two methods to talk back to the interface
188
+ # class
189
+ #
190
+ # add_char_to_packet(char)
191
+ # called for each char, that is received.
192
+ #
193
+ # send_packet(data, options)
194
+ # called from the application to send a packet. The class is
195
+ # expected to wrap the data in the specific packet format string and in
196
+ # turn call send_callback(data_str) which will take care of the actual
197
+ # transmission
198
+ #
199
+ # A protocol class is expected to call receive_callback(packet_str) as soon
200
+ # as a valid packet is received
201
+ #
202
+
203
+
204
+ class LineBased
205
+ def initialize(send_callback, receive_callback, options = {})
206
+ @send_callback, @receive_callback = send_callback, receive_callback
207
+ @packet_buffer = ""
208
+ end
209
+
210
+ def add_char_to_packet(char)
211
+ if /\n/ === char.chr
212
+ @receive_callback.call(@receive_buffer)
213
+ @packet_buffer = ""
214
+ else
215
+ @packet_buffer << char
216
+ end
217
+ end
218
+
219
+ def send_packet(data, options = {})
220
+ @send_callback.call(data + "\n")
221
+ end
222
+ end
223
+ end
224
+
@@ -0,0 +1,116 @@
1
+ #!usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ gem 'traits', "0.8.1"
5
+ require 'traits' # from http://www.codeforpeople.com/lib/ruby/traits/traits-0.8.1/
6
+
7
+ # SerialPacket is used to define the format of Packets that
8
+ # can be sent and received over a serial link
9
+ #
10
+ # they are essentially a description how to create a string
11
+ # representation from an array
12
+ #
13
+ # a packet has the following properties:
14
+ #
15
+ # data_format(string)
16
+ # this is a string that is passed to 'pack' and 'unpack'
17
+ #
18
+ # header_format(string)
19
+ # this is the format of the header of received packets
20
+ # this property is used with SerialPacket.matches?
21
+ #
22
+ # header_data
23
+ # an array that is used to decide if a given String is
24
+ #
25
+ module SerialPacketModule
26
+ def self.included(other)
27
+ other.class_eval do
28
+
29
+ def initialize_from_packet(str)
30
+ self.data = str.unpack(self.class.data_format)
31
+ end
32
+
33
+ def initialize(*d)
34
+ self.data = d
35
+ end
36
+
37
+ def to_str
38
+ self.class.header_str << self.data.pack(self.class.data_format)
39
+ end
40
+
41
+ class << self
42
+
43
+ # a packet can only be sent if it has a header
44
+ #
45
+ def sendable?
46
+ (self.header && self.header_format) ? true : false
47
+ end
48
+
49
+ def header_str
50
+ if sendable?
51
+ h = self.header || []
52
+ h.pack(self.header_format) || ""
53
+ else
54
+ ""
55
+ end
56
+ end
57
+
58
+ # a packet can only be received, if it has a filter-expression
59
+ def receiveable?
60
+ defined? header_format and header_filter
61
+ end
62
+
63
+ # checks if some string conforms to the format of this packet
64
+ #
65
+ # this is tested by matching the packet "header" against the
66
+ # provided filter-expression
67
+ #
68
+ def matches?(str)
69
+ header = str.unpack(header_format)
70
+ filter = self.header_filter || []
71
+ filter.zip(header) { |f,a| return false unless f === a }
72
+ return true
73
+ end
74
+
75
+ def from_str(str) #:nodoc:
76
+ p = self.allocate
77
+ p.initialize_from_packet(str)
78
+ p
79
+ end
80
+
81
+ def create(&block)
82
+ klass = Class.new(self)
83
+ klass.instance_eval(&block) if block
84
+ return klass
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ class SerialPacket
92
+ include SerialPacketModule
93
+
94
+ class_trait :data_format => "C*" # packet defaults to an array of bytes
95
+ class_trait :header_format => "CC" # it has a 2-byte header
96
+ class_trait :header_filter => "" # it will react to every byte
97
+ class_trait :header => nil # there is no default header
98
+ # this should be "[]" but arrays do not work
99
+ # in traits
100
+ traits :data => nil
101
+
102
+ # to store custom data in the packat, override
103
+ # one or more of the following methods:
104
+ #
105
+ # initialize_from_packet(str)
106
+ # populate instance variables with data
107
+ #
108
+ # to_str
109
+ # return the string that is sent over the wire
110
+ #
111
+ # matches?(str)
112
+ # default implementation uses +header_format+ and +header+
113
+ # to determine if a given string matches a packet
114
+
115
+ end
116
+
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{serial_interface}
3
+ s.version = "0.2.3"
4
+
5
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
+ s.authors = ["Levin Alexander"]
7
+ s.date = %q{2008-10-12}
8
+ s.description = %q{serial_interface intends to be a small library that makes it easy to define packet based protocols over a serial link (RS232) in a declarative fashion.}
9
+ s.email = ["mail@levinalex.net"]
10
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
11
+ s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib/protocol/rca2006.rb", "lib/serial_interface.rb", "lib/serial_packet.rb", "serial_interface.gemspec", "test/test_serial_interface.rb", "test/test_serial_io.rb", "test/test_serial_packets.rb"]
12
+ s.has_rdoc = true
13
+ s.homepage = %q{http://levinalex.net/src/serial_interface}
14
+ s.rdoc_options = ["--main", "README.txt"]
15
+ s.require_paths = ["lib"]
16
+ s.rubyforge_project = %q{serial_interface}
17
+ s.rubygems_version = %q{1.2.0}
18
+ s.summary = %q{abstracts protocols on a serial link}
19
+ s.test_files = ["test/test_serial_interface.rb", "test/test_serial_io.rb", "test/test_serial_packets.rb"]
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 2
24
+
25
+ if current_version >= 3 then
26
+ s.add_runtime_dependency(%q<traits>, [">= 0"])
27
+ s.add_development_dependency(%q<hoe>, [">= 1.8.0"])
28
+ else
29
+ s.add_dependency(%q<traits>, [">= 0"])
30
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
31
+ end
32
+ else
33
+ s.add_dependency(%q<traits>, [">= 0"])
34
+ s.add_dependency(%q<hoe>, [">= 1.8.0"])
35
+ end
36
+ end
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ require 'test/unit'
4
+ require 'stringio'
5
+ require 'serial_interface'
6
+ require 'protocol/rca2006'
7
+
8
+ class TestSerialInterface < Test::Unit::TestCase
9
+ def setup
10
+
11
+ @data_packet = SerialPacket.create { data_format "C*"; header [?:,?D] }
12
+
13
+ @io_send = StringIO.new
14
+ @io_receive = StringIO.new
15
+
16
+ @sender = PacketIO.new(SerialProtocol::RCA2006, nil, @io_send)
17
+ @receiver = PacketIO.new(SerialProtocol::RCA2006, @io_receive, nil)
18
+ end
19
+
20
+ def test_send_packet
21
+ @sender.add_sender(:data => @data_packet).run
22
+
23
+ @sender.send_packet :data, ?A, ?B, ?C, ?D, ?E
24
+ @io_send.rewind
25
+
26
+ assert_equal("\x65\xEB\x00\x00\a:DABCDE\2443",@io_send.read)
27
+ end
28
+
29
+ def test_receive_packet
30
+ @io_receive << "\x65\xEB\x00\x00\a:DABCDE\2443"
31
+ @io_receive.rewind
32
+
33
+ @receiver.add_receiver(:data => @data_packet) do |packet|
34
+ assert_equal( [?:,?D,?A,?B,?C,?D,?E], packet.data )
35
+ end
36
+ @receiver.run
37
+ end
38
+
39
+ def test_timeout
40
+ @receiver.on_receive { |str| @data = str }
41
+ @receiver.run
42
+
43
+ Thread.new {
44
+ Thread.pass
45
+ @io_receive << "\x65\xEB\x00\x00\a:DABCDE\2443"
46
+ @io_receive.rewind
47
+ }
48
+
49
+ assert_equal( nil, @data )
50
+ assert_nothing_raised {
51
+ @receiver.wait_for_packet(1,2)
52
+ }
53
+ assert_equal( ":DABCDE", @data )
54
+
55
+ assert_raises(Timeout::Error) {
56
+ @receiver.wait_for_packet(1,1)
57
+ }
58
+ end
59
+
60
+
61
+ end
62
+
63
+
@@ -0,0 +1,7 @@
1
+ require 'test/unit'
2
+
3
+ class TestSerialIO < Test::Unit::TestCase
4
+ def test_success
5
+
6
+ end
7
+ end
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ require 'test/unit'
4
+ require 'tempfile'
5
+
6
+ require 'serial_packet'
7
+
8
+ class TestPacketFilter < Test::Unit::TestCase
9
+ def setup
10
+ @default_packet_format = SerialPacket.create
11
+ @default_packet = @default_packet_format.new
12
+ end
13
+
14
+ def test_instantiate_empty_packet
15
+ assert_equal([], @default_packet.data)
16
+ assert_equal("", @default_packet.to_str)
17
+ end
18
+
19
+ def test_instantiate_basic_packet
20
+ p = @default_packet_format.new ?A,?B,?C
21
+
22
+ assert_equal([?A,?B,?C], p.data)
23
+ assert_equal("ABC", p.to_str)
24
+ end
25
+
26
+ def test_packet_format_string
27
+ my_packet = SerialPacket.create { data_format "A*" }
28
+ p = my_packet.new "Hallo"
29
+
30
+ assert_equal(["Hallo"], p.data)
31
+ assert_equal("Hallo", p.to_str)
32
+ end
33
+
34
+ def test_packet_create_from_str
35
+ p = @default_packet_format.from_str("bar")
36
+
37
+ assert_equal([?b,?a,?r], p.data)
38
+ end
39
+
40
+ def test_packet_header
41
+ my_packet = SerialPacket.create { header_format "CC"; header [?a,?b] }
42
+
43
+ assert_equal([?a,?b], my_packet.header)
44
+ end
45
+
46
+ def test_match
47
+ empty = SerialPacket.create
48
+ numbers = SerialPacket.create { header_format "ss"; header_filter [-1,32767] }
49
+ regex = SerialPacket.create{ header_format "a*"; header_filter [/foo/] }
50
+ mixed = SerialPacket.create { header_format "@5C @2C"; header_filter [?X,?Y] }
51
+
52
+ assert_equal true, empty.matches?("abcde")
53
+
54
+ assert_equal(true, numbers.matches?("\xff\xff\xff\x7f"))
55
+ assert_equal(false, numbers.matches?("\xff\xff\xff\x80"))
56
+ assert_equal(false, numbers.matches?("Packet with foo in it"))
57
+ assert_equal(true, regex.matches?("Packet with foo in it"))
58
+ assert_equal(false, regex.matches?("Packet with bar in it"))
59
+ assert_equal(false, regex.matches?("\xff\xff\xff\x80"))
60
+ assert_equal true, mixed.matches?("__Y__X___")
61
+ assert_equal false, mixed.matches?("YYYYYYY")
62
+ end
63
+
64
+ class Position < SerialPacket
65
+ header_format "CC"
66
+ header [?P,?p]
67
+ data_format "SS"
68
+ end
69
+
70
+ def test_position_packet
71
+ p = Position.new 1, -1
72
+ assert_equal("Pp\001\000\xff\xff", p.to_str)
73
+ end
74
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: levinalex-serial_interface
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - Levin Alexander
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-12 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: traits
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: hoe
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.8.0
32
+ version:
33
+ description: serial_interface intends to be a small library that makes it easy to define packet based protocols over a serial link (RS232) in a declarative fashion.
34
+ email:
35
+ - mail@levinalex.net
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - History.txt
42
+ - Manifest.txt
43
+ - README.txt
44
+ files:
45
+ - History.txt
46
+ - Manifest.txt
47
+ - README.txt
48
+ - Rakefile
49
+ - lib/protocol/rca2006.rb
50
+ - lib/serial_interface.rb
51
+ - lib/serial_packet.rb
52
+ - serial_interface.gemspec
53
+ - test/test_serial_interface.rb
54
+ - test/test_serial_io.rb
55
+ - test/test_serial_packets.rb
56
+ has_rdoc: true
57
+ homepage: http://levinalex.net/src/serial_interface
58
+ post_install_message:
59
+ rdoc_options:
60
+ - --main
61
+ - README.txt
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project: serial_interface
79
+ rubygems_version: 1.2.0
80
+ signing_key:
81
+ specification_version: 2
82
+ summary: abstracts protocols on a serial link
83
+ test_files:
84
+ - test/test_serial_interface.rb
85
+ - test/test_serial_io.rb
86
+ - test/test_serial_packets.rb