levinalex-serial_interface 0.2.3

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/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