├── .github └── workflows │ └── python.yaml ├── .gitignore ├── License ├── docs ├── Markdown │ └── readme.md └── iRobot │ ├── BatteryPower.pdf │ ├── Bluetooth_Serial.pdf │ ├── Create2_PrimeSense.pdf │ ├── Create_2_Deco_Cover_Modification.pdf │ ├── Create_2_Serial_to_33V_Logic.pdf │ ├── Create_2_Serial_to_USB_Cable_Creation.pdf │ └── iRobot_Roomba_600_Open_Interface_Spec.pdf ├── examples ├── displayExample.py ├── playSong.py ├── runExample.py ├── runQuery.py └── sensorExample.py ├── pics ├── 7-pin-din.jpg ├── ascii.png ├── battery.jpg ├── create.png ├── create_modes.png ├── din_pinout.png ├── midi.png ├── quick-ref.png ├── robot.jpg ├── sensor-packet-size.png └── sensor-packets.png ├── pycreate2 ├── OI.py ├── __init__.py ├── bin │ ├── create_monitor.py │ ├── create_reset.py │ └── create_shutdown.py ├── create2api.py ├── createSerial.py └── packets.py ├── pyproject.toml ├── readme.md └── tests └── test_pycreate.py /.github/workflows/python.yaml: -------------------------------------------------------------------------------- 1 | name: CheckPackage 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | max-parallel: 5 9 | matrix: 10 | python-version: ["3.8", "3.9","3.10","3.11"] 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: Setup Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Install packages 18 | run: | 19 | echo "Installing dependencies" 20 | python3 -m venv .venv 21 | source .venv/bin/activate 22 | pip install -U pip setuptools wheel poetry pytest 23 | poetry install 24 | pytest tests/ 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | old/ 3 | .DS_Store 4 | ._* 5 | __pycache__ 6 | build/ 7 | dist/ 8 | *.egg-info 9 | .pytest_cache 10 | .mypy_cache 11 | poetry.lock 12 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2007 Damon Kohler 4 | Copyright (c) 2015 Jonathan Le Roux (Modifications for Create 2) 5 | Copyright (c) 2015 Brandon Pomeroy 6 | Copyright (c) 2017 Kevin J. Walchko 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal in 10 | the Software without restriction, including without limitation the rights to 11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 | of the Software, and to permit persons to whom the Software is furnished to do 13 | so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 20 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 21 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /docs/Markdown/readme.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | Some clarification on the Roomba Open Interface and this library. 4 | 5 | ## Implemented OI codes 6 | 7 | Not everything is implemented. Eventually, I will put some documentation here, but right now it is probably easier to just read the code or look at the examples. 8 | 9 | - Start 10 | - Reset 11 | - Stop 12 | - Safe 13 | - Full 14 | - Seek Dock 15 | - Power 16 | - Drive 17 | - Digit LED ASCII 18 | - Sensors 19 | - Song 20 | - Play 21 | - Query List 22 | 23 | # Useful Tables and Figures from the Manual 24 | 25 | ![](../../pics/din_pinout.png) 26 | 27 | ![](../../pics/ascii.png) 28 | 29 | ![](../../pics/midi.png) 30 | 31 | ![](../../pics/sensor-packet-size.png) 32 | 33 | ![](../../pics/sensor-packets.png) 34 | 35 | # More Mode Details 36 | 37 | Note, these are copied from the manual. 38 | 39 | ## Passive Mode 40 | 41 | Upon sending the Start command or any one of the cleaning mode commands (e.g., 42 | Spot, Clean, Seek Dock), the OI enters into Passive mode. When the OI is in 43 | Passive mode, you can request and receive sensor data using any of the sensor 44 | commands, but you cannot change the current command parameters for the actuators 45 | (motors, speaker, lights, low side drivers, digital outputs) to something else. 46 | To change how one of the actuators operates, you must switch from Passive mode 47 | to Full mode or Safe mode. 48 | 49 | While in Passive mode, you can read Roomba’s sensors, watch Roomba perform a 50 | cleaning cycle, and charge the battery. 51 | 52 | In Passive mode, Roomba will go into power saving mode to conserve battery 53 | power after five minutes of inactivity. To disable sleep, pulse the BRC pin low 54 | periodically before these five minutes expire. Each pulse resets this five 55 | minute counter. (One example that would not cause the baud rate to inadvertently 56 | change is to pulse the pin low for one second, every minute, but there are other 57 | periods and duty cycles that would work, as well.) 58 | 59 | ## Safe Mode 60 | 61 | When you send a Safe command to the OI, Roomba enters into Safe mode. Safe mode 62 | gives you full control of Roomba, with the exception of the following safety-related 63 | conditions: 64 | 65 | - Detection of a cliff while moving forward (or moving backward with a small turning radius, less than one robot radius) 66 | - Detection of a wheel drop (on any wheel) 67 | - Charger plugged in and powered 68 | 69 | Should one of the above safety-related conditions occur while the OI is in Safe 70 | mode, Roomba stops all motors and reverts to the Passive mode. 71 | 72 | If no commands are sent to the OI when in Safe mode, Roomba waits with all motors 73 | and LEDs off and does not respond to button presses or other sensor input. 74 | 75 | Note that charging terminates when you enter Safe Mode, and Roomba will not power 76 | save. 77 | 78 | ## Full Mode 79 | 80 | When you send a Full command to the OI, Roomba enters into Full mode. Full mode 81 | gives you complete control over Roomba, all of its actuators, and all of the 82 | safety-related conditions that are restricted when the OI is in Safe mode, as 83 | Full mode shuts off the cliff, wheel-drop and internal charger safety features. 84 | To put the OI back into Safe mode, you must send the Safe command. 85 | 86 | If no commands are sent to the OI when in Full mode, Roomba waits with all motors 87 | and LEDs off and does not respond to button presses or other sensor input. 88 | 89 | Note that charging terminates when you enter Full Mode, and Roomba will not power 90 | save. 91 | 92 | # Encoders 93 | 94 | From the manual. 95 | 96 | **NOTE:** These encoders are square wave, not quadrature, so they rely on the 97 | robot’s commanded velocity direction to know when to count up/down. So if the 98 | robot is trying to drive forward, and you force the wheels to spin in reverse, 99 | the encoders will count up, (and vice-versa). Additionally, the encoders will 100 | count up when the commanded velocity is zero and the wheels spin. 101 | 102 | To convert counts to distance, simply do a unit conversion using the equation 103 | for circle circumference. 104 | 105 | - N counts * (mm in 1 wheel revolution / counts in 1 wheel revolution) = mm 106 | - N counts * (pi * 72.0 / 508.8) = mm 107 | 108 | # Issues 109 | 110 | There are numerous issues with the Create 2, like the version before it. 111 | 112 | - the encoders aren't that great 113 | - why are they using a DIN-7 for the interface, what is this ... the 80's? Ok, maybe in the 90's when this all started that was fine, but it is many decades later ... do they have stock in DIN connectors? 114 | - there is no power available for an arduino or raspberry pi from the provided USB cable, you have to make your own and some people have reported it didn't work. It is just easier, probably, to get another battery to power your processor 115 | - the song function is finicky and doesn't always work right 116 | - roomba can become confused and the only way to reset it is to unscrew the base plate and pull the battery ... great job geniuses!! 117 | - the robot seems to refuse to report its current mode (response to packet 35) ... I never get a response 118 | - Why didn't iRobot drill the hole for the serial port (DIN-7) for us? It is labelled as a programming robot and the only way to program it is through that hole. I guess it was either paint the cover green or give us a useful item out of the box. 119 | - there are numerous reports that the USB cable is wired wrong, so trying to wake the roomba by toggling the BRC pin (RTS on serial) doesn't work. I think I got it to work once on my roomba, but it is not repeatable or reliable. 120 | - basically the firmware of the roomba is shit, closed source, and full of vacuum functions that shouldn't be there anymore. I know it is basically a reconditioned vacuum, but a separate firmware (open source maybe) would be great to fix all the ills of the robot. Also, I can't find anyway to update the firmware either ... there is probably some super painful way to do it, but I haven't found it yet. 121 | - I think some intern wrote this mess of a firmware 122 | - basically, iRobot is just trying to make more money by pushing old crap, but there aren't a lot of good alternatives ... yes I am am ranting. 123 | 124 | ## macOS 125 | 126 | Apple's [USB-A-to-C](https://www.apple.com/shop/product/MJ1M2AM/A/usb-c-to-usb-adapter) 127 | converter doesn't work with iRobot's USB-to-serial converter. I used a 128 | [Monoprice USB-C Hub](https://www.amazon.com/gp/product/B019FN66IC/ref=oh_aui_detailpage_o03_s01?ie=UTF8&psc=1) 129 | and it worked fine. 130 | -------------------------------------------------------------------------------- /docs/iRobot/BatteryPower.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/docs/iRobot/BatteryPower.pdf -------------------------------------------------------------------------------- /docs/iRobot/Bluetooth_Serial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/docs/iRobot/Bluetooth_Serial.pdf -------------------------------------------------------------------------------- /docs/iRobot/Create2_PrimeSense.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/docs/iRobot/Create2_PrimeSense.pdf -------------------------------------------------------------------------------- /docs/iRobot/Create_2_Deco_Cover_Modification.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/docs/iRobot/Create_2_Deco_Cover_Modification.pdf -------------------------------------------------------------------------------- /docs/iRobot/Create_2_Serial_to_33V_Logic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/docs/iRobot/Create_2_Serial_to_33V_Logic.pdf -------------------------------------------------------------------------------- /docs/iRobot/Create_2_Serial_to_USB_Cable_Creation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/docs/iRobot/Create_2_Serial_to_USB_Cable_Creation.pdf -------------------------------------------------------------------------------- /docs/iRobot/iRobot_Roomba_600_Open_Interface_Spec.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/docs/iRobot/iRobot_Roomba_600_Open_Interface_Spec.pdf -------------------------------------------------------------------------------- /examples/displayExample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #-*-coding:utf-8-*- 3 | ############################################## 4 | # The MIT License (MIT) 5 | # Copyright (c) 2017 Kevin Walchko 6 | # see LICENSE for full details 7 | ############################################## 8 | # display random characters to the roomba display. Note, there are some that 9 | # roomba can't print, those are changed to ' ' 10 | import pycreate2 11 | import time 12 | import string 13 | import random 14 | 15 | 16 | if __name__ == "__main__": 17 | # Create a Create2 Bot 18 | port = '/dev/tty.usbserial-DA01NX3Z' 19 | 20 | # setup create 2 21 | bot = pycreate2.Create2(port) 22 | bot.start() 23 | bot.safe() 24 | 25 | # get the set of all printable ascii characters 26 | char_set = string.printable 27 | 28 | print('WARNING: Not all of the allowed printable characters really look good on the LCD') 29 | 30 | while True: 31 | word = ''.join(random.sample(char_set, 4)) 32 | print('phrase:', word) 33 | bot.digit_led_ascii(word) 34 | time.sleep(2) 35 | -------------------------------------------------------------------------------- /examples/playSong.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #-*-coding:utf-8-*- 3 | ############################################## 4 | # The MIT License (MIT) 5 | # Copyright (c) 2017 Kevin Walchko 6 | # see LICENSE for full details 7 | ############################################## 8 | # play a song 9 | 10 | import pycreate2 11 | import time 12 | 13 | 14 | if __name__ == "__main__": 15 | port = '/dev/tty.usbserial-DA01NX3Z' 16 | # port = "/dev/serial/by-id/usb-FTDI_FT231X_USB_UART_DA01NX3Z-if00-port0" 17 | 18 | bot = pycreate2.Create2(port=port) 19 | bot.start() 20 | bot.full() 21 | 22 | # random MIDI songs I found on the internet 23 | # they cannot be more than 16 midi notes or really 32 bytes arranged 24 | # as [(note, duration), ...] 25 | song = [59, 64, 62, 32, 69, 96, 67, 64, 62, 32, 60, 96, 59, 64, 59, 32, 59, 32, 60, 32, 62, 32, 64, 96, 62, 96] 26 | song = [76, 16, 76, 16, 76, 32, 76, 16, 76, 16, 76, 32, 76, 16, 79, 16, 72, 16, 74, 16, 76, 32, 77, 16, 77, 16, 77, 16, 77, 32, 77, 16] 27 | song = [76, 12, 76, 12, 20, 12, 76, 12, 20, 12, 72, 12, 76, 12, 20, 12, 79, 12, 20, 36, 67, 12, 20, 36] 28 | song = [72, 12, 20, 24, 67, 12, 20, 24, 64, 24, 69, 16, 71, 16, 69, 16, 68, 24, 70, 24, 68, 24, 67, 12, 65, 12, 67, 48] 29 | 30 | print(">> song len: ", len(song)//2) 31 | 32 | # song number can be 0-3 33 | song_num = 3 34 | bot.createSong(song_num, song) 35 | time.sleep(0.1) 36 | how_long = bot.playSong(song_num) 37 | 38 | # The song will run in the back ground, don't interrupt it 39 | # how_long is the time in seconds for it to finish 40 | print('Sleep for:', how_long) 41 | time.sleep(how_long) 42 | -------------------------------------------------------------------------------- /examples/runExample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #-*-coding:utf-8-*- 3 | ############################################## 4 | # The MIT License (MIT) 5 | # Copyright (c) 2017 Kevin Walchko 6 | # see LICENSE for full details 7 | ############################################## 8 | # moves the roomba through a simple sequence 9 | 10 | import pycreate2 11 | import time 12 | 13 | 14 | if __name__ == "__main__": 15 | # Create a Create2 Bot 16 | port = '/dev/tty.usbserial-DA01NX3Z' # this is the serial port on my iMac 17 | # port = '/dev/ttyUSB0' # this is the serial port on my raspberry pi 18 | baud = { 19 | 'default': 115200, 20 | 'alt': 19200 # shouldn't need this unless you accidentally set it to this 21 | } 22 | 23 | bot = pycreate2.Create2(port=port, baud=baud['default']) 24 | 25 | # define a movement path 26 | path = [ 27 | [ 200, 200, 3, 'for'], 28 | [-200,-200, 3, 'back'], 29 | [ 0, 0, 1, 'stop'], 30 | [ 100, 0, 2, 'rite'], 31 | [ 0, 100, 4, 'left'], 32 | [ 100, 0, 2, 'rite'], 33 | [ 0, 0, 1, 'stop'] 34 | ] 35 | 36 | bot.start() 37 | bot.safe() 38 | 39 | # path to move 40 | for lft, rht, dt, s in path: 41 | print(s) 42 | bot.digit_led_ascii(s) 43 | bot.drive_direct(lft, rht) 44 | time.sleep(dt) 45 | 46 | print('shutting down ... bye') 47 | bot.drive_stop() 48 | time.sleep(0.1) 49 | -------------------------------------------------------------------------------- /examples/runQuery.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #-*-coding:utf-8-*- 3 | ############################################## 4 | # The MIT License (MIT) 5 | # Copyright (c) 2017 Kevin Walchko 6 | # see LICENSE for full details 7 | ############################################## 8 | 9 | import pycreate2 10 | import time 11 | 12 | 13 | def prettyPrint(sensors): 14 | print('-'*70) 15 | print('{:>40} | {:<5}'.format('Sensor', 'Value')) 16 | print('-'*70) 17 | for k, v in sensors._asdict().items(): 18 | print(f"{k}: {v}") 19 | 20 | 21 | if __name__ == "__main__": 22 | # Create a Create2 Bot 23 | port = '/dev/tty.usbserial-DA01NX3Z' 24 | baud = { 25 | 'default': 115200, 26 | 'alt': 19200 # shouldn't need this unless you accidentally set it to this 27 | } 28 | 29 | # setup create 2 30 | bot = pycreate2.Create2(port) 31 | bot.start() 32 | bot.safe() 33 | 34 | bot.digit_led_ascii('hi') # set a nice message 35 | bot.led(1) # turn on debris light 36 | 37 | sensors = {} 38 | 39 | try: 40 | while True: 41 | sensors = bot.get_sensors() 42 | if sensors: 43 | prettyPrint(sensors) 44 | else: 45 | print('robot asleep') 46 | 47 | time.sleep(1) 48 | 49 | except KeyboardInterrupt: 50 | print('shutting down ... bye') 51 | -------------------------------------------------------------------------------- /examples/sensorExample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #-*-coding:utf-8-*- 3 | ############################################## 4 | # The MIT License (MIT) 5 | # Copyright (c) 2017 Kevin Walchko 6 | # see LICENSE for full details 7 | ############################################## 8 | 9 | from pycreate2 import Create2 10 | import time 11 | 12 | 13 | if __name__ == "__main__": 14 | port = '/dev/tty.usbserial-DA01NX3Z' 15 | baud = { 16 | 'default': 115200, 17 | 'alt': 19200 # shouldn't need this unless you accidentally set it to this 18 | } 19 | 20 | bot = Create2(port=port, baud=baud['default']) 21 | 22 | bot.start() 23 | 24 | bot.safe() 25 | 26 | print('Starting ...') 27 | 28 | cnt = 0 29 | while True: 30 | # Packet 100 contains all sensor data. 31 | sensor = bot.get_sensors() 32 | 33 | if cnt%20 == 0: 34 | print("[L ] [LF] [LC] [CR] [RF] [ R]") 35 | 36 | print(f"{sensor.light_bumper_left:4} {sensor.light_bumper_front_left:4} {sensor.light_bumper_center_left:4} {sensor.light_bumper_center_right:4} {sensor.light_bumper_front_right:4} {sensor.light_bumper_right:4}") 37 | time.sleep(.01) 38 | 39 | cnt += 1 40 | -------------------------------------------------------------------------------- /pics/7-pin-din.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/7-pin-din.jpg -------------------------------------------------------------------------------- /pics/ascii.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/ascii.png -------------------------------------------------------------------------------- /pics/battery.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/battery.jpg -------------------------------------------------------------------------------- /pics/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/create.png -------------------------------------------------------------------------------- /pics/create_modes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/create_modes.png -------------------------------------------------------------------------------- /pics/din_pinout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/din_pinout.png -------------------------------------------------------------------------------- /pics/midi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/midi.png -------------------------------------------------------------------------------- /pics/quick-ref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/quick-ref.png -------------------------------------------------------------------------------- /pics/robot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/robot.jpg -------------------------------------------------------------------------------- /pics/sensor-packet-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/sensor-packet-size.png -------------------------------------------------------------------------------- /pics/sensor-packets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomsFriendlyRobotCompany/pycreate2/d0ae3b2d97b29b2c0f5fefe7085e705ebfbe5b4c/pics/sensor-packets.png -------------------------------------------------------------------------------- /pycreate2/OI.py: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # The MIT License (MIT) 3 | # Copyright (c) 2017 Kevin Walchko 4 | # see LICENSE for full details 5 | ############################################## 6 | # I took some of these ideas from: https://bitbucket.org/lemoneer/irobot 7 | 8 | # could replace with: 9 | # class LEDS(object): DEBRIS=0x01; SPOT=0x02; DOCK=0x04; CHECK_ROBOT=0x08 10 | class Namespace(object): 11 | def __init__(self, **kwds): 12 | self.__dict__.update(kwds) 13 | 14 | 15 | BAUD_RATE = Namespace(BAUD_300=0, BAUD_600=1, BAUD_1200=2, BAUD_2400=3, BAUD_4800=4, BAUD_9600=5, BAUD_14400=6, BAUD_19200=7, BAUD_28800=8, BAUD_38400=9, BAUD_57600=10, BAUD_115200=11, DEFAULT=11) 16 | DAYS = Namespace(SUNDAY=0x01, MONDAY=0x02, TUESDAY=0x04, WEDNESDAY=0x08, THURSDAY=0x10, FRIDAY=0x20, SATURDAY=0x40) 17 | DRIVE = Namespace(STRAIGHT=0x8000, STRAIGHT_ALT=0x7FFF, TURN_CW=-1, TURN_CCW=0x0001) 18 | MOTORS = Namespace(SIDE_BRUSH=0x01, VACUUM=0x02, MAIN_BRUSH=0x04, SIDE_BRUSH_DIRECTION=0x08, MAIN_BRUSH_DIRECTION=0x10) 19 | LEDS = Namespace(DEBRIS=0x01, SPOT=0x02, DOCK=0x04, CHECK_ROBOT=0x08) 20 | # WEEKDAY_LEDS = Namespace(SUNDAY=0x01, MONDAY=0x02, TUESDAY=0x04, WEDNESDAY=0x08, THURSDAY=0x10, FRIDAY=0x20, SATURDAY=0x40) 21 | WEEKDAY_LEDS = DAYS 22 | SCHEDULING_LEDS = Namespace(COLON=0x01, PM=0x02, AM=0x04, CLOCK=0x08, SCHEDULE=0x10) 23 | RAW_LED = Namespace(A=0x01, B=0x02, C=0x04, D=0x08, E=0x10, F=0x20, G=0x40) 24 | BUTTONS = Namespace(CLEAN=0x01, SPOT=0x02, DOCK=0x04, MINUTE=0x08, HOUR=0x10, DAY=0x20, SCHEDULE=0x40, CLOCK=0x80) 25 | ROBOT = Namespace(TICK_PER_REV=508.8, WHEEL_DIAMETER=72, WHEEL_BASE=235, TICK_TO_DISTANCE=0.44456499814949904317867595046408) 26 | MODES = Namespace(OFF=0, PASSIVE=1, SAFE=2, FULL=3) 27 | WHEEL_OVERCURRENT = Namespace(SIDE_BRUSH=0x01, MAIN_BRUSH=0x02, RIGHT_WHEEL=0x04, LEFT_WHEEL=0x08) 28 | BUMPS_WHEEL_DROPS = Namespace(BUMP_RIGHT=0x01, BUMP_LEFT=0x02, WHEEL_DROP_RIGHT=0x04, WHEEL_DROP_LEFT=0x08) 29 | CHARGE_SOURCE = Namespace(INTERNAL=0x01, HOME_BASE=0x02) 30 | LIGHT_BUMPER = Namespace(LEFT=0x01, FRONT_LEFT=0x02, CENTER_LEFT=0x04, CENTER_RIGHT=0x08, FRONT_RIGHT=0x10, RIGHT=0x20) 31 | STASIS = Namespace(TOGGLING=0x01, DISABLED=0x02) 32 | OPCODES = Namespace( 33 | RESET=7, 34 | OI_MODE=35, 35 | START=128, 36 | # CONTROL=130, # oi spec, p 10, this is the same as SAFE 37 | SAFE=131, 38 | FULL=132, 39 | POWER=133, 40 | # SPOT=134, 41 | # CLEAN=135, 42 | # MAX=136, 43 | DRIVE=137, 44 | MOTORS=138, 45 | LED=139, 46 | SONG=140, 47 | PLAY=141, 48 | SENSORS=142, 49 | SEEK_DOCK=143, 50 | MOTORS_PWM=144, 51 | DRIVE_DIRECT=145, 52 | DRIVE_PWM=146, 53 | # STREAM=148, 54 | QUERY_LIST=149, 55 | # PAUSE_RESUME_STREAM=150, 56 | # SCHEDULING_LED=162, 57 | # DIGIT_LED_RAW=163, # doesn't work 58 | DIGIT_LED_ASCII=164, 59 | # BUTTONS=165, 60 | # SCHEDULE=167, 61 | # SET_DAY_TIME=168, 62 | STOP=173 63 | ) 64 | 65 | RESPONSE_SIZES = { 66 | 0: 26, 1: 10, 2: 6, 3: 10, 4: 14, 5: 12, 6: 52, 67 | # actual sensors 68 | 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, 69 | 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, 70 | 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, 71 | 50: 2, 51: 2, 52: 1, 53: 1, 54: 2, 55: 2, 56: 2, 57: 2, 58: 1, 72 | # end actual sensors 73 | 100: 80, 101: 28, 106: 12, 107: 9 74 | } 75 | 76 | 77 | def calc_query_data_len(pkts): 78 | packet_size = 0 79 | for p in pkts: 80 | packet_size += RESPONSE_SIZES[p] 81 | return packet_size 82 | 83 | 84 | CHARGING_STATE = { 85 | 0: 'not charging', 86 | 1: 'reconditioning charging', 87 | 2: 'full charging', 88 | 3: 'trickle charging', 89 | 4: 'waiting', 90 | 5: 'charging fault condition' 91 | } 92 | 93 | 94 | MIDI_TABLE = { 95 | "rest": 0, 96 | "G#1": 32, 97 | "G#3": 56, 98 | "G#2": 44, 99 | "G#5": 80, 100 | "G#4": 68, 101 | "G#7": 104, 102 | "G#6": 92, 103 | "G#8": 116, 104 | "G7": 103, 105 | "G6": 91, 106 | "G5": 79, 107 | "G4": 67, 108 | "G3": 55, 109 | "G2": 43, 110 | "G1": 31, 111 | "G9": 127, 112 | "G8": 115, 113 | "A7": 105, 114 | "D#9": 123, 115 | "A8": 117, 116 | "B4": 71, 117 | "B5": 83, 118 | "B6": 95, 119 | "B7": 107, 120 | "B1": 35, 121 | "B2": 47, 122 | "B3": 59, 123 | "B8": 119, 124 | "F#2": 42, 125 | "F#3": 54, 126 | "F#4": 66, 127 | "F#5": 78, 128 | "F#6": 90, 129 | "F#7": 102, 130 | "F#8": 114, 131 | "F#9": 126, 132 | "E9": 124, 133 | "E8": 112, 134 | "E5": 76, 135 | "E4": 64, 136 | "E7": 100, 137 | "E6": 88, 138 | "E3": 52, 139 | "E2": 40, 140 | "A#3": 58, 141 | "A#2": 46, 142 | "A#1": 34, 143 | "pause": 0, 144 | "A#7": 106, 145 | "A#6": 94, 146 | "A#5": 82, 147 | "A#4": 70, 148 | "A#8": 118, 149 | "C9": 120, 150 | "C8": 108, 151 | "C3": 48, 152 | "C2": 36, 153 | "C7": 96, 154 | "C6": 84, 155 | "C5": 72, 156 | "C4": 60, 157 | "R": 0, 158 | "F2": 41, 159 | "F3": 53, 160 | "F4": 65, 161 | "F5": 77, 162 | "F6": 89, 163 | "F7": 101, 164 | "F8": 113, 165 | "F9": 125, 166 | "A1": 33, 167 | "A3": 57, 168 | "A2": 45, 169 | "A5": 81, 170 | "A4": 69, 171 | "D#8": 111, 172 | "A6": 93, 173 | "D#6": 87, 174 | "D#7": 99, 175 | "D#4": 63, 176 | "D#5": 75, 177 | "D#2": 39, 178 | "D#3": 51, 179 | "C#9": 121, 180 | "C#8": 109, 181 | "C#5": 73, 182 | "C#4": 61, 183 | "C#7": 97, 184 | "C#6": 85, 185 | "C#3": 49, 186 | "C#2": 37, 187 | "D8": 110, 188 | "D9": 122, 189 | "D6": 86, 190 | "D7": 98, 191 | "D4": 62, 192 | "D5": 74, 193 | "D2": 38, 194 | "D3": 50 195 | } 196 | 197 | REMOTE_OPCODES = { 198 | 0: "none", 199 | 129: "left", 200 | 130: "forward", 201 | 131: "right", 202 | 132: "spot", 203 | 133: "max", 204 | 134: "small", 205 | 135: "medium", 206 | 136: "clean", 207 | 137: "pause", 208 | 138: "power", 209 | 139: "arc-left", 210 | 140: "arc-right", 211 | 141: "drive-stop", 212 | 142: "send-all", 213 | 143: "seek-dock", 214 | 160: "reserved", 215 | 161: "force-field", 216 | 162: "virtual-wall", 217 | 164: "green-buoy", 218 | 165: "green-buoy-and-force-field", 219 | 168: "red-buoy", 220 | 169: "red-buoy-and-force-field", 221 | 172: "red-buoy-and-green-buoy", 222 | 173: "red-buoy-and-green-buoy-and-force-field", 223 | 240: "reserved", 224 | 242: "force-field", 225 | 244: "green-buoy", 226 | 246: "green-buoy-and-force-field", 227 | 248: "red-buoy", 228 | 250: "red-buoy-and-force-field", 229 | 252: "red-buoy-and-green-buoy", 230 | 254: "red-buoy-and-green-buoy-and-force-field", 231 | 255: "none" 232 | } 233 | -------------------------------------------------------------------------------- /pycreate2/__init__.py: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # The MIT License (MIT) 3 | # Copyright (c) 2017 Kevin Walchko 4 | # see LICENSE for full details 5 | ############################################## 6 | 7 | try: 8 | from importlib.metadata import version # type: ignore 9 | except ImportError: 10 | from importlib_metadata import version # type: ignore 11 | 12 | from .create2api import Create2 13 | 14 | __license__ = 'MIT' 15 | __author__ = 'Kevin Walchko' 16 | __version__ = version("pycreate2") 17 | -------------------------------------------------------------------------------- /pycreate2/bin/create_monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #-*-coding:utf-8-*- 3 | ############################################## 4 | # The MIT License (MIT) 5 | # Copyright (c) 2017 Kevin Walchko 6 | # see LICENSE for full details 7 | ############################################## 8 | import argparse 9 | import pycreate2 10 | import time 11 | from pycreate2.packets import Buttons, WheelOvercurrents, ChargingSources, LightBumper, Stasis, BumpsAndWheelDrop 12 | 13 | DESCRIPTION = """ 14 | Prints the raw data from a Create 2. The default packet is 100 which get everything. 15 | However, this can be changed and a different packet and refresh rates can be selected. 16 | 17 | Example: 18 | $ ./monitor.py /dev/tty.usbserial-DA01NX3Z 19 | Create opened serial: Serial(port='/dev/tty.usbserial-DA01NX3Z', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False) 20 | ---------------------------------------------------------------------- 21 | infared char left | 0 22 | temperature | 35 23 | wheel overcurrents | 24 | right wheel : 0 25 | left wheel : 0 26 | main brush : 0 27 | side brush : 0 28 | cliff left | 0 29 | infared char omni | 0 30 | cliff front right | 0 31 | voltage | 14806 32 | cliff right signal | 2880 33 | light bump left signal | 25 34 | cliff front left signal | 2915 35 | requested velocity | 0 36 | buttons | 37 | dock : 0 38 | clean : 0 39 | hour : 0 40 | clock : 0 41 | schedule : 0 42 | spot : 0 43 | day : 0 44 | minute : 0 45 | cliff right | 0 46 | battery charge | 596 47 | charging state | 0 48 | side brush motor current | 1 49 | song playing | 0 50 | requested radius | 0 51 | left motor current | 1 52 | dirt detect | 0 53 | current | -250 54 | light bumper | 55 | right : 0 56 | center right : 0 57 | front left : 0 58 | center left : 0 59 | front right : 0 60 | left : 0 61 | requested left velocity | 0 62 | virtual wall | 0 63 | wheel drop and bumps | 64 | bump right : 0 65 | drop left : 0 66 | drop right : 0 67 | bump left : 0 68 | number of stream packets | 0 69 | wall seen | 0 70 | song number | 0 71 | distance | 0 72 | oi mode | 2 73 | stasis | 0 74 | cliff left signal | 2871 75 | cliff front right signal | 2880 76 | main brush motor current | -1 77 | requested right velocity | 0 78 | angle | 0 79 | cliff front left | 0 80 | light bump center right signal | 0 81 | light bump front right signal | 26 82 | battery capacity | 2696 83 | right encoder counts | 1 84 | light bump center left signal | 32 85 | charging sources available | 86 | home base : 0 87 | internal charger : 0 88 | light bump right signal | 3 89 | light bump front left signal | 29 90 | wall signal | 0 91 | infared char right | 0 92 | left encoder counts | 1 93 | right motor current | 1 94 | """ 95 | 96 | 97 | def handleArgs(): 98 | parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter) 99 | # parser.add_argument('-m', '--max', help='max id', type=int, default=253) 100 | parser.add_argument('-s', '--sleep', help='time in seconds between samples, default 1.0', type=float, default=1.0) 101 | # parser.add_argument('-i', '--id', help='packet ID, default is 100', type=int, default=100) 102 | parser.add_argument('port', help='serial port name, Ex: /dev/ttyUSB0 or COM1', type=str) 103 | 104 | args = vars(parser.parse_args()) 105 | return args 106 | 107 | 108 | class Monitor(object): 109 | def __init__(self): 110 | pass 111 | 112 | def display_raw(self, sensor): 113 | sensor = sensor._asdict() 114 | print('-'*70) 115 | for k, v in sensor.items(): 116 | # if dict is type(v): 117 | if type(v) in [Buttons, WheelOvercurrents, ChargingSources, LightBumper, Stasis, BumpsAndWheelDrop]: 118 | v = v._asdict() 119 | print('{:>40} |'.format(k)) 120 | for kk, vv in v.items(): 121 | print('{:>50} : {:<5}'.format(kk, vv)) 122 | else: 123 | print('{:>40} | {:<10}'.format(k, v)) 124 | 125 | def display_formated(self, sensor): 126 | raise NotImplementedError() 127 | 128 | print('================================================') 129 | print('Sensors from left to right') 130 | print('------------------------------------------------') 131 | print(' IR: {} {} {} {}'.format()) 132 | print(' Bump: {} {} {} {} {} {}'.format()) 133 | print(' Cliff: {} {} {} {}'.format()) 134 | print(' Wheel drops: {} {}'.format()) 135 | print(' Encoder: {} {}'.format()) 136 | print(' Temperature: {} C / {} F'.format()) 137 | print(' Wheel Overcurrents: {} {}'.format()) 138 | print('------------------------------------------------') 139 | print('Electrical:') 140 | print('------------------------------------------------') 141 | print(' Battery: {:.2f}% at {} V'.format()) 142 | print(' Current: {} A'.format()) 143 | print(' Motor Current: {} A {} A'.format()) 144 | print(' Charging: {}'.format()) 145 | print('------------------------------------------------') 146 | print('Commands:') 147 | print('------------------------------------------------') 148 | print(' Motors: {} {} mm/sec'.format()) 149 | print(' Turn Radius: {} mm'.format()) 150 | 151 | 152 | def main(): 153 | # get command line args 154 | args = handleArgs() 155 | port = args['port'] 156 | dt = args['sleep'] 157 | 158 | # create print monitor 159 | mon = Monitor() 160 | 161 | # create robot 162 | bot = pycreate2.Create2(port) 163 | bot.start() 164 | bot.safe() 165 | 166 | # now run forever, until someone hits ctrl-C 167 | try: 168 | while True: 169 | try: 170 | sensor_state = bot.get_sensors() 171 | mon.display_raw(sensor_state) 172 | time.sleep(dt) 173 | except Exception as e: 174 | print(e) 175 | continue 176 | except: 177 | raise 178 | except KeyboardInterrupt: 179 | print('bye ... ') 180 | 181 | if __name__ == '__main__': 182 | main() 183 | -------------------------------------------------------------------------------- /pycreate2/bin/create_reset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #-*-coding:utf-8-*- 3 | ############################################## 4 | # The MIT License (MIT) 5 | # Copyright (c) 2017 Kevin Walchko 6 | # see LICENSE for full details 7 | ############################################## 8 | import pycreate2 9 | import argparse 10 | 11 | DESCRIPTION = """ 12 | Resets the Create 2. 13 | """ 14 | 15 | 16 | def handleArgs(): 17 | parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter) 18 | # parser.add_argument('-m', '--max', help='max id', type=int, default=253) 19 | # parser.add_argument('-s', '--sleep', help='time in seconds between samples, default 1.0', type=float, default=1.0) 20 | parser.add_argument('-b', '--baud', help='baudrate, default is 115200', type=int, default=115200) 21 | parser.add_argument('port', help='serial port name, Ex: /dev/ttyUSB0 or COM1', type=str) 22 | 23 | args = vars(parser.parse_args()) 24 | return args 25 | 26 | def main(): 27 | args = handleArgs() 28 | port = args['port'] 29 | baud = args['baud'] 30 | 31 | bot = pycreate2.Create2(port=port, baud=baud) 32 | 33 | bot.start() 34 | ret = bot.reset() 35 | print(ret) 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /pycreate2/bin/create_shutdown.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #-*-coding:utf-8-*- 3 | ############################################## 4 | # The MIT License (MIT) 5 | # Copyright (c) 2017 Kevin Walchko 6 | # see LICENSE for full details 7 | ############################################## 8 | import pycreate2 9 | import argparse 10 | import time 11 | 12 | 13 | DESCRIPTION = """ 14 | Shuts down the Create 2. 15 | """ 16 | 17 | 18 | def handleArgs(): 19 | parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter) 20 | parser.add_argument('-b', '--baud', help='baudrate, default is 115200', type=int, default=115200) 21 | parser.add_argument('port', help='serial port name, Ex: /dev/ttyUSB0 or COM1', type=str) 22 | 23 | args = vars(parser.parse_args()) 24 | return args 25 | 26 | def main(): 27 | 28 | args = handleArgs() 29 | port = args['port'] 30 | baud = args['baud'] 31 | 32 | bot = pycreate2.Create2(port=port, baud=baud) 33 | 34 | bot.start() 35 | time.sleep(0.25) 36 | bot.power() # this seems to shut it down more than stop ... confused 37 | # bot.shutdown() 38 | time.sleep(0.25) 39 | bot.stop() 40 | time.sleep(1) 41 | 42 | print('=====================================================') 43 | print('\n\tCreate Shutdown') 44 | print('\tHit power button to wake-up\n') 45 | print('=====================================================') 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /pycreate2/create2api.py: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # The MIT License (MIT) 3 | # Copyright (c) 2017 Kevin Walchko 4 | # see LICENSE for full details 5 | ############################################## 6 | # This is the main code for interacting with the Create 2 7 | 8 | import struct # there are 2 places that use this ... why? 9 | import time 10 | from pycreate2.packets import SensorPacketDecoder 11 | from pycreate2.createSerial import SerialCommandInterface 12 | from pycreate2.OI import OPCODES 13 | from pycreate2.OI import DRIVE 14 | 15 | 16 | class Create2(object): 17 | """ 18 | The top level class for controlling a Create2. 19 | This is the only class that outside scripts should be interacting with. 20 | """ 21 | 22 | def __init__(self, port, baud=115200): 23 | """ 24 | Constructor, sets up class 25 | - creates serial port 26 | - creates decoder 27 | """ 28 | self.SCI = SerialCommandInterface() 29 | self.SCI.open(port, baud) 30 | # self.decoder = SensorPacketDecoder() 31 | self.decoder = None 32 | self.sleep_timer = 0.5 33 | self.song_list = {} 34 | 35 | def __del__(self): 36 | """Destructor, cleans up when class goes out of scope""" 37 | # stop motors 38 | self.drive_stop() 39 | time.sleep(self.sleep_timer) 40 | 41 | # turn off LEDs 42 | self.led() 43 | self.digit_led_ascii(' ') 44 | time.sleep(0.1) 45 | 46 | # close it down 47 | # self.power() 48 | # self.safe() # this beeps every now and then, but doesn't seem to go off 49 | time.sleep(0.1) 50 | self.stop() # power down, makes a low beep sound 51 | time.sleep(0.1) 52 | self.close() # close serial port 53 | time.sleep(0.1) 54 | 55 | def close(self): 56 | """ 57 | Closes up serial ports and terminates connection to the Create2 58 | """ 59 | self.SCI.close() 60 | 61 | # ------------------- Mode Control ------------------------ 62 | 63 | def start(self): 64 | """ 65 | Puts the Create 2 into Passive mode. You must always send the Start command 66 | before sending any other commands to the OI. 67 | """ 68 | # self.SCI.open() 69 | self.SCI.write(OPCODES.START) 70 | time.sleep(self.sleep_timer) 71 | 72 | def getMode(self): 73 | """ 74 | This doesn't seem to work 75 | """ 76 | self.SCI.write(OPCODES.MODE) 77 | time.sleep(0.005) 78 | ans = self.SCI.read(1) 79 | if len(ans) == 1: 80 | byte = struct.unpack('B', ans)[0] 81 | else: 82 | byte = 'Error, not mode returned' 83 | print('Mode: {}'.format(byte)) 84 | 85 | def wake(self): 86 | """ 87 | Wake up robot. See OI spec, pg 7 under passive mode. This should reset 88 | the 5 min timer in passive mode. 89 | 90 | Unfortunately, if you are using the "offical" create cable ... it doesn't 91 | work! They wired it wrong: 92 | https://robotics.stackexchange.com/questions/7895/irobot-create-2-powering-up-after-sleep 93 | """ 94 | self.SCI.ser.rts = True 95 | self.SCI.ser.dtr = True 96 | time.sleep(1) 97 | self.SCI.ser.rts = False 98 | self.SCI.ser.dtr = False 99 | time.sleep(1) 100 | self.SCI.ser.rts = True 101 | self.SCI.ser.dtr = True 102 | time.sleep(1) # Technically it should wake after 500ms. 103 | 104 | def reset(self): 105 | """ 106 | This command resets the robot, as if you had removed and reinserted the 107 | battery. This command is buggy. 108 | 109 | ('Firmware Version:', 'bl-start\r\nSTR730\r\nbootloader id: #x47186549 82ECCFFF\r\nbootloader info rev: #xF000\r\nbootloader rev: #x0001\r\n2007-05-14-1715-L \r') 110 | """ 111 | self.clearSongMemory() 112 | self.SCI.write(OPCODES.RESET) 113 | time.sleep(1) 114 | ret = self.SCI.read(128) 115 | return ret 116 | 117 | def stop(self): 118 | """ 119 | Puts the Create 2 into OFF mode. All streams will stop and the robot will no 120 | longer respond to commands. Use this command when you are finished 121 | working with the robot. 122 | """ 123 | self.clearSongMemory() 124 | self.SCI.write(OPCODES.STOP) 125 | time.sleep(self.sleep_timer) 126 | 127 | def safe(self): 128 | """ 129 | Puts the Create 2 into safe mode. Blocks for a short (<.5 sec) amount 130 | of time so the bot has time to change modes. 131 | """ 132 | self.SCI.write(OPCODES.SAFE) 133 | time.sleep(self.sleep_timer) 134 | self.clearSongMemory() 135 | 136 | def full(self): 137 | """ 138 | Puts the Create 2 into full mode. Blocks for a short (<.5 sec) amount 139 | of time so the bot has time to change modes. 140 | """ 141 | self.SCI.write(OPCODES.FULL) 142 | time.sleep(self.sleep_timer) 143 | self.clearSongMemory() 144 | 145 | # def seek_dock(self): 146 | # self.SCI.write(OPCODES.SEEK_DOCK) 147 | 148 | def power(self): 149 | """ 150 | Puts the Create 2 into Passive mode. The OI can be in Safe, or 151 | Full mode to accept this command. 152 | """ 153 | self.SCI.write(OPCODES.POWER) 154 | time.sleep(self.sleep_timer) 155 | 156 | # ------------------ Drive Commands ------------------ 157 | 158 | def drive_stop(self): 159 | # self.drive_straight(0) 160 | self.drive_direct(0,0) 161 | time.sleep(self.sleep_timer) # wait just a little for the robot to stop 162 | 163 | def limit(self, val, low, hi): 164 | val = val if val < hi else hi 165 | val = val if val > low else low 166 | return val 167 | 168 | def drive_direct(self, r_vel, l_vel): 169 | """ 170 | Drive motors directly: [-500, 500] mm/sec 171 | """ 172 | r_vel = self.limit(r_vel, -500, 500) 173 | l_vel = self.limit(l_vel, -500, 500) 174 | data = struct.unpack('4B', struct.pack('>2h', r_vel, l_vel)) # write do this? 175 | self.SCI.write(OPCODES.DRIVE_DIRECT, data) 176 | 177 | def drive_pwm(self, r_pwm, l_pwm): 178 | """ 179 | Drive motors PWM directly: [-255, 255] PWM 180 | """ 181 | r_pwm = self.limit(r_pwm, -255, 255) 182 | l_pwm = self.limit(l_pwm, -255, 255) 183 | data = struct.unpack('4B', struct.pack('>2h', r_pwm, l_pwm)) # write do this? 184 | self.SCI.write(OPCODES.DRIVE_PWM, data) 185 | 186 | # ------------------------ LED ---------------------------- 187 | 188 | def led(self, led_bits=0, power_color=0, power_intensity=0): 189 | """ 190 | led_bits: [check robot, dock, spot, debris] 191 | power_color: green [0] - red [255] 192 | power_instensity: off [0] - [255] full on 193 | 194 | All leds other than power are on/off. 195 | """ 196 | data = (led_bits, power_color, power_intensity) 197 | self.SCI.write(OPCODES.LED, data) 198 | 199 | def digit_led_ascii(self, display_string): 200 | """ 201 | This command controls the four 7 segment displays using ASCII character codes. 202 | 203 | Arguments: 204 | display_string: A four character string to be displayed. This must be four 205 | characters. Any blank characters should be represented with a space: ' ' 206 | Due to the limited display, there is no control over upper or lowercase 207 | letters. create2api will automatically convert all chars to uppercase, but 208 | some letters (Such as 'B' and 'D') will still display as lowercase on the 209 | Create 2's display. C'est la vie. Any Create non-printable character 210 | will be replaced with a space ' '. 211 | """ 212 | display_list = [32]*4 213 | for i, c in enumerate(display_string[:4]): 214 | val = ord(c.upper()) 215 | if 32 <= val <= 126: 216 | display_list[i] = val 217 | else: 218 | # Char was not available. Just print a blank space 219 | display_list[i] = 32 220 | 221 | self.SCI.write(OPCODES.DIGIT_LED_ASCII, tuple(display_list)) 222 | 223 | # ------------------------ Songs ---------------------------- 224 | 225 | def clearSongMemory(self): 226 | for sn in range(4): 227 | song = [70,0] 228 | self.createSong(sn,song) 229 | self.playSong(sn) 230 | time.sleep(0.1) 231 | 232 | def createSong(self, song_num, notes): 233 | """ 234 | Creates a song 235 | 236 | Arguments 237 | song_num: 1-4 238 | notes: 16 notes and 16 durations each note should be held for (1 duration = 1/64 second) 239 | """ 240 | size = len(notes) 241 | if (2 > size > 32) or (size % 2 != 0): 242 | raise Exception('Songs must be between 1-16 notes and have a duration for each note') 243 | if 0 > song_num > 3: 244 | raise Exception('Song number must be 0 - 3') 245 | 246 | if not isinstance(notes, tuple): 247 | notes = tuple(notes) 248 | 249 | dt = 0 250 | for i in range(len(notes)//2): 251 | dt += notes[2*i+1] 252 | dt = dt/64 253 | 254 | msg = (song_num, size//2,) + notes 255 | # print('>> msg:', (OPCODES.SONG,) + msg) 256 | self.SCI.write(OPCODES.SONG, msg) 257 | 258 | self.song_list[song_num] = dt 259 | 260 | return dt 261 | 262 | def playSong(self, song_num): 263 | """ 264 | Play a song 265 | Arguments 266 | song_num: 0-4 267 | returns the song duration in seconds to sleep for 268 | """ 269 | # if 0 > song_num > 3: 270 | # raise Exception('Song number must be 0 - 3') 271 | 272 | # print('let us play', song_num) 273 | try: 274 | time_len = self.song_list[song_num] 275 | except: 276 | print("*** Invalid Song: {} ***".format(song_num)) 277 | return 0 278 | 279 | # print('>> msg:', (OPCODES.PLAY, song_num,)) 280 | self.SCI.write(OPCODES.PLAY, (song_num,)) 281 | 282 | return time_len 283 | 284 | 285 | # ------------------------ Sensors ---------------------------- 286 | 287 | def get_sensors(self): 288 | """ 289 | return: a namedtuple 290 | 291 | WARNING: now this only returns pkt 100, everything. And it is the default 292 | packet reques now. 293 | """ 294 | 295 | opcode = OPCODES.SENSORS 296 | cmd = (100,) 297 | sensor_pkt_len = 80 298 | 299 | self.SCI.write(opcode, cmd) 300 | time.sleep(0.015) # wait 15 msec 301 | packet_byte_data = self.SCI.read(sensor_pkt_len) 302 | sensors = SensorPacketDecoder(packet_byte_data) 303 | 304 | return sensors 305 | -------------------------------------------------------------------------------- /pycreate2/createSerial.py: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # The MIT License (MIT) 3 | # Copyright (c) 2017 Kevin Walchko 4 | # see LICENSE for full details 5 | ############################################## 6 | # This is basically the interface between the Create2 and pyserial 7 | 8 | import serial # type:ignore 9 | import struct 10 | 11 | 12 | class SerialCommandInterface(object): 13 | """ 14 | This class handles sending commands to the Create2. Writes will take in tuples 15 | and format the data to transfer to the Create. 16 | """ 17 | 18 | def __init__(self): 19 | """ 20 | Constructor. 21 | 22 | Creates the serial port, but doesn't open it yet. Call open(port) to open 23 | it. 24 | """ 25 | self.ser = serial.Serial() 26 | 27 | def __del__(self): 28 | """ 29 | Destructor. 30 | 31 | Closes the serial port 32 | """ 33 | self.close() 34 | 35 | def open(self, port, baud=115200, timeout=1): 36 | """ 37 | Opens a serial port to the create. 38 | 39 | port: the serial port to open, ie, '/dev/ttyUSB0' 40 | buad: default is 115200, but can be changed to a lower rate via the create api 41 | """ 42 | self.ser.port = port 43 | self.ser.baudrate = baud 44 | self.ser.timeout = timeout 45 | # print self.ser.name 46 | if self.ser.is_open: 47 | self.ser.close() 48 | self.ser.open() 49 | if self.ser.is_open: 50 | # print("Create opened serial: {}".format(self.ser)) 51 | print('-'*40) 52 | print(' Create opened serial connection') 53 | print(' port: {}'.format(self.ser.port)) 54 | print(' datarate: {} bps'.format(self.ser.baudrate)) 55 | print('-'*40) 56 | else: 57 | raise Exception('Failed to open {} at {}'.format(port, baud)) 58 | 59 | def write(self, opcode, data=None): 60 | """ 61 | Writes a command to the create. There needs to be an opcode and optionally 62 | data. Not all commands have data associated with it. 63 | 64 | opcode: see creaet api 65 | data: a tuple with data associated with a given opcode (see api) 66 | """ 67 | msg = (opcode,) 68 | 69 | # Sometimes opcodes don't need data. Since we can't add 70 | # a None type to a tuple, we have to make this check. 71 | if data: 72 | msg += data 73 | # print(">> write:", msg) 74 | self.ser.write(struct.pack('B' * len(msg), *msg)) 75 | 76 | def read(self, num_bytes): 77 | """ 78 | Read a string of 'num_bytes' bytes from the robot. 79 | 80 | Arguments: 81 | num_bytes: The number of bytes we expect to read. 82 | """ 83 | if not self.ser.is_open: 84 | raise Exception('You must open the serial port first') 85 | 86 | data = self.ser.read(num_bytes) 87 | 88 | return data 89 | 90 | def close(self): 91 | """ 92 | Closes the serial connection. 93 | """ 94 | if self.ser.is_open: 95 | print('Closing port {} @ {}'.format(self.ser.port, self.ser.baudrate)) 96 | self.ser.close() 97 | -------------------------------------------------------------------------------- /pycreate2/packets.py: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # The MIT License (MIT) 3 | # Copyright (c) 2017 Kevin Walchko 4 | # see LICENSE for full details 5 | ############################################## 6 | # This decodes packets and returns a namedtuple (immutable) 7 | 8 | from struct import Struct 9 | from collections import namedtuple 10 | from .OI import WHEEL_OVERCURRENT, BUMPS_WHEEL_DROPS, BUTTONS, CHARGE_SOURCE, LIGHT_BUMPER, STASIS 11 | 12 | # build some packet decoders: 13 | # use: unpack_bool_byte(data)[0] -> returns tuple, but grab 0 entry 14 | unpack_bool_byte = Struct('?').unpack # 1 byte bool 15 | unpack_byte = Struct('b').unpack # 1 signed byte 16 | unpack_unsigned_byte = Struct('B').unpack # 1 unsigned byte 17 | unpack_short = Struct('>h').unpack # 2 signed bytes (short) 18 | unpack_unsigned_short = Struct('>H').unpack # 2 unsigned bytes (ushort) 19 | 20 | 21 | # Some data is bit mapped. These namedtuples break those out for easier use 22 | BumpsAndWheelDrop = namedtuple('BumpsAndWheelDrop', 'bump_left bump_right wheeldrop_left wheeldrop_right') 23 | WheelOvercurrents = namedtuple('WheelOvercurrents', 'side_brush_overcurrent main_brush_overcurrent right_wheel_overcurrent left_wheel_overcurrent') 24 | Buttons = namedtuple('Buttons', 'clean spot dock minute hour day schedule clock') 25 | ChargingSources = namedtuple('ChargingSources', 'internal_charger home_base') 26 | LightBumper = namedtuple('LightBumper', 'left front_left center_left center_right front_right right') 27 | Stasis = namedtuple('Stasis', 'toggling disabled') 28 | 29 | # This is the big kahuna ... packet 100, everything 30 | Sensors = namedtuple('Sensors', [ 31 | 'bumps_wheeldrops', 32 | 'wall', 33 | 'cliff_left', 34 | 'cliff_front_left', 35 | 'cliff_front_right', 36 | 'cliff_right', 37 | 'virtual_wall', 38 | 'overcurrents', 39 | 'dirt_detect', 40 | 'ir_opcode', 41 | 'buttons', 42 | 'distance', 43 | 'angle', 44 | 'charger_state', 45 | 'voltage', 46 | 'current', 47 | 'temperature', 48 | 'battery_charge', 49 | 'battery_capacity', 50 | 'wall_signal', 51 | 'cliff_left_signal', 52 | 'cliff_front_left_signal', 53 | 'cliff_front_right_signal', 54 | 'cliff_right_signal', 55 | 'charger_available', 56 | 'open_interface_mode', 57 | 'song_number', 58 | 'song_playing', 59 | 'oi_stream_num_packets', 60 | 'velocity', 61 | 'radius', 62 | 'velocity_right', 63 | 'velocity_left', 64 | 'encoder_counts_left', 65 | 'encoder_counts_right', 66 | 'light_bumper', 67 | 'light_bumper_left', 68 | 'light_bumper_front_left', 69 | 'light_bumper_center_left', 70 | 'light_bumper_center_right', 71 | 'light_bumper_front_right', 72 | 'light_bumper_right', 73 | 'ir_opcode_left', 74 | 'ir_opcode_right', 75 | 'left_motor_current', 76 | 'right_motor_current', 77 | 'main_brush_current', 78 | 'side_brush_current', 79 | 'statis' 80 | ]) 81 | 82 | 83 | def SensorPacketDecoder(data): 84 | """ 85 | This function decodes a Create 2 packet 100 and returns a Sensor object, 86 | which is really a namedtuple. The Sensor class holds all sensor values for 87 | the Create 2. It is basically like a C struct. 88 | """ 89 | 90 | if len(data) != 80: 91 | raise Exception('Sensor data not 80 bytes long, it is: {}'.format(len(data))) 92 | 93 | # packets 32, 33 or data bits 36, 37, 38 - unused 94 | # packet 16 or data bit 9 - unused 95 | 96 | # self.bumps_and_wheel_drops = BumpsAndWheelDrop(data[0:1]) 97 | d = unpack_unsigned_byte(data[0:1])[0] 98 | bumps_wheeldrops = BumpsAndWheelDrop( 99 | bool(d & BUMPS_WHEEL_DROPS.BUMP_RIGHT), 100 | bool(d & BUMPS_WHEEL_DROPS.BUMP_LEFT), 101 | bool(d & BUMPS_WHEEL_DROPS.WHEEL_DROP_RIGHT), 102 | bool(d & BUMPS_WHEEL_DROPS.WHEEL_DROP_LEFT) 103 | ) 104 | d = unpack_unsigned_byte(data[7:8])[0] 105 | overcurrents = WheelOvercurrents( 106 | bool(d & WHEEL_OVERCURRENT.SIDE_BRUSH), 107 | bool(d & WHEEL_OVERCURRENT.MAIN_BRUSH), 108 | bool(d & WHEEL_OVERCURRENT.RIGHT_WHEEL), 109 | bool(d & WHEEL_OVERCURRENT.LEFT_WHEEL) 110 | ) 111 | # self.buttons = Buttons(data[11:12]) 112 | d = unpack_unsigned_byte(data[11:12])[0] 113 | buttons = Buttons( 114 | bool(d & BUTTONS.CLEAN), 115 | bool(d & BUTTONS.SPOT), 116 | bool(d & BUTTONS.DOCK), 117 | bool(d & BUTTONS.MINUTE), 118 | bool(d & BUTTONS.HOUR), 119 | bool(d & BUTTONS.DAY), 120 | bool(d & BUTTONS.SCHEDULE), 121 | bool(d & BUTTONS.CLOCK) 122 | ) 123 | # self.charging_sources = ChargingSources(data[39:40]) 124 | d = unpack_unsigned_byte(data[39:40])[0] 125 | charging_sources = ChargingSources( 126 | bool(d & CHARGE_SOURCE.INTERNAL), 127 | bool(d & CHARGE_SOURCE.HOME_BASE) 128 | ) 129 | # self.light_bumper = LightBumper(data[56:57]) 130 | d = unpack_unsigned_byte(data[56:57])[0] 131 | light_bumper = LightBumper( 132 | bool(d & LIGHT_BUMPER.LEFT), 133 | bool(d & LIGHT_BUMPER.FRONT_LEFT), 134 | bool(d & LIGHT_BUMPER.CENTER_LEFT), 135 | bool(d & LIGHT_BUMPER.CENTER_RIGHT), 136 | bool(d & LIGHT_BUMPER.FRONT_RIGHT), 137 | bool(d & LIGHT_BUMPER.RIGHT) 138 | ) 139 | # self.stasis = Stasis(data[79:80]) 140 | d = unpack_unsigned_byte(data[79:80])[0] 141 | stasis = Stasis( 142 | bool(d & STASIS.TOGGLING), 143 | bool(d & STASIS.DISABLED) 144 | ) 145 | 146 | sensors = Sensors( 147 | bumps_wheeldrops, 148 | unpack_bool_byte(data[1:2])[0], # wall 149 | unpack_bool_byte(data[2:3])[0], # cliff left 150 | unpack_bool_byte(data[3:4])[0], # cliff front left 151 | unpack_bool_byte(data[4:5])[0], # cliff front right 152 | unpack_bool_byte(data[5:6])[0], # cliff right 153 | unpack_bool_byte(data[6:7])[0], # virtual wall 154 | overcurrents, 155 | unpack_byte(data[8:9])[0], # dirt detect 156 | # packet 16 or data bit 9 - unused 157 | unpack_unsigned_byte(data[10:11])[0], # ir opcode 158 | buttons, 159 | unpack_short(data[12:14])[0], # distance 160 | unpack_short(data[14:16])[0], # angle 161 | unpack_unsigned_byte(data[16:17])[0], # charge state 162 | unpack_unsigned_short(data[17:19])[0], # voltage 163 | unpack_short(data[19:21])[0], # current 164 | unpack_byte(data[21:22])[0], # temperature in C, use CtoF if needed 165 | unpack_unsigned_short(data[22:24])[0], # battery charge 166 | unpack_unsigned_short(data[24:26])[0], # battery capacity 167 | unpack_unsigned_short(data[26:28])[0], # wall 168 | unpack_unsigned_short(data[28:30])[0], # cliff left 169 | unpack_unsigned_short(data[30:32])[0], # cliff ront left 170 | unpack_unsigned_short(data[32:34])[0], # cliff front right 171 | unpack_unsigned_short(data[34:36])[0], # cliff right 172 | # packets 32, 33 or data bits 36, 37, 38 - unused 173 | charging_sources, 174 | unpack_unsigned_byte(data[40:41])[0], # oi mode 175 | unpack_unsigned_byte(data[41:42])[0], # song number 176 | unpack_bool_byte(data[42:43])[0], # song playing 177 | unpack_unsigned_byte(data[43:44])[0], # oi stream num packets 178 | unpack_short(data[44:46])[0], # velocity 179 | unpack_short(data[46:48])[0], # turn radius 180 | unpack_short(data[48:50])[0], # velocity right 181 | unpack_short(data[50:52])[0], # velocity left 182 | unpack_unsigned_short(data[52:54])[0], # encoder left 183 | unpack_unsigned_short(data[54:56])[0], # encoder right 184 | light_bumper, 185 | unpack_unsigned_short(data[57:59])[0], # light bump left 186 | unpack_unsigned_short(data[59:61])[0], # light bmp front left 187 | unpack_unsigned_short(data[61:63])[0], # light bump center left 188 | unpack_unsigned_short(data[63:65])[0], # light bump center right 189 | unpack_unsigned_short(data[65:67])[0], # light bump front right 190 | unpack_unsigned_short(data[67:69])[0], # light bump right 191 | unpack_unsigned_byte(data[69:70])[0], # ir opcode left 192 | unpack_unsigned_byte(data[70:71])[0], # ir opcode right 193 | unpack_short(data[71:73])[0], # left motor current 194 | unpack_short(data[73:75])[0], # right motor current 195 | unpack_short(data[75:77])[0], # main brush current 196 | unpack_short(data[77:79])[0], # side brush current 197 | stasis 198 | ) 199 | 200 | return sensors 201 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # The MIT License (MIT) 3 | # Copyright (c) 2017 Kevin Walchko 4 | # see LICENSE for full details 5 | ############################################## 6 | 7 | [tool.poetry] 8 | name = "pycreate2" 9 | version = "0.8.1" 10 | description = "A library to control iRobot Create 2 with python" 11 | authors = ["walchko "] 12 | readme = "readme.md" 13 | license = "MIT" 14 | homepage = "https://pypi.org/project/pycreate2/" 15 | repository = 'https://github.com/MomsFriendlyRobotCompany/pycreate2' 16 | # documentation = "http://..." 17 | keywords = ['roomba','irobot', 'create 2', 'api', 'framework', 'library', 'robotics', 'robot'] 18 | classifiers = [ 19 | 'Development Status :: 4 - Beta', 20 | 'Intended Audience :: Developers', 21 | 'License :: OSI Approved :: MIT License', 22 | 'Operating System :: OS Independent', 23 | 'Programming Language :: Python :: 3.8', 24 | 'Programming Language :: Python :: 3.9', 25 | 'Programming Language :: Python :: 3.10', 26 | 'Programming Language :: Python :: 3.11', 27 | 'Topic :: Software Development :: Libraries', 28 | 'Topic :: Software Development :: Libraries :: Python Modules', 29 | 'Topic :: Software Development :: Libraries :: Application Frameworks' 30 | ] 31 | 32 | [tool.poetry.scripts] 33 | create_monitor = "pycreate2.bin.create_monitor:main" 34 | create_reset = "pycreate2.bin.create_reset:main" 35 | create_shutdown = "pycreate2.bin.create_shutdown:main" 36 | 37 | [tool.poetry.dependencies] 38 | python = ">=3.8" 39 | pyserial = "*" 40 | colorama = "*" 41 | simplejson = "*" 42 | #importlib-metadata = {version="*", python="<3.8"} 43 | 44 | [tool.poetry.dev-dependencies] 45 | pytest = "*" 46 | 47 | # [mypy-pi_servo_serial] 48 | # ignore_missing_imports = True 49 | 50 | [build-system] 51 | requires = ["poetry>=1.0.0"] 52 | build-backend = "poetry.masonry.api" 53 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![image](https://raw.githubusercontent.com/walchko/pycreate2/master/pics/create.png) 2 | 3 | # pyCreate2 4 | 5 | [![Actions Status](https://github.com/MomsFriendlyRobotCompany/pycreate2/workflows/CheckPackage/badge.svg)](https://github.com/MomsFriendlyRobotCompany/pycreate2/actions) 6 | ![GitHub](https://img.shields.io/github/license/MomsFriendlyRobotCompany/pycreate2) 7 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pycreate2) 8 | ![PyPI](https://img.shields.io/pypi/v/pycreate2) 9 | ![PyPI - Downloads](https://img.shields.io/pypi/dm/pycreate2?color=aqua) 10 | 11 | A python library for controlling the [iRobot 12 | Create 2](http://www.irobot.com/About-iRobot/STEM/Create-2.aspx). This was used 13 | in ECE 387 Introduction to Robotics class I taught at the US Air Force Academy. 14 | 15 | - [video](https://vimeo.com/266619301): robot could only follow the black tap road and couldn't run into anything. If anything got in the way, it had to naviage around it to its final destination 16 | - [video](https://vimeo.com/266619767): robot pet, follow the pink ball 17 | - [video](https://vimeo.com/266619636): robot pet, follow the pink ball 18 | 19 | ## Install 20 | 21 | ### pip 22 | 23 | The recommended way to install this library is: 24 | 25 | ```bash 26 | pip install pycreate2 27 | ``` 28 | 29 | ### Development 30 | 31 | If you wish to develop and submit git-pulls, you can do: 32 | 33 | ```bash 34 | git clone https://github.com/walchko/pycreate2 35 | cd pycreate2 36 | poetry install 37 | poetry run pytest -v 38 | ``` 39 | 40 | ## Use 41 | 42 | There are multiple ways to command the Create to move, here are some 43 | examples: 44 | 45 | ```python 46 | from pycreate2 import Create2 47 | import time 48 | 49 | # Create a Create2. 50 | port = "/dev/serial" # where is your serial port? 51 | bot = Create2(port) 52 | 53 | # Start the Create 2 54 | bot.start() 55 | 56 | # Put the Create2 into 'safe' mode so we can drive it 57 | # This will still provide some protection 58 | bot.safe() 59 | 60 | # You are responsible for handling issues, no protection/safety in 61 | # this mode ... becareful 62 | bot.full() 63 | 64 | # directly set the motor speeds ... move forward 65 | bot.drive_direct(100, 100) 66 | time.sleep(2) 67 | 68 | # turn in place 69 | bot.drive_direct(200,-200) # inputs for motors are +/- 500 max 70 | time.sleep(2) 71 | 72 | # Stop the bot 73 | bot.drive_stop() 74 | 75 | # query some sensors 76 | sensors = bot.get_sensors() # returns all data 77 | print(sensors.light_bumper_left) 78 | 79 | # Close the connection 80 | # bot.close() 81 | ``` 82 | 83 | More examples are found in the [examples 84 | folder](https://github.com/walchko/pycreate2/tree/master/examples). 85 | 86 | ## Documents 87 | 88 | Additional notes and documents are in the [docs 89 | folder](https://github.com/walchko/pycreate2/tree/master/docs/Markdown). 90 | 91 | ### Modes 92 | 93 | ![image](https://raw.githubusercontent.com/walchko/pycreate2/master/pics/create_modes.png) 94 | 95 | The different modes (OFF, PASSIVE, SAFE, and FULL) can be switched 96 | between by calling different commands. 97 | 98 | - **OFF:** The robot is off and can charge, it will accept no commands 99 | - **PASSIVE:** The robot is in standbye and can charge. It will send 100 | sensor packets, but will not move 101 | - **SAFE:** The robot will not charge, but you full control over it 102 | with a few exceptions. If the cliff sensors or wheel drop sensors 103 | are triggered, the robot goes back to PASSIVE mode. 104 | - **FULL:** The robot will not charge and you have full control. You 105 | are responsible to handle any response due to cliff, wheel drop or 106 | any other sensors. 107 | 108 | ### Sensor Data 109 | 110 | Sensor data is returned as a `namedtuple` from `collections`. The 111 | information can be accessed as either: 112 | 113 | ```python 114 | sensors = bot.get_sensors() 115 | sensors.wall == sensors[1] # True 116 | ``` 117 | 118 | | Sensor | Range | Index | 119 | |------------------------------|-------------------|-------| 120 | | bumps\_wheeldrops | \[0-15\] | 0 | 121 | | wall | \[0-1\] | 1 | 122 | | cliff\_left | \[0-1\] | 2 | 123 | | cliff\_front\_left | \[0-1\] | 3 | 124 | | cliff\_front\_right | \[0-1\] | 4 | 125 | | cliff\_right | \[0-1\] | 5 | 126 | | virtual\_wall | \[0-1\] | 6 | 127 | | overcurrents | \[0-29\] | 7 | 128 | | dirt\_detect | \[0-255\] | 8 | 129 | | ir\_opcode | \[0-255\] | 9 | 130 | | buttons | \[0-255\] | 10 | 131 | | distance | \[-322768-32767\] | 11 | 132 | | angle | \[-322768-32767\] | 12 | 133 | | charger\_state | \[0-6\] | 13 | 134 | | voltage | \[0-65535\] | 14 | 135 | | current | \[-322768-32767\] | 15 | 136 | | temperature | \[-128-127\] | 16 | 137 | | battery\_charge | \[0-65535\] | 17 | 138 | | battery\_capacity | \[0-65535\] | 18 | 139 | | wall\_signal | \[0-1023\] | 19 | 140 | | cliff\_left\_signal | \[0-4095\] | 20 | 141 | | cliff\_front\_left\_signal | \[0-4095\] | 21 | 142 | | cliff\_front\_right\_signal | \[0-4095\] | 22 | 143 | | cliff\_right\_signal | \[0-4095\] | 23 | 144 | | charger\_available | \[0-3\] | 24 | 145 | | open\_interface\_mode | \[0-3\] | 25 | 146 | | song\_number | \[0-4\] | 26 | 147 | | song\_playing | \[0-1\] | 27 | 148 | | oi\_stream\_num\_packets | \[0-108\] | 28 | 149 | | velocity | \[-500-500\] | 29 | 150 | | radius | \[-322768-32767\] | 30 | 151 | | velocity\_right | \[-500-500\] | 31 | 152 | | velocity\_left | \[-500-500\] | 32 | 153 | | encoder\_counts\_left | \[-322768-32767\] | 33 | 154 | | encoder\_counts\_right | \[-322768-32767\] | 34 | 155 | | light\_bumper | \[0-127\] | 35 | 156 | | light\_bumper\_left | \[0-4095\] | 36 | 157 | | light\_bumper\_front\_left | \[0-4095\] | 37 | 158 | | light\_bumper\_center\_left | \[0-4095\] | 38 | 159 | | light\_bumper\_center\_right | \[0-4095\] | 39 | 160 | | light\_bumper\_front\_right | \[0-4095\] | 40 | 161 | | light\_bumper\_right | \[0-4095\] | 41 | 162 | | ir\_opcode\_left | \[0-255\] | 42 | 163 | | ir\_opcode\_right | \[0-255\] | 43 | 164 | | left\_motor\_current | \[-322768-32767\] | 44 | 165 | | right\_motor\_current | \[-322768-32767\] | 45 | 166 | | main\_brush\_current | \[-322768-32767\] | 46 | 167 | | side\_brush\_current | \[-322768-32767\] | 47 | 168 | | statis | \[0-3\] | 48 | 169 | 170 | 171 | ## Change Log 172 | 173 | | | | | 174 | | ---------- | ----- | ----------------------------- | 175 | | 2021-02-22 | 0.8.1 | Cleaned up code | 176 | | 2020-02-16 | 0.8.0 | Simplified interface and bug fixes | 177 | | 2020-02-16 | 0.7.7 | Fixed typo with poetry | 178 | | 2020-02-16 | 0.7.6 | Fixed typo erro in `bin` | 179 | | 2020-02-16 | 0.7.5 | Switched to toml and poetry | 180 | | 2019-06-30 | 0.7.4 | Midi sounds working | 181 | | 2017-08-26 | 0.7.3 | code clean up and doc updates | 182 | | 2017-08-26 | 0.7.2 | updates and fixes | 183 | | 2017-05-26 | 0.5.0 | init and published to pypi | 184 | 185 | # The MIT License 186 | 187 | **Copyright (c) 2007 Damon Kohler** 188 | 189 | **Copyright (c) 2015 Jonathan Le Roux (Modifications for Create 2)** 190 | 191 | **Copyright (c) 2015 Brandon Pomeroy** 192 | 193 | **Copyright (c) 2017 Kevin Walchko** 194 | 195 | Permission is hereby granted, free of charge, to any person obtaining a 196 | copy of this software and associated documentation files (the 197 | "Software"), to deal in the Software without restriction, including 198 | without limitation the rights to use, copy, modify, merge, publish, 199 | distribute, sublicense, and/or sell copies of the Software, and to 200 | permit persons to whom the Software is furnished to do so, subject to 201 | the following conditions: 202 | 203 | The above copyright notice and this permission notice shall be included 204 | in all copies or substantial portions of the Software. 205 | 206 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 207 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 208 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 209 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 210 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 211 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 212 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 213 | -------------------------------------------------------------------------------- /tests/test_pycreate.py: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # The MIT License (MIT) 3 | # Copyright (c) 2017 Kevin Walchko 4 | # see LICENSE for full details 5 | ############################################## 6 | from pycreate2.OI import RESPONSE_SIZES 7 | from pycreate2.OI import calc_query_data_len 8 | from pycreate2.packets import SensorPacketDecoder 9 | import os 10 | 11 | 12 | def test_packet_id(): 13 | packet_id = 100 14 | if packet_id in RESPONSE_SIZES: 15 | packet_size = RESPONSE_SIZES[packet_id] 16 | assert packet_size == 80 17 | else: 18 | assert False 19 | 20 | 21 | def test_packet_length(): 22 | pkts = [21, 22, 23, 24, 25] 23 | packet_len = calc_query_data_len(pkts) 24 | assert packet_len == 8 25 | pkts = [100] 26 | packet_len = calc_query_data_len(pkts) 27 | assert packet_len == 80 28 | 29 | 30 | def test_process_packet(): 31 | try: 32 | # i need to get a known packet and check values 33 | data = bytearray(os.urandom(80)) 34 | sensors = SensorPacketDecoder(data) 35 | assert isinstance(sensors, tuple) 36 | except: 37 | assert False 38 | --------------------------------------------------------------------------------