rumba 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/lib/rumba.rb +240 -11
- data/lib/sensors.rb +128 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1d712a0e9b5806c625ea88f90a7ac66d326cde2
|
4
|
+
data.tar.gz: 8119e32a631feffedfdced0645e5cfbf3aefe958
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3cb42c3be2c4d84dbfa611840eb8351b628607d486d46ccd216ee53863e1b128d8c0cf4991cfc37dbeed6b72f7fa9808fd307571fd78c3c35ae669c3de4dc44
|
7
|
+
data.tar.gz: 1db707dc89538cc544c587e2efbd00a9959d5df7a3e01405defc576a36afe96b5bdbaf66f30a6dd007953de68cf3ff4239fe2142f40c077681343003611cfd92
|
data/lib/rumba.rb
CHANGED
@@ -1,15 +1,22 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'serialport'
|
3
3
|
require 'timeout'
|
4
|
+
require_relative 'sensors.rb'
|
4
5
|
|
5
6
|
class Roomba
|
7
|
+
attr_accessor :serial
|
8
|
+
|
6
9
|
# These opcodes require no arguments
|
7
10
|
OPCODES = {
|
8
|
-
start:
|
9
|
-
control:
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
13
20
|
}
|
14
21
|
|
15
22
|
# Create a method for each opcode that writes its data.
|
@@ -26,9 +33,12 @@ class Roomba
|
|
26
33
|
|
27
34
|
# These opcodes require arguments
|
28
35
|
DRIVE = 137
|
36
|
+
MOTORS = 138
|
29
37
|
LEDS = 139
|
30
38
|
SONG = 140
|
31
39
|
PLAY_SONG = 141
|
40
|
+
SENSORS = 142
|
41
|
+
QUERY_LIST = 149
|
32
42
|
DRIVE_DIRECT = 145
|
33
43
|
|
34
44
|
# Used for making the Roomba sing!
|
@@ -38,14 +48,118 @@ class Roomba
|
|
38
48
|
'D#' => 75, 'E' => 76, 'F' => 77, 'F#' => 78, 'G' => 79, 'G#' => 80,
|
39
49
|
nil => 0
|
40
50
|
}
|
41
|
-
|
51
|
+
|
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
|
+
}
|
155
|
+
|
42
156
|
#############################################################################
|
43
157
|
# HELPERS #
|
44
158
|
#############################################################################
|
45
159
|
|
46
160
|
# Converts input data (an array) into bytes before
|
47
161
|
# sending it over the serial connection.
|
48
|
-
def write_chars(data)
|
162
|
+
def write_chars(data)
|
49
163
|
data.map! do |c|
|
50
164
|
if c.class == String
|
51
165
|
result = c.bytes.to_a.map { |b| [b].pack("C") }
|
@@ -61,7 +175,75 @@ class Roomba
|
|
61
175
|
@serial.write(data)
|
62
176
|
@serial.flush
|
63
177
|
end
|
64
|
-
|
178
|
+
|
179
|
+
# Write data then read response
|
180
|
+
def write_chars_with_read(data)
|
181
|
+
data.map! do |c|
|
182
|
+
if c.class == String
|
183
|
+
result = c.bytes.to_a.map { |b| [b].pack("C") }
|
184
|
+
else
|
185
|
+
result = [c].pack("C")
|
186
|
+
end
|
187
|
+
|
188
|
+
result
|
189
|
+
end
|
190
|
+
|
191
|
+
data = data.flatten.join
|
192
|
+
|
193
|
+
@serial.write(data)
|
194
|
+
sleep(0.1)
|
195
|
+
data=""
|
196
|
+
while(data.length==0)
|
197
|
+
data+=@serial.read
|
198
|
+
end
|
199
|
+
data
|
200
|
+
end
|
201
|
+
|
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
|
+
|
65
247
|
# Convert integer to two's complement signed 16 bit integer.
|
66
248
|
# Note that the Roomba is big-endian...I need to fix this
|
67
249
|
# code to make it portable across different architectures.
|
@@ -141,7 +323,27 @@ class Roomba
|
|
141
323
|
raise RangeError if song_number < 0 || song_number > 15
|
142
324
|
write_chars([PLAY_SONG,song_number])
|
143
325
|
end
|
144
|
-
|
326
|
+
|
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
|
+
|
145
347
|
#############################################################################
|
146
348
|
# Convenience methods #
|
147
349
|
#############################################################################
|
@@ -173,6 +375,31 @@ class Roomba
|
|
173
375
|
@serial.close
|
174
376
|
end
|
175
377
|
|
378
|
+
def battery_percentage
|
379
|
+
sensors=get_sensors(3)
|
380
|
+
((sensors[:battery_charge].to_f / sensors[:battery_capacity].to_f) * 100).to_i
|
381
|
+
end
|
382
|
+
|
383
|
+
def stop_all_motors
|
384
|
+
write_chars([MOTORS,0])
|
385
|
+
end
|
386
|
+
|
387
|
+
def start_all_motors
|
388
|
+
write_chars([MOTORS,MOTORS_MASK_SIDE_BRUSH|MOTORS_MASK_VACUUM|MOTORS_MASK_MAIN_BRUSH])
|
389
|
+
end
|
390
|
+
|
391
|
+
def start_side_brush_motor
|
392
|
+
write_chars([MOTORS,MOTORS_MASK_SIDE_BRUSH])
|
393
|
+
end
|
394
|
+
|
395
|
+
def start_vacumm_motor
|
396
|
+
write_chars([MOTORS,MOTORS_MASK_VACUUM])
|
397
|
+
end
|
398
|
+
|
399
|
+
def start_main_brush_motor
|
400
|
+
write_chars([MOTORS,MOTORS_MASK_MAIN_BRUSH])
|
401
|
+
end
|
402
|
+
|
176
403
|
def initialize(port, timeout=10)
|
177
404
|
@leds = {
|
178
405
|
advance: false,
|
@@ -184,8 +411,10 @@ class Roomba
|
|
184
411
|
@timeout = timeout
|
185
412
|
Timeout::timeout(@timeout) do
|
186
413
|
# Initialize the serialport
|
187
|
-
|
188
|
-
|
414
|
+
# 115200 for Roomba 5xx
|
415
|
+
# 57600 for older models ?
|
416
|
+
@serial = SerialPort.new(port, 115200)
|
417
|
+
@serial.read_timeout = 15
|
189
418
|
self.start
|
190
419
|
end
|
191
420
|
end
|
data/lib/sensors.rb
ADDED
@@ -0,0 +1,128 @@
|
|
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
|
+
|
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Wood
|
@@ -31,6 +31,7 @@ extensions: []
|
|
31
31
|
extra_rdoc_files: []
|
32
32
|
files:
|
33
33
|
- lib/rumba.rb
|
34
|
+
- lib/sensors.rb
|
34
35
|
homepage: http://github.com/eric-wood/roomba
|
35
36
|
licenses:
|
36
37
|
- BSD
|