rumba 0.2.0 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1d712a0e9b5806c625ea88f90a7ac66d326cde2
4
- data.tar.gz: 8119e32a631feffedfdced0645e5cfbf3aefe958
3
+ metadata.gz: 61b352b09eef601f9b36509b7283772d366fe906
4
+ data.tar.gz: 5fd8ed4266bbe4b72ccac8a2ee5ead83217857ac
5
5
  SHA512:
6
- metadata.gz: e3cb42c3be2c4d84dbfa611840eb8351b628607d486d46ccd216ee53863e1b128d8c0cf4991cfc37dbeed6b72f7fa9808fd307571fd78c3c35ae669c3de4dc44
7
- data.tar.gz: 1db707dc89538cc544c587e2efbd00a9959d5df7a3e01405defc576a36afe96b5bdbaf66f30a6dd007953de68cf3ff4239fe2142f40c077681343003611cfd92
6
+ metadata.gz: fe9715009338618cd7357377b666b95243eb4261abc356ed2d208b6f9ce18c96bdafac2c6e3bee85df21c9e81775b85dd464ffd9d4d38a284353e8125b1d08b8
7
+ data.tar.gz: e85b669ef50bf3faf18fbe0fe41650e7a156fe4c83ccf8a09b1debe0daa00681905b1717be630b7ecfc99b5a624a5d476528b657787a65382afd6dbc502d7e32
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Rumba
2
+ A Ruby wrapper for the Roomba Serial Command Interface
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/rumba.svg)](http://badge.fury.io/rb/rumba)
5
+
6
+ - - -
7
+
8
+ This is a no-frills, lightweight, and cross-platform implementation of the iRobot Roomba Serial Command Interface. You can use it to control your Roomba from your computer :D
9
+
10
+ Practically all of the interesting parts of the SCI have been implemented. There's also an optional DSL that only supports movements currently (but is under active development!).
11
+
12
+ Originally used as part of my senior design project in school, but now gem-ified for the handful of other people who think robots and Ruby are a good combination :)
13
+
14
+ For more serious robot hacking in Ruby, check out [Artoo](http://artoo.io/)! It's really neat, but possibly a little overkill for small hacks.
15
+
16
+ I welcome pull requests and feedback!
17
+
18
+ Happy hacking!
19
+
20
+ ### Dependencies
21
+ * [serialport](http://ruby-serialport.rubyforge.org/)
22
+
23
+ ### Usage
24
+
25
+ Here's an example program:
26
+
27
+ (using the experimental DSL)
28
+
29
+ ```ruby
30
+ require 'rumba'
31
+
32
+ Roomba.new('/dev/tty.usbserial') do
33
+ safe_mode
34
+ forward meter(1)
35
+ rotate :left
36
+ rotate -90 # degrees
37
+
38
+ rotate :right
39
+ rotate 90
40
+ backward meter(1)
41
+
42
+ # access to any methods in the Roomba class here!
43
+ end
44
+ ```
45
+
46
+ (the old-fashioned way!)
47
+
48
+ ```ruby
49
+ require 'rumba'
50
+ r = Roomba.new('/dev/tty.SerialIO1-SPP')
51
+ r.full_mode # Change to full mode (unrestricted access)
52
+ r.straight(300) # Move forwards at 300 mm/s
53
+ sleep(2)
54
+ r.straight(-300) # Move backwards at 300 mm/s
55
+ sleep(2)
56
+ r.spin_left(500) # Spin to the left at 500 mm/s
57
+ sleep(2)
58
+ r.spin_right(500) # Spin to the right at 500 mm/s
59
+ r.halt # Stop moving
60
+ ```
61
+
62
+ ### Roadmap
63
+ * Create an optional DSL (IN PROGRESS!)
64
+ * REAL documentation!
65
+ * ???
66
+ * idk
67
+
68
+ ### More Information
69
+
70
+ The complete Roomba SCI specification can be found [here](http://www.irobot.com/images/consumer/hacker/roomba_sci_spec_manual.pdf)
71
+
72
+ ### License
73
+
74
+ ```
75
+ Copyright (c) 2014, Eric Wood
76
+ All rights reserved.
77
+
78
+ Redistribution and use in source and binary forms, with or without
79
+ modification, are permitted provided that the following conditions are met:
80
+ * Redistributions of source code must retain the above copyright
81
+ notice, this list of conditions and the following disclaimer.
82
+ * Redistributions in binary form must reproduce the above copyright
83
+ notice, this list of conditions and the following disclaimer in the
84
+ documentation and/or other materials provided with the distribution.
85
+ * Neither the name of the author nor the
86
+ names of its contributors may be used to endorse or promote products
87
+ derived from this software without specific prior written permission.
88
+
89
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
90
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
91
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
92
+ DISCLAIMED. IN NO EVENT SHALL ERIC WOOD BE LIABLE FOR ANY
93
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
94
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
95
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
96
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
97
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
98
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
99
+ ```
@@ -0,0 +1,54 @@
1
+ # This library has a TON of annoying constants...
2
+ # we have quarantined them here to save your eyes
3
+
4
+ class Rumba
5
+ module Constants
6
+ # These opcodes require no arguments
7
+ OPCODES = {
8
+ start: 128,
9
+ control: 130,
10
+ power: 133,
11
+ spot: 134,
12
+ clean: 135,
13
+ max: 136,
14
+ dock: 143,
15
+ play_script: 153,
16
+ show_script: 154
17
+ }
18
+
19
+ # Create a method for each opcode that writes its data.
20
+ # This allows us to simply call roomba.code,
21
+ # and it's a cool excuse to do some metaprogramming :)
22
+ OPCODES.each do |name,val|
23
+ send :define_method, name do
24
+ write_chars([val])
25
+ end
26
+ end
27
+
28
+ SAFE_MODE = 131
29
+ FULL_MODE = 132
30
+
31
+ # These opcodes require arguments
32
+ DRIVE = 137
33
+ MOTORS = 138
34
+ LEDS = 139
35
+ SONG = 140
36
+ PLAY_SONG = 141
37
+ SENSORS = 142
38
+ QUERY_LIST = 149
39
+ DRIVE_DIRECT = 145
40
+
41
+ # Used for making the Roomba sing!
42
+ # Note that nil is simply a rest
43
+ NOTES = {
44
+ 'A' => 69, 'A#' => 70, 'B' => 71, 'C' => 72, 'C#' => 73, 'D' => 74,
45
+ 'D#' => 75, 'E' => 76, 'F' => 77, 'F#' => 78, 'G' => 79, 'G#' => 80,
46
+ nil => 0
47
+ }
48
+
49
+ MOTORS_MASK_SIDE_BRUSH = 0x1
50
+ MOTORS_MASK_VACUUM = 0x2
51
+ MOTORS_MASK_MAIN_BRUSH = 0x4
52
+
53
+ end
54
+ end
data/lib/rumba/dsl.rb ADDED
@@ -0,0 +1,72 @@
1
+ # Define the Rumba "DSL"
2
+ # Lots of easy to use methods for basic tasks
3
+
4
+ class Rumba
5
+ module Dsl
6
+ # Remember, Roomba speeds are defined in mm/s (max is 200)
7
+ DEFAULT_SPEED = 200
8
+
9
+ # Radius of an average Roomba, used for calculating rotation
10
+ RADIUS = 165.1 # 6.5 inches
11
+
12
+ # distance is in mm!
13
+ def forward(distance, speed: DEFAULT_SPEED)
14
+ duration = distance / speed
15
+ straight(speed)
16
+ sleep(duration)
17
+ halt
18
+ end
19
+
20
+ # distance is in mm!
21
+ def backward(distance, speed: DEFAULT_SPEED)
22
+ duration = distance / speed
23
+ straight(-speed)
24
+ sleep(duration)
25
+ halt
26
+ end
27
+
28
+ # Direction can either be a Fixnum for number of degrees,
29
+ # or a symbol for the direction (:left, :right)
30
+ def rotate(direction, speed: DEFAULT_SPEED)
31
+ # handle symbols...
32
+ case direction
33
+ when :left
34
+ direction = -90
35
+ when :right
36
+ direction = 90
37
+ end
38
+
39
+ circumfrence = 2 * Math::PI * RADIUS
40
+
41
+ # based on the angle, this is how far we need to turn
42
+ distance = ((circumfrence / 360) * direction).abs
43
+
44
+ direction < 0 ? spin_left(speed) : spin_right(speed)
45
+ duration = (distance / speed).abs
46
+ sleep(duration)
47
+ halt
48
+ end
49
+
50
+ # MEASUREMENT HELPERS
51
+ # TODO: break these out into separate helpers file?
52
+ def inches(num)
53
+ 25.4 * num
54
+ end
55
+ alias_method :inch, :inches
56
+
57
+ def feet(num)
58
+ inches(num) * 12
59
+ end
60
+ alias_method :foot, :feet
61
+
62
+ def meters(num)
63
+ num * 1000
64
+ end
65
+ alias_method :meter, :meters
66
+
67
+ # eh, why not?
68
+ alias_method :forwards, :forward
69
+ alias_method :backwards, :backward
70
+ alias_method :turn, :rotate
71
+ end
72
+ end
@@ -0,0 +1,296 @@
1
+ # Sensor-related code is all here!
2
+
3
+ class Rumba
4
+ module Sensor
5
+ class Boolean
6
+ def self.convert(v)
7
+ v == 1 ? true : false
8
+ end
9
+ end
10
+
11
+ class ChargingState
12
+ def self.convert(v)
13
+ case v
14
+ when 0
15
+ :not_charging
16
+ when 1
17
+ :reconditioning_charging
18
+ when 2
19
+ :full_charging
20
+ when 3
21
+ :trickle_charging
22
+ when 4
23
+ :waiting
24
+ when 5
25
+ :charging_fault_condition
26
+ end
27
+ end
28
+ end
29
+
30
+ class OIMode
31
+ def self.convert(v)
32
+ case v
33
+ when 0
34
+ :off
35
+ when 1
36
+ :passive
37
+ when 2
38
+ :safe
39
+ when 3
40
+ :full
41
+ end
42
+ end
43
+ end
44
+
45
+ class ChargingSourceAvailable
46
+ def self.convert(v)
47
+ h = {}
48
+ h[:internal_charger] = v & 0b1 > 0 ? true : false
49
+ h[:home_base] = v & 0b10 > 0 ? true : false
50
+ h
51
+ end
52
+ end
53
+
54
+ class LightBumper
55
+ def self.convert(v)
56
+ h = {}
57
+ h[:light_bumper_left] = v & 0b1 > 0 ? true : false
58
+ h[:light_bumper_front_left] = v & 0b10 > 0 ? true : false
59
+ h[:light_bumper_center_left] = v & 0b100 > 0 ? true : false
60
+ h[:light_bumper_center_right] = v & 0b1000 > 0 ? true : false
61
+ h[:light_bumper_front_right] = v & 0b10000 > 0 ? true : false
62
+ h[:light_bumper_right] = v & 0b100000 > 0 ? true : false
63
+ h
64
+ end
65
+ end
66
+
67
+ class WheelOvercurrents
68
+ def self.convert(v)
69
+ h = {}
70
+ h[:side_brush] = v & 0b1 > 0 ? true : false
71
+ h[:main_brush] = v & 0b100 > 0 ? true : false
72
+ h[:right_wheel] = v & 0b1000 > 0 ? true : false
73
+ h[:left_wheel] = v & 0b10000 > 0 ? true : false
74
+ h
75
+ end
76
+ end
77
+
78
+ class BumpsAndWheelDrops
79
+ def self.convert(v)
80
+ h = {}
81
+ h[:bump_right] = v & 0b1 > 0 ? true : false
82
+ h[:bump_left] = v & 0b10 > 0 ? true : false
83
+ h[:wheel_drop_right] = v & 0b100 > 0 ? true : false
84
+ h[:wheel_drop_left] = v & 0b1000 > 0 ? true : false
85
+ h
86
+ end
87
+
88
+ end
89
+
90
+ INFRARED_CHARACTER = {
91
+ 129 => :left,
92
+ 130 => :forward,
93
+ 131 => :right,
94
+ 132 => :spot,
95
+ 133 => :max,
96
+ 134 => :small,
97
+ 135 => :medium,
98
+ 136 => :large,
99
+ 137 => :stop,
100
+ 138 => :power,
101
+ 139 => :arc_left,
102
+ 140 => :arc_right,
103
+ 141 => :stop,
104
+ 142 => :download,
105
+ 143 => :seek_dock,
106
+ 160 => :reserved,
107
+ 161 => :force_field,
108
+ 164 => :green_buoy,
109
+ 165 => :green_buoy_and_force_field,
110
+ 168 => :red_buoy,
111
+ 169 => :red_buoy_and_force_field,
112
+ 172 => :red_and_green_buoy,
113
+ 173 => :red_and_green_buoy_and_force_field,
114
+ 240 => :reserved,
115
+ 248 => :red_buoy,
116
+ 244 => :green_buoy,
117
+ 242 => :force_field,
118
+ 252 => :red_and_green_buoy,
119
+ 250 => :red_buoy_and_force_field,
120
+ 246 => :green_buoy_and_force_field,
121
+ 254 => :red_and_green_buoy_and_force_field,
122
+ 162 => :virtual_wall
123
+ }
124
+
125
+ class InfraredCharacter
126
+ def self.convert(v)
127
+ INFRARED_CHARACTER[v]
128
+ end
129
+ end
130
+
131
+ SENSORS_PACKETS_SIZE = [
132
+ 0, # 0
133
+ 0,0,0,0,0,0, # 1-6
134
+ 1,1,1,1,1,1,1,1,1,1,1,1, # 7-18
135
+ 2,2, # 19-20
136
+ 1, # 21
137
+ 2,2, # 22-23
138
+ 1, # 24
139
+ 2,2,2,2,2,2,2, # 25-31
140
+ 1, # 32
141
+ 2, # 33
142
+ 1,1,1,1,1, # 34-38
143
+ 2,2,2,2,2,2, # 39-44
144
+ 1, # 45
145
+ 2,2,2,2,2,2, # 46-51
146
+ 1,1, # 52-53
147
+ 2,2,2,2, # 54-57
148
+ 1 # 58
149
+ ]
150
+
151
+ SENSORS_PACKETS_SIGNEDNESS = [
152
+ :na, # 0
153
+ :na,:na,:na,:na,:na,:na, # 1-6
154
+ :unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned, # 7-14
155
+ :signed,:signed,:unsigned,:unsigned, # 15-18
156
+ :signed,:signed, # 19-20
157
+ :unsigned, # 21
158
+ :unsigned,:signed, # 22-23
159
+ :signed, # 24
160
+ :unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned, # 25-31
161
+ :unsigned, # 32
162
+ :unsigned, # 33
163
+ :unsigned,:unsigned,:unsigned,:unsigned,:unsigned, # 34-38
164
+ :signed,:signed,:signed,:signed,:unsigned,:unsigned, # 39-44
165
+ :unsigned, # 45
166
+ :unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned, # 46-51
167
+ :unsigned,:unsigned, # 52-53
168
+ :signed,:signed,:signed,:signed, # 54-57
169
+ :unsigned # 58
170
+ ]
171
+
172
+ # Human readable packets name
173
+ SENSORS_PACKETS_SYMBOL = [
174
+ :ignore, # 0
175
+ :ignore,:ignore,:ignore,:ignore,:ignore,:ignore, # 1-6
176
+ :bumps_and_wheel_drops,:wall,:cliff_left,:cliff_front_left,:cliff_front_right,:cliff_right,:virtual_wall,:wheel_overcurrents, # 7-14
177
+ :dirt_detect,:ignore,:infrared_character_omni,:buttons,# 15-18
178
+ :distance,:angle, # 19-20
179
+ :charging_state, # 21
180
+ :voltage,:current, # 22-23
181
+ :temperature, # 24
182
+ :battery_charge,:battery_capacity,:wall_signal,:cliff_left_signal,:cliff_front_left_signal,:cliff_front_right_signal,:cliff_right_signal, # 25-31
183
+ :ignore, # 32
184
+ :ignore, # 33
185
+ :charging_sources_available,:oi_mode,:song_number,:song_playing,:number_of_stream_packets, # 34-38
186
+ :requested_velocity,:requested_radius,:requested_right_velocity,:requested_left_velocity,:right_encoder_count,:left_encoder_count, # 39-44
187
+ :light_bumper, # 45
188
+ :light_bump_left_signal,:light_bump_front_left_signal,:light_bump_center_left_signal,:light_bump_center_right_signal,:light_bump_front_right_signal,:light_bump_right_signal, # 46-51
189
+ :infrared_character_left,:infrared_character_right, # 52-53
190
+ :left_motor_current,:right_motor_current,:main_brush_motor_current,:side_brush_motor_current, # 54-57
191
+ :stasis # 58
192
+ ]
193
+
194
+ # Sensors mapper
195
+ SENSORS_PACKETS_VALUE = {
196
+ wall: Boolean,
197
+ cliff_left: Boolean,
198
+ cliff_front_left: Boolean,
199
+ cliff_front_right: Boolean,
200
+ cliff_right: Boolean,
201
+ virtual_wall: Boolean,
202
+ song_playing: Boolean,
203
+ stasis: Boolean,
204
+
205
+ charging_state: ChargingState,
206
+ oi_mode: OIMode,
207
+ charging_sources_available: ChargingSourceAvailable,
208
+ light_bumper: LightBumper,
209
+ wheel_overcurrents: WheelOvercurrents,
210
+ bumps_and_wheel_drops: BumpsAndWheelDrops,
211
+ infrared_character_omni: InfraredCharacter,
212
+ infrared_character_left: InfraredCharacter,
213
+ infrared_character_right: InfraredCharacter
214
+ }
215
+
216
+ # Sensors groups
217
+ SENSORS_GROUP_PACKETS = {
218
+ 0 => 7..26,
219
+ 1 => 7..16,
220
+ 2 => 17..20,
221
+ 3 => 21..26,
222
+ 4 => 27..34,
223
+ 5 => 35..42,
224
+ 6 => 7..42,
225
+ 100 => 7..58,
226
+ 101 => 43..58,
227
+ 106 => 40..51,
228
+ 107 => 54..58
229
+ }
230
+
231
+ # Convert sensors bytes to packets hash
232
+ def sensors_bytes_to_packets(bytes,packets)
233
+ packets_h = {}
234
+ pack = ""
235
+ packets.each do |packet|
236
+ size = SENSORS_PACKETS_SIZE[packet]
237
+ signedness = SENSORS_PACKETS_SIGNEDNESS[packet]
238
+ case size
239
+ when 1
240
+ case signedness
241
+ when :signed
242
+ pack += "c"
243
+ when :unsigned
244
+ pack += "C"
245
+ end
246
+ when 2
247
+ case signedness
248
+ when :signed
249
+ pack += "s>"
250
+ when :unsigned
251
+ pack += "S>"
252
+ end
253
+ end
254
+ end
255
+
256
+ nums = bytes.unpack(pack)
257
+
258
+ cur_packet = 0
259
+ packets.each do |packet|
260
+ pname = SENSORS_PACKETS_SYMBOL[packet]
261
+ unless pname == :ignore
262
+ value = nums[cur_packet]
263
+ conv = SENSORS_PACKETS_VALUE[pname]
264
+ if conv
265
+ value = conv.convert(value)
266
+ end
267
+ packets_h[pname] = value
268
+ end
269
+
270
+ cur_packet += 1
271
+ end
272
+
273
+ packets_h
274
+ end
275
+
276
+ # Get sensors by group
277
+ # Default group 100 = all packets
278
+ def get_sensors(group=100)
279
+ sensors_bytes_to_packets(write_chars_with_read([SENSORS,group]),SENSORS_GROUP_PACKETS[group])
280
+ end
281
+
282
+ # Get sensors by list
283
+ # Array entry can be packet ID or symbol
284
+ def get_sensors_list(list)
285
+ ids_list=(list.map do |l|
286
+ if l.class == Symbol
287
+ SENSORS_PACKETS_SYMBOL.find_index(l)
288
+ else
289
+ l
290
+ end
291
+ end)
292
+
293
+ sensors_bytes_to_packets(write_chars_with_read([QUERY_LIST,ids_list.length]+ids_list),ids_list)
294
+ end
295
+ end
296
+ end
data/lib/rumba.rb CHANGED
@@ -1,157 +1,16 @@
1
1
  require 'rubygems'
2
2
  require 'serialport'
3
3
  require 'timeout'
4
- require_relative 'sensors.rb'
4
+ require 'rumba/constants'
5
+ require 'rumba/sensors'
6
+ require 'rumba/dsl'
5
7
 
6
- class Roomba
7
- attr_accessor :serial
8
-
9
- # These opcodes require no arguments
10
- OPCODES = {
11
- start: 128,
12
- control: 130,
13
- power: 133,
14
- spot: 134,
15
- clean: 135,
16
- max: 136,
17
- dock: 143,
18
- play_script: 153,
19
- show_script: 154
20
- }
21
-
22
- # Create a method for each opcode that writes its data.
23
- # This allows us to simply call roomba.code,
24
- # and it's a cool excuse to do some metaprogramming :)
25
- OPCODES.each do |name,val|
26
- send :define_method, name do
27
- write_chars([val])
28
- end
29
- end
30
-
31
- SAFE_MODE = 131
32
- FULL_MODE = 132
33
-
34
- # These opcodes require arguments
35
- DRIVE = 137
36
- MOTORS = 138
37
- LEDS = 139
38
- SONG = 140
39
- PLAY_SONG = 141
40
- SENSORS = 142
41
- QUERY_LIST = 149
42
- DRIVE_DIRECT = 145
43
-
44
- # Used for making the Roomba sing!
45
- # Note that nil is simply a rest
46
- NOTES = {
47
- 'A' => 69, 'A#' => 70, 'B' => 71, 'C' => 72, 'C#' => 73, 'D' => 74,
48
- 'D#' => 75, 'E' => 76, 'F' => 77, 'F#' => 78, 'G' => 79, 'G#' => 80,
49
- nil => 0
50
- }
8
+ class Rumba
9
+ include Constants
10
+ include Sensor
11
+ include Dsl
51
12
 
52
- MOTORS_MASK_SIDE_BRUSH = 0x1
53
- MOTORS_MASK_VACUUM = 0x2
54
- MOTORS_MASK_MAIN_BRUSH = 0x4
55
-
56
- SENSORS_PACKETS_SIZE = [
57
- 0, # 0
58
- 0,0,0,0,0,0, # 1-6
59
- 1,1,1,1,1,1,1,1,1,1,1,1, # 7-18
60
- 2,2, # 19-20
61
- 1, # 21
62
- 2,2, # 22-23
63
- 1, # 24
64
- 2,2,2,2,2,2,2, # 25-31
65
- 1, # 32
66
- 2, # 33
67
- 1,1,1,1,1, # 34-38
68
- 2,2,2,2,2,2, # 39-44
69
- 1, # 45
70
- 2,2,2,2,2,2, # 46-51
71
- 1,1, # 52-53
72
- 2,2,2,2, # 54-57
73
- 1 # 58
74
- ]
75
-
76
- SENSORS_PACKETS_SIGNEDNESS = [
77
- :na, # 0
78
- :na,:na,:na,:na,:na,:na, # 1-6
79
- :unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned, # 7-14
80
- :signed,:signed,:unsigned,:unsigned, # 15-18
81
- :signed,:signed, # 19-20
82
- :unsigned, # 21
83
- :unsigned,:signed, # 22-23
84
- :signed, # 24
85
- :unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned, # 25-31
86
- :unsigned, # 32
87
- :unsigned, # 33
88
- :unsigned,:unsigned,:unsigned,:unsigned,:unsigned, # 34-38
89
- :signed,:signed,:signed,:signed,:unsigned,:unsigned, # 39-44
90
- :unsigned, # 45
91
- :unsigned,:unsigned,:unsigned,:unsigned,:unsigned,:unsigned, # 46-51
92
- :unsigned,:unsigned, # 52-53
93
- :signed,:signed,:signed,:signed, # 54-57
94
- :unsigned # 58
95
- ]
96
-
97
- # Human readable packets name
98
- SENSORS_PACKETS_SYMBOL = [
99
- :ignore, # 0
100
- :ignore,:ignore,:ignore,:ignore,:ignore,:ignore, # 1-6
101
- :bumps_and_wheel_drops,:wall,:cliff_left,:cliff_front_left,:cliff_front_right,:cliff_right,:virtual_wall,:wheel_overcurrents, # 7-14
102
- :dirt_detect,:ignore,:infrared_character_omni,:buttons,# 15-18
103
- :distance,:angle, # 19-20
104
- :charging_state, # 21
105
- :voltage,:current, # 22-23
106
- :temperature, # 24
107
- :battery_charge,:battery_capacity,:wall_signal,:cliff_left_signal,:cliff_front_left_signal,:cliff_front_right_signal,:cliff_right_signal, # 25-31
108
- :ignore, # 32
109
- :ignore, # 33
110
- :charging_sources_available,:oi_mode,:song_number,:song_playing,:number_of_stream_packets, # 34-38
111
- :requested_velocity,:requested_radius,:requested_right_velocity,:requested_left_velocity,:right_encoder_count,:left_encoder_count, # 39-44
112
- :light_bumper, # 45
113
- :light_bump_left_signal,:light_bump_front_left_signal,:light_bump_center_left_signal,:light_bump_center_right_signal,:light_bump_front_right_signal,:light_bump_right_signal, # 46-51
114
- :infrared_character_left,:infrared_character_right, # 52-53
115
- :left_motor_current,:right_motor_current,:main_brush_motor_current,:side_brush_motor_current, # 54-57
116
- :stasis # 58
117
- ]
118
-
119
- # Sensors mapper
120
- SENSORS_PACKETS_VALUE = {
121
- :wall=>RoombaSensor::Boolean,
122
- :cliff_left=>RoombaSensor::Boolean,
123
- :cliff_front_left=>RoombaSensor::Boolean,
124
- :cliff_front_right=>RoombaSensor::Boolean,
125
- :cliff_right=>RoombaSensor::Boolean,
126
- :virtual_wall=>RoombaSensor::Boolean,
127
- :song_playing=>RoombaSensor::Boolean,
128
- :stasis=>RoombaSensor::Boolean,
129
-
130
- :charging_state=>RoombaSensor::ChargingState,
131
- :oi_mode=>RoombaSensor::OIMode,
132
- :charging_sources_available=>RoombaSensor::ChargingSourceAvailable,
133
- :light_bumper=>RoombaSensor::LightBumper,
134
- :wheel_overcurrents=>RoombaSensor::WheelOvercurrents,
135
- :bumps_and_wheel_drops=>RoombaSensor::BumpsAndWheelDrops,
136
- :infrared_character_omni=>RoombaSensor::InfraredCharacter,
137
- :infrared_character_left=>RoombaSensor::InfraredCharacter,
138
- :infrared_character_right=>RoombaSensor::InfraredCharacter
139
- }
140
-
141
- # Sensors groups
142
- SENSORS_GROUP_PACKETS = {
143
- 0 => 7..26,
144
- 1 => 7..16,
145
- 2 => 17..20,
146
- 3 => 21..26,
147
- 4 => 27..34,
148
- 5 => 35..42,
149
- 6 => 7..42,
150
- 100 => 7..58,
151
- 101 => 43..58,
152
- 106 => 40..51,
153
- 107 => 54..58
154
- }
13
+ attr_accessor :serial
155
14
 
156
15
  #############################################################################
157
16
  # HELPERS #
@@ -194,56 +53,11 @@ class Roomba
194
53
  sleep(0.1)
195
54
  data=""
196
55
  while(data.length==0)
197
- data+=@serial.read
56
+ data += @serial.read
198
57
  end
199
58
  data
200
59
  end
201
60
 
202
- # Convert sensors bytes to packets hash
203
- def sensors_bytes_to_packets(bytes,packets)
204
- packets_h = {}
205
- pack = ""
206
- packets.each do |packet|
207
- size = SENSORS_PACKETS_SIZE[packet]
208
- signedness = SENSORS_PACKETS_SIGNEDNESS[packet]
209
- case size
210
- when 1
211
- case signedness
212
- when :signed
213
- pack += "c"
214
- when :unsigned
215
- pack += "C"
216
- end
217
- when 2
218
- case signedness
219
- when :signed
220
- pack += "s>"
221
- when :unsigned
222
- pack += "S>"
223
- end
224
- end
225
- end
226
-
227
- nums = bytes.unpack(pack)
228
-
229
- cur_packet = 0
230
- packets.each do |packet|
231
- pname = SENSORS_PACKETS_SYMBOL[packet]
232
- unless pname == :ignore
233
- value = nums[cur_packet]
234
- conv = SENSORS_PACKETS_VALUE[pname]
235
- if conv
236
- value = conv.convert(value)
237
- end
238
- packets_h[pname] = value
239
- end
240
-
241
- cur_packet+=1
242
- end
243
-
244
- packets_h
245
- end
246
-
247
61
  # Convert integer to two's complement signed 16 bit integer.
248
62
  # Note that the Roomba is big-endian...I need to fix this
249
63
  # code to make it portable across different architectures.
@@ -324,26 +138,6 @@ class Roomba
324
138
  write_chars([PLAY_SONG,song_number])
325
139
  end
326
140
 
327
- # Get sensors by group
328
- # Default group 100 = all packets
329
- def get_sensors(group=100)
330
- sensors_bytes_to_packets(write_chars_with_read([SENSORS,group]),SENSORS_GROUP_PACKETS[group])
331
- end
332
-
333
- # Get sensors by list
334
- # Array entry can be packet ID or symbol
335
- def get_sensors_list(list)
336
- ids_list=(list.map do |l|
337
- if l.class == Symbol
338
- Roomba::SENSORS_PACKETS_SYMBOL.find_index(l)
339
- else
340
- l
341
- end
342
- end)
343
-
344
- sensors_bytes_to_packets(write_chars_with_read([QUERY_LIST,ids_list.length]+ids_list),ids_list)
345
- end
346
-
347
141
  #############################################################################
348
142
  # Convenience methods #
349
143
  #############################################################################
@@ -376,7 +170,7 @@ class Roomba
376
170
  end
377
171
 
378
172
  def battery_percentage
379
- sensors=get_sensors(3)
173
+ sensors = get_sensors(3)
380
174
  ((sensors[:battery_charge].to_f / sensors[:battery_capacity].to_f) * 100).to_i
381
175
  end
382
176
 
@@ -400,7 +194,7 @@ class Roomba
400
194
  write_chars([MOTORS,MOTORS_MASK_MAIN_BRUSH])
401
195
  end
402
196
 
403
- def initialize(port, timeout=10)
197
+ def initialize(port, baud=57600, &block)
404
198
  @leds = {
405
199
  advance: false,
406
200
  play: false,
@@ -408,15 +202,22 @@ class Roomba
408
202
  intensity: 0
409
203
  }
410
204
 
411
- @timeout = timeout
412
- Timeout::timeout(@timeout) do
413
- # Initialize the serialport
414
- # 115200 for Roomba 5xx
415
- # 57600 for older models ?
416
- @serial = SerialPort.new(port, 115200)
417
- @serial.read_timeout = 15
418
- self.start
205
+ # Initialize the serialport
206
+ # 115200 for Roomba 5xx
207
+ # 57600 for older models (and iRobot Create)
208
+ @serial = SerialPort.new(port, baud)
209
+ @serial.read_timeout = 15
210
+ self.start
211
+
212
+ # initialize the "DSL" here!
213
+ if block_given?
214
+ instance_eval(&block)
215
+
216
+ # clean up after ourselves (this is a Roomba, after all!)
217
+ self.power_off
419
218
  end
420
219
  end
421
220
  end
422
221
 
222
+ # Alias for backwards-compatibility
223
+ Roomba = Rumba
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rumba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Wood
@@ -30,8 +30,11 @@ executables: []
30
30
  extensions: []
31
31
  extra_rdoc_files: []
32
32
  files:
33
+ - README.md
33
34
  - lib/rumba.rb
34
- - lib/sensors.rb
35
+ - lib/rumba/constants.rb
36
+ - lib/rumba/dsl.rb
37
+ - lib/rumba/sensors.rb
35
38
  homepage: http://github.com/eric-wood/roomba
36
39
  licenses:
37
40
  - BSD
data/lib/sensors.rb DELETED
@@ -1,128 +0,0 @@
1
- module RoombaSensor
2
- class Boolean
3
- def self.convert(v)
4
- v == 1 ? true : false
5
- end
6
- end
7
-
8
- class ChargingState
9
- def self.convert(v)
10
- case v
11
- when 0
12
- :not_charging
13
- when 1
14
- :reconditioning_charging
15
- when 2
16
- :full_charging
17
- when 3
18
- :trickle_charging
19
- when 4
20
- :waiting
21
- when 5
22
- :charging_fault_condition
23
- end
24
- end
25
- end
26
-
27
- class OIMode
28
- def self.convert(v)
29
- case v
30
- when 0
31
- :off
32
- when 1
33
- :passive
34
- when 2
35
- :safe
36
- when 3
37
- :full
38
- end
39
- end
40
- end
41
-
42
- class ChargingSourceAvailable
43
- def self.convert(v)
44
- h = {}
45
- h[:internal_charger] = v & 0b1 > 0 ? true : false
46
- h[:home_base] = v & 0b10 > 0 ? true : false
47
- h
48
- end
49
- end
50
-
51
- class LightBumper
52
- def self.convert(v)
53
- h = {}
54
- h[:light_bumper_left] = v & 0b1 > 0 ? true : false
55
- h[:light_bumper_front_left] = v & 0b10 > 0 ? true : false
56
- h[:light_bumper_center_left] = v & 0b100 > 0 ? true : false
57
- h[:light_bumper_center_right] = v & 0b1000 > 0 ? true : false
58
- h[:light_bumper_front_right] = v & 0b10000 > 0 ? true : false
59
- h[:light_bumper_right] = v & 0b100000 > 0 ? true : false
60
- h
61
- end
62
- end
63
-
64
- class WheelOvercurrents
65
- def self.convert(v)
66
- h = {}
67
- h[:side_brush] = v & 0b1 > 0 ? true : false
68
- h[:main_brush] = v & 0b100 > 0 ? true : false
69
- h[:right_wheel] = v & 0b1000 > 0 ? true : false
70
- h[:left_wheel] = v & 0b10000 > 0 ? true : false
71
- h
72
- end
73
- end
74
-
75
- class BumpsAndWheelDrops
76
- def self.convert(v)
77
- h = {}
78
- h[:bump_right] = v & 0b1 > 0 ? true : false
79
- h[:bump_left] = v & 0b10 > 0 ? true : false
80
- h[:wheel_drop_right] = v & 0b100 > 0 ? true : false
81
- h[:wheel_drop_left] = v & 0b1000 > 0 ? true : false
82
- h
83
- end
84
-
85
- end
86
-
87
- INFRARED_CHARACTER = {
88
- 129 => :left,
89
- 130 => :forward,
90
- 131 => :right,
91
- 132 => :spot,
92
- 133 => :max,
93
- 134 => :small,
94
- 135 => :medium,
95
- 136 => :large,
96
- 137 => :stop,
97
- 138 => :power,
98
- 139 => :arc_left,
99
- 140 => :arc_right,
100
- 141 => :stop,
101
- 142 => :download,
102
- 143 => :seek_dock,
103
- 160 => :reserved,
104
- 161 => :force_field,
105
- 164 => :green_buoy,
106
- 165 => :green_buoy_and_force_field,
107
- 168 => :red_buoy,
108
- 169 => :red_buoy_and_force_field,
109
- 172 => :red_and_green_buoy,
110
- 173 => :red_and_green_buoy_and_force_field,
111
- 240 => :reserved,
112
- 248 => :red_buoy,
113
- 244 => :green_buoy,
114
- 242 => :force_field,
115
- 252 => :red_and_green_buoy,
116
- 250 => :red_buoy_and_force_field,
117
- 246 => :green_buoy_and_force_field,
118
- 254 => :red_and_green_buoy_and_force_field,
119
- 162 => :virtual_wall
120
- }
121
-
122
- class InfraredCharacter
123
- def self.convert(v)
124
- INFRARED_CHARACTER[v]
125
- end
126
- end
127
- end
128
-