├── irobot ├── __init__.py ├── openinterface │ ├── __init__.py │ ├── constants.py │ ├── commands.py │ └── response_parsers.py ├── console_interfaces │ ├── __init__.py │ └── create2.py ├── robots │ ├── __init__.py │ └── create2.py ├── tests │ ├── __init__.py │ ├── create2_test.py │ └── commands_test.py └── LICENSE.txt ├── .hgignore ├── irobot.egg-info ├── dependency_links.txt ├── top_level.txt ├── requires.txt ├── entry_points.txt ├── SOURCES.txt └── PKG-INFO ├── setup.cfg ├── setup.py └── README.rst /irobot/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /irobot/openinterface/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /irobot/console_interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | build/* 3 | -------------------------------------------------------------------------------- /irobot.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /irobot.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | irobot 2 | -------------------------------------------------------------------------------- /irobot.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | pyserial 2 | six 3 | -------------------------------------------------------------------------------- /irobot/robots/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Matthew Witherwax (lemoneer)' -------------------------------------------------------------------------------- /irobot/tests/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Matthew Witherwax (lemoneer)' 2 | -------------------------------------------------------------------------------- /irobot.egg-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | create2 = irobot.console_interfaces.create2:main 3 | 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal=1 6 | -------------------------------------------------------------------------------- /irobot.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.rst 2 | setup.cfg 3 | setup.py 4 | irobot/__init__.py 5 | irobot.egg-info/PKG-INFO 6 | irobot.egg-info/SOURCES.txt 7 | irobot.egg-info/dependency_links.txt 8 | irobot.egg-info/entry_points.txt 9 | irobot.egg-info/requires.txt 10 | irobot.egg-info/top_level.txt 11 | irobot/console_interfaces/__init__.py 12 | irobot/console_interfaces/create2.py 13 | irobot/openinterface/__init__.py 14 | irobot/openinterface/commands.py 15 | irobot/openinterface/constants.py 16 | irobot/openinterface/response_parsers.py 17 | irobot/robots/__init__.py 18 | irobot/robots/create2.py 19 | irobot/tests/__init__.py 20 | irobot/tests/commands_test.py 21 | irobot/tests/create2_test.py -------------------------------------------------------------------------------- /irobot/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Matthew Witherwax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /irobot/tests/create2_test.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Matthew Witherwax (lemoneer)' 2 | 3 | from time import sleep 4 | from six.moves import input 5 | from irobot.robots.create2 import Create2 6 | 7 | 8 | def print_properties(obj, level=1): 9 | if not hasattr(obj, '__dict__'): 10 | print('\t' * level, obj) 11 | else: 12 | for prop in [prop for prop in dir(obj) if not prop.startswith('_')]: 13 | prop_val = getattr(obj, prop) 14 | if not hasattr(prop_val, '__dict__'): 15 | print('\t' * level, prop + ':', prop_val) 16 | else: 17 | print('\t' * level, prop + ':') 18 | print_properties(prop_val, level + 1) 19 | 20 | 21 | def get_all_sensor_readings(robot): 22 | print('Reading all sensors') 23 | for p in [p for p in dir(Create2) if 24 | isinstance(getattr(Create2, p), property) and p not in ['enable_quirks', 'enable_logging', 25 | 'firmware_version']]: 26 | print(p) 27 | print_properties(getattr(robot, p)) 28 | print() 29 | sleep(.03) 30 | 31 | 32 | if __name__ == "__main__": 33 | print('Launching robots Tests') 34 | port = input('Serial Port?> ') 35 | robot = Create2(port, enable_quirks=True) 36 | get_all_sensor_readings(robot) 37 | robot.stop() 38 | -------------------------------------------------------------------------------- /irobot/tests/commands_test.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Matthew Witherwax (lemoneer)' 2 | 3 | import unittest 4 | 5 | from irobot.openinterface.commands import * 6 | 7 | 8 | def to_str(data): 9 | return '[' + '|'.join((('0x%0.2X' % b) for b in data)) + ']' 10 | 11 | 12 | class TestCommands(unittest.TestCase): 13 | def test_drive(self): 14 | cmd = drive(-200, 500) 15 | self.assertEqual(to_str(cmd), '[0x89|0xFF|0x38|0x01|0xF4]') 16 | 17 | def test_get_days(self): 18 | cmd = get_days(sun_hour=0, sun_min=0, mon_hour=0, mon_min=0, tues_hour=0, tues_min=0, wed_hour=15, wed_min=0, 19 | thurs_hour=0, thurs_min=0, fri_hour=10, fri_min=36, sat_hour=0, sat_min=0) 20 | self.assertEquals(40, cmd) 21 | 22 | def test_set_schedule(self): 23 | cmd = set_schedule(sun_hour=0, sun_min=0, mon_hour=0, mon_min=0, tues_hour=0, tues_min=0, wed_hour=15, 24 | wed_min=0, thurs_hour=0, thurs_min=0, fri_hour=10, fri_min=36, sat_hour=0, sat_min=0) 25 | self.assertEqual(to_str(cmd), 26 | '[0xA7|0x28|0x00|0x00|0x00|0x00|0x00|0x00|0x0F|0x00|0x00|0x00|0x0A|0x24|0x00|0x00]') 27 | 28 | def test_set_motors(self): 29 | cmd = set_motors(True, False, True, True, False) 30 | self.assertEqual(to_str(cmd), '[0x8A|0x0D]') 31 | 32 | def test_set_leds(self): 33 | cmd = set_leds(False, False, True, False, 0, 128) 34 | self.assertEqual(to_str(cmd), '[0x8B|0x04|0x00|0x80]') 35 | 36 | def test_set_ascii_leds(self): 37 | cmd = set_ascii_leds(65, 66, 67, 68) 38 | self.assertEqual(to_str(cmd), '[0xA4|0x41|0x42|0x43|0x44]') 39 | 40 | def test_set_song(self): 41 | cmd = set_song(0, [(31, 32), (85, 100)]) 42 | self.assertEqual(to_str(cmd), '[0x8C|0x00|0x02|0x1F|0x20|0x55|0x64]') 43 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | from os import path 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | 7 | # Get the long description from the README file 8 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 9 | long_description = f.read() 10 | 11 | setup( 12 | name='irobot', 13 | version='1.0.0b3', 14 | 15 | description='Python implementation of iRobot''s Open Interface', 16 | long_description=long_description, 17 | url='http://blog.lemoneerlabs.com', 18 | author='Matthew Witherwax (lemoneer)', 19 | author_email='mwax@lemoneerlabs.com ', 20 | 21 | # Choose your license 22 | license='MIT', 23 | 24 | classifiers=[ 25 | # How mature is this project? Common values are 26 | # 3 - Alpha 27 | # 4 - Beta 28 | # 5 - Production/Stable 29 | 'Development Status :: 4 - Beta', 30 | 31 | # Indicate who your project is intended for 32 | 'Intended Audience :: Developers', 33 | 'Topic :: Software Development', 34 | 35 | # Pick your license as you wish (should match "license" above) 36 | 'License :: OSI Approved :: MIT License', 37 | 38 | # Specify the Python versions you support here. In particular, ensure 39 | # that you indicate whether you support Python 2, Python 3 or both. 40 | 'Programming Language :: Python :: 2.7', 41 | 'Programming Language :: Python :: 3', 42 | 'Programming Language :: Python :: 3.3', 43 | 'Programming Language :: Python :: 3.4', 44 | 'Programming Language :: Python :: 3.5', 45 | ], 46 | keywords='robotics irobot roomba', 47 | packages=find_packages(), 48 | install_requires=['pyserial', 'six'], 49 | # To provide executable scripts, use entry points in preference to the 50 | # "scripts" keyword. Entry points provide cross-platform support and allow 51 | # pip to create the appropriate form of executable for the target platform. 52 | entry_points={ 53 | 'console_scripts': [ 54 | 'create2=irobot.console_interfaces.create2:main' 55 | ], 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /irobot/openinterface/constants.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Matthew Witherwax (lemoneer)' 2 | 3 | 4 | class Constant(object): 5 | def __init__(self, **kwds): 6 | self.__dict__.update(kwds) 7 | 8 | 9 | BAUD_RATE = Constant(BAUD_300=0, BAUD_600=1, BAUD_1200=2, BAUD_2400=3, BAUD_4800=4, BAUD_9600=5, BAUD_14400=6, 10 | BAUD_19200=7, BAUD_28800=8, BAUD_38400=9, BAUD_57600=10, BAUD_115200=11, DEFAULT=11) 11 | DAYS = Constant(SUNDAY=0x01, MONDAY=0x02, TUESDAY=0x04, WEDNESDAY=0x08, THURSDAY=0x10, FRIDAY=0x20, 12 | SATURDAY=0x40) 13 | DRIVE = Constant(STRAIGHT=0x8000, STRAIGHT_ALT=0x7FFF, TURN_IN_PLACE_CW=0xFFFF, TURN_IN_PLACE_CCW=0x0001) 14 | MOTORS = Constant(SIDE_BRUSH=0x01, VACUUM=0x02, MAIN_BRUSH=0x04, SIDE_BRUSH_DIRECTION=0x08, 15 | MAIN_BRUSH_DIRECTION=0x10) 16 | LEDS = Constant(DEBRIS=0x01, SPOT=0x02, DOCK=0x04, CHECK_ROBOT=0x08) 17 | WEEKDAY_LEDS = Constant(SUNDAY=0x01, MONDAY=0x02, TUESDAY=0x04, WEDNESDAY=0x08, THURSDAY=0x10, FRIDAY=0x20, 18 | SATURDAY=0x40) 19 | SCHEDULING_LEDS = Constant(COLON=0x01, PM=0x02, AM=0x04, CLOCK=0x08, SCHEDULE=0x10) 20 | RAW_LED = Constant(A=0x01, B=0x02, C=0x04, D=0x08, E=0x10, F=0x20, G=0x40) 21 | BUTTONS = Constant(CLEAN=0x01, SPOT=0x02, DOCK=0x04, MINUTE=0x08, HOUR=0x10, DAY=0x20, SCHEDULE=0x40, 22 | CLOCK=0x80) 23 | ROBOT = Constant(TICK_PER_REV=508.8, WHEEL_DIAMETER=72, WHEEL_BASE=235, 24 | TICK_TO_DISTANCE=0.44456499814949904317867595046408) 25 | MODES = Constant(OFF=0, PASSIVE=1, SAFE=2, FULL=3) 26 | WHEEL_OVERCURRENT = Constant(SIDE_BRUSH=0x01, MAIN_BRUSH=0x02, RIGHT_WHEEL=0x04, LEFT_WHEEL=0x08) 27 | BUMPS_WHEEL_DROPS = Constant(BUMP_RIGHT=0x01, BUMP_LEFT=0x02, WHEEL_DROP_RIGHT=0x04, WHEEL_DROP_LEFT=0x08) 28 | CHARGE_SOURCE = Constant(INTERNAL=0x01, HOME_BASE=0x02) 29 | LIGHT_BUMPER = Constant(LEFT=0x01, FRONT_LEFT=0x02, CENTER_LEFT=0x04, CENTER_RIGHT=0x08, FRONT_RIGHT=0x10, 30 | RIGHT=0x20) 31 | STASIS = Constant(TOGGLING=0x01, DISABLED=0x02) 32 | 33 | POWER_SAVE_TIME = 300 # seconds 34 | 35 | RESPONSE_SIZES = {0: 26, 1: 10, 2: 6, 3: 10, 4: 14, 5: 12, 6: 52, 36 | # actual sensors 37 | 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 2, 20: 2, 21: 1, 38 | 22: 2, 23: 2, 24: 1, 25: 2, 26: 2, 27: 2, 28: 2, 29: 2, 30: 2, 31: 2, 32: 3, 33: 3, 34: 1, 35: 1, 39 | 36: 1, 37: 1, 38: 1, 39: 2, 40: 2, 41: 2, 42: 2, 43: 2, 44: 2, 45: 1, 46: 2, 47: 2, 48: 2, 49: 2, 40 | 50: 2, 51: 2, 52: 1, 53: 1, 54: 2, 55: 2, 56: 2, 57: 2, 58: 1, 41 | # end actual sensors 42 | 100: 80, 101: 28, 106: 12, 107: 9} 43 | -------------------------------------------------------------------------------- /irobot/console_interfaces/create2.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | __author__ = 'Matthew Witherwax (lemoneer)' 3 | 4 | import logging 5 | from sys import stdout 6 | from re import match 7 | from six.moves import input 8 | from irobot.robots.create2 import Create2, RobotConnectionError, ModeChangeError 9 | from irobot.openinterface.constants import MODES, RAW_LED 10 | 11 | FIRMWARE_PREFIX = '^r3[_-]robot\/tags\/release-' 12 | FIRMWARE_PREFIX_LENGTH = 22 13 | 14 | 15 | def check_for_quirks(robot): 16 | 17 | print('Checking firmware for quirks') 18 | print('Restarting Robot...') 19 | boot_message = robot.firmware_version.split(u'\r\n') 20 | 21 | for line in boot_message: 22 | if match(FIRMWARE_PREFIX, line): 23 | print('Found firmware:', line) 24 | line = line[FIRMWARE_PREFIX_LENGTH:] 25 | if ':' in line: 26 | line = line.split(':')[0] 27 | major, minor, point = line.split('.') 28 | if int(major) < 3 or (int(major) == 3 and int(minor) < 3): 29 | print('Enabling quirks') 30 | robot.enable_quirks = True 31 | else: 32 | print('No known quirks') 33 | robot.enable_quirks = False 34 | print() 35 | return 36 | 37 | print('Could not determine firmware') 38 | print('Enabling quirks for safety') 39 | robot.enable_quirks = True 40 | 41 | 42 | def _configure_logger(): 43 | class Formatter(logging.Formatter): 44 | def __init__(self, fmt): 45 | logging.Formatter.__init__(self, fmt) 46 | 47 | def format(self, record): 48 | msg = logging.Formatter.format(self, record) 49 | lines = msg.split('\n') 50 | return '{0}\n{1}'.format( 51 | lines[0], 52 | '\n'.join(['\t{0}'.format(line) for line in lines[1:]])) 53 | 54 | logger = logging.getLogger('Create2') 55 | logger.setLevel(logging.INFO) 56 | ch = logging.StreamHandler(stdout) 57 | ch.setLevel(logging.INFO) 58 | ch.setFormatter(Formatter('%(levelname)s\n%(message)s')) 59 | logger.addHandler(ch) 60 | logger.disabled = True 61 | 62 | return logger 63 | 64 | 65 | def main(): 66 | import code 67 | import atexit 68 | import os 69 | import readline 70 | import rlcompleter 71 | 72 | historyPath = os.path.expanduser("~/.pyhistory") 73 | 74 | def save_history(historyPath=historyPath): 75 | import readline 76 | readline.write_history_file(historyPath) 77 | 78 | if os.path.exists(historyPath): 79 | readline.read_history_file(historyPath) 80 | 81 | atexit.register(save_history) 82 | del os, atexit, readline, rlcompleter, save_history, historyPath 83 | 84 | print('Launching REPL') 85 | port = input('Serial Port> ') 86 | brc_pin = int(input('BRC Pin> ')) 87 | print() 88 | 89 | # give the user a way out before we launch into interactive mode 90 | if port.lower() == 'quit()': 91 | return 92 | try: 93 | robot = Create2(port,brc_pin) 94 | except RobotConnectionError as e: 95 | print(e, '\nInner Exception:', e.__cause__) 96 | return 97 | except ModeChangeError: 98 | print('Failed to enter Passive mode') 99 | return 100 | 101 | # add methods to turn logging to console on/off 102 | logger = _configure_logger() 103 | 104 | def enable_logging(): 105 | logger.disabled = False 106 | 107 | def disable_logging(): 108 | logger.disabled = True 109 | 110 | # determine if we need to handle the issue with angle/distance 111 | check_for_quirks(robot) 112 | 113 | # add our goodies to the scope 114 | # I hope the Python powers don't come to get me foe doing this 115 | vars = globals().copy() 116 | vars.update(locals()) 117 | # punch user out to a repl 118 | shell = code.InteractiveConsole(vars) 119 | shell.interact(banner='Create2 attached as robot on {0}\nMay it serve you well'.format(port)) 120 | 121 | if __name__ == "__main__": 122 | main() 123 | -------------------------------------------------------------------------------- /irobot/openinterface/commands.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Matthew Witherwax (lemoneer)' 2 | 3 | from struct import Struct, pack 4 | 5 | from .constants import DAYS, MOTORS, LEDS, BUTTONS, DRIVE, WEEKDAY_LEDS, SCHEDULING_LEDS 6 | 7 | pack_op_code = Struct('B').pack 8 | # note all the packs below include a leading byte for the op code 9 | # for instance, pack_signed_byte actually packs two bytes - the 10 | # op code and the data byte 11 | pack_signed_byte = Struct('Bb').pack 12 | pack_unsigned_byte = Struct('B' * 2).pack 13 | pack_2unsigned_bytes = Struct('B' * 3).pack 14 | pack_3signed_bytes = Struct('B' + 'b' * 3).pack 15 | pack_3unsigned_bytes = Struct('B' * 4).pack 16 | pack_4unsigned_bytes = Struct('B' * 5).pack 17 | pack_schedule = Struct('B' + 'b' * 15).pack 18 | pack_drive = Struct('>Bhh').pack 19 | pack_drive_special_cases = Struct('>BhH').pack 20 | 21 | 22 | def start(): 23 | return pack_op_code(128) 24 | 25 | 26 | def reset(): 27 | return pack_op_code(7) 28 | 29 | 30 | def stop(): 31 | return pack_op_code(173) 32 | 33 | 34 | def set_baud(baud): 35 | return pack_signed_byte(129, baud) 36 | 37 | 38 | set_mode_passive = start 39 | 40 | 41 | def set_mode_safe(): 42 | return pack_op_code(131) 43 | 44 | 45 | def set_mode_full(): 46 | return pack_op_code(132) 47 | 48 | 49 | def clean(): 50 | return pack_op_code(135) 51 | 52 | 53 | def clean_max(): 54 | return pack_op_code(136) 55 | 56 | 57 | def clean_spot(): 58 | return pack_op_code(134) 59 | 60 | 61 | def seek_dock(): 62 | return pack_op_code(143) 63 | 64 | 65 | def power_down(): 66 | return pack_op_code(133) 67 | 68 | 69 | def get_days(sun_hour, sun_min, mon_hour, mon_min, tues_hour, tues_min, wed_hour, wed_min, thurs_hour, thurs_min, 70 | fri_hour, fri_min, sat_hour, sat_min): 71 | days = 0 72 | if sun_hour != 0 or sun_min != 0: 73 | days |= DAYS.SUNDAY 74 | if mon_hour != 0 or mon_min != 0: 75 | days |= DAYS.MONDAY 76 | if tues_hour != 0 or tues_min != 0: 77 | days |= DAYS.TUESDAY 78 | if wed_hour != 0 or wed_min != 0: 79 | days |= DAYS.WEDNESDAY 80 | if thurs_hour != 0 or thurs_min != 0: 81 | days |= DAYS.THURSDAY 82 | if fri_hour != 0 or fri_min != 0: 83 | days |= DAYS.FRIDAY 84 | if sat_hour != 0 or sat_min != 0: 85 | days |= DAYS.SATURDAY 86 | return days 87 | 88 | 89 | def set_schedule(sun_hour, sun_min, mon_hour, mon_min, tues_hour, tues_min, wed_hour, wed_min, thurs_hour, thurs_min, 90 | fri_hour, fri_min, sat_hour, sat_min): 91 | days = get_days(sun_hour, sun_min, mon_hour, mon_min, tues_hour, tues_min, wed_hour, wed_min, thurs_hour, thurs_min, 92 | fri_hour, fri_min, sat_hour, sat_min) 93 | return pack_schedule(167, days, sun_hour, sun_min, mon_hour, mon_min, tues_hour, tues_min, wed_hour, wed_min, 94 | thurs_hour, thurs_min, fri_hour, fri_min, sat_hour, sat_min) 95 | 96 | 97 | def set_day_time(day=0, hour=0, minute=0): 98 | return pack_3signed_bytes(168, day, hour, minute) 99 | 100 | 101 | def drive(velocity, radius): 102 | if radius == DRIVE.STRAIGHT or radius == DRIVE.TURN_IN_PLACE_CW or radius == DRIVE.TURN_IN_PLACE_CCW: 103 | return pack_drive_special_cases(137, velocity, radius) 104 | return pack_drive(137, velocity, radius) 105 | 106 | 107 | def drive_direct(right_velocity, left_velocity): 108 | return pack_drive(145, right_velocity, left_velocity) 109 | 110 | 111 | def drive_pwm(right_pwm, left_pwm): 112 | return pack_drive(146, right_pwm, left_pwm) 113 | 114 | 115 | def set_motors(main_brush_on, main_brush_reverse, side_brush, side_brush_reverse, vacuum): 116 | motors = 0 117 | if main_brush_on: 118 | motors |= MOTORS.MAIN_BRUSH 119 | if main_brush_reverse: 120 | motors |= MOTORS.MAIN_BRUSH_DIRECTION 121 | if side_brush: 122 | motors |= MOTORS.SIDE_BRUSH 123 | if side_brush_reverse: 124 | motors |= MOTORS.SIDE_BRUSH_DIRECTION 125 | if vacuum: 126 | motors |= MOTORS.VACUUM 127 | 128 | return pack_signed_byte(138, motors) 129 | 130 | 131 | def set_motors_pwm(main_brush_pwm, side_brush_pwm, vacuum_pwm): 132 | return pack_3signed_bytes(144, main_brush_pwm, side_brush_pwm, vacuum_pwm) 133 | 134 | 135 | def set_leds(debris, spot, dock, check_robot, power_color, power_intensity): 136 | leds = 0 137 | if debris: 138 | leds |= LEDS.DEBRIS 139 | if spot: 140 | leds |= LEDS.SPOT 141 | if dock: 142 | leds |= LEDS.DOCK 143 | if check_robot: 144 | leds |= LEDS.CHECK_ROBOT 145 | 146 | return pack_3unsigned_bytes(139, leds, power_color, power_intensity) 147 | 148 | 149 | def set_scheduling_leds(sun, mon, tues, wed, thurs, fri, sat, schedule, clock, am, pm, colon): 150 | weekday_leds = 0 151 | if sun: 152 | weekday_leds |= WEEKDAY_LEDS.SUNDAY 153 | if mon: 154 | weekday_leds |= WEEKDAY_LEDS.MONDAY 155 | if tues: 156 | weekday_leds |= WEEKDAY_LEDS.TUESDAY 157 | if wed: 158 | weekday_leds |= WEEKDAY_LEDS.WEDNESDAY 159 | if thurs: 160 | weekday_leds |= WEEKDAY_LEDS.THURSDAY 161 | if fri: 162 | weekday_leds |= WEEKDAY_LEDS.FRIDAY 163 | if sat: 164 | weekday_leds |= WEEKDAY_LEDS.SATURDAY 165 | 166 | scheduling_leds = 0 167 | if schedule: 168 | scheduling_leds |= SCHEDULING_LEDS.SCHEDULE 169 | if clock: 170 | scheduling_leds |= SCHEDULING_LEDS.CLOCK 171 | if am: 172 | scheduling_leds |= SCHEDULING_LEDS.AM 173 | if pm: 174 | scheduling_leds |= SCHEDULING_LEDS.PM 175 | if colon: 176 | scheduling_leds |= SCHEDULING_LEDS.COLON 177 | 178 | return pack_2unsigned_bytes(162, weekday_leds, scheduling_leds) 179 | 180 | 181 | def set_raw_leds(digit1, digit2, digit3, digit4): 182 | return pack_4unsigned_bytes(163, digit4, digit3, digit2, digit1) 183 | 184 | 185 | def trigger_buttons(clean, spot, dock, minute, hour, day, schedule, clock): 186 | buttons = 0 187 | if clean: 188 | buttons |= BUTTONS.CLEAN 189 | if spot: 190 | buttons |= BUTTONS.SPOT 191 | if dock: 192 | buttons |= BUTTONS.DOCK 193 | if minute: 194 | buttons |= BUTTONS.MINUTE 195 | if hour: 196 | buttons |= BUTTONS.HOUR 197 | if day: 198 | buttons |= BUTTONS.DAY 199 | if schedule: 200 | buttons |= BUTTONS.SCHEDULE 201 | if clock: 202 | buttons |= BUTTONS.CLOCK 203 | 204 | return pack_unsigned_byte(165, buttons) 205 | 206 | 207 | def set_ascii_leds(char1, char2, char3, char4): 208 | return pack_4unsigned_bytes(164, char1, char2, char3, char4) 209 | 210 | 211 | def set_song(song_number, notes): 212 | num_notes = len(notes) 213 | 214 | note_data = [0] * (3 + num_notes * 2) 215 | note_data[0] = 140 216 | note_data[1] = song_number 217 | note_data[2] = num_notes 218 | 219 | for i in range(0, num_notes): 220 | note = notes[i] 221 | note_idx = i * 2 222 | note_data[note_idx + 3] = note[0] 223 | note_data[note_idx + 4] = note[1] 224 | 225 | return pack('%sB' % len(note_data), *note_data) 226 | 227 | 228 | def play_song(song_number): 229 | return pack_unsigned_byte(141, song_number) 230 | 231 | 232 | def request_sensor_data(packet_id): 233 | return pack_unsigned_byte(142, packet_id) 234 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | iRobot 3 | ====== 4 | 5 | A Python implementation of the iRobot Open Interface. 6 | 7 | | 8 | 9 | Main Features 10 | ------------- 11 | * Provides a low level interface for building Open Interface commands to send over a serial connection 12 | * Provides a low level set of response parsers to read data returned from robots 13 | * Provides a high level object oriented Interface for the Create2 14 | * Provides a REPL to interactively control a Create2 robot over a serial connection 15 | | 16 | Code Examples 17 | ------------- 18 | **Low Level Interface**:: 19 | 20 | from irobot.openinterface.commands import set_mode_full, request_sensor_data # or other commands 21 | from irobot.openinterface.constants import RESPONSE_SIZES 22 | from irobot.openinterface.response_parsers import unsigned_byte_response 23 | # open a serial connection 24 | # assumed to be serial here 25 | # change mode 26 | serial.write(set_mode_full()) 27 | # read oi mode 28 | serial.write(request_sensor_data(35)) 29 | unsigned_byte_response(serial.read(RESPONSE_SIZES[35])) 30 | 31 | **High Level Object Oriented Interface for the Create2**:: 32 | 33 | from irobot.robots.create2 import Create2 34 | from irobot.openinterface.constants import MODES 35 | # instantiate robot 36 | robot = Create2(port) 37 | # read sensor 38 | print(robot.left_encoder_counts) 39 | # change mode to drive (SAFE|FULL) 40 | robot.oi_mode = MODES.SAFE 41 | robot.drive_straight(50) 42 | # stop driving 43 | robot.drive_straight(0) 44 | # return to passive mode 45 | robot.oi_mode = MODES.PASSIVE 46 | # shutdown OI 47 | robot.stop() 48 | 49 | **REPL** 50 | 51 | | Launch ``console_interfaces.create2`` (this should be callable as Create2 from the command line after installation) 52 | | In the REPL, the robot is bound to the ``robot`` variable 53 | | Issue Python commands as you see fit 54 | | To see what data is being sent/received enable logging 55 | | ``enable_logging()`` 56 | | To turn off 57 | | ``disable_logging()`` 58 | 59 | Example: 60 | 61 | :: 62 | 63 | >>> enable_logging() 64 | >>> robot.sensor_group0 65 | INFO 66 | Last command sent 19.53 seconds ago 67 | Sending Command 68 | Decimal: [142|0] 69 | Hex: [8E|0] 70 | Bin: [10001110|00000000] 71 | INFO 72 | Received 73 | Decimal: [3|0|1|1|1|1|0|0|0|0|0|0|0|0|0|0|2|67|20|4|101|38|10|136|10|136] 74 | Hex: [3|0|1|1|1|1|0|0|0|0|0|0|0|0|0|0|2|43|14|4|65|26|A|88|A|88] 75 | Bin: [00000011|00000000|00000001|00000001|00000001|00000001|00000000| ... |10001000|00001010|10001000] 76 | | 77 | API Reference 78 | ------------- 79 | The commands and response parsers in ``openinterface`` provide all the primitives needed to issue and retrieve the response for any command in the Roomba Open Interface Spec.:: 80 | 81 | openinterface.commands 82 | 83 | low level support for packing data to send over a serial connection 84 | 85 | pack_op_code 86 | pack_signed_byte 87 | pack_unsigned_byte 88 | pack_2unsigned_bytes 89 | pack_3signed_bytes 90 | pack_3unsigned_bytes 91 | pack_4unsigned_bytes 92 | pack_schedule 93 | pack_drive 94 | pack_drive_special_cases 95 | 96 | functions for building data packets for each command 97 | 98 | start 99 | reset 100 | stop 101 | set_baud 102 | set_mode_passive 103 | set_mode_safe 104 | set_mode_full 105 | clean 106 | clean_max 107 | clean_spot 108 | seek_dock 109 | power_down 110 | get_days 111 | set_schedule 112 | set_day_time 113 | drive 114 | drive_direct 115 | drive_pwm 116 | set_motors 117 | set_motors_pwm 118 | set_leds 119 | set_scheduling_leds 120 | set_raw_leds 121 | trigger_buttons 122 | set_ascii_leds 123 | set_song 124 | play_song 125 | request_sensor_data 126 | 127 | :: 128 | 129 | openinterface.response_parsers 130 | 131 | low level support for unpacking data received over a serial connection 132 | 133 | binary_response 134 | packed_binary_response 135 | byte_response 136 | unsigned_byte_response 137 | short_response 138 | unsigned_short_response 139 | 140 | classes to extract composite responses 141 | 142 | PackedBinaryData Ex: BumpsAndWheelDrop 143 | BumpsAndWheelDrop bump_right 144 | WheelOvercurrents bump_left 145 | Buttons wheel_drop_right 146 | ChargingSources wheel_drop_left 147 | LightBumper 148 | Stasis 149 | SensorGroup0 150 | SensorGroup1 151 | SensorGroup2 152 | SensorGroup3 153 | SensorGroup4 154 | SensorGroup5 155 | SensorGroup6 156 | SensorGroup100 157 | SensorGroup101 158 | SensorGroup106 159 | SensorGroup107 160 | 161 | :: 162 | 163 | openinterface.constants 164 | 165 | named value used in the spec used as parameters to functions and range checking 166 | 167 | BAUD_RATE 168 | DAYS 169 | DRIVE 170 | MOTORS 171 | LEDS 172 | WEEKDAY_LEDS 173 | SCHEDULING_LEDS 174 | RAW_LED 175 | BUTTONS 176 | ROBOT 177 | MODES 178 | WHEEL_OVERCURRENT 179 | BUMPS_WHEEL_DROPS 180 | CHARGE_SOURCE 181 | LIGHT_BUMPER 182 | STASIS 183 | POWER_SAVE_TIME 184 | RESPONSE_SIZES 185 | 186 | 187 | The class ``Create2`` in ``robots.create2`` is built upon the primitives in ``openinterface`` and provides niceties like management of the serial connection. All sensors are exposed as properties of the ``Create2`` class while actions are implemented as methods. 188 | 189 | :: 190 | 191 | robots.create2 192 | methods 193 | wake 194 | start 195 | reset 196 | stop 197 | set_baud 198 | clean 199 | clean_max 200 | clean_spot 201 | seek_dock 202 | power_down 203 | set_schedule 204 | clear_schedule 205 | set_day_time 206 | drive 207 | drive_straight 208 | spin_left 209 | spin_right 210 | drive_direct 211 | drive_pwm 212 | set_motors 213 | set_motors_pwm 214 | set_leds 215 | set_scheduling_leds 216 | set_raw_leds 217 | set_ascii_leds 218 | trigger_buttons 219 | set_song 220 | play_song 221 | 222 | properties 223 | enable_quirks 224 | auto_wake 225 | bumps_and_wheel_drops 226 | wall_sensor 227 | cliff_left 228 | cliff_front_left 229 | cliff_front_right 230 | cliff_right 231 | virtual_wall 232 | wheel_overcurrents 233 | dirt_detect 234 | ir_char_omni 235 | ir_char_left 236 | ir_char_right 237 | buttons 238 | distance 239 | angle 240 | charging_state 241 | voltage 242 | current 243 | temperature 244 | battery_charge 245 | battery_capacity 246 | wall_signal 247 | cliff_left_signal 248 | cliff_front_left_signal 249 | cliff_front_right_signal 250 | cliff_right_signal 251 | charging_sources 252 | oi_mode 253 | song_number 254 | is_song_playing 255 | number_stream_packets 256 | requested_velocity 257 | requested_radius 258 | requested_right_velocity 259 | requested_left_velocity 260 | left_encoder_counts 261 | right_encoder_counts 262 | light_bumper 263 | light_bump_left_signal 264 | light_bump_front_left_signal 265 | light_bump_center_left_signal 266 | light_bump_center_right_signal 267 | light_bump_front_right_signal 268 | light_bump_right_signal 269 | left_motor_current 270 | right_motor_current 271 | main_brush_motor_current 272 | side_brush_motor_current 273 | stasis 274 | sensor_group0 275 | sensor_group1 276 | sensor_group2 277 | sensor_group3 278 | sensor_group4 279 | sensor_group5 280 | sensor_group6 281 | sensor_group100 282 | sensor_group101 283 | sensor_group106 284 | sensor_group107 285 | firmware_version 286 | 287 | The ``Create2`` class also provides the following features not explicitly provided in the spec: 288 | 289 | * auto_wake - the Open Interface goes to sleep after 5 minutes of inactivity when in Passive mode. With this property set to True, the ``Create2`` object will track idle time when in Passive mode and automatically wake the robot when a command is issued if necessary. Enabled by default in the constructor. *wake maybe be called any time with wake()* 290 | * enable_quirks - Roomba 500/600 firmware versions prior to 3.3.0 return an incorrect value for distance and angle. With this property set to True, the properties ``distance`` and ``angle`` will use the encoder counts to determine the correct value. This only works for the ``distance`` and ``angle`` properties. Distance and angle in the *sensor groups* will still report the wrong value. 291 | * firmware_version - a property of the ``Create2`` class that gets the welcome message in order to determine the firmware version. Reading this property will reset the robot and will take approximately 5 seconds to complete. To see this used to automatically determine if ``enable_quirks`` should be set, please see ``check_for_quirks`` in ``console_interfaces.create2``. 292 | 293 | 294 | Please see the `iRobot Roomba Open Interface Spec `_ for a listing of all commands and their purposes. 295 | 296 | | 297 | 298 | Changelog 299 | --------- 300 | | irobot-1.0.0b1 301 | - Initial release 302 | | 303 | | irobot-1.0.0b2 304 | - Bugfix: Improperly set baud rate on serial connection preventing the library from working under Linux. 305 | | 306 | | irobot-1.0.0b3 307 | - Bugfix: Wrong op code for seek_dock 308 | - Bugfix: Use of Python 2.7 incompatible version of super() 309 | 310 | | 311 | 312 | Installation 313 | ------------ 314 | | This is beta software. It has been tested under Pyhon 2.7 and 3.x under Windows 8 and Python 3.x under Debain GNU/Linux 8 (jessie) 64 bit. 315 | | 316 | | Download the package `irobot-1.0.0b3.tar.gz `_ 317 | | 318 | | Install with pip 319 | | ``pip install [path to file]`` 320 | | 321 | 322 | Linux notes: 323 | 324 | * In order to use the Create Cable on ``/dev/ttyUSB0`` I had to 325 | - remove modemmanager (apparently is takes possession of ``/dev/ttyUSB0``) 326 | - add myself to ``dialout`` with ``sudo adduser [username] dialout`` 327 | | 328 | 329 | Tests 330 | ----- 331 | | Unit tests for verifying some of the command builders may be found in ``tests.commands_test`` 332 | | A test script to connect to a Create2 over a serial connection and exercise all read commands maybe found in ``tests.create2_test`` 333 | 334 | | 335 | Known Issues/Notes 336 | ------------------ 337 | * Issues 338 | - set_raw_leds does not presently behave as detailed in the spec. The issue has been reported to the manufacturer. 339 | - The orange and green wires are swapped on the official Create Cables preventing the robot form waking. You will need to create your own cable in order to use the ``auto_wake`` feature. 340 | * Notes 341 | - the arguments to ``set_raw_leds`` are integers that should be composed by ORing the segments you wish to turn on. Example: ``set_raw_leds(RAW_LED.A | RAW_LED.B | RAW_LED.C)`` to turn on segments A, B, and C of the first display. 342 | - the notes argument to ``set_song`` is a list of tuples where each tuple is a note and a duration. Eample: ``set_song(0, [(57,32),(59,32),(60,32)])`` to create a song as song0 consisting of the notes A, B, and C played for .5 seconds each. 343 | 344 | Author 345 | ------ 346 | `Matthew Witherwax (lemoneer) `_ 347 | 348 | | 349 | 350 | License 351 | ------- 352 | :: 353 | 354 | MIT License 355 | 356 | Copyright (c) 2016 Matthew Witherwax 357 | 358 | Permission is hereby granted, free of charge, to any person obtaining a copy 359 | of this software and associated documentation files (the "Software"), to deal 360 | in the Software without restriction, including without limitation the rights 361 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 362 | copies of the Software, and to permit persons to whom the Software is 363 | furnished to do so, subject to the following conditions: 364 | 365 | The above copyright notice and this permission notice shall be included in all 366 | copies or substantial portions of the Software. 367 | 368 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 369 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 370 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 371 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 372 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 373 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 374 | SOFTWARE. -------------------------------------------------------------------------------- /irobot.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: irobot 3 | Version: 1.0.0b3 4 | Summary: Python implementation of iRobots Open Interface 5 | Home-page: http://blog.lemoneerlabs.com 6 | Author: Matthew Witherwax (lemoneer) 7 | Author-email: mwax@lemoneerlabs.com 8 | License: MIT 9 | Description: ====== 10 | iRobot 11 | ====== 12 | 13 | A Python implementation of the iRobot Open Interface. 14 | 15 | Main Features 16 | ------------- 17 | * Provides a low level interface for building Open Interface commands to send over a serial connection 18 | * Provides a low level set of response parsers to read data returned from robots 19 | * Provides a high level object oriented Interface for the Create2 20 | * Provides a REPL to interactively control a Create2 robot over a serial connection 21 | 22 | Code Examples 23 | ------------- 24 | **Low Level Interface**:: 25 | 26 | from irobot.openinterface.commands import set_mode_full, request_sensor_data # or other commands 27 | from irobot.openinterface.constants import RESPONSE_SIZES 28 | from irobot.openinterface.response_parsers import unsigned_byte_response 29 | # open a serial connection 30 | # assumed to be serial here 31 | # change mode 32 | serial.write(set_mode_full()) 33 | # read oi mode 34 | serial.write(request_sensor_data(35)) 35 | unsigned_byte_response(serial.read(RESPONSE_SIZES[35])) 36 | 37 | **High Level Object Oriented Interface for the Create2**:: 38 | 39 | from irobot.robots.create2 import Create2 40 | from irobot.openinterface.constants import MODES 41 | # instantiate robot 42 | robot = Create2(port) 43 | # read sensor 44 | print(robot.left_encoder_counts) 45 | # change mode to drive (SAFE|FULL) 46 | robot.oi_mode = MODES.SAFE 47 | robot.drive_straight(50) 48 | # stop driving 49 | robot.drive_straight(0) 50 | # return to passive mode 51 | robot.oi_mode = MODES.PASSIVE 52 | # shutdown OI 53 | robot.stop() 54 | 55 | | **REPL** 56 | | 57 | | Launch ``console_interfaces.create2`` (this should be callable as Create2 from the command line after installation) 58 | | In the REPL, the robot is bound to the ``robot`` variable 59 | | Issue Python commands as you see fit 60 | | To see what data is being sent/received enable logging 61 | | ``enable_logging()`` 62 | | To turn off 63 | | ``disable_logging()`` 64 | 65 | Example: 66 | 67 | :: 68 | 69 | >>> enable_logging() 70 | >>> robot.sensor_group0 71 | INFO 72 | Last command sent 19.53 seconds ago 73 | Sending Command 74 | Decimal: [142|0] 75 | Hex: [8E|0] 76 | Bin: [10001110|00000000] 77 | INFO 78 | Received 79 | Decimal: [3|0|1|1|1|1|0|0|0|0|0|0|0|0|0|0|2|67|20|4|101|38|10|136|10|136] 80 | Hex: [3|0|1|1|1|1|0|0|0|0|0|0|0|0|0|0|2|43|14|4|65|26|A|88|A|88] 81 | Bin: [00000011|00000000|00000001|00000001|00000001|00000001|00000000| ... |10001000|00001010|10001000] 82 | 83 | API Reference 84 | ------------- 85 | The commands and response parsers in ``openinterface`` provide all the primitives needed to issue and retrieve the response for any command in the Roomba Open Interface Spec.:: 86 | 87 | openinterface.commands 88 | 89 | low level support for packing data to send over a serial connection 90 | 91 | pack_op_code 92 | pack_signed_byte 93 | pack_unsigned_byte 94 | pack_2unsigned_bytes 95 | pack_3signed_bytes 96 | pack_3unsigned_bytes 97 | pack_4unsigned_bytes 98 | pack_schedule 99 | pack_drive 100 | pack_drive_special_cases 101 | 102 | functions for building data packets for each command 103 | 104 | start 105 | reset 106 | stop 107 | set_baud 108 | set_mode_passive 109 | set_mode_safe 110 | set_mode_full 111 | clean 112 | clean_max 113 | clean_spot 114 | seek_dock 115 | power_down 116 | get_days 117 | set_schedule 118 | set_day_time 119 | drive 120 | drive_direct 121 | drive_pwm 122 | set_motors 123 | set_motors_pwm 124 | set_leds 125 | set_scheduling_leds 126 | set_raw_leds 127 | trigger_buttons 128 | set_ascii_leds 129 | set_song 130 | play_song 131 | request_sensor_data 132 | 133 | :: 134 | 135 | openinterface.response_parsers 136 | 137 | low level support for unpacking data received over a serial connection 138 | 139 | binary_response 140 | packed_binary_response 141 | byte_response 142 | unsigned_byte_response 143 | short_response 144 | unsigned_short_response 145 | 146 | classes to extract composite responses 147 | 148 | PackedBinaryData Ex: BumpsAndWheelDrop 149 | BumpsAndWheelDrop bump_right 150 | WheelOvercurrents bump_left 151 | Buttons wheel_drop_right 152 | ChargingSources wheel_drop_left 153 | LightBumper 154 | Stasis 155 | SensorGroup0 156 | SensorGroup1 157 | SensorGroup2 158 | SensorGroup3 159 | SensorGroup4 160 | SensorGroup5 161 | SensorGroup6 162 | SensorGroup100 163 | SensorGroup101 164 | SensorGroup106 165 | SensorGroup107 166 | 167 | :: 168 | 169 | openinterface.constants 170 | 171 | named value used in the spec used as parameters to functions and range checking 172 | 173 | BAUD_RATE 174 | DAYS 175 | DRIVE 176 | MOTORS 177 | LEDS 178 | WEEKDAY_LEDS 179 | SCHEDULING_LEDS 180 | RAW_LED 181 | BUTTONS 182 | ROBOT 183 | MODES 184 | WHEEL_OVERCURRENT 185 | BUMPS_WHEEL_DROPS 186 | CHARGE_SOURCE 187 | LIGHT_BUMPER 188 | STASIS 189 | POWER_SAVE_TIME 190 | RESPONSE_SIZES 191 | 192 | 193 | The class ``Create2`` in ``robots.create2`` is built upon the primitives in ``openinterface`` and provides niceties like management of the serial connection. All sensors are exposed as properties of the ``Create2`` class while actions are implemented as methods. 194 | 195 | :: 196 | 197 | robots.create2 198 | methods 199 | wake 200 | start 201 | reset 202 | stop 203 | set_baud 204 | clean 205 | clean_max 206 | clean_spot 207 | seek_dock 208 | power_down 209 | set_schedule 210 | clear_schedule 211 | set_day_time 212 | drive 213 | drive_straight 214 | spin_left 215 | spin_right 216 | drive_direct 217 | drive_pwm 218 | set_motors 219 | set_motors_pwm 220 | set_leds 221 | set_scheduling_leds 222 | set_raw_leds 223 | set_ascii_leds 224 | trigger_buttons 225 | set_song 226 | play_song 227 | 228 | properties 229 | enable_quirks 230 | auto_wake 231 | bumps_and_wheel_drops 232 | wall_sensor 233 | cliff_left 234 | cliff_front_left 235 | cliff_front_right 236 | cliff_right 237 | virtual_wall 238 | wheel_overcurrents 239 | dirt_detect 240 | ir_char_omni 241 | ir_char_left 242 | ir_char_right 243 | buttons 244 | distance 245 | angle 246 | charging_state 247 | voltage 248 | current 249 | temperature 250 | battery_charge 251 | battery_capacity 252 | wall_signal 253 | cliff_left_signal 254 | cliff_front_left_signal 255 | cliff_front_right_signal 256 | cliff_right_signal 257 | charging_sources 258 | oi_mode 259 | song_number 260 | is_song_playing 261 | number_stream_packets 262 | requested_velocity 263 | requested_radius 264 | requested_right_velocity 265 | requested_left_velocity 266 | left_encoder_counts 267 | right_encoder_counts 268 | light_bumper 269 | light_bump_left_signal 270 | light_bump_front_left_signal 271 | light_bump_center_left_signal 272 | light_bump_center_right_signal 273 | light_bump_front_right_signal 274 | light_bump_right_signal 275 | left_motor_current 276 | right_motor_current 277 | main_brush_motor_current 278 | side_brush_motor_current 279 | stasis 280 | sensor_group0 281 | sensor_group1 282 | sensor_group2 283 | sensor_group3 284 | sensor_group4 285 | sensor_group5 286 | sensor_group6 287 | sensor_group100 288 | sensor_group101 289 | sensor_group106 290 | sensor_group107 291 | firmware_version 292 | 293 | The ``Create2`` class also provides the following features not explicitly provided in the spec: 294 | 295 | * auto_wake - the Open Interface goes to sleep after 5 minutes of inactivity when in Passive mode. With this property set to True, the ``Create2`` object will track idle time when in Passive mode and automatically wake the robot when a command is issued if necessary. Enabled by default in the constructor. *wake maybe be called any time with wake()* 296 | * enable_quirks - Roomba 500/600 firmware versions prior to 3.3.0 return an incorrect value for distance and angle. With this property set to True, the properties ``distance`` and ``angle`` will use the encoder counts to determine the correct value. This only works for the ``distance`` and ``angle`` properties. Distance and angle in the *sensor groups* will still report the wrong value. 297 | * firmware_version - a property of the ``Create2`` class that gets the welcome message in order to determine the firmware version. Reading this property will reset the robot and will take approximately 5 seconds to complete. To see this used to automatically determine if ``enable_quirks`` should be set, please see ``check_for_quirks`` in ``console_interfaces.create2``. 298 | 299 | 300 | Please see the `iRobot Roomba Open Interface Spec `_ for a listing of all commands and their purposes. 301 | 302 | Changelog 303 | --------- 304 | | irobot-1.0.0b1 305 | | Initial release 306 | | 307 | | irobot-1.0.0b2 308 | | Bugfix: Improperly set baud rate on serial connection preventing the library from working under Linux. 309 | | irobot-1.0.0b3 310 | | Bugfix: Wrong op code for seek_dock 311 | | Bugfix: Use of Python 2.7 incompatible version of super() 312 | 313 | 314 | Installation 315 | ------------ 316 | | This is beta software. It has been tested under Pyhon 2.7 and 3.x under Windows 8 and Python 3.x under Debain GNU/Linux 8 (jessie) 64 bit. 317 | | 318 | | Download the zip package `irobot-1.0.0b3.tar.gz `_ 319 | | 320 | | Install with pip 321 | | ``pip install [path to zip file]`` 322 | | 323 | 324 | Linux notes: 325 | 326 | * In order to use the Create Cable on ``/dev/ttyUSB0`` I had to 327 | - remove modemmanager (apparently is takes possession of ``/dev/ttyUSB0``) 328 | - add myself to ``dialout`` with ``sudo adduser [username] dialout`` 329 | 330 | Tests 331 | ----- 332 | | Unit tests for verifying some of the command builders may be found in ``tests.commands_test`` 333 | | A test script to connect to a Create2 over a serial connection and exercise all read commands maybe found in ``tests.create2_test`` 334 | 335 | 336 | Known Issues/Notes 337 | ------------------ 338 | * Issues 339 | - set_raw_leds does not presently behave as detailed in the spec. The issue has been reported to the manufacturer. 340 | - The orange and green wires are swapped on the official Create Cables preventing the robot form waking. You will need to create your own cable in order to use the ``auto_wake`` feature. 341 | * Notes 342 | - the arguments to ``set_raw_leds`` are integers that should be composed by ORing the segments you wish to turn on. Example: ``set_raw_leds(RAW_LED.A | RAW_LED.B | RAW_LED.C)`` to turn on segments A, B, and C of the first display. 343 | - the notes argument to ``set_song`` is a list of tuples where each tuple is a note and a duration. Eample: ``set_song(0, [(57,32),(59,32),(60,32)])`` to create a song as song0 consisting of the notes A, B, and C played for .5 seconds each. 344 | 345 | Author 346 | ------ 347 | `Matthew Witherwax (lemoneer) `_ 348 | 349 | License 350 | ------- 351 | :: 352 | 353 | MIT License 354 | 355 | Copyright (c) 2016 Matthew Witherwax 356 | 357 | Permission is hereby granted, free of charge, to any person obtaining a copy 358 | of this software and associated documentation files (the "Software"), to deal 359 | in the Software without restriction, including without limitation the rights 360 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 361 | copies of the Software, and to permit persons to whom the Software is 362 | furnished to do so, subject to the following conditions: 363 | 364 | The above copyright notice and this permission notice shall be included in all 365 | copies or substantial portions of the Software. 366 | 367 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 368 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 369 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 370 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 371 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 372 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 373 | SOFTWARE. 374 | Keywords: robotics irobot roomba 375 | Platform: UNKNOWN 376 | Classifier: Development Status :: 4 - Beta 377 | Classifier: Intended Audience :: Developers 378 | Classifier: Topic :: Software Development 379 | Classifier: License :: OSI Approved :: MIT License 380 | Classifier: Programming Language :: Python :: 2.7 381 | Classifier: Programming Language :: Python :: 3 382 | Classifier: Programming Language :: Python :: 3.3 383 | Classifier: Programming Language :: Python :: 3.4 384 | Classifier: Programming Language :: Python :: 3.5 385 | -------------------------------------------------------------------------------- /irobot/robots/create2.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Matthew Witherwax (lemoneer)' 2 | __maintainer__ = "Daniel Macías Perea (dani.macias.perea@domoticproject.com)" 3 | 4 | import types 5 | from time import sleep, time 6 | import logging 7 | import serial 8 | from six import raise_from, iterbytes 9 | import RPi.GPIO as GPIO 10 | 11 | from irobot.openinterface.commands import set_mode_full, set_mode_passive, set_mode_safe, power_down, reset, start, stop, \ 12 | drive, drive_direct, drive_pwm, seek_dock, set_baud, set_day_time, set_schedule, clean, clean_max, clean_spot, \ 13 | set_motors, set_motors_pwm, set_leds, set_ascii_leds, trigger_buttons, set_song, play_song, request_sensor_data, \ 14 | set_scheduling_leds, set_raw_leds 15 | from irobot.openinterface.constants import BAUD_RATE, DRIVE, RESPONSE_SIZES, ROBOT, MODES, POWER_SAVE_TIME 16 | from serial.serialutil import SerialException 17 | 18 | from irobot.openinterface.response_parsers import binary_response, byte_response, unsigned_byte_response, short_response, \ 19 | unsigned_short_response, BumpsAndWheelDrop, WheelOvercurrents, Buttons, ChargingSources, LightBumper, Stasis, \ 20 | SensorGroup0, SensorGroup1, SensorGroup2, SensorGroup3, SensorGroup4, SensorGroup5, SensorGroup6, SensorGroup100, \ 21 | SensorGroup101, SensorGroup106, SensorGroup107 22 | 23 | _error_msg_range = 'Argument {0} out of range' 24 | 25 | 26 | class Create2(object): 27 | def __init__(self, port, brc_pin=0, baud_rate=115200, timeout=1, auto_wake=True, enable_quirks=True): 28 | self._auto_wake = auto_wake 29 | self._oi_mode = MODES.OFF 30 | self._last_command_time = time() 31 | 32 | self._enable_quirks = enable_quirks 33 | self._toggle_quirks() 34 | 35 | self.logger = logging.getLogger('Create2') 36 | 37 | #If Roomba BRC pin is set , configure GPIO as wake up pin and configure correct wake function 38 | self._brc_pin = brc_pin 39 | if (self._brc_pin != 0): 40 | self.wake = types.MethodType(Create2._wake_BRC, self) 41 | GPIO.setmode(GPIO.BCM) 42 | GPIO.setwarnings(False) 43 | GPIO.setup(self._brc_pin, GPIO.OUT) 44 | GPIO.output(self._brc_pin, True) 45 | sleep(1) 46 | GPIO.output(self._brc_pin, False) 47 | sleep(1) 48 | GPIO.output(self._brc_pin, True) 49 | else: 50 | self.wake = types.MethodType(Create2._wake_RTS, self) 51 | 52 | self._attach_to_robot(port, baud_rate, timeout) 53 | 54 | 55 | 56 | def __del__(self): 57 | self.stop() 58 | self._serial_port.close() 59 | if (self._brc_pin != 0): 60 | GPIO.cleanup() 61 | 62 | def _attach_to_robot(self, port, baud_rate, timeout): 63 | try: 64 | self._serial_port = serial.Serial(port=port, 65 | baudrate=baud_rate, 66 | bytesize=serial.EIGHTBITS, 67 | parity=serial.PARITY_NONE, 68 | stopbits=serial.STOPBITS_ONE, 69 | timeout=timeout, 70 | writeTimeout=timeout, 71 | xonxoff=False, 72 | rtscts=False, 73 | dsrdtr=False) 74 | except SerialException as e: 75 | raise_from(RobotConnectionError(port, baud_rate), e) 76 | 77 | # wait for the robot to wake up on connection opened 78 | sleep(1) 79 | 80 | self.start() 81 | 82 | def _send(self, data): 83 | self._log_send(data) 84 | 85 | self._handle_auto_wake() 86 | 87 | #Roomba will sleep after 5 minutes of inactivity. 88 | #Requesting a data packet is not considered as activity. 89 | if (data[0] != 142): 90 | self._last_command_time = time() 91 | 92 | self._serial_port.write(data) 93 | 94 | def _read_sensor_data(self, id): 95 | #Flush input buffer to avoid inconsistent reads 96 | self._serial_port.reset_input_buffer() 97 | 98 | self._send(request_sensor_data(id)) 99 | size = RESPONSE_SIZES[id] 100 | data = self._serial_port.read(size) 101 | 102 | #If no data was received, wake the roomba JIC 103 | if len(data) != size: 104 | self.wake() 105 | raise Exception('Received %d bytes but %d where expected', len(data), size) 106 | 107 | self.logger.info('Received\n{0}'.format(self._format_data(data))) 108 | 109 | return data 110 | 111 | def _handle_auto_wake(self): 112 | if not self._auto_wake or self._oi_mode != MODES.PASSIVE: 113 | return 114 | 115 | now = time() 116 | # wake the robot if the last command was sent any time after power saves 117 | if (now - self._last_command_time) >= POWER_SAVE_TIME : 118 | self.wake() 119 | 120 | def _log_send(self, data): 121 | self.logger.info('Last command sent {0:.2f} seconds ago\nSending Command\n{1}'.format( 122 | time() - self._last_command_time, 123 | self._format_data(data) 124 | )) 125 | 126 | @staticmethod 127 | def _format_data(data): 128 | data = iterbytes(data) 129 | return 'Decimal:\t[{0}]\nHex:\t\t[{1}]\nBin:\t\t[{2}]'.format( 130 | '|'.join(('{0:d}'.format(b)) for b in data), 131 | '|'.join(('{0:X}'.format(b)) for b in data), 132 | '|'.join(('{0:08b}'.format(b)) for b in data) 133 | ) 134 | 135 | def _toggle_quirks(self): 136 | if self._enable_quirks: 137 | self._get_distance = types.MethodType(Create2._get_distance_quirks, self) 138 | self._get_angle = types.MethodType(Create2._get_angle_quirks, self) 139 | else: 140 | self._get_distance = types.MethodType(Create2._get_distance_std, self) 141 | self._get_angle = types.MethodType(Create2._get_angle_std, self) 142 | 143 | def _change_mode(self, mode): 144 | if mode == MODES.PASSIVE: 145 | mode_cmd = set_mode_passive() 146 | elif mode == MODES.SAFE: 147 | mode_cmd = set_mode_safe() 148 | elif mode == MODES.FULL: 149 | mode_cmd = set_mode_full() 150 | else: 151 | raise ValueError('Invalid mode') 152 | 153 | self._send(mode_cmd) 154 | self._verify_mode(mode) 155 | 156 | def _verify_mode(self, mode): 157 | if self.oi_mode != mode: 158 | raise ModeChangeError(mode, self._oi_mode) 159 | 160 | @staticmethod 161 | def _is_valid_hour(hour): 162 | return 0 <= hour <= 23 163 | 164 | @staticmethod 165 | def _is_valid_minute(minute): 166 | return 0 <= minute <= 59 167 | 168 | @property 169 | def enable_quirks(self): 170 | return self._enable_quirks 171 | 172 | @enable_quirks.setter 173 | def enable_quirks(self, value): 174 | self._enable_quirks = value 175 | self._toggle_quirks() 176 | 177 | @property 178 | def auto_wake(self): 179 | return self._auto_wake 180 | 181 | @auto_wake.setter 182 | def auto_wake(self, value): 183 | self._auto_wake = value 184 | 185 | def wake(self): 186 | pass 187 | 188 | def _wake_BRC(self): 189 | self.logger.info('Waking robot after {0:.2f} seconds of inactivity'.format(time() - self._last_command_time)) 190 | GPIO.output(self._brc_pin, False) 191 | sleep(1) 192 | GPIO.output(self._brc_pin, True) 193 | sleep(1) 194 | # after a wake up the robot sends some information. Flush it! 195 | self._serial_port.reset_input_buffer() 196 | self._last_command_time = time() 197 | 198 | def _wake_RTS(self): 199 | self.logger.info('Waking robot after {0:.2f} seconds of inactivity'.format(time() - self._last_command_time)) 200 | self._serial_port.setRTS(True) # rts in pyserial 3.0 201 | sleep(1) 202 | self._serial_port.setRTS(False) 203 | sleep(1) 204 | self._serial_port.setRTS(True) 205 | sleep(1) 206 | # after a wake up the robot sends some information. Flush it! 207 | self._serial_port.reset_input_buffer() 208 | self._last_command_time = time() 209 | 210 | def start(self): 211 | self._send(start()) 212 | 213 | # read data waiting on start 214 | welcome_message = self._serial_port.read(1024).decode('utf-8') 215 | if welcome_message is not None: 216 | self.logger.info('First 1024 characters of welcome message: {0}'.format(welcome_message)) 217 | # flush anything after the first 1024 bytes 218 | self._serial_port.reset_input_buffer() 219 | 220 | self._verify_mode(MODES.PASSIVE) 221 | 222 | def reset(self): 223 | self._send(reset()) 224 | self._oi_mode = MODES.OFF 225 | 226 | def stop(self): 227 | self._send(stop()) 228 | self._oi_mode = MODES.OFF 229 | 230 | def set_baud(self, baud=BAUD_RATE.DEFAULT): 231 | self._send(set_baud(baud)) 232 | 233 | def clean(self): 234 | self._send(clean()) 235 | self._verify_mode(MODES.PASSIVE) 236 | 237 | def clean_max(self): 238 | self._send(clean_max()) 239 | self._verify_mode(MODES.PASSIVE) 240 | 241 | def clean_spot(self): 242 | self._send(clean_spot()) 243 | self._verify_mode(MODES.PASSIVE) 244 | 245 | def seek_dock(self): 246 | self._send(seek_dock()) 247 | self._verify_mode(MODES.PASSIVE) 248 | 249 | def power_down(self): 250 | self._send(power_down()) 251 | self._oi_mode = MODES.OFF 252 | 253 | def set_schedule(self, sun_hour=0, sun_min=0, mon_hour=0, mon_min=0, tues_hour=0, tues_min=0, wed_hour=0, 254 | wed_min=0, thurs_hour=0, thurs_min=0, fri_hour=0, fri_min=0, sat_hour=0, sat_min=0): 255 | 256 | if not self._is_valid_hour(sun_hour): 257 | raise ValueError(_error_msg_range.format('sun_hour')) 258 | if not self._is_valid_hour(mon_hour): 259 | raise ValueError(_error_msg_range.format('mon_hour')) 260 | if not self._is_valid_hour(tues_hour): 261 | raise ValueError(_error_msg_range.format('tues_hour')) 262 | if not self._is_valid_hour(wed_hour): 263 | raise ValueError(_error_msg_range.format('wed_hour')) 264 | if not self._is_valid_hour(thurs_hour): 265 | raise ValueError(_error_msg_range.format('thurs_hour')) 266 | if not self._is_valid_hour(fri_hour): 267 | raise ValueError(_error_msg_range.format('fri_hour')) 268 | if not self._is_valid_hour(sat_hour): 269 | raise ValueError(_error_msg_range.format('sat_hour')) 270 | 271 | if not self._is_valid_minute(sun_min): 272 | raise ValueError(_error_msg_range.format('sun_min')) 273 | if not self._is_valid_minute(mon_min): 274 | raise ValueError(_error_msg_range.format('mon_min')) 275 | if not self._is_valid_minute(tues_min): 276 | raise ValueError(_error_msg_range.format('tues_min')) 277 | if not self._is_valid_minute(wed_min): 278 | raise ValueError(_error_msg_range.format('wed_min')) 279 | if not self._is_valid_minute(thurs_min): 280 | raise ValueError(_error_msg_range.format('thurs_min')) 281 | if not self._is_valid_minute(fri_min): 282 | raise ValueError(_error_msg_range.format('fri_min')) 283 | if not self._is_valid_minute(sat_min): 284 | raise ValueError(_error_msg_range.format('sat_min')) 285 | 286 | self._send( 287 | set_schedule(sun_hour, sun_min, mon_hour, mon_min, tues_hour, tues_min, wed_hour, wed_min, thurs_hour, 288 | thurs_min, fri_hour, fri_min, sat_hour, sat_min)) 289 | 290 | def clear_schedule(self): 291 | self._send(self.schedule()) 292 | 293 | def set_day_time(self, day=0, hour=0, minute=0): 294 | if not 0 <= day <= 6: 295 | raise ValueError(_error_msg_range.format('day')) 296 | if not self._is_valid_hour(hour): 297 | raise ValueError(_error_msg_range.format('hour')) 298 | if not self._is_valid_minute(minute): 299 | raise ValueError(_error_msg_range.format('minute')) 300 | self._send(set_day_time(day, hour, minute)) 301 | 302 | def drive(self, velocity, radius): 303 | if not -500 <= velocity <= 500: 304 | raise ValueError(_error_msg_range.format('velocity')) 305 | if not -2000 <= radius <= 2000 and\ 306 | (radius != DRIVE.STRAIGHT and radius != DRIVE.STRAIGHT_ALT 307 | and radius != DRIVE.TURN_IN_PLACE_CCW and radius != DRIVE.TURN_IN_PLACE_CW): 308 | raise ValueError(_error_msg_range.format('radius')) 309 | 310 | self._send(drive(velocity, radius)) 311 | 312 | def drive_straight(self, velocity): 313 | self.drive(velocity, DRIVE.STRAIGHT) 314 | 315 | def spin_left(self, velocity): 316 | self.drive(velocity, DRIVE.TURN_IN_PLACE_CCW) 317 | 318 | def spin_right(self, velocity): 319 | self.drive(velocity, DRIVE.TURN_IN_PLACE_CW) 320 | 321 | def drive_direct(self, right_velocity, left_velocity): 322 | if not -500 <= right_velocity <= 500: 323 | raise ValueError(_error_msg_range.format('right_velocity')) 324 | if not -500 <= left_velocity <= 500: 325 | raise ValueError(_error_msg_range.format('left_velocity')) 326 | 327 | self._send(drive_direct(right_velocity, left_velocity)) 328 | 329 | def drive_pwm(self, right_pwm, left_pwm): 330 | if not -255 <= right_pwm <= 255: 331 | raise ValueError(_error_msg_range.format('right_pwm')) 332 | if not -255 <= left_pwm <= 255: 333 | raise ValueError(_error_msg_range.format('left_pwm')) 334 | self._send(drive_pwm(right_pwm, left_pwm)) 335 | 336 | def set_motors(self, main_brush_on=False, main_brush_reverse=False, side_brush=False, side_brush_reverse=False, 337 | vacuum=False): 338 | self._send(set_motors(main_brush_on, main_brush_reverse, side_brush, side_brush_reverse, vacuum)) 339 | 340 | def set_motors_pwm(self, main_brush_pwm, side_brush_pwm, vacuum_pwm): 341 | if not -127 <= main_brush_pwm <= 127: 342 | raise ValueError(_error_msg_range.format('main_brush_pwm')) 343 | if not -127 <= side_brush_pwm <= 127: 344 | raise ValueError(_error_msg_range.format('side_brush_pwm')) 345 | if not 0 <= vacuum_pwm <= 127: 346 | raise ValueError(_error_msg_range.format('vacuum_pwm')) 347 | 348 | self._send(set_motors_pwm(main_brush_pwm, side_brush_pwm, vacuum_pwm)) 349 | 350 | def set_leds(self, debris=False, spot=False, dock=False, check_robot=False, power_color=0, power_intensity=0): 351 | if not 0 <= power_color <= 255: 352 | raise ValueError(_error_msg_range.format('power_color')) 353 | if not 0 <= power_intensity <= 255: 354 | raise ValueError(_error_msg_range.format('power_intensity')) 355 | 356 | self._send(set_leds(debris, spot, dock, check_robot, power_color, power_intensity)) 357 | 358 | def set_scheduling_leds(self, sun=False, mon=False, tues=False, wed=False, thurs=False, fri=False, sat=False, 359 | schedule=False, clock=False, am=False, pm=False, colon=False): 360 | self._send(set_scheduling_leds(sun, mon, tues, wed, thurs, fri, sat, schedule, clock, am, pm, colon)) 361 | 362 | def set_raw_leds(self, digit1=0, digit2=0, digit3=0, digit4=0): 363 | """ 364 | Arguments - ORed set of segments to turn on 365 | ex RAW_LED.A | RAW_LED.B | RAW_LED.C 366 | """ 367 | self._send(set_raw_leds(digit1, digit2, digit3, digit4)) 368 | 369 | def set_ascii_leds(self, char1=32, char2=32, char3=32, char4=32): 370 | self._send(set_ascii_leds(char1, char2, char3, char4)) 371 | 372 | def trigger_buttons(self, clean=False, spot=False, dock=False, minute=False, hour=False, day=False, schedule=False, clock=False): 373 | self._send(trigger_buttons(clean, spot, dock, minute, hour, day, schedule, clock)) 374 | 375 | def set_song(self, song_number, notes): 376 | if not 0 <= song_number <= 3: 377 | raise ValueError(_error_msg_range.format('song_number')) 378 | 379 | num_notes = len(notes) 380 | if not 0 < num_notes <= 16: 381 | raise ValueError('Length of notes out of range') 382 | 383 | for i in range(0, num_notes): 384 | note = notes[i] 385 | if not 31 <= note[0] <= 127: 386 | note[0] = 0 387 | 388 | if not 0 <= note[1] <= 255: 389 | raise ValueError(_error_msg_range.format('notes' + '[' + i + '][1]')) 390 | 391 | self._send(set_song(song_number, notes)) 392 | 393 | def play_song(self, song_number): 394 | if not 0 <= song_number <= 3: 395 | pass 396 | self._send(play_song(song_number)) 397 | 398 | @property 399 | def bumps_and_wheel_drops(self): 400 | return BumpsAndWheelDrop(self._read_sensor_data(7)) 401 | 402 | @property 403 | def wall_sensor(self): 404 | return binary_response(self._read_sensor_data(8)) 405 | 406 | @property 407 | def cliff_left(self): 408 | return binary_response(self._read_sensor_data(9)) 409 | 410 | @property 411 | def cliff_front_left(self): 412 | return binary_response(self._read_sensor_data(10)) 413 | 414 | @property 415 | def cliff_front_right(self): 416 | return binary_response(self._read_sensor_data(11)) 417 | 418 | @property 419 | def cliff_right(self): 420 | return binary_response(self._read_sensor_data(12)) 421 | 422 | @property 423 | def virtual_wall(self): 424 | return binary_response(self._read_sensor_data(13)) 425 | 426 | @property 427 | def wheel_overcurrents(self): 428 | return WheelOvercurrents(self._read_sensor_data(14)) 429 | 430 | @property 431 | def dirt_detect(self): 432 | return byte_response(self._read_sensor_data(15)) 433 | 434 | @property 435 | def ir_char_omni(self): 436 | return unsigned_byte_response(self._read_sensor_data(17)) 437 | 438 | @property 439 | def ir_char_left(self): 440 | return unsigned_byte_response(self._read_sensor_data(52)) 441 | 442 | @property 443 | def ir_char_right(self): 444 | return unsigned_byte_response(self._read_sensor_data(53)) 445 | 446 | @property 447 | def buttons(self): 448 | return Buttons(self._read_sensor_data(18)) 449 | 450 | @property 451 | def distance(self): 452 | return self._get_distance() 453 | 454 | @property 455 | def angle(self): 456 | return self._get_angle() 457 | 458 | def _get_distance(self): 459 | pass 460 | 461 | def _get_angle(self): 462 | pass 463 | 464 | def _get_distance_std(self): 465 | return short_response(self._read_sensor_data(19)) 466 | 467 | def _get_angle_std(self): 468 | return short_response(self._read_sensor_data(20)) 469 | 470 | def _get_distance_quirks(self): 471 | """ 472 | As detailed in the OI Spec Create 2 and Roomba 500/600 473 | firmware versions prior to 3.3.0 return an incorrect 474 | value for distance measured in millimeters 475 | 476 | This calculates the distance from the raw encoder counts 477 | """ 478 | left = self.left_encoder_counts 479 | right = self.right_encoder_counts 480 | return (left * ROBOT.TICK_TO_DISTANCE + right * ROBOT.TICK_TO_DISTANCE) / 2 481 | 482 | def _get_angle_quirks(self): 483 | """ 484 | As detailed in the OI Spec Create 2 and Roomba firmware 485 | versions 3.4.0 and earlier return an incorrect value for 486 | angle measured in degrees. 487 | 488 | This calculates the angle from the raw encoder counts 489 | """ 490 | left = self.left_encoder_counts 491 | right = self.right_encoder_counts 492 | return (right * ROBOT.TICK_TO_DISTANCE - left * ROBOT.TICK_TO_DISTANCE) / ROBOT.WHEEL_BASE 493 | 494 | @property 495 | def charging_state(self): 496 | return unsigned_byte_response(self._read_sensor_data(21)) 497 | 498 | @property 499 | def voltage(self): 500 | return unsigned_short_response(self._read_sensor_data(22)) 501 | 502 | @property 503 | def current(self): 504 | return short_response(self._read_sensor_data(23)) 505 | 506 | @property 507 | def temperature(self): 508 | return byte_response(self._read_sensor_data(24)) 509 | 510 | @property 511 | def battery_charge(self): 512 | return unsigned_short_response(self._read_sensor_data(25)) 513 | 514 | @property 515 | def battery_capacity(self): 516 | return unsigned_short_response(self._read_sensor_data(26)) 517 | 518 | @property 519 | def wall_signal(self): 520 | return unsigned_short_response(self._read_sensor_data(27)) 521 | 522 | @property 523 | def cliff_left_signal(self): 524 | return unsigned_short_response(self._read_sensor_data(28)) 525 | 526 | @property 527 | def cliff_front_left_signal(self): 528 | return unsigned_short_response(self._read_sensor_data(29)) 529 | 530 | @property 531 | def cliff_front_right_signal(self): 532 | return unsigned_short_response(self._read_sensor_data(30)) 533 | 534 | @property 535 | def cliff_right_signal(self): 536 | return unsigned_short_response(self._read_sensor_data(31)) 537 | 538 | @property 539 | def charging_sources(self): 540 | return ChargingSources(self._read_sensor_data(34)) 541 | 542 | @property 543 | def oi_mode(self): 544 | self._oi_mode = unsigned_byte_response(self._read_sensor_data(35)) 545 | return self._oi_mode 546 | 547 | @oi_mode.setter 548 | def oi_mode(self, value): 549 | self._change_mode(value) 550 | 551 | @property 552 | def song_number(self): 553 | return unsigned_byte_response(self._read_sensor_data(36)) 554 | 555 | @property 556 | def is_song_playing(self): 557 | return binary_response(self._read_sensor_data(37)) 558 | 559 | @property 560 | def number_stream_packets(self): 561 | return unsigned_byte_response(self._read_sensor_data(38)) 562 | 563 | @property 564 | def requested_velocity(self): 565 | return short_response(self._read_sensor_data(39)) 566 | 567 | @property 568 | def requested_radius(self): 569 | return short_response(self._read_sensor_data(40)) 570 | 571 | @property 572 | def requested_right_velocity(self): 573 | return short_response(self._read_sensor_data(41)) 574 | 575 | @property 576 | def requested_left_velocity(self): 577 | return short_response(self._read_sensor_data(42)) 578 | 579 | @property 580 | def left_encoder_counts(self): 581 | return short_response(self._read_sensor_data(43)) 582 | 583 | @property 584 | def right_encoder_counts(self): 585 | return short_response(self._read_sensor_data(44)) 586 | 587 | @property 588 | def light_bumper(self): 589 | return LightBumper(self._read_sensor_data(45)) 590 | 591 | @property 592 | def light_bump_left_signal(self): 593 | return unsigned_short_response(self._read_sensor_data(46)) 594 | 595 | @property 596 | def light_bump_front_left_signal(self): 597 | return unsigned_short_response(self._read_sensor_data(47)) 598 | 599 | @property 600 | def light_bump_center_left_signal(self): 601 | return unsigned_short_response(self._read_sensor_data(48)) 602 | 603 | @property 604 | def light_bump_center_right_signal(self): 605 | return unsigned_short_response(self._read_sensor_data(49)) 606 | 607 | @property 608 | def light_bump_front_right_signal(self): 609 | return unsigned_short_response(self._read_sensor_data(50)) 610 | 611 | @property 612 | def light_bump_right_signal(self): 613 | return unsigned_short_response(self._read_sensor_data(51)) 614 | 615 | @property 616 | def left_motor_current(self): 617 | return short_response(self._read_sensor_data(54)) 618 | 619 | @property 620 | def right_motor_current(self): 621 | return short_response(self._read_sensor_data(55)) 622 | 623 | @property 624 | def main_brush_motor_current(self): 625 | return short_response(self._read_sensor_data(56)) 626 | 627 | @property 628 | def side_brush_motor_current(self): 629 | return short_response(self._read_sensor_data(57)) 630 | 631 | @property 632 | def stasis(self): 633 | return Stasis(self._read_sensor_data(58)) 634 | 635 | @property 636 | def sensor_group0(self): 637 | return SensorGroup0(self._read_sensor_data(0)) 638 | 639 | @property 640 | def sensor_group1(self): 641 | return SensorGroup1(self._read_sensor_data(1)) 642 | 643 | @property 644 | def sensor_group2(self): 645 | return SensorGroup2(self._read_sensor_data(2)) 646 | 647 | @property 648 | def sensor_group3(self): 649 | return SensorGroup3(self._read_sensor_data(3)) 650 | 651 | @property 652 | def sensor_group4(self): 653 | return SensorGroup4(self._read_sensor_data(4)) 654 | 655 | @property 656 | def sensor_group5(self): 657 | return SensorGroup5(self._read_sensor_data(5)) 658 | 659 | @property 660 | def sensor_group6(self): 661 | return SensorGroup6(self._read_sensor_data(6)) 662 | 663 | @property 664 | def sensor_group100(self): 665 | return SensorGroup100(self._read_sensor_data(100)) 666 | 667 | @property 668 | def sensor_group101(self): 669 | return SensorGroup101(self._read_sensor_data(101)) 670 | 671 | @property 672 | def sensor_group106(self): 673 | return SensorGroup106(self._read_sensor_data(106)) 674 | 675 | @property 676 | def sensor_group107(self): 677 | return SensorGroup107(self._read_sensor_data(107)) 678 | 679 | @property 680 | def firmware_version(self): 681 | self.reset() 682 | sleep(5) 683 | msg = self._serial_port.read(1024).decode('utf-8') 684 | self.start() 685 | self._serial_port.reset_input_buffer() # reset_input_buffer() in pyserial 3.0 686 | return msg 687 | 688 | 689 | class ModeChangeError(Exception): 690 | def __init__(self, requested_mode, actual_mode): 691 | self.requested_mode = requested_mode 692 | self.actual_mode = actual_mode 693 | 694 | 695 | class RobotConnectionError(Exception): 696 | def __init__(self, port, baud): 697 | self.port = port 698 | self.baud = baud 699 | 700 | def __str__(self): 701 | return 'Failed to connect to robot on Port {} with Baud Code: {!r}'.format(self.port, self.baud) 702 | 703 | -------------------------------------------------------------------------------- /irobot/openinterface/response_parsers.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Matthew Witherwax (lemoneer)' 2 | 3 | from struct import Struct 4 | 5 | from .constants import WHEEL_OVERCURRENT, BUMPS_WHEEL_DROPS, BUTTONS, CHARGE_SOURCE, LIGHT_BUMPER, STASIS 6 | 7 | unpack_bool_byte = Struct('?').unpack 8 | unpack_byte = Struct('b').unpack 9 | unpack_unsigned_byte = Struct('B').unpack 10 | unpack_short = Struct('>h').unpack 11 | unpack_unsigned_short = Struct('>H').unpack 12 | 13 | 14 | def binary_response(data): 15 | return unpack_bool_byte(data)[0] 16 | 17 | 18 | def packed_binary_response(data): 19 | return unpack_unsigned_byte(data)[0] 20 | 21 | 22 | def byte_response(data): 23 | return unpack_byte(data)[0] 24 | 25 | 26 | def unsigned_byte_response(data): 27 | return unpack_unsigned_byte(data)[0] 28 | 29 | 30 | def short_response(data): 31 | return unpack_short(data)[0] 32 | 33 | 34 | def unsigned_short_response(data): 35 | return unpack_unsigned_short(data)[0] 36 | 37 | 38 | class PackedBinaryData(object): 39 | def __init__(self, data): 40 | self._data = packed_binary_response(data) 41 | 42 | def __bool__(self): 43 | return self._data != b'\x00' 44 | 45 | 46 | class BumpsAndWheelDrop(PackedBinaryData): 47 | def __init__(self, data): 48 | super(BumpsAndWheelDrop, self).__init__(data) 49 | 50 | @property 51 | def bump_right(self): 52 | return bool(self._data & BUMPS_WHEEL_DROPS.BUMP_RIGHT) 53 | 54 | @property 55 | def bump_left(self): 56 | return bool(self._data & BUMPS_WHEEL_DROPS.BUMP_LEFT) 57 | 58 | @property 59 | def wheel_drop_right(self): 60 | return bool(self._data & BUMPS_WHEEL_DROPS.WHEEL_DROP_RIGHT) 61 | 62 | @property 63 | def wheel_drop_left(self): 64 | return bool(self._data & BUMPS_WHEEL_DROPS.WHEEL_DROP_LEFT) 65 | 66 | 67 | class WheelOvercurrents(PackedBinaryData): 68 | def __init__(self, data): 69 | super(WheelOvercurrents, self).__init__(data) 70 | 71 | @property 72 | def side_brush_overcurrent(self): 73 | return bool(self._data & WHEEL_OVERCURRENT.SIDE_BRUSH) 74 | 75 | @property 76 | def main_brush_overcurrent(self): 77 | return bool(self._data & WHEEL_OVERCURRENT.MAIN_BRUSH) 78 | 79 | @property 80 | def right_wheel_overcurrent(self): 81 | return bool(self._data & WHEEL_OVERCURRENT.RIGHT_WHEEL) 82 | 83 | @property 84 | def left_wheel_overcurrent(self): 85 | return bool(self._data & WHEEL_OVERCURRENT.LEFT_WHEEL) 86 | 87 | 88 | class Buttons(PackedBinaryData): 89 | def __init__(self, data): 90 | super(Buttons, self).__init__(data) 91 | 92 | @property 93 | def clean(self): 94 | return bool(self._data & BUTTONS.CLEAN) 95 | 96 | @property 97 | def spot(self): 98 | return bool(self._data & BUTTONS.SPOT) 99 | 100 | @property 101 | def dock(self): 102 | return bool(self._data & BUTTONS.DOCK) 103 | 104 | @property 105 | def minute(self): 106 | return bool(self._data & BUTTONS.MINUTE) 107 | 108 | @property 109 | def hour(self): 110 | return bool(self._data & BUTTONS.HOUR) 111 | 112 | @property 113 | def day(self): 114 | return bool(self._data & BUTTONS.DAY) 115 | 116 | @property 117 | def schedule(self): 118 | return bool(self._data & BUTTONS.SCHEDULE) 119 | 120 | @property 121 | def clock(self): 122 | return bool(self._data & BUTTONS.CLOCK) 123 | 124 | 125 | class ChargingSources(PackedBinaryData): 126 | def __init__(self, data): 127 | super(ChargingSources, self).__init__(data) 128 | 129 | @property 130 | def internal_charger(self): 131 | return bool(self._data & CHARGE_SOURCE.INTERNAL) 132 | 133 | @property 134 | def home_base(self): 135 | return bool(self._data & CHARGE_SOURCE.HOME_BASE) 136 | 137 | 138 | class LightBumper(PackedBinaryData): 139 | def __init__(self, data): 140 | super(LightBumper, self).__init__(data) 141 | 142 | @property 143 | def left(self): 144 | return bool(self._data & LIGHT_BUMPER.LEFT) 145 | 146 | @property 147 | def front_left(self): 148 | return bool(self._data & LIGHT_BUMPER.FRONT_LEFT) 149 | 150 | @property 151 | def center_left(self): 152 | return bool(self._data & LIGHT_BUMPER.CENTER_LEFT) 153 | 154 | @property 155 | def center_right(self): 156 | return bool(self._data & LIGHT_BUMPER.CENTER_RIGHT) 157 | 158 | @property 159 | def front_right(self): 160 | return bool(self._data & LIGHT_BUMPER.FRONT_RIGHT) 161 | 162 | @property 163 | def right(self): 164 | return bool(self._data & LIGHT_BUMPER.RIGHT) 165 | 166 | 167 | class Stasis(PackedBinaryData): 168 | def __init__(self, data): 169 | super(Stasis, self).__init__(data) 170 | 171 | @property 172 | def toggling(self): 173 | return bool(self._data & STASIS.TOGGLING) 174 | 175 | @property 176 | def disabled(self): 177 | return bool(self._data & STASIS.DISABLED) 178 | 179 | 180 | class SensorGroup0(object): 181 | def __init__(self, data): 182 | self._data = data 183 | self._bumps_and_wheel_drops = None 184 | self._wall_sensor = None 185 | self._cliff_left_sensor = None 186 | self._cliff_front_left_sensor = None 187 | self._cliff_front_right_sensor = None 188 | self._cliff_right_sensor = None 189 | self._virtual_wall_sensor = None 190 | self._wheel_overcurrents = None 191 | self._dirt_detect_sensor = None 192 | self._ir_char_omni_sensor = None 193 | self._buttons = None 194 | self._distance = None 195 | self._angle = None 196 | self._charging_state = None 197 | self._voltage = None 198 | self._current = None 199 | self._temperature = None 200 | self._battery_charge = None 201 | self._battery_capacity = None 202 | 203 | @property 204 | def bumps_and_wheel_drops(self): 205 | if self._bumps_and_wheel_drops is None: 206 | self._bumps_and_wheel_drops = BumpsAndWheelDrop(self._data[0:1]) 207 | return self._bumps_and_wheel_drops 208 | 209 | @property 210 | def wall_sensor(self): 211 | if self._wall_sensor is None: 212 | self._wall_sensor = binary_response(self._data[1:2]) 213 | return self._wall_sensor 214 | 215 | @property 216 | def cliff_left_sensor(self): 217 | if self._cliff_left_sensor is None: 218 | self._cliff_left_sensor = binary_response(self._data[2:3]) 219 | return self._cliff_left_sensor 220 | 221 | @property 222 | def cliff_front_left_sensor(self): 223 | if self._cliff_front_left_sensor is None: 224 | self._cliff_front_left_sensor = binary_response(self._data[3:4]) 225 | return self._cliff_front_left_sensor 226 | 227 | @property 228 | def cliff_front_right_sensor(self): 229 | if self._cliff_front_right_sensor is None: 230 | self._cliff_front_right_sensor = binary_response(self._data[4:5]) 231 | return self._cliff_front_right_sensor 232 | 233 | @property 234 | def cliff_right_sensor(self): 235 | if self._cliff_right_sensor is None: 236 | self._cliff_right_sensor = binary_response(self._data[5:6]) 237 | return self._cliff_right_sensor 238 | 239 | @property 240 | def virtual_wall_sensor(self): 241 | if self._virtual_wall_sensor is None: 242 | self._virtual_wall_sensor = binary_response(self._data[6:7]) 243 | return self._virtual_wall_sensor 244 | 245 | @property 246 | def wheel_overcurrents(self): 247 | if self._wheel_overcurrents is None: 248 | self._wheel_overcurrents = WheelOvercurrents(self._data[7:8]) 249 | return self._wheel_overcurrents 250 | 251 | @property 252 | def dirt_detect_sensor(self): 253 | if self._dirt_detect_sensor is None: 254 | self._dirt_detect_sensor = byte_response(self._data[8:9]) 255 | return self._dirt_detect_sensor 256 | 257 | @property 258 | def ir_char_omni_sensor(self): 259 | if self._ir_char_omni_sensor is None: 260 | self._ir_char_omni_sensor = unsigned_byte_response(self._data[10:11]) 261 | return self._ir_char_omni_sensor 262 | 263 | @property 264 | def buttons(self): 265 | if self._buttons is None: 266 | self._buttons = Buttons(self._data[11:12]) 267 | return self._buttons 268 | 269 | @property 270 | def distance(self): 271 | if self._distance is None: 272 | self._distance = short_response(self._data[12:14]) 273 | return self._distance 274 | 275 | @property 276 | def angle(self): 277 | if self._angle is None: 278 | self._angle = short_response(self._data[14:16]) 279 | return self._angle 280 | 281 | @property 282 | def charging_state(self): 283 | if self._charging_state is None: 284 | self._charging_state = unsigned_byte_response(self._data[16:17]) 285 | return self._charging_state 286 | 287 | @property 288 | def voltage(self): 289 | if self._voltage is None: 290 | self._voltage = unsigned_short_response(self._data[17:19]) 291 | return self._voltage 292 | 293 | @property 294 | def current(self): 295 | if self._current is None: 296 | self._current = short_response(self._data[19:21]) 297 | return self._current 298 | 299 | @property 300 | def temperature(self): 301 | if self._temperature is None: 302 | self._temperature = byte_response(self._data[21:22]) 303 | return self._temperature 304 | 305 | @property 306 | def battery_charge(self): 307 | if self._battery_charge is None: 308 | self._battery_charge = unsigned_short_response(self._data[22:24]) 309 | return self._battery_charge 310 | 311 | @property 312 | def battery_capacity(self): 313 | if self._battery_capacity is None: 314 | self._battery_capacity = unsigned_short_response(self._data[24:26]) 315 | return self._battery_capacity 316 | 317 | 318 | class SensorGroup1(object): 319 | def __init__(self, data): 320 | self._data = data 321 | self._bumps_and_wheel_drops = None 322 | self._wall_sensor = None 323 | self._cliff_left_sensor = None 324 | self._cliff_front_left_sensor = None 325 | self._cliff_front_right_sensor = None 326 | self._cliff_right_sensor = None 327 | self._virtual_wall_sensor = None 328 | self._wheel_overcurrents = None 329 | self._dirt_detect_sensor = None 330 | 331 | @property 332 | def bumps_and_wheel_drops(self): 333 | if self._bumps_and_wheel_drops is None: 334 | self._bumps_and_wheel_drops = BumpsAndWheelDrop(self._data[0:1]) 335 | return self._bumps_and_wheel_drops 336 | 337 | @property 338 | def wall_sensor(self): 339 | if self._wall_sensor is None: 340 | self._wall_sensor = binary_response(self._data[1:2]) 341 | return self._wall_sensor 342 | 343 | @property 344 | def cliff_left_sensor(self): 345 | if self._cliff_left_sensor is None: 346 | self._cliff_left_sensor = binary_response(self._data[2:3]) 347 | return self._cliff_left_sensor 348 | 349 | @property 350 | def cliff_front_left_sensor(self): 351 | if self._cliff_front_left_sensor is None: 352 | self._cliff_front_left_sensor = binary_response(self._data[3:4]) 353 | return self._cliff_front_left_sensor 354 | 355 | @property 356 | def cliff_front_right_sensor(self): 357 | if self._cliff_front_right_sensor is None: 358 | self._cliff_front_right_sensor = binary_response(self._data[4:5]) 359 | return self._cliff_front_right_sensor 360 | 361 | @property 362 | def cliff_right_sensor(self): 363 | if self._cliff_right_sensor is None: 364 | self._cliff_right_sensor = binary_response(self._data[5:6]) 365 | return self._cliff_right_sensor 366 | 367 | @property 368 | def virtual_wall_sensor(self): 369 | if self._virtual_wall_sensor is None: 370 | self._virtual_wall_sensor = binary_response(self._data[6:7]) 371 | return self._virtual_wall_sensor 372 | 373 | @property 374 | def wheel_overcurrents(self): 375 | if self._wheel_overcurrents is None: 376 | self._wheel_overcurrents = WheelOvercurrents(self._data[7:8]) 377 | return self._wheel_overcurrents 378 | 379 | @property 380 | def dirt_detect_sensor(self): 381 | if self._dirt_detect_sensor is None: 382 | self._dirt_detect_sensor = byte_response(self._data[8:9]) 383 | return self._dirt_detect_sensor 384 | 385 | 386 | class SensorGroup2(object): 387 | def __init__(self, data): 388 | self._data = data 389 | self._ir_char_omni_sensor = None 390 | self._buttons = None 391 | self._distance = None 392 | self._angle = None 393 | 394 | @property 395 | def ir_char_omni_sensor(self): 396 | if self._ir_char_omni_sensor is None: 397 | self._ir_char_omni_sensor = unsigned_byte_response(self._data[0:1]) 398 | return self._ir_char_omni_sensor 399 | 400 | @property 401 | def buttons(self): 402 | if self._buttons is None: 403 | self._buttons = Buttons(self._data[1:2]) 404 | return self._buttons 405 | 406 | @property 407 | def distance(self): 408 | if self._distance is None: 409 | self._distance = short_response(self._data[2:4]) 410 | return self._distance 411 | 412 | @property 413 | def angle(self): 414 | if self._angle is None: 415 | self._angle = short_response(self._data[4:6]) 416 | return self._angle 417 | 418 | 419 | class SensorGroup3(object): 420 | def __init__(self, data): 421 | self._data = data 422 | self._charging_state = None 423 | self._voltage = None 424 | self._current = None 425 | self._temperature = None 426 | self._battery_charge = None 427 | self._battery_capacity = None 428 | 429 | @property 430 | def charging_state(self): 431 | if self._charging_state is None: 432 | self._charging_state = unsigned_byte_response(self._data[0:1]) 433 | return self._charging_state 434 | 435 | @property 436 | def voltage(self): 437 | if self._voltage is None: 438 | self._voltage = unsigned_short_response(self._data[1:3]) 439 | return self._voltage 440 | 441 | @property 442 | def current(self): 443 | if self._current is None: 444 | self._current = short_response(self._data[3:5]) 445 | return self._current 446 | 447 | @property 448 | def temperature(self): 449 | if self._temperature is None: 450 | self._temperature = byte_response(self._data[5:6]) 451 | return self._temperature 452 | 453 | @property 454 | def battery_charge(self): 455 | if self._battery_charge is None: 456 | self._battery_charge = unsigned_short_response(self._data[6:8]) 457 | return self._battery_charge 458 | 459 | @property 460 | def battery_capacity(self): 461 | if self._battery_capacity is None: 462 | self._battery_capacity = unsigned_short_response(self._data[8:10]) 463 | return self._battery_capacity 464 | 465 | 466 | class SensorGroup4(object): 467 | def __init__(self, data): 468 | self._data = data 469 | self._wall_signal = None 470 | self._cliff_left_signal = None 471 | self._cliff_front_left_signal = None 472 | self._cliff_front_right_signal = None 473 | self._cliff_right_signal = None 474 | self._charging_sources = None 475 | 476 | @property 477 | def wall_signal(self): 478 | if self._wall_signal is None: 479 | self._wall_signal = unsigned_short_response(self._data[0:2]) 480 | return self._wall_signal 481 | 482 | @property 483 | def cliff_left_signal(self): 484 | if self._cliff_left_signal is None: 485 | self._cliff_left_signal = unsigned_short_response(self._data[2:4]) 486 | return self._cliff_left_signal 487 | 488 | @property 489 | def cliff_front_left_signal(self): 490 | if self._cliff_front_left_signal is None: 491 | self._cliff_front_left_signal = unsigned_short_response(self._data[4:6]) 492 | return self._cliff_front_left_signal 493 | 494 | @property 495 | def cliff_front_right_signal(self): 496 | if self._cliff_front_right_signal is None: 497 | self._cliff_front_right_signal = unsigned_short_response(self._data[6:8]) 498 | return self._cliff_front_right_signal 499 | 500 | @property 501 | def cliff_right_signal(self): 502 | if self._cliff_right_signal is None: 503 | self._cliff_right_signal = unsigned_short_response(self._data[8:10]) 504 | return self._cliff_right_signal 505 | 506 | @property 507 | def charging_sources(self): 508 | if self._charging_sources is None: 509 | self._charging_sources = ChargingSources(self._data[13:14]) 510 | return self._charging_sources 511 | 512 | 513 | class SensorGroup5(object): 514 | def __init__(self, data): 515 | self._data = data 516 | self._oi_mode = None 517 | self._song_number = None 518 | self._is_song_playing = None 519 | self._number_of_stream_packets = None 520 | self._requested_velocity = None 521 | self._requested_radius = None 522 | self._requested_right_velocity = None 523 | self._requested_left_velocity = None 524 | 525 | @property 526 | def oi_mode(self): 527 | if self._oi_mode is None: 528 | self._oi_mode = unsigned_byte_response(self._data[0:1]) 529 | return self._oi_mode 530 | 531 | @property 532 | def song_number(self): 533 | if self._song_number is None: 534 | self._song_number = unsigned_byte_response(self._data[1:2]) 535 | return self._song_number 536 | 537 | @property 538 | def is_song_playing(self): 539 | if self._is_song_playing is None: 540 | self._is_song_playing = binary_response(self._data[2:3]) 541 | return self._is_song_playing 542 | 543 | @property 544 | def number_of_stream_packets(self): 545 | if self._number_of_stream_packets is None: 546 | self._number_of_stream_packets = unsigned_byte_response(self._data[3:4]) 547 | return self._number_of_stream_packets 548 | 549 | @property 550 | def requested_velocity(self): 551 | if self._requested_velocity is None: 552 | self._requested_velocity = short_response(self._data[4:6]) 553 | return self._requested_velocity 554 | 555 | @property 556 | def requested_radius(self): 557 | if self._requested_radius is None: 558 | self._requested_radius = short_response(self._data[6:8]) 559 | return self._requested_radius 560 | 561 | @property 562 | def requested_right_velocity(self): 563 | if self._requested_right_velocity is None: 564 | self._requested_right_velocity = short_response(self._data[8:10]) 565 | return self._requested_right_velocity 566 | 567 | @property 568 | def requested_left_velocity(self): 569 | if self._requested_left_velocity is None: 570 | self._requested_left_velocity = short_response(self._data[10:12]) 571 | return self._requested_left_velocity 572 | 573 | 574 | class SensorGroup6(object): 575 | def __init__(self, data): 576 | self._data = data 577 | self._bumps_and_wheel_drops = None 578 | self._wall_sensor = None 579 | self._cliff_left_sensor = None 580 | self._cliff_front_left_sensor = None 581 | self._cliff_front_right_sensor = None 582 | self._cliff_right_sensor = None 583 | self._virtual_wall_sensor = None 584 | self._wheel_overcurrents = None 585 | self._dirt_detect_sensor = None 586 | self._ir_char_omni_sensor = None 587 | self._buttons = None 588 | self._distance = None 589 | self._angle = None 590 | self._charging_state = None 591 | self._voltage = None 592 | self._current = None 593 | self._temperature = None 594 | self._battery_charge = None 595 | self._battery_capacity = None 596 | self._wall_signal = None 597 | self._cliff_left_signal = None 598 | self._cliff_front_left_signal = None 599 | self._cliff_front_right_signal = None 600 | self._cliff_right_signal = None 601 | self._charging_sources = None 602 | self._oi_mode = None 603 | self._song_number = None 604 | self._is_song_playing = None 605 | self._number_of_stream_packets = None 606 | self._requested_velocity = None 607 | self._requested_radius = None 608 | self._requested_right_velocity = None 609 | self._requested_left_velocity = None 610 | 611 | @property 612 | def bumps_and_wheel_drops(self): 613 | if self._bumps_and_wheel_drops is None: 614 | self._bumps_and_wheel_drops = BumpsAndWheelDrop(self._data[0:1]) 615 | return self._bumps_and_wheel_drops 616 | 617 | @property 618 | def wall_sensor(self): 619 | if self._wall_sensor is None: 620 | self._wall_sensor = binary_response(self._data[1:2]) 621 | return self._wall_sensor 622 | 623 | @property 624 | def cliff_left_sensor(self): 625 | if self._cliff_left_sensor is None: 626 | self._cliff_left_sensor = binary_response(self._data[2:3]) 627 | return self._cliff_left_sensor 628 | 629 | @property 630 | def cliff_front_left_sensor(self): 631 | if self._cliff_front_left_sensor is None: 632 | self._cliff_front_left_sensor = binary_response(self._data[3:4]) 633 | return self._cliff_front_left_sensor 634 | 635 | @property 636 | def cliff_front_right_sensor(self): 637 | if self._cliff_front_right_sensor is None: 638 | self._cliff_front_right_sensor = binary_response(self._data[4:5]) 639 | return self._cliff_front_right_sensor 640 | 641 | @property 642 | def cliff_right_sensor(self): 643 | if self._cliff_right_sensor is None: 644 | self._cliff_right_sensor = binary_response(self._data[5:6]) 645 | return self._cliff_right_sensor 646 | 647 | @property 648 | def virtual_wall_sensor(self): 649 | if self._virtual_wall_sensor is None: 650 | self._virtual_wall_sensor = binary_response(self._data[6:7]) 651 | return self._virtual_wall_sensor 652 | 653 | @property 654 | def wheel_overcurrents(self): 655 | if self._wheel_overcurrents is None: 656 | self._wheel_overcurrents = WheelOvercurrents(self._data[7:8]) 657 | return self._wheel_overcurrents 658 | 659 | @property 660 | def dirt_detect_sensor(self): 661 | if self._dirt_detect_sensor is None: 662 | self._dirt_detect_sensor = byte_response(self._data[8:9]) 663 | return self._dirt_detect_sensor 664 | 665 | @property 666 | def ir_char_omni_sensor(self): 667 | if self._ir_char_omni_sensor is None: 668 | self._ir_char_omni_sensor = unsigned_byte_response(self._data[10:11]) 669 | return self._ir_char_omni_sensor 670 | 671 | @property 672 | def buttons(self): 673 | if self._buttons is None: 674 | self._buttons = Buttons(self._data[11:12]) 675 | return self._buttons 676 | 677 | @property 678 | def distance(self): 679 | if self._distance is None: 680 | self._distance = short_response(self._data[12:14]) 681 | return self._distance 682 | 683 | @property 684 | def angle(self): 685 | if self._angle is None: 686 | self._angle = short_response(self._data[14:16]) 687 | return self._angle 688 | 689 | @property 690 | def charging_state(self): 691 | if self._charging_state is None: 692 | self._charging_state = unsigned_byte_response(self._data[16:17]) 693 | return self._charging_state 694 | 695 | @property 696 | def voltage(self): 697 | if self._voltage is None: 698 | self._voltage = unsigned_short_response(self._data[17:19]) 699 | return self._voltage 700 | 701 | @property 702 | def current(self): 703 | if self._current is None: 704 | self._current = short_response(self._data[19:21]) 705 | return self._current 706 | 707 | @property 708 | def temperature(self): 709 | if self._temperature is None: 710 | self._temperature = byte_response(self._data[21:22]) 711 | return self._temperature 712 | 713 | @property 714 | def battery_charge(self): 715 | if self._battery_charge is None: 716 | self._battery_charge = unsigned_short_response(self._data[22:24]) 717 | return self._battery_charge 718 | 719 | @property 720 | def battery_capacity(self): 721 | if self._battery_capacity is None: 722 | self._battery_capacity = unsigned_short_response(self._data[24:26]) 723 | return self._battery_capacity 724 | 725 | @property 726 | def wall_signal(self): 727 | if self._wall_signal is None: 728 | self._wall_signal = unsigned_short_response(self._data[26:28]) 729 | return self._wall_signal 730 | 731 | @property 732 | def cliff_left_signal(self): 733 | if self._cliff_left_signal is None: 734 | self._cliff_left_signal = unsigned_short_response(self._data[28:30]) 735 | return self._cliff_left_signal 736 | 737 | @property 738 | def cliff_front_left_signal(self): 739 | if self._cliff_front_left_signal is None: 740 | self._cliff_front_left_signal = unsigned_short_response(self._data[30:32]) 741 | return self._cliff_front_left_signal 742 | 743 | @property 744 | def cliff_front_right_signal(self): 745 | if self._cliff_front_right_signal is None: 746 | self._cliff_front_right_signal = unsigned_short_response(self._data[32:34]) 747 | return self._cliff_front_right_signal 748 | 749 | @property 750 | def cliff_right_signal(self): 751 | if self._cliff_right_signal is None: 752 | self._cliff_right_signal = unsigned_short_response(self._data[34:36]) 753 | return self._cliff_right_signal 754 | 755 | @property 756 | def charging_sources(self): 757 | if self._charging_sources is None: 758 | self._charging_sources = ChargingSources(self._data[39:40]) 759 | return self._charging_sources 760 | 761 | @property 762 | def oi_mode(self): 763 | if self._oi_mode is None: 764 | self._oi_mode = unsigned_byte_response(self._data[40:41]) 765 | return self._oi_mode 766 | 767 | @property 768 | def song_number(self): 769 | if self._song_number is None: 770 | self._song_number = unsigned_byte_response(self._data[41:42]) 771 | return self._song_number 772 | 773 | @property 774 | def is_song_playing(self): 775 | if self._is_song_playing is None: 776 | self._is_song_playing = binary_response(self._data[42:43]) 777 | return self._is_song_playing 778 | 779 | @property 780 | def number_of_stream_packets(self): 781 | if self._number_of_stream_packets is None: 782 | self._number_of_stream_packets = unsigned_byte_response(self._data[43:44]) 783 | return self._number_of_stream_packets 784 | 785 | @property 786 | def requested_velocity(self): 787 | if self._requested_velocity is None: 788 | self._requested_velocity = short_response(self._data[44:46]) 789 | return self._requested_velocity 790 | 791 | @property 792 | def requested_radius(self): 793 | if self._requested_radius is None: 794 | self._requested_radius = short_response(self._data[46:48]) 795 | return self._requested_radius 796 | 797 | @property 798 | def requested_right_velocity(self): 799 | if self._requested_right_velocity is None: 800 | self._requested_right_velocity = short_response(self._data[48:50]) 801 | return self._requested_right_velocity 802 | 803 | @property 804 | def requested_left_velocity(self): 805 | if self._requested_left_velocity is None: 806 | self._requested_left_velocity = short_response(self._data[50:52]) 807 | return self._requested_left_velocity 808 | 809 | 810 | class SensorGroup100(object): 811 | def __init__(self, data): 812 | self._data = data 813 | self._bumps_and_wheel_drops = None 814 | self._wall_sensor = None 815 | self._cliff_left_sensor = None 816 | self._cliff_front_left_sensor = None 817 | self._cliff_front_right_sensor = None 818 | self._cliff_right_sensor = None 819 | self._virtual_wall_sensor = None 820 | self._wheel_overcurrents = None 821 | self._dirt_detect_sensor = None 822 | self._ir_char_omni_sensor = None 823 | self._buttons = None 824 | self._distance = None 825 | self._angle = None 826 | self._charging_state = None 827 | self._voltage = None 828 | self._current = None 829 | self._temperature = None 830 | self._battery_charge = None 831 | self._battery_capacity = None 832 | self._wall_signal = None 833 | self._cliff_left_signal = None 834 | self._cliff_front_left_signal = None 835 | self._cliff_front_right_signal = None 836 | self._cliff_right_signal = None 837 | self._charging_sources = None 838 | self._oi_mode = None 839 | self._song_number = None 840 | self._is_song_playing = None 841 | self._number_of_stream_packets = None 842 | self._requested_velocity = None 843 | self._requested_radius = None 844 | self._requested_right_velocity = None 845 | self._requested_left_velocity = None 846 | self._left_encoder_counts = None 847 | self._right_encoder_counts = None 848 | self._light_bumper = None 849 | self._light_bump_left_signal = None 850 | self._light_bump_front_left_signal = None 851 | self._light_bump_center_left_signal = None 852 | self._light_bump_center_right_signal = None 853 | self._light_bump_front_right_signal = None 854 | self._light_bump_right_signal = None 855 | self._ir_character_left = None 856 | self._ir_character_right = None 857 | self._left_motor_current = None 858 | self._right_motor_current = None 859 | self._main_brush_motor_current = None 860 | self._side_brush_motor_current = None 861 | self._stasis = None 862 | 863 | @property 864 | def bumps_and_wheel_drops(self): 865 | if self._bumps_and_wheel_drops is None: 866 | self._bumps_and_wheel_drops = BumpsAndWheelDrop(self._data[0:1]) 867 | return self._bumps_and_wheel_drops 868 | 869 | @property 870 | def wall_sensor(self): 871 | if self._wall_sensor is None: 872 | self._wall_sensor = binary_response(self._data[1:2]) 873 | return self._wall_sensor 874 | 875 | @property 876 | def cliff_left_sensor(self): 877 | if self._cliff_left_sensor is None: 878 | self._cliff_left_sensor = binary_response(self._data[2:3]) 879 | return self._cliff_left_sensor 880 | 881 | @property 882 | def cliff_front_left_sensor(self): 883 | if self._cliff_front_left_sensor is None: 884 | self._cliff_front_left_sensor = binary_response(self._data[3:4]) 885 | return self._cliff_front_left_sensor 886 | 887 | @property 888 | def cliff_front_right_sensor(self): 889 | if self._cliff_front_right_sensor is None: 890 | self._cliff_front_right_sensor = binary_response(self._data[4:5]) 891 | return self._cliff_front_right_sensor 892 | 893 | @property 894 | def cliff_right_sensor(self): 895 | if self._cliff_right_sensor is None: 896 | self._cliff_right_sensor = binary_response(self._data[5:6]) 897 | return self._cliff_right_sensor 898 | 899 | @property 900 | def virtual_wall_sensor(self): 901 | if self._virtual_wall_sensor is None: 902 | self._virtual_wall_sensor = binary_response(self._data[6:7]) 903 | return self._virtual_wall_sensor 904 | 905 | @property 906 | def wheel_overcurrents(self): 907 | if self._wheel_overcurrents is None: 908 | self._wheel_overcurrents = WheelOvercurrents(self._data[7:8]) 909 | return self._wheel_overcurrents 910 | 911 | @property 912 | def dirt_detect_sensor(self): 913 | if self._dirt_detect_sensor is None: 914 | self._dirt_detect_sensor = byte_response(self._data[8:9]) 915 | return self._dirt_detect_sensor 916 | 917 | @property 918 | def ir_char_omni_sensor(self): 919 | if self._ir_char_omni_sensor is None: 920 | self._ir_char_omni_sensor = unsigned_byte_response(self._data[9:10]) 921 | return self._ir_char_omni_sensor 922 | 923 | @property 924 | def buttons(self): 925 | if self._buttons is None: 926 | self._buttons = Buttons(self._data[11:12]) 927 | return self._buttons 928 | 929 | @property 930 | def distance(self): 931 | if self._distance is None: 932 | self._distance = short_response(self._data[12:14]) 933 | return self._distance 934 | 935 | @property 936 | def angle(self): 937 | if self._angle is None: 938 | self._angle = short_response(self._data[14:16]) 939 | return self._angle 940 | 941 | @property 942 | def charging_state(self): 943 | if self._charging_state is None: 944 | self._charging_state = unsigned_byte_response(self._data[16:17]) 945 | return self._charging_state 946 | 947 | @property 948 | def voltage(self): 949 | if self._voltage is None: 950 | self._voltage = unsigned_short_response(self._data[17:19]) 951 | return self._voltage 952 | 953 | @property 954 | def current(self): 955 | if self._current is None: 956 | self._current = short_response(self._data[19:21]) 957 | return self._current 958 | 959 | @property 960 | def temperature(self): 961 | if self._temperature is None: 962 | self._temperature = byte_response(self._data[21:22]) 963 | return self._temperature 964 | 965 | @property 966 | def battery_charge(self): 967 | if self._battery_charge is None: 968 | self._battery_charge = unsigned_short_response(self._data[22:24]) 969 | return self._battery_charge 970 | 971 | @property 972 | def battery_capacity(self): 973 | if self._battery_capacity is None: 974 | self._battery_capacity = unsigned_short_response(self._data[24:26]) 975 | return self._battery_capacity 976 | 977 | @property 978 | def wall_signal(self): 979 | if self._wall_signal is None: 980 | self._wall_signal = unsigned_short_response(self._data[26:28]) 981 | return self._wall_signal 982 | 983 | @property 984 | def cliff_left_signal(self): 985 | if self._cliff_left_signal is None: 986 | self._cliff_left_signal = unsigned_short_response(self._data[28:30]) 987 | return self._cliff_left_signal 988 | 989 | @property 990 | def cliff_front_left_signal(self): 991 | if self._cliff_front_left_signal is None: 992 | self._cliff_front_left_signal = unsigned_short_response(self._data[30:32]) 993 | return self._cliff_front_left_signal 994 | 995 | @property 996 | def cliff_front_right_signal(self): 997 | if self._cliff_front_right_signal is None: 998 | self._cliff_front_right_signal = unsigned_short_response(self._data[32:34]) 999 | return self._cliff_front_right_signal 1000 | 1001 | @property 1002 | def cliff_right_signal(self): 1003 | if self._cliff_right_signal is None: 1004 | self._cliff_right_signal = unsigned_short_response(self._data[34:36]) 1005 | return self._cliff_right_signal 1006 | 1007 | @property 1008 | def charging_sources(self): 1009 | if self._charging_sources is None: 1010 | self._charging_sources = ChargingSources(self._data[39:40]) 1011 | return self._charging_sources 1012 | 1013 | @property 1014 | def oi_mode(self): 1015 | if self._oi_mode is None: 1016 | self._oi_mode = unsigned_byte_response(self._data[40:41]) 1017 | return self._oi_mode 1018 | 1019 | @property 1020 | def song_number(self): 1021 | if self._song_number is None: 1022 | self._song_number = unsigned_byte_response(self._data[41:42]) 1023 | return self._song_number 1024 | 1025 | @property 1026 | def is_song_playing(self): 1027 | if self._is_song_playing is None: 1028 | self._is_song_playing = binary_response(self._data[42:43]) 1029 | return self._is_song_playing 1030 | 1031 | @property 1032 | def number_of_stream_packets(self): 1033 | if self._number_of_stream_packets is None: 1034 | self._number_of_stream_packets = unsigned_byte_response(self._data[43:44]) 1035 | return self._number_of_stream_packets 1036 | 1037 | @property 1038 | def requested_velocity(self): 1039 | if self._requested_velocity is None: 1040 | self._requested_velocity = short_response(self._data[44:46]) 1041 | return self._requested_velocity 1042 | 1043 | @property 1044 | def requested_radius(self): 1045 | if self._requested_radius is None: 1046 | self._requested_radius = short_response(self._data[46:48]) 1047 | return self._requested_radius 1048 | 1049 | @property 1050 | def requested_right_velocity(self): 1051 | if self._requested_right_velocity is None: 1052 | self._requested_right_velocity = short_response(self._data[48:50]) 1053 | return self._requested_right_velocity 1054 | 1055 | @property 1056 | def requested_left_velocity(self): 1057 | if self._requested_left_velocity is None: 1058 | self._requested_left_velocity = short_response(self._data[50:52]) 1059 | return self._requested_left_velocity 1060 | 1061 | @property 1062 | def left_encoder_counts(self): 1063 | if self._left_encoder_counts is None: 1064 | self._left_encoder_counts = unsigned_short_response(self._data[52:54]) 1065 | return self._left_encoder_counts 1066 | 1067 | @property 1068 | def right_encoder_counts(self): 1069 | if self._right_encoder_counts is None: 1070 | self._right_encoder_counts = unsigned_short_response(self._data[54:56]) 1071 | return self._right_encoder_counts 1072 | 1073 | @property 1074 | def light_bumper(self): 1075 | if self._light_bumper is None: 1076 | self._light_bumper = LightBumper(self._data[56:57]) 1077 | return self._light_bumper 1078 | 1079 | @property 1080 | def light_bump_left_signal(self): 1081 | if self._light_bump_left_signal is None: 1082 | self._light_bump_left_signal = unsigned_short_response(self._data[57:59]) 1083 | return self._light_bump_left_signal 1084 | 1085 | @property 1086 | def light_bump_front_left_signal(self): 1087 | if self._light_bump_front_left_signal is None: 1088 | self._light_bump_front_left_signal = unsigned_short_response(self._data[59:61]) 1089 | return self._light_bump_front_left_signal 1090 | 1091 | @property 1092 | def light_bump_center_left_signal(self): 1093 | if self._light_bump_center_left_signal is None: 1094 | self._light_bump_center_left_signal = unsigned_short_response(self._data[61:63]) 1095 | return self._light_bump_center_left_signal 1096 | 1097 | @property 1098 | def light_bump_center_right_signal(self): 1099 | if self._light_bump_center_right_signal is None: 1100 | self._light_bump_center_right_signal = unsigned_short_response(self._data[63:65]) 1101 | return self._light_bump_center_right_signal 1102 | 1103 | @property 1104 | def light_bump_front_right_signal(self): 1105 | if self._light_bump_front_right_signal is None: 1106 | self._light_bump_front_right_signal = unsigned_short_response(self._data[65:67]) 1107 | return self._light_bump_front_right_signal 1108 | 1109 | @property 1110 | def light_bump_right_signal(self): 1111 | if self._light_bump_right_signal is None: 1112 | self._light_bump_right_signal = unsigned_short_response(self._data[67:69]) 1113 | return self._light_bump_right_signal 1114 | 1115 | @property 1116 | def ir_character_left(self): 1117 | if self._ir_character_left is None: 1118 | self._ir_character_left = unsigned_byte_response(self._data[69:70]) 1119 | return self._ir_character_left 1120 | 1121 | @property 1122 | def ir_character_right(self): 1123 | if self._ir_character_right is None: 1124 | self._ir_character_right = unsigned_byte_response(self._data[70:71]) 1125 | return self._ir_character_right 1126 | 1127 | @property 1128 | def left_motor_current(self): 1129 | if self._left_motor_current is None: 1130 | self._left_motor_current = short_response(self._data[71:73]) 1131 | return self._left_motor_current 1132 | 1133 | @property 1134 | def right_motor_current(self): 1135 | if self._right_motor_current is None: 1136 | self._right_motor_current = short_response(self._data[73:75]) 1137 | return self._right_motor_current 1138 | 1139 | @property 1140 | def main_brush_motor_current(self): 1141 | if self._main_brush_motor_current is None: 1142 | self._main_brush_motor_current = short_response(self._data[75:77]) 1143 | return self._main_brush_motor_current 1144 | 1145 | @property 1146 | def side_brush_motor_current(self): 1147 | if self._side_brush_motor_current is None: 1148 | self._side_brush_motor_current = short_response(self._data[77:79]) 1149 | return self._side_brush_motor_current 1150 | 1151 | @property 1152 | def stasis(self): 1153 | if self._stasis is None: 1154 | self._stasis = Stasis(self._data[79:80]) 1155 | return self._stasis 1156 | 1157 | 1158 | class SensorGroup101(object): 1159 | def __init__(self, data): 1160 | self._data = data 1161 | self._left_encoder_counts = None 1162 | self._right_encoder_counts = None 1163 | self._light_bumper = None 1164 | self._light_bump_left_signal = None 1165 | self._light_bump_front_left_signal = None 1166 | self._light_bump_center_left_signal = None 1167 | self._light_bump_center_right_signal = None 1168 | self._light_bump_front_right_signal = None 1169 | self._light_bump_right_signal = None 1170 | self._ir_character_left = None 1171 | self._ir_character_right = None 1172 | self._left_motor_current = None 1173 | self._right_motor_current = None 1174 | self._main_brush_motor_current = None 1175 | self._side_brush_motor_current = None 1176 | self._stasis = None 1177 | 1178 | @property 1179 | def left_encoder_counts(self): 1180 | if self._left_encoder_counts is None: 1181 | self._left_encoder_counts = unsigned_short_response(self._data[0:2]) 1182 | return self._left_encoder_counts 1183 | 1184 | @property 1185 | def right_encoder_counts(self): 1186 | if self._right_encoder_counts is None: 1187 | self._right_encoder_counts = unsigned_short_response(self._data[2:4]) 1188 | return self._right_encoder_counts 1189 | 1190 | @property 1191 | def light_bumper(self): 1192 | if self._light_bumper is None: 1193 | self._light_bumper = LightBumper(self._data[4:5]) 1194 | return self._light_bumper 1195 | 1196 | @property 1197 | def light_bump_left_signal(self): 1198 | if self._light_bump_left_signal is None: 1199 | self._light_bump_left_signal = unsigned_short_response(self._data[5:7]) 1200 | return self._light_bump_left_signal 1201 | 1202 | @property 1203 | def light_bump_front_left_signal(self): 1204 | if self._light_bump_front_left_signal is None: 1205 | self._light_bump_front_left_signal = unsigned_short_response(self._data[7:9]) 1206 | return self._light_bump_front_left_signal 1207 | 1208 | @property 1209 | def light_bump_center_left_signal(self): 1210 | if self._light_bump_center_left_signal is None: 1211 | self._light_bump_center_left_signal = unsigned_short_response(self._data[9:11]) 1212 | return self._light_bump_center_left_signal 1213 | 1214 | @property 1215 | def light_bump_center_right_signal(self): 1216 | if self._light_bump_center_right_signal is None: 1217 | self._light_bump_center_right_signal = unsigned_short_response(self._data[11:13]) 1218 | return self._light_bump_center_right_signal 1219 | 1220 | @property 1221 | def light_bump_front_right_signal(self): 1222 | if self._light_bump_front_right_signal is None: 1223 | self._light_bump_front_right_signal = unsigned_short_response(self._data[13:15]) 1224 | return self._light_bump_front_right_signal 1225 | 1226 | @property 1227 | def light_bump_right_signal(self): 1228 | if self._light_bump_right_signal is None: 1229 | self._light_bump_right_signal = unsigned_short_response(self._data[15:17]) 1230 | return self._light_bump_right_signal 1231 | 1232 | @property 1233 | def ir_character_left(self): 1234 | if self._ir_character_left is None: 1235 | self._ir_character_left = unsigned_byte_response(self._data[17:18]) 1236 | return self._ir_character_left 1237 | 1238 | @property 1239 | def ir_character_right(self): 1240 | if self._ir_character_right is None: 1241 | self._ir_character_right = unsigned_byte_response(self._data[18:19]) 1242 | return self._ir_character_right 1243 | 1244 | @property 1245 | def left_motor_current(self): 1246 | if self._left_motor_current is None: 1247 | self._left_motor_current = short_response(self._data[19:21]) 1248 | return self._left_motor_current 1249 | 1250 | @property 1251 | def right_motor_current(self): 1252 | if self._right_motor_current is None: 1253 | self._right_motor_current = short_response(self._data[21:23]) 1254 | return self._right_motor_current 1255 | 1256 | @property 1257 | def main_brush_motor_current(self): 1258 | if self._main_brush_motor_current is None: 1259 | self._main_brush_motor_current = short_response(self._data[23:25]) 1260 | return self._main_brush_motor_current 1261 | 1262 | @property 1263 | def side_brush_motor_current(self): 1264 | if self._side_brush_motor_current is None: 1265 | self._side_brush_motor_current = short_response(self._data[25:27]) 1266 | return self._side_brush_motor_current 1267 | 1268 | @property 1269 | def stasis(self): 1270 | if self._stasis is None: 1271 | self._stasis = Stasis(self._data[27:28]) 1272 | return self._stasis 1273 | 1274 | 1275 | class SensorGroup106(object): 1276 | def __init__(self, data): 1277 | self._data = data 1278 | self._light_bump_left_signal = None 1279 | self._light_bump_front_left_signal = None 1280 | self._light_bump_center_left_signal = None 1281 | self._light_bump_center_right_signal = None 1282 | self._light_bump_front_right_signal = None 1283 | self._light_bump_right_signal = None 1284 | 1285 | @property 1286 | def light_bump_left_signal(self): 1287 | if self._light_bump_left_signal is None: 1288 | self._light_bump_left_signal = unsigned_short_response(self._data[0:2]) 1289 | return self._light_bump_left_signal 1290 | 1291 | @property 1292 | def light_bump_front_left_signal(self): 1293 | if self._light_bump_front_left_signal is None: 1294 | self._light_bump_front_left_signal = unsigned_short_response(self._data[2:4]) 1295 | return self._light_bump_front_left_signal 1296 | 1297 | @property 1298 | def light_bump_center_left_signal(self): 1299 | if self._light_bump_center_left_signal is None: 1300 | self._light_bump_center_left_signal = unsigned_short_response(self._data[4:6]) 1301 | return self._light_bump_center_left_signal 1302 | 1303 | @property 1304 | def light_bump_center_right_signal(self): 1305 | if self._light_bump_center_right_signal is None: 1306 | self._light_bump_center_right_signal = unsigned_short_response(self._data[6:8]) 1307 | return self._light_bump_center_right_signal 1308 | 1309 | @property 1310 | def light_bump_front_right_signal(self): 1311 | if self._light_bump_front_right_signal is None: 1312 | self._light_bump_front_right_signal = unsigned_short_response(self._data[8:10]) 1313 | return self._light_bump_front_right_signal 1314 | 1315 | @property 1316 | def light_bump_right_signal(self): 1317 | if self._light_bump_right_signal is None: 1318 | self._light_bump_right_signal = unsigned_short_response(self._data[10:12]) 1319 | return self._light_bump_right_signal 1320 | 1321 | 1322 | class SensorGroup107(object): 1323 | def __init__(self, data): 1324 | self._data = data 1325 | self._left_motor_current = None 1326 | self._right_motor_current = None 1327 | self._main_brush_motor_current = None 1328 | self._side_brush_motor_current = None 1329 | self._stasis = None 1330 | 1331 | @property 1332 | def left_motor_current(self): 1333 | if self._left_motor_current is None: 1334 | self._left_motor_current = short_response(self._data[0:2]) 1335 | return self._left_motor_current 1336 | 1337 | @property 1338 | def right_motor_current(self): 1339 | if self._right_motor_current is None: 1340 | self._right_motor_current = short_response(self._data[2:4]) 1341 | return self._right_motor_current 1342 | 1343 | @property 1344 | def main_brush_motor_current(self): 1345 | if self._main_brush_motor_current is None: 1346 | self._main_brush_motor_current = short_response(self._data[4:6]) 1347 | return self._main_brush_motor_current 1348 | 1349 | @property 1350 | def side_brush_motor_current(self): 1351 | if self._side_brush_motor_current is None: 1352 | self._side_brush_motor_current = short_response(self._data[6:8]) 1353 | return self._side_brush_motor_current 1354 | 1355 | @property 1356 | def stasis(self): 1357 | if self._stasis is None: 1358 | self._stasis = Stasis(self._data[8:9]) 1359 | return self._stasis 1360 | --------------------------------------------------------------------------------