rumba 0.2.0 → 0.2.5
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.
- checksums.yaml +4 -4
- data/README.md +99 -0
- data/lib/rumba/constants.rb +54 -0
- data/lib/rumba/dsl.rb +72 -0
- data/lib/rumba/sensors.rb +296 -0
- data/lib/rumba.rb +26 -225
- metadata +5 -2
- data/lib/sensors.rb +0 -128
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61b352b09eef601f9b36509b7283772d366fe906
|
4
|
+
data.tar.gz: 5fd8ed4266bbe4b72ccac8a2ee5ead83217857ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
[](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
|
-
|
4
|
+
require 'rumba/constants'
|
5
|
+
require 'rumba/sensors'
|
6
|
+
require 'rumba/dsl'
|
5
7
|
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
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,
|
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
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
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.
|
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/
|
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
|
-
|