serial_interface 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.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