Commit fd73c257 authored by Kaloyan Tenchov's avatar Kaloyan Tenchov
Browse files

Merge branch 'dev'

parents dd971aad 6f0197ca
Revision History
================
v0.5.0 (Oct 12, 2019)
---------------------
- Added initial client API.
- Separated low-level Cozmo connection handling into a new ClientConnection class.
- Improved the ImageDecoder class and added a new ImageEncoder class for handling Cozmo protocol image encoding.
- Added new examples for displaying images from files and drawing on Cozmo's OLED display.
- New protocol commands: EnableBodyACC, EnableAnimationState, AnimHead, AnimLift, AnimBackpackLights, AnimBody,
StartAnimation, EndAnimation, AnimationStarted, AnimationEnded, DebugData.
- Initial support for Cozmo animations in FlatBuffer .bin files.
- Improved filtering through packet groups for pycozmo_dump.py and pycozmo_replay.py .
- Added type hints in the protocol generator.
- Bug fixes and documentation improvements.
v0.4.0 (Sep 13, 2019)
---------------------
- New commands: Enable, TurnInPlace, DriveStraight, ButtonPressed, HardwareInfo, BodyInfo, EnableColorImages,
......
......@@ -21,7 +21,7 @@ import pycozmo
def pycozmo_program(cli):
pkt = pycozmo.protocol_encoder.SetHeadAngle(angle_rad=0.6)
cli.send(pkt)
cli.conn.send(pkt)
time.sleep(1)
pycozmo.run_program(pycozmo_program)
......@@ -38,10 +38,10 @@ cli.connect()
cli.wait_for_robot()
pkt = pycozmo.protocol_encoder.DriveWheels(lwheel_speed_mmps=50.0, rwheel_speed_mmps=50.0)
cli.send(pkt)
cli.conn.send(pkt)
time.sleep(2.0)
pkt = pycozmo.protocol_encoder.StopAllMotors()
cli.send(pkt)
cli.conn.send(pkt)
cli.disconnect()
cli.stop()
......@@ -54,7 +54,6 @@ Documentation
- [Cozmo protocol](docs/protocol.md) description
- [Cozmo function](docs/functions.md) description
- [Capturing Cozmo communication](docs/capturing.md)
- API documentation: http://pycozmo.readthedocs.io/
......@@ -68,7 +67,9 @@ Examples
- [cube_lights.py](examples/cube_lights.py) - demonstrates cube connection and LED control
- [cube_light_animation.py](examples/cube_light_animation.py) - demonstrates cube LED animation control
- [charger_lights.py](examples/charger_lights.py) - demonstrates Cozmo charging platform LED control
- [display.py](examples/display.py) - demonstrates low-level visualization of images on Cozmo's display
- [display_image.py](examples/display_image.py) - demonstrates visualization of image files on Cozmo's display
- [display_lines.py](examples/display_lines.py) - demonstrates 2D graphics, using
[PIL.ImageDraw](https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html) on Cozmo's display
- [audio.py](examples/audio.py) - demonstrates 22 kHz, 8-bit, mono WAVE file playback through Cozmo's speaker
- [events.py](examples/events.py) - demonstrates event handling
- [camera.py](examples/camera.py) - demonstrates capturing a camera image
......@@ -77,9 +78,11 @@ Examples
Tools
-----
- `pycozmo_dump.py` - a command-line application that can read and annotate Cozmo communication from
[pcap files](https://en.wikipedia.org/wiki/Pcap)
- `pycozmo_replay.py` - a basic command-line application that can replay .pcap files back to Cozmo.
- [pycozmo_dump.py](tools/pycozmo_dump.py) - a command-line application that can read and annotate Cozmo communication
from [pcap files](https://en.wikipedia.org/wiki/Pcap) or capture it live using
[pypcap](https://github.com/pynetwork/pypcap).
- [pycozmo_replay.py](tools/pycozmo_replay.py) - a basic command-line application that can replay .pcap files back to
Cozmo.
Robot Support
......@@ -112,6 +115,9 @@ Storage:
- NVRAM - supported
- Firmware update - supported
Other:
- Animations - work in progress
Connecting to Cozmo over Wi-Fi
------------------------------
......@@ -134,12 +140,12 @@ In contrast, an application using PyCozmo basically replaces the Cozmo app and a
the low-level UDP communication with Cozmo.
```
+------------------+ +------------------+ +------------------+
| SDK app | Cozmo SDK | Cozmo app | PyCozmo | Cozmo |
| "game" | cozmoclad | "engine" | | "robot" |
| | +------------------> | Wi-Fi client | +------------------> | Wi-Fi AP |
| | USB | UDP client | UDP/Wi-Fi | UDP Server |
+------------------+ +------------------+ +------------------+
+------------------+ +------------------+ +------------------+
| SDK app | Cozmo SDK | Cozmo app | PyCozmo | Cozmo |
| "game" | cozmoclad | "engine" | | "robot" |
| | ------------------> | Wi-Fi client | ------------------> | Wi-Fi AP |
| | USB | UDP client | UDP/Wi-Fi | UDP Server |
+------------------+ +------------------+ +------------------+
```
......@@ -163,6 +169,9 @@ Requirements
------------
- Python 3.5.4
- [Pillow](https://github.com/python-pillow/Pillow) 6.0.0 - Python image library
- [FlatBuffers](https://github.com/google/flatbuffers) - serialization library
- [dpkt](https://github.com/kbandla/dpkt) - TCP/IP packet parsing library
Installation
......@@ -183,9 +192,13 @@ python setup.py install
```
Bugs
----
Support
-------
Bug reports and patches should be sent via GitHub:
https://github.com/zayfod/pycozmo
Anki Robot Discord server, channel #cozmo:
https://discord.gg/ew92haS
......@@ -260,3 +260,14 @@ Cube Accelerometers
`ObjectUpAxisChnaged`
`ObjectTapped`
`ObjectTapFiltered`
Animations
----------
`AnimHead`
`AnimLift`
`AnimBody`
`AnimBackpackLights`
`OutputAudio`
`Keyframe`
......@@ -37,7 +37,7 @@ The app acts as a client and initiates connections. It will only accept packets
+--------------------+ +--------------------+
| Cozmo app | | Cozmo |
| "engine" | UDP/Wi-Fi | "robot" |
| Wi-Fi client | +--------------------> | Wi-Fi AP |
| Wi-Fi client | ---------------------> | Wi-Fi AP |
| UDP client | | UDP Server |
+--------------------+ +--------------------+
172.31.1.0/24 172.31.1.1:5551
......@@ -68,9 +68,9 @@ Type Source Description
0x01 engine Reset
0x02 robot Reset ACK
0x03 engine Disconnect
0x04 engine Engine action
0x07 engine Engine packets
0x09 robot Robot packets
0x04 engine Engine packet - single
0x07 engine Engine packets - zero or more
0x09 robot Robot packets - zero or more
0x0b engine Out-of-band engine ping
```
......@@ -85,7 +85,7 @@ Type OOB Source Description
---------------------------------------------------------------------------------
0x02 n robot Connect
0x03 n engine Disconnect
0x04 n both Action
0x04 n both Command
0x05 y robot Event
0x0a y engine Unknown
0x0b y engine Ping
......@@ -95,9 +95,11 @@ Out of band packets do not get assigned sequence IDs.
Packet content is Cozmo firmware version specific.
Commands and events are identified by an 8-bit ID. IDs in the range 0-0xaf are sent by the engine. IDs in the range
0xb0-0xff are sent by the robot.
Commands
--------
IDs in the range 0xf0-0xff are used for out-of-band updates. These are packets that are not tracked by a sequence ID
and thus not retransmitted. Only their latest received value is considered important.
```
ID Min Max Name
......@@ -120,7 +122,7 @@ ID Min Max Name
0x3b 0 0 StopAllMotors
0x3d DriveStraight
0x45 24 24
0x4b 8 8
0x4b 8 8 EnableBodyACC
0x4c 2 2 EnableCamera
0x50 2 2
0x54 2 2
......@@ -142,7 +144,7 @@ ID Min Max Name
0x9b 1 1
0x9d 1 1
0x9e 1 1
0x9f 0 0
0x9f 0 0 EnableAnimationState
0xa0 16 16
0xaf 1026 1026 FirmwareUpdate
0xb0 8 40 * UnknownB0
......@@ -169,15 +171,6 @@ ID Min Max Name
0xed 12 12 BodyInfo
0xee 449 449 FirmwareSignature
0xef 7 7 FirmwareUpdateResult
```
Events
------
```
ID Min Max Name
---------------------------------------------------------------------------------
0xf0 91 91 RobotState
0xf1 15 15 AnimationState
0xf2 24 1172 * ImageChunk
......
#!/usr/bin/env python
import pycozmo
def pycozmo_program(cli: pycozmo.client.Client):
fspec = "com.anki.cozmo/files/cozmo/cozmo_resources/assets/animations/anim_codelab_tap.bin"
clips = pycozmo.anim.load_anim_clips(fspec)
clip = clips[0]
cli.play_anim(clip)
pycozmo.run_program(pycozmo_program)
......@@ -20,7 +20,7 @@ def pycozmo_program(cli: pycozmo.client.Client):
assert channels == 1
pkt = pycozmo.protocol_encoder.SetRobotVolume(20000)
cli.send(pkt)
cli.conn.send(pkt)
done = False
while not done:
......@@ -30,7 +30,7 @@ def pycozmo_program(cli: pycozmo.client.Client):
frame = frame.ljust(744, b"\x80")
pkt = pycozmo.protocol_encoder.OutputAudio(frame)
cli.send(pkt)
cli.conn.send(pkt)
time.sleep(0.033742)
......
......@@ -14,12 +14,7 @@ def pycozmo_program(cli: pycozmo.client.Client):
pycozmo.lights.off_light,
]
for light in lights:
pkt = pycozmo.protocol_encoder.LightStateCenter(states=(light, light, light))
cli.send(pkt)
pkt = pycozmo.protocol_encoder.LightStateSide(states=(light, light))
cli.send(pkt)
cli.set_all_backpack_lights(light)
time.sleep(2)
......
......@@ -13,18 +13,17 @@ def on_camera_image(cli, image):
def pycozmo_program(cli: pycozmo.client.Client):
angle = (pycozmo.robot.MAX_HEAD_ANGLE.radians - pycozmo.robot.MIN_HEAD_ANGLE.radians) / 2.0
pkt = pycozmo.protocol_encoder.SetHeadAngle(angle_rad=angle)
cli.send(pkt)
cli.set_head_angle(angle)
pkt = pycozmo.protocol_encoder.EnableCamera(enable=True)
cli.send(pkt)
cli.conn.send(pkt)
pkt = pycozmo.protocol_encoder.EnableColorImages(enable=True)
cli.send(pkt)
cli.conn.send(pkt)
# Wait for image to stabilize.
time.sleep(2.0)
cli.add_handler(pycozmo.client.EvtNewRawCameraImage, on_camera_image, one_shot=True)
cli.add_handler(pycozmo.event.EvtNewRawCameraImage, on_camera_image, one_shot=True)
# Wait for image to be captured.
time.sleep(1)
......
......@@ -18,8 +18,8 @@ def pycozmo_program(cli: pycozmo.client.Client):
print("Connecting to charger...")
pkt = pycozmo.protocol_encoder.ObjectConnect(factory_id=charger_factory_id, connect=True)
cli.send(pkt)
cli.wait_for(pycozmo.protocol_encoder.ObjectConnectionState)
cli.conn.send(pkt)
cli.conn.wait_for(pycozmo.protocol_encoder.ObjectConnectionState)
charger_id = list(cli.connected_objects.keys())[0]
print("Charger connected - ID {}.".format(charger_id))
......@@ -32,10 +32,10 @@ def pycozmo_program(cli: pycozmo.client.Client):
for light in lights:
# Select
pkt = pycozmo.protocol_encoder.CubeId(object_id=charger_id)
cli.send(pkt)
cli.conn.send(pkt)
# Set lights
pkt = pycozmo.protocol_encoder.CubeLights(states=(light, light, light, pycozmo.lights.off_light))
cli.send(pkt)
cli.conn.send(pkt)
time.sleep(2)
......
......@@ -18,13 +18,13 @@ def pycozmo_program(cli: pycozmo.client.Client):
print("Connecting to cube...")
pkt = pycozmo.protocol_encoder.ObjectConnect(factory_id=cube_factory_id, connect=True)
cli.send(pkt)
cli.wait_for(pycozmo.protocol_encoder.ObjectConnectionState)
cli.conn.send(pkt)
cli.conn.wait_for(pycozmo.protocol_encoder.ObjectConnectionState)
cube_id = list(cli.connected_objects.keys())[0]
print("Cube connected - ID {}.".format(cube_id))
color = pycozmo.lights.Color(int_color=0x00ff00ff)
light = pycozmo.lights.LightState(
light = pycozmo.protocol_encoder.LightState(
on_color=color.to_int16(),
off_color=pycozmo.lights.off.to_int16(),
on_frames=5,
......@@ -34,14 +34,14 @@ def pycozmo_program(cli: pycozmo.client.Client):
# Select cube
pkt = pycozmo.protocol_encoder.CubeId(object_id=cube_id, rotation_period_frames=40)
cli.send(pkt)
cli.conn.send(pkt)
# Set lights
pkt = pycozmo.protocol_encoder.CubeLights(states=(
light,
pycozmo.lights.off_light,
pycozmo.lights.off_light,
pycozmo.lights.off_light))
cli.send(pkt)
cli.conn.send(pkt)
time.sleep(30.0)
......
......@@ -18,8 +18,8 @@ def pycozmo_program(cli: pycozmo.client.Client):
print("Connecting to cube...")
pkt = pycozmo.protocol_encoder.ObjectConnect(factory_id=cube_factory_id, connect=True)
cli.send(pkt)
cli.wait_for(pycozmo.protocol_encoder.ObjectConnectionState)
cli.conn.send(pkt)
cli.conn.wait_for(pycozmo.protocol_encoder.ObjectConnectionState)
cube_id = list(cli.connected_objects.keys())[0]
print("Cube connected - ID {}.".format(cube_id))
......@@ -32,10 +32,10 @@ def pycozmo_program(cli: pycozmo.client.Client):
for light in lights:
# Select cube
pkt = pycozmo.protocol_encoder.CubeId(object_id=cube_id)
cli.send(pkt)
cli.conn.send(pkt)
# Set lights
pkt = pycozmo.protocol_encoder.CubeLights(states=(light, light, light, light))
cli.send(pkt)
cli.conn.send(pkt)
time.sleep(2)
......
#!/usr/bin/env python
import time
import pycozmo
def pycozmo_program(cli: pycozmo.client.Client):
angle = (pycozmo.robot.MAX_HEAD_ANGLE.radians - pycozmo.robot.MIN_HEAD_ANGLE.radians) / 2.0
pkt = pycozmo.protocol_encoder.SetHeadAngle(angle_rad=angle)
cli.send(pkt)
for _ in range(3):
pkt = pycozmo.protocol_encoder.NextFrame()
cli.send(pkt)
pkt = pycozmo.protocol_encoder.DisplayImage(pycozmo.util.hex_load(
"11:9c:b6:a4:98:be:40:94:c6:40:90:ce:5b:94:c6:9c:98:be:a0:"
"9c:b6:06:a0:ae:40:9c:b6:40:98:be:5d:9c:b6:40:a0:ae:1b"))
cli.send(pkt)
time.sleep(1)
pkt = pycozmo.protocol_encoder.NextFrame()
cli.send(pkt)
pkt = pycozmo.protocol_encoder.DisplayImage(pycozmo.util.hex_load(
"16:a0:b6:41:9c:be:40:98:c6:5b:9c:be:9c:a0:b6:40:06:a4:ae:"
"a4:a0:b6:40:9c:be:40:98:c6:5b:9c:be:40:a0:b6:40:16"))
cli.send(pkt)
time.sleep(1)
pycozmo.run_program(pycozmo_program)
#!/usr/bin/env python
import os
import time
from PIL import Image
import pycozmo
def pycozmo_program(cli: pycozmo.client.Client):
angle = (pycozmo.robot.MAX_HEAD_ANGLE.radians - pycozmo.robot.MIN_HEAD_ANGLE.radians) / 2.0
cli.set_head_angle(angle)
time.sleep(1)
# Load image
im = Image.open(os.path.join(os.path.dirname(__file__), "..", "assets", "pycozmo.png"))
# Convert to binary image.
im = im.convert('1')
cli.display_image(im, 10)
pycozmo.run_program(pycozmo_program)
#!/usr/bin/env python
import time
import random
import itertools
from PIL import Image, ImageDraw
import pycozmo
WIDTH = 128
HEIGHT = 32
MAX_SPEED = 2
NUM_DOTS = 3
DOT_SIZE = 1
LINE_WIDTH = 1
DELAY = 1 / 25
class Dot(object):
def __init__(self, x: int, y: int, vx: int, vy: int):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
def pycozmo_program(cli: pycozmo.client.Client):
angle = (pycozmo.robot.MAX_HEAD_ANGLE.radians - pycozmo.robot.MIN_HEAD_ANGLE.radians) / 2.0
cli.set_head_angle(angle)
time.sleep(1)
# Generate random dots.
dots = []
for i in range(NUM_DOTS):
x = random.randint(0, WIDTH)
y = random.randint(0, HEIGHT)
vx = random.randint(-MAX_SPEED, MAX_SPEED)
vy = random.randint(-MAX_SPEED, MAX_SPEED)
dot = Dot(x, y, vx, vy)
dots.append(dot)
while True:
# Create a blank image.
im = Image.new("1", (128, 32), color=0)
# Draw lines.
draw = ImageDraw.Draw(im)
for a, b in itertools.combinations(dots, 2):
draw.line((a.x, a.y, b.x, b.y), width=LINE_WIDTH, fill=1)
# Move dots.
for dot in dots:
dot.x += dot.vx
dot.y += dot.vy
if dot.x <= DOT_SIZE:
dot.x = DOT_SIZE
dot.vx = abs(dot.vx)
elif dot.x >= WIDTH - DOT_SIZE:
dot.x = WIDTH - DOT_SIZE
dot.vx = -abs(dot.vx)
if dot.y <= DOT_SIZE:
dot.y = DOT_SIZE
dot.vy = abs(dot.vy)
elif dot.y >= HEIGHT - DOT_SIZE:
dot.y = HEIGHT - DOT_SIZE
dot.vy = -abs(dot.vy)
cli.display_image(im, DELAY)
pycozmo.run_program(pycozmo_program)
......@@ -65,15 +65,15 @@ def on_robot_wheels_moving(cli, state):
def pycozmo_program(cli: pycozmo.client.Client):
cli.add_handler(pycozmo.protocol_encoder.RobotState, on_robot_state, one_shot=True)
cli.add_handler(pycozmo.protocol_encoder.RobotPoked, on_robot_poked)
cli.add_handler(pycozmo.protocol_encoder.FallingStarted, on_robot_falling_started)
cli.add_handler(pycozmo.protocol_encoder.FallingStopped, on_robot_falling_stopped)
cli.add_handler(pycozmo.protocol_encoder.ButtonPressed, on_button_pressed)
cli.add_handler(pycozmo.client.EvtRobotPickedUpChange, on_robot_picked_up)
cli.add_handler(pycozmo.client.EvtRobotChargingChange, on_robot_charging)
cli.add_handler(pycozmo.client.EvtCliffDetectedChange, on_cliff_detected)
cli.add_handler(pycozmo.client.EvtRobotWheelsMovingChange, on_robot_wheels_moving)
cli.conn.add_handler(pycozmo.protocol_encoder.RobotState, on_robot_state, one_shot=True)
cli.conn.add_handler(pycozmo.protocol_encoder.RobotPoked, on_robot_poked)
cli.conn.add_handler(pycozmo.protocol_encoder.FallingStarted, on_robot_falling_started)
cli.conn.add_handler(pycozmo.protocol_encoder.FallingStopped, on_robot_falling_stopped)
cli.conn.add_handler(pycozmo.protocol_encoder.ButtonPressed, on_button_pressed)
cli.add_handler(pycozmo.event.EvtRobotPickedUpChange, on_robot_picked_up)
cli.add_handler(pycozmo.event.EvtRobotChargingChange, on_robot_charging)
cli.add_handler(pycozmo.event.EvtCliffDetectedChange, on_cliff_detected)
cli.add_handler(pycozmo.event.EvtRobotWheelsMovingChange, on_robot_wheels_moving)
while True:
try:
......
......@@ -7,17 +7,15 @@ import pycozmo
def pycozmo_program(cli: pycozmo.client.Client):
cli.send(pycozmo.protocol_encoder.SetHeadAngle(angle_rad=pycozmo.MAX_HEAD_ANGLE.radians))
cli.set_head_angle(pycozmo.MAX_HEAD_ANGLE.radians)
time.sleep(1)
cli.send(pycozmo.protocol_encoder.SetHeadAngle(angle_rad=pycozmo.MIN_HEAD_ANGLE.radians))
cli.set_head_angle(pycozmo.MIN_HEAD_ANGLE.radians)
time.sleep(1)
cli.send(pycozmo.protocol_encoder.DriveHead())
cli.send(pycozmo.protocol_encoder.SetLiftHeight(height_mm=pycozmo.MAX_LIFT_HEIGHT.mm))
cli.set_lift_height(pycozmo.MAX_LIFT_HEIGHT.mm)
time.sleep(1)
cli.send(pycozmo.protocol_encoder.SetLiftHeight(height_mm=pycozmo.MIN_LIFT_HEIGHT.mm))
cli.set_lift_height(pycozmo.MIN_LIFT_HEIGHT.mm)
time.sleep(1)
cli.send(pycozmo.protocol_encoder.DriveLift())
pycozmo.run_program(pycozmo_program)
File mode changed from 100644 to 100755
......@@ -147,8 +147,7 @@ class RCApp(object):
self.cli.wait_for_robot()
# Raise head
angle = (pycozmo.robot.MAX_HEAD_ANGLE.radians - pycozmo.robot.MIN_HEAD_ANGLE.radians) * 0.1
pkt = pycozmo.protocol_encoder.SetHeadAngle(angle_rad=angle)
self.cli.send(pkt)
self.cli.set_head_angle(angle)
time.sleep(0.5)
return True
......@@ -156,7 +155,7 @@ class RCApp(object):
""" Terminate application. """
logging.info("Terminating...")
self.cli.send(pycozmo.protocol_encoder.StopAllMotors())
self.cli.stop_all_motors()
self.cli.disconnect()
self.cli.stop()
......@@ -182,14 +181,14 @@ class RCApp(object):
def _drive_lift(self, speed):
if self.lift:
self.cli.send(pycozmo.protocol_encoder.DriveLift(speed))
self.cli.move_lift(speed)
else:
self.cli.send(pycozmo.protocol_encoder.DriveHead(speed))
self.cli.move_head(speed)
def _drive_wheels(self, speed_left, speed_right):
lw = int(speed_left * pycozmo.MAX_WHEEL_SPEED.mmps)
rw = int(speed_right * pycozmo.MAX_WHEEL_SPEED.mmps)
self.cli.send(pycozmo.protocol_encoder.DriveWheels(lwheel_speed_mmps=lw, rwheel_speed_mmps=rw))
self.cli.drive_wheels(lwheel_speed=lw, rwheel_speed=rw)
@staticmethod
def _get_motor_thrust(r, theta):
......
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: CozmoAnim
import flatbuffers
class AnimClip(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsAnimClip(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = AnimClip()
x.Init(buf, n + offset)