1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 from __future__ import with_statement
29
30 """iRobot Roomba Serial control Interface (SCI) and Turtlebot Open Interface (OI).
31
32 turtlebot.py is a fork of PyRobot.
33
34 PyRobot was originally based on openinterface.py, developed by iRobot
35 Corporation. Many of the docstrings from openinterface.py, particularly those
36 which describe the specification, are also used here. Also, docstrings may
37 contain specification text copied directly from the Roomba SCI Spec Manual and
38 the Turtlebot Open Interface specification.
39
40 Since SCI is a subset of OI, PyRobot first defines the Roomba's functionality
41 in the Roomba class and then extends that with the Turtlebot's additional
42 functionality in the Turtlebot class. In addition, since OI is built on SCI the
43 SerialCommandInterface class is also used for OI.
44
45 """
46 __author__ = "tang.tiong.yew@gmail.com (Tang Tiong Yew)"
47
48 import logging
49 import math
50 import serial
51 import struct
52 import time
53 import threading
54 import traceback
55 import rospy
56
57 import binascii
58
59
60 EDDIE_OPCODES = dict(
61 start = '',
62 baud = '',
63 control = '',
64 safe = '',
65 full = '',
66 power = '',
67 spot = '',
68 clean = '',
69 max = '',
70 drive = 'GO',
71
72 motors = 'SV',
73 leds = '',
74 song = '',
75 play = '',
76 sensors = '',
77 force_seeking_dock = '',
78
79
80 soft_reset = '',
81 low_side_drivers = '',
82 play_song = '',
83 pwm_low_side_drivers = '',
84 direct_drive = 'GO',
85
86 digital_outputs = '',
87 stream = '',
88 query_list = '',
89 pause_resume_stream = '',
90 send_ir = '',
91 script = '',
92 play_script = '',
93 show_script = '',
94 wait_time = '',
95 wait_distance = '',
96 wait_angle = '',
97 wait_event = '',
98 )
99
100 REMOTE_OPCODES = {
101
102 129: 'left',
103 130: 'forward',
104 131: 'right',
105 132: 'spot',
106 133: 'max',
107 134: 'small',
108 135: 'medium',
109 136: 'large',
110 136: 'clean',
111 137: 'pause',
112 138: 'power',
113 139: 'arc-left',
114 140: 'arc-right',
115 141: 'drive-stop',
116
117 142: 'send-all',
118 143: 'seek-dock',
119
120 240: 'reserved',
121 242: 'force-field',
122 244: 'green-buoy',
123 246: 'green-buoy-and-force-field',
124 248: 'red-buoy',
125 250: 'red-buoy-and-force-field',
126 252: 'red-buoy-and-green-buoy',
127 254: 'red-buoy-and-green-buoy-and-force-field',
128 255: 'none',
129 }
130
131 BAUD_RATES = (
132 300,
133 600,
134 1200,
135 2400,
136 4800,
137 9600,
138 14400,
139 19200,
140 28800,
141 38400,
142 57600,
143 115200)
144
145 CHARGING_STATES = (
146 'not-charging',
147 'charging-recovery',
148 'charging',
149 'trickle-charging',
150 'waiting',
151 'charging-error')
152
153 OI_MODES = (
154 'off',
155 'passive',
156 'safe',
157 'full')
158
159
160 WHEEL_DROP_CASTER = 0x10
161 WHEEL_DROP_LEFT = 0x08
162 WHEEL_DROP_RIGHT = 0x04
163 BUMP_LEFT = 0x02
164 BUMP_RIGHT = 0x01
165
166 OVERCURRENTS_DRIVE_LEFT = 0x10
167 OVERCURRENTS_DRIVE_RIGHT = 0x08
168 OVERCURRENTS_MAIN_BRUSH = 0x04
169 OVERCURRENTS_VACUUM = 0x02
170 OVERCURRENTS_SIDE_BRUSH = 0x01
171
172 BUTTON_POWER = 0x08
173 BUTTON_SPOT = 0x04
174 BUTTON_CLEAN = 0x02
175 BUTTON_MAX = 0x01
176
177 SENSOR_GROUP_PACKET_LENGTHS = {
178 0: 26,
179 1: 10,
180 2: 6,
181 3: 10,
182 4: 14,
183 5: 12,
184 6: 52,
185 100: 80 }
186
187
188 RADIUS_TURN_IN_PLACE_CW = -2000
189 RADIUS_TURN_IN_PLACE_CCW = 2000
190 RADIUS_STRAIGHT = 32768
191 RADIUS_MAX = 2000
192
193 VELOCITY_MAX = 127
194 VELOCITY_SLOW = int(VELOCITY_MAX * 0.10)
195 VELOCITY_FAST = int(VELOCITY_MAX * 0.66)
196
197 PWM_RATIO_FORWARD_MAX = 127
198 PWM_RATIO_BACKWARD_MAX = -127
199
200 MAX_WHEEL_SPEED = 70
201 WHEEL_SEPARATION = 260
202
203 SERIAL_TIMEOUT = 2
204 START_DELAY = 5
205
206 BAUDRATE = 115200
207
208 assert struct.calcsize('H') == 2, 'Expecting 2-byte shorts.'
209
212
214
215 """A higher-level wrapper around PySerial specifically designed for use with
216 Parallax Eddie Propeller Board.
217
218 """
219
221 self.ser = serial.Serial(
222 port='/dev/ttyUSB0',
223 baudrate=BAUDRATE,
224 parity=serial.PARITY_NONE,
225 stopbits=serial.STOPBITS_ONE,
226 bytesize=serial.EIGHTBITS,
227 timeout=SERIAL_TIMEOUT
228 )
229
230 self.ser.open()
231
232 self.opcodes = {}
233
234
235
236 self.lock = threading.RLock()
237
239 """wake up robot."""
240 print "wake"
241
242
243
244
245
246
247
248
249
250
252 """Add available opcodes to the SCI."""
253 self.opcodes.update(opcodes)
254
255 - def send(self, bytes):
256 """send a string of bytes to the robot."""
257 with self.lock:
258 self.ser.write(bytes)
259
260
261 - def read(self, num_bytes):
262 """Read a string of 'num_bytes' bytes from the robot."""
263
264
265 data = self.ser.read(num_bytes)
266
267
268
269
270
271 return data
272
277
279 """ InWaiting Called """
280 logging.debug('Called inWaiting')
281 self.ser.inWaiting()
282
284 """Eddiebots methods for opcodes on the fly.
285
286 Each opcode method sends the opcode optionally followed by a string of
287 parameter.
288
289 """
290
291 if name in self.opcodes:
292 def send_opcode(input_string):
293 logging.debug('sending opcode %s.' % name)
294 self.send(self.opcodes[name] + ' ' + input_string)
295 return send_opcode
296 raise AttributeError
297
300
301
302
304
305 """Represents a Eddiebot robot."""
306
308 """
309 @param sensor_class: Sensor class to use for fetching and decoding sensor data.
310 """
311 logging.basicConfig(filename='eddiebot_driver.log', level=logging.INFO)
312 self.tty = None
313 self.sci = None
314 self.safe = True
315 self.start()
316
317 - def start(self, tty='/dev/ttyUSB0', baudrate=115200):
321
323 """Start the robot's SCI interface and place it in safe or full mode."""
324 logging.info('sending control opcodes.')
325
326
327
328
329
330
331
333 """Enable or disable power to low side drivers.
334
335 'drivers' should be a list of booleans indicating which low side drivers
336 should be powered.
337
338 """
339 assert len(drivers) == 3, 'Expecting 3 low side driver power settings.'
340 byte = 0
341 for driver, power in enumerate(drivers):
342 byte += (2 ** driver) * int(power)
343
344
346 """Enable or disable digital outputs."""
347
348
350 """Do a soft reset of the Turtlebot."""
351 logging.info('sending soft reset.')
352
353
354
355
356
357
359 """Sets the baud rate in bits per second (bps) at which SCI commands and
360 data are sent according to the baud code sent in the data byte.
361
362 The default baud rate at power up is 57600 bps. (See Serial Port Settings,
363 above.) Once the baud rate is changed, it will persist until Roomba is
364 power cycled by removing the battery (or until the battery voltage falls
365 below the minimum requir''' ed for processor operation). You must wait 100ms
366 after sending this command before sending additional commands at the new
367 baud rate. The SCI must be in passive, safe, or full mode to accept this
368 command. This command puts the SCI in passive mode.
369
370 """
371 if baud_rate not in BAUD_RATES:
372 raise DriverError('Invalid baud rate specified.')
373 self.sci.baud(baud_rate)
374 self.sci = SerialCommandInterface(self.tty, baud_rate)
375
377 """Put the robot in passive mode."""
378
379 print "passive"
380
381
383
384
385
386
387 vl = int(velocity_left) & 0xffff
388 vr = int(velocity_right) & 0xffff
389
390 parameters = binascii.hexlify(struct.pack('>2H', vr, vl))
391
392 parameters_output = ''
393
394 for i in range(len(parameters)):
395 parameters_output += parameters[i]
396 if(i == 3):
397 parameters_output += ' '
398
399 self.sci.direct_drive(parameters_output + chr(13))
400
401 - def drive(self, velocity, radius):
402 logging.debug("drive(self, velocity, radius)")
403 """controls Roomba's drive wheels.
404
405 NOTE(damonkohler): The following specification applies to both the Roomba
406 and the Turtlebot.
407
408 The Roomba takes four data bytes, interpreted as two 16-bit signed values
409 using two's complement. The first two bytes specify the average velocity
410 of the drive wheels in millimeters per second (mm/s), with the high byte
411 being sent first. The next two bytes specify the radius in millimeters at
412 which Roomba will turn. The longer radii make Roomba drive straighter,
413 while the shorter radii make Roomba turn more. The radius is measured from
414 the center of the turning circle to the center of Roomba.
415
416 A drive command with a positive velocity and a positive radius makes
417 Roomba drive forward while turning toward the left. A negative radius
418 makes Roomba turn toward the right. Special cases for the radius make
419 Roomba turn in place or drive straight, as specified below. A negative
420 velocity makes Roomba drive backward.
421
422 Also see drive_straight and turn_in_place convenience methods.
423
424 """
425
426 velocity = int(velocity)
427 radius = int(radius)
428
429
430 velocity_left = ((2000.0 + radius) / 2000.0) * velocity
431 velocity_right = ((2000.0 - radius) / 2000.0) * velocity
432
433
434 if(RADIUS_STRAIGHT == radius):
435 velocity_left = velocity
436 velocity_right = velocity
437
438 self.direct_drive(velocity_left, velocity_right)
439
441 pan_degree = int(pan_degree)
442 tilt_degree = int(tilt_degree)
443 parameters = binascii.hexlify(struct.pack('>H', pan_degree))
444 self.sci.motors(' 4 ' + parameters + chr(13))
445
446
447
448
450 """Set velocity and radius to 0 to stop movement."""
451 self.direct_drive(0, 0)
452
454 """Slowly reduce the velocity to 0 to stop movement."""
455 velocities = xrange(velocity, VELOCITY_SLOW, -8)
456 if velocity < 0:
457 velocities = xrange(velocity, -VELOCITY_SLOW, 8)
458 for v in velocities:
459 self.drive(v, RADIUS_STRAIGHT)
460 time.sleep(1)
461 self.stop()
462
463
464
465
466
469
471 """Turn in place either clockwise or counter-clockwise."""
472
473
474
475 velocity = int(velocity)
476 if(direction == 'cw'):
477 self.direct_drive(-velocity, velocity)
478 if(direction == 'ccw'):
479 self.direct_drive(velocity, -velocity)
480
481
483 """Start looking for the dock and then dock."""
484
485
486
487
488
489 print "dock"
490
492 """This is a sensor command to emulate SCI sensor sending"""
493
494
495 ping_left_f = 0
496 ping_right_f = 0
497 ping_left_r = 0
498 ping_right_r = 0
499
500 ping_left_f_str = ''
501 ping_right_f_str = ''
502 ping_left_r_str = ''
503 ping_right_r_str = ''
504
505 self.sci.getSer().write('PING' + chr(13))
506 out = ''
507
508 time.sleep(0.07)
509 i = 0;
510 while self.sci.getSer().inWaiting() > 0:
511 out += self.sci.getSer().read(1)
512 if(i == 0 or i == 1 or i == 2):
513 ping_right_f_str += out[i]
514 if(i == 4 or i == 5 or i == 6):
515 ping_left_f_str += out[i]
516 if(i == 8 or i == 9 or i == 10):
517 ping_right_r_str += out[i]
518 if(i == 12 or i == 13 or i == 14):
519 ping_left_r_str += out[i]
520 i+=1
521
522 try:
523 ping_left_f = int(ping_left_f_str,16)
524 ping_right_f = int(ping_right_f_str,16)
525 ping_left_r = int(ping_left_r_str,16)
526 ping_right_r = int(ping_right_r_str,16)
527 except Exception as ex:
528 print ex
529
530
531
532
533
534
535 adc_left_f_str = ''
536 adc_right_f_str = ''
537 adc_center_f_str = ''
538 adc_left_r_str = ''
539 adc_right_r_str = ''
540 adc_center_r_str = ''
541
542
543 adc_battery_str = ''
544 self.sci.getSer().write('ADC' + chr(13))
545 out = ''
546
547 time.sleep(0.07)
548
549 i = 0;
550 while self.sci.getSer().inWaiting() > 0:
551 out += self.sci.getSer().read(1)
552 if(i == 0 or i == 1 or i == 2):
553 adc_right_f_str += out[i]
554 if(i == 4 or i == 5 or i == 6):
555 adc_center_f_str += out[i]
556 if(i == 8 or i == 9 or i == 10):
557 adc_left_f_str += out[i]
558 if(i == 12 or i == 13 or i == 14):
559 adc_right_r_str += out[i]
560 if(i == 16 or i == 17 or i == 18):
561 adc_center_r_str += out[i]
562 if(i == 20 or i == 21 or i == 22):
563 adc_left_r_str += out[i]
564
565 if(i == 28 or i == 29 or i == 30):
566 adc_battery_str += out[i]
567 i+=1
568
569 try:
570 adc_left_f = int(adc_left_f_str,16)
571 adc_right_f = int(adc_right_f_str,16)
572 adc_center_f = int(adc_center_f_str,16)
573
574 adc_left_r = int(adc_left_r_str,16)
575 adc_right_r = int(adc_right_r_str,16)
576 adc_center_r = int(adc_center_r_str,16)
577 adc_battery = int(adc_battery_str,16)
578 except Exception as ex:
579 print ex
580
581
582
583
584
585 PING_THRESHOLD = 0
586 ADC_THRESHOLD = 800
587
588 ping_left_bumps_f = False
589 ping_right_bumps_f = False
590 ping_left_bumps_r = False
591 ping_right_bumps_r = False
592
593 adc_left_bumps_f = False
594 adc_right_bumps_f = False
595 adc_center_bumps_f = False
596 adc_left_bumps_r = False
597 adc_right_bumps_r = False
598 adc_center_bumps_r = False
599
600
601
602
603
604
605
606
607
608
609 if(adc_left_f > ADC_THRESHOLD):
610 adc_left_bumps_f = True
611 if(adc_right_f > ADC_THRESHOLD + 100):
612 adc_right_bumps_f = True
613 if(adc_center_f > ADC_THRESHOLD + 100):
614 adc_center_bumps_f = True
615 if(adc_left_r > ADC_THRESHOLD + 300):
616 adc_left_bumps_r = True
617 if(adc_right_r > ADC_THRESHOLD + 300):
618 adc_right_bumps_r = True
619
620
621
622
623
624
625 return (0,
626 adc_center_bumps_f,
627 adc_left_bumps_r,
628 adc_left_bumps_f,
629 adc_right_bumps_f,
630 adc_right_bumps_r,
631 ping_left_bumps_f or ping_right_bumps_f or adc_left_bumps_f or adc_right_bumps_f or adc_center_bumps_f or ping_left_bumps_r or ping_right_bumps_r or adc_left_bumps_r or adc_right_bumps_r or adc_center_bumps_r,
632 0,
633 0,
634 0,
635 0,
636 0,
637 0,
638 0,
639 0,
640 0,
641 0,
642 0,
643 0,
644 0,
645 adc_center_f,
646 adc_left_r,
647 adc_left_f,
648 adc_right_f,
649 adc_right_r,
650 0,
651 adc_center_r,
652 0,
653 0,
654 0,
655 0,
656 0,
657 ping_left_f,
658 ping_right_f,
659 ping_left_r,
660 ping_right_r,
661 0,
662 0,
663 0,
664 0,
665 0,
666 0,
667 0,
668 0,
669 0,
670 0,
671 0,
672 0,
673 0,
674 0,
675 0,
676 0)
677
678