serial_interface 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ == 0.3.0 / 2008-10-12
2
+ * initial release
3
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Levin Alexander
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc 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,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "serial_interface"
8
+ gem.summary = %Q{abstracts protocols on a serial link}
9
+ gem.description = %Q{serial_interface intends to be a small library that makes it easy
10
+ to define packet based protocols over a serial link (RS232) in a
11
+ declarative fashion.}
12
+ gem.email = "mail@levinalex.net"
13
+ gem.homepage = "http://github.com/levinalex/serial_interface"
14
+ gem.authors = ["Levin Alexander"]
15
+ gem.add_dependency "traits"
16
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << 'lib' << 'test'
26
+ test.pattern = 'test/**/test_*.rb'
27
+ test.verbose = true
28
+ end
29
+
30
+ begin
31
+ require 'rcov/rcovtask'
32
+ Rcov::RcovTask.new do |test|
33
+ test.libs << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+ rescue LoadError
38
+ task :rcov do
39
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
+ end
41
+ end
42
+
43
+ task :test => :check_dependencies
44
+
45
+ task :default => :test
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "serial_interface #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ 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.3.0'
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,115 @@
1
+ #!usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'traits' # from http://www.codeforpeople.com/lib/ruby/traits/traits-0.8.1/
5
+
6
+ # SerialPacket is used to define the format of Packets that
7
+ # can be sent and received over a serial link
8
+ #
9
+ # they are essentially a description how to create a string
10
+ # representation from an array
11
+ #
12
+ # a packet has the following properties:
13
+ #
14
+ # data_format(string)
15
+ # this is a string that is passed to 'pack' and 'unpack'
16
+ #
17
+ # header_format(string)
18
+ # this is the format of the header of received packets
19
+ # this property is used with SerialPacket.matches?
20
+ #
21
+ # header_data
22
+ # an array that is used to decide if a given String is
23
+ #
24
+ module SerialPacketModule
25
+ def self.included(other)
26
+ other.class_eval do
27
+
28
+ def initialize_from_packet(str)
29
+ self.data = str.unpack(self.class.data_format)
30
+ end
31
+
32
+ def initialize(*d)
33
+ self.data = d
34
+ end
35
+
36
+ def to_str
37
+ self.class.header_str << self.data.pack(self.class.data_format)
38
+ end
39
+
40
+ class << self
41
+
42
+ # a packet can only be sent if it has a header
43
+ #
44
+ def sendable?
45
+ (self.header && self.header_format) ? true : false
46
+ end
47
+
48
+ def header_str
49
+ if sendable?
50
+ h = self.header || []
51
+ h.pack(self.header_format) || ""
52
+ else
53
+ ""
54
+ end
55
+ end
56
+
57
+ # a packet can only be received, if it has a filter-expression
58
+ def receiveable?
59
+ defined? header_format and header_filter
60
+ end
61
+
62
+ # checks if some string conforms to the format of this packet
63
+ #
64
+ # this is tested by matching the packet "header" against the
65
+ # provided filter-expression
66
+ #
67
+ def matches?(str)
68
+ header = str.unpack(header_format)
69
+ filter = self.header_filter || []
70
+ filter.zip(header) { |f,a| return false unless f === a }
71
+ return true
72
+ end
73
+
74
+ def from_str(str) #:nodoc:
75
+ p = self.allocate
76
+ p.initialize_from_packet(str)
77
+ p
78
+ end
79
+
80
+ def create(&block)
81
+ klass = Class.new(self)
82
+ klass.instance_eval(&block) if block
83
+ return klass
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ class SerialPacket
91
+ include SerialPacketModule
92
+
93
+ class_trait :data_format => "C*" # packet defaults to an array of bytes
94
+ class_trait :header_format => "CC" # it has a 2-byte header
95
+ class_trait :header_filter => "" # it will react to every byte
96
+ class_trait :header => nil # there is no default header
97
+ # this should be "[]" but arrays do not work
98
+ # in traits
99
+ traits :data => nil
100
+
101
+ # to store custom data in the packat, override
102
+ # one or more of the following methods:
103
+ #
104
+ # initialize_from_packet(str)
105
+ # populate instance variables with data
106
+ #
107
+ # to_str
108
+ # return the string that is sent over the wire
109
+ #
110
+ # matches?(str)
111
+ # default implementation uses +header_format+ and +header+
112
+ # to determine if a given string matches a packet
113
+
114
+ end
115
+
@@ -0,0 +1,61 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{serial_interface}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Levin Alexander"]
12
+ s.date = %q{2010-01-15}
13
+ s.description = %q{serial_interface intends to be a small library that makes it easy
14
+ to define packet based protocols over a serial link (RS232) in a
15
+ declarative fashion.}
16
+ s.email = %q{mail@levinalex.net}
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".gitignore",
23
+ "History.txt",
24
+ "Rakefile",
25
+ "lib/protocol/rca2006.rb",
26
+ "lib/serial_interface.rb",
27
+ "lib/serial_packet.rb",
28
+ "serial_interface.gemspec",
29
+ "test/test_serial_interface.rb",
30
+ "test/test_serial_io.rb",
31
+ "test/test_serial_packets.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/levinalex/serial_interface}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.3.5}
37
+ s.summary = %q{abstracts protocols on a serial link}
38
+ s.test_files = [
39
+ "test/helper.rb",
40
+ "test/test_serial_interface.rb",
41
+ "test/test_serial_io.rb",
42
+ "test/test_serial_packets.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<traits>, [">= 0"])
51
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
52
+ else
53
+ s.add_dependency(%q<traits>, [">= 0"])
54
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
55
+ end
56
+ else
57
+ s.add_dependency(%q<traits>, [">= 0"])
58
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
59
+ end
60
+ end
61
+
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'serial_interface'
8
+
9
+ class Test::Unit::TestCase
10
+ 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,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: serial_interface
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Levin Alexander
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-15 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: traits
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: thoughtbot-shoulda
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: |-
36
+ serial_interface intends to be a small library that makes it easy
37
+ to define packet based protocols over a serial link (RS232) in a
38
+ declarative fashion.
39
+ email: mail@levinalex.net
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - LICENSE
46
+ - README.rdoc
47
+ files:
48
+ - .gitignore
49
+ - History.txt
50
+ - Rakefile
51
+ - lib/protocol/rca2006.rb
52
+ - lib/serial_interface.rb
53
+ - lib/serial_packet.rb
54
+ - serial_interface.gemspec
55
+ - test/test_serial_interface.rb
56
+ - test/test_serial_io.rb
57
+ - test/test_serial_packets.rb
58
+ - LICENSE
59
+ - README.rdoc
60
+ has_rdoc: true
61
+ homepage: http://github.com/levinalex/serial_interface
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options:
66
+ - --charset=UTF-8
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.3.5
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: abstracts protocols on a serial link
88
+ test_files:
89
+ - test/helper.rb
90
+ - test/test_serial_interface.rb
91
+ - test/test_serial_io.rb
92
+ - test/test_serial_packets.rb