diff --git a/README.md b/README.md index 5caa72d..f3bb2be 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,134 @@ -# Anki Vector - Python SDK - -![Vector](docs/source/images/vector-sdk-alpha.jpg) - -Learn more about Vector: https://www.anki.com/en-us/vector - -Learn more about the SDK: https://developer.anki.com/ - -SDK documentation: https://developer.anki.com/vector/docs/index.html - -Forums: https://forums.anki.com/ - - -## Getting Started - -You can follow steps [here](https://developer.anki.com/vector/docs/index.html) to set up your Vector robot with the SDK. - -You can also generate a local copy of the SDK documetation by -following the instructions in the `docs` folder of this project. - - -## Privacy Policy and Terms and Conditions - -Use of Vector and the Vector SDK is subject to Anki's [Privacy Policy](https://www.anki.com/en-us/company/privacy) and [Terms and Conditions](https://www.anki.com/en-us/company/terms-and-conditions). + +# Anki/DDL Vector - Python SDK +by cyb3rdog + +## With support for Production, EscapePod and OSKR robots! +Compatible with Ubuntu 16.04 - 20.04 and Python 3.6.1 - 3.9 + +This is the extended fork of the original Anki Vector Python SDK. + +![Vector](docs/source/images/vector-sdk-alpha.jpg) + + +## Getting Started + +For the steps undocumented here, you can still refer to [this original SDK documentation](https://developer.anki.com/vector/docs/index.html). +*(TODO: docs are old and needs to be updated, contributors wanted)* + +If you are new to the Vector's Python SDK, refer to this documentation anyways, as it still contains lots of valuable information. + + +### Python Installation + +#### Windows: + +If you dont have python installed yet, download and install it from the [Python.org](https://www.python.org/downloads/windows/). +Make sure to tick the “Add Python 3.X to PATH” checkbox on the Setup screen. + +To avoid dificulties during the SDK install on your existing python installation, open the command line and run: + +``` +py -m pip install -U pip +py -m pip install --upgrade setuptools +``` + +#### Linux: + +Open the Terminal and run following commands to install and update the Python, and packages required by SDK: + +``` +sudo apt-get update +sudo apt-get install -y python3 python3-pip python3-tk python3-pil.imagetk build-essential libssl-dev libffi-dev freeglut3 +pip3 install --upgrade setuptools +``` + +### SDK Installation + + - Note: Use either **```pip```** or **```pip3```** correspondingly to the Python version you are using. + +In case you have previously installed the original ***Anki*** or ***Ikkez*** SDK, uninstall it/them with following command(s): + +- ```pip uninstall anki_vector``` or ```pip3 uninstall anki_vector``` +- ```pip uninstall ikkez_vector``` or ```pip3 uninstall ikkez_vector``` + +To install this new SDK, run: + +- ```pip install cyb3r_vector_sdk``` or ```pip3 install cyb3r_vector_sdk``` +and +- ```pip install "cyb3r_vector_sdk[3dviewer]"``` or ```pip3 install "cyb3r_vector_sdk[3dviewer]"``` + + +To upgrade this SDK to its latest version, use: + +- ```pip install cyb3r_vector_sdk --upgrade``` or ```pip3 install cyb3r_vector_sdk --upgrade``` + + +If you want to know where the SDK files are installed, use following command: + +- Windows: ```py -c "import anki_vector as _; print(_.__path__)"``` +- Linux: ```python3 -c "import anki_vector as _; print(_.__path__)"``` + + +### SDK Configuration + +To configure the Python SDK for **Prod**, and/or **Prod+OSKR** robots, run: + +- Windows: **```py -m anki_vector.configure```** +- Linux: **```python3 -m anki_vector.configure```** + +To configure the Python SDK for **EscapePod**, and/or **EP+OSKR** robots, run: + +- Windows: **```py -m anki_vector.configure_pod```** +- Linux: **```python3 -m anki_vector.configure_pod```** + + +### SDK Usage - EscapePod + +You can either use the ```anki_vector.configure_pod``` in order to save your authentication into the sdk_config.ini file, and use all the [examples](https://github.com/cyb3rdog/vector-python-sdk/tree/master/examples) and your own programs and as you have them, OR you can use the Robot object setting (or sdk_config) with the ```escape_pod``` parameter to True, and passing just the robot's ip address: + +``` + with anki_vector.Robot(ip="192.168.0.148", escape_pod=True) as robot: + robot.behavior.say_text("Hello Escape Pod") +``` + + +### Log Level + +In order to change the log level to other then default value of `INFO`, set the `VECTOR_LOG_LEVEL` enviroment variable: + +Allowed values are: +``` +CRITICAL = 50 +FATAL = CRITICAL +ERROR = 40 +WARNING = 30 +WARN = WARNING +INFO = 20 +DEBUG = 10 +``` + +Example: + +- Windows: ```SET VECTOR_LOG_LEVEL=DEBUG``` +- Lunux: ```VECTOR_LOG_LEVEL="DEBUG"``` + + +### Documentation + +You can generate a local copy of the SDK documetation by +following the instructions in the `docs` folder of this project. + +Learn more about Vector: https://www.anki.com/en-us/vector + +Learn more about how Vector works: [Vector Bible](https://github.com/GooeyChickenman/victor/blob/master/documentation/Vector-TRM.pdf) + +Learn more about the SDK: https://developer.anki.com/ + +SDK documentation: https://developer.anki.com/vector/docs/index.html + +Forums: https://forums.anki.com/ + + +## Privacy Policy and Terms and Conditions + +Use of Vector and the Vector SDK is subject to Anki's [Privacy Policy](https://www.anki.com/en-us/company/privacy) and [Terms and Conditions](https://www.anki.com/en-us/company/terms-and-conditions). diff --git a/anki_vector/animation.py b/anki_vector/animation.py index ddbdd3e..80ac10d 100644 --- a/anki_vector/animation.py +++ b/anki_vector/animation.py @@ -131,14 +131,14 @@ async def _ensure_loaded(self): async def _load_animation_list(self): req = protocol.ListAnimationsRequest() - result = await self.grpc_interface.ListAnimations(req) + result = await self.grpc_interface.ListAnimations(req, timeout=10) self.logger.debug(f"Animation List status={text_format.MessageToString(result.status, as_one_line=True)}, number of animations={len(result.animation_names)}") self._anim_dict = {a.name: a for a in result.animation_names} return result async def _load_animation_trigger_list(self): req = protocol.ListAnimationTriggersRequest() - result = await self.grpc_interface.ListAnimationTriggers(req) + result = await self.grpc_interface.ListAnimationTriggers(req, timeout=10) self.logger.debug(f"Animation Triggers List status={text_format.MessageToString(result.status, as_one_line=True)}, number of animation_triggers={len(result.animation_trigger_names)}") self._anim_trigger_dict = {a.name: a for a in result.animation_trigger_names} return result @@ -165,7 +165,10 @@ async def load_animation_list(self): for anim_name in anim_names: print(anim_name) """ - return await self._load_animation_list() + try: + return await self._load_animation_list() + except: + return await self._load_animation_list() @connection.on_connection_thread(log_messaging=False, requires_control=False) async def load_animation_trigger_list(self): @@ -188,8 +191,12 @@ async def load_animation_trigger_list(self): for anim_trigger_name in anim_trigger_names: print(anim_trigger_name) """ - return await self._load_animation_trigger_list() + try: + return await self._load_animation_trigger_list() + except: + return await self._load_animation_trigger_list() + # TODO: add return type hint @connection.on_connection_thread() async def play_animation_trigger(self, anim_trigger: str, loop_count: int = 1, use_lift_safe: bool = False, ignore_body_track: bool = False, ignore_head_track: bool = False, ignore_lift_track: bool = False): # START """Starts an animation trigger playing on a robot. @@ -227,6 +234,7 @@ async def play_animation_trigger(self, anim_trigger: str, loop_count: int = 1, u ignore_lift_track=ignore_lift_track) return await self.grpc_interface.PlayAnimationTrigger(req) + # TODO: add return type hint @connection.on_connection_thread() async def play_animation(self, anim: str, loop_count: int = 1, ignore_body_track: bool = False, ignore_head_track: bool = False, ignore_lift_track: bool = False): """Starts an animation playing on a robot. diff --git a/anki_vector/annotate.py b/anki_vector/annotate.py index 5a07bd8..73eb98c 100644 --- a/anki_vector/annotate.py +++ b/anki_vector/annotate.py @@ -43,7 +43,7 @@ from typing import Callable, Iterable, Tuple, Union try: - from PIL import Image, ImageDraw, ImageFont + from PIL import Image, ImageDraw except ImportError: sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") except SyntaxError: @@ -137,7 +137,7 @@ def clock(image, scale, annotator=None, world=None, **kw): """ def __init__(self, text: str, position: int = AnnotationPosition.BOTTOM_RIGHT, align: str = "left", color: str = "white", - font = None, line_spacing: int = 3, outline_color: str = None, full_outline: bool = True): + font=None, line_spacing: int = 3, outline_color: str = None, full_outline: bool = True): self.text = text self.position = position self.align = align diff --git a/anki_vector/audio.py b/anki_vector/audio.py index 58ef29c..19a8a32 100644 --- a/anki_vector/audio.py +++ b/anki_vector/audio.py @@ -15,6 +15,7 @@ """Support for accessing Vector's audio. Vector's speakers can be used for playing user-provided audio. +TODO Ability to access the Vector's audio stream to come. The :class:`AudioComponent` class defined in this module is made available as :attr:`anki_vector.robot.Robot.audio` and can be used to play audio data on the robot. @@ -55,6 +56,7 @@ class AudioComponent(util.Component): """Handles audio on Vector. The AudioComponent object plays audio data to Vector's speaker. + Ability to access the Vector's audio stream to come. The :class:`anki_vector.robot.Robot` or :class:`anki_vector.robot.AsyncRobot` instance owns this audio component. @@ -67,6 +69,8 @@ class AudioComponent(util.Component): robot.audio.stream_wav_file('../examples/sounds/vector_alert.wav') """ + # TODO restore audio feed code when ready + def __init__(self, robot): super().__init__(robot) self._is_shutdown = False diff --git a/anki_vector/behavior.py b/anki_vector/behavior.py index d4555fc..7e37d0f 100644 --- a/anki_vector/behavior.py +++ b/anki_vector/behavior.py @@ -67,26 +67,29 @@ class BehaviorComponent(util.Component): """Run behaviors on Vector""" - _next_behavior_id = protocol.FIRST_SDK_TAG + _next_action_id = protocol.FIRST_SDK_TAG @classmethod - def _get_next_behavior_id(cls): - # Post increment _current_behavior_id (and loop within the SDK_TAG range) - next_behavior_id = cls._next_behavior_id - if cls._next_behavior_id == protocol.LAST_SDK_TAG: - cls._next_behavior_id = protocol.FIRST_SDK_TAG + def _get_next_action_id(cls): + # Post increment _current_action_id (and loop within the SDK_TAG range) + next_action_id = cls._next_action_id + if cls._next_action_id == protocol.LAST_SDK_TAG: + cls._next_action_id = protocol.FIRST_SDK_TAG else: - cls._next_behavior_id += 1 - return next_behavior_id + cls._next_action_id += 1 + return next_action_id @connection.on_connection_thread() - async def _abort(self, behavior_id): - # TODO Currently cancels actions only. Add ability to cancel behaviors. - cancel_action_request = protocol.CancelActionByIdTagRequest(id_tag=behavior_id) + async def _abort_action(self, action_id): + cancel_action_request = protocol.CancelActionByIdTagRequest(id_tag=action_id) return await self.grpc_interface.CancelActionByIdTag(cancel_action_request) - # TODO Make this cancellable with is_cancellable_behavior @connection.on_connection_thread() + async def _abort_behavior(self): + cancel_behavior_request = protocol.CancelBehaviorRequest() + return await self.grpc_interface.CancelBehavior(cancel_behavior_request) + + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_BEHAVIOR) async def drive_off_charger(self) -> protocol.DriveOffChargerResponse: """Drive Vector off the charger @@ -98,12 +101,23 @@ async def drive_off_charger(self) -> protocol.DriveOffChargerResponse: with anki_vector.Robot() as robot: robot.behavior.drive_off_charger() + + Example of cancelling the :meth:`drive_off_charger` behavior: + + .. testcode:: + + import anki_vector + import time + + with anki_vector.AsyncRobot() as robot: + drive_off_future = robot.behavior.drive_off_charger() + time.sleep(3.0) + drive_off_future.cancel() """ drive_off_charger_request = protocol.DriveOffChargerRequest() return await self.grpc_interface.DriveOffCharger(drive_off_charger_request) - # TODO Make this cancellable with is_cancellable_behavior - @connection.on_connection_thread() + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_BEHAVIOR) async def drive_on_charger(self) -> protocol.DriveOnChargerResponse: """Drive Vector onto the charger @@ -119,12 +133,23 @@ async def drive_on_charger(self) -> protocol.DriveOnChargerResponse: with anki_vector.Robot() as robot: robot.behavior.drive_on_charger() + + Example of cancelling the :meth:`drive_on_charger` behavior: + + .. testcode:: + + import anki_vector + import time + + with anki_vector.AsyncRobot() as robot: + drive_on_future = robot.behavior.drive_on_charger() + time.sleep(3.0) + drive_on_future.cancel() """ drive_on_charger_request = protocol.DriveOnChargerRequest() return await self.grpc_interface.DriveOnCharger(drive_on_charger_request) - # TODO Make this cancellable with is_cancellable_behavior - @connection.on_connection_thread() + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_BEHAVIOR) async def find_faces(self) -> protocol.FindFacesResponse: """Look around for faces @@ -136,12 +161,23 @@ async def find_faces(self) -> protocol.FindFacesResponse: with anki_vector.Robot() as robot: robot.behavior.find_faces() + + Example of cancelling the :meth:`find_faces` behavior: + + .. testcode:: + + import anki_vector + import time + + with anki_vector.AsyncRobot() as robot: + find_faces_future = robot.behavior.find_faces() + time.sleep(3.0) + find_faces_future.cancel() """ find_faces_request = protocol.FindFacesRequest() return await self.grpc_interface.FindFaces(find_faces_request) - # TODO Make this cancellable with is_cancellable_behavior - @connection.on_connection_thread() + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_BEHAVIOR) async def look_around_in_place(self) -> protocol.LookAroundInPlaceResponse: """Look around in place @@ -153,12 +189,23 @@ async def look_around_in_place(self) -> protocol.LookAroundInPlaceResponse: with anki_vector.Robot() as robot: robot.behavior.look_around_in_place() + + Example of cancelling the :meth:`look_around_in_place` behavior: + + .. testcode:: + + import anki_vector + import time + + with anki_vector.AsyncRobot() as robot: + look_around_in_place_future = robot.behavior.look_around_in_place() + time.sleep(3.0) + look_around_in_place_future.cancel() """ look_around_in_place_request = protocol.LookAroundInPlaceRequest() return await self.grpc_interface.LookAroundInPlace(look_around_in_place_request) - # TODO Make this cancellable with is_cancellable_behavior - @connection.on_connection_thread() + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_BEHAVIOR) async def roll_visible_cube(self) -> protocol.RollBlockResponse: """Roll a cube that is currently known to the robot @@ -173,11 +220,23 @@ async def roll_visible_cube(self) -> protocol.RollBlockResponse: with anki_vector.Robot() as robot: robot.behavior.roll_visible_cube() + + Example of cancelling the :meth:`roll_visible_cube` behavior: + + .. testcode:: + + import anki_vector + import time + + with anki_vector.AsyncRobot() as robot: + roll_visible_cube_future = robot.behavior.roll_visible_cube() + time.sleep(3.0) + roll_visible_cube_future.cancel() """ roll_block_request = protocol.RollBlockRequest() return await self.grpc_interface.RollBlock(roll_block_request) - # TODO Make this cancellable with is_cancellable_behavior + # TODO Make this cancellable with is_cancellable @connection.on_connection_thread() async def say_text(self, text: str, use_vector_voice: bool = True, duration_scalar: float = 1.0) -> protocol.SayTextResponse: """Make Vector speak text. @@ -201,7 +260,7 @@ async def say_text(self, text: str, use_vector_voice: bool = True, duration_scal duration_scalar=duration_scalar) return await self.conn.grpc_interface.SayText(say_text_request) - # TODO Make this cancellable with is_cancellable_behavior? + # TODO Make this cancellable with is_cancellable? @connection.on_connection_thread() async def set_eye_color(self, hue: float, saturation: float) -> protocol.SetEyeColorResponse: """Set Vector's eye color. @@ -230,12 +289,12 @@ async def set_eye_color(self, hue: float, saturation: float) -> protocol.SetEyeC eye_color_request = protocol.SetEyeColorRequest(hue=hue, saturation=saturation) return await self.conn.grpc_interface.SetEyeColor(eye_color_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def go_to_pose(self, pose: util.Pose, relative_to_robot: bool = False, num_retries: int = 0, - _behavior_id: int = None) -> protocol.GoToPoseResponse: + _action_id: int = None) -> protocol.GoToPoseResponse: """Tells Vector to drive to the specified pose and orientation. In navigating to the requested pose, Vector will use path planning. @@ -289,20 +348,20 @@ async def go_to_pose(self, go_to_pose_request = protocol.GoToPoseRequest(x_mm=pose.position.x, y_mm=pose.position.y, rad=pose.rotation.angle_z.radians, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.GoToPose(go_to_pose_request) # TODO alignment_type coming out ugly in the docs without real values - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def dock_with_cube(self, target_object: objects.LightCube, approach_angle: util.Angle = None, alignment_type: protocol.AlignmentType = protocol.ALIGNMENT_TYPE_LIFT_PLATE, distance_from_marker: util.Distance = None, num_retries: int = 0, - _behavior_id: int = None) -> protocol.DockWithCubeResponse: + _action_id: int = None) -> protocol.DockWithCubeResponse: """Tells Vector to dock with a light cube, optionally using a given approach angle and distance. While docking with the cube, Vector will use path planning. @@ -360,7 +419,7 @@ async def dock_with_cube(self, dock_request = protocol.DockWithCubeRequest(object_id=target_object.object_id, alignment_type=alignment_type, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) if approach_angle is not None: dock_request.use_approach_angle = True @@ -372,13 +431,13 @@ async def dock_with_cube(self, return await self.grpc_interface.DockWithCube(dock_request) # Movement actions - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def drive_straight(self, distance: util.Distance, speed: util.Speed, should_play_anim: bool = True, num_retries: int = 0, - _behavior_id: int = None) -> protocol.DriveStraightResponse: + _action_id: int = None) -> protocol.DriveStraightResponse: """Tells Vector to drive in a straight line. Vector will drive for the specified distance (forwards or backwards) @@ -424,12 +483,12 @@ async def drive_straight(self, drive_straight_request = protocol.DriveStraightRequest(speed_mmps=speed.speed_mmps, dist_mm=distance.distance_mm, should_play_animation=should_play_anim, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.DriveStraight(drive_straight_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def turn_in_place(self, angle: util.Angle, speed: util.Angle = util.Angle(0.0), @@ -437,7 +496,7 @@ async def turn_in_place(self, angle_tolerance: util.Angle = util.Angle(0.0), is_absolute: bool = 0, num_retries: int = 0, - _behavior_id: int = None) -> protocol.TurnInPlaceResponse: + _action_id: int = None) -> protocol.TurnInPlaceResponse: """Turn the robot around its current position. Vector must be off of the charger for this movement action. @@ -487,19 +546,19 @@ async def turn_in_place(self, accel_rad_per_sec2=accel.radians, tol_rad=angle_tolerance.radians, is_absolute=is_absolute, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.TurnInPlace(turn_in_place_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def set_head_angle(self, angle: util.Angle, accel: float = 10.0, max_speed: float = 10.0, duration: float = 0.0, num_retries: int = 0, - _behavior_id: int = None) -> protocol.SetHeadAngleResponse: + _action_id: int = None) -> protocol.SetHeadAngleResponse: """Tell Vector's head to move to a given angle. :param angle: Desired angle for Vector's head. @@ -558,18 +617,18 @@ async def set_head_angle(self, max_speed_rad_per_sec=max_speed, accel_rad_per_sec2=accel, duration_sec=duration, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.SetHeadAngle(set_head_angle_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def set_lift_height(self, height: float, accel: float = 10.0, max_speed: float = 10.0, duration: float = 0.0, num_retries: int = 0, - _behavior_id: int = None) -> protocol.SetLiftHeightResponse: + _action_id: int = None) -> protocol.SetLiftHeightResponse: """Tell Vector's lift to move to a given height. :param height: desired height for Vector's lift 0.0 (bottom) to @@ -621,16 +680,16 @@ async def set_lift_height(self, max_speed_rad_per_sec=max_speed, accel_rad_per_sec2=accel, duration_sec=duration, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.SetLiftHeight(set_lift_height_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def turn_towards_face(self, face: faces.Face, num_retries: int = 0, - _behavior_id: int = None) -> protocol.TurnTowardsFaceResponse: + _action_id: int = None) -> protocol.TurnTowardsFaceResponse: """Tells Vector to turn towards this face. :param face_id: The face Vector will turn towards. @@ -658,17 +717,17 @@ async def turn_towards_face(self, """ turn_towards_face_request = protocol.TurnTowardsFaceRequest(face_id=face.face_id, max_turn_angle_rad=util.degrees(180).radians, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.TurnTowardsFace(turn_towards_face_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def go_to_object(self, target_object: objects.LightCube, distance_from_object, num_retries: int = 0, - _behavior_id: int = None) -> protocol.GoToObjectResponse: + _action_id: int = None) -> protocol.GoToObjectResponse: """Tells Vector to drive to his Cube. :param target_object: The destination object. CustomObject instances are not supported. @@ -696,17 +755,17 @@ async def go_to_object(self, go_to_object_request = protocol.GoToObjectRequest(object_id=target_object.object_id, distance_from_object_origin_mm=distance_from_object.distance_mm, use_pre_dock_pose=False, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.GoToObject(go_to_object_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def roll_cube(self, target_object: objects.LightCube, approach_angle: util.Angle = None, num_retries: int = 0, - _behavior_id: int = None) -> protocol.RollObjectResponse: + _action_id: int = None) -> protocol.RollObjectResponse: """Tells Vector to roll a specified cube object. :param target_object: The cube to roll. @@ -742,17 +801,17 @@ async def roll_cube(self, approach_angle_rad=approach_angle.radians, use_approach_angle=use_approach_angle, use_pre_dock_pose=use_approach_angle, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.RollObject(roll_object_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def pop_a_wheelie(self, target_object: objects.LightCube, approach_angle: util.Angle = None, num_retries: int = 0, - _behavior_id: int = None) -> protocol.PopAWheelieResponse: + _action_id: int = None) -> protocol.PopAWheelieResponse: """Tells Vector to "pop a wheelie" using his light cube. :param target_object: The cube to push down on with Vector's lift, to start the wheelie. @@ -788,17 +847,17 @@ async def pop_a_wheelie(self, approach_angle_rad=approach_angle.radians, use_approach_angle=use_approach_angle, use_pre_dock_pose=use_approach_angle, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.PopAWheelie(pop_a_wheelie_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def pickup_object(self, target_object: objects.LightCube, use_pre_dock_pose: bool = True, num_retries: int = 0, - _behavior_id: int = None) -> protocol.PickupObjectResponse: + _action_id: int = None) -> protocol.PickupObjectResponse: """Instruct the robot to pick up his LightCube. While picking up the cube, Vector will use path planning. @@ -830,15 +889,15 @@ async def pickup_object(self, pickup_object_request = protocol.PickupObjectRequest(object_id=target_object.object_id, use_pre_dock_pose=use_pre_dock_pose, - id_tag=_behavior_id, + id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.PickupObject(pickup_object_request) - @connection.on_connection_thread(is_cancellable_behavior=True) + @connection.on_connection_thread(is_cancellable=connection.CancelType.CANCELLABLE_ACTION) async def place_object_on_ground_here(self, num_retries: int = 0, - _behavior_id: int = None) -> protocol.PlaceObjectOnGroundHereResponse: + _action_id: int = None) -> protocol.PlaceObjectOnGroundHereResponse: """Ask Vector to place the object he is carrying on the ground at the current location. :param num_retries: Number of times to reattempt action in case of a failure. @@ -857,7 +916,7 @@ async def place_object_on_ground_here(self, robot.behavior.pickup_object(robot.world.connected_light_cube) robot.behavior.place_object_on_ground_here() """ - place_object_on_ground_here_request = protocol.PlaceObjectOnGroundHereRequest(id_tag=_behavior_id, + place_object_on_ground_here_request = protocol.PlaceObjectOnGroundHereRequest(id_tag=_action_id, num_retries=num_retries) return await self.grpc_interface.PlaceObjectOnGroundHere(place_object_on_ground_here_request) @@ -939,7 +998,7 @@ def __init__(self, '{"name":"Vector-XXXX", "ip":"XX.XX.XX.XX", "cert":"/path/to/cert_file", "guid":""}') self._conn = connection.Connection(self._name, ':'.join([self._ip, self._port]), self._cert_file, self._guid, - behavior_control_level=connection.CONTROL_PRIORITY_LEVEL.RESERVE_CONTROL) + behavior_control_level=connection.ControlPriorityLevel.RESERVE_CONTROL) self._behavior_activation_timeout = behavior_activation_timeout def __enter__(self): diff --git a/anki_vector/camera.py b/anki_vector/camera.py index 379c08d..f0845c6 100644 --- a/anki_vector/camera.py +++ b/anki_vector/camera.py @@ -26,7 +26,7 @@ # __all__ should order by constants, event classes, other classes, functions. __all__ = ["EvtNewRawCameraImage", "EvtNewCameraImage", - "CameraComponent", "CameraImage"] + "CameraComponent", "CameraConfig", "CameraImage"] import asyncio from concurrent.futures import CancelledError @@ -36,7 +36,7 @@ from . import annotate, connection, util from .events import Events -from .exceptions import VectorCameraFeedException +from .exceptions import VectorCameraFeedException, VectorCameraImageCaptureException from .messaging import protocol try: @@ -129,12 +129,124 @@ def annotate_image(self, scale: float = None, fit_size: tuple = None, resample_m (fast) or :attr:`~anki_vector.annotate.RESAMPLE_MODE_BILINEAR` (slower, but smoother). """ + if self._raw_image.size != (640, 360): + raise VectorCameraImageCaptureException("Annotation is only supported for default resolution images.") return self._image_annotator.annotate_image(self._raw_image, scale=scale, fit_size=fit_size, resample_mode=resample_mode) +class CameraConfig: + """ The fixed properties for Vector's camera. + + A full 3x3 calibration matrix for doing 3D reasoning based on the camera + images would look like: + + +--------------+--------------+---------------+ + |focal_length.x| 0 | center.x | + +--------------+--------------+---------------+ + | 0 |focal_length.y| center.y | + +--------------+--------------+---------------+ + | 0 | 0 | 1 | + +--------------+--------------+---------------+ + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + min = robot.camera.config.min_gain + max = robot.camera.config.max_gain + print(f"Robot camera allowable exposure gain range is from {min} to {max}") + """ + + def __init__(self, + focal_length_x: float, + focal_length_y: float, + center_x: float, + center_y: float, + fov_x: float, + fov_y: float, + min_exposure_time_ms: int, + max_exposure_time_ms: int, + min_gain: float, + max_gain: float): + self._focal_length = util.Vector2(focal_length_x, focal_length_y) + self._center = util.Vector2(center_x, center_y) + self._fov_x = util.degrees(fov_x) + self._fov_y = util.degrees(fov_y) + self._min_exposure_ms = min_exposure_time_ms + self._max_exposure_ms = max_exposure_time_ms + self._min_gain = min_gain + self._max_gain = max_gain + + @classmethod + def create_from_message(cls, msg: protocol.CameraConfigResponse): + """Create camera configuration based on Vector's camera configuration from the message sent from the Robot """ + return cls(msg.focal_length_x, + msg.focal_length_y, + msg.center_x, + msg.center_y, + msg.fov_x, + msg.fov_y, + msg.min_camera_exposure_time_ms, + msg.max_camera_exposure_time_ms, + msg.min_camera_gain, + msg.max_camera_gain) + + @property + def min_gain(self) -> float: + """The minimum supported camera gain.""" + return self._min_gain + + @property + def max_gain(self) -> float: + """The maximum supported camera gain.""" + return self._max_gain + + @property + def min_exposure_time_ms(self) -> int: + """The minimum supported exposure time in milliseconds.""" + return self._min_exposure_ms + + @property + def max_exposure_time_ms(self) -> int: + """The maximum supported exposure time in milliseconds.""" + return self._max_exposure_ms + + @property + def focal_length(self): + """:class:`anki_vector.util.Vector2`: The focal length of the camera. + + This is focal length combined with pixel skew (as the pixels aren't + perfectly square), so there are subtly different values for x and y. + It is in floating point pixel values e.g. <288.87, 288.36>. + """ + return self._focal_length + + @property + def center(self): + """:class:`anki_vector.util.Vector2`: The focal center of the camera. + + This is the position of the optical center of projection within the + image. It will be close to the center of the image, but adjusted based + on the calibration of the lens. It is in floating point pixel values + e.g. <155.11, 111.40>. + """ + return self._center + + @property + def fov_x(self): + """:class:`anki_vector.util.Angle`: The x (horizontal) field of view.""" + return self._fov_x + + @property + def fov_y(self): + """:class:`anki_vector.util.Angle`: The y (vertical) field of view.""" + return self._fov_y + + class CameraComponent(util.Component): """Represents Vector's camera. @@ -167,6 +279,61 @@ def __init__(self, robot): self._latest_image_id: int = None self._camera_feed_task: asyncio.Task = None self._enabled = False + self._config = None # type CameraConfig + self._gain = 0.0 + self._exposure_ms = 0 + self._auto_exposure_enabled = True + + def set_config(self, message: protocol.CameraConfigRequest): + """Update Vector's camera configuration from the message sent from the Robot """ + try: + self._config = CameraConfig.create_from_message(message) + except: + self._config = CameraConfig(0,0,0,0,0,0,0,0,0,0) + + @connection.on_connection_thread(requires_control=False) + async def get_camera_config(self) -> protocol.CameraConfigResponse: + """ Get Vector's camera configuration + + Retrieves the calibrated camera settings. This is called during the Robot connection initialization, SDK + users should use the `config` property in most instances. + + :return: + """ + try: + request = protocol.CameraConfigRequest() + return await self.conn.grpc_interface.GetCameraConfig(request) + except: + pass + + @property + def config(self) -> CameraConfig: + """:class:`anki_vector.camera.CameraConfig`: The read-only config/calibration for the camera""" + return self._config + + @property + def is_auto_exposure_enabled(self) -> bool: + """bool: True if auto exposure is currently enabled + + If auto exposure is enabled the `gain` and `exposure_ms` + values will constantly be updated by Vector. + """ + return self._auto_exposure_enabled + + @property + def gain(self) -> float: + """float: The current camera gain setting.""" + return self._gain + + @property + def exposure_ms(self) -> int: + """int: The current camera exposure setting in milliseconds.""" + return self._exposure_ms + + def update_state(self, _robot, _event_type, msg): + self._gain = msg.gain + self._exposure_ms = msg.exposure_ms + self._auto_exposure_enabled = msg.auto_exposure_enabled @property @util.block_while_none() @@ -330,7 +497,7 @@ async def _request_and_handle_images(self) -> None: self.logger.debug('Camera feed task was cancelled. This is expected during disconnection.') @connection.on_connection_thread() - async def capture_single_image(self) -> CameraImage: + async def capture_single_image(self, enable_high_resolution: bool = False) -> CameraImage: """Request to capture a single image from the robot's camera. This call requests the robot to capture an image and returns the @@ -346,10 +513,16 @@ async def capture_single_image(self) -> CameraImage: with anki_vector.Robot() as robot: image = robot.camera.capture_single_image() image.raw_image.show() + + :param enable_high_resolution: Enable/disable request for high resolution images. The default resolution + is 640x360, while the high resolution is 1280x720. """ if self._enabled: + self.logger.warning('Camera feed is enabled. Receiving image from the feed at default resolution.') return self._latest_image - req = protocol.CaptureSingleImageRequest() + if enable_high_resolution: + self.logger.warning('Capturing a high resolution (1280*720) image. Image events for this frame need to be scaled.') + req = protocol.CaptureSingleImageRequest(enable_high_resolution=enable_high_resolution) res = await self.grpc_interface.CaptureSingleImage(req) if res and res.data: image = _convert_to_pillow_image(res.data) @@ -357,6 +530,71 @@ async def capture_single_image(self) -> CameraImage: self.logger.error('Failed to capture a single image') + @connection.on_connection_thread() + async def enable_auto_exposure(self, enable_auto_exposure=True) -> protocol.SetCameraSettingsResponse: + """Enable auto exposure on Vector's Camera. + + Enable auto exposure on Vector's camera to constantly update the exposure + time and gain values based on the recent images. This is the default mode + when any SDK program starts. + + .. testcode:: + + import time + import anki_vector + with anki_vector.Robot() as robot: + robot.camera.enable_auto_exposure(False) + time.sleep(5) + + :param enable_auto_exposure: whether the camera should automatically adjust exposure + """ + + set_camera_settings_request = protocol.SetCameraSettingsRequest(enable_auto_exposure=enable_auto_exposure) + result = await self.conn.grpc_interface.SetCameraSettings(set_camera_settings_request) + self._auto_exposure_enabled = enable_auto_exposure + return result + + @connection.on_connection_thread() + async def set_manual_exposure(self, exposure_ms: int, gain: float) -> protocol.SetCameraSettingsResponse: + """Set manual exposure values for Vector's Camera. + + This will disable auto exposure on Vector's camera and force the specified exposure + time and gain values. + + .. testcode:: + + import time + import anki_vector + with anki_vector.Robot() as robot: + robot.camera.set_manual_exposure(1, 0.25) + time.sleep(5) + + :param exposure_ms: The desired exposure time in milliseconds. + Must be within the robot's exposure range from :attr:`CameraConfig.min_exposure_time_ms` to + :attr:`CameraConfig.max_exposure_time_ms` + :param gain: The desired gain value. + Must be within the robot's gain range from :attr:`CameraConfig.min_gain` to + :attr:`CameraConfig.max_gain` + Raises: + :class:`ValueError` if supplied an out-of-range exposure or gain + + """ + + if exposure_ms < self._config.min_exposure_time_ms \ + or exposure_ms > self._config.max_exposure_time_ms \ + or gain < self._config.min_gain \ + or gain > self._config.max_gain: + raise ValueError("Exposure settings out of range") + + set_camera_settings_request = protocol.SetCameraSettingsRequest(gain=gain, + exposure_ms=exposure_ms, + enable_auto_exposure=False) + result = await self.conn.grpc_interface.SetCameraSettings(set_camera_settings_request) + self._gain = gain + self._exposure_ms = exposure_ms + self._auto_exposure_enabled = False + return result + class EvtNewRawCameraImage: # pylint: disable=too-few-public-methods """Dispatched when a new raw image is received from the robot's camera. diff --git a/anki_vector/camera_viewer/__init__.py b/anki_vector/camera_viewer/__init__.py index aacabcc..da66443 100644 --- a/anki_vector/camera_viewer/__init__.py +++ b/anki_vector/camera_viewer/__init__.py @@ -1,121 +1,121 @@ -# Copyright (c) 2018 Anki, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License in the file LICENSE.txt or at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This module provides the camera viewer's render process. - -It should be launched in a separate process to allow Vector to run freely while -the viewer is rendering. - -It uses Tkinter, a standard Python GUI package. -It also depends on the Pillow library for image processing. -""" - -import multiprocessing as mp -import sys -import tkinter as tk - -try: - from PIL import ImageTk -except ImportError: - sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") - - -class TkCameraViewer: # pylint: disable=too-few-public-methods - """A Tkinter based camera video feed. - - :param queue: A queue to send frames between the user's main thread and the viewer process. - :param event: An event to signal that the viewer process has closed. - :param overlays: Overlays to be drawn on the images of the renderer. - :param timeout: The time without a new frame before the process will exit. - :param force_on_top: Specifies whether the window should be forced on top of all others. - """ - - def __init__(self, queue: mp.Queue, event: mp.Event, overlays: list = None, timeout: float = 10.0, force_on_top: bool = True): - self.tk_root = tk.Tk() - self.width = None - self.height = None - self.queue = queue - self.event = event - self.overlays = overlays - self.timeout = timeout - self.tk_root.title("Vector Camera Feed") - self.tk_root.protocol("WM_DELETE_WINDOW", self._delete_window) - self.tk_root.bind("", self._resize_window) - if force_on_top: - self.tk_root.wm_attributes("-topmost", 1) - self.label = tk.Label(self.tk_root, borderwidth=0) - self.label.pack(fill=tk.BOTH, expand=True) - - def _delete_window(self) -> None: - """Handle window close event.""" - self.event.set() - self.tk_root.destroy() - - def _resize_window(self, evt: tk.Event) -> None: - """Handle window resize event. - - :param evt: A Tkinter window event (keyboard, mouse events, etc). - """ - self.width = evt.width - self.height = evt.height - - def draw_frame(self) -> None: - """Display an image on to a Tkinter label widget.""" - try: - image = self.queue.get(True, timeout=self.timeout) - except: - return - self.width, self.height = image.size - while image: - if self.event.is_set(): - break - if self.overlays: - for overlay in self.overlays: - overlay.apply_overlay(image) - if (self.width, self.height) != image.size: - image = image.resize((self.width, self.height)) - tk_image = ImageTk.PhotoImage(image) - self.label.config(image=tk_image) - self.label.image = tk_image - self.tk_root.update_idletasks() - self.tk_root.update() - try: - image = self.queue.get(True, timeout=self.timeout) - except: - return - - -def main(queue: mp.Queue, event: mp.Event, overlays: list = None, timeout: float = 10.0, force_on_top: bool = False) -> None: - """Rendering the frames in another process. This allows the UI to have the - main thread of its process while the user code continues to execute. - - :param queue: A queue to send frames between the user's main thread and the viewer process. - :param event: An event to signal that the viewer process has closed. - :param overlays: Overlays to be drawn on the images of the renderer. - :param timeout: The time without a new frame before the process will exit. - :param force_on_top: Specifies whether the window should be forced on top of all others. - """ - - try: - tk_viewer = TkCameraViewer(queue, event, overlays, timeout, force_on_top) - tk_viewer.draw_frame() - except TimeoutError: - pass - except KeyboardInterrupt: - pass - finally: - event.set() - - -__all__ = ['TkCameraViewer', 'main'] +# Copyright (c) 2018 Anki, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License in the file LICENSE.txt or at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module provides the camera viewer's render process. + +It should be launched in a separate process to allow Vector to run freely while +the viewer is rendering. + +It uses Tkinter, a standard Python GUI package. +It also depends on the Pillow library for image processing. +""" + +import multiprocessing as mp +import sys +import tkinter as tk + +try: + from PIL import ImageTk +except ImportError: + sys.exit('Cannot import from PIL: Do `pip3 install --user "cyb3r_vector_sdk[3dviewer]"` to install') + + +class TkCameraViewer: # pylint: disable=too-few-public-methods + """A Tkinter based camera video feed. + + :param queue: A queue to send frames between the user's main thread and the viewer process. + :param event: An event to signal that the viewer process has closed. + :param overlays: Overlays to be drawn on the images of the renderer. + :param timeout: The time without a new frame before the process will exit. + :param force_on_top: Specifies whether the window should be forced on top of all others. + """ + + def __init__(self, queue: mp.Queue, event: mp.Event, overlays: list = None, timeout: float = 10.0, force_on_top: bool = True): + self.tk_root = tk.Tk() + self.width = None + self.height = None + self.queue = queue + self.event = event + self.overlays = overlays + self.timeout = timeout + self.tk_root.title("Vector Camera Feed") + self.tk_root.protocol("WM_DELETE_WINDOW", self._delete_window) + self.tk_root.bind("", self._resize_window) + if force_on_top: + self.tk_root.wm_attributes("-topmost", 1) + self.label = tk.Label(self.tk_root, borderwidth=0) + self.label.pack(fill=tk.BOTH, expand=True) + + def _delete_window(self) -> None: + """Handle window close event.""" + self.event.set() + self.tk_root.destroy() + + def _resize_window(self, evt: tk.Event) -> None: + """Handle window resize event. + + :param evt: A Tkinter window event (keyboard, mouse events, etc). + """ + self.width = evt.width + self.height = evt.height + + def draw_frame(self) -> None: + """Display an image on to a Tkinter label widget.""" + try: + image = self.queue.get(True, timeout=self.timeout) + except: + return + self.width, self.height = image.size + while image: + if self.event.is_set(): + break + if self.overlays: + for overlay in self.overlays: + overlay.apply_overlay(image) + if (self.width, self.height) != image.size: + image = image.resize((self.width, self.height)) + tk_image = ImageTk.PhotoImage(image) + self.label.config(image=tk_image) + self.label.image = tk_image + self.tk_root.update_idletasks() + self.tk_root.update() + try: + image = self.queue.get(True, timeout=self.timeout) + except: + return + + +def main(queue: mp.Queue, event: mp.Event, overlays: list = None, timeout: float = 10.0, force_on_top: bool = False) -> None: + """Rendering the frames in another process. This allows the UI to have the + main thread of its process while the user code continues to execute. + + :param queue: A queue to send frames between the user's main thread and the viewer process. + :param event: An event to signal that the viewer process has closed. + :param overlays: Overlays to be drawn on the images of the renderer. + :param timeout: The time without a new frame before the process will exit. + :param force_on_top: Specifies whether the window should be forced on top of all others. + """ + + try: + tk_viewer = TkCameraViewer(queue, event, overlays, timeout, force_on_top) + tk_viewer.draw_frame() + except TimeoutError: + pass + except KeyboardInterrupt: + pass + finally: + event.set() + + +__all__ = ['TkCameraViewer', 'main'] diff --git a/anki_vector/configure/__main__.py b/anki_vector/configure/__main__.py index 41c08cf..252cafe 100755 --- a/anki_vector/configure/__main__.py +++ b/anki_vector/configure/__main__.py @@ -30,6 +30,7 @@ import json import os from pathlib import Path +import platform import re import socket import sys @@ -66,7 +67,10 @@ class Api: def __init__(self): self._handler = ApiHandler( headers={ - 'User-Agent': f'Vector-sdk/{anki_vector.__version__}', + 'User-Agent': 'Vector-sdk/{} {}/{}'.format(anki_vector.__version__, + platform.python_implementation(), + platform.python_version()), + 'Anki-App-Key': 'aung2ieCho3aiph7Een3Ei' }, url='https://accounts.api.anki.com/1/sessions' diff --git a/anki_vector/configure_pod/__main__.py b/anki_vector/configure_pod/__main__.py new file mode 100644 index 0000000..423b8d0 --- /dev/null +++ b/anki_vector/configure_pod/__main__.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 + +""" +***Vector EscapePod Python SDK Setup*** + +Vector requires all requests be authorized by an authenticated Anki user. + +This script will enable this device to authenticate with your Vector +robot for use with a Vector Python SDK program. + +Vector must be powered on and connected on the same network as your +computer. By running this script, you will be asked to provide your +Anki account credentials, and the script will download an authentication +token and cert that will grant you access to the robot and his +capabilities (such as camera and audio) as well as data stored on the +robot (such as faces and photos). + +See the README for more information. + +Use of Vector and the Vector SDK is subject to Anki's Privacy Policy and Terms and Conditions. + +https://www.anki.com/en-us/company/privacy +https://www.anki.com/en-us/company/terms-and-conditions + +""" + +import argparse +import configparser +from getpass import getpass +import json +import os +from pathlib import Path +import platform +import re +import socket +import sys +import ssl + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +import grpc +import requests +try: + from termcolor import colored # pylint: disable=import-error +except: # pylint: disable=bare-except + def colored(text, color=None, on_color=None, attrs=None): # pylint: disable=unused-argument + return text + +import anki_vector +from anki_vector import messaging + + +def get_serial(serial=None): + if not serial: + serial = os.environ.get('ANKI_ROBOT_SERIAL') + if not serial: + print("\n\nPlease find your robot serial number (ex. 00e20100) located on the underside of Vector, or accessible from Vector's debug screen.") + serial = input('Enter robot serial number: ') + else: + print("Found robot serial number in environment variable '{}'".format(colored("ANKI_ROBOT_SERIAL", "green"))) + serial = serial.lower() + print("Using robot serial number: {}".format(colored(serial, "cyan"))) + return serial + + +def get_cert(hostname=None): + print("\nDownloading Vector certificate...", end="") + sys.stdout.flush() + + conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + sock = context.wrap_socket(conn, server_hostname=hostname) + sock.connect((hostname, 443)) + cert = ssl.DER_cert_to_PEM_cert(sock.getpeercert(True)) + return str.encode(cert) + + +def user_authentication(session_id: bytes, cert: bytes, ip: str, name: str) -> str: + # Pin the robot certificate for opening the channel + creds = grpc.ssl_channel_credentials(root_certificates=cert) + + print("Attempting to download guid from {} at {}:443...".format(colored(name, "cyan"), colored(ip, "cyan")), end="") + sys.stdout.flush() + channel = grpc.secure_channel("{}:443".format(ip), creds, + options=(("grpc.ssl_target_name_override", name,),)) + + # Verify the connection to Vector is able to be established (client-side) + try: + # Explicitly grab _channel._channel to test the underlying grpc channel directly + grpc.channel_ready_future(channel).result(timeout=15) + except grpc.FutureTimeoutError: + print(colored(" ERROR", "red")) + sys.exit("\nUnable to connect to Vector\n" + "Please be sure to connect via the Vector companion app first, and connect your computer to the same network as your Vector.") + + try: + interface = messaging.client.ExternalInterfaceStub(channel) + request = messaging.protocol.UserAuthenticationRequest( + user_session_id=session_id.encode('utf-8'), + client_name=socket.gethostname().encode('utf-8')) + response = interface.UserAuthentication(request) + if response.code != messaging.protocol.UserAuthenticationResponse.AUTHORIZED: # pylint: disable=no-member + print(colored(" ERROR", "red")) + sys.exit("\nFailed to authorize request:\n" + "Please be sure to first set up Vector using the companion app.") + except grpc.RpcError as e: + print(colored(" ERROR", "red")) + sys.exit("\nFailed to authorize request:\n" + "An unknown error occurred '{}'".format(e)) + + print(colored(" DONE\n", "green")) + return response.client_token_guid + + + +def standardize_name(robot_name): + # Extend the name if not enough is provided + if len(robot_name) == 4: + robot_name = "Vector-{}".format(robot_name.upper()) + # Fix possible capitalization and space/dash/etc. + if re.match("[Vv]ector.[A-Za-z0-9]{4}", robot_name): + robot_name = "V{}-{}".format(robot_name[1:-5], robot_name[-4:].upper()) + # Check that the end is valid + if re.match("Vector-[A-Z0-9]{4}", robot_name): + return robot_name + print(colored(" ERROR", "red")) + sys.exit("Invalid robot name. Please match the format exactly. Example: Vector-A1B2") + + +def get_name_and_ip(robot_name=None, ip=None): + if not robot_name: + robot_name = os.environ.get('VECTOR_ROBOT_NAME') + if not robot_name: + print("\n\nFind your robot name (ex. Vector-A1B2) by placing Vector on the charger and double-clicking Vector's backpack button.") + robot_name = input("Enter robot name: ") + else: + print("Found robot name in environment variable '{}'".format(colored("VECTOR_ROBOT_NAME", "green"))) + robot_name = standardize_name(robot_name) + print("Using robot name: {}".format(colored(robot_name, "cyan"))) + if not ip: + ip = os.environ.get('ANKI_ROBOT_HOST') + if not ip: + print("\n\nFind your robot ip address (ex. 192.168.42.42) by placing Vector on the charger, double-clicking Vector's backpack button,\n" + "then raising and lowering his arms. If you see {} on his face, reconnect Vector to your WiFi using the Vector Companion App.".format(colored("XX.XX.XX.XX", "red"))) + ip = input("Enter robot ip: ") + else: + print("Found robot ip address in environment variable '{}'".format(colored("ANKI_ROBOT_HOST", "green"))) + print("Using IP: {}".format(colored(ip, "cyan"))) + return robot_name, ip + + +def save_cert(cert, name, serial, anki_dir): + """Write Vector's certificate to a file located in the user's home directory""" + os.makedirs(str(anki_dir), exist_ok=True) + cert_file = str(anki_dir / "{name}-{serial}.cert".format(name=name, serial=serial)) + print("Writing certificate file to '{}'...\n".format(colored(cert_file, "cyan"))) + with os.fdopen(os.open(cert_file, os.O_WRONLY | os.O_CREAT, 0o600), 'wb') as f: + f.write(cert) + return cert_file + + +def validate_cert_name(cert_file, robot_name): + """Validate the name on Vector's certificate against the user-provided name""" + with open(cert_file, "rb") as f: + cert_file = f.read() + cert = x509.load_pem_x509_certificate(cert_file, default_backend()) + for fields in cert.subject: + current = str(fields.oid) + if "commonName" in current: + common_name = fields.value + if common_name != robot_name: + print(colored(" ERROR", "red")) + sys.exit("The name of the certificate ({}) does not match the name provided ({}).\n" + "Please verify the name, and try again.".format(common_name, robot_name)) + else: + return + + +def write_config(serial, cert_file=None, ip=None, name=None, guid=None, clear=True): + home = Path.home() + config_file = str(home / ".anki_vector" / "sdk_config.ini") + print("Writing config file to '{}'...".format(colored(config_file, "cyan"))) + + config = configparser.ConfigParser(strict=False) + + try: + config.read(config_file) + except configparser.ParsingError: + if os.path.exists(config_file): + os.rename(config_file, config_file + "-error") + if clear: + config[serial] = {} + if cert_file: + config[serial]["cert"] = cert_file + if ip: + config[serial]["ip"] = ip + if name: + config[serial]["name"] = name + if guid: + config[serial]["guid"] = guid.decode("utf-8") + temp_file = config_file + "-temp" + if os.path.exists(config_file): + os.rename(config_file, temp_file) + try: + with os.fdopen(os.open(config_file, os.O_WRONLY | os.O_CREAT, 0o600), 'w') as f: + config.write(f) + except Exception as e: + if os.path.exists(temp_file): + os.rename(temp_file, config_file) + raise e + else: + if os.path.exists(temp_file): + os.remove(temp_file) + + +def main(): + parser = argparse.ArgumentParser(description=("Vector requires all requests be authorized by an authenticated Anki user. " + "This script will enable this device to authenticate with your Vector " + "robot for use with a Vector Python SDK program."), + epilog=("See the README for more information. " + "Use of Vector and the Vector SDK is subject to Anki's Privacy Policy and Terms and Conditions. " + "https://www.anki.com/en-us/company/privacy and " + "https://www.anki.com/en-us/company/terms-and-conditions")) + parser.add_argument("-i", "--ip", help=("Your robot ip address (ex. 192.168.42.42). " + "It may be found by placing Vector on the charger, " + "double-clicking Vector's backpack button, " + "then raising and lowering his arms. " + "If you see {} on his face, " + "reconnect Vector to your WiFi using the Vector Companion App.".format(colored("XX.XX.XX.XX", "red")))) + parser.add_argument("-n", "--name", help=("Your robot name (ex. Vector-A1B2). " + "It may be found by placing Vector on the charger and double-clicking Vector's backpack button.")) + parser.add_argument("-s", "--serial", help=("Your robot serial number (ex. 00e20100). " + "It is located on the underside of Vector, or accessible from Vector's debug screen.")) + parser.add_argument("-u", "--update", dest="new_ip", help=("Update the stored ip for Vector. This makes it easier to transfer between networks.")) + args = parser.parse_args() + + if args.new_ip: + serial = get_serial(args.serial) + write_config(serial, ip=args.new_ip, clear=False) + print(colored("\nIP Updated!", "green")) + sys.exit() + + print(__doc__) + + valid = ["y", "Y", "yes", "YES"] + environ = input("Do you wish to proceed? (y/n) ") + if environ not in valid: + sys.exit("Stopping...") + + name, ip = get_name_and_ip(args.name, args.ip) + serial = get_serial(args.serial) + cert = get_cert(ip) + + home = Path.home() + anki_dir = home / ".anki_vector" + + cert_file = save_cert(cert, name, serial, anki_dir) + validate_cert_name(cert_file, name) + + guid = user_authentication("Anything1", cert, ip, name) + + # Store credentials in the .anki_vector directory's sdk_config.ini file + write_config(serial, cert_file, ip, name, guid) + print(colored("\nSUCCESS!", "green")) + +if __name__ == "__main__": + main() diff --git a/anki_vector/connection.py b/anki_vector/connection.py index 472b0d8..51c5cab 100644 --- a/anki_vector/connection.py +++ b/anki_vector/connection.py @@ -35,6 +35,7 @@ import aiogrpc from . import util +from .escapepod import EscapePod from .exceptions import (connection_error, VectorAsyncException, VectorBehaviorControlException, @@ -47,6 +48,14 @@ from .version import __version__ +class CancelType(Enum): + """Enum used to specify cancellation options for behaviors -- internal use only """ + #: Cancellable as an 'Action' + CANCELLABLE_ACTION = 0 + #: Cancellable as a 'Behavior' + CANCELLABLE_BEHAVIOR = 1 + + class ControlPriorityLevel(Enum): """Enum used to specify the priority level for the program.""" #: Runs above mandatory physical reactions, will drive off table, perform while on a slope, @@ -198,13 +207,12 @@ async def play_animation(): requires behavior control, or None to decline control. """ - def __init__(self, name: str, host: str, cert_file: str, guid: str, behavior_control_level: ControlPriorityLevel = ControlPriorityLevel.DEFAULT_PRIORITY): - if cert_file is None: - raise VectorConfigurationException("Must provide a cert file to authenticate to Vector.") + def __init__(self, name: str, host: str, cert_file: str, guid: str, escape_pod: bool = False, behavior_control_level: ControlPriorityLevel = ControlPriorityLevel.DEFAULT_PRIORITY): self._loop: asyncio.BaseEventLoop = None self.name = name self.host = host self.cert_file = cert_file + self._escape_pod = escape_pod self._interface = None self._channel = None self._has_control = False @@ -466,7 +474,7 @@ async def play_animation(): self._ready_signal.clear() self._thread = threading.Thread(target=self._connect, args=(timeout,), daemon=True, name="gRPC Connection Handler Thread") self._thread.start() - ready = self._ready_signal.wait(timeout=2 * timeout) + ready = self._ready_signal.wait(timeout=4 * timeout) if not ready: raise VectorNotFoundException() if hasattr(self._ready_signal, "exception"): @@ -488,9 +496,20 @@ def _connect(self, timeout: float) -> None: self._control_events = _ControlEventManager(self._loop) else: self._control_events = _ControlEventManager(self._loop, priority=self._behavior_control_level) + trusted_certs = None - with open(self.cert_file, 'rb') as cert: - trusted_certs = cert.read() + if not self.cert_file is None: + with open(self.cert_file, 'rb') as cert: + trusted_certs = cert.read() + else: + if not self._escape_pod: + raise VectorConfigurationException("Must provide a cert file to authenticate to Vector.") + + if self._escape_pod: + if not EscapePod.validate_certificate_name(self.cert_file, self.name): + trusted_certs = EscapePod.get_authentication_certificate(self.host) + self.name = EscapePod.get_certificate_name(trusted_certs) + self._guid = EscapePod.authenticate_escape_pod(self.host, self.name, trusted_certs) # Pin the robot certificate for opening the channel channel_credentials = aiogrpc.ssl_channel_credentials(root_certificates=trusted_certs) @@ -535,6 +554,10 @@ def _connect(self, timeout: float) -> None: if self._behavior_control_level: self._loop.run_until_complete(self._request_control(behavior_control_level=self._behavior_control_level, timeout=timeout)) + except grpc.RpcError as rpc_error: # pylint: disable=broad-except + setattr(self._ready_signal, "exception", connection_error(rpc_error)) + self._loop.close() + return except Exception as e: # pylint: disable=broad-except # Propagate the errors to the calling thread setattr(self._ready_signal, "exception", e) @@ -609,17 +632,21 @@ async def play_animation(): # Close the connection conn.close() """ - if self._control_events: - self._control_events.shutdown() - if self._control_stream_task: - self._control_stream_task.cancel() - self.run_coroutine(self._control_stream_task).result() - self._cancel_active() - if self._channel: - self.run_coroutine(self._channel.close()).result() - self.run_coroutine(self._done_signal.set) - self._thread.join(timeout=5) - self._thread = None + try: + if self._control_events: + self._control_events.shutdown() + if self._control_stream_task: + self._control_stream_task.cancel() + self.run_coroutine(self._control_stream_task).result() + self._cancel_active() + if self._channel: + self.run_coroutine(self._channel.close()).result() + self.run_coroutine(self._done_signal.set) + self._thread.join(timeout=5) + except: + pass + finally: + self._thread = None def run_soon(self, coro: Awaitable) -> None: """Schedules the given awaitable to run on the event loop for the connection thread. @@ -689,7 +716,7 @@ def _run_coroutine(self, coro): return asyncio.run_coroutine_threadsafe(coro, self._loop) -def on_connection_thread(log_messaging: bool = True, requires_control: bool = True, is_cancellable_behavior=False) -> Callable[[Coroutine[util.Component, Any, None]], Any]: +def on_connection_thread(log_messaging: bool = True, requires_control: bool = True, is_cancellable: CancelType = None) -> Callable[[Coroutine[util.Component, Any, None]], Any]: """A decorator generator used internally to denote which functions will run on the connection thread. This unblocks the caller of the wrapped function and allows them to continue running while the messages are being processed. @@ -706,7 +733,8 @@ async def on_connection_thread(self): :param log_messaging: True if the log output should include the entire message or just the size. Recommended for large binary return values. :param requires_control: True if the function should wait until behavior control is granted before executing. - :param is_cancellable_behavior: True if the behavior can be cancelled before it has completed. + :param is_cancellable: use a valid enum of :class:`CancelType` to specify the type of cancellation for the + function. Defaults to 'None' implying no support for responding to cancellation. :returns: A decorator which has 3 possible returns based on context: the result of the decorated function, the :class:`concurrent.futures.Future` which points to the decorated function, or the :class:`asyncio.Future` which points to the decorated function. @@ -769,10 +797,10 @@ def result(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: # if the call supplies a _return_future parameter then override force_async with that. _return_future = kwargs.pop('_return_future', self.force_async) - behavior_id = None - if is_cancellable_behavior: - behavior_id = self._get_next_behavior_id() - kwargs['_behavior_id'] = behavior_id + action_id = None + if is_cancellable == CancelType.CANCELLABLE_ACTION: + action_id = self._get_next_action_id() + kwargs['_action_id'] = action_id wrapped_coroutine = log_handler(self.conn, func, self.logger, *args, **kwargs) @@ -783,15 +811,22 @@ def result(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: "function '{}' is being invoked on that thread.\n".format(func.__name__ if hasattr(func, "__name__") else func)) future = asyncio.run_coroutine_threadsafe(wrapped_coroutine, self.conn.loop) - if is_cancellable_behavior: - def user_cancelled(fut): - if behavior_id is None: + if is_cancellable == CancelType.CANCELLABLE_ACTION: + def user_cancelled_action(fut): + if action_id is None: return if fut.cancelled(): - self._abort(behavior_id) + self._abort_action(action_id) + + future.add_done_callback(user_cancelled_action) + + if is_cancellable == CancelType.CANCELLABLE_BEHAVIOR: + def user_cancelled_behavior(fut): + if fut.cancelled(): + self._abort_behavior() - future.add_done_callback(user_cancelled) + future.add_done_callback(user_cancelled_behavior) if requires_control: self.conn.active_commands.append(future) diff --git a/anki_vector/escapepod.py b/anki_vector/escapepod.py new file mode 100644 index 0000000..b67f7cb --- /dev/null +++ b/anki_vector/escapepod.py @@ -0,0 +1,171 @@ +# Copyright (c) 2021 cyb3rdog +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License in the file LICENSE.txt or at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc +import json +import ssl +import socket +import requests +import base64 +import urllib3 +urllib3.disable_warnings() + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend + +from os import name + +from . import messaging +from .exceptions import (connection_error, + VectorUnauthenticatedException, + VectorNotFoundException) + +class EscapePodMetadataPlugin: + """A specification for custom escape pod authentication.""" + + def __init__(self, token_guid): + self.token_hash = token_guid + + def __call__(self, context, callback): + """Implements authentication by passing metadata to a callback. + Args: + access_token: A string to place directly in the http request + authorization header, for example + "authorization: Bearer ". + callback: An EscapePodMetadataPlugin to be invoked either + synchronously or asynchronously. + """ + + callback((('authorization', 'Bearer ' + self.token_hash ),), None) + + +class EscapePod: + + def url_for(host, method): + return "https://{}/v1/{}".format(host, method) + + # Make a request and return a pair (status_code,result_json) + # result_json is null if the call fails + def request(host, guid, method, request_json): + url = EscapePod.url_for( host, method ) + auth = 'Bearer ' + guid + headers = {"Authorization": auth, "Content-Type" : "application/json" } + try: + response = requests.post( url, json=request_json, headers=headers, stream=False, + # cert = self._cfg['cert'] ) + verify=False ) + except requests.exceptions.ConnectionError as e: + raise VectorNotFoundException() from e + except requests.exceptions.Timeout as e: + raise VectorNotFoundException() from e + except requests.exceptions.RequestException as e: + raise VectorNotFoundException() from e + + if response.status_code == 200: + return (response.status_code, response.json()) + + return (response.status_code, None) + + # + # The new DDL's Go-SDK uses grpc insecure channel together with call credentials. + # That kind of credentials composition is not supported on low-level of many grpc + # implementations including the python's grpc core. The reason for that is to + # discourage developers from sending the credentials over the unecrypted channels. + # This method is a replication of the Go-SDK current authentication method and is + # not being used by this SDK. For the reference and educational purposes only. + # + def authenticate_insecure(host: str, name:str, cert: bytes = None) -> str: + + guid = base64.b64encode( bytes("Anything1", 'utf-8')).decode('utf-8') + request_json = {"user_session_id": guid } + response = EscapePod.request( host, guid, 'user_authentication', request_json ) + if response[1]: + guid = base64.b64decode(response[1]['client_token_guid']).decode( 'utf-8' ) + return guid + else: + print( response[0] ) + + return None + + + def authenticate_escape_pod(host: str, name: str, cert: bytes = None) -> str: + """Authenticates the escape pod Vector and returns the token hash guid""" + + call_credentials = grpc.metadata_call_credentials(EscapePodMetadataPlugin('Anything1'), + name='authorization') + # Channel credential will be valid for the entire channel + channel_credential = grpc.ssl_channel_credentials(root_certificates=cert) + + # Combining channel credentials and call credentials together + credentials = grpc.composite_channel_credentials( + channel_credential, + call_credentials, + ) + + channel = grpc.secure_channel(host, credentials, options=(("grpc.ssl_target_name_override", name,),)) + + # Verify the connection to Vector is able to be established (client-side) + try: + # Explicitly grab _channel._channel to test the underlying grpc channel directly + grpc.channel_ready_future(channel).result(timeout=15) + except grpc.FutureTimeoutError as e: + raise VectorNotFoundException() from e + + try: + interface = messaging.client.ExternalInterfaceStub(channel) + request = messaging.protocol.UserAuthenticationRequest( + user_session_id='anything1'.encode('utf-8'), + client_name='anything2'.encode('utf-8')) + + response = interface.UserAuthentication(request) + if response.code != messaging.protocol.UserAuthenticationResponse.AUTHORIZED: # pylint: disable=no-member + raise VectorUnauthenticatedException('Failed to authenticate') + except grpc.RpcError as e: + raise VectorUnauthenticatedException() from e + + return response.client_token_guid.decode("utf-8") + + + def get_authentication_certificate(hostname:str) -> str: + """Get the Vector gateway certificate""" + host = hostname.split(":")[0] + port = int(hostname.split(":")[1] or 443) + conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + sock = context.wrap_socket(conn, server_hostname=host) + sock.connect((host, port)) + cert = ssl.DER_cert_to_PEM_cert(sock.getpeercert(True)) + return str.encode(cert) + + + def get_certificate_name(cert_data) -> str: + """Validate the name on Vector's certificate against the user-provided name""" + if cert_data is None: + return None + + cert = x509.load_pem_x509_certificate(cert_data, default_backend()) + for fields in cert.subject: + current = str(fields.oid) + if "commonName" in current: + return fields.value + + + def validate_certificate_name(cert_file, robot_name) -> bool: + """Validate the name on Vector's certificate against the user-provided name""" + if cert_file is None or robot_name is None: + return False + + with open(cert_file, "rb") as f: + cert_data = f.read() + return EscapePod.get_certificate_name(cert_data) == robot_name \ No newline at end of file diff --git a/anki_vector/events.py b/anki_vector/events.py index 259bc28..c847896 100644 --- a/anki_vector/events.py +++ b/anki_vector/events.py @@ -37,6 +37,7 @@ class Events(Enum): robot_state = "robot_state" #: Robot event containing changes to the robot's state. mirror_mode_disabled = "mirror_mode_disabled" # : Robot event triggered when mirror mode (camera feed displayed on robot's face) is automatically disabled due to SDK no longer having control of the robot. vision_modes_auto_disabled = "vision_modes_auto_disabled" # : Robot event triggered when all vision modes are automatically disabled due to the SDK no longer having control of the robot. + camera_settings_update = "camera_settings_update" # : Robot event triggered when camera exposure settings change # Objects object_available = "object_available" #: After the ConnectCube process is started, all available light cubes in range will broadcast an availability message through the Robot. @@ -48,16 +49,23 @@ class Events(Enum): robot_observed_object = "robot_observed_object" #: Robot event triggered when an object is observed by the robot. cube_connection_lost = "cube_connection_lost" #: Robot event triggered when an object's subscribed connection has been lost. - robot_observed_face = "robot_observed_face" #: Robot event for when a face is observed by the robot. - robot_changed_observed_face_id = "robot_changed_observed_face_id" # : Robot event for when a known face changes its id. + robot_observed_motion = "robot_observed_motion" #: Robot event dispatched when Vector observes motion. + robot_observed_face = "robot_observed_face" #: Robot event for when a face is observed by the robot. + robot_changed_observed_face_id = "robot_changed_observed_face_id" #: Robot event for when a known face changes its id. + robot_erased_enrolled_face = "robot_erased_enrolled_face" #: Robot event for when an enrolled face has been removed from the robot. + robot_renamed_enrolled_face = "robot_renamed_enrolled_face" #: Robot event for when a known face changes its name. + unexpected_movement = "unexpected_movement" #: Robot event for when it detects movement that does not match what motors were commanded (e.g. turning in the wrong direction) - wake_word = "wake_word" #: Robot event triggered when Vector hears "Hey Vector" - user_intent = "user_intent" #: Robot event triggered after Vector processes voice commands + wake_word = "wake_word" #: Robot event triggered when Vector hears "Hey Vector". + user_intent = "user_intent" #: Robot event triggered after Vector processes voice commands. # Audio audio_send_mode_changed = "audio_send_mode_changed" #: Robot event containing changes to the robot's audio stream source data processing mode. # Generated by SDK + face_observed = "face_observed" #: Python event triggered in response to robot_observed_face with sdk metadata. + face_appeared = "face_appeared" #: Python event triggered when an face first receives robot_observed_face. + face_disappeared = "face_disappeared" #: Python event triggered when an face has not received a robot_observed_face for a specified time. object_observed = "object_observed" #: Python event triggered in response to robot_observed_object with sdk metadata. object_appeared = "object_appeared" #: Python event triggered when an object first receives robot_observed_object. object_disappeared = "object_disappeared" #: Python event triggered when an object has not received a robot_observed_object for a specified time. diff --git a/anki_vector/exceptions.py b/anki_vector/exceptions.py index 309ddbf..83548a6 100644 --- a/anki_vector/exceptions.py +++ b/anki_vector/exceptions.py @@ -24,6 +24,7 @@ __all__ = ['VectorAsyncException', 'VectorBehaviorControlException', 'VectorCameraFeedException', + 'VectorCameraImageCaptureException', 'VectorConfigurationException', 'VectorConnectionException', 'VectorControlException', @@ -144,6 +145,10 @@ class VectorCameraFeedException(_VectorGenericException): Make sure to enable the camera feed either using Robot(show_viewer=True), or robot.camera.init_camera_feed()""" +class VectorCameraImageCaptureException(_VectorGenericException): + """Image capture exception.""" + + class VectorConfigurationException(_VectorGenericException): """Invalid or missing configuration data.""" diff --git a/anki_vector/faces.py b/anki_vector/faces.py index d8d0e6f..49a680c 100644 --- a/anki_vector/faces.py +++ b/anki_vector/faces.py @@ -22,15 +22,22 @@ Each face is assigned a :class:`Face` object, which generates a number of observable events whenever the face is observed or when the face id is updated. + +Faces can generate events which can be subscribed to from the anki_vector.events +class, such as face_appeared (of type EvtFaceAppeared), and face_disappeared (of +type EvtFaceDisappeared), which are broadcast based on both robot originating +events and local state. """ # __all__ should order by constants, event classes, other classes, functions. -__all__ = ['Expression', 'Face', 'FaceComponent'] +__all__ = ['FACE_VISIBILITY_TIMEOUT', 'EvtFaceAppeared', 'EvtFaceDisappeared', + 'EvtFaceObserved', 'Expression', 'Face', 'FaceComponent'] from enum import Enum from typing import List -from . import connection, util, objects, events +from . import connection, events, util, objects +from .events import Events from .messaging import protocol #: Length of time in seconds to go without receiving an observed event before @@ -198,6 +205,8 @@ class Expression(Enum): #: Facial expression sadness SADNESS = protocol.FacialExpression.Value("EXPRESSION_SADNESS") +# TODO Review this file and add pytests as is reasonable, like name_face (requires a face object), request_enrolled_names, maybe update_enrolled_face_by_id, etc. + class Face(objects.ObservableObject): """A single face that Vector has detected. @@ -210,6 +219,10 @@ class Face(objects.ObservableObject): which face he is looking at. """ + #: Length of time in seconds to go without receiving an observed event before + #: assuming that Vector can no longer see a face. + visibility_timeout = FACE_VISIBILITY_TIMEOUT + def __init__(self, robot, pose: util.Pose, @@ -255,6 +268,56 @@ def __repr__(self): f"Updated face id: {self.updated_face_id} Name: {self.name} " f"Expression: {protocol.FacialExpression.Name(self.expression)}>") + @connection.on_connection_thread(requires_control=False) + async def name_face(self, name: str) -> protocol.EnrollFaceResponse: + """Request to enroll this face with a name. Vector will remember this name between SDK runs. + + Triggers Vector to run his animation that scans faces and use the camera feed to store the face. + + While enrolling a face, make sure to look at Vector straight-on during the enrollment from about 1.5 to 2 feet away. + + :param name: The name that will be assigned to this face. + + .. testcode:: + + import anki_vector + from anki_vector.util import degrees + + with anki_vector.Robot(enable_face_detection=True) as robot: + # If necessary, move Vector's Head and Lift to make it easy to see his face + robot.behavior.set_head_angle(degrees(45.0)) + robot.behavior.set_lift_height(0.0) + + # TODO Replace with wait_for_observed_face + face = None + for face in robot.world.visible_faces: + break + + if face is None: + print("--- No face found ---") + else: + print("--- Existing Face attributes ---") + print(f"Visible face name: {face.name}") + print(f"Visible face id: {face.face_id}") + + # Name this face "Boris" + face.name_face("Boris") + + print(f"{robot.faces.request_enrolled_names()}") + """ + self.logger.info("Enrolling face=%s with name '%s'", self, name) + + req = protocol.SetFaceToEnrollRequest(name=name, + observed_id=self.face_id, + save_id=0, # must be 0 if self.face_id doesn't already have a name + save_to_robot=True, + say_name=False, + use_music=False) + await self.grpc_interface.SetFaceToEnroll(req) + + enroll_face_request = protocol.EnrollFaceRequest() + return await self.grpc_interface.EnrollFace(enroll_face_request) + def teardown(self): """All faces will be torn down by the world when no longer needed.""" self._robot.events.unsubscribe(self._on_face_observed, @@ -631,6 +694,15 @@ def test_subscriber(robot, event_type, event): #### Private Event Handlers #### + def _dispatch_observed_event(self, image_rect): + self.conn.run_soon(self._robot.events.dispatch_event(EvtFaceObserved(self, image_rect=image_rect, name=self._name, pose=self._pose), Events.face_observed)) + + def _dispatch_appeared_event(self, image_rect): + self.conn.run_soon(self._robot.events.dispatch_event(EvtFaceAppeared(self, image_rect=image_rect, name=self._name, pose=self._pose), Events.face_appeared)) + + def _dispatch_disappeared_event(self): + self.conn.run_soon(self._robot.events.dispatch_event(EvtFaceDisappeared(self), Events.face_disappeared)) + def _on_face_observed(self, _robot, _event_type, msg): """Unpacks the face observed stream data from Vector into a Face instance.""" if self._face_id == msg.face_id: @@ -665,7 +737,7 @@ class FaceComponent(util.Component): """Manage the state of the faces on the robot.""" @connection.on_connection_thread(requires_control=False) - async def request_enrolled_names(self) -> protocol.RequestEnrolledNamesRequest: + async def request_enrolled_names(self) -> protocol.RequestEnrolledNamesResponse: """Asks the robot for the list of names attached to faces that it can identify. .. testcode:: @@ -691,7 +763,11 @@ async def update_enrolled_face_by_id(self, face_id: int, old_name: str, new_name import anki_vector + def on_robot_renamed_enrolled_face(robot, event_type, event): + print(f"----Face has been renamed on robot. Event: {event_type} = {event}----") + with anki_vector.Robot() as robot: + robot.events.subscribe(on_robot_renamed_enrolled_face, Events.robot_renamed_enrolled_face) robot.faces.update_enrolled_face_by_id(1, 'Hanns', 'Boris') """ req = protocol.UpdateEnrolledFaceByIDRequest(face_id=face_id, @@ -706,10 +782,24 @@ async def erase_enrolled_face_by_id(self, face_id: int): .. testcode:: + import time import anki_vector + from anki_vector.events import Events + + def on_robot_erased_enrolled_face(robot, event_type, event): + print(f"Face has been erased from robot. Event: {event_type} = {event}") with anki_vector.Robot() as robot: - robot.faces.erase_enrolled_face_by_id(1) + robot.events.subscribe(on_robot_erased_enrolled_face, Events.robot_erased_enrolled_face) + + name_data_list = robot.faces.request_enrolled_names() + print(f"Enrolled names: {name_data_list}") + + # Deletes all enrolled faces from Vector. Use with care! + for face in name_data_list.faces: + robot.faces.erase_enrolled_face_by_id(face.face_id) + + time.sleep(3) """ req = protocol.EraseEnrolledFaceByIDRequest(face_id=face_id) return await self.grpc_interface.EraseEnrolledFaceByID(req) diff --git a/anki_vector/messaging/alexa.proto b/anki_vector/messaging/alexa.proto index d4397d7..b780c4e 100644 --- a/anki_vector/messaging/alexa.proto +++ b/anki_vector/messaging/alexa.proto @@ -18,4 +18,41 @@ syntax = "proto3"; package Anki.Vector.external_interface; +import "anki_vector/messaging/response_status.proto"; + +enum AlexaAuthState { + // Invalid/error/versioning issue + ALEXA_AUTH_INVALID = 0; + + // Not opted in, or opt-in attempted but failed + ALEXA_AUTH_UNINITIALIZED = 1; + // Opted in, and attempting to authorize + ALEXA_AUTH_REQUESTING_AUTH = 2; + // Opted in, and waiting on the user to enter a code + ALEXA_AUTH_WAITING_FOR_CODE = 3; + // Opted in, and authorized / in use + ALEXA_AUTH_AUTHORIZED = 4; +} + +message AlexaAuthStateRequest { +} + +message AlexaAuthStateResponse { + ResponseStatus status = 1; + AlexaAuthState auth_state = 2; + string extra = 3; +} + +message AlexaOptInRequest { + bool opt_in = 1; +} + +message AlexaOptInResponse{ + ResponseStatus status = 1; +} + +message AlexaAuthEvent { + AlexaAuthState auth_state = 1; + string extra = 2; +} diff --git a/anki_vector/messaging/external_interface.proto b/anki_vector/messaging/external_interface.proto index aeaedb3..c1809fd 100644 --- a/anki_vector/messaging/external_interface.proto +++ b/anki_vector/messaging/external_interface.proto @@ -19,11 +19,13 @@ syntax = "proto3"; package Anki.Vector.external_interface; import "google/api/annotations.proto"; +import "anki_vector/messaging/alexa.proto"; import "anki_vector/messaging/behavior.proto"; import "anki_vector/messaging/cube.proto"; import "anki_vector/messaging/messages.proto"; import "anki_vector/messaging/nav_map.proto"; import "anki_vector/messaging/shared.proto"; +import "anki_vector/messaging/settings.proto"; //

The valid versions of the protocol.

//

Protocol versions are updated when messages change significantly: @@ -171,6 +173,14 @@ service ExternalInterface { }; } + // Enroll a face. Must be used with SetFaceToEnroll (v1.7) + rpc EnrollFace(EnrollFaceRequest) returns (EnrollFaceResponse) { + option (google.api.http) = { + post: "/v1/enroll_face", + body: "*" + }; + } + rpc EnableMarkerDetection(EnableMarkerDetectionRequest) returns (EnableMarkerDetectionResponse) { option (google.api.http) = { post: "/v1/enable_marker_detection", @@ -221,6 +231,14 @@ service ExternalInterface { }; } + // Cancel running SDK Behavior (v1.7) + rpc CancelBehavior(CancelBehaviorRequest) returns (CancelBehaviorResponse) { + option (google.api.http) = { + post: "/v1/cancel_behavior", + body: "*" + }; + } + // Tells Vector to drive to the specified pose and orientation. rpc GoToPose(GoToPoseRequest) returns (GoToPoseResponse) { option (google.api.http) = { @@ -480,6 +498,14 @@ service ExternalInterface { }; } + // Get Vector's camera configuration. (v1.7) + rpc GetCameraConfig(CameraConfigRequest) returns (CameraConfigResponse) { + option (google.api.http) = { + post: "/v1/get_camera_config", + body: "*" + }; + } + // Set Vector's eye color. rpc SetEyeColor(SetEyeColorRequest) returns (SetEyeColorResponse) { option (google.api.http) = { @@ -496,4 +522,109 @@ service ExternalInterface { }; } -} \ No newline at end of file + // Set Vector's camera settings (v1.7) + rpc SetCameraSettings(SetCameraSettingsRequest) returns (SetCameraSettingsResponse) { + option (google.api.http) = { + post: "/v1/set_camera_settings", + body: "*" + }; + } + + rpc AppIntent(AppIntentRequest) returns (AppIntentResponse) { + option (google.api.http) = { + post: "/v1/app_intent", + body: "*" + }; + } + + // Update settings + // Added by wayne@codaris.com + rpc UpdateSettings(UpdateSettingsRequest) returns (UpdateSettingsResponse) { + option (google.api.http) = { + post: "/v1/update_settings", + body: "*" + }; + } + + rpc GetLatestAttentionTransfer(LatestAttentionTransferRequest) returns (LatestAttentionTransferResponse) { + option (google.api.http) = { + post: "/v1/get_latest_attention_transfer", + body: "*" + }; + } + + // Pull Jdocs + // Added by wayne@codaris.com + rpc PullJdocs(PullJdocsRequest) returns (PullJdocsResponse) { + option (google.api.http) = { + post: "/v1/pull_jdocs", + body: "*" + }; + } + + rpc UpdateAccountSettings(UpdateAccountSettingsRequest) returns (UpdateAccountSettingsResponse) { + option (google.api.http) = { + post: "/v1/update_account_settings", + body: "*" + }; + } + + // StartUpdateEngine cycles the update-engine service (to start a new check for an update) and sets up a stream of + // UpdateStatusResponse Events. + rpc StartUpdateEngine(CheckUpdateStatusRequest) returns (CheckUpdateStatusResponse) { + option (google.api.http) = { + post: "/v1/start_update_engine", + body: "*" + }; + } + + // CheckUpdateStatus tells if the robot is ready to reboot and update. + rpc CheckUpdateStatus(CheckUpdateStatusRequest) returns (CheckUpdateStatusResponse) { + option (google.api.http) = { + post: "/v1/check_update_status", + body: "*" + }; + } + + rpc UpdateAndRestart(UpdateAndRestartRequest) returns (UpdateAndRestartResponse) { + option (google.api.http) = { + post: "/v1/update_and_restart", + body: "*" + }; + } + + rpc CheckCloudConnection(CheckCloudRequest) returns (CheckCloudResponse) { + option (google.api.http) = { + post: "/v1/check_cloud_connection", + body: "*" + }; + } + + rpc GetFeatureFlag(FeatureFlagRequest) returns (FeatureFlagResponse) { + option (google.api.http) = { + post: "/v1/feature_flag", + body: "*" + }; + } + + rpc GetFeatureFlagList(FeatureFlagListRequest) returns (FeatureFlagListResponse) { + option (google.api.http) = { + post: "/v1/feature_flag_list", + body: "*" + }; + } + + rpc GetAlexaAuthState(AlexaAuthStateRequest) returns (AlexaAuthStateResponse) { + option (google.api.http) = { + post: "/v1/alexa_auth_state", + body: "*" + }; + } + + rpc AlexaOptIn(AlexaOptInRequest) returns (AlexaOptInResponse) { + option (google.api.http) = { + post: "/v1/alexa_opt_in", + body: "*" + }; + } +} diff --git a/anki_vector/messaging/external_interface_pb2.py b/anki_vector/messaging/external_interface_pb2.py index 1a9e771..efe6c17 100644 --- a/anki_vector/messaging/external_interface_pb2.py +++ b/anki_vector/messaging/external_interface_pb2.py @@ -26,7 +26,7 @@ name='anki_vector/messaging/external_interface.proto', package='Anki.Vector.external_interface', syntax='proto3', - serialized_pb=_b('\n.anki_vector/messaging/external_interface.proto\x12\x1e\x41nki.Vector.external_interface\x1a\x1cgoogle/api/annotations.proto\x1a$anki_vector/messaging/behavior.proto\x1a anki_vector/messaging/cube.proto\x1a$anki_vector/messaging/messages.proto\x1a#anki_vector/messaging/nav_map.proto\x1a\"anki_vector/messaging/shared.proto*o\n\x0fProtocolVersion\x12\x1c\n\x18PROTOCOL_VERSION_UNKNOWN\x10\x00\x12\x1c\n\x18PROTOCOL_VERSION_MINIMUM\x10\x00\x12\x1c\n\x18PROTOCOL_VERSION_CURRENT\x10\x05\x1a\x02\x10\x01\x32\xddT\n\x11\x45xternalInterface\x12\xa3\x01\n\x0fProtocolVersion\x12\x36.Anki.Vector.external_interface.ProtocolVersionRequest\x1a\x37.Anki.Vector.external_interface.ProtocolVersionResponse\"\x1f\x82\xd3\xe4\x93\x02\x19\"\x14/v1/protocol_version:\x01*\x12\xab\x01\n\x11SDKInitialization\x12\x38.Anki.Vector.external_interface.SDKInitializationRequest\x1a\x39.Anki.Vector.external_interface.SDKInitializationResponse\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/sdk_initialization:\x01*\x12x\n\x0b\x44riveWheels\x12\x32.Anki.Vector.external_interface.DriveWheelsRequest\x1a\x33.Anki.Vector.external_interface.DriveWheelsResponse\"\x00\x12\x8c\x01\n\x14PlayAnimationTrigger\x12;.Anki.Vector.external_interface.PlayAnimationTriggerRequest\x1a\x35.Anki.Vector.external_interface.PlayAnimationResponse\"\x00\x12~\n\rPlayAnimation\x12\x34.Anki.Vector.external_interface.PlayAnimationRequest\x1a\x35.Anki.Vector.external_interface.PlayAnimationResponse\"\x00\x12\x9f\x01\n\x0eListAnimations\x12\x35.Anki.Vector.external_interface.ListAnimationsRequest\x1a\x36.Anki.Vector.external_interface.ListAnimationsResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/list_animations:\x01*\x12\xbc\x01\n\x15ListAnimationTriggers\x12<.Anki.Vector.external_interface.ListAnimationTriggersRequest\x1a=.Anki.Vector.external_interface.ListAnimationTriggersResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/list_animation_triggers:\x01*\x12o\n\x08MoveHead\x12/.Anki.Vector.external_interface.MoveHeadRequest\x1a\x30.Anki.Vector.external_interface.MoveHeadResponse\"\x00\x12o\n\x08MoveLift\x12/.Anki.Vector.external_interface.MoveLiftRequest\x1a\x30.Anki.Vector.external_interface.MoveLiftResponse\"\x00\x12~\n\rStopAllMotors\x12\x34.Anki.Vector.external_interface.StopAllMotorsRequest\x1a\x35.Anki.Vector.external_interface.StopAllMotorsResponse\"\x00\x12\xb5\x01\n\x13\x44isplayFaceImageRGB\x12:.Anki.Vector.external_interface.DisplayFaceImageRGBRequest\x1a;.Anki.Vector.external_interface.DisplayFaceImageRGBResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/v1/display_face_image_rgb:\x01*\x12\x9d\x01\n\x0b\x45ventStream\x12,.Anki.Vector.external_interface.EventRequest\x1a-.Anki.Vector.external_interface.EventResponse\"/\x82\xd3\xe4\x93\x02)\"\x10/v1/event_stream:\x01*Z\x12\x12\x10/v1/event_stream0\x01\x12\x9c\x01\n\x1b\x45xternalAudioStreamPlayback\x12:.Anki.Vector.external_interface.ExternalAudioStreamRequest\x1a;.Anki.Vector.external_interface.ExternalAudioStreamResponse\"\x00(\x01\x30\x01\x12\x88\x01\n\x0f\x42\x65haviorControl\x12\x36.Anki.Vector.external_interface.BehaviorControlRequest\x1a\x37.Anki.Vector.external_interface.BehaviorControlResponse\"\x00(\x01\x30\x01\x12\xb2\x01\n\x15\x41ssumeBehaviorControl\x12\x36.Anki.Vector.external_interface.BehaviorControlRequest\x1a\x37.Anki.Vector.external_interface.BehaviorControlResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/assume_behavior_control:\x01*0\x01\x12\xb8\x01\n\x14\x43\x61ncelFaceEnrollment\x12;.Anki.Vector.external_interface.CancelFaceEnrollmentRequest\x1a<.Anki.Vector.external_interface.CancelFaceEnrollmentResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/v1/cancel_face_enrollment:\x01*\x12\xb8\x01\n\x14RequestEnrolledNames\x12;.Anki.Vector.external_interface.RequestEnrolledNamesRequest\x1a<.Anki.Vector.external_interface.RequestEnrolledNamesResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/v1/request_enrolled_names:\x01*\x12\xc2\x01\n\x16UpdateEnrolledFaceByID\x12=.Anki.Vector.external_interface.UpdateEnrolledFaceByIDRequest\x1a>.Anki.Vector.external_interface.UpdateEnrolledFaceByIDResponse\")\x82\xd3\xe4\x93\x02#\"\x1e/v1/update_enrolled_face_by_id:\x01*\x12\xbe\x01\n\x15\x45raseEnrolledFaceByID\x12<.Anki.Vector.external_interface.EraseEnrolledFaceByIDRequest\x1a=.Anki.Vector.external_interface.EraseEnrolledFaceByIDResponse\"(\x82\xd3\xe4\x93\x02\"\"\x1d/v1/erase_enrolled_face_by_id:\x01*\x12\xbd\x01\n\x15\x45raseAllEnrolledFaces\x12<.Anki.Vector.external_interface.EraseAllEnrolledFacesRequest\x1a=.Anki.Vector.external_interface.EraseAllEnrolledFacesResponse\"\'\x82\xd3\xe4\x93\x02!\"\x1c/v1/erase_all_enrolled_faces:\x01*\x12\xa5\x01\n\x0fSetFaceToEnroll\x12\x36.Anki.Vector.external_interface.SetFaceToEnrollRequest\x1a\x37.Anki.Vector.external_interface.SetFaceToEnrollResponse\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/set_face_to_enroll:\x01*\x12\xbc\x01\n\x15\x45nableMarkerDetection\x12<.Anki.Vector.external_interface.EnableMarkerDetectionRequest\x1a=.Anki.Vector.external_interface.EnableMarkerDetectionResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/enable_marker_detection:\x01*\x12\xb4\x01\n\x13\x45nableFaceDetection\x12:.Anki.Vector.external_interface.EnableFaceDetectionRequest\x1a;.Anki.Vector.external_interface.EnableFaceDetectionResponse\"$\x82\xd3\xe4\x93\x02\x1e\"\x19/v1/enable_face_detection:\x01*\x12\xbc\x01\n\x15\x45nableMotionDetection\x12<.Anki.Vector.external_interface.EnableMotionDetectionRequest\x1a=.Anki.Vector.external_interface.EnableMotionDetectionResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/enable_motion_detection:\x01*\x12\xa8\x01\n\x10\x45nableMirrorMode\x12\x37.Anki.Vector.external_interface.EnableMirrorModeRequest\x1a\x38.Anki.Vector.external_interface.EnableMirrorModeResponse\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/enable_mirror_mode:\x01*\x12\xb8\x01\n\x14\x45nableImageStreaming\x12;.Anki.Vector.external_interface.EnableImageStreamingRequest\x1a<.Anki.Vector.external_interface.EnableImageStreamingResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/v1/enable_image_streaming:\x01*\x12\xc5\x01\n\x17IsImageStreamingEnabled\x12>.Anki.Vector.external_interface.IsImageStreamingEnabledRequest\x1a?.Anki.Vector.external_interface.IsImageStreamingEnabledResponse\")\x82\xd3\xe4\x93\x02#\"\x1e/v1/is_image_streaming_enabled:\x01*\x12\xb6\x01\n\x13\x43\x61ncelActionByIdTag\x12:.Anki.Vector.external_interface.CancelActionByIdTagRequest\x1a;.Anki.Vector.external_interface.CancelActionByIdTagResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/cancel_action_by_id_tag:\x01*\x12\x88\x01\n\x08GoToPose\x12/.Anki.Vector.external_interface.GoToPoseRequest\x1a\x30.Anki.Vector.external_interface.GoToPoseResponse\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/v1/go_to_pose:\x01*\x12\x98\x01\n\x0c\x44ockWithCube\x12\x33.Anki.Vector.external_interface.DockWithCubeRequest\x1a\x34.Anki.Vector.external_interface.DockWithCubeResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\"\x12/v1/dock_with_cube:\x01*\x12\xa4\x01\n\x0f\x44riveOffCharger\x12\x36.Anki.Vector.external_interface.DriveOffChargerRequest\x1a\x37.Anki.Vector.external_interface.DriveOffChargerResponse\" \x82\xd3\xe4\x93\x02\x1a\"\x15/v1/drive_off_charger:\x01*\x12\xa0\x01\n\x0e\x44riveOnCharger\x12\x35.Anki.Vector.external_interface.DriveOnChargerRequest\x1a\x36.Anki.Vector.external_interface.DriveOnChargerResponse\"\x1f\x82\xd3\xe4\x93\x02\x19\"\x14/v1/drive_on_charger:\x01*\x12\x8b\x01\n\tFindFaces\x12\x30.Anki.Vector.external_interface.FindFacesRequest\x1a\x31.Anki.Vector.external_interface.FindFacesResponse\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/v1/find_faces:\x01*\x12\xad\x01\n\x11LookAroundInPlace\x12\x38.Anki.Vector.external_interface.LookAroundInPlaceRequest\x1a\x39.Anki.Vector.external_interface.LookAroundInPlaceResponse\"#\x82\xd3\xe4\x93\x02\x1d\"\x18/v1/look_around_in_place:\x01*\x12\x8b\x01\n\tRollBlock\x12\x30.Anki.Vector.external_interface.RollBlockRequest\x1a\x31.Anki.Vector.external_interface.RollBlockResponse\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/v1/roll_block:\x01*\x12\x8f\x01\n\nPhotosInfo\x12\x31.Anki.Vector.external_interface.PhotosInfoRequest\x1a\x32.Anki.Vector.external_interface.PhotosInfoResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\"\x0f/v1/photos_info:\x01*\x12z\n\x05Photo\x12,.Anki.Vector.external_interface.PhotoRequest\x1a-.Anki.Vector.external_interface.PhotoResponse\"\x14\x82\xd3\xe4\x93\x02\x0e\"\t/v1/photo:\x01*\x12\x8a\x01\n\tThumbnail\x12\x30.Anki.Vector.external_interface.ThumbnailRequest\x1a\x31.Anki.Vector.external_interface.ThumbnailResponse\"\x18\x82\xd3\xe4\x93\x02\x12\"\r/v1/thumbnail:\x01*\x12\x93\x01\n\x0b\x44\x65letePhoto\x12\x32.Anki.Vector.external_interface.DeletePhotoRequest\x1a\x33.Anki.Vector.external_interface.DeletePhotoResponse\"\x1b\x82\xd3\xe4\x93\x02\x15\"\x10/v1/delete_photo:\x01*\x12~\n\rDriveStraight\x12\x34.Anki.Vector.external_interface.DriveStraightRequest\x1a\x35.Anki.Vector.external_interface.DriveStraightResponse\"\x00\x12x\n\x0bTurnInPlace\x12\x32.Anki.Vector.external_interface.TurnInPlaceRequest\x1a\x33.Anki.Vector.external_interface.TurnInPlaceResponse\"\x00\x12{\n\x0cSetHeadAngle\x12\x33.Anki.Vector.external_interface.SetHeadAngleRequest\x1a\x34.Anki.Vector.external_interface.SetHeadAngleResponse\"\x00\x12~\n\rSetLiftHeight\x12\x34.Anki.Vector.external_interface.SetLiftHeightRequest\x1a\x35.Anki.Vector.external_interface.SetLiftHeightResponse\"\x00\x12\x84\x01\n\x0fTurnTowardsFace\x12\x36.Anki.Vector.external_interface.TurnTowardsFaceRequest\x1a\x37.Anki.Vector.external_interface.TurnTowardsFaceResponse\"\x00\x12u\n\nGoToObject\x12\x31.Anki.Vector.external_interface.GoToObjectRequest\x1a\x32.Anki.Vector.external_interface.GoToObjectResponse\"\x00\x12u\n\nRollObject\x12\x31.Anki.Vector.external_interface.RollObjectRequest\x1a\x32.Anki.Vector.external_interface.RollObjectResponse\"\x00\x12x\n\x0bPopAWheelie\x12\x32.Anki.Vector.external_interface.PopAWheelieRequest\x1a\x33.Anki.Vector.external_interface.PopAWheelieResponse\"\x00\x12{\n\x0cPickupObject\x12\x33.Anki.Vector.external_interface.PickupObjectRequest\x1a\x34.Anki.Vector.external_interface.PickupObjectResponse\"\x00\x12\x9c\x01\n\x17PlaceObjectOnGroundHere\x12>.Anki.Vector.external_interface.PlaceObjectOnGroundHereRequest\x1a?.Anki.Vector.external_interface.PlaceObjectOnGroundHereResponse\"\x00\x12~\n\x0fSetMasterVolume\x12\x33.Anki.Vector.external_interface.MasterVolumeRequest\x1a\x34.Anki.Vector.external_interface.MasterVolumeResponse\"\x00\x12\xaf\x01\n\x12UserAuthentication\x12\x39.Anki.Vector.external_interface.UserAuthenticationRequest\x1a:.Anki.Vector.external_interface.UserAuthenticationResponse\"\"\x82\xd3\xe4\x93\x02\x1c\"\x17/v1/user_authentication:\x01*\x12\x97\x01\n\x0c\x42\x61tteryState\x12\x33.Anki.Vector.external_interface.BatteryStateRequest\x1a\x34.Anki.Vector.external_interface.BatteryStateResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/battery_state:\x01*\x12\x97\x01\n\x0cVersionState\x12\x33.Anki.Vector.external_interface.VersionStateRequest\x1a\x34.Anki.Vector.external_interface.VersionStateResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/version_state:\x01*\x12\x83\x01\n\x07SayText\x12..Anki.Vector.external_interface.SayTextRequest\x1a/.Anki.Vector.external_interface.SayTextResponse\"\x17\x82\xd3\xe4\x93\x02\x11\"\x0c/v1/say_text:\x01*\x12\x93\x01\n\x0b\x43onnectCube\x12\x32.Anki.Vector.external_interface.ConnectCubeRequest\x1a\x33.Anki.Vector.external_interface.ConnectCubeResponse\"\x1b\x82\xd3\xe4\x93\x02\x15\"\x10/v1/connect_cube:\x01*\x12\x9f\x01\n\x0e\x44isconnectCube\x12\x35.Anki.Vector.external_interface.DisconnectCubeRequest\x1a\x36.Anki.Vector.external_interface.DisconnectCubeResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/disconnect_cube:\x01*\x12\x9f\x01\n\x0e\x43ubesAvailable\x12\x35.Anki.Vector.external_interface.CubesAvailableRequest\x1a\x36.Anki.Vector.external_interface.CubesAvailableResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/cubes_available:\x01*\x12\xa4\x01\n\x0f\x46lashCubeLights\x12\x36.Anki.Vector.external_interface.FlashCubeLightsRequest\x1a\x37.Anki.Vector.external_interface.FlashCubeLightsResponse\" \x82\xd3\xe4\x93\x02\x1a\"\x15/v1/flash_cube_lights:\x01*\x12\xb4\x01\n\x13\x46orgetPreferredCube\x12:.Anki.Vector.external_interface.ForgetPreferredCubeRequest\x1a;.Anki.Vector.external_interface.ForgetPreferredCubeResponse\"$\x82\xd3\xe4\x93\x02\x1e\"\x19/v1/forget_preferred_cube:\x01*\x12\xa8\x01\n\x10SetPreferredCube\x12\x37.Anki.Vector.external_interface.SetPreferredCubeRequest\x1a\x38.Anki.Vector.external_interface.SetPreferredCubeResponse\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/set_preferred_cube:\x01*\x12\xb4\x01\n\x13\x44\x65leteCustomObjects\x12:.Anki.Vector.external_interface.DeleteCustomObjectsRequest\x1a;.Anki.Vector.external_interface.DeleteCustomObjectsResponse\"$\x82\xd3\xe4\x93\x02\x1e\"\x19/v1/delete_custom_objects:\x01*\x12\xc5\x01\n\x17\x43reateFixedCustomObject\x12>.Anki.Vector.external_interface.CreateFixedCustomObjectRequest\x1a?.Anki.Vector.external_interface.CreateFixedCustomObjectResponse\")\x82\xd3\xe4\x93\x02#\"\x1e/v1/create_fixed_custom_object:\x01*\x12\xb0\x01\n\x12\x44\x65\x66ineCustomObject\x12\x39.Anki.Vector.external_interface.DefineCustomObjectRequest\x1a:.Anki.Vector.external_interface.DefineCustomObjectResponse\"#\x82\xd3\xe4\x93\x02\x1d\"\x18/v1/define_custom_object:\x01*\x12~\n\rSetCubeLights\x12\x34.Anki.Vector.external_interface.SetCubeLightsRequest\x1a\x35.Anki.Vector.external_interface.SetCubeLightsResponse\"\x00\x12\x8d\x01\n\tAudioFeed\x12\x30.Anki.Vector.external_interface.AudioFeedRequest\x1a\x31.Anki.Vector.external_interface.AudioFeedResponse\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/v1/audio_feed:\x01*0\x01\x12\x91\x01\n\nCameraFeed\x12\x31.Anki.Vector.external_interface.CameraFeedRequest\x1a\x32.Anki.Vector.external_interface.CameraFeedResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\"\x0f/v1/camera_feed:\x01*0\x01\x12\xb0\x01\n\x12\x43\x61ptureSingleImage\x12\x39.Anki.Vector.external_interface.CaptureSingleImageRequest\x1a:.Anki.Vector.external_interface.CaptureSingleImageResponse\"#\x82\xd3\xe4\x93\x02\x1d\"\x18/v1/capture_single_image:\x01*\x12\x94\x01\n\x0bSetEyeColor\x12\x32.Anki.Vector.external_interface.SetEyeColorRequest\x1a\x33.Anki.Vector.external_interface.SetEyeColorResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/set_eye_color:\x01*\x12\x92\x01\n\nNavMapFeed\x12\x31.Anki.Vector.external_interface.NavMapFeedRequest\x1a\x32.Anki.Vector.external_interface.NavMapFeedResponse\"\x1b\x82\xd3\xe4\x93\x02\x15\"\x10/v1/nav_map_feed:\x01*0\x01\x62\x06proto3') + serialized_pb=_b('\n.anki_vector/messaging/external_interface.proto\x12\x1e\x41nki.Vector.external_interface\x1a\x1cgoogle/api/annotations.proto\x1a$anki_vector/messaging/behavior.proto\x1a anki_vector/messaging/cube.proto\x1a$anki_vector/messaging/messages.proto\x1a#anki_vector/messaging/nav_map.proto\x1a\"anki_vector/messaging/shared.proto*o\n\x0fProtocolVersion\x12\x1c\n\x18PROTOCOL_VERSION_UNKNOWN\x10\x00\x12\x1c\n\x18PROTOCOL_VERSION_MINIMUM\x10\x00\x12\x1c\n\x18PROTOCOL_VERSION_CURRENT\x10\x05\x1a\x02\x10\x01\x32\xacX\n\x11\x45xternalInterface\x12\xa3\x01\n\x0fProtocolVersion\x12\x36.Anki.Vector.external_interface.ProtocolVersionRequest\x1a\x37.Anki.Vector.external_interface.ProtocolVersionResponse\"\x1f\x82\xd3\xe4\x93\x02\x19\"\x14/v1/protocol_version:\x01*\x12\xab\x01\n\x11SDKInitialization\x12\x38.Anki.Vector.external_interface.SDKInitializationRequest\x1a\x39.Anki.Vector.external_interface.SDKInitializationResponse\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/sdk_initialization:\x01*\x12x\n\x0b\x44riveWheels\x12\x32.Anki.Vector.external_interface.DriveWheelsRequest\x1a\x33.Anki.Vector.external_interface.DriveWheelsResponse\"\x00\x12\x8c\x01\n\x14PlayAnimationTrigger\x12;.Anki.Vector.external_interface.PlayAnimationTriggerRequest\x1a\x35.Anki.Vector.external_interface.PlayAnimationResponse\"\x00\x12~\n\rPlayAnimation\x12\x34.Anki.Vector.external_interface.PlayAnimationRequest\x1a\x35.Anki.Vector.external_interface.PlayAnimationResponse\"\x00\x12\x9f\x01\n\x0eListAnimations\x12\x35.Anki.Vector.external_interface.ListAnimationsRequest\x1a\x36.Anki.Vector.external_interface.ListAnimationsResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/list_animations:\x01*\x12\xbc\x01\n\x15ListAnimationTriggers\x12<.Anki.Vector.external_interface.ListAnimationTriggersRequest\x1a=.Anki.Vector.external_interface.ListAnimationTriggersResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/list_animation_triggers:\x01*\x12o\n\x08MoveHead\x12/.Anki.Vector.external_interface.MoveHeadRequest\x1a\x30.Anki.Vector.external_interface.MoveHeadResponse\"\x00\x12o\n\x08MoveLift\x12/.Anki.Vector.external_interface.MoveLiftRequest\x1a\x30.Anki.Vector.external_interface.MoveLiftResponse\"\x00\x12~\n\rStopAllMotors\x12\x34.Anki.Vector.external_interface.StopAllMotorsRequest\x1a\x35.Anki.Vector.external_interface.StopAllMotorsResponse\"\x00\x12\xb5\x01\n\x13\x44isplayFaceImageRGB\x12:.Anki.Vector.external_interface.DisplayFaceImageRGBRequest\x1a;.Anki.Vector.external_interface.DisplayFaceImageRGBResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/v1/display_face_image_rgb:\x01*\x12\x9d\x01\n\x0b\x45ventStream\x12,.Anki.Vector.external_interface.EventRequest\x1a-.Anki.Vector.external_interface.EventResponse\"/\x82\xd3\xe4\x93\x02)\"\x10/v1/event_stream:\x01*Z\x12\x12\x10/v1/event_stream0\x01\x12\x9c\x01\n\x1b\x45xternalAudioStreamPlayback\x12:.Anki.Vector.external_interface.ExternalAudioStreamRequest\x1a;.Anki.Vector.external_interface.ExternalAudioStreamResponse\"\x00(\x01\x30\x01\x12\x88\x01\n\x0f\x42\x65haviorControl\x12\x36.Anki.Vector.external_interface.BehaviorControlRequest\x1a\x37.Anki.Vector.external_interface.BehaviorControlResponse\"\x00(\x01\x30\x01\x12\xb8\x01\n\x14\x43\x61ncelFaceEnrollment\x12;.Anki.Vector.external_interface.CancelFaceEnrollmentRequest\x1a<.Anki.Vector.external_interface.CancelFaceEnrollmentResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/v1/cancel_face_enrollment:\x01*\x12\xb8\x01\n\x14RequestEnrolledNames\x12;.Anki.Vector.external_interface.RequestEnrolledNamesRequest\x1a<.Anki.Vector.external_interface.RequestEnrolledNamesResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/v1/request_enrolled_names:\x01*\x12\xc2\x01\n\x16UpdateEnrolledFaceByID\x12=.Anki.Vector.external_interface.UpdateEnrolledFaceByIDRequest\x1a>.Anki.Vector.external_interface.UpdateEnrolledFaceByIDResponse\")\x82\xd3\xe4\x93\x02#\"\x1e/v1/update_enrolled_face_by_id:\x01*\x12\xbe\x01\n\x15\x45raseEnrolledFaceByID\x12<.Anki.Vector.external_interface.EraseEnrolledFaceByIDRequest\x1a=.Anki.Vector.external_interface.EraseEnrolledFaceByIDResponse\"(\x82\xd3\xe4\x93\x02\"\"\x1d/v1/erase_enrolled_face_by_id:\x01*\x12\xbd\x01\n\x15\x45raseAllEnrolledFaces\x12<.Anki.Vector.external_interface.EraseAllEnrolledFacesRequest\x1a=.Anki.Vector.external_interface.EraseAllEnrolledFacesResponse\"\'\x82\xd3\xe4\x93\x02!\"\x1c/v1/erase_all_enrolled_faces:\x01*\x12\xa5\x01\n\x0fSetFaceToEnroll\x12\x36.Anki.Vector.external_interface.SetFaceToEnrollRequest\x1a\x37.Anki.Vector.external_interface.SetFaceToEnrollResponse\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/set_face_to_enroll:\x01*\x12\x8f\x01\n\nEnrollFace\x12\x31.Anki.Vector.external_interface.EnrollFaceRequest\x1a\x32.Anki.Vector.external_interface.EnrollFaceResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\"\x0f/v1/enroll_face:\x01*\x12\xbc\x01\n\x15\x45nableMarkerDetection\x12<.Anki.Vector.external_interface.EnableMarkerDetectionRequest\x1a=.Anki.Vector.external_interface.EnableMarkerDetectionResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/enable_marker_detection:\x01*\x12\xb4\x01\n\x13\x45nableFaceDetection\x12:.Anki.Vector.external_interface.EnableFaceDetectionRequest\x1a;.Anki.Vector.external_interface.EnableFaceDetectionResponse\"$\x82\xd3\xe4\x93\x02\x1e\"\x19/v1/enable_face_detection:\x01*\x12\xbc\x01\n\x15\x45nableMotionDetection\x12<.Anki.Vector.external_interface.EnableMotionDetectionRequest\x1a=.Anki.Vector.external_interface.EnableMotionDetectionResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/enable_motion_detection:\x01*\x12\xa8\x01\n\x10\x45nableMirrorMode\x12\x37.Anki.Vector.external_interface.EnableMirrorModeRequest\x1a\x38.Anki.Vector.external_interface.EnableMirrorModeResponse\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/enable_mirror_mode:\x01*\x12\xb8\x01\n\x14\x45nableImageStreaming\x12;.Anki.Vector.external_interface.EnableImageStreamingRequest\x1a<.Anki.Vector.external_interface.EnableImageStreamingResponse\"%\x82\xd3\xe4\x93\x02\x1f\"\x1a/v1/enable_image_streaming:\x01*\x12\xc5\x01\n\x17IsImageStreamingEnabled\x12>.Anki.Vector.external_interface.IsImageStreamingEnabledRequest\x1a?.Anki.Vector.external_interface.IsImageStreamingEnabledResponse\")\x82\xd3\xe4\x93\x02#\"\x1e/v1/is_image_streaming_enabled:\x01*\x12\xb6\x01\n\x13\x43\x61ncelActionByIdTag\x12:.Anki.Vector.external_interface.CancelActionByIdTagRequest\x1a;.Anki.Vector.external_interface.CancelActionByIdTagResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/cancel_action_by_id_tag:\x01*\x12\x9f\x01\n\x0e\x43\x61ncelBehavior\x12\x35.Anki.Vector.external_interface.CancelBehaviorRequest\x1a\x36.Anki.Vector.external_interface.CancelBehaviorResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/cancel_behavior:\x01*\x12\x88\x01\n\x08GoToPose\x12/.Anki.Vector.external_interface.GoToPoseRequest\x1a\x30.Anki.Vector.external_interface.GoToPoseResponse\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/v1/go_to_pose:\x01*\x12\x98\x01\n\x0c\x44ockWithCube\x12\x33.Anki.Vector.external_interface.DockWithCubeRequest\x1a\x34.Anki.Vector.external_interface.DockWithCubeResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\"\x12/v1/dock_with_cube:\x01*\x12\xa4\x01\n\x0f\x44riveOffCharger\x12\x36.Anki.Vector.external_interface.DriveOffChargerRequest\x1a\x37.Anki.Vector.external_interface.DriveOffChargerResponse\" \x82\xd3\xe4\x93\x02\x1a\"\x15/v1/drive_off_charger:\x01*\x12\xa0\x01\n\x0e\x44riveOnCharger\x12\x35.Anki.Vector.external_interface.DriveOnChargerRequest\x1a\x36.Anki.Vector.external_interface.DriveOnChargerResponse\"\x1f\x82\xd3\xe4\x93\x02\x19\"\x14/v1/drive_on_charger:\x01*\x12\x8b\x01\n\tFindFaces\x12\x30.Anki.Vector.external_interface.FindFacesRequest\x1a\x31.Anki.Vector.external_interface.FindFacesResponse\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/v1/find_faces:\x01*\x12\xad\x01\n\x11LookAroundInPlace\x12\x38.Anki.Vector.external_interface.LookAroundInPlaceRequest\x1a\x39.Anki.Vector.external_interface.LookAroundInPlaceResponse\"#\x82\xd3\xe4\x93\x02\x1d\"\x18/v1/look_around_in_place:\x01*\x12\x8b\x01\n\tRollBlock\x12\x30.Anki.Vector.external_interface.RollBlockRequest\x1a\x31.Anki.Vector.external_interface.RollBlockResponse\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/v1/roll_block:\x01*\x12\x8f\x01\n\nPhotosInfo\x12\x31.Anki.Vector.external_interface.PhotosInfoRequest\x1a\x32.Anki.Vector.external_interface.PhotosInfoResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\"\x0f/v1/photos_info:\x01*\x12z\n\x05Photo\x12,.Anki.Vector.external_interface.PhotoRequest\x1a-.Anki.Vector.external_interface.PhotoResponse\"\x14\x82\xd3\xe4\x93\x02\x0e\"\t/v1/photo:\x01*\x12\x8a\x01\n\tThumbnail\x12\x30.Anki.Vector.external_interface.ThumbnailRequest\x1a\x31.Anki.Vector.external_interface.ThumbnailResponse\"\x18\x82\xd3\xe4\x93\x02\x12\"\r/v1/thumbnail:\x01*\x12\x93\x01\n\x0b\x44\x65letePhoto\x12\x32.Anki.Vector.external_interface.DeletePhotoRequest\x1a\x33.Anki.Vector.external_interface.DeletePhotoResponse\"\x1b\x82\xd3\xe4\x93\x02\x15\"\x10/v1/delete_photo:\x01*\x12~\n\rDriveStraight\x12\x34.Anki.Vector.external_interface.DriveStraightRequest\x1a\x35.Anki.Vector.external_interface.DriveStraightResponse\"\x00\x12x\n\x0bTurnInPlace\x12\x32.Anki.Vector.external_interface.TurnInPlaceRequest\x1a\x33.Anki.Vector.external_interface.TurnInPlaceResponse\"\x00\x12{\n\x0cSetHeadAngle\x12\x33.Anki.Vector.external_interface.SetHeadAngleRequest\x1a\x34.Anki.Vector.external_interface.SetHeadAngleResponse\"\x00\x12~\n\rSetLiftHeight\x12\x34.Anki.Vector.external_interface.SetLiftHeightRequest\x1a\x35.Anki.Vector.external_interface.SetLiftHeightResponse\"\x00\x12\x84\x01\n\x0fTurnTowardsFace\x12\x36.Anki.Vector.external_interface.TurnTowardsFaceRequest\x1a\x37.Anki.Vector.external_interface.TurnTowardsFaceResponse\"\x00\x12u\n\nGoToObject\x12\x31.Anki.Vector.external_interface.GoToObjectRequest\x1a\x32.Anki.Vector.external_interface.GoToObjectResponse\"\x00\x12u\n\nRollObject\x12\x31.Anki.Vector.external_interface.RollObjectRequest\x1a\x32.Anki.Vector.external_interface.RollObjectResponse\"\x00\x12x\n\x0bPopAWheelie\x12\x32.Anki.Vector.external_interface.PopAWheelieRequest\x1a\x33.Anki.Vector.external_interface.PopAWheelieResponse\"\x00\x12{\n\x0cPickupObject\x12\x33.Anki.Vector.external_interface.PickupObjectRequest\x1a\x34.Anki.Vector.external_interface.PickupObjectResponse\"\x00\x12\x9c\x01\n\x17PlaceObjectOnGroundHere\x12>.Anki.Vector.external_interface.PlaceObjectOnGroundHereRequest\x1a?.Anki.Vector.external_interface.PlaceObjectOnGroundHereResponse\"\x00\x12~\n\x0fSetMasterVolume\x12\x33.Anki.Vector.external_interface.MasterVolumeRequest\x1a\x34.Anki.Vector.external_interface.MasterVolumeResponse\"\x00\x12\xaf\x01\n\x12UserAuthentication\x12\x39.Anki.Vector.external_interface.UserAuthenticationRequest\x1a:.Anki.Vector.external_interface.UserAuthenticationResponse\"\"\x82\xd3\xe4\x93\x02\x1c\"\x17/v1/user_authentication:\x01*\x12\x97\x01\n\x0c\x42\x61tteryState\x12\x33.Anki.Vector.external_interface.BatteryStateRequest\x1a\x34.Anki.Vector.external_interface.BatteryStateResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/battery_state:\x01*\x12\x97\x01\n\x0cVersionState\x12\x33.Anki.Vector.external_interface.VersionStateRequest\x1a\x34.Anki.Vector.external_interface.VersionStateResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/version_state:\x01*\x12\x83\x01\n\x07SayText\x12..Anki.Vector.external_interface.SayTextRequest\x1a/.Anki.Vector.external_interface.SayTextResponse\"\x17\x82\xd3\xe4\x93\x02\x11\"\x0c/v1/say_text:\x01*\x12\x93\x01\n\x0b\x43onnectCube\x12\x32.Anki.Vector.external_interface.ConnectCubeRequest\x1a\x33.Anki.Vector.external_interface.ConnectCubeResponse\"\x1b\x82\xd3\xe4\x93\x02\x15\"\x10/v1/connect_cube:\x01*\x12\x9f\x01\n\x0e\x44isconnectCube\x12\x35.Anki.Vector.external_interface.DisconnectCubeRequest\x1a\x36.Anki.Vector.external_interface.DisconnectCubeResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/disconnect_cube:\x01*\x12\x9f\x01\n\x0e\x43ubesAvailable\x12\x35.Anki.Vector.external_interface.CubesAvailableRequest\x1a\x36.Anki.Vector.external_interface.CubesAvailableResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/cubes_available:\x01*\x12\xa4\x01\n\x0f\x46lashCubeLights\x12\x36.Anki.Vector.external_interface.FlashCubeLightsRequest\x1a\x37.Anki.Vector.external_interface.FlashCubeLightsResponse\" \x82\xd3\xe4\x93\x02\x1a\"\x15/v1/flash_cube_lights:\x01*\x12\xb4\x01\n\x13\x46orgetPreferredCube\x12:.Anki.Vector.external_interface.ForgetPreferredCubeRequest\x1a;.Anki.Vector.external_interface.ForgetPreferredCubeResponse\"$\x82\xd3\xe4\x93\x02\x1e\"\x19/v1/forget_preferred_cube:\x01*\x12\xa8\x01\n\x10SetPreferredCube\x12\x37.Anki.Vector.external_interface.SetPreferredCubeRequest\x1a\x38.Anki.Vector.external_interface.SetPreferredCubeResponse\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/set_preferred_cube:\x01*\x12\xb4\x01\n\x13\x44\x65leteCustomObjects\x12:.Anki.Vector.external_interface.DeleteCustomObjectsRequest\x1a;.Anki.Vector.external_interface.DeleteCustomObjectsResponse\"$\x82\xd3\xe4\x93\x02\x1e\"\x19/v1/delete_custom_objects:\x01*\x12\xc5\x01\n\x17\x43reateFixedCustomObject\x12>.Anki.Vector.external_interface.CreateFixedCustomObjectRequest\x1a?.Anki.Vector.external_interface.CreateFixedCustomObjectResponse\")\x82\xd3\xe4\x93\x02#\"\x1e/v1/create_fixed_custom_object:\x01*\x12\xb0\x01\n\x12\x44\x65\x66ineCustomObject\x12\x39.Anki.Vector.external_interface.DefineCustomObjectRequest\x1a:.Anki.Vector.external_interface.DefineCustomObjectResponse\"#\x82\xd3\xe4\x93\x02\x1d\"\x18/v1/define_custom_object:\x01*\x12~\n\rSetCubeLights\x12\x34.Anki.Vector.external_interface.SetCubeLightsRequest\x1a\x35.Anki.Vector.external_interface.SetCubeLightsResponse\"\x00\x12\x8d\x01\n\tAudioFeed\x12\x30.Anki.Vector.external_interface.AudioFeedRequest\x1a\x31.Anki.Vector.external_interface.AudioFeedResponse\"\x19\x82\xd3\xe4\x93\x02\x13\"\x0e/v1/audio_feed:\x01*0\x01\x12\x91\x01\n\nCameraFeed\x12\x31.Anki.Vector.external_interface.CameraFeedRequest\x1a\x32.Anki.Vector.external_interface.CameraFeedResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\"\x0f/v1/camera_feed:\x01*0\x01\x12\xb0\x01\n\x12\x43\x61ptureSingleImage\x12\x39.Anki.Vector.external_interface.CaptureSingleImageRequest\x1a:.Anki.Vector.external_interface.CaptureSingleImageResponse\"#\x82\xd3\xe4\x93\x02\x1d\"\x18/v1/capture_single_image:\x01*\x12\x9e\x01\n\x0fGetCameraConfig\x12\x33.Anki.Vector.external_interface.CameraConfigRequest\x1a\x34.Anki.Vector.external_interface.CameraConfigResponse\" \x82\xd3\xe4\x93\x02\x1a\"\x15/v1/get_camera_config:\x01*\x12\x94\x01\n\x0bSetEyeColor\x12\x32.Anki.Vector.external_interface.SetEyeColorRequest\x1a\x33.Anki.Vector.external_interface.SetEyeColorResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/set_eye_color:\x01*\x12\x92\x01\n\nNavMapFeed\x12\x31.Anki.Vector.external_interface.NavMapFeedRequest\x1a\x32.Anki.Vector.external_interface.NavMapFeedResponse\"\x1b\x82\xd3\xe4\x93\x02\x15\"\x10/v1/nav_map_feed:\x01*0\x01\x12\xac\x01\n\x11SetCameraSettings\x12\x38.Anki.Vector.external_interface.SetCameraSettingsRequest\x1a\x39.Anki.Vector.external_interface.SetCameraSettingsResponse\"\"\x82\xd3\xe4\x93\x02\x1c\"\x17/v1/set_camera_settings:\x01*b\x06proto3') , dependencies=[google_dot_api_dot_annotations__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_behavior__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_cube__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_messages__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_nav__map__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_shared__pb2.DESCRIPTOR,]) @@ -76,7 +76,7 @@ index=0, options=None, serialized_start=409, - serialized_end=11254, + serialized_end=11717, methods=[ _descriptor.MethodDescriptor( name='ProtocolVersion', @@ -204,19 +204,10 @@ output_type=anki__vector_dot_messaging_dot_behavior__pb2._BEHAVIORCONTROLRESPONSE, options=None, ), - _descriptor.MethodDescriptor( - name='AssumeBehaviorControl', - full_name='Anki.Vector.external_interface.ExternalInterface.AssumeBehaviorControl', - index=14, - containing_service=None, - input_type=anki__vector_dot_messaging_dot_behavior__pb2._BEHAVIORCONTROLREQUEST, - output_type=anki__vector_dot_messaging_dot_behavior__pb2._BEHAVIORCONTROLRESPONSE, - options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002 \"\033/v1/assume_behavior_control:\001*')), - ), _descriptor.MethodDescriptor( name='CancelFaceEnrollment', full_name='Anki.Vector.external_interface.ExternalInterface.CancelFaceEnrollment', - index=15, + index=14, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._CANCELFACEENROLLMENTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._CANCELFACEENROLLMENTRESPONSE, @@ -225,7 +216,7 @@ _descriptor.MethodDescriptor( name='RequestEnrolledNames', full_name='Anki.Vector.external_interface.ExternalInterface.RequestEnrolledNames', - index=16, + index=15, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._REQUESTENROLLEDNAMESREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._REQUESTENROLLEDNAMESRESPONSE, @@ -234,7 +225,7 @@ _descriptor.MethodDescriptor( name='UpdateEnrolledFaceByID', full_name='Anki.Vector.external_interface.ExternalInterface.UpdateEnrolledFaceByID', - index=17, + index=16, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._UPDATEENROLLEDFACEBYIDREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._UPDATEENROLLEDFACEBYIDRESPONSE, @@ -243,7 +234,7 @@ _descriptor.MethodDescriptor( name='EraseEnrolledFaceByID', full_name='Anki.Vector.external_interface.ExternalInterface.EraseEnrolledFaceByID', - index=18, + index=17, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ERASEENROLLEDFACEBYIDREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ERASEENROLLEDFACEBYIDRESPONSE, @@ -252,7 +243,7 @@ _descriptor.MethodDescriptor( name='EraseAllEnrolledFaces', full_name='Anki.Vector.external_interface.ExternalInterface.EraseAllEnrolledFaces', - index=19, + index=18, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ERASEALLENROLLEDFACESREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ERASEALLENROLLEDFACESRESPONSE, @@ -261,12 +252,21 @@ _descriptor.MethodDescriptor( name='SetFaceToEnroll', full_name='Anki.Vector.external_interface.ExternalInterface.SetFaceToEnroll', - index=20, + index=19, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SETFACETOENROLLREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SETFACETOENROLLRESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\033\"\026/v1/set_face_to_enroll:\001*')), ), + _descriptor.MethodDescriptor( + name='EnrollFace', + full_name='Anki.Vector.external_interface.ExternalInterface.EnrollFace', + index=20, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._ENROLLFACEREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._ENROLLFACERESPONSE, + options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\024\"\017/v1/enroll_face:\001*')), + ), _descriptor.MethodDescriptor( name='EnableMarkerDetection', full_name='Anki.Vector.external_interface.ExternalInterface.EnableMarkerDetection', @@ -330,10 +330,19 @@ output_type=anki__vector_dot_messaging_dot_messages__pb2._CANCELACTIONBYIDTAGRESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002 \"\033/v1/cancel_action_by_id_tag:\001*')), ), + _descriptor.MethodDescriptor( + name='CancelBehavior', + full_name='Anki.Vector.external_interface.ExternalInterface.CancelBehavior', + index=28, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._CANCELBEHAVIORREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._CANCELBEHAVIORRESPONSE, + options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\030\"\023/v1/cancel_behavior:\001*')), + ), _descriptor.MethodDescriptor( name='GoToPose', full_name='Anki.Vector.external_interface.ExternalInterface.GoToPose', - index=28, + index=29, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._GOTOPOSEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._GOTOPOSERESPONSE, @@ -342,7 +351,7 @@ _descriptor.MethodDescriptor( name='DockWithCube', full_name='Anki.Vector.external_interface.ExternalInterface.DockWithCube', - index=29, + index=30, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DOCKWITHCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DOCKWITHCUBERESPONSE, @@ -351,7 +360,7 @@ _descriptor.MethodDescriptor( name='DriveOffCharger', full_name='Anki.Vector.external_interface.ExternalInterface.DriveOffCharger', - index=30, + index=31, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVEOFFCHARGERREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVEOFFCHARGERRESPONSE, @@ -360,7 +369,7 @@ _descriptor.MethodDescriptor( name='DriveOnCharger', full_name='Anki.Vector.external_interface.ExternalInterface.DriveOnCharger', - index=31, + index=32, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVEONCHARGERREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVEONCHARGERRESPONSE, @@ -369,7 +378,7 @@ _descriptor.MethodDescriptor( name='FindFaces', full_name='Anki.Vector.external_interface.ExternalInterface.FindFaces', - index=32, + index=33, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._FINDFACESREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._FINDFACESRESPONSE, @@ -378,7 +387,7 @@ _descriptor.MethodDescriptor( name='LookAroundInPlace', full_name='Anki.Vector.external_interface.ExternalInterface.LookAroundInPlace', - index=33, + index=34, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._LOOKAROUNDINPLACEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._LOOKAROUNDINPLACERESPONSE, @@ -387,7 +396,7 @@ _descriptor.MethodDescriptor( name='RollBlock', full_name='Anki.Vector.external_interface.ExternalInterface.RollBlock', - index=34, + index=35, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ROLLBLOCKREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ROLLBLOCKRESPONSE, @@ -396,7 +405,7 @@ _descriptor.MethodDescriptor( name='PhotosInfo', full_name='Anki.Vector.external_interface.ExternalInterface.PhotosInfo', - index=35, + index=36, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._PHOTOSINFOREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._PHOTOSINFORESPONSE, @@ -405,7 +414,7 @@ _descriptor.MethodDescriptor( name='Photo', full_name='Anki.Vector.external_interface.ExternalInterface.Photo', - index=36, + index=37, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._PHOTOREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._PHOTORESPONSE, @@ -414,7 +423,7 @@ _descriptor.MethodDescriptor( name='Thumbnail', full_name='Anki.Vector.external_interface.ExternalInterface.Thumbnail', - index=37, + index=38, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._THUMBNAILREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._THUMBNAILRESPONSE, @@ -423,7 +432,7 @@ _descriptor.MethodDescriptor( name='DeletePhoto', full_name='Anki.Vector.external_interface.ExternalInterface.DeletePhoto', - index=38, + index=39, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DELETEPHOTOREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DELETEPHOTORESPONSE, @@ -432,7 +441,7 @@ _descriptor.MethodDescriptor( name='DriveStraight', full_name='Anki.Vector.external_interface.ExternalInterface.DriveStraight', - index=39, + index=40, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVESTRAIGHTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVESTRAIGHTRESPONSE, @@ -441,7 +450,7 @@ _descriptor.MethodDescriptor( name='TurnInPlace', full_name='Anki.Vector.external_interface.ExternalInterface.TurnInPlace', - index=40, + index=41, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._TURNINPLACEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._TURNINPLACERESPONSE, @@ -450,7 +459,7 @@ _descriptor.MethodDescriptor( name='SetHeadAngle', full_name='Anki.Vector.external_interface.ExternalInterface.SetHeadAngle', - index=41, + index=42, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SETHEADANGLEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SETHEADANGLERESPONSE, @@ -459,7 +468,7 @@ _descriptor.MethodDescriptor( name='SetLiftHeight', full_name='Anki.Vector.external_interface.ExternalInterface.SetLiftHeight', - index=42, + index=43, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SETLIFTHEIGHTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SETLIFTHEIGHTRESPONSE, @@ -468,7 +477,7 @@ _descriptor.MethodDescriptor( name='TurnTowardsFace', full_name='Anki.Vector.external_interface.ExternalInterface.TurnTowardsFace', - index=43, + index=44, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._TURNTOWARDSFACEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._TURNTOWARDSFACERESPONSE, @@ -477,7 +486,7 @@ _descriptor.MethodDescriptor( name='GoToObject', full_name='Anki.Vector.external_interface.ExternalInterface.GoToObject', - index=44, + index=45, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._GOTOOBJECTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._GOTOOBJECTRESPONSE, @@ -486,7 +495,7 @@ _descriptor.MethodDescriptor( name='RollObject', full_name='Anki.Vector.external_interface.ExternalInterface.RollObject', - index=45, + index=46, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ROLLOBJECTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ROLLOBJECTRESPONSE, @@ -495,7 +504,7 @@ _descriptor.MethodDescriptor( name='PopAWheelie', full_name='Anki.Vector.external_interface.ExternalInterface.PopAWheelie', - index=46, + index=47, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._POPAWHEELIEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._POPAWHEELIERESPONSE, @@ -504,7 +513,7 @@ _descriptor.MethodDescriptor( name='PickupObject', full_name='Anki.Vector.external_interface.ExternalInterface.PickupObject', - index=47, + index=48, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._PICKUPOBJECTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._PICKUPOBJECTRESPONSE, @@ -513,7 +522,7 @@ _descriptor.MethodDescriptor( name='PlaceObjectOnGroundHere', full_name='Anki.Vector.external_interface.ExternalInterface.PlaceObjectOnGroundHere', - index=48, + index=49, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._PLACEOBJECTONGROUNDHEREREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._PLACEOBJECTONGROUNDHERERESPONSE, @@ -522,7 +531,7 @@ _descriptor.MethodDescriptor( name='SetMasterVolume', full_name='Anki.Vector.external_interface.ExternalInterface.SetMasterVolume', - index=49, + index=50, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._MASTERVOLUMEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._MASTERVOLUMERESPONSE, @@ -531,7 +540,7 @@ _descriptor.MethodDescriptor( name='UserAuthentication', full_name='Anki.Vector.external_interface.ExternalInterface.UserAuthentication', - index=50, + index=51, containing_service=None, input_type=anki__vector_dot_messaging_dot_shared__pb2._USERAUTHENTICATIONREQUEST, output_type=anki__vector_dot_messaging_dot_shared__pb2._USERAUTHENTICATIONRESPONSE, @@ -540,7 +549,7 @@ _descriptor.MethodDescriptor( name='BatteryState', full_name='Anki.Vector.external_interface.ExternalInterface.BatteryState', - index=51, + index=52, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._BATTERYSTATEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._BATTERYSTATERESPONSE, @@ -549,7 +558,7 @@ _descriptor.MethodDescriptor( name='VersionState', full_name='Anki.Vector.external_interface.ExternalInterface.VersionState', - index=52, + index=53, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._VERSIONSTATEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._VERSIONSTATERESPONSE, @@ -558,7 +567,7 @@ _descriptor.MethodDescriptor( name='SayText', full_name='Anki.Vector.external_interface.ExternalInterface.SayText', - index=53, + index=54, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SAYTEXTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SAYTEXTRESPONSE, @@ -567,7 +576,7 @@ _descriptor.MethodDescriptor( name='ConnectCube', full_name='Anki.Vector.external_interface.ExternalInterface.ConnectCube', - index=54, + index=55, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._CONNECTCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._CONNECTCUBERESPONSE, @@ -576,7 +585,7 @@ _descriptor.MethodDescriptor( name='DisconnectCube', full_name='Anki.Vector.external_interface.ExternalInterface.DisconnectCube', - index=55, + index=56, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._DISCONNECTCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._DISCONNECTCUBERESPONSE, @@ -585,7 +594,7 @@ _descriptor.MethodDescriptor( name='CubesAvailable', full_name='Anki.Vector.external_interface.ExternalInterface.CubesAvailable', - index=56, + index=57, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._CUBESAVAILABLEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._CUBESAVAILABLERESPONSE, @@ -594,7 +603,7 @@ _descriptor.MethodDescriptor( name='FlashCubeLights', full_name='Anki.Vector.external_interface.ExternalInterface.FlashCubeLights', - index=57, + index=58, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._FLASHCUBELIGHTSREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._FLASHCUBELIGHTSRESPONSE, @@ -603,7 +612,7 @@ _descriptor.MethodDescriptor( name='ForgetPreferredCube', full_name='Anki.Vector.external_interface.ExternalInterface.ForgetPreferredCube', - index=58, + index=59, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._FORGETPREFERREDCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._FORGETPREFERREDCUBERESPONSE, @@ -612,7 +621,7 @@ _descriptor.MethodDescriptor( name='SetPreferredCube', full_name='Anki.Vector.external_interface.ExternalInterface.SetPreferredCube', - index=59, + index=60, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._SETPREFERREDCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._SETPREFERREDCUBERESPONSE, @@ -621,7 +630,7 @@ _descriptor.MethodDescriptor( name='DeleteCustomObjects', full_name='Anki.Vector.external_interface.ExternalInterface.DeleteCustomObjects', - index=60, + index=61, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._DELETECUSTOMOBJECTSREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._DELETECUSTOMOBJECTSRESPONSE, @@ -630,7 +639,7 @@ _descriptor.MethodDescriptor( name='CreateFixedCustomObject', full_name='Anki.Vector.external_interface.ExternalInterface.CreateFixedCustomObject', - index=61, + index=62, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._CREATEFIXEDCUSTOMOBJECTREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._CREATEFIXEDCUSTOMOBJECTRESPONSE, @@ -639,7 +648,7 @@ _descriptor.MethodDescriptor( name='DefineCustomObject', full_name='Anki.Vector.external_interface.ExternalInterface.DefineCustomObject', - index=62, + index=63, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._DEFINECUSTOMOBJECTREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._DEFINECUSTOMOBJECTRESPONSE, @@ -648,7 +657,7 @@ _descriptor.MethodDescriptor( name='SetCubeLights', full_name='Anki.Vector.external_interface.ExternalInterface.SetCubeLights', - index=63, + index=64, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._SETCUBELIGHTSREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._SETCUBELIGHTSRESPONSE, @@ -657,7 +666,7 @@ _descriptor.MethodDescriptor( name='AudioFeed', full_name='Anki.Vector.external_interface.ExternalInterface.AudioFeed', - index=64, + index=65, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._AUDIOFEEDREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._AUDIOFEEDRESPONSE, @@ -666,7 +675,7 @@ _descriptor.MethodDescriptor( name='CameraFeed', full_name='Anki.Vector.external_interface.ExternalInterface.CameraFeed', - index=65, + index=66, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._CAMERAFEEDREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._CAMERAFEEDRESPONSE, @@ -675,16 +684,25 @@ _descriptor.MethodDescriptor( name='CaptureSingleImage', full_name='Anki.Vector.external_interface.ExternalInterface.CaptureSingleImage', - index=66, + index=67, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._CAPTURESINGLEIMAGEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._CAPTURESINGLEIMAGERESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\035\"\030/v1/capture_single_image:\001*')), ), + _descriptor.MethodDescriptor( + name='GetCameraConfig', + full_name='Anki.Vector.external_interface.ExternalInterface.GetCameraConfig', + index=68, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._CAMERACONFIGREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._CAMERACONFIGRESPONSE, + options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\032\"\025/v1/get_camera_config:\001*')), + ), _descriptor.MethodDescriptor( name='SetEyeColor', full_name='Anki.Vector.external_interface.ExternalInterface.SetEyeColor', - index=67, + index=69, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SETEYECOLORREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SETEYECOLORRESPONSE, @@ -693,12 +711,21 @@ _descriptor.MethodDescriptor( name='NavMapFeed', full_name='Anki.Vector.external_interface.ExternalInterface.NavMapFeed', - index=68, + index=70, containing_service=None, input_type=anki__vector_dot_messaging_dot_nav__map__pb2._NAVMAPFEEDREQUEST, output_type=anki__vector_dot_messaging_dot_nav__map__pb2._NAVMAPFEEDRESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\025\"\020/v1/nav_map_feed:\001*')), ), + _descriptor.MethodDescriptor( + name='SetCameraSettings', + full_name='Anki.Vector.external_interface.ExternalInterface.SetCameraSettings', + index=71, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._SETCAMERASETTINGSREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._SETCAMERASETTINGSRESPONSE, + options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\034\"\027/v1/set_camera_settings:\001*')), + ), ]) _sym_db.RegisterServiceDescriptor(_EXTERNALINTERFACE) diff --git a/anki_vector/messaging/external_interface_pb2_grpc.py b/anki_vector/messaging/external_interface_pb2_grpc.py index a41ae3e..ee065da 100644 --- a/anki_vector/messaging/external_interface_pb2_grpc.py +++ b/anki_vector/messaging/external_interface_pb2_grpc.py @@ -88,11 +88,6 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlResponse.FromString, ) - self.AssumeBehaviorControl = channel.unary_stream( - '/Anki.Vector.external_interface.ExternalInterface/AssumeBehaviorControl', - request_serializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlRequest.SerializeToString, - response_deserializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlResponse.FromString, - ) self.CancelFaceEnrollment = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/CancelFaceEnrollment', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.CancelFaceEnrollmentRequest.SerializeToString, @@ -123,6 +118,11 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.SetFaceToEnrollRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.SetFaceToEnrollResponse.FromString, ) + self.EnrollFace = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/EnrollFace', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.EnrollFaceRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.EnrollFaceResponse.FromString, + ) self.EnableMarkerDetection = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/EnableMarkerDetection', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.EnableMarkerDetectionRequest.SerializeToString, @@ -158,6 +158,11 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.CancelActionByIdTagRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CancelActionByIdTagResponse.FromString, ) + self.CancelBehavior = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/CancelBehavior', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.CancelBehaviorRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CancelBehaviorResponse.FromString, + ) self.GoToPose = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/GoToPose', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.GoToPoseRequest.SerializeToString, @@ -353,6 +358,11 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.CaptureSingleImageRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CaptureSingleImageResponse.FromString, ) + self.GetCameraConfig = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/GetCameraConfig', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.CameraConfigRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CameraConfigResponse.FromString, + ) self.SetEyeColor = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/SetEyeColor', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.SetEyeColorRequest.SerializeToString, @@ -363,6 +373,11 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_nav__map__pb2.NavMapFeedRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_nav__map__pb2.NavMapFeedResponse.FromString, ) + self.SetCameraSettings = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/SetCameraSettings', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.SetCameraSettingsRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.SetCameraSettingsResponse.FromString, + ) class ExternalInterfaceServicer(object): @@ -468,13 +483,6 @@ def BehaviorControl(self, request_iterator, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def AssumeBehaviorControl(self, request, context): - """Acquire control of Vector's AI system. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - def CancelFaceEnrollment(self, request, context): # missing associated documentation comment in .proto file pass @@ -517,6 +525,13 @@ def SetFaceToEnroll(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def EnrollFace(self, request, context): + """Enroll a face. Must be used with SetFaceToEnroll + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def EnableMarkerDetection(self, request, context): # missing associated documentation comment in .proto file pass @@ -566,6 +581,13 @@ def CancelActionByIdTag(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def CancelBehavior(self, request, context): + """Cancel running SDK Behavior + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def GoToPose(self, request, context): """Tells Vector to drive to the specified pose and orientation. """ @@ -848,6 +870,13 @@ def CaptureSingleImage(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def GetCameraConfig(self, request, context): + """Get Vector's camera configuration. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def SetEyeColor(self, request, context): """Set Vector's eye color. """ @@ -862,6 +891,13 @@ def NavMapFeed(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def SetCameraSettings(self, request, context): + """Set Vector's camera settings + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_ExternalInterfaceServicer_to_server(servicer, server): rpc_method_handlers = { @@ -935,11 +971,6 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlResponse.SerializeToString, ), - 'AssumeBehaviorControl': grpc.unary_stream_rpc_method_handler( - servicer.AssumeBehaviorControl, - request_deserializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlRequest.FromString, - response_serializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlResponse.SerializeToString, - ), 'CancelFaceEnrollment': grpc.unary_unary_rpc_method_handler( servicer.CancelFaceEnrollment, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CancelFaceEnrollmentRequest.FromString, @@ -970,6 +1001,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.SetFaceToEnrollRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.SetFaceToEnrollResponse.SerializeToString, ), + 'EnrollFace': grpc.unary_unary_rpc_method_handler( + servicer.EnrollFace, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.EnrollFaceRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.EnrollFaceResponse.SerializeToString, + ), 'EnableMarkerDetection': grpc.unary_unary_rpc_method_handler( servicer.EnableMarkerDetection, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.EnableMarkerDetectionRequest.FromString, @@ -1005,6 +1041,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CancelActionByIdTagRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.CancelActionByIdTagResponse.SerializeToString, ), + 'CancelBehavior': grpc.unary_unary_rpc_method_handler( + servicer.CancelBehavior, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CancelBehaviorRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.CancelBehaviorResponse.SerializeToString, + ), 'GoToPose': grpc.unary_unary_rpc_method_handler( servicer.GoToPose, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.GoToPoseRequest.FromString, @@ -1200,6 +1241,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CaptureSingleImageRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.CaptureSingleImageResponse.SerializeToString, ), + 'GetCameraConfig': grpc.unary_unary_rpc_method_handler( + servicer.GetCameraConfig, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CameraConfigRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.CameraConfigResponse.SerializeToString, + ), 'SetEyeColor': grpc.unary_unary_rpc_method_handler( servicer.SetEyeColor, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.SetEyeColorRequest.FromString, @@ -1210,6 +1256,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_nav__map__pb2.NavMapFeedRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_nav__map__pb2.NavMapFeedResponse.SerializeToString, ), + 'SetCameraSettings': grpc.unary_unary_rpc_method_handler( + servicer.SetCameraSettings, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.SetCameraSettingsRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.SetCameraSettingsResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'Anki.Vector.external_interface.ExternalInterface', rpc_method_handlers) diff --git a/anki_vector/messaging/messages.proto b/anki_vector/messaging/messages.proto index 37bb0d8..9955572 100644 --- a/anki_vector/messaging/messages.proto +++ b/anki_vector/messaging/messages.proto @@ -78,7 +78,7 @@ message StopAllMotorsRequest { } // See the StopAllMotors rpc for more details. -message StopAllMotorsResponse { +message StopAllMotorsResponse { ResponseStatus status = 1; } @@ -170,6 +170,7 @@ message MeetVictorFaceScanComplete message Status { oneof status_type { + FeatureStatus feature_status = 1; MeetVictorFaceScanStarted meet_victor_face_scan_started = 2; MeetVictorFaceScanComplete meet_victor_face_scan_complete = 3; FaceEnrollmentCompleted face_enrollment_completed = 4; @@ -193,7 +194,7 @@ message PoseStruct { float x = 1; float y = 2; float z = 3; - + // Rotation quaternion float q0 = 4; float q1 = 5; @@ -250,8 +251,7 @@ message RobotState { TouchData touch_data = 17; } -enum RobotStatus -{ +enum RobotStatus { ROBOT_STATUS_NONE = 0x0; ROBOT_STATUS_IS_MOVING = 0x1; ROBOT_STATUS_IS_CARRYING_BLOCK = 0x2; @@ -286,10 +286,78 @@ message CladRect float height = 4; } +// (v1.7) +message RobotObservedMotion { + uint32 timestamp = 1; // Timestamp of the corresponding image + + // Area of the supporting region for the point, as a fraction of the image + float img_area = 2; + int32 img_x = 3; // Pixel coordinate of the point in the image, relative to top-left corner. + int32 img_y = 4; // Pixel coordinate of the point in the image, relative to top-left corner. + + // Area of the supporting region for the point, as a fraction of the ground ROI + // If unable to map to the ground, area=0 + float ground_area = 5; + int32 ground_x = 6; // Coordinates of the point on the ground, relative to robot, in mm + int32 ground_y = 7; // Coordinates of the point on the ground, relative to robot, in mm + + // Top area + // Area of the supporting region for the point, as a fraction of the top region + float top_img_area = 8; + int32 top_img_x = 9; // Pixel coordinate of the point in the image, relative to top-left corner. + int32 top_img_y = 10; // Pixel coordinate of the point in the image, relative to top-left corner. + + // Bottom area + // Area of the supporting region for the point, as a fraction of the bottom region + float bottom_img_area = 11; + int32 bottom_img_x = 12; // Pixel coordinate of the point in the image, relative to top-left corner. + int32 bottom_img_y = 13; // Pixel coordinate of the point in the image, relative to top-left corner. + + // Left area + // Area of the supporting region for the point, as a fraction of the left region + float left_img_area = 14; + int32 left_img_x = 15; // Pixel coordinate of the point in the image, relative to top-left corner. + int32 left_img_y = 16; // Pixel coordinate of the point in the image, relative to top-left corner. + + // Right area + // Area of the supporting region for the point, as a fraction of the right region + float right_img_area = 17; + int32 right_img_x = 18; // Pixel coordinate of the point in the image, relative to top-left corner. + int32 right_img_y = 19; // Pixel coordinate of the point in the image, relative to top-left corner. +} + + // Event confirming that an enrolled face has been removed from the robot. +message RobotErasedEnrolledFace { + int32 face_id = 1; + string name = 2; +} + +enum UnexpectedMovementType { + TURNED_BUT_STOPPED = 0; + TURNED_IN_SAME_DIRECTION = 1; + TURNED_IN_OPPOSITE_DIRECTION = 2; + ROTATING_WITHOUT_MOTORS = 3; +} + +enum UnexpectedMovementSide { + UNKNOWN = 0; + FRONT = 1; + BACK = 2; + LEFT = 3; + RIGHT = 4; +} + +// Event dispatched when the robot does not move as expected. Has additional information +// such as the direction and type of unexpected motion. +message UnexpectedMovement { + uint32 timestamp = 1; + UnexpectedMovementType movement_type = 2; + UnexpectedMovementSide movement_side = 3; +} + // This is an int8 on the clad side. // Proto field names are prefixed with "EXPRESSION_" -enum FacialExpression -{ +enum FacialExpression { option allow_alias = true; EXPRESSION_UNKNOWN = 0; // e.g. expression estimation disabled. @@ -330,8 +398,7 @@ message RobotChangedObservedFaceID { int32 new_id = 2; } -enum FaceEnrollmentResult -{ +enum FaceEnrollmentResult { SUCCESS = 0; // Failures: @@ -373,7 +440,7 @@ message LoadedKnownFace { message RobotRenamedEnrolledFace { int32 face_id = 1; - string name = 2; + string name = 2; } message RequestEnrolledNamesResponse { @@ -421,8 +488,17 @@ message SetFaceToEnrollResponse { ResponseStatus status = 1; } -enum BehaviorResults -{ +// (v1.7) +message EnrollFaceRequest { +} + +// (v1.7) +message EnrollFaceResponse { + ResponseStatus status = 1; + BehaviorResults result = 2; +} + +enum BehaviorResults { BEHAVIOR_INVALID_STATE = 0; BEHAVIOR_COMPLETE_STATE = 1; BEHAVIOR_WONT_ACTIVATE_STATE = 2; @@ -540,7 +616,7 @@ message PhotoTaken { uint32 photo_id = 1; } -// Struct containing all the information relevant to how a +// Struct containing all the information relevant to how a // path should be modified or traversed. message PathMotionProfile { float speed_mmps = 1; @@ -556,6 +632,7 @@ message PathMotionProfile { bool is_custom = 11; } +// These codes are duplicated from actionResults.clad file and need to be kept in sync // The possible results of running an action. message ActionResult { // The possible results of running an action. @@ -637,6 +714,15 @@ message CancelActionByIdTagResponse { ResponseStatus status = 1; } +// Cancel a behavior in progress (v1.7) +message CancelBehaviorRequest { +} + +// Response from the robot to CancelBehaviorRequest. (v1.7) +message CancelBehaviorResponse { + ResponseStatus status = 1; +} + // GotoPose message GoToPoseRequest { float x_mm = 1; @@ -755,7 +841,7 @@ message TurnTowardsFaceResponse { } // GoToObject -message GoToObjectRequest { +message GoToObjectRequest { int32 object_id = 1; PathMotionProfile motion_prof = 2; float distance_from_object_origin_mm = 3; @@ -869,12 +955,12 @@ message VersionStateResponse { message SayTextRequest { string text = 1; bool use_vector_voice = 2; - float duration_scalar = 3; + float duration_scalar = 3; // Ranges from 0.05 (fast) to 20.0 (slow) + float pitch_scalar = 4; // Ranges from -1.0 (low) to +1.0 (high) (v1.7) } message SayTextResponse { - enum UtteranceState - { + enum UtteranceState { INVALID = 0; GENERATING = 1; READY = 2; @@ -1006,8 +1092,7 @@ message ExternalAudioStreamResponse { } } -enum MasterVolumeLevel -{ +enum MasterVolumeLevel { VOLUME_LOW = 0; VOLUME_MEDIUM_LOW = 1; VOLUME_MEDIUM = 2; @@ -1015,7 +1100,7 @@ enum MasterVolumeLevel VOLUME_HIGH = 4; } -message MasterVolumeRequest +message MasterVolumeRequest { MasterVolumeLevel volume_level = 1; } @@ -1063,7 +1148,7 @@ message EnableMotionDetectionResponse ResponseStatus status = 1; } -// When enabled, camera feed will appear on the robot's face, along with any +// When enabled, camera feed will appear on the robot's face, along with any // detections that are enabled from above messages message EnableMirrorModeRequest { @@ -1086,6 +1171,7 @@ message MirrorModeDisabled message EnableImageStreamingRequest { bool enable = 1; + bool enable_high_resolution = 2; } message EnableImageStreamingResponse @@ -1111,8 +1197,7 @@ message VisionModesAutoDisabled // One frame of image data and associated metadata message ImageChunk { - enum ImageEncoding - { + enum ImageEncoding { NONE_IMAGE_ENCODING = 0; RAW_GRAY = 1; // no compression RAW_RGB = 2; // no compression, just [RGBRGBRG...] @@ -1147,7 +1232,11 @@ message CameraFeedResponse { bytes data = 4; } -message CaptureSingleImageRequest {} +// Request for a single image from the robot. Default resolution is 640*360, +// enabling high resolution provides a 1280*720 image. +message CaptureSingleImageRequest { + bool enable_high_resolution = 1; // (v1.7) +} message CaptureSingleImageResponse { ResponseStatus status = 1; @@ -1166,6 +1255,43 @@ message SetEyeColorResponse { ResponseStatus status = 1; } +// (v1.7) +message CameraConfigRequest {} + +// (v1.7) +message CameraConfigResponse { + float focal_length_x = 1; + float focal_length_y = 2; + float center_x = 3; + float center_y = 4; + float fov_x = 5; // Full FOV in degrees + float fov_y = 6; // Full FOV in degrees + uint32 min_camera_exposure_time_ms = 7; + uint32 max_camera_exposure_time_ms = 8; + float min_camera_gain = 9; + float max_camera_gain = 10; +} + +// (v1.7) +message SetCameraSettingsRequest { + float gain = 1; + uint32 exposure_ms = 2; + bool enable_auto_exposure = 3; +} + +// (v1.7) +message SetCameraSettingsResponse { + ResponseStatus status = 1; + string status_message = 2; +} + +// Event sent when camera exposure settings change (v1.7) +message CameraSettingsUpdate { + float gain = 1; + uint32 exposure_ms = 2; + bool auto_exposure_enabled = 3; +} + message SDKInitializationRequest { string sdk_module_version = 1; string python_version = 2; @@ -1178,4 +1304,107 @@ message SDKInitializationResponse { ResponseStatus status = 1; } +message AppDisconnected{} + +message AppIntentRequest { + string intent = 1; + string param = 2; +} + +message AppIntentResponse { + ResponseStatus status = 1; +} + +message FeatureStatus { + string feature_name = 1; + string source = 2; // Voice, App, AI, Unknown +} + +message FeatureFlagRequest { + string feature_name = 1; +} +message FeatureFlagResponse { + ResponseStatus status = 1; + bool valid_feature = 2; + bool feature_enabled = 3; +} + +message FeatureFlagListRequest { + repeated string request_list = 1; +} + +message FeatureFlagListResponse { + ResponseStatus status = 1; + repeated string list = 2; +} + +// NOTE: must match attentionTransferTypes.clad +enum AttentionTransferReason { + Invalid = 0; + + NoCloudConnection = 1; + NoWifi = 2; + UnmatchedIntent = 3; +} + +message AttentionTransfer { + AttentionTransferReason reason = 1; + float seconds_ago = 2; +} + +message LatestAttentionTransferRequest { +} + +message LatestAttentionTransfer { + oneof oneof_message_type { + AttentionTransfer attention_transfer = 1; + } +} + +message LatestAttentionTransferResponse { + ResponseStatus status = 1; + LatestAttentionTransfer latest_attention_transfer = 2; +} + +message UpdateAndRestartRequest{} + +message UpdateAndRestartResponse{ + ResponseStatus status = 1; +} + + +message CheckUpdateStatusRequest{} + +message CheckUpdateStatusResponse{ + option(streamed) = true; + + ResponseStatus status = 1; + enum UpdateStatus { + NO_UPDATE = 0; + READY_TO_INSTALL = 1; + IN_PROGRESS_DOWNLOAD = 2; + } + UpdateStatus update_status = 2; + int64 expected = 3; + int64 progress = 4; + string update_version = 5; +} + +message CheckCloudRequest{} + +message CheckCloudResponse{ + ResponseStatus status = 1; + enum ConnectionCode { + UNKNOWN = 0; + AVAILABLE = 1; + BAD_CONNECTIVITY = 2; + FAILED_TLS = 3; + FAILED_AUTH = 4; + INSUFFICIENT_BANDWIDTH = 5; + } + ConnectionCode code = 2; + string status_message = 3; + int32 num_packets = 4; + int32 expected_packets = 5; +} diff --git a/anki_vector/messaging/messages_pb2.py b/anki_vector/messaging/messages_pb2.py index c30261a..879e5bf 100644 --- a/anki_vector/messaging/messages_pb2.py +++ b/anki_vector/messaging/messages_pb2.py @@ -22,7 +22,7 @@ name='anki_vector/messaging/messages.proto', package='Anki.Vector.external_interface', syntax='proto3', - serialized_pb=_b('\n$anki_vector/messaging/messages.proto\x12\x1e\x41nki.Vector.external_interface\x1a+anki_vector/messaging/response_status.proto\x1a&anki_vector/messaging/extensions.proto\"\x0f\n\rKeepAlivePing\" \n\x10\x41nimationTrigger\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x19\n\tAnimation\x12\x0c\n\x04name\x18\x01 \x01(\t\"|\n\x12\x44riveWheelsRequest\x12\x17\n\x0fleft_wheel_mmps\x18\x01 \x01(\x02\x12\x18\n\x10right_wheel_mmps\x18\x02 \x01(\x02\x12\x18\n\x10left_wheel_mmps2\x18\x03 \x01(\x02\x12\x19\n\x11right_wheel_mmps2\x18\x04 \x01(\x02\"U\n\x13\x44riveWheelsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\",\n\x0fMoveHeadRequest\x12\x19\n\x11speed_rad_per_sec\x18\x01 \x01(\x02\"R\n\x10MoveHeadResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\",\n\x0fMoveLiftRequest\x12\x19\n\x11speed_rad_per_sec\x18\x01 \x01(\x02\"R\n\x10MoveLiftResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x16\n\x14StopAllMotorsRequest\"W\n\x15StopAllMotorsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\xe1\x01\n\x1bPlayAnimationTriggerRequest\x12K\n\x11\x61nimation_trigger\x18\x01 \x01(\x0b\x32\x30.Anki.Vector.external_interface.AnimationTrigger\x12\r\n\x05loops\x18\x02 \x01(\r\x12\x15\n\ruse_lift_safe\x18\x03 \x01(\x08\x12\x19\n\x11ignore_body_track\x18\x04 \x01(\x08\x12\x19\n\x11ignore_head_track\x18\x05 \x01(\x08\x12\x19\n\x11ignore_lift_track\x18\x06 \x01(\x08\"\xb4\x01\n\x14PlayAnimationRequest\x12<\n\tanimation\x18\x01 \x01(\x0b\x32).Anki.Vector.external_interface.Animation\x12\r\n\x05loops\x18\x02 \x01(\r\x12\x19\n\x11ignore_body_track\x18\x03 \x01(\x08\x12\x19\n\x11ignore_head_track\x18\x04 \x01(\x08\x12\x19\n\x11ignore_lift_track\x18\x05 \x01(\x08\"\xd6\x01\n\x15PlayAnimationResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\x12<\n\tanimation\x18\x03 \x01(\x0b\x32).Anki.Vector.external_interface.Animation\"\x17\n\x15ListAnimationsRequest\"\x9c\x01\n\x16ListAnimationsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x42\n\x0f\x61nimation_names\x18\x02 \x03(\x0b\x32).Anki.Vector.external_interface.Animation\"\x1e\n\x1cListAnimationTriggersRequest\"\xb2\x01\n\x1dListAnimationTriggersResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12Q\n\x17\x61nimation_trigger_names\x18\x02 \x03(\x0b\x32\x30.Anki.Vector.external_interface.AnimationTrigger\"_\n\x1a\x44isplayFaceImageRGBRequest\x12\x11\n\tface_data\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64uration_ms\x18\x02 \x01(\r\x12\x19\n\x11interrupt_running\x18\x03 \x01(\x08\"]\n\x1b\x44isplayFaceImageRGBResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x1b\n\x19MeetVictorFaceScanStarted\"\x1c\n\x1aMeetVictorFaceScanComplete\"\xbf\x02\n\x06Status\x12\x62\n\x1dmeet_victor_face_scan_started\x18\x02 \x01(\x0b\x32\x39.Anki.Vector.external_interface.MeetVictorFaceScanStartedH\x00\x12\x64\n\x1emeet_victor_face_scan_complete\x18\x03 \x01(\x0b\x32:.Anki.Vector.external_interface.MeetVictorFaceScanCompleteH\x00\x12\\\n\x19\x66\x61\x63\x65_enrollment_completed\x18\x04 \x01(\x0b\x32\x37.Anki.Vector.external_interface.FaceEnrollmentCompletedH\x00\x42\r\n\x0bstatus_type\"\xac\x01\n\x08WakeWord\x12H\n\x0fwake_word_begin\x18\x01 \x01(\x0b\x32-.Anki.Vector.external_interface.WakeWordBeginH\x00\x12\x44\n\rwake_word_end\x18\x02 \x01(\x0b\x32+.Anki.Vector.external_interface.WakeWordEndH\x00\x42\x10\n\x0ewake_word_type\"b\n\x11TimeStampedStatus\x12\x36\n\x06status\x18\x01 \x01(\x0b\x32&.Anki.Vector.external_interface.Status\x12\x15\n\rtimestamp_utc\x18\x02 \x01(\r\"p\n\nPoseStruct\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\x12\n\n\x02q0\x18\x04 \x01(\x02\x12\n\n\x02q1\x18\x05 \x01(\x02\x12\n\n\x02q2\x18\x06 \x01(\x02\x12\n\n\x02q3\x18\x07 \x01(\x02\x12\x11\n\torigin_id\x18\x08 \x01(\r\",\n\tAccelData\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\"+\n\x08GyroData\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\"{\n\x08ProxData\x12\x13\n\x0b\x64istance_mm\x18\x01 \x01(\r\x12\x16\n\x0esignal_quality\x18\x02 \x01(\x02\x12\x14\n\x0cunobstructed\x18\x03 \x01(\x08\x12\x14\n\x0c\x66ound_object\x18\x04 \x01(\x08\x12\x16\n\x0eis_lift_in_fov\x18\x05 \x01(\x08\">\n\tTouchData\x12\x17\n\x0fraw_touch_value\x18\x01 \x01(\r\x12\x18\n\x10is_being_touched\x18\x02 \x01(\x08\"\x82\x05\n\nRobotState\x12\x38\n\x04pose\x18\x01 \x01(\x0b\x32*.Anki.Vector.external_interface.PoseStruct\x12\x16\n\x0epose_angle_rad\x18\x02 \x01(\x02\x12\x16\n\x0epose_pitch_rad\x18\x03 \x01(\x02\x12\x1d\n\x15left_wheel_speed_mmps\x18\x04 \x01(\x02\x12\x1e\n\x16right_wheel_speed_mmps\x18\x05 \x01(\x02\x12\x16\n\x0ehead_angle_rad\x18\x06 \x01(\x02\x12\x16\n\x0elift_height_mm\x18\x07 \x01(\x02\x12\x38\n\x05\x61\x63\x63\x65l\x18\x08 \x01(\x0b\x32).Anki.Vector.external_interface.AccelData\x12\x36\n\x04gyro\x18\t \x01(\x0b\x32(.Anki.Vector.external_interface.GyroData\x12\x1a\n\x12\x63\x61rrying_object_id\x18\n \x01(\x05\x12!\n\x19\x63\x61rrying_object_on_top_id\x18\x0b \x01(\x05\x12\x1f\n\x17head_tracking_object_id\x18\x0c \x01(\x05\x12\x1e\n\x16localized_to_object_id\x18\r \x01(\x05\x12\x1d\n\x15last_image_time_stamp\x18\x0e \x01(\r\x12\x0e\n\x06status\x18\x0f \x01(\r\x12;\n\tprox_data\x18\x10 \x01(\x0b\x32(.Anki.Vector.external_interface.ProxData\x12=\n\ntouch_data\x18\x11 \x01(\x0b\x32).Anki.Vector.external_interface.TouchData\"!\n\tCladPoint\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"Q\n\x08\x43ladRect\x12\x12\n\nx_top_left\x18\x01 \x01(\x02\x12\x12\n\ny_top_left\x18\x02 \x01(\x02\x12\r\n\x05width\x18\x03 \x01(\x02\x12\x0e\n\x06height\x18\x04 \x01(\x02\"\x8a\x04\n\x11RobotObservedFace\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\x12\x11\n\ttimestamp\x18\x02 \x01(\r\x12\x38\n\x04pose\x18\x03 \x01(\x0b\x32*.Anki.Vector.external_interface.PoseStruct\x12:\n\x08img_rect\x18\x04 \x01(\x0b\x32(.Anki.Vector.external_interface.CladRect\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x44\n\nexpression\x18\x06 \x01(\x0e\x32\x30.Anki.Vector.external_interface.FacialExpression\x12\x19\n\x11\x65xpression_values\x18\x07 \x03(\r\x12;\n\x08left_eye\x18\x08 \x03(\x0b\x32).Anki.Vector.external_interface.CladPoint\x12<\n\tright_eye\x18\t \x03(\x0b\x32).Anki.Vector.external_interface.CladPoint\x12\x37\n\x04nose\x18\n \x03(\x0b\x32).Anki.Vector.external_interface.CladPoint\x12\x38\n\x05mouth\x18\x0b \x03(\x0b\x32).Anki.Vector.external_interface.CladPoint\"<\n\x1aRobotChangedObservedFaceID\x12\x0e\n\x06old_id\x18\x01 \x01(\x05\x12\x0e\n\x06new_id\x18\x02 \x01(\x05\"~\n\x17\x46\x61\x63\x65\x45nrollmentCompleted\x12\x44\n\x06result\x18\x01 \x01(\x0e\x32\x34.Anki.Vector.external_interface.FaceEnrollmentResult\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\"\x1d\n\x1b\x43\x61ncelFaceEnrollmentRequest\"^\n\x1c\x43\x61ncelFaceEnrollmentResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x1d\n\x1bRequestEnrolledNamesRequest\"\xc2\x01\n\x0fLoadedKnownFace\x12$\n\x1cseconds_since_first_enrolled\x18\x01 \x01(\x03\x12\"\n\x1aseconds_since_last_updated\x18\x02 \x01(\x03\x12\x1f\n\x17seconds_since_last_seen\x18\x03 \x01(\x03\x12%\n\x1dlast_seen_seconds_since_epoch\x18\x04 \x01(\x03\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x05 \x01(\x05\x12\x0c\n\x04name\x18\x06 \x01(\t\"9\n\x18RobotRenamedEnrolledFace\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\"\x9e\x01\n\x1cRequestEnrolledNamesResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12>\n\x05\x66\x61\x63\x65s\x18\x02 \x03(\x0b\x32/.Anki.Vector.external_interface.LoadedKnownFace\"T\n\x1dUpdateEnrolledFaceByIDRequest\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\x12\x10\n\x08old_name\x18\x02 \x01(\t\x12\x10\n\x08new_name\x18\x03 \x01(\t\"`\n\x1eUpdateEnrolledFaceByIDResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"/\n\x1c\x45raseEnrolledFaceByIDRequest\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\"_\n\x1d\x45raseEnrolledFaceByIDResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x1e\n\x1c\x45raseAllEnrolledFacesRequest\"_\n\x1d\x45raseAllEnrolledFacesResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x88\x01\n\x16SetFaceToEnrollRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0bobserved_id\x18\x02 \x01(\x05\x12\x0f\n\x07save_id\x18\x03 \x01(\x05\x12\x15\n\rsave_to_robot\x18\x04 \x01(\x08\x12\x10\n\x08say_name\x18\x05 \x01(\x08\x12\x11\n\tuse_music\x18\x06 \x01(\x08\"Y\n\x17SetFaceToEnrollResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x18\n\x16\x44riveOffChargerRequest\"\x9a\x01\n\x17\x44riveOffChargerResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x17\n\x15\x44riveOnChargerRequest\"\x99\x01\n\x16\x44riveOnChargerResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x12\n\x10\x46indFacesRequest\"\x94\x01\n\x11\x46indFacesResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x1a\n\x18LookAroundInPlaceRequest\"\x9c\x01\n\x19LookAroundInPlaceResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x12\n\x10RollBlockRequest\"\x94\x01\n\x11RollBlockResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x0f\n\rWakeWordBegin\"8\n\x0bWakeWordEnd\x12\x14\n\x0cintent_heard\x18\x01 \x01(\x08\x12\x13\n\x0bintent_json\x18\x02 \x01(\t\"2\n\nUserIntent\x12\x11\n\tintent_id\x18\x01 \x01(\r\x12\x11\n\tjson_data\x18\x02 \x01(\t\"n\n\tPhotoInfo\x12\x10\n\x08photo_id\x18\x01 \x01(\r\x12\x15\n\rtimestamp_utc\x18\x02 \x01(\r\x12\x1b\n\x13photo_copied_to_app\x18\x03 \x01(\x08\x12\x1b\n\x13thumb_copied_to_app\x18\x04 \x01(\x08\"\x13\n\x11PhotosInfoRequest\"\x94\x01\n\x12PhotosInfoResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12>\n\x0bphoto_infos\x18\x02 \x03(\x0b\x32).Anki.Vector.external_interface.PhotoInfo\" \n\x0cPhotoRequest\x12\x10\n\x08photo_id\x18\x01 \x01(\r\"6\n\x10PhotoPathMessage\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x11\n\tfull_path\x18\x02 \x01(\t\"o\n\rPhotoResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05image\x18\x03 \x01(\x0c\"$\n\x10ThumbnailRequest\x12\x10\n\x08photo_id\x18\x01 \x01(\r\":\n\x14ThumbnailPathMessage\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x11\n\tfull_path\x18\x02 \x01(\t\"s\n\x11ThumbnailResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05image\x18\x03 \x01(\x0c\"&\n\x12\x44\x65letePhotoRequest\x12\x10\n\x08photo_id\x18\x01 \x01(\r\"f\n\x13\x44\x65letePhotoResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\"\x1e\n\nPhotoTaken\x12\x10\n\x08photo_id\x18\x01 \x01(\r\"\xc1\x02\n\x11PathMotionProfile\x12\x12\n\nspeed_mmps\x18\x01 \x01(\x02\x12\x13\n\x0b\x61\x63\x63\x65l_mmps2\x18\x02 \x01(\x02\x12\x13\n\x0b\x64\x65\x63\x65l_mmps2\x18\x03 \x01(\x02\x12$\n\x1cpoint_turn_speed_rad_per_sec\x18\x04 \x01(\x02\x12%\n\x1dpoint_turn_accel_rad_per_sec2\x18\x05 \x01(\x02\x12%\n\x1dpoint_turn_decel_rad_per_sec2\x18\x06 \x01(\x02\x12\x17\n\x0f\x64ock_speed_mmps\x18\x07 \x01(\x02\x12\x18\n\x10\x64ock_accel_mmps2\x18\x08 \x01(\x02\x12\x18\n\x10\x64ock_decel_mmps2\x18\t \x01(\x02\x12\x1a\n\x12reverse_speed_mmps\x18\n \x01(\x02\x12\x11\n\tis_custom\x18\x0b \x01(\x08\"\xf8\x0c\n\x0c\x41\x63tionResult\x12K\n\x04\x63ode\x18\x01 \x01(\x0e\x32=.Anki.Vector.external_interface.ActionResult.ActionResultCode\"\x9a\x0c\n\x10\x41\x63tionResultCode\x12\x19\n\x15\x41\x43TION_RESULT_SUCCESS\x10\x00\x12\x1c\n\x15\x41\x43TION_RESULT_RUNNING\x10\x80\x80\x80\x08\x12,\n%ACTION_RESULT_CANCELLED_WHILE_RUNNING\x10\x80\x80\x80\x10\x12\x12\n\x0bNOT_STARTED\x10\x81\x80\x80\x10\x12\x0c\n\x05\x41\x42ORT\x10\x80\x80\x80\x18\x12\x13\n\x0c\x41NIM_ABORTED\x10\x81\x80\x80\x18\x12\x11\n\nBAD_MARKER\x10\x82\x80\x80\x18\x12\x16\n\x0f\x42\x41\x44_MESSAGE_TAG\x10\x83\x80\x80\x18\x12\x11\n\nBAD_OBJECT\x10\x84\x80\x80\x18\x12\x0f\n\x08\x42\x41\x44_POSE\x10\x85\x80\x80\x18\x12\x0e\n\x07\x42\x41\x44_TAG\x10\x86\x80\x80\x18\x12\x1e\n\x17\x43HARGER_UNPLUGGED_ABORT\x10\x87\x80\x80\x18\x12!\n\x1a\x43LIFF_ALIGN_FAILED_TIMEOUT\x10\x88\x80\x80\x18\x12$\n\x1d\x43LIFF_ALIGN_FAILED_NO_TURNING\x10\x89\x80\x80\x18\x12&\n\x1f\x43LIFF_ALIGN_FAILED_OVER_TURNING\x10\x8a\x80\x80\x18\x12\"\n\x1b\x43LIFF_ALIGN_FAILED_NO_WHITE\x10\x8b\x80\x80\x18\x12!\n\x1a\x43LIFF_ALIGN_FAILED_STOPPED\x10\x8c\x80\x80\x18\x12!\n\x1a\x46\x41ILED_SETTING_CALIBRATION\x10\x8d\x80\x80\x18\x12(\n!FOLLOWING_PATH_BUT_NOT_TRAVERSING\x10\x8e\x80\x80\x18\x12\x12\n\x0bINTERRUPTED\x10\x8f\x80\x80\x18\x12\x1f\n\x18INVALID_OFF_TREADS_STATE\x10\x90\x80\x80\x18\x12\x19\n\x12MISMATCHED_UP_AXIS\x10\x91\x80\x80\x18\x12\x13\n\x0cNO_ANIM_NAME\x10\x92\x80\x80\x18\x12\x16\n\x0fNO_DISTANCE_SET\x10\x93\x80\x80\x18\x12\x0e\n\x07NO_FACE\x10\x94\x80\x80\x18\x12\x12\n\x0bNO_GOAL_SET\x10\x95\x80\x80\x18\x12\x19\n\x12NO_PREACTION_POSES\x10\x96\x80\x80\x18\x12 \n\x19NOT_CARRYING_OBJECT_ABORT\x10\x97\x80\x80\x18\x12\x1b\n\x14NOT_ON_CHARGER_ABORT\x10\x98\x80\x80\x18\x12\x15\n\x0eNULL_SUBACTION\x10\x99\x80\x80\x18\x12!\n\x1aPATH_PLANNING_FAILED_ABORT\x10\x9a\x80\x80\x18\x12(\n!PICKUP_OBJECT_UNEXPECTEDLY_MOVING\x10\x9b\x80\x80\x18\x12#\n\x1cSEND_MESSAGE_TO_ROBOT_FAILED\x10\x9c\x80\x80\x18\x12\x1c\n\x15STILL_CARRYING_OBJECT\x10\x9d\x80\x80\x18\x12\x0e\n\x07TIMEOUT\x10\x9e\x80\x80\x18\x12\x14\n\rTRACKS_LOCKED\x10\x9f\x80\x80\x18\x12\x1d\n\x16UNEXPECTED_DOCK_ACTION\x10\xa0\x80\x80\x18\x12\x18\n\x11UNKNOWN_TOOL_CODE\x10\xa1\x80\x80\x18\x12\x1c\n\x15UPDATE_DERIVED_FAILED\x10\xa2\x80\x80\x18\x12 \n\x19VISUAL_OBSERVATION_FAILED\x10\xa3\x80\x80\x18\x12 \n\x19SHOULDNT_DRIVE_ON_CHARGER\x10\xa4\x80\x80\x18\x12\x0c\n\x05RETRY\x10\x80\x80\x80 \x12#\n\x1c\x44ID_NOT_REACH_PREACTION_POSE\x10\x81\x80\x80 \x12\x1d\n\x16\x46\x41ILED_TRAVERSING_PATH\x10\x82\x80\x80 \x12!\n\x1aLAST_PICK_AND_PLACE_FAILED\x10\x83\x80\x80 \x12$\n\x1dMOTOR_STOPPED_MAKING_PROGRESS\x10\x84\x80\x80 \x12 \n\x19NOT_CARRYING_OBJECT_RETRY\x10\x85\x80\x80 \x12\x1b\n\x14NOT_ON_CHARGER_RETRY\x10\x86\x80\x80 \x12!\n\x1aPATH_PLANNING_FAILED_RETRY\x10\x87\x80\x80 \x12\x1e\n\x17PLACEMENT_GOAL_NOT_FREE\x10\x88\x80\x80 \x12,\n%PICKUP_OBJECT_UNEXPECTEDLY_NOT_MOVING\x10\x89\x80\x80 \x12\x17\n\x10STILL_ON_CHARGER\x10\x8a\x80\x80 \x12\x1d\n\x16UNEXPECTED_PITCH_ANGLE\x10\x8b\x80\x80 \",\n\x1a\x43\x61ncelActionByIdTagRequest\x12\x0e\n\x06id_tag\x18\x01 \x01(\r\"]\n\x1b\x43\x61ncelActionByIdTagResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\xa7\x01\n\x0fGoToPoseRequest\x12\x0c\n\x04x_mm\x18\x01 \x01(\x02\x12\x0c\n\x04y_mm\x18\x02 \x01(\x02\x12\x0b\n\x03rad\x18\x03 \x01(\x02\x12\x46\n\x0bmotion_prof\x18\x04 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x0e\n\x06id_tag\x18\x05 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x06 \x01(\x05\"\x90\x01\n\x10GoToPoseResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xd0\x02\n\x13\x44ockWithCubeRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x1f\n\x17\x64istance_from_marker_mm\x18\x02 \x01(\x02\x12\x1a\n\x12\x61pproach_angle_rad\x18\x03 \x01(\x02\x12\x45\n\x0e\x61lignment_type\x18\x04 \x01(\x0e\x32-.Anki.Vector.external_interface.AlignmentType\x12\x1a\n\x12use_approach_angle\x18\x05 \x01(\x08\x12\x19\n\x11use_pre_dock_pose\x18\x06 \x01(\x08\x12\x46\n\x0bmotion_prof\x18\x07 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x0e\n\x06id_tag\x18\x08 \x01(\x05\x12\x13\n\x0bnum_retries\x18\t \x01(\x05\"\x94\x01\n\x14\x44ockWithCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\x7f\n\x14\x44riveStraightRequest\x12\x12\n\nspeed_mmps\x18\x01 \x01(\x02\x12\x0f\n\x07\x64ist_mm\x18\x02 \x01(\x02\x12\x1d\n\x15should_play_animation\x18\x03 \x01(\x08\x12\x0e\n\x06id_tag\x18\x04 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x05 \x01(\x05\"\x95\x01\n\x15\x44riveStraightResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xa9\x01\n\x12TurnInPlaceRequest\x12\x11\n\tangle_rad\x18\x01 \x01(\x02\x12\x19\n\x11speed_rad_per_sec\x18\x02 \x01(\x02\x12\x1a\n\x12\x61\x63\x63\x65l_rad_per_sec2\x18\x03 \x01(\x02\x12\x0f\n\x07tol_rad\x18\x04 \x01(\x02\x12\x13\n\x0bis_absolute\x18\x05 \x01(\r\x12\x0e\n\x06id_tag\x18\x06 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x07 \x01(\x05\"\x93\x01\n\x13TurnInPlaceResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\x9e\x01\n\x13SetHeadAngleRequest\x12\x11\n\tangle_rad\x18\x01 \x01(\x02\x12\x1d\n\x15max_speed_rad_per_sec\x18\x02 \x01(\x02\x12\x1a\n\x12\x61\x63\x63\x65l_rad_per_sec2\x18\x03 \x01(\x02\x12\x14\n\x0c\x64uration_sec\x18\x04 \x01(\x02\x12\x0e\n\x06id_tag\x18\x05 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x06 \x01(\x05\"\x94\x01\n\x14SetHeadAngleResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\x9f\x01\n\x14SetLiftHeightRequest\x12\x11\n\theight_mm\x18\x01 \x01(\x02\x12\x1d\n\x15max_speed_rad_per_sec\x18\x02 \x01(\x02\x12\x1a\n\x12\x61\x63\x63\x65l_rad_per_sec2\x18\x03 \x01(\x02\x12\x14\n\x0c\x64uration_sec\x18\x04 \x01(\x02\x12\x0e\n\x06id_tag\x18\x05 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x06 \x01(\x05\"\x95\x01\n\x15SetLiftHeightResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"j\n\x16TurnTowardsFaceRequest\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\x12\x1a\n\x12max_turn_angle_rad\x18\x02 \x01(\x02\x12\x0e\n\x06id_tag\x18\x03 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x04 \x01(\x05\"\x97\x01\n\x17TurnTowardsFaceResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xd6\x01\n\x11GoToObjectRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x46\n\x0bmotion_prof\x18\x02 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12&\n\x1e\x64istance_from_object_origin_mm\x18\x03 \x01(\x02\x12\x19\n\x11use_pre_dock_pose\x18\x04 \x01(\x08\x12\x0e\n\x06id_tag\x18\x05 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x06 \x01(\x05\"\x92\x01\n\x12GoToObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xe6\x01\n\x11RollObjectRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x46\n\x0bmotion_prof\x18\x02 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x1a\n\x12\x61pproach_angle_rad\x18\x03 \x01(\x02\x12\x1a\n\x12use_approach_angle\x18\x04 \x01(\x08\x12\x19\n\x11use_pre_dock_pose\x18\x05 \x01(\x08\x12\x0e\n\x06id_tag\x18\x06 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x07 \x01(\x05\"\x92\x01\n\x12RollObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xe7\x01\n\x12PopAWheelieRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x46\n\x0bmotion_prof\x18\x02 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x1a\n\x12\x61pproach_angle_rad\x18\x03 \x01(\x02\x12\x1a\n\x12use_approach_angle\x18\x04 \x01(\x08\x12\x19\n\x11use_pre_dock_pose\x18\x05 \x01(\x08\x12\x0e\n\x06id_tag\x18\x06 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x07 \x01(\x05\"\x93\x01\n\x13PopAWheelieResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xe8\x01\n\x13PickupObjectRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x46\n\x0bmotion_prof\x18\x02 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x1a\n\x12\x61pproach_angle_rad\x18\x03 \x01(\x02\x12\x1a\n\x12use_approach_angle\x18\x04 \x01(\x08\x12\x19\n\x11use_pre_dock_pose\x18\x05 \x01(\x08\x12\x0e\n\x06id_tag\x18\x06 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x07 \x01(\x05\"\x94\x01\n\x14PickupObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"E\n\x1ePlaceObjectOnGroundHereRequest\x12\x0e\n\x06id_tag\x18\x01 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x02 \x01(\x05\"\x9f\x01\n\x1fPlaceObjectOnGroundHereResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\x15\n\x13\x42\x61tteryStateRequest\"\xc9\x02\n\x14\x42\x61tteryStateResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x43\n\rbattery_level\x18\x02 \x01(\x0e\x32,.Anki.Vector.external_interface.BatteryLevel\x12\x15\n\rbattery_volts\x18\x03 \x01(\x02\x12\x13\n\x0bis_charging\x18\x04 \x01(\x08\x12\x1e\n\x16is_on_charger_platform\x18\x05 \x01(\x08\x12\x1d\n\x15suggested_charger_sec\x18\x06 \x01(\x02\x12\x41\n\x0c\x63ube_battery\x18\x07 \x01(\x0b\x32+.Anki.Vector.external_interface.CubeBattery\"\xd3\x01\n\x0b\x43ubeBattery\x12K\n\x05level\x18\x01 \x01(\x0e\x32<.Anki.Vector.external_interface.CubeBattery.CubeBatteryLevel\x12\x12\n\nfactory_id\x18\x02 \x01(\t\x12\x15\n\rbattery_volts\x18\x03 \x01(\x02\x12#\n\x1btime_since_last_reading_sec\x18\x04 \x01(\x02\"\'\n\x10\x43ubeBatteryLevel\x12\x07\n\x03Low\x10\x00\x12\n\n\x06Normal\x10\x01\"\x15\n\x13VersionStateRequest\"\x83\x01\n\x14VersionStateResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x12\n\nos_version\x18\x02 \x01(\t\x12\x17\n\x0f\x65ngine_build_id\x18\x03 \x01(\t\"Q\n\x0eSayTextRequest\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x18\n\x10use_vector_voice\x18\x02 \x01(\x08\x12\x17\n\x0f\x64uration_scalar\x18\x03 \x01(\x02\"\xf5\x01\n\x0fSayTextResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12M\n\x05state\x18\x02 \x01(\x0e\x32>.Anki.Vector.external_interface.SayTextResponse.UtteranceState\"S\n\x0eUtteranceState\x12\x0b\n\x07INVALID\x10\x00\x12\x0e\n\nGENERATING\x10\x01\x12\t\n\x05READY\x10\x02\x12\x0b\n\x07PLAYING\x10\x03\x12\x0c\n\x08\x46INISHED\x10\x04\"\x9b\x01\n\x0fStimulationInfo\x12\x16\n\x0e\x65motion_events\x18\x01 \x03(\t\x12\r\n\x05value\x18\x02 \x01(\x02\x12\x10\n\x08velocity\x18\x03 \x01(\x02\x12\r\n\x05\x61\x63\x63\x65l\x18\x04 \x01(\x02\x12\x1a\n\x12value_before_event\x18\x05 \x01(\x02\x12\x11\n\tmin_value\x18\x06 \x01(\x02\x12\x11\n\tmax_value\x18\x07 \x01(\x02\"Y\n\x14\x41udioSendModeRequest\x12\x41\n\x04mode\x18\x01 \x01(\x0e\x32\x33.Anki.Vector.external_interface.AudioProcessingMode\"Y\n\x14\x41udioSendModeChanged\x12\x41\n\x04mode\x18\x01 \x01(\x0e\x32\x33.Anki.Vector.external_interface.AudioProcessingMode\"\xe8\x01\n\nAudioChunk\x12\x18\n\x10robot_time_stamp\x18\x01 \x01(\r\x12\x10\n\x08group_id\x18\x02 \x01(\r\x12\x10\n\x08\x63hunk_id\x18\x03 \x01(\r\x12\x19\n\x11\x61udio_chunk_count\x18\x04 \x01(\r\x12\x14\n\x0csignal_power\x18\x05 \x01(\x0c\x12\x1b\n\x13\x64irection_strengths\x18\x06 \x01(\x0c\x12\x18\n\x10source_direction\x18\x07 \x01(\r\x12\x19\n\x11source_confidence\x18\x08 \x01(\r\x12\x19\n\x11noise_floor_power\x18\t \x01(\r\"\x12\n\x10\x41udioFeedRequest\"\xc8\x01\n\x11\x41udioFeedResponse\x12\x18\n\x10robot_time_stamp\x18\x01 \x01(\r\x12\x10\n\x08group_id\x18\x02 \x01(\r\x12\x14\n\x0csignal_power\x18\x03 \x01(\x0c\x12\x1b\n\x13\x64irection_strengths\x18\x04 \x01(\x0c\x12\x18\n\x10source_direction\x18\x05 \x01(\r\x12\x19\n\x11source_confidence\x18\x06 \x01(\r\x12\x19\n\x11noise_floor_power\x18\x07 \x01(\r:\x04\x80\xa6\x1d\x01\"L\n\x1a\x45xternalAudioStreamPrepare\x12\x18\n\x10\x61udio_frame_rate\x18\x01 \x01(\r\x12\x14\n\x0c\x61udio_volume\x18\x02 \x01(\r\"W\n\x18\x45xternalAudioStreamChunk\x12\x1e\n\x16\x61udio_chunk_size_bytes\x18\x01 \x01(\r\x12\x1b\n\x13\x61udio_chunk_samples\x18\x02 \x01(\x0c\"\x1d\n\x1b\x45xternalAudioStreamComplete\"\x1b\n\x19\x45xternalAudioStreamCancel\"\x9e\x03\n\x1a\x45xternalAudioStreamRequest\x12Z\n\x14\x61udio_stream_prepare\x18\x01 \x01(\x0b\x32:.Anki.Vector.external_interface.ExternalAudioStreamPrepareH\x00\x12V\n\x12\x61udio_stream_chunk\x18\x02 \x01(\x0b\x32\x38.Anki.Vector.external_interface.ExternalAudioStreamChunkH\x00\x12\\\n\x15\x61udio_stream_complete\x18\x03 \x01(\x0b\x32;.Anki.Vector.external_interface.ExternalAudioStreamCompleteH\x00\x12X\n\x13\x61udio_stream_cancel\x18\x04 \x01(\x0b\x32\x39.Anki.Vector.external_interface.ExternalAudioStreamCancelH\x00\x42\x14\n\x12\x61udio_request_type\"%\n#ExternalAudioStreamPlaybackComplete\"$\n\"ExternalAudioStreamPlaybackFailure\"\\\n ExternalAudioStreamBufferOverrun\x12\x1a\n\x12\x61udio_samples_sent\x18\x01 \x01(\r\x12\x1c\n\x14\x61udio_samples_played\x18\x02 \x01(\r\"\xf9\x02\n\x1b\x45xternalAudioStreamResponse\x12m\n\x1e\x61udio_stream_playback_complete\x18\x01 \x01(\x0b\x32\x43.Anki.Vector.external_interface.ExternalAudioStreamPlaybackCompleteH\x00\x12g\n\x1b\x61udio_stream_buffer_overrun\x18\x02 \x01(\x0b\x32@.Anki.Vector.external_interface.ExternalAudioStreamBufferOverrunH\x00\x12k\n\x1d\x61udio_stream_playback_failyer\x18\x03 \x01(\x0b\x32\x42.Anki.Vector.external_interface.ExternalAudioStreamPlaybackFailureH\x00\x42\x15\n\x13\x61udio_response_type\"^\n\x13MasterVolumeRequest\x12G\n\x0cvolume_level\x18\x01 \x01(\x0e\x32\x31.Anki.Vector.external_interface.MasterVolumeLevel\"V\n\x14MasterVolumeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\".\n\x1c\x45nableMarkerDetectionRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"_\n\x1d\x45nableMarkerDetectionResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\xb1\x01\n\x1a\x45nableFaceDetectionRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\x12\x1e\n\x16\x65nable_smile_detection\x18\x02 \x01(\x08\x12$\n\x1c\x65nable_expression_estimation\x18\x03 \x01(\x08\x12\x1e\n\x16\x65nable_blink_detection\x18\x04 \x01(\x08\x12\x1d\n\x15\x65nable_gaze_detection\x18\x05 \x01(\x08\"]\n\x1b\x45nableFaceDetectionResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\".\n\x1c\x45nableMotionDetectionRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"_\n\x1d\x45nableMotionDetectionResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\")\n\x17\x45nableMirrorModeRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"Z\n\x18\x45nableMirrorModeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x14\n\x12MirrorModeDisabled\"-\n\x1b\x45nableImageStreamingRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"^\n\x1c\x45nableImageStreamingResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\" \n\x1eIsImageStreamingEnabledRequest\"E\n\x1fIsImageStreamingEnabledResponse\x12\"\n\x1ais_image_streaming_enabled\x18\x01 \x01(\x08\"\x19\n\x17VisionModesAutoDisabled\"\xd1\x03\n\nImageChunk\x12\x18\n\x10\x66rame_time_stamp\x18\x01 \x01(\r\x12\x10\n\x08image_id\x18\x02 \x01(\r\x12\r\n\x05width\x18\x03 \x01(\r\x12\x0e\n\x06height\x18\x04 \x01(\r\x12P\n\x0eimage_encoding\x18\x05 \x01(\x0e\x32\x38.Anki.Vector.external_interface.ImageChunk.ImageEncoding\x12\x15\n\rdisplay_index\x18\x06 \x01(\r\x12\x19\n\x11image_chunk_count\x18\x07 \x01(\r\x12\x10\n\x08\x63hunk_id\x18\x08 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\t \x01(\x0c\"\xd3\x01\n\rImageEncoding\x12\x17\n\x13NONE_IMAGE_ENCODING\x10\x00\x12\x0c\n\x08RAW_GRAY\x10\x01\x12\x0b\n\x07RAW_RGB\x10\x02\x12\x08\n\x04YUYV\x10\x03\x12\x0c\n\x08YUV420SP\x10\x04\x12\t\n\x05\x42\x41YER\x10\x05\x12\r\n\tJPEG_GRAY\x10\x06\x12\x0e\n\nJPEG_COLOR\x10\x07\x12\x19\n\x15JPEG_COLOR_HALF_WIDTH\x10\x08\x12\x17\n\x13JPEG_MINIMIZED_GRAY\x10\t\x12\x18\n\x14JPEG_MINIMIZED_COLOR\x10\n\"\x13\n\x11\x43\x61meraFeedRequest\"\xa6\x01\n\x12\x43\x61meraFeedResponse\x12\x18\n\x10\x66rame_time_stamp\x18\x01 \x01(\r\x12\x10\n\x08image_id\x18\x02 \x01(\r\x12P\n\x0eimage_encoding\x18\x03 \x01(\x0e\x32\x38.Anki.Vector.external_interface.ImageChunk.ImageEncoding\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c:\x04\x80\xa6\x1d\x01\"\x1b\n\x19\x43\x61ptureSingleImageRequest\"\xe8\x01\n\x1a\x43\x61ptureSingleImageResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x18\n\x10\x66rame_time_stamp\x18\x02 \x01(\r\x12\x10\n\x08image_id\x18\x03 \x01(\r\x12P\n\x0eimage_encoding\x18\x04 \x01(\x0e\x32\x38.Anki.Vector.external_interface.ImageChunk.ImageEncoding\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\"5\n\x12SetEyeColorRequest\x12\x0b\n\x03hue\x18\x01 \x01(\x02\x12\x12\n\nsaturation\x18\x02 \x01(\x02\"U\n\x13SetEyeColorResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x96\x01\n\x18SDKInitializationRequest\x12\x1a\n\x12sdk_module_version\x18\x01 \x01(\t\x12\x16\n\x0epython_version\x18\x02 \x01(\t\x12\x1d\n\x15python_implementation\x18\x03 \x01(\t\x12\x12\n\nos_version\x18\x04 \x01(\t\x12\x13\n\x0b\x63pu_version\x18\x05 \x01(\t\"[\n\x19SDKInitializationResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus*\xdc\x04\n\x0bRobotStatus\x12\x15\n\x11ROBOT_STATUS_NONE\x10\x00\x12\x1a\n\x16ROBOT_STATUS_IS_MOVING\x10\x01\x12\"\n\x1eROBOT_STATUS_IS_CARRYING_BLOCK\x10\x02\x12&\n\"ROBOT_STATUS_IS_PICKING_OR_PLACING\x10\x04\x12\x1d\n\x19ROBOT_STATUS_IS_PICKED_UP\x10\x08\x12\"\n\x1eROBOT_STATUS_IS_BUTTON_PRESSED\x10\x10\x12\x1b\n\x17ROBOT_STATUS_IS_FALLING\x10 \x12\x1d\n\x19ROBOT_STATUS_IS_ANIMATING\x10@\x12\x1c\n\x17ROBOT_STATUS_IS_PATHING\x10\x80\x01\x12\x1d\n\x18ROBOT_STATUS_LIFT_IN_POS\x10\x80\x02\x12\x1d\n\x18ROBOT_STATUS_HEAD_IN_POS\x10\x80\x04\x12!\n\x1cROBOT_STATUS_CALM_POWER_MODE\x10\x80\x08\x12\x1f\n\x1aROBOT_STATUS_IS_ON_CHARGER\x10\x80 \x12\x1d\n\x18ROBOT_STATUS_IS_CHARGING\x10\x80@\x12!\n\x1bROBOT_STATUS_CLIFF_DETECTED\x10\x80\x80\x01\x12$\n\x1eROBOT_STATUS_ARE_WHEELS_MOVING\x10\x80\x80\x02\x12 \n\x1aROBOT_STATUS_IS_BEING_HELD\x10\x80\x80\x04\x12%\n\x1fROBOT_STATUS_IS_MOTION_DETECTED\x10\x80\x80\x08*\xbd\x01\n\x10\x46\x61\x63ialExpression\x12\x16\n\x12\x45XPRESSION_UNKNOWN\x10\x00\x12\x16\n\x12\x45XPRESSION_NEUTRAL\x10\x01\x12\x18\n\x14\x45XPRESSION_HAPPINESS\x10\x02\x12\x17\n\x13\x45XPRESSION_SURPRISE\x10\x03\x12\x14\n\x10\x45XPRESSION_ANGER\x10\x04\x12\x16\n\x12\x45XPRESSION_SADNESS\x10\x05\x12\x14\n\x10\x45XPRESSION_COUNT\x10\x05\x1a\x02\x10\x01*\xcc\x01\n\x14\x46\x61\x63\x65\x45nrollmentResult\x12\x0b\n\x07SUCCESS\x10\x00\x12\x12\n\x0eSAW_WRONG_FACE\x10\x01\x12\x16\n\x12SAW_MULTIPLE_FACES\x10\x02\x12\r\n\tTIMED_OUT\x10\x03\x12\x0f\n\x0bSAVE_FAILED\x10\x04\x12\x0e\n\nINCOMPLETE\x10\x05\x12\r\n\tCANCELLED\x10\x06\x12\x0f\n\x0bNAME_IN_USE\x10\x07\x12\x16\n\x12NAMED_STORAGE_FULL\x10\x08\x12\x13\n\x0fUNKNOWN_FAILURE\x10\t*l\n\x0f\x42\x65haviorResults\x12\x1a\n\x16\x42\x45HAVIOR_INVALID_STATE\x10\x00\x12\x1b\n\x17\x42\x45HAVIOR_COMPLETE_STATE\x10\x01\x12 \n\x1c\x42\x45HAVIOR_WONT_ACTIVATE_STATE\x10\x02*S\n\x12\x41\x63tionTagConstants\x12\x13\n\x0fINVALID_SDK_TAG\x10\x00\x12\x13\n\rFIRST_SDK_TAG\x10\x81\x89z\x12\x13\n\x0cLAST_SDK_TAG\x10\xc0\x8d\xb7\x01*\x9e\x01\n\rAlignmentType\x12\x1a\n\x16\x41LIGNMENT_TYPE_UNKNOWN\x10\x00\x12\x1e\n\x1a\x41LIGNMENT_TYPE_LIFT_FINGER\x10\x01\x12\x1d\n\x19\x41LIGNMENT_TYPE_LIFT_PLATE\x10\x02\x12\x17\n\x13\x41LIGNMENT_TYPE_BODY\x10\x03\x12\x19\n\x15\x41LIGNMENT_TYPE_CUSTOM\x10\x04*s\n\x0c\x42\x61tteryLevel\x12\x19\n\x15\x42\x41TTERY_LEVEL_UNKNOWN\x10\x00\x12\x15\n\x11\x42\x41TTERY_LEVEL_LOW\x10\x01\x12\x19\n\x15\x42\x41TTERY_LEVEL_NOMINAL\x10\x02\x12\x16\n\x12\x42\x41TTERY_LEVEL_FULL\x10\x03*\xcc\x01\n\x0e\x41udioConstants\x12\x18\n\x14\x41UDIO_CONSTANTS_NULL\x10\x00\x12\x1c\n\x18MIC_DETECTION_DIRECTIONS\x10\x0c\x12%\n SAMPLE_COUNTS_PER_ENGINE_MESSAGE\x10\xa0\x01\x12\"\n\x1dSAMPLE_COUNTS_PER_SDK_MESSAGE\x10\xc0\x0c\x12\x1b\n\x16MICROPHONE_SAMPLE_RATE\x10\x89z\x12\x1a\n\x15PROCESSED_SAMPLE_RATE\x10\x80}*\x85\x01\n\x13\x41udioProcessingMode\x12\x11\n\rAUDIO_UNKNOWN\x10\x00\x12\r\n\tAUDIO_OFF\x10\x01\x12\x13\n\x0f\x41UDIO_FAST_MODE\x10\x02\x12\x1a\n\x16\x41UDIO_DIRECTIONAL_MODE\x10\x03\x12\x1b\n\x17\x41UDIO_VOICE_DETECT_MODE\x10\x04*v\n\x11MasterVolumeLevel\x12\x0e\n\nVOLUME_LOW\x10\x00\x12\x15\n\x11VOLUME_MEDIUM_LOW\x10\x01\x12\x11\n\rVOLUME_MEDIUM\x10\x02\x12\x16\n\x12VOLUME_MEDIUM_HIGH\x10\x03\x12\x0f\n\x0bVOLUME_HIGH\x10\x04\x62\x06proto3') + serialized_pb=_b('\n$anki_vector/messaging/messages.proto\x12\x1e\x41nki.Vector.external_interface\x1a+anki_vector/messaging/response_status.proto\x1a&anki_vector/messaging/extensions.proto\"\x0f\n\rKeepAlivePing\" \n\x10\x41nimationTrigger\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x19\n\tAnimation\x12\x0c\n\x04name\x18\x01 \x01(\t\"|\n\x12\x44riveWheelsRequest\x12\x17\n\x0fleft_wheel_mmps\x18\x01 \x01(\x02\x12\x18\n\x10right_wheel_mmps\x18\x02 \x01(\x02\x12\x18\n\x10left_wheel_mmps2\x18\x03 \x01(\x02\x12\x19\n\x11right_wheel_mmps2\x18\x04 \x01(\x02\"U\n\x13\x44riveWheelsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\",\n\x0fMoveHeadRequest\x12\x19\n\x11speed_rad_per_sec\x18\x01 \x01(\x02\"R\n\x10MoveHeadResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\",\n\x0fMoveLiftRequest\x12\x19\n\x11speed_rad_per_sec\x18\x01 \x01(\x02\"R\n\x10MoveLiftResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x16\n\x14StopAllMotorsRequest\"W\n\x15StopAllMotorsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\xe1\x01\n\x1bPlayAnimationTriggerRequest\x12K\n\x11\x61nimation_trigger\x18\x01 \x01(\x0b\x32\x30.Anki.Vector.external_interface.AnimationTrigger\x12\r\n\x05loops\x18\x02 \x01(\r\x12\x15\n\ruse_lift_safe\x18\x03 \x01(\x08\x12\x19\n\x11ignore_body_track\x18\x04 \x01(\x08\x12\x19\n\x11ignore_head_track\x18\x05 \x01(\x08\x12\x19\n\x11ignore_lift_track\x18\x06 \x01(\x08\"\xb4\x01\n\x14PlayAnimationRequest\x12<\n\tanimation\x18\x01 \x01(\x0b\x32).Anki.Vector.external_interface.Animation\x12\r\n\x05loops\x18\x02 \x01(\r\x12\x19\n\x11ignore_body_track\x18\x03 \x01(\x08\x12\x19\n\x11ignore_head_track\x18\x04 \x01(\x08\x12\x19\n\x11ignore_lift_track\x18\x05 \x01(\x08\"\xd6\x01\n\x15PlayAnimationResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\x12<\n\tanimation\x18\x03 \x01(\x0b\x32).Anki.Vector.external_interface.Animation\"\x17\n\x15ListAnimationsRequest\"\x9c\x01\n\x16ListAnimationsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x42\n\x0f\x61nimation_names\x18\x02 \x03(\x0b\x32).Anki.Vector.external_interface.Animation\"\x1e\n\x1cListAnimationTriggersRequest\"\xb2\x01\n\x1dListAnimationTriggersResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12Q\n\x17\x61nimation_trigger_names\x18\x02 \x03(\x0b\x32\x30.Anki.Vector.external_interface.AnimationTrigger\"_\n\x1a\x44isplayFaceImageRGBRequest\x12\x11\n\tface_data\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64uration_ms\x18\x02 \x01(\r\x12\x19\n\x11interrupt_running\x18\x03 \x01(\x08\"]\n\x1b\x44isplayFaceImageRGBResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x1b\n\x19MeetVictorFaceScanStarted\"\x1c\n\x1aMeetVictorFaceScanComplete\"\xbf\x02\n\x06Status\x12\x62\n\x1dmeet_victor_face_scan_started\x18\x02 \x01(\x0b\x32\x39.Anki.Vector.external_interface.MeetVictorFaceScanStartedH\x00\x12\x64\n\x1emeet_victor_face_scan_complete\x18\x03 \x01(\x0b\x32:.Anki.Vector.external_interface.MeetVictorFaceScanCompleteH\x00\x12\\\n\x19\x66\x61\x63\x65_enrollment_completed\x18\x04 \x01(\x0b\x32\x37.Anki.Vector.external_interface.FaceEnrollmentCompletedH\x00\x42\r\n\x0bstatus_type\"\xac\x01\n\x08WakeWord\x12H\n\x0fwake_word_begin\x18\x01 \x01(\x0b\x32-.Anki.Vector.external_interface.WakeWordBeginH\x00\x12\x44\n\rwake_word_end\x18\x02 \x01(\x0b\x32+.Anki.Vector.external_interface.WakeWordEndH\x00\x42\x10\n\x0ewake_word_type\"b\n\x11TimeStampedStatus\x12\x36\n\x06status\x18\x01 \x01(\x0b\x32&.Anki.Vector.external_interface.Status\x12\x15\n\rtimestamp_utc\x18\x02 \x01(\r\"p\n\nPoseStruct\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\x12\n\n\x02q0\x18\x04 \x01(\x02\x12\n\n\x02q1\x18\x05 \x01(\x02\x12\n\n\x02q2\x18\x06 \x01(\x02\x12\n\n\x02q3\x18\x07 \x01(\x02\x12\x11\n\torigin_id\x18\x08 \x01(\r\",\n\tAccelData\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\"+\n\x08GyroData\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\"{\n\x08ProxData\x12\x13\n\x0b\x64istance_mm\x18\x01 \x01(\r\x12\x16\n\x0esignal_quality\x18\x02 \x01(\x02\x12\x14\n\x0cunobstructed\x18\x03 \x01(\x08\x12\x14\n\x0c\x66ound_object\x18\x04 \x01(\x08\x12\x16\n\x0eis_lift_in_fov\x18\x05 \x01(\x08\">\n\tTouchData\x12\x17\n\x0fraw_touch_value\x18\x01 \x01(\r\x12\x18\n\x10is_being_touched\x18\x02 \x01(\x08\"\x82\x05\n\nRobotState\x12\x38\n\x04pose\x18\x01 \x01(\x0b\x32*.Anki.Vector.external_interface.PoseStruct\x12\x16\n\x0epose_angle_rad\x18\x02 \x01(\x02\x12\x16\n\x0epose_pitch_rad\x18\x03 \x01(\x02\x12\x1d\n\x15left_wheel_speed_mmps\x18\x04 \x01(\x02\x12\x1e\n\x16right_wheel_speed_mmps\x18\x05 \x01(\x02\x12\x16\n\x0ehead_angle_rad\x18\x06 \x01(\x02\x12\x16\n\x0elift_height_mm\x18\x07 \x01(\x02\x12\x38\n\x05\x61\x63\x63\x65l\x18\x08 \x01(\x0b\x32).Anki.Vector.external_interface.AccelData\x12\x36\n\x04gyro\x18\t \x01(\x0b\x32(.Anki.Vector.external_interface.GyroData\x12\x1a\n\x12\x63\x61rrying_object_id\x18\n \x01(\x05\x12!\n\x19\x63\x61rrying_object_on_top_id\x18\x0b \x01(\x05\x12\x1f\n\x17head_tracking_object_id\x18\x0c \x01(\x05\x12\x1e\n\x16localized_to_object_id\x18\r \x01(\x05\x12\x1d\n\x15last_image_time_stamp\x18\x0e \x01(\r\x12\x0e\n\x06status\x18\x0f \x01(\r\x12;\n\tprox_data\x18\x10 \x01(\x0b\x32(.Anki.Vector.external_interface.ProxData\x12=\n\ntouch_data\x18\x11 \x01(\x0b\x32).Anki.Vector.external_interface.TouchData\"!\n\tCladPoint\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"Q\n\x08\x43ladRect\x12\x12\n\nx_top_left\x18\x01 \x01(\x02\x12\x12\n\ny_top_left\x18\x02 \x01(\x02\x12\r\n\x05width\x18\x03 \x01(\x02\x12\x0e\n\x06height\x18\x04 \x01(\x02\"\x93\x03\n\x13RobotObservedMotion\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x10\n\x08img_area\x18\x02 \x01(\x02\x12\r\n\x05img_x\x18\x03 \x01(\x05\x12\r\n\x05img_y\x18\x04 \x01(\x05\x12\x13\n\x0bground_area\x18\x05 \x01(\x02\x12\x10\n\x08ground_x\x18\x06 \x01(\x05\x12\x10\n\x08ground_y\x18\x07 \x01(\x05\x12\x14\n\x0ctop_img_area\x18\x08 \x01(\x02\x12\x11\n\ttop_img_x\x18\t \x01(\x05\x12\x11\n\ttop_img_y\x18\n \x01(\x05\x12\x17\n\x0f\x62ottom_img_area\x18\x0b \x01(\x02\x12\x14\n\x0c\x62ottom_img_x\x18\x0c \x01(\x05\x12\x14\n\x0c\x62ottom_img_y\x18\r \x01(\x05\x12\x15\n\rleft_img_area\x18\x0e \x01(\x02\x12\x12\n\nleft_img_x\x18\x0f \x01(\x05\x12\x12\n\nleft_img_y\x18\x10 \x01(\x05\x12\x16\n\x0eright_img_area\x18\x11 \x01(\x02\x12\x13\n\x0bright_img_x\x18\x12 \x01(\x05\x12\x13\n\x0bright_img_y\x18\x13 \x01(\x05\"8\n\x17RobotErasedEnrolledFace\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\"\xc5\x01\n\x12UnexpectedMovement\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12M\n\rmovement_type\x18\x02 \x01(\x0e\x32\x36.Anki.Vector.external_interface.UnexpectedMovementType\x12M\n\rmovement_side\x18\x03 \x01(\x0e\x32\x36.Anki.Vector.external_interface.UnexpectedMovementSide\"\x8a\x04\n\x11RobotObservedFace\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\x12\x11\n\ttimestamp\x18\x02 \x01(\r\x12\x38\n\x04pose\x18\x03 \x01(\x0b\x32*.Anki.Vector.external_interface.PoseStruct\x12:\n\x08img_rect\x18\x04 \x01(\x0b\x32(.Anki.Vector.external_interface.CladRect\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x44\n\nexpression\x18\x06 \x01(\x0e\x32\x30.Anki.Vector.external_interface.FacialExpression\x12\x19\n\x11\x65xpression_values\x18\x07 \x03(\r\x12;\n\x08left_eye\x18\x08 \x03(\x0b\x32).Anki.Vector.external_interface.CladPoint\x12<\n\tright_eye\x18\t \x03(\x0b\x32).Anki.Vector.external_interface.CladPoint\x12\x37\n\x04nose\x18\n \x03(\x0b\x32).Anki.Vector.external_interface.CladPoint\x12\x38\n\x05mouth\x18\x0b \x03(\x0b\x32).Anki.Vector.external_interface.CladPoint\"<\n\x1aRobotChangedObservedFaceID\x12\x0e\n\x06old_id\x18\x01 \x01(\x05\x12\x0e\n\x06new_id\x18\x02 \x01(\x05\"~\n\x17\x46\x61\x63\x65\x45nrollmentCompleted\x12\x44\n\x06result\x18\x01 \x01(\x0e\x32\x34.Anki.Vector.external_interface.FaceEnrollmentResult\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\"\x1d\n\x1b\x43\x61ncelFaceEnrollmentRequest\"^\n\x1c\x43\x61ncelFaceEnrollmentResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x1d\n\x1bRequestEnrolledNamesRequest\"\xc2\x01\n\x0fLoadedKnownFace\x12$\n\x1cseconds_since_first_enrolled\x18\x01 \x01(\x03\x12\"\n\x1aseconds_since_last_updated\x18\x02 \x01(\x03\x12\x1f\n\x17seconds_since_last_seen\x18\x03 \x01(\x03\x12%\n\x1dlast_seen_seconds_since_epoch\x18\x04 \x01(\x03\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x05 \x01(\x05\x12\x0c\n\x04name\x18\x06 \x01(\t\"9\n\x18RobotRenamedEnrolledFace\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\"\x9e\x01\n\x1cRequestEnrolledNamesResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12>\n\x05\x66\x61\x63\x65s\x18\x02 \x03(\x0b\x32/.Anki.Vector.external_interface.LoadedKnownFace\"T\n\x1dUpdateEnrolledFaceByIDRequest\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\x12\x10\n\x08old_name\x18\x02 \x01(\t\x12\x10\n\x08new_name\x18\x03 \x01(\t\"`\n\x1eUpdateEnrolledFaceByIDResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"/\n\x1c\x45raseEnrolledFaceByIDRequest\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\"_\n\x1d\x45raseEnrolledFaceByIDResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x1e\n\x1c\x45raseAllEnrolledFacesRequest\"_\n\x1d\x45raseAllEnrolledFacesResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x88\x01\n\x16SetFaceToEnrollRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0bobserved_id\x18\x02 \x01(\x05\x12\x0f\n\x07save_id\x18\x03 \x01(\x05\x12\x15\n\rsave_to_robot\x18\x04 \x01(\x08\x12\x10\n\x08say_name\x18\x05 \x01(\x08\x12\x11\n\tuse_music\x18\x06 \x01(\x08\"Y\n\x17SetFaceToEnrollResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x13\n\x11\x45nrollFaceRequest\"\x95\x01\n\x12\x45nrollFaceResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x18\n\x16\x44riveOffChargerRequest\"\x9a\x01\n\x17\x44riveOffChargerResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x17\n\x15\x44riveOnChargerRequest\"\x99\x01\n\x16\x44riveOnChargerResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x12\n\x10\x46indFacesRequest\"\x94\x01\n\x11\x46indFacesResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x1a\n\x18LookAroundInPlaceRequest\"\x9c\x01\n\x19LookAroundInPlaceResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x12\n\x10RollBlockRequest\"\x94\x01\n\x11RollBlockResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12?\n\x06result\x18\x02 \x01(\x0e\x32/.Anki.Vector.external_interface.BehaviorResults\"\x0f\n\rWakeWordBegin\"8\n\x0bWakeWordEnd\x12\x14\n\x0cintent_heard\x18\x01 \x01(\x08\x12\x13\n\x0bintent_json\x18\x02 \x01(\t\"2\n\nUserIntent\x12\x11\n\tintent_id\x18\x01 \x01(\r\x12\x11\n\tjson_data\x18\x02 \x01(\t\"n\n\tPhotoInfo\x12\x10\n\x08photo_id\x18\x01 \x01(\r\x12\x15\n\rtimestamp_utc\x18\x02 \x01(\r\x12\x1b\n\x13photo_copied_to_app\x18\x03 \x01(\x08\x12\x1b\n\x13thumb_copied_to_app\x18\x04 \x01(\x08\"\x13\n\x11PhotosInfoRequest\"\x94\x01\n\x12PhotosInfoResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12>\n\x0bphoto_infos\x18\x02 \x03(\x0b\x32).Anki.Vector.external_interface.PhotoInfo\" \n\x0cPhotoRequest\x12\x10\n\x08photo_id\x18\x01 \x01(\r\"6\n\x10PhotoPathMessage\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x11\n\tfull_path\x18\x02 \x01(\t\"o\n\rPhotoResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05image\x18\x03 \x01(\x0c\"$\n\x10ThumbnailRequest\x12\x10\n\x08photo_id\x18\x01 \x01(\r\":\n\x14ThumbnailPathMessage\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x11\n\tfull_path\x18\x02 \x01(\t\"s\n\x11ThumbnailResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05image\x18\x03 \x01(\x0c\"&\n\x12\x44\x65letePhotoRequest\x12\x10\n\x08photo_id\x18\x01 \x01(\r\"f\n\x13\x44\x65letePhotoResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\"\x1e\n\nPhotoTaken\x12\x10\n\x08photo_id\x18\x01 \x01(\r\"\xc1\x02\n\x11PathMotionProfile\x12\x12\n\nspeed_mmps\x18\x01 \x01(\x02\x12\x13\n\x0b\x61\x63\x63\x65l_mmps2\x18\x02 \x01(\x02\x12\x13\n\x0b\x64\x65\x63\x65l_mmps2\x18\x03 \x01(\x02\x12$\n\x1cpoint_turn_speed_rad_per_sec\x18\x04 \x01(\x02\x12%\n\x1dpoint_turn_accel_rad_per_sec2\x18\x05 \x01(\x02\x12%\n\x1dpoint_turn_decel_rad_per_sec2\x18\x06 \x01(\x02\x12\x17\n\x0f\x64ock_speed_mmps\x18\x07 \x01(\x02\x12\x18\n\x10\x64ock_accel_mmps2\x18\x08 \x01(\x02\x12\x18\n\x10\x64ock_decel_mmps2\x18\t \x01(\x02\x12\x1a\n\x12reverse_speed_mmps\x18\n \x01(\x02\x12\x11\n\tis_custom\x18\x0b \x01(\x08\"\xf8\x0c\n\x0c\x41\x63tionResult\x12K\n\x04\x63ode\x18\x01 \x01(\x0e\x32=.Anki.Vector.external_interface.ActionResult.ActionResultCode\"\x9a\x0c\n\x10\x41\x63tionResultCode\x12\x19\n\x15\x41\x43TION_RESULT_SUCCESS\x10\x00\x12\x1c\n\x15\x41\x43TION_RESULT_RUNNING\x10\x80\x80\x80\x08\x12,\n%ACTION_RESULT_CANCELLED_WHILE_RUNNING\x10\x80\x80\x80\x10\x12\x12\n\x0bNOT_STARTED\x10\x81\x80\x80\x10\x12\x0c\n\x05\x41\x42ORT\x10\x80\x80\x80\x18\x12\x13\n\x0c\x41NIM_ABORTED\x10\x81\x80\x80\x18\x12\x11\n\nBAD_MARKER\x10\x82\x80\x80\x18\x12\x16\n\x0f\x42\x41\x44_MESSAGE_TAG\x10\x83\x80\x80\x18\x12\x11\n\nBAD_OBJECT\x10\x84\x80\x80\x18\x12\x0f\n\x08\x42\x41\x44_POSE\x10\x85\x80\x80\x18\x12\x0e\n\x07\x42\x41\x44_TAG\x10\x86\x80\x80\x18\x12\x1e\n\x17\x43HARGER_UNPLUGGED_ABORT\x10\x87\x80\x80\x18\x12!\n\x1a\x43LIFF_ALIGN_FAILED_TIMEOUT\x10\x88\x80\x80\x18\x12$\n\x1d\x43LIFF_ALIGN_FAILED_NO_TURNING\x10\x89\x80\x80\x18\x12&\n\x1f\x43LIFF_ALIGN_FAILED_OVER_TURNING\x10\x8a\x80\x80\x18\x12\"\n\x1b\x43LIFF_ALIGN_FAILED_NO_WHITE\x10\x8b\x80\x80\x18\x12!\n\x1a\x43LIFF_ALIGN_FAILED_STOPPED\x10\x8c\x80\x80\x18\x12!\n\x1a\x46\x41ILED_SETTING_CALIBRATION\x10\x8d\x80\x80\x18\x12(\n!FOLLOWING_PATH_BUT_NOT_TRAVERSING\x10\x8e\x80\x80\x18\x12\x12\n\x0bINTERRUPTED\x10\x8f\x80\x80\x18\x12\x1f\n\x18INVALID_OFF_TREADS_STATE\x10\x90\x80\x80\x18\x12\x19\n\x12MISMATCHED_UP_AXIS\x10\x91\x80\x80\x18\x12\x13\n\x0cNO_ANIM_NAME\x10\x92\x80\x80\x18\x12\x16\n\x0fNO_DISTANCE_SET\x10\x93\x80\x80\x18\x12\x0e\n\x07NO_FACE\x10\x94\x80\x80\x18\x12\x12\n\x0bNO_GOAL_SET\x10\x95\x80\x80\x18\x12\x19\n\x12NO_PREACTION_POSES\x10\x96\x80\x80\x18\x12 \n\x19NOT_CARRYING_OBJECT_ABORT\x10\x97\x80\x80\x18\x12\x1b\n\x14NOT_ON_CHARGER_ABORT\x10\x98\x80\x80\x18\x12\x15\n\x0eNULL_SUBACTION\x10\x99\x80\x80\x18\x12!\n\x1aPATH_PLANNING_FAILED_ABORT\x10\x9a\x80\x80\x18\x12(\n!PICKUP_OBJECT_UNEXPECTEDLY_MOVING\x10\x9b\x80\x80\x18\x12#\n\x1cSEND_MESSAGE_TO_ROBOT_FAILED\x10\x9c\x80\x80\x18\x12\x1c\n\x15STILL_CARRYING_OBJECT\x10\x9d\x80\x80\x18\x12\x0e\n\x07TIMEOUT\x10\x9e\x80\x80\x18\x12\x14\n\rTRACKS_LOCKED\x10\x9f\x80\x80\x18\x12\x1d\n\x16UNEXPECTED_DOCK_ACTION\x10\xa0\x80\x80\x18\x12\x18\n\x11UNKNOWN_TOOL_CODE\x10\xa1\x80\x80\x18\x12\x1c\n\x15UPDATE_DERIVED_FAILED\x10\xa2\x80\x80\x18\x12 \n\x19VISUAL_OBSERVATION_FAILED\x10\xa3\x80\x80\x18\x12 \n\x19SHOULDNT_DRIVE_ON_CHARGER\x10\xa4\x80\x80\x18\x12\x0c\n\x05RETRY\x10\x80\x80\x80 \x12#\n\x1c\x44ID_NOT_REACH_PREACTION_POSE\x10\x81\x80\x80 \x12\x1d\n\x16\x46\x41ILED_TRAVERSING_PATH\x10\x82\x80\x80 \x12!\n\x1aLAST_PICK_AND_PLACE_FAILED\x10\x83\x80\x80 \x12$\n\x1dMOTOR_STOPPED_MAKING_PROGRESS\x10\x84\x80\x80 \x12 \n\x19NOT_CARRYING_OBJECT_RETRY\x10\x85\x80\x80 \x12\x1b\n\x14NOT_ON_CHARGER_RETRY\x10\x86\x80\x80 \x12!\n\x1aPATH_PLANNING_FAILED_RETRY\x10\x87\x80\x80 \x12\x1e\n\x17PLACEMENT_GOAL_NOT_FREE\x10\x88\x80\x80 \x12,\n%PICKUP_OBJECT_UNEXPECTEDLY_NOT_MOVING\x10\x89\x80\x80 \x12\x17\n\x10STILL_ON_CHARGER\x10\x8a\x80\x80 \x12\x1d\n\x16UNEXPECTED_PITCH_ANGLE\x10\x8b\x80\x80 \",\n\x1a\x43\x61ncelActionByIdTagRequest\x12\x0e\n\x06id_tag\x18\x01 \x01(\r\"]\n\x1b\x43\x61ncelActionByIdTagResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x17\n\x15\x43\x61ncelBehaviorRequest\"X\n\x16\x43\x61ncelBehaviorResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\xa7\x01\n\x0fGoToPoseRequest\x12\x0c\n\x04x_mm\x18\x01 \x01(\x02\x12\x0c\n\x04y_mm\x18\x02 \x01(\x02\x12\x0b\n\x03rad\x18\x03 \x01(\x02\x12\x46\n\x0bmotion_prof\x18\x04 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x0e\n\x06id_tag\x18\x05 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x06 \x01(\x05\"\x90\x01\n\x10GoToPoseResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xd0\x02\n\x13\x44ockWithCubeRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x1f\n\x17\x64istance_from_marker_mm\x18\x02 \x01(\x02\x12\x1a\n\x12\x61pproach_angle_rad\x18\x03 \x01(\x02\x12\x45\n\x0e\x61lignment_type\x18\x04 \x01(\x0e\x32-.Anki.Vector.external_interface.AlignmentType\x12\x1a\n\x12use_approach_angle\x18\x05 \x01(\x08\x12\x19\n\x11use_pre_dock_pose\x18\x06 \x01(\x08\x12\x46\n\x0bmotion_prof\x18\x07 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x0e\n\x06id_tag\x18\x08 \x01(\x05\x12\x13\n\x0bnum_retries\x18\t \x01(\x05\"\x94\x01\n\x14\x44ockWithCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\x7f\n\x14\x44riveStraightRequest\x12\x12\n\nspeed_mmps\x18\x01 \x01(\x02\x12\x0f\n\x07\x64ist_mm\x18\x02 \x01(\x02\x12\x1d\n\x15should_play_animation\x18\x03 \x01(\x08\x12\x0e\n\x06id_tag\x18\x04 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x05 \x01(\x05\"\x95\x01\n\x15\x44riveStraightResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xa9\x01\n\x12TurnInPlaceRequest\x12\x11\n\tangle_rad\x18\x01 \x01(\x02\x12\x19\n\x11speed_rad_per_sec\x18\x02 \x01(\x02\x12\x1a\n\x12\x61\x63\x63\x65l_rad_per_sec2\x18\x03 \x01(\x02\x12\x0f\n\x07tol_rad\x18\x04 \x01(\x02\x12\x13\n\x0bis_absolute\x18\x05 \x01(\r\x12\x0e\n\x06id_tag\x18\x06 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x07 \x01(\x05\"\x93\x01\n\x13TurnInPlaceResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\x9e\x01\n\x13SetHeadAngleRequest\x12\x11\n\tangle_rad\x18\x01 \x01(\x02\x12\x1d\n\x15max_speed_rad_per_sec\x18\x02 \x01(\x02\x12\x1a\n\x12\x61\x63\x63\x65l_rad_per_sec2\x18\x03 \x01(\x02\x12\x14\n\x0c\x64uration_sec\x18\x04 \x01(\x02\x12\x0e\n\x06id_tag\x18\x05 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x06 \x01(\x05\"\x94\x01\n\x14SetHeadAngleResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\x9f\x01\n\x14SetLiftHeightRequest\x12\x11\n\theight_mm\x18\x01 \x01(\x02\x12\x1d\n\x15max_speed_rad_per_sec\x18\x02 \x01(\x02\x12\x1a\n\x12\x61\x63\x63\x65l_rad_per_sec2\x18\x03 \x01(\x02\x12\x14\n\x0c\x64uration_sec\x18\x04 \x01(\x02\x12\x0e\n\x06id_tag\x18\x05 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x06 \x01(\x05\"\x95\x01\n\x15SetLiftHeightResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"j\n\x16TurnTowardsFaceRequest\x12\x0f\n\x07\x66\x61\x63\x65_id\x18\x01 \x01(\x05\x12\x1a\n\x12max_turn_angle_rad\x18\x02 \x01(\x02\x12\x0e\n\x06id_tag\x18\x03 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x04 \x01(\x05\"\x97\x01\n\x17TurnTowardsFaceResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xd6\x01\n\x11GoToObjectRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x46\n\x0bmotion_prof\x18\x02 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12&\n\x1e\x64istance_from_object_origin_mm\x18\x03 \x01(\x02\x12\x19\n\x11use_pre_dock_pose\x18\x04 \x01(\x08\x12\x0e\n\x06id_tag\x18\x05 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x06 \x01(\x05\"\x92\x01\n\x12GoToObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xe6\x01\n\x11RollObjectRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x46\n\x0bmotion_prof\x18\x02 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x1a\n\x12\x61pproach_angle_rad\x18\x03 \x01(\x02\x12\x1a\n\x12use_approach_angle\x18\x04 \x01(\x08\x12\x19\n\x11use_pre_dock_pose\x18\x05 \x01(\x08\x12\x0e\n\x06id_tag\x18\x06 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x07 \x01(\x05\"\x92\x01\n\x12RollObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xe7\x01\n\x12PopAWheelieRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x46\n\x0bmotion_prof\x18\x02 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x1a\n\x12\x61pproach_angle_rad\x18\x03 \x01(\x02\x12\x1a\n\x12use_approach_angle\x18\x04 \x01(\x08\x12\x19\n\x11use_pre_dock_pose\x18\x05 \x01(\x08\x12\x0e\n\x06id_tag\x18\x06 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x07 \x01(\x05\"\x93\x01\n\x13PopAWheelieResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\xe8\x01\n\x13PickupObjectRequest\x12\x11\n\tobject_id\x18\x01 \x01(\x05\x12\x46\n\x0bmotion_prof\x18\x02 \x01(\x0b\x32\x31.Anki.Vector.external_interface.PathMotionProfile\x12\x1a\n\x12\x61pproach_angle_rad\x18\x03 \x01(\x02\x12\x1a\n\x12use_approach_angle\x18\x04 \x01(\x08\x12\x19\n\x11use_pre_dock_pose\x18\x05 \x01(\x08\x12\x0e\n\x06id_tag\x18\x06 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x07 \x01(\x05\"\x94\x01\n\x14PickupObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"E\n\x1ePlaceObjectOnGroundHereRequest\x12\x0e\n\x06id_tag\x18\x01 \x01(\x05\x12\x13\n\x0bnum_retries\x18\x02 \x01(\x05\"\x9f\x01\n\x1fPlaceObjectOnGroundHereResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12<\n\x06result\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.ActionResult\"\x15\n\x13\x42\x61tteryStateRequest\"\xc9\x02\n\x14\x42\x61tteryStateResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x43\n\rbattery_level\x18\x02 \x01(\x0e\x32,.Anki.Vector.external_interface.BatteryLevel\x12\x15\n\rbattery_volts\x18\x03 \x01(\x02\x12\x13\n\x0bis_charging\x18\x04 \x01(\x08\x12\x1e\n\x16is_on_charger_platform\x18\x05 \x01(\x08\x12\x1d\n\x15suggested_charger_sec\x18\x06 \x01(\x02\x12\x41\n\x0c\x63ube_battery\x18\x07 \x01(\x0b\x32+.Anki.Vector.external_interface.CubeBattery\"\xd3\x01\n\x0b\x43ubeBattery\x12K\n\x05level\x18\x01 \x01(\x0e\x32<.Anki.Vector.external_interface.CubeBattery.CubeBatteryLevel\x12\x12\n\nfactory_id\x18\x02 \x01(\t\x12\x15\n\rbattery_volts\x18\x03 \x01(\x02\x12#\n\x1btime_since_last_reading_sec\x18\x04 \x01(\x02\"\'\n\x10\x43ubeBatteryLevel\x12\x07\n\x03Low\x10\x00\x12\n\n\x06Normal\x10\x01\"\x15\n\x13VersionStateRequest\"\x83\x01\n\x14VersionStateResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x12\n\nos_version\x18\x02 \x01(\t\x12\x17\n\x0f\x65ngine_build_id\x18\x03 \x01(\t\"g\n\x0eSayTextRequest\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x18\n\x10use_vector_voice\x18\x02 \x01(\x08\x12\x17\n\x0f\x64uration_scalar\x18\x03 \x01(\x02\x12\x14\n\x0cpitch_scalar\x18\x04 \x01(\x02\"\xf5\x01\n\x0fSayTextResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12M\n\x05state\x18\x02 \x01(\x0e\x32>.Anki.Vector.external_interface.SayTextResponse.UtteranceState\"S\n\x0eUtteranceState\x12\x0b\n\x07INVALID\x10\x00\x12\x0e\n\nGENERATING\x10\x01\x12\t\n\x05READY\x10\x02\x12\x0b\n\x07PLAYING\x10\x03\x12\x0c\n\x08\x46INISHED\x10\x04\"\x9b\x01\n\x0fStimulationInfo\x12\x16\n\x0e\x65motion_events\x18\x01 \x03(\t\x12\r\n\x05value\x18\x02 \x01(\x02\x12\x10\n\x08velocity\x18\x03 \x01(\x02\x12\r\n\x05\x61\x63\x63\x65l\x18\x04 \x01(\x02\x12\x1a\n\x12value_before_event\x18\x05 \x01(\x02\x12\x11\n\tmin_value\x18\x06 \x01(\x02\x12\x11\n\tmax_value\x18\x07 \x01(\x02\"Y\n\x14\x41udioSendModeRequest\x12\x41\n\x04mode\x18\x01 \x01(\x0e\x32\x33.Anki.Vector.external_interface.AudioProcessingMode\"Y\n\x14\x41udioSendModeChanged\x12\x41\n\x04mode\x18\x01 \x01(\x0e\x32\x33.Anki.Vector.external_interface.AudioProcessingMode\"\xe8\x01\n\nAudioChunk\x12\x18\n\x10robot_time_stamp\x18\x01 \x01(\r\x12\x10\n\x08group_id\x18\x02 \x01(\r\x12\x10\n\x08\x63hunk_id\x18\x03 \x01(\r\x12\x19\n\x11\x61udio_chunk_count\x18\x04 \x01(\r\x12\x14\n\x0csignal_power\x18\x05 \x01(\x0c\x12\x1b\n\x13\x64irection_strengths\x18\x06 \x01(\x0c\x12\x18\n\x10source_direction\x18\x07 \x01(\r\x12\x19\n\x11source_confidence\x18\x08 \x01(\r\x12\x19\n\x11noise_floor_power\x18\t \x01(\r\"\x12\n\x10\x41udioFeedRequest\"\xc8\x01\n\x11\x41udioFeedResponse\x12\x18\n\x10robot_time_stamp\x18\x01 \x01(\r\x12\x10\n\x08group_id\x18\x02 \x01(\r\x12\x14\n\x0csignal_power\x18\x03 \x01(\x0c\x12\x1b\n\x13\x64irection_strengths\x18\x04 \x01(\x0c\x12\x18\n\x10source_direction\x18\x05 \x01(\r\x12\x19\n\x11source_confidence\x18\x06 \x01(\r\x12\x19\n\x11noise_floor_power\x18\x07 \x01(\r:\x04\x80\xa6\x1d\x01\"L\n\x1a\x45xternalAudioStreamPrepare\x12\x18\n\x10\x61udio_frame_rate\x18\x01 \x01(\r\x12\x14\n\x0c\x61udio_volume\x18\x02 \x01(\r\"W\n\x18\x45xternalAudioStreamChunk\x12\x1e\n\x16\x61udio_chunk_size_bytes\x18\x01 \x01(\r\x12\x1b\n\x13\x61udio_chunk_samples\x18\x02 \x01(\x0c\"\x1d\n\x1b\x45xternalAudioStreamComplete\"\x1b\n\x19\x45xternalAudioStreamCancel\"\x9e\x03\n\x1a\x45xternalAudioStreamRequest\x12Z\n\x14\x61udio_stream_prepare\x18\x01 \x01(\x0b\x32:.Anki.Vector.external_interface.ExternalAudioStreamPrepareH\x00\x12V\n\x12\x61udio_stream_chunk\x18\x02 \x01(\x0b\x32\x38.Anki.Vector.external_interface.ExternalAudioStreamChunkH\x00\x12\\\n\x15\x61udio_stream_complete\x18\x03 \x01(\x0b\x32;.Anki.Vector.external_interface.ExternalAudioStreamCompleteH\x00\x12X\n\x13\x61udio_stream_cancel\x18\x04 \x01(\x0b\x32\x39.Anki.Vector.external_interface.ExternalAudioStreamCancelH\x00\x42\x14\n\x12\x61udio_request_type\"%\n#ExternalAudioStreamPlaybackComplete\"$\n\"ExternalAudioStreamPlaybackFailure\"\\\n ExternalAudioStreamBufferOverrun\x12\x1a\n\x12\x61udio_samples_sent\x18\x01 \x01(\r\x12\x1c\n\x14\x61udio_samples_played\x18\x02 \x01(\r\"\xf9\x02\n\x1b\x45xternalAudioStreamResponse\x12m\n\x1e\x61udio_stream_playback_complete\x18\x01 \x01(\x0b\x32\x43.Anki.Vector.external_interface.ExternalAudioStreamPlaybackCompleteH\x00\x12g\n\x1b\x61udio_stream_buffer_overrun\x18\x02 \x01(\x0b\x32@.Anki.Vector.external_interface.ExternalAudioStreamBufferOverrunH\x00\x12k\n\x1d\x61udio_stream_playback_failyer\x18\x03 \x01(\x0b\x32\x42.Anki.Vector.external_interface.ExternalAudioStreamPlaybackFailureH\x00\x42\x15\n\x13\x61udio_response_type\"^\n\x13MasterVolumeRequest\x12G\n\x0cvolume_level\x18\x01 \x01(\x0e\x32\x31.Anki.Vector.external_interface.MasterVolumeLevel\"V\n\x14MasterVolumeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\".\n\x1c\x45nableMarkerDetectionRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"_\n\x1d\x45nableMarkerDetectionResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\xb1\x01\n\x1a\x45nableFaceDetectionRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\x12\x1e\n\x16\x65nable_smile_detection\x18\x02 \x01(\x08\x12$\n\x1c\x65nable_expression_estimation\x18\x03 \x01(\x08\x12\x1e\n\x16\x65nable_blink_detection\x18\x04 \x01(\x08\x12\x1d\n\x15\x65nable_gaze_detection\x18\x05 \x01(\x08\"]\n\x1b\x45nableFaceDetectionResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\".\n\x1c\x45nableMotionDetectionRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"_\n\x1d\x45nableMotionDetectionResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\")\n\x17\x45nableMirrorModeRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\"Z\n\x18\x45nableMirrorModeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x14\n\x12MirrorModeDisabled\"M\n\x1b\x45nableImageStreamingRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08\x12\x1e\n\x16\x65nable_high_resolution\x18\x02 \x01(\x08\"^\n\x1c\x45nableImageStreamingResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\" \n\x1eIsImageStreamingEnabledRequest\"E\n\x1fIsImageStreamingEnabledResponse\x12\"\n\x1ais_image_streaming_enabled\x18\x01 \x01(\x08\"\x19\n\x17VisionModesAutoDisabled\"\xd1\x03\n\nImageChunk\x12\x18\n\x10\x66rame_time_stamp\x18\x01 \x01(\r\x12\x10\n\x08image_id\x18\x02 \x01(\r\x12\r\n\x05width\x18\x03 \x01(\r\x12\x0e\n\x06height\x18\x04 \x01(\r\x12P\n\x0eimage_encoding\x18\x05 \x01(\x0e\x32\x38.Anki.Vector.external_interface.ImageChunk.ImageEncoding\x12\x15\n\rdisplay_index\x18\x06 \x01(\r\x12\x19\n\x11image_chunk_count\x18\x07 \x01(\r\x12\x10\n\x08\x63hunk_id\x18\x08 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\t \x01(\x0c\"\xd3\x01\n\rImageEncoding\x12\x17\n\x13NONE_IMAGE_ENCODING\x10\x00\x12\x0c\n\x08RAW_GRAY\x10\x01\x12\x0b\n\x07RAW_RGB\x10\x02\x12\x08\n\x04YUYV\x10\x03\x12\x0c\n\x08YUV420SP\x10\x04\x12\t\n\x05\x42\x41YER\x10\x05\x12\r\n\tJPEG_GRAY\x10\x06\x12\x0e\n\nJPEG_COLOR\x10\x07\x12\x19\n\x15JPEG_COLOR_HALF_WIDTH\x10\x08\x12\x17\n\x13JPEG_MINIMIZED_GRAY\x10\t\x12\x18\n\x14JPEG_MINIMIZED_COLOR\x10\n\"\x13\n\x11\x43\x61meraFeedRequest\"\xa6\x01\n\x12\x43\x61meraFeedResponse\x12\x18\n\x10\x66rame_time_stamp\x18\x01 \x01(\r\x12\x10\n\x08image_id\x18\x02 \x01(\r\x12P\n\x0eimage_encoding\x18\x03 \x01(\x0e\x32\x38.Anki.Vector.external_interface.ImageChunk.ImageEncoding\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c:\x04\x80\xa6\x1d\x01\";\n\x19\x43\x61ptureSingleImageRequest\x12\x1e\n\x16\x65nable_high_resolution\x18\x01 \x01(\x08\"\xe8\x01\n\x1a\x43\x61ptureSingleImageResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x18\n\x10\x66rame_time_stamp\x18\x02 \x01(\r\x12\x10\n\x08image_id\x18\x03 \x01(\r\x12P\n\x0eimage_encoding\x18\x04 \x01(\x0e\x32\x38.Anki.Vector.external_interface.ImageChunk.ImageEncoding\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\"5\n\x12SetEyeColorRequest\x12\x0b\n\x03hue\x18\x01 \x01(\x02\x12\x12\n\nsaturation\x18\x02 \x01(\x02\"U\n\x13SetEyeColorResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x15\n\x13\x43\x61meraConfigRequest\"\x84\x02\n\x14\x43\x61meraConfigResponse\x12\x16\n\x0e\x66ocal_length_x\x18\x01 \x01(\x02\x12\x16\n\x0e\x66ocal_length_y\x18\x02 \x01(\x02\x12\x10\n\x08\x63\x65nter_x\x18\x03 \x01(\x02\x12\x10\n\x08\x63\x65nter_y\x18\x04 \x01(\x02\x12\r\n\x05\x66ov_x\x18\x05 \x01(\x02\x12\r\n\x05\x66ov_y\x18\x06 \x01(\x02\x12#\n\x1bmin_camera_exposure_time_ms\x18\x07 \x01(\r\x12#\n\x1bmax_camera_exposure_time_ms\x18\x08 \x01(\r\x12\x17\n\x0fmin_camera_gain\x18\t \x01(\x02\x12\x17\n\x0fmax_camera_gain\x18\n \x01(\x02\"[\n\x18SetCameraSettingsRequest\x12\x0c\n\x04gain\x18\x01 \x01(\x02\x12\x13\n\x0b\x65xposure_ms\x18\x02 \x01(\r\x12\x1c\n\x14\x65nable_auto_exposure\x18\x03 \x01(\x08\"s\n\x19SetCameraSettingsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x16\n\x0estatus_message\x18\x02 \x01(\t\"X\n\x14\x43\x61meraSettingsUpdate\x12\x0c\n\x04gain\x18\x01 \x01(\x02\x12\x13\n\x0b\x65xposure_ms\x18\x02 \x01(\r\x12\x1d\n\x15\x61uto_exposure_enabled\x18\x03 \x01(\x08\"\x96\x01\n\x18SDKInitializationRequest\x12\x1a\n\x12sdk_module_version\x18\x01 \x01(\t\x12\x16\n\x0epython_version\x18\x02 \x01(\t\x12\x1d\n\x15python_implementation\x18\x03 \x01(\t\x12\x12\n\nos_version\x18\x04 \x01(\t\x12\x13\n\x0b\x63pu_version\x18\x05 \x01(\t\"[\n\x19SDKInitializationResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus*\xdc\x04\n\x0bRobotStatus\x12\x15\n\x11ROBOT_STATUS_NONE\x10\x00\x12\x1a\n\x16ROBOT_STATUS_IS_MOVING\x10\x01\x12\"\n\x1eROBOT_STATUS_IS_CARRYING_BLOCK\x10\x02\x12&\n\"ROBOT_STATUS_IS_PICKING_OR_PLACING\x10\x04\x12\x1d\n\x19ROBOT_STATUS_IS_PICKED_UP\x10\x08\x12\"\n\x1eROBOT_STATUS_IS_BUTTON_PRESSED\x10\x10\x12\x1b\n\x17ROBOT_STATUS_IS_FALLING\x10 \x12\x1d\n\x19ROBOT_STATUS_IS_ANIMATING\x10@\x12\x1c\n\x17ROBOT_STATUS_IS_PATHING\x10\x80\x01\x12\x1d\n\x18ROBOT_STATUS_LIFT_IN_POS\x10\x80\x02\x12\x1d\n\x18ROBOT_STATUS_HEAD_IN_POS\x10\x80\x04\x12!\n\x1cROBOT_STATUS_CALM_POWER_MODE\x10\x80\x08\x12\x1f\n\x1aROBOT_STATUS_IS_ON_CHARGER\x10\x80 \x12\x1d\n\x18ROBOT_STATUS_IS_CHARGING\x10\x80@\x12!\n\x1bROBOT_STATUS_CLIFF_DETECTED\x10\x80\x80\x01\x12$\n\x1eROBOT_STATUS_ARE_WHEELS_MOVING\x10\x80\x80\x02\x12 \n\x1aROBOT_STATUS_IS_BEING_HELD\x10\x80\x80\x04\x12%\n\x1fROBOT_STATUS_IS_MOTION_DETECTED\x10\x80\x80\x08*\x8d\x01\n\x16UnexpectedMovementType\x12\x16\n\x12TURNED_BUT_STOPPED\x10\x00\x12\x1c\n\x18TURNED_IN_SAME_DIRECTION\x10\x01\x12 \n\x1cTURNED_IN_OPPOSITE_DIRECTION\x10\x02\x12\x1b\n\x17ROTATING_WITHOUT_MOTORS\x10\x03*O\n\x16UnexpectedMovementSide\x12\x0b\n\x07UNKNOWN\x10\x00\x12\t\n\x05\x46RONT\x10\x01\x12\x08\n\x04\x42\x41\x43K\x10\x02\x12\x08\n\x04LEFT\x10\x03\x12\t\n\x05RIGHT\x10\x04*\xbd\x01\n\x10\x46\x61\x63ialExpression\x12\x16\n\x12\x45XPRESSION_UNKNOWN\x10\x00\x12\x16\n\x12\x45XPRESSION_NEUTRAL\x10\x01\x12\x18\n\x14\x45XPRESSION_HAPPINESS\x10\x02\x12\x17\n\x13\x45XPRESSION_SURPRISE\x10\x03\x12\x14\n\x10\x45XPRESSION_ANGER\x10\x04\x12\x16\n\x12\x45XPRESSION_SADNESS\x10\x05\x12\x14\n\x10\x45XPRESSION_COUNT\x10\x05\x1a\x02\x10\x01*\xcc\x01\n\x14\x46\x61\x63\x65\x45nrollmentResult\x12\x0b\n\x07SUCCESS\x10\x00\x12\x12\n\x0eSAW_WRONG_FACE\x10\x01\x12\x16\n\x12SAW_MULTIPLE_FACES\x10\x02\x12\r\n\tTIMED_OUT\x10\x03\x12\x0f\n\x0bSAVE_FAILED\x10\x04\x12\x0e\n\nINCOMPLETE\x10\x05\x12\r\n\tCANCELLED\x10\x06\x12\x0f\n\x0bNAME_IN_USE\x10\x07\x12\x16\n\x12NAMED_STORAGE_FULL\x10\x08\x12\x13\n\x0fUNKNOWN_FAILURE\x10\t*l\n\x0f\x42\x65haviorResults\x12\x1a\n\x16\x42\x45HAVIOR_INVALID_STATE\x10\x00\x12\x1b\n\x17\x42\x45HAVIOR_COMPLETE_STATE\x10\x01\x12 \n\x1c\x42\x45HAVIOR_WONT_ACTIVATE_STATE\x10\x02*S\n\x12\x41\x63tionTagConstants\x12\x13\n\x0fINVALID_SDK_TAG\x10\x00\x12\x13\n\rFIRST_SDK_TAG\x10\x81\x89z\x12\x13\n\x0cLAST_SDK_TAG\x10\xc0\x8d\xb7\x01*\x9e\x01\n\rAlignmentType\x12\x1a\n\x16\x41LIGNMENT_TYPE_UNKNOWN\x10\x00\x12\x1e\n\x1a\x41LIGNMENT_TYPE_LIFT_FINGER\x10\x01\x12\x1d\n\x19\x41LIGNMENT_TYPE_LIFT_PLATE\x10\x02\x12\x17\n\x13\x41LIGNMENT_TYPE_BODY\x10\x03\x12\x19\n\x15\x41LIGNMENT_TYPE_CUSTOM\x10\x04*s\n\x0c\x42\x61tteryLevel\x12\x19\n\x15\x42\x41TTERY_LEVEL_UNKNOWN\x10\x00\x12\x15\n\x11\x42\x41TTERY_LEVEL_LOW\x10\x01\x12\x19\n\x15\x42\x41TTERY_LEVEL_NOMINAL\x10\x02\x12\x16\n\x12\x42\x41TTERY_LEVEL_FULL\x10\x03*\xcc\x01\n\x0e\x41udioConstants\x12\x18\n\x14\x41UDIO_CONSTANTS_NULL\x10\x00\x12\x1c\n\x18MIC_DETECTION_DIRECTIONS\x10\x0c\x12%\n SAMPLE_COUNTS_PER_ENGINE_MESSAGE\x10\xa0\x01\x12\"\n\x1dSAMPLE_COUNTS_PER_SDK_MESSAGE\x10\xc0\x0c\x12\x1b\n\x16MICROPHONE_SAMPLE_RATE\x10\x89z\x12\x1a\n\x15PROCESSED_SAMPLE_RATE\x10\x80}*\x85\x01\n\x13\x41udioProcessingMode\x12\x11\n\rAUDIO_UNKNOWN\x10\x00\x12\r\n\tAUDIO_OFF\x10\x01\x12\x13\n\x0f\x41UDIO_FAST_MODE\x10\x02\x12\x1a\n\x16\x41UDIO_DIRECTIONAL_MODE\x10\x03\x12\x1b\n\x17\x41UDIO_VOICE_DETECT_MODE\x10\x04*v\n\x11MasterVolumeLevel\x12\x0e\n\nVOLUME_LOW\x10\x00\x12\x15\n\x11VOLUME_MEDIUM_LOW\x10\x01\x12\x11\n\rVOLUME_MEDIUM\x10\x02\x12\x16\n\x12VOLUME_MEDIUM_HIGH\x10\x03\x12\x0f\n\x0bVOLUME_HIGH\x10\x04\x62\x06proto3') , dependencies=[anki__vector_dot_messaging_dot_response__status__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_extensions__pb2.DESCRIPTOR,]) @@ -107,12 +107,78 @@ ], containing_type=None, options=None, - serialized_start=19443, - serialized_end=20047, + serialized_start=21067, + serialized_end=21671, ) _sym_db.RegisterEnumDescriptor(_ROBOTSTATUS) RobotStatus = enum_type_wrapper.EnumTypeWrapper(_ROBOTSTATUS) +_UNEXPECTEDMOVEMENTTYPE = _descriptor.EnumDescriptor( + name='UnexpectedMovementType', + full_name='Anki.Vector.external_interface.UnexpectedMovementType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='TURNED_BUT_STOPPED', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TURNED_IN_SAME_DIRECTION', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TURNED_IN_OPPOSITE_DIRECTION', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ROTATING_WITHOUT_MOTORS', index=3, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=21674, + serialized_end=21815, +) +_sym_db.RegisterEnumDescriptor(_UNEXPECTEDMOVEMENTTYPE) + +UnexpectedMovementType = enum_type_wrapper.EnumTypeWrapper(_UNEXPECTEDMOVEMENTTYPE) +_UNEXPECTEDMOVEMENTSIDE = _descriptor.EnumDescriptor( + name='UnexpectedMovementSide', + full_name='Anki.Vector.external_interface.UnexpectedMovementSide', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='UNKNOWN', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='FRONT', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='BACK', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='LEFT', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='RIGHT', index=4, number=4, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=21817, + serialized_end=21896, +) +_sym_db.RegisterEnumDescriptor(_UNEXPECTEDMOVEMENTSIDE) + +UnexpectedMovementSide = enum_type_wrapper.EnumTypeWrapper(_UNEXPECTEDMOVEMENTSIDE) _FACIALEXPRESSION = _descriptor.EnumDescriptor( name='FacialExpression', full_name='Anki.Vector.external_interface.FacialExpression', @@ -150,8 +216,8 @@ ], containing_type=None, options=_descriptor._ParseOptions(descriptor_pb2.EnumOptions(), _b('\020\001')), - serialized_start=20050, - serialized_end=20239, + serialized_start=21899, + serialized_end=22088, ) _sym_db.RegisterEnumDescriptor(_FACIALEXPRESSION) @@ -205,8 +271,8 @@ ], containing_type=None, options=None, - serialized_start=20242, - serialized_end=20446, + serialized_start=22091, + serialized_end=22295, ) _sym_db.RegisterEnumDescriptor(_FACEENROLLMENTRESULT) @@ -232,8 +298,8 @@ ], containing_type=None, options=None, - serialized_start=20448, - serialized_end=20556, + serialized_start=22297, + serialized_end=22405, ) _sym_db.RegisterEnumDescriptor(_BEHAVIORRESULTS) @@ -259,8 +325,8 @@ ], containing_type=None, options=None, - serialized_start=20558, - serialized_end=20641, + serialized_start=22407, + serialized_end=22490, ) _sym_db.RegisterEnumDescriptor(_ACTIONTAGCONSTANTS) @@ -294,8 +360,8 @@ ], containing_type=None, options=None, - serialized_start=20644, - serialized_end=20802, + serialized_start=22493, + serialized_end=22651, ) _sym_db.RegisterEnumDescriptor(_ALIGNMENTTYPE) @@ -325,8 +391,8 @@ ], containing_type=None, options=None, - serialized_start=20804, - serialized_end=20919, + serialized_start=22653, + serialized_end=22768, ) _sym_db.RegisterEnumDescriptor(_BATTERYLEVEL) @@ -364,8 +430,8 @@ ], containing_type=None, options=None, - serialized_start=20922, - serialized_end=21126, + serialized_start=22771, + serialized_end=22975, ) _sym_db.RegisterEnumDescriptor(_AUDIOCONSTANTS) @@ -399,8 +465,8 @@ ], containing_type=None, options=None, - serialized_start=21129, - serialized_end=21262, + serialized_start=22978, + serialized_end=23111, ) _sym_db.RegisterEnumDescriptor(_AUDIOPROCESSINGMODE) @@ -434,8 +500,8 @@ ], containing_type=None, options=None, - serialized_start=21264, - serialized_end=21382, + serialized_start=23113, + serialized_end=23231, ) _sym_db.RegisterEnumDescriptor(_MASTERVOLUMELEVEL) @@ -458,6 +524,15 @@ ROBOT_STATUS_ARE_WHEELS_MOVING = 32768 ROBOT_STATUS_IS_BEING_HELD = 65536 ROBOT_STATUS_IS_MOTION_DETECTED = 131072 +TURNED_BUT_STOPPED = 0 +TURNED_IN_SAME_DIRECTION = 1 +TURNED_IN_OPPOSITE_DIRECTION = 2 +ROTATING_WITHOUT_MOTORS = 3 +UNKNOWN = 0 +FRONT = 1 +BACK = 2 +LEFT = 3 +RIGHT = 4 EXPRESSION_UNKNOWN = 0 EXPRESSION_NEUTRAL = 1 EXPRESSION_HAPPINESS = 2 @@ -729,8 +804,8 @@ ], containing_type=None, options=None, - serialized_start=8147, - serialized_end=9709, + serialized_start=8984, + serialized_end=10546, ) _sym_db.RegisterEnumDescriptor(_ACTIONRESULT_ACTIONRESULTCODE) @@ -751,8 +826,8 @@ ], containing_type=None, options=None, - serialized_start=14429, - serialized_end=14468, + serialized_start=15381, + serialized_end=15420, ) _sym_db.RegisterEnumDescriptor(_CUBEBATTERY_CUBEBATTERYLEVEL) @@ -785,8 +860,8 @@ ], containing_type=None, options=None, - serialized_start=14873, - serialized_end=14956, + serialized_start=15847, + serialized_end=15930, ) _sym_db.RegisterEnumDescriptor(_SAYTEXTRESPONSE_UTTERANCESTATE) @@ -843,8 +918,8 @@ ], containing_type=None, options=None, - serialized_start=18387, - serialized_end=18598, + serialized_start=19393, + serialized_end=19604, ) _sym_db.RegisterEnumDescriptor(_IMAGECHUNK_IMAGEENCODING) @@ -2242,6 +2317,246 @@ ) +_ROBOTOBSERVEDMOTION = _descriptor.Descriptor( + name='RobotObservedMotion', + full_name='Anki.Vector.external_interface.RobotObservedMotion', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='timestamp', full_name='Anki.Vector.external_interface.RobotObservedMotion.timestamp', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='img_area', full_name='Anki.Vector.external_interface.RobotObservedMotion.img_area', index=1, + number=2, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='img_x', full_name='Anki.Vector.external_interface.RobotObservedMotion.img_x', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='img_y', full_name='Anki.Vector.external_interface.RobotObservedMotion.img_y', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ground_area', full_name='Anki.Vector.external_interface.RobotObservedMotion.ground_area', index=4, + number=5, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ground_x', full_name='Anki.Vector.external_interface.RobotObservedMotion.ground_x', index=5, + number=6, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ground_y', full_name='Anki.Vector.external_interface.RobotObservedMotion.ground_y', index=6, + number=7, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='top_img_area', full_name='Anki.Vector.external_interface.RobotObservedMotion.top_img_area', index=7, + number=8, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='top_img_x', full_name='Anki.Vector.external_interface.RobotObservedMotion.top_img_x', index=8, + number=9, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='top_img_y', full_name='Anki.Vector.external_interface.RobotObservedMotion.top_img_y', index=9, + number=10, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bottom_img_area', full_name='Anki.Vector.external_interface.RobotObservedMotion.bottom_img_area', index=10, + number=11, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bottom_img_x', full_name='Anki.Vector.external_interface.RobotObservedMotion.bottom_img_x', index=11, + number=12, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bottom_img_y', full_name='Anki.Vector.external_interface.RobotObservedMotion.bottom_img_y', index=12, + number=13, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='left_img_area', full_name='Anki.Vector.external_interface.RobotObservedMotion.left_img_area', index=13, + number=14, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='left_img_x', full_name='Anki.Vector.external_interface.RobotObservedMotion.left_img_x', index=14, + number=15, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='left_img_y', full_name='Anki.Vector.external_interface.RobotObservedMotion.left_img_y', index=15, + number=16, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='right_img_area', full_name='Anki.Vector.external_interface.RobotObservedMotion.right_img_area', index=16, + number=17, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='right_img_x', full_name='Anki.Vector.external_interface.RobotObservedMotion.right_img_x', index=17, + number=18, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='right_img_y', full_name='Anki.Vector.external_interface.RobotObservedMotion.right_img_y', index=18, + number=19, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3852, + serialized_end=4255, +) + + +_ROBOTERASEDENROLLEDFACE = _descriptor.Descriptor( + name='RobotErasedEnrolledFace', + full_name='Anki.Vector.external_interface.RobotErasedEnrolledFace', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='face_id', full_name='Anki.Vector.external_interface.RobotErasedEnrolledFace.face_id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='Anki.Vector.external_interface.RobotErasedEnrolledFace.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4257, + serialized_end=4313, +) + + +_UNEXPECTEDMOVEMENT = _descriptor.Descriptor( + name='UnexpectedMovement', + full_name='Anki.Vector.external_interface.UnexpectedMovement', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='timestamp', full_name='Anki.Vector.external_interface.UnexpectedMovement.timestamp', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='movement_type', full_name='Anki.Vector.external_interface.UnexpectedMovement.movement_type', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='movement_side', full_name='Anki.Vector.external_interface.UnexpectedMovement.movement_side', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4316, + serialized_end=4513, +) + + _ROBOTOBSERVEDFACE = _descriptor.Descriptor( name='RobotObservedFace', full_name='Anki.Vector.external_interface.RobotObservedFace', @@ -2338,8 +2653,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3852, - serialized_end=4374, + serialized_start=4516, + serialized_end=5038, ) @@ -2376,8 +2691,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4376, - serialized_end=4436, + serialized_start=5040, + serialized_end=5100, ) @@ -2421,8 +2736,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4438, - serialized_end=4564, + serialized_start=5102, + serialized_end=5228, ) @@ -2445,8 +2760,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4566, - serialized_end=4595, + serialized_start=5230, + serialized_end=5259, ) @@ -2476,8 +2791,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4597, - serialized_end=4691, + serialized_start=5261, + serialized_end=5355, ) @@ -2500,8 +2815,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4693, - serialized_end=4722, + serialized_start=5357, + serialized_end=5386, ) @@ -2566,8 +2881,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4725, - serialized_end=4919, + serialized_start=5389, + serialized_end=5583, ) @@ -2604,8 +2919,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4921, - serialized_end=4978, + serialized_start=5585, + serialized_end=5642, ) @@ -2642,8 +2957,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4981, - serialized_end=5139, + serialized_start=5645, + serialized_end=5803, ) @@ -2687,8 +3002,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5141, - serialized_end=5225, + serialized_start=5805, + serialized_end=5889, ) @@ -2718,8 +3033,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5227, - serialized_end=5323, + serialized_start=5891, + serialized_end=5987, ) @@ -2749,8 +3064,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5325, - serialized_end=5372, + serialized_start=5989, + serialized_end=6036, ) @@ -2780,8 +3095,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5374, - serialized_end=5469, + serialized_start=6038, + serialized_end=6133, ) @@ -2804,8 +3119,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5471, - serialized_end=5501, + serialized_start=6135, + serialized_end=6165, ) @@ -2835,8 +3150,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5503, - serialized_end=5598, + serialized_start=6167, + serialized_end=6262, ) @@ -2901,8 +3216,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5601, - serialized_end=5737, + serialized_start=6265, + serialized_end=6401, ) @@ -2932,8 +3247,70 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5739, - serialized_end=5828, + serialized_start=6403, + serialized_end=6492, +) + + +_ENROLLFACEREQUEST = _descriptor.Descriptor( + name='EnrollFaceRequest', + full_name='Anki.Vector.external_interface.EnrollFaceRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6494, + serialized_end=6513, +) + + +_ENROLLFACERESPONSE = _descriptor.Descriptor( + name='EnrollFaceResponse', + full_name='Anki.Vector.external_interface.EnrollFaceResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.EnrollFaceResponse.status', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='result', full_name='Anki.Vector.external_interface.EnrollFaceResponse.result', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6516, + serialized_end=6665, ) @@ -2956,8 +3333,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5830, - serialized_end=5854, + serialized_start=6667, + serialized_end=6691, ) @@ -2994,8 +3371,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5857, - serialized_end=6011, + serialized_start=6694, + serialized_end=6848, ) @@ -3018,8 +3395,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6013, - serialized_end=6036, + serialized_start=6850, + serialized_end=6873, ) @@ -3056,8 +3433,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6039, - serialized_end=6192, + serialized_start=6876, + serialized_end=7029, ) @@ -3080,8 +3457,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6194, - serialized_end=6212, + serialized_start=7031, + serialized_end=7049, ) @@ -3118,8 +3495,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6215, - serialized_end=6363, + serialized_start=7052, + serialized_end=7200, ) @@ -3142,8 +3519,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6365, - serialized_end=6391, + serialized_start=7202, + serialized_end=7228, ) @@ -3180,8 +3557,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6394, - serialized_end=6550, + serialized_start=7231, + serialized_end=7387, ) @@ -3204,8 +3581,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6552, - serialized_end=6570, + serialized_start=7389, + serialized_end=7407, ) @@ -3242,8 +3619,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6573, - serialized_end=6721, + serialized_start=7410, + serialized_end=7558, ) @@ -3266,8 +3643,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6723, - serialized_end=6738, + serialized_start=7560, + serialized_end=7575, ) @@ -3304,8 +3681,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6740, - serialized_end=6796, + serialized_start=7577, + serialized_end=7633, ) @@ -3342,8 +3719,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6798, - serialized_end=6848, + serialized_start=7635, + serialized_end=7685, ) @@ -3394,8 +3771,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6850, - serialized_end=6960, + serialized_start=7687, + serialized_end=7797, ) @@ -3418,8 +3795,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6962, - serialized_end=6981, + serialized_start=7799, + serialized_end=7818, ) @@ -3456,8 +3833,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6984, - serialized_end=7132, + serialized_start=7821, + serialized_end=7969, ) @@ -3487,8 +3864,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7134, - serialized_end=7166, + serialized_start=7971, + serialized_end=8003, ) @@ -3525,8 +3902,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7168, - serialized_end=7222, + serialized_start=8005, + serialized_end=8059, ) @@ -3570,8 +3947,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7224, - serialized_end=7335, + serialized_start=8061, + serialized_end=8172, ) @@ -3601,8 +3978,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7337, - serialized_end=7373, + serialized_start=8174, + serialized_end=8210, ) @@ -3639,8 +4016,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7375, - serialized_end=7433, + serialized_start=8212, + serialized_end=8270, ) @@ -3684,8 +4061,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7435, - serialized_end=7550, + serialized_start=8272, + serialized_end=8387, ) @@ -3715,8 +4092,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7552, - serialized_end=7590, + serialized_start=8389, + serialized_end=8427, ) @@ -3753,8 +4130,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7592, - serialized_end=7694, + serialized_start=8429, + serialized_end=8531, ) @@ -3784,8 +4161,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7696, - serialized_end=7726, + serialized_start=8533, + serialized_end=8563, ) @@ -3885,8 +4262,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7729, - serialized_end=8050, + serialized_start=8566, + serialized_end=8887, ) @@ -3917,8 +4294,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=8053, - serialized_end=9709, + serialized_start=8890, + serialized_end=10546, ) @@ -3948,8 +4325,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=9711, - serialized_end=9755, + serialized_start=10548, + serialized_end=10592, ) @@ -3979,8 +4356,63 @@ extension_ranges=[], oneofs=[ ], - serialized_start=9757, - serialized_end=9850, + serialized_start=10594, + serialized_end=10687, +) + + +_CANCELBEHAVIORREQUEST = _descriptor.Descriptor( + name='CancelBehaviorRequest', + full_name='Anki.Vector.external_interface.CancelBehaviorRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=10689, + serialized_end=10712, +) + + +_CANCELBEHAVIORRESPONSE = _descriptor.Descriptor( + name='CancelBehaviorResponse', + full_name='Anki.Vector.external_interface.CancelBehaviorResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.CancelBehaviorResponse.status', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=10714, + serialized_end=10802, ) @@ -4045,8 +4477,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=9853, - serialized_end=10020, + serialized_start=10805, + serialized_end=10972, ) @@ -4083,8 +4515,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10023, - serialized_end=10167, + serialized_start=10975, + serialized_end=11119, ) @@ -4170,8 +4602,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10170, - serialized_end=10506, + serialized_start=11122, + serialized_end=11458, ) @@ -4208,8 +4640,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10509, - serialized_end=10657, + serialized_start=11461, + serialized_end=11609, ) @@ -4267,8 +4699,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10659, - serialized_end=10786, + serialized_start=11611, + serialized_end=11738, ) @@ -4305,8 +4737,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10789, - serialized_end=10938, + serialized_start=11741, + serialized_end=11890, ) @@ -4378,8 +4810,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10941, - serialized_end=11110, + serialized_start=11893, + serialized_end=12062, ) @@ -4416,8 +4848,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11113, - serialized_end=11260, + serialized_start=12065, + serialized_end=12212, ) @@ -4482,8 +4914,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11263, - serialized_end=11421, + serialized_start=12215, + serialized_end=12373, ) @@ -4520,8 +4952,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11424, - serialized_end=11572, + serialized_start=12376, + serialized_end=12524, ) @@ -4586,8 +5018,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11575, - serialized_end=11734, + serialized_start=12527, + serialized_end=12686, ) @@ -4624,8 +5056,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11737, - serialized_end=11886, + serialized_start=12689, + serialized_end=12838, ) @@ -4676,8 +5108,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11888, - serialized_end=11994, + serialized_start=12840, + serialized_end=12946, ) @@ -4714,8 +5146,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11997, - serialized_end=12148, + serialized_start=12949, + serialized_end=13100, ) @@ -4780,8 +5212,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12151, - serialized_end=12365, + serialized_start=13103, + serialized_end=13317, ) @@ -4818,8 +5250,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12368, - serialized_end=12514, + serialized_start=13320, + serialized_end=13466, ) @@ -4891,8 +5323,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12517, - serialized_end=12747, + serialized_start=13469, + serialized_end=13699, ) @@ -4929,8 +5361,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12750, - serialized_end=12896, + serialized_start=13702, + serialized_end=13848, ) @@ -5002,8 +5434,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12899, - serialized_end=13130, + serialized_start=13851, + serialized_end=14082, ) @@ -5040,8 +5472,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13133, - serialized_end=13280, + serialized_start=14085, + serialized_end=14232, ) @@ -5113,8 +5545,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13283, - serialized_end=13515, + serialized_start=14235, + serialized_end=14467, ) @@ -5151,8 +5583,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13518, - serialized_end=13666, + serialized_start=14470, + serialized_end=14618, ) @@ -5189,8 +5621,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13668, - serialized_end=13737, + serialized_start=14620, + serialized_end=14689, ) @@ -5227,8 +5659,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13740, - serialized_end=13899, + serialized_start=14692, + serialized_end=14851, ) @@ -5251,8 +5683,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13901, - serialized_end=13922, + serialized_start=14853, + serialized_end=14874, ) @@ -5324,8 +5756,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13925, - serialized_end=14254, + serialized_start=14877, + serialized_end=15206, ) @@ -5377,8 +5809,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14257, - serialized_end=14468, + serialized_start=15209, + serialized_end=15420, ) @@ -5401,8 +5833,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14470, - serialized_end=14491, + serialized_start=15422, + serialized_end=15443, ) @@ -5446,8 +5878,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14494, - serialized_end=14625, + serialized_start=15446, + serialized_end=15577, ) @@ -5473,8 +5905,15 @@ is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='duration_scalar', full_name='Anki.Vector.external_interface.SayTextRequest.duration_scalar', index=2, - number=3, type=2, cpp_type=6, label=1, + name='duration_scalar', full_name='Anki.Vector.external_interface.SayTextRequest.duration_scalar', index=2, + number=3, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='pitch_scalar', full_name='Anki.Vector.external_interface.SayTextRequest.pitch_scalar', index=3, + number=4, type=2, cpp_type=6, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, @@ -5491,8 +5930,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14627, - serialized_end=14708, + serialized_start=15579, + serialized_end=15682, ) @@ -5530,8 +5969,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14711, - serialized_end=14956, + serialized_start=15685, + serialized_end=15930, ) @@ -5603,8 +6042,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14959, - serialized_end=15114, + serialized_start=15933, + serialized_end=16088, ) @@ -5634,8 +6073,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15116, - serialized_end=15205, + serialized_start=16090, + serialized_end=16179, ) @@ -5665,8 +6104,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15207, - serialized_end=15296, + serialized_start=16181, + serialized_end=16270, ) @@ -5752,8 +6191,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15299, - serialized_end=15531, + serialized_start=16273, + serialized_end=16505, ) @@ -5776,8 +6215,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15533, - serialized_end=15551, + serialized_start=16507, + serialized_end=16525, ) @@ -5849,8 +6288,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15554, - serialized_end=15754, + serialized_start=16528, + serialized_end=16728, ) @@ -5887,8 +6326,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15756, - serialized_end=15832, + serialized_start=16730, + serialized_end=16806, ) @@ -5925,8 +6364,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15834, - serialized_end=15921, + serialized_start=16808, + serialized_end=16895, ) @@ -5949,8 +6388,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15923, - serialized_end=15952, + serialized_start=16897, + serialized_end=16926, ) @@ -5973,8 +6412,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15954, - serialized_end=15981, + serialized_start=16928, + serialized_end=16955, ) @@ -6028,8 +6467,8 @@ name='audio_request_type', full_name='Anki.Vector.external_interface.ExternalAudioStreamRequest.audio_request_type', index=0, containing_type=None, fields=[]), ], - serialized_start=15984, - serialized_end=16398, + serialized_start=16958, + serialized_end=17372, ) @@ -6052,8 +6491,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=16400, - serialized_end=16437, + serialized_start=17374, + serialized_end=17411, ) @@ -6076,8 +6515,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=16439, - serialized_end=16475, + serialized_start=17413, + serialized_end=17449, ) @@ -6114,8 +6553,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=16477, - serialized_end=16569, + serialized_start=17451, + serialized_end=17543, ) @@ -6162,8 +6601,8 @@ name='audio_response_type', full_name='Anki.Vector.external_interface.ExternalAudioStreamResponse.audio_response_type', index=0, containing_type=None, fields=[]), ], - serialized_start=16572, - serialized_end=16949, + serialized_start=17546, + serialized_end=17923, ) @@ -6193,8 +6632,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=16951, - serialized_end=17045, + serialized_start=17925, + serialized_end=18019, ) @@ -6224,8 +6663,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17047, - serialized_end=17133, + serialized_start=18021, + serialized_end=18107, ) @@ -6255,8 +6694,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17135, - serialized_end=17181, + serialized_start=18109, + serialized_end=18155, ) @@ -6286,8 +6725,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17183, - serialized_end=17278, + serialized_start=18157, + serialized_end=18252, ) @@ -6345,8 +6784,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17281, - serialized_end=17458, + serialized_start=18255, + serialized_end=18432, ) @@ -6376,8 +6815,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17460, - serialized_end=17553, + serialized_start=18434, + serialized_end=18527, ) @@ -6407,8 +6846,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17555, - serialized_end=17601, + serialized_start=18529, + serialized_end=18575, ) @@ -6438,8 +6877,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17603, - serialized_end=17698, + serialized_start=18577, + serialized_end=18672, ) @@ -6469,8 +6908,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17700, - serialized_end=17741, + serialized_start=18674, + serialized_end=18715, ) @@ -6500,8 +6939,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17743, - serialized_end=17833, + serialized_start=18717, + serialized_end=18807, ) @@ -6524,8 +6963,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17835, - serialized_end=17855, + serialized_start=18809, + serialized_end=18829, ) @@ -6543,6 +6982,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='enable_high_resolution', full_name='Anki.Vector.external_interface.EnableImageStreamingRequest.enable_high_resolution', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -6555,8 +7001,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17857, - serialized_end=17902, + serialized_start=18831, + serialized_end=18908, ) @@ -6586,8 +7032,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=17904, - serialized_end=17998, + serialized_start=18910, + serialized_end=19004, ) @@ -6610,8 +7056,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=18000, - serialized_end=18032, + serialized_start=19006, + serialized_end=19038, ) @@ -6641,8 +7087,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=18034, - serialized_end=18103, + serialized_start=19040, + serialized_end=19109, ) @@ -6665,8 +7111,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=18105, - serialized_end=18130, + serialized_start=19111, + serialized_end=19136, ) @@ -6753,8 +7199,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=18133, - serialized_end=18598, + serialized_start=19139, + serialized_end=19604, ) @@ -6777,8 +7223,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=18600, - serialized_end=18619, + serialized_start=19606, + serialized_end=19625, ) @@ -6829,8 +7275,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=18622, - serialized_end=18788, + serialized_start=19628, + serialized_end=19794, ) @@ -6841,6 +7287,13 @@ file=DESCRIPTOR, containing_type=None, fields=[ + _descriptor.FieldDescriptor( + name='enable_high_resolution', full_name='Anki.Vector.external_interface.CaptureSingleImageRequest.enable_high_resolution', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -6853,8 +7306,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=18790, - serialized_end=18817, + serialized_start=19796, + serialized_end=19855, ) @@ -6912,8 +7365,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=18820, - serialized_end=19052, + serialized_start=19858, + serialized_end=20090, ) @@ -6950,8 +7403,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=19054, - serialized_end=19107, + serialized_start=20092, + serialized_end=20145, ) @@ -6981,8 +7434,254 @@ extension_ranges=[], oneofs=[ ], - serialized_start=19109, - serialized_end=19194, + serialized_start=20147, + serialized_end=20232, +) + + +_CAMERACONFIGREQUEST = _descriptor.Descriptor( + name='CameraConfigRequest', + full_name='Anki.Vector.external_interface.CameraConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=20234, + serialized_end=20255, +) + + +_CAMERACONFIGRESPONSE = _descriptor.Descriptor( + name='CameraConfigResponse', + full_name='Anki.Vector.external_interface.CameraConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='focal_length_x', full_name='Anki.Vector.external_interface.CameraConfigResponse.focal_length_x', index=0, + number=1, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='focal_length_y', full_name='Anki.Vector.external_interface.CameraConfigResponse.focal_length_y', index=1, + number=2, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='center_x', full_name='Anki.Vector.external_interface.CameraConfigResponse.center_x', index=2, + number=3, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='center_y', full_name='Anki.Vector.external_interface.CameraConfigResponse.center_y', index=3, + number=4, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='fov_x', full_name='Anki.Vector.external_interface.CameraConfigResponse.fov_x', index=4, + number=5, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='fov_y', full_name='Anki.Vector.external_interface.CameraConfigResponse.fov_y', index=5, + number=6, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='min_camera_exposure_time_ms', full_name='Anki.Vector.external_interface.CameraConfigResponse.min_camera_exposure_time_ms', index=6, + number=7, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='max_camera_exposure_time_ms', full_name='Anki.Vector.external_interface.CameraConfigResponse.max_camera_exposure_time_ms', index=7, + number=8, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='min_camera_gain', full_name='Anki.Vector.external_interface.CameraConfigResponse.min_camera_gain', index=8, + number=9, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='max_camera_gain', full_name='Anki.Vector.external_interface.CameraConfigResponse.max_camera_gain', index=9, + number=10, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=20258, + serialized_end=20518, +) + + +_SETCAMERASETTINGSREQUEST = _descriptor.Descriptor( + name='SetCameraSettingsRequest', + full_name='Anki.Vector.external_interface.SetCameraSettingsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='gain', full_name='Anki.Vector.external_interface.SetCameraSettingsRequest.gain', index=0, + number=1, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='exposure_ms', full_name='Anki.Vector.external_interface.SetCameraSettingsRequest.exposure_ms', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='enable_auto_exposure', full_name='Anki.Vector.external_interface.SetCameraSettingsRequest.enable_auto_exposure', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=20520, + serialized_end=20611, +) + + +_SETCAMERASETTINGSRESPONSE = _descriptor.Descriptor( + name='SetCameraSettingsResponse', + full_name='Anki.Vector.external_interface.SetCameraSettingsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.SetCameraSettingsResponse.status', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='status_message', full_name='Anki.Vector.external_interface.SetCameraSettingsResponse.status_message', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=20613, + serialized_end=20728, +) + + +_CAMERASETTINGSUPDATE = _descriptor.Descriptor( + name='CameraSettingsUpdate', + full_name='Anki.Vector.external_interface.CameraSettingsUpdate', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='gain', full_name='Anki.Vector.external_interface.CameraSettingsUpdate.gain', index=0, + number=1, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='exposure_ms', full_name='Anki.Vector.external_interface.CameraSettingsUpdate.exposure_ms', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='auto_exposure_enabled', full_name='Anki.Vector.external_interface.CameraSettingsUpdate.auto_exposure_enabled', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=20730, + serialized_end=20818, ) @@ -7040,8 +7739,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=19197, - serialized_end=19347, + serialized_start=20821, + serialized_end=20971, ) @@ -7071,8 +7770,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=19349, - serialized_end=19440, + serialized_start=20973, + serialized_end=21064, ) _DRIVEWHEELSRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS @@ -7115,6 +7814,8 @@ _ROBOTSTATE.fields_by_name['gyro'].message_type = _GYRODATA _ROBOTSTATE.fields_by_name['prox_data'].message_type = _PROXDATA _ROBOTSTATE.fields_by_name['touch_data'].message_type = _TOUCHDATA +_UNEXPECTEDMOVEMENT.fields_by_name['movement_type'].enum_type = _UNEXPECTEDMOVEMENTTYPE +_UNEXPECTEDMOVEMENT.fields_by_name['movement_side'].enum_type = _UNEXPECTEDMOVEMENTSIDE _ROBOTOBSERVEDFACE.fields_by_name['pose'].message_type = _POSESTRUCT _ROBOTOBSERVEDFACE.fields_by_name['img_rect'].message_type = _CLADRECT _ROBOTOBSERVEDFACE.fields_by_name['expression'].enum_type = _FACIALEXPRESSION @@ -7130,6 +7831,8 @@ _ERASEENROLLEDFACEBYIDRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _ERASEALLENROLLEDFACESRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _SETFACETOENROLLRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_ENROLLFACERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_ENROLLFACERESPONSE.fields_by_name['result'].enum_type = _BEHAVIORRESULTS _DRIVEOFFCHARGERRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _DRIVEOFFCHARGERRESPONSE.fields_by_name['result'].enum_type = _BEHAVIORRESULTS _DRIVEONCHARGERRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS @@ -7148,6 +7851,7 @@ _ACTIONRESULT.fields_by_name['code'].enum_type = _ACTIONRESULT_ACTIONRESULTCODE _ACTIONRESULT_ACTIONRESULTCODE.containing_type = _ACTIONRESULT _CANCELACTIONBYIDTAGRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_CANCELBEHAVIORRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _GOTOPOSEREQUEST.fields_by_name['motion_prof'].message_type = _PATHMOTIONPROFILE _GOTOPOSERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _GOTOPOSERESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT @@ -7231,6 +7935,7 @@ _CAPTURESINGLEIMAGERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _CAPTURESINGLEIMAGERESPONSE.fields_by_name['image_encoding'].enum_type = _IMAGECHUNK_IMAGEENCODING _SETEYECOLORRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_SETCAMERASETTINGSRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _SDKINITIALIZATIONRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS DESCRIPTOR.message_types_by_name['KeepAlivePing'] = _KEEPALIVEPING DESCRIPTOR.message_types_by_name['AnimationTrigger'] = _ANIMATIONTRIGGER @@ -7265,6 +7970,9 @@ DESCRIPTOR.message_types_by_name['RobotState'] = _ROBOTSTATE DESCRIPTOR.message_types_by_name['CladPoint'] = _CLADPOINT DESCRIPTOR.message_types_by_name['CladRect'] = _CLADRECT +DESCRIPTOR.message_types_by_name['RobotObservedMotion'] = _ROBOTOBSERVEDMOTION +DESCRIPTOR.message_types_by_name['RobotErasedEnrolledFace'] = _ROBOTERASEDENROLLEDFACE +DESCRIPTOR.message_types_by_name['UnexpectedMovement'] = _UNEXPECTEDMOVEMENT DESCRIPTOR.message_types_by_name['RobotObservedFace'] = _ROBOTOBSERVEDFACE DESCRIPTOR.message_types_by_name['RobotChangedObservedFaceID'] = _ROBOTCHANGEDOBSERVEDFACEID DESCRIPTOR.message_types_by_name['FaceEnrollmentCompleted'] = _FACEENROLLMENTCOMPLETED @@ -7282,6 +7990,8 @@ DESCRIPTOR.message_types_by_name['EraseAllEnrolledFacesResponse'] = _ERASEALLENROLLEDFACESRESPONSE DESCRIPTOR.message_types_by_name['SetFaceToEnrollRequest'] = _SETFACETOENROLLREQUEST DESCRIPTOR.message_types_by_name['SetFaceToEnrollResponse'] = _SETFACETOENROLLRESPONSE +DESCRIPTOR.message_types_by_name['EnrollFaceRequest'] = _ENROLLFACEREQUEST +DESCRIPTOR.message_types_by_name['EnrollFaceResponse'] = _ENROLLFACERESPONSE DESCRIPTOR.message_types_by_name['DriveOffChargerRequest'] = _DRIVEOFFCHARGERREQUEST DESCRIPTOR.message_types_by_name['DriveOffChargerResponse'] = _DRIVEOFFCHARGERRESPONSE DESCRIPTOR.message_types_by_name['DriveOnChargerRequest'] = _DRIVEONCHARGERREQUEST @@ -7311,6 +8021,8 @@ DESCRIPTOR.message_types_by_name['ActionResult'] = _ACTIONRESULT DESCRIPTOR.message_types_by_name['CancelActionByIdTagRequest'] = _CANCELACTIONBYIDTAGREQUEST DESCRIPTOR.message_types_by_name['CancelActionByIdTagResponse'] = _CANCELACTIONBYIDTAGRESPONSE +DESCRIPTOR.message_types_by_name['CancelBehaviorRequest'] = _CANCELBEHAVIORREQUEST +DESCRIPTOR.message_types_by_name['CancelBehaviorResponse'] = _CANCELBEHAVIORRESPONSE DESCRIPTOR.message_types_by_name['GoToPoseRequest'] = _GOTOPOSEREQUEST DESCRIPTOR.message_types_by_name['GoToPoseResponse'] = _GOTOPOSERESPONSE DESCRIPTOR.message_types_by_name['DockWithCubeRequest'] = _DOCKWITHCUBEREQUEST @@ -7380,9 +8092,16 @@ DESCRIPTOR.message_types_by_name['CaptureSingleImageResponse'] = _CAPTURESINGLEIMAGERESPONSE DESCRIPTOR.message_types_by_name['SetEyeColorRequest'] = _SETEYECOLORREQUEST DESCRIPTOR.message_types_by_name['SetEyeColorResponse'] = _SETEYECOLORRESPONSE +DESCRIPTOR.message_types_by_name['CameraConfigRequest'] = _CAMERACONFIGREQUEST +DESCRIPTOR.message_types_by_name['CameraConfigResponse'] = _CAMERACONFIGRESPONSE +DESCRIPTOR.message_types_by_name['SetCameraSettingsRequest'] = _SETCAMERASETTINGSREQUEST +DESCRIPTOR.message_types_by_name['SetCameraSettingsResponse'] = _SETCAMERASETTINGSRESPONSE +DESCRIPTOR.message_types_by_name['CameraSettingsUpdate'] = _CAMERASETTINGSUPDATE DESCRIPTOR.message_types_by_name['SDKInitializationRequest'] = _SDKINITIALIZATIONREQUEST DESCRIPTOR.message_types_by_name['SDKInitializationResponse'] = _SDKINITIALIZATIONRESPONSE DESCRIPTOR.enum_types_by_name['RobotStatus'] = _ROBOTSTATUS +DESCRIPTOR.enum_types_by_name['UnexpectedMovementType'] = _UNEXPECTEDMOVEMENTTYPE +DESCRIPTOR.enum_types_by_name['UnexpectedMovementSide'] = _UNEXPECTEDMOVEMENTSIDE DESCRIPTOR.enum_types_by_name['FacialExpression'] = _FACIALEXPRESSION DESCRIPTOR.enum_types_by_name['FaceEnrollmentResult'] = _FACEENROLLMENTRESULT DESCRIPTOR.enum_types_by_name['BehaviorResults'] = _BEHAVIORRESULTS @@ -7625,6 +8344,27 @@ )) _sym_db.RegisterMessage(CladRect) +RobotObservedMotion = _reflection.GeneratedProtocolMessageType('RobotObservedMotion', (_message.Message,), dict( + DESCRIPTOR = _ROBOTOBSERVEDMOTION, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.RobotObservedMotion) + )) +_sym_db.RegisterMessage(RobotObservedMotion) + +RobotErasedEnrolledFace = _reflection.GeneratedProtocolMessageType('RobotErasedEnrolledFace', (_message.Message,), dict( + DESCRIPTOR = _ROBOTERASEDENROLLEDFACE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.RobotErasedEnrolledFace) + )) +_sym_db.RegisterMessage(RobotErasedEnrolledFace) + +UnexpectedMovement = _reflection.GeneratedProtocolMessageType('UnexpectedMovement', (_message.Message,), dict( + DESCRIPTOR = _UNEXPECTEDMOVEMENT, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.UnexpectedMovement) + )) +_sym_db.RegisterMessage(UnexpectedMovement) + RobotObservedFace = _reflection.GeneratedProtocolMessageType('RobotObservedFace', (_message.Message,), dict( DESCRIPTOR = _ROBOTOBSERVEDFACE, __module__ = 'anki_vector.messaging.messages_pb2' @@ -7744,6 +8484,20 @@ )) _sym_db.RegisterMessage(SetFaceToEnrollResponse) +EnrollFaceRequest = _reflection.GeneratedProtocolMessageType('EnrollFaceRequest', (_message.Message,), dict( + DESCRIPTOR = _ENROLLFACEREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.EnrollFaceRequest) + )) +_sym_db.RegisterMessage(EnrollFaceRequest) + +EnrollFaceResponse = _reflection.GeneratedProtocolMessageType('EnrollFaceResponse', (_message.Message,), dict( + DESCRIPTOR = _ENROLLFACERESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.EnrollFaceResponse) + )) +_sym_db.RegisterMessage(EnrollFaceResponse) + DriveOffChargerRequest = _reflection.GeneratedProtocolMessageType('DriveOffChargerRequest', (_message.Message,), dict( DESCRIPTOR = _DRIVEOFFCHARGERREQUEST, __module__ = 'anki_vector.messaging.messages_pb2' @@ -7947,6 +8701,20 @@ )) _sym_db.RegisterMessage(CancelActionByIdTagResponse) +CancelBehaviorRequest = _reflection.GeneratedProtocolMessageType('CancelBehaviorRequest', (_message.Message,), dict( + DESCRIPTOR = _CANCELBEHAVIORREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.CancelBehaviorRequest) + )) +_sym_db.RegisterMessage(CancelBehaviorRequest) + +CancelBehaviorResponse = _reflection.GeneratedProtocolMessageType('CancelBehaviorResponse', (_message.Message,), dict( + DESCRIPTOR = _CANCELBEHAVIORRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.CancelBehaviorResponse) + )) +_sym_db.RegisterMessage(CancelBehaviorResponse) + GoToPoseRequest = _reflection.GeneratedProtocolMessageType('GoToPoseRequest', (_message.Message,), dict( DESCRIPTOR = _GOTOPOSEREQUEST, __module__ = 'anki_vector.messaging.messages_pb2' @@ -8430,6 +9198,41 @@ )) _sym_db.RegisterMessage(SetEyeColorResponse) +CameraConfigRequest = _reflection.GeneratedProtocolMessageType('CameraConfigRequest', (_message.Message,), dict( + DESCRIPTOR = _CAMERACONFIGREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.CameraConfigRequest) + )) +_sym_db.RegisterMessage(CameraConfigRequest) + +CameraConfigResponse = _reflection.GeneratedProtocolMessageType('CameraConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _CAMERACONFIGRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.CameraConfigResponse) + )) +_sym_db.RegisterMessage(CameraConfigResponse) + +SetCameraSettingsRequest = _reflection.GeneratedProtocolMessageType('SetCameraSettingsRequest', (_message.Message,), dict( + DESCRIPTOR = _SETCAMERASETTINGSREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.SetCameraSettingsRequest) + )) +_sym_db.RegisterMessage(SetCameraSettingsRequest) + +SetCameraSettingsResponse = _reflection.GeneratedProtocolMessageType('SetCameraSettingsResponse', (_message.Message,), dict( + DESCRIPTOR = _SETCAMERASETTINGSRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.SetCameraSettingsResponse) + )) +_sym_db.RegisterMessage(SetCameraSettingsResponse) + +CameraSettingsUpdate = _reflection.GeneratedProtocolMessageType('CameraSettingsUpdate', (_message.Message,), dict( + DESCRIPTOR = _CAMERASETTINGSUPDATE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.CameraSettingsUpdate) + )) +_sym_db.RegisterMessage(CameraSettingsUpdate) + SDKInitializationRequest = _reflection.GeneratedProtocolMessageType('SDKInitializationRequest', (_message.Message,), dict( DESCRIPTOR = _SDKINITIALIZATIONREQUEST, __module__ = 'anki_vector.messaging.messages_pb2' diff --git a/anki_vector/messaging/settings.proto b/anki_vector/messaging/settings.proto index 36fc97e..c7342ae 100644 --- a/anki_vector/messaging/settings.proto +++ b/anki_vector/messaging/settings.proto @@ -18,4 +18,159 @@ syntax = "proto3"; package Anki.Vector.external_interface; +import "anki_vector/messaging/response_status.proto"; +// Updated by wayne@codaris.com +message RobotSettingsConfig { + bool clock_24_hour = 1; + EyeColor eye_color = 2; + string default_location = 3; + bool dist_is_metric = 4; + string locale = 5; + Volume master_volume = 6; + bool temp_is_fahrenheit = 7; + string time_zone = 8; + ButtonWakeWord button_wakeword = 9; +} + + +message AccountSettingsConfig { + oneof oneof_data_collection { + bool data_collection = 1; + } + + oneof oneof_app_locale { + string app_locale = 2; + } +} + +message UserEntitlementsConfig { + oneof oneof_kickstarter_eyes { + bool kickstarter_eyes = 1; + } +} + +message Jdoc { + uint64 doc_version = 1; + uint64 fmt_version = 2; + string client_metadata = 3; + string json_doc = 4; +} + +message NamedJdoc { + JdocType jdoc_type = 1; + Jdoc doc = 2; +} + +message PullJdocsRequest { + repeated JdocType jdoc_types = 1; +} + +message PullJdocsResponse { + ResponseStatus status = 1; + repeated NamedJdoc named_jdocs = 2; +} + +message UpdateSettingsRequest { + RobotSettingsConfig settings = 1; +} + +message UpdateSettingsResponse { + ResponseStatus status = 1; + ResultCode code = 2; + Jdoc doc = 3; +} + +message UpdateAccountSettingsRequest { + AccountSettingsConfig account_settings = 1; +} + +message UpdateAccountSettingsResponse { + ResponseStatus status = 1; + ResultCode code = 2; + Jdoc doc = 3; +} + +message UpdateUserEntitlementsRequest { + UserEntitlementsConfig user_entitlements = 1; +} + +message UpdateUserEntitlementsResponse { + ResponseStatus status = 1; + ResultCode code = 2; + Jdoc doc = 3; +} + +message JdocsChanged { + repeated JdocType jdoc_types = 1; +} + +enum ApiVersion { + INVALID = 0; + LATEST = 1; +} + +enum Volume { + MUTE = 0; + LOW = 1; + MEDIUM_LOW = 2; + MEDIUM = 3; + MEDIUM_HIGH = 4; + HIGH = 5; +} + +enum JdocType { + ROBOT_SETTINGS = 0; + ROBOT_LIFETIME_STATS = 1; + ACCOUNT_SETTINGS = 2; + USER_ENTITLEMENTS = 3; +} + +enum JdocResolveMethod { + PUSH_TO_CLOUD = 0; + PULL_FROM_CLOUD = 1; +} + +// RobotSetting enum values are not in all caps for historical reasons. +// Changing that now would involve a format migration because we're now +// saving robot settings jdocs in the cloud. +enum RobotSetting { + clock_24_hour = 0; + eye_color = 1; + default_location = 2; + dist_is_metric = 3; + locale = 4; + master_volume = 5; + temp_is_fahrenheit = 6; + time_zone = 7; + button_wakeword = 8; +} + +enum EyeColor { + TIP_OVER_TEAL = 0; + OVERFIT_ORANGE = 1; + UNCANNY_YELLOW = 2; + NON_LINEAR_LIME = 3; + SINGULARITY_SAPPHIRE = 4; + FALSE_POSITIVE_PURPLE = 5; + CONFUSION_MATRIX_GREEN = 6; +} + +enum ButtonWakeWord { + BUTTON_WAKEWORD_HEY_VECTOR = 0; + BUTTON_WAKEWORD_ALEXA = 1; +} + +enum AccountSetting { + DATA_COLLECTION = 0; + APP_LOCALE = 1; +} + +enum UserEntitlement { + KICKSTARTER_EYES = 0; +} + +enum ResultCode { + SETTINGS_ACCEPTED = 0; + ERROR_UPDATE_IN_PROGRESS = 1; +} diff --git a/anki_vector/messaging/shared.proto b/anki_vector/messaging/shared.proto index d193d80..316f9eb 100644 --- a/anki_vector/messaging/shared.proto +++ b/anki_vector/messaging/shared.proto @@ -20,6 +20,7 @@ package Anki.Vector.external_interface; import "anki_vector/messaging/behavior.proto"; import "anki_vector/messaging/cube.proto"; +import "anki_vector/messaging/alexa.proto"; import "anki_vector/messaging/messages.proto"; import "anki_vector/messaging/settings.proto"; import "anki_vector/messaging/extensions.proto"; @@ -49,6 +50,7 @@ message Event { oneof event_type { TimeStampedStatus time_stamped_status = 1; WakeWord wake_word = 3; + AttentionTransfer attention_transfer = 4; RobotObservedFace robot_observed_face = 5; RobotChangedObservedFaceID robot_changed_observed_face_id = 6; ObjectEvent object_event = 7; @@ -56,12 +58,19 @@ message Event { PhotoTaken photo_taken = 9; RobotState robot_state = 10; CubeBattery cube_battery = 11; - // Used by Vector to verify the connection is still alive. - KeepAlivePing keep_alive = 12; + KeepAlivePing keep_alive = 12; // Used by Vector to verify the connection is still alive. ConnectionResponse connection_response = 13; + JdocsChanged jdocs_changed = 14; + AlexaAuthEvent alexa_auth_event = 15; MirrorModeDisabled mirror_mode_disabled = 16; VisionModesAutoDisabled vision_modes_auto_disabled = 17; + CheckUpdateStatusResponse check_update_status_response = 18; UserIntent user_intent = 19; + RobotObservedMotion robot_observed_motion = 20; + RobotErasedEnrolledFace robot_erased_enrolled_face = 21; + RobotRenamedEnrolledFace robot_renamed_enrolled_face = 22; + CameraSettingsUpdate camera_settings_update = 23; + UnexpectedMovement unexpected_movement = 24; } } diff --git a/anki_vector/messaging/shared_pb2.py b/anki_vector/messaging/shared_pb2.py index a713482..f175481 100644 --- a/anki_vector/messaging/shared_pb2.py +++ b/anki_vector/messaging/shared_pb2.py @@ -25,7 +25,7 @@ name='anki_vector/messaging/shared.proto', package='Anki.Vector.external_interface', syntax='proto3', - serialized_pb=_b('\n\"anki_vector/messaging/shared.proto\x12\x1e\x41nki.Vector.external_interface\x1a$anki_vector/messaging/behavior.proto\x1a anki_vector/messaging/cube.proto\x1a$anki_vector/messaging/messages.proto\x1a$anki_vector/messaging/settings.proto\x1a&anki_vector/messaging/extensions.proto\x1a+anki_vector/messaging/response_status.proto\"J\n\x16ProtocolVersionRequest\x12\x16\n\x0e\x63lient_version\x18\x01 \x01(\x03\x12\x18\n\x10min_host_version\x18\x02 \x01(\x03\"\xa7\x01\n\x17ProtocolVersionResponse\x12N\n\x06result\x18\x01 \x01(\x0e\x32>.Anki.Vector.external_interface.ProtocolVersionResponse.Result\x12\x14\n\x0chost_version\x18\x02 \x01(\x03\"&\n\x06Result\x12\x0f\n\x0bUNSUPPORTED\x10\x00\x12\x0b\n\x07SUCCESS\x10\x01\"h\n\x12\x43onnectionResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x12\n\nis_primary\x18\x02 \x01(\x08\"\xc9\x08\n\x05\x45vent\x12P\n\x13time_stamped_status\x18\x01 \x01(\x0b\x32\x31.Anki.Vector.external_interface.TimeStampedStatusH\x00\x12=\n\twake_word\x18\x03 \x01(\x0b\x32(.Anki.Vector.external_interface.WakeWordH\x00\x12P\n\x13robot_observed_face\x18\x05 \x01(\x0b\x32\x31.Anki.Vector.external_interface.RobotObservedFaceH\x00\x12\x64\n\x1erobot_changed_observed_face_id\x18\x06 \x01(\x0b\x32:.Anki.Vector.external_interface.RobotChangedObservedFaceIDH\x00\x12\x43\n\x0cobject_event\x18\x07 \x01(\x0b\x32+.Anki.Vector.external_interface.ObjectEventH\x00\x12K\n\x10stimulation_info\x18\x08 \x01(\x0b\x32/.Anki.Vector.external_interface.StimulationInfoH\x00\x12\x41\n\x0bphoto_taken\x18\t \x01(\x0b\x32*.Anki.Vector.external_interface.PhotoTakenH\x00\x12\x41\n\x0brobot_state\x18\n \x01(\x0b\x32*.Anki.Vector.external_interface.RobotStateH\x00\x12\x43\n\x0c\x63ube_battery\x18\x0b \x01(\x0b\x32+.Anki.Vector.external_interface.CubeBatteryH\x00\x12\x43\n\nkeep_alive\x18\x0c \x01(\x0b\x32-.Anki.Vector.external_interface.KeepAlivePingH\x00\x12Q\n\x13\x63onnection_response\x18\r \x01(\x0b\x32\x32.Anki.Vector.external_interface.ConnectionResponseH\x00\x12R\n\x14mirror_mode_disabled\x18\x10 \x01(\x0b\x32\x32.Anki.Vector.external_interface.MirrorModeDisabledH\x00\x12]\n\x1avision_modes_auto_disabled\x18\x11 \x01(\x0b\x32\x37.Anki.Vector.external_interface.VisionModesAutoDisabledH\x00\x12\x41\n\x0buser_intent\x18\x13 \x01(\x0b\x32*.Anki.Vector.external_interface.UserIntentH\x00\x42\x0c\n\nevent_type\"\x1a\n\nFilterList\x12\x0c\n\x04list\x18\x01 \x03(\t\"\xb6\x01\n\x0c\x45ventRequest\x12@\n\nwhite_list\x18\x01 \x01(\x0b\x32*.Anki.Vector.external_interface.FilterListH\x00\x12@\n\nblack_list\x18\x02 \x01(\x0b\x32*.Anki.Vector.external_interface.FilterListH\x00\x12\x15\n\rconnection_id\x18\x03 \x01(\tB\x0b\n\tlist_type\"\x8b\x01\n\rEventResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x34\n\x05\x65vent\x18\x02 \x01(\x0b\x32%.Anki.Vector.external_interface.Event:\x04\x80\xa6\x1d\x01\"I\n\x19UserAuthenticationRequest\x12\x17\n\x0fuser_session_id\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63lient_name\x18\x02 \x01(\x0c\"\xf0\x01\n\x1aUserAuthenticationResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12M\n\x04\x63ode\x18\x02 \x01(\x0e\x32?.Anki.Vector.external_interface.UserAuthenticationResponse.Code\x12\x19\n\x11\x63lient_token_guid\x18\x03 \x01(\x0c\"(\n\x04\x43ode\x12\x10\n\x0cUNAUTHORIZED\x10\x00\x12\x0e\n\nAUTHORIZED\x10\x01\x62\x06proto3') + serialized_pb=_b('\n\"anki_vector/messaging/shared.proto\x12\x1e\x41nki.Vector.external_interface\x1a$anki_vector/messaging/behavior.proto\x1a anki_vector/messaging/cube.proto\x1a$anki_vector/messaging/messages.proto\x1a$anki_vector/messaging/settings.proto\x1a&anki_vector/messaging/extensions.proto\x1a+anki_vector/messaging/response_status.proto\"J\n\x16ProtocolVersionRequest\x12\x16\n\x0e\x63lient_version\x18\x01 \x01(\x03\x12\x18\n\x10min_host_version\x18\x02 \x01(\x03\"\xa7\x01\n\x17ProtocolVersionResponse\x12N\n\x06result\x18\x01 \x01(\x0e\x32>.Anki.Vector.external_interface.ProtocolVersionResponse.Result\x12\x14\n\x0chost_version\x18\x02 \x01(\x03\"&\n\x06Result\x12\x0f\n\x0bUNSUPPORTED\x10\x00\x12\x0b\n\x07SUCCESS\x10\x01\"h\n\x12\x43onnectionResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x12\n\nis_primary\x18\x02 \x01(\x08\"\x8a\x0c\n\x05\x45vent\x12P\n\x13time_stamped_status\x18\x01 \x01(\x0b\x32\x31.Anki.Vector.external_interface.TimeStampedStatusH\x00\x12=\n\twake_word\x18\x03 \x01(\x0b\x32(.Anki.Vector.external_interface.WakeWordH\x00\x12P\n\x13robot_observed_face\x18\x05 \x01(\x0b\x32\x31.Anki.Vector.external_interface.RobotObservedFaceH\x00\x12\x64\n\x1erobot_changed_observed_face_id\x18\x06 \x01(\x0b\x32:.Anki.Vector.external_interface.RobotChangedObservedFaceIDH\x00\x12\x43\n\x0cobject_event\x18\x07 \x01(\x0b\x32+.Anki.Vector.external_interface.ObjectEventH\x00\x12K\n\x10stimulation_info\x18\x08 \x01(\x0b\x32/.Anki.Vector.external_interface.StimulationInfoH\x00\x12\x41\n\x0bphoto_taken\x18\t \x01(\x0b\x32*.Anki.Vector.external_interface.PhotoTakenH\x00\x12\x41\n\x0brobot_state\x18\n \x01(\x0b\x32*.Anki.Vector.external_interface.RobotStateH\x00\x12\x43\n\x0c\x63ube_battery\x18\x0b \x01(\x0b\x32+.Anki.Vector.external_interface.CubeBatteryH\x00\x12\x43\n\nkeep_alive\x18\x0c \x01(\x0b\x32-.Anki.Vector.external_interface.KeepAlivePingH\x00\x12Q\n\x13\x63onnection_response\x18\r \x01(\x0b\x32\x32.Anki.Vector.external_interface.ConnectionResponseH\x00\x12R\n\x14mirror_mode_disabled\x18\x10 \x01(\x0b\x32\x32.Anki.Vector.external_interface.MirrorModeDisabledH\x00\x12]\n\x1avision_modes_auto_disabled\x18\x11 \x01(\x0b\x32\x37.Anki.Vector.external_interface.VisionModesAutoDisabledH\x00\x12\x41\n\x0buser_intent\x18\x13 \x01(\x0b\x32*.Anki.Vector.external_interface.UserIntentH\x00\x12T\n\x15robot_observed_motion\x18\x14 \x01(\x0b\x32\x33.Anki.Vector.external_interface.RobotObservedMotionH\x00\x12]\n\x1arobot_erased_enrolled_face\x18\x15 \x01(\x0b\x32\x37.Anki.Vector.external_interface.RobotErasedEnrolledFaceH\x00\x12_\n\x1brobot_renamed_enrolled_face\x18\x16 \x01(\x0b\x32\x38.Anki.Vector.external_interface.RobotRenamedEnrolledFaceH\x00\x12V\n\x16\x63\x61mera_settings_update\x18\x17 \x01(\x0b\x32\x34.Anki.Vector.external_interface.CameraSettingsUpdateH\x00\x12Q\n\x13unexpected_movement\x18\x18 \x01(\x0b\x32\x32.Anki.Vector.external_interface.UnexpectedMovementH\x00\x42\x0c\n\nevent_type\"\x1a\n\nFilterList\x12\x0c\n\x04list\x18\x01 \x03(\t\"\xb6\x01\n\x0c\x45ventRequest\x12@\n\nwhite_list\x18\x01 \x01(\x0b\x32*.Anki.Vector.external_interface.FilterListH\x00\x12@\n\nblack_list\x18\x02 \x01(\x0b\x32*.Anki.Vector.external_interface.FilterListH\x00\x12\x15\n\rconnection_id\x18\x03 \x01(\tB\x0b\n\tlist_type\"\x8b\x01\n\rEventResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x34\n\x05\x65vent\x18\x02 \x01(\x0b\x32%.Anki.Vector.external_interface.Event:\x04\x80\xa6\x1d\x01\"I\n\x19UserAuthenticationRequest\x12\x17\n\x0fuser_session_id\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63lient_name\x18\x02 \x01(\x0c\"\xf0\x01\n\x1aUserAuthenticationResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12M\n\x04\x63ode\x18\x02 \x01(\x0e\x32?.Anki.Vector.external_interface.UserAuthenticationResponse.Code\x12\x19\n\x11\x63lient_token_guid\x18\x03 \x01(\x0c\"(\n\x04\x43ode\x12\x10\n\x0cUNAUTHORIZED\x10\x00\x12\x0e\n\nAUTHORIZED\x10\x01\x62\x06proto3') , dependencies=[anki__vector_dot_messaging_dot_behavior__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_cube__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_messages__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_settings__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_extensions__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_response__status__pb2.DESCRIPTOR,]) @@ -70,8 +70,8 @@ ], containing_type=None, options=None, - serialized_start=2386, - serialized_end=2426, + serialized_start=2835, + serialized_end=2875, ) _sym_db.RegisterEnumDescriptor(_USERAUTHENTICATIONRESPONSE_CODE) @@ -296,6 +296,41 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='robot_observed_motion', full_name='Anki.Vector.external_interface.Event.robot_observed_motion', index=14, + number=20, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='robot_erased_enrolled_face', full_name='Anki.Vector.external_interface.Event.robot_erased_enrolled_face', index=15, + number=21, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='robot_renamed_enrolled_face', full_name='Anki.Vector.external_interface.Event.robot_renamed_enrolled_face', index=16, + number=22, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='camera_settings_update', full_name='Anki.Vector.external_interface.Event.camera_settings_update', index=17, + number=23, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='unexpected_movement', full_name='Anki.Vector.external_interface.Event.unexpected_movement', index=18, + number=24, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -312,7 +347,7 @@ index=0, containing_type=None, fields=[]), ], serialized_start=656, - serialized_end=1753, + serialized_end=2202, ) @@ -342,8 +377,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1755, - serialized_end=1781, + serialized_start=2204, + serialized_end=2230, ) @@ -390,8 +425,8 @@ name='list_type', full_name='Anki.Vector.external_interface.EventRequest.list_type', index=0, containing_type=None, fields=[]), ], - serialized_start=1784, - serialized_end=1966, + serialized_start=2233, + serialized_end=2415, ) @@ -428,8 +463,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1969, - serialized_end=2108, + serialized_start=2418, + serialized_end=2557, ) @@ -466,8 +501,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2110, - serialized_end=2183, + serialized_start=2559, + serialized_end=2632, ) @@ -512,8 +547,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2186, - serialized_end=2426, + serialized_start=2635, + serialized_end=2875, ) _PROTOCOLVERSIONRESPONSE.fields_by_name['result'].enum_type = _PROTOCOLVERSIONRESPONSE_RESULT @@ -533,6 +568,11 @@ _EVENT.fields_by_name['mirror_mode_disabled'].message_type = anki__vector_dot_messaging_dot_messages__pb2._MIRRORMODEDISABLED _EVENT.fields_by_name['vision_modes_auto_disabled'].message_type = anki__vector_dot_messaging_dot_messages__pb2._VISIONMODESAUTODISABLED _EVENT.fields_by_name['user_intent'].message_type = anki__vector_dot_messaging_dot_messages__pb2._USERINTENT +_EVENT.fields_by_name['robot_observed_motion'].message_type = anki__vector_dot_messaging_dot_messages__pb2._ROBOTOBSERVEDMOTION +_EVENT.fields_by_name['robot_erased_enrolled_face'].message_type = anki__vector_dot_messaging_dot_messages__pb2._ROBOTERASEDENROLLEDFACE +_EVENT.fields_by_name['robot_renamed_enrolled_face'].message_type = anki__vector_dot_messaging_dot_messages__pb2._ROBOTRENAMEDENROLLEDFACE +_EVENT.fields_by_name['camera_settings_update'].message_type = anki__vector_dot_messaging_dot_messages__pb2._CAMERASETTINGSUPDATE +_EVENT.fields_by_name['unexpected_movement'].message_type = anki__vector_dot_messaging_dot_messages__pb2._UNEXPECTEDMOVEMENT _EVENT.oneofs_by_name['event_type'].fields.append( _EVENT.fields_by_name['time_stamped_status']) _EVENT.fields_by_name['time_stamped_status'].containing_oneof = _EVENT.oneofs_by_name['event_type'] @@ -575,6 +615,21 @@ _EVENT.oneofs_by_name['event_type'].fields.append( _EVENT.fields_by_name['user_intent']) _EVENT.fields_by_name['user_intent'].containing_oneof = _EVENT.oneofs_by_name['event_type'] +_EVENT.oneofs_by_name['event_type'].fields.append( + _EVENT.fields_by_name['robot_observed_motion']) +_EVENT.fields_by_name['robot_observed_motion'].containing_oneof = _EVENT.oneofs_by_name['event_type'] +_EVENT.oneofs_by_name['event_type'].fields.append( + _EVENT.fields_by_name['robot_erased_enrolled_face']) +_EVENT.fields_by_name['robot_erased_enrolled_face'].containing_oneof = _EVENT.oneofs_by_name['event_type'] +_EVENT.oneofs_by_name['event_type'].fields.append( + _EVENT.fields_by_name['robot_renamed_enrolled_face']) +_EVENT.fields_by_name['robot_renamed_enrolled_face'].containing_oneof = _EVENT.oneofs_by_name['event_type'] +_EVENT.oneofs_by_name['event_type'].fields.append( + _EVENT.fields_by_name['camera_settings_update']) +_EVENT.fields_by_name['camera_settings_update'].containing_oneof = _EVENT.oneofs_by_name['event_type'] +_EVENT.oneofs_by_name['event_type'].fields.append( + _EVENT.fields_by_name['unexpected_movement']) +_EVENT.fields_by_name['unexpected_movement'].containing_oneof = _EVENT.oneofs_by_name['event_type'] _EVENTREQUEST.fields_by_name['white_list'].message_type = _FILTERLIST _EVENTREQUEST.fields_by_name['black_list'].message_type = _FILTERLIST _EVENTREQUEST.oneofs_by_name['list_type'].fields.append( diff --git a/anki_vector/objects.py b/anki_vector/objects.py index cf44929..43bf963 100644 --- a/anki_vector/objects.py +++ b/anki_vector/objects.py @@ -207,7 +207,7 @@ def __init__(self, obj, move_duration: float): class ObservableObject(util.Component): - """Represents any object Vector can see in the world.""" + """The base type for anything Vector can see.""" visibility_timeout = OBJECT_VISIBILITY_TIMEOUT @@ -324,6 +324,18 @@ def is_visible(self) -> bool: def _repr_values(self): # pylint: disable=no-self-use return '' + def _dispatch_observed_event(self, image_rect): + # Override in subclass if there is a specific event for that type + self.conn.run_soon(self._robot.events.dispatch_event(EvtObjectObserved(self, image_rect, self._pose), Events.object_observed)) + + def _dispatch_appeared_event(self, image_rect): + # Override in subclass if there is a specific event for that type + self.conn.run_soon(self._robot.events.dispatch_event(EvtObjectAppeared(self, image_rect, self._pose), Events.object_appeared)) + + def _dispatch_disappeared_event(self): + # Override in subclass if there is a specific event for that type + self.conn.run_soon(self._robot.events.dispatch_event(EvtObjectDisappeared(self), Events.object_disappeared)) + def _reset_observed_timeout_handler(self): if self._observed_timeout_handler is not None: self._observed_timeout_handler.cancel() @@ -333,7 +345,7 @@ def _observed_timeout(self): # Triggered when the element is no longer considered "visible". # i.e. visibility_timeout seconds after the last observed event. self._is_visible = False - self.conn.run_soon(self._robot.events.dispatch_event(EvtObjectDisappeared(self), Events.object_disappeared)) + self._dispatch_disappeared_event() def _on_observed(self, pose: util.Pose, image_rect: util.ImageRect, robot_timestamp: int): # Called from subclasses on their corresponding observed messages. @@ -347,10 +359,10 @@ def _on_observed(self, pose: util.Pose, image_rect: util.ImageRect, robot_timest self._last_observed_image_rect = image_rect self._pose = pose self._reset_observed_timeout_handler() - self.conn.run_soon(self._robot.events.dispatch_event(EvtObjectObserved(self, image_rect, pose), Events.object_observed)) + self._dispatch_observed_event(image_rect) if newly_visible: - self.conn.run_soon(self._robot.events.dispatch_event(EvtObjectAppeared(self, image_rect, pose), Events.object_appeared)) + self._dispatch_appeared_event(image_rect) #: LIGHT_CUBE_1_TYPE's markers look like 2 concentric circles with lines and gaps. @@ -481,6 +493,7 @@ def teardown(self): self.robot.events.unsubscribe(self._on_object_connection_lost, Events.cube_connection_lost) + # TODO: add return type hint @connection.on_connection_thread() async def set_light_corners(self, light1: lights.Light, diff --git a/anki_vector/opengl/__init__.py b/anki_vector/opengl/__init__.py index bc9c542..f4eb8f5 100644 --- a/anki_vector/opengl/__init__.py +++ b/anki_vector/opengl/__init__.py @@ -24,9 +24,9 @@ This package requires Python to have the PyOpenGL package installed, along with an implementation of GLUT (OpenGL Utility Toolkit). - To install the Python packages on Mac and Linux do ``python3 -m pip install --user "anki_vector[3dviewer]"`` + To install the Python packages on Mac and Linux do ``python3 -m pip install --user "cyb3r_vector_sdk[3dviewer]"`` - To install the Python packages on Windows do ``py -3 -m pip install --user "anki_vector[3dviewer]"`` + To install the Python packages on Windows do ``py -3 -m pip install --user "cyb3r_vector_sdk[3dviewer]"`` On Windows and Linux you must also install freeglut (macOS / OSX has one preinstalled). diff --git a/anki_vector/opengl/opengl.py b/anki_vector/opengl/opengl.py index b387e07..2643b51 100644 --- a/anki_vector/opengl/opengl.py +++ b/anki_vector/opengl/opengl.py @@ -21,9 +21,9 @@ This package requires Python to have the PyOpenGL package installed, along with an implementation of GLUT (OpenGL Utility Toolkit). - To install the Python packages on Mac and Linux do ``python3 -m pip install --user "anki_vector[3dviewer]"`` + To install the Python packages on Mac and Linux do ``python3 -m pip install --user "cyb3r_vector_sdk[3dviewer]"`` - To install the Python packages on Windows do ``py -3 -m pip install --user "anki_vector[3dviewer]"`` + To install the Python packages on Windows do ``py -3 -m pip install --user "cyb3r_vector_sdk[3dviewer]"`` On Windows and Linux you must also install freeglut (macOS / OSX has one preinstalled). @@ -62,7 +62,7 @@ def raise_opengl_or_pillow_import_error(opengl_import_exc): % opengl_import_exc) raise NotImplementedError('OpenGL is not available; ' 'make sure the PyOpenGL and Pillow packages are installed:\n' - 'Do `pip3 install --user "anki_vector[3dviewer]"` to install. Error: %s' % opengl_import_exc) + 'Do `pip3 install --user "cyb3r_vector_sdk[3dviewer]"` to install. Error: %s' % opengl_import_exc) try: diff --git a/anki_vector/opengl/opengl_vector.py b/anki_vector/opengl/opengl_vector.py index 702cb27..180e1bc 100644 --- a/anki_vector/opengl/opengl_vector.py +++ b/anki_vector/opengl/opengl_vector.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Anki, Inc. +py# Copyright (c) 2018 Anki, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,9 +18,9 @@ This package requires Python to have the PyOpenGL package installed, along with an implementation of GLUT (OpenGL Utility Toolkit). - To install the Python packages on Mac and Linux do ``python3 -m pip install --user "anki_vector[3dviewer]"`` + To install the Python packages on Mac and Linux do ``python3 -m pip install --user "cyb3r_vector_sdk[3dviewer]"`` - To install the Python packages on Windows do ``py -3 -m pip install --user "anki_vector[3dviewer]"`` + To install the Python packages on Windows do ``py -3 -m pip install --user "cyb3r_vector_sdk[3dviewer]"`` On Windows and Linux you must also install freeglut (macOS / OSX has one preinstalled). diff --git a/anki_vector/opengl/opengl_viewer.py b/anki_vector/opengl/opengl_viewer.py index e18f9b1..8274c6a 100644 --- a/anki_vector/opengl/opengl_viewer.py +++ b/anki_vector/opengl/opengl_viewer.py @@ -35,9 +35,9 @@ This package requires Python to have the PyOpenGL package installed, along with an implementation of GLUT (OpenGL Utility Toolkit). - To install the Python packages on Mac and Linux do ``python3 -m pip install --user "anki_vector[3dviewer]"`` + To install the Python packages on Mac and Linux do ``python3 -m pip install --user "cyb3r_vector_sdk[3dviewer]"`` - To install the Python packages on Windows do ``py -3 -m pip install --user "anki_vector[3dviewer]"`` + To install the Python packages on Windows do ``py -3 -m pip install --user "cyb3r_vector_sdk[3dviewer]"`` On Windows and Linux you must also install freeglut (macOS / OSX has one preinstalled). diff --git a/anki_vector/robot.py b/anki_vector/robot.py index 77ccae8..5a9724c 100755 --- a/anki_vector/robot.py +++ b/anki_vector/robot.py @@ -1,917 +1,933 @@ -# Copyright (c) 2018 Anki, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License in the file LICENSE.txt or at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -This contains the :class:`Robot` and :class:`AsyncRobot` classes for managing Vector. - -:class:`Robot` will run all behaviors in sequence and directly return the results. - -:class:`AsyncRobot` will instead provide a :class:`concurrent.futures.Future` which the -caller may use to obtain the result when they desire. -""" - -# __all__ should order by constants, event classes, other classes, functions. -__all__ = ['Robot', 'AsyncRobot'] - -import concurrent -import functools - -from . import (animation, audio, behavior, camera, - events, faces, motors, nav_map, screen, - photos, proximity, status, touch, - util, viewer, vision, world) -from .connection import (Connection, - on_connection_thread, - ControlPriorityLevel) -from .exceptions import (VectorNotReadyException, - VectorPropertyValueNotReadyException, - VectorUnreliableEventStreamException) -from .viewer import (ViewerComponent, Viewer3DComponent) -from .messaging import protocol -from .mdns import VectorMdns - - -class Robot: - """The Robot object is responsible for managing the state and connections - to a Vector, and is typically the entry-point to running the sdk. - - The majority of the robot will not work until it is properly connected - to Vector. There are two ways to get connected: - - 1. Using :code:`with`: it works just like opening a file, and will close when - the :code:`with` block's indentation ends. - - - .. testcode:: - - import anki_vector - - # Create the robot connection - with anki_vector.Robot() as robot: - # Run your commands - robot.anim.play_animation_trigger("GreetAfterLongTime") - - 2. Using :func:`connect` and :func:`disconnect` to explicitly open and close the connection: - it allows the robot's connection to continue in the context in which it started. - - .. testcode:: - - import anki_vector - - # Create a Robot object - robot = anki_vector.Robot() - # Connect to the Robot - robot.connect() - # Run your commands - robot.anim.play_animation_trigger("GreetAfterLongTime") - # Disconnect from Vector - robot.disconnect() - - :param serial: Vector's serial number. The robot's serial number (ex. 00e20100) is located on the underside of Vector, - or accessible from Vector's debug screen. Used to identify which Vector configuration to load. - :param ip: Vector's IP address. (optional) - :param name: Vector's name (in format :code:`"Vector-XXXX"`) to be used for mDNS discovery. If a Vector with the given name - is discovered, the :code:`ip` parameter (and config field) will be overridden. - :param config: A custom :class:`dict` to override values in Vector's configuration. (optional) - Example: :code:`{"cert": "/path/to/file.cert", "name": "Vector-XXXX", "guid": ""}` - where :code:`cert` is the certificate to identify Vector, :code:`name` is the name on Vector's face - when his backpack is double-clicked on the charger, and :code:`guid` is the authorization token - that identifies the SDK user. Note: Never share your authentication credentials with anyone. - :param default_logging: Toggle default logging. - :param behavior_activation_timeout: The time to wait for control of the robot before failing. - :param cache_animation_lists: Get the list of animation triggers and animations available at startup. - :param enable_face_detection: Turn on face detection. - :param estimate_facial_expression: Turn estimating facial expression on/off. Enabling :code:`estimate_facial_expression` - returns a facial expression, the expression values and the :class:`anki_vector.util.ImageRect` - for observed face regions (eyes, nose, and mouth) as part of the :code:`RobotObservedFace` event. - It is turned off by default as the number of :code:`RobotObservedFace` events - are reduced due to the increased processing time. - :param enable_audio_feed: Turn audio feed on/off. - :param enable_custom_object_detection: Turn custom object detection on/off. - :param enable_nav_map_feed: Turn navigation map feed on/off. - :param show_viewer: Specifies whether to display a view of Vector's camera in a window. - :param show_3d_viewer: Specifies whether to display a 3D view of Vector's understanding of the world in a window. - :param behavior_control_level: Request control of Vector's behavior system at a specific level of control. Pass - :code:`None` if behavior control is not needed. - See :class:`ControlPriorityLevel` for more information.""" - - def __init__(self, - serial: str = None, - ip: str = None, - name: str = None, - config: dict = None, - default_logging: bool = True, - behavior_activation_timeout: int = 10, - cache_animation_lists: bool = True, - enable_face_detection: bool = False, - estimate_facial_expression: bool = False, - enable_audio_feed: bool = False, - enable_custom_object_detection: bool = False, - enable_nav_map_feed: bool = None, - show_viewer: bool = False, - show_3d_viewer: bool = False, - behavior_control_level: ControlPriorityLevel = ControlPriorityLevel.DEFAULT_PRIORITY): - if default_logging: - util.setup_basic_logging() - self.logger = util.get_class_logger(__name__, self) - self._force_async = False - config = config if config is not None else {} - config = {**util.read_configuration(serial, name, self.logger), **config} - - if name is not None: - vector_mdns = VectorMdns.find_vector(name) - - if vector_mdns is not None: - ip = vector_mdns['ipv4'] - - self._name = config["name"] - self._ip = ip if ip is not None else config["ip"] - self._cert_file = config["cert"] - self._guid = config["guid"] - - self._port = "443" - if 'port' in config: - self._port = config["port"] - - if self._name is None or self._ip is None or self._cert_file is None or self._guid is None: - raise ValueError("The Robot object requires a serial and for Vector to be logged in (using the app then running the anki_vector.configure executable submodule).\n" - "You may also provide the values necessary for connection through the config parameter. ex: " - '{"name":"Vector-XXXX", "ip":"XX.XX.XX.XX", "cert":"/path/to/cert_file", "guid":""}') - - #: :class:`anki_vector.connection.Connection`: The active connection to the robot. - self._conn = Connection(self._name, ':'.join([self._ip, self._port]), self._cert_file, self._guid, behavior_control_level=behavior_control_level) - self._events = events.EventHandler(self) - - # placeholders for components before they exist - self._anim: animation.AnimationComponent = None - self._audio: audio.AudioComponent = None - self._behavior: behavior.BehaviorComponent = None - self._camera: camera.CameraComponent = None - self._faces: faces.FaceComponent = None - self._motors: motors.MotorComponent = None - self._nav_map: nav_map.NavMapComponent = None - self._screen: screen.ScreenComponent = None - self._photos: photos.PhotographComponent = None - self._proximity: proximity.ProximityComponent = None - self._touch: touch.TouchComponent = None - self._viewer: viewer.ViewerComponent = None - self._viewer_3d: viewer.Viewer3DComponent = None - self._vision: vision.VisionComponent = None - self._world: world.World = None - - self.behavior_activation_timeout = behavior_activation_timeout - self.enable_face_detection = enable_face_detection - self.estimate_facial_expression = estimate_facial_expression - self.enable_custom_object_detection = enable_custom_object_detection - self.cache_animation_lists = cache_animation_lists - - # Robot state/sensor data - self._pose: util.Pose = None - self._pose_angle_rad: float = None - self._pose_pitch_rad: float = None - self._left_wheel_speed_mmps: float = None - self._right_wheel_speed_mmps: float = None - self._head_angle_rad: float = None - self._lift_height_mm: float = None - self._accel: util.Vector3 = None - self._gyro: util.Vector3 = None - self._carrying_object_id: float = None - self._head_tracking_object_id: float = None - self._localized_to_object_id: float = None - self._last_image_time_stamp: float = None - self._status: status.RobotStatus = status.RobotStatus() - - self._enable_audio_feed = enable_audio_feed - if enable_nav_map_feed is not None: - self._enable_nav_map_feed = enable_nav_map_feed - else: - self._enable_nav_map_feed = False - self._show_viewer = show_viewer - self._show_3d_viewer = show_3d_viewer - if show_3d_viewer and enable_nav_map_feed is None: - self.logger.warning("enable_nav_map_feed should be True for 3d viewer to render correctly.") - self._enable_nav_map_feed = True - - @property - def force_async(self) -> bool: - """A flag used to determine if this is a :class:`Robot` or :class:`AsyncRobot`.""" - return self._force_async - - @property - def conn(self) -> Connection: - """A reference to the :class:`~anki_vector.connection.Connection` instance.""" - return self._conn - - @property - def events(self) -> events.EventHandler: - """A reference to the :class:`~anki_vector.events.EventHandler` instance.""" - return self._events - - @property - def anim(self) -> animation.AnimationComponent: - """A reference to the :class:`~anki_vector.animation.AnimationComponent` instance.""" - if self._anim is None: - raise VectorNotReadyException("AnimationComponent is not yet initialized") - return self._anim - - @property - def audio(self) -> audio.AudioComponent: - """The audio instance used to control Vector's microphone feed and speaker playback.""" - - if self._audio is None: - raise VectorNotReadyException("AudioComponent is not yet initialized") - return self._audio - - @property - def behavior(self) -> behavior.BehaviorComponent: - """A reference to the :class:`~anki_vector.behavior.BehaviorComponent` instance.""" - return self._behavior - - @property - def camera(self) -> camera.CameraComponent: - """The :class:`~anki_vector.camera.CameraComponent` instance used to control Vector's camera feed. - - .. testcode:: - - import anki_vector - - with anki_vector.Robot() as robot: - robot.camera.init_camera_feed() - image = robot.camera.latest_image - image.raw_image.show() - """ - if self._camera is None: - raise VectorNotReadyException("CameraComponent is not yet initialized") - return self._camera - - @property - def faces(self) -> faces.FaceComponent: - """A reference to the :class:`~anki_vector.faces.FaceComponent` instance.""" - if self._faces is None: - raise VectorNotReadyException("FaceComponent is not yet initialized") - return self._faces - - @property - def motors(self) -> motors.MotorComponent: - """A reference to the :class:`~anki_vector.motors.MotorComponent` instance.""" - if self._motors is None: - raise VectorNotReadyException("MotorComponent is not yet initialized") - return self._motors - - @property - def nav_map(self) -> nav_map.NavMapComponent: - """A reference to the :class:`~anki_vector.nav_map.NavMapComponent` instance.""" - if self._nav_map is None: - raise VectorNotReadyException("NavMapComponent is not yet initialized") - return self._nav_map - - @property - def screen(self) -> screen.ScreenComponent: - """A reference to the :class:`~anki_vector.screen.ScreenComponent` instance.""" - if self._screen is None: - raise VectorNotReadyException("ScreenComponent is not yet initialized") - return self._screen - - @property - def photos(self) -> photos.PhotographComponent: - """A reference to the :class:`~anki_vector.photos.PhotographComponent` instance.""" - if self._photos is None: - raise VectorNotReadyException("PhotographyComponent is not yet initialized") - return self._photos - - @property - def proximity(self) -> proximity.ProximityComponent: - """:class:`~anki_vector.proximity.ProximityComponent` containing state related to object proximity detection. - - .. code-block:: python - - import anki_vector - with anki_vector.Robot() as robot: - proximity_data = robot.proximity.last_sensor_reading - if proximity_data is not None: - print(proximity_data.distance) - """ - return self._proximity - - @property - def touch(self) -> touch.TouchComponent: - """:class:`~anki_vector.touch.TouchComponent` containing state related to object touch detection. - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - print('Robot is being touched: {0}'.format(robot.touch.last_sensor_reading.is_being_touched)) - """ - return self._touch - - @property - def viewer(self) -> ViewerComponent: - """The :class:`~anki_vector.viewer.ViewerComponent` instance used to render Vector's camera feed. - - .. testcode:: - - import time - - import anki_vector - - with anki_vector.Robot() as robot: - # Render video for 5 seconds - robot.viewer.show() - time.sleep(5) - - # Disable video render and camera feed for 5 seconds - robot.viewer.close() - """ - if self._viewer is None: - raise VectorNotReadyException("ViewerComponent is not yet initialized") - return self._viewer - - @property - def viewer_3d(self) -> Viewer3DComponent: - """The :class:`~anki_vector.viewer.Viewer3DComponent` instance used to render Vector's navigation map. - - .. testcode:: - - import time - - import anki_vector - - with anki_vector.Robot(show_3d_viewer=True, enable_nav_map_feed=True) as robot: - # Render 3D view of navigation map for 5 seconds - time.sleep(5) - """ - if self._viewer_3d is None: - raise VectorNotReadyException("Viewer3DComponent is not yet initialized") - return self._viewer_3d - - @property - def vision(self) -> vision.VisionComponent: - """:class:`~anki_vector.vision.VisionComponent` containing functionality related to vision based object detection. - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - robot.vision.enable_custom_object_detection() - """ - return self._vision - - @property - def world(self) -> world.World: - """A reference to the :class:`~anki_vector.world.World` instance, or None if the World is not yet initialized.""" - if self._world is None: - raise VectorNotReadyException("WorldComponent is not yet initialized") - return self._world - - @property - @util.block_while_none() - def pose(self) -> util.Pose: - """:class:`anki_vector.util.Pose`: The current pose (position and orientation) of Vector. - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_robot_pose = robot.pose - """ - return self._pose - - @property - @util.block_while_none() - def pose_angle_rad(self) -> float: - """Vector's pose angle (heading in X-Y plane). - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_pose_angle_rad = robot.pose_angle_rad - """ - return self._pose_angle_rad - - @property - @util.block_while_none() - def pose_pitch_rad(self) -> float: - """Vector's pose pitch (angle up/down). - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_pose_pitch_rad = robot.pose_pitch_rad - """ - return self._pose_pitch_rad - - @property - @util.block_while_none() - def left_wheel_speed_mmps(self) -> float: - """Vector's left wheel speed in mm/sec - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_left_wheel_speed_mmps = robot.left_wheel_speed_mmps - """ - return self._left_wheel_speed_mmps - - @property - @util.block_while_none() - def right_wheel_speed_mmps(self) -> float: - """Vector's right wheel speed in mm/sec - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_right_wheel_speed_mmps = robot.right_wheel_speed_mmps - """ - return self._right_wheel_speed_mmps - - @property - @util.block_while_none() - def head_angle_rad(self) -> float: - """Vector's head angle (up/down). - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_head_angle_rad = robot.head_angle_rad - """ - return self._head_angle_rad - - @property - @util.block_while_none() - def lift_height_mm(self) -> float: - """Height of Vector's lift from the ground. - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_lift_height_mm = robot.lift_height_mm - """ - return self._lift_height_mm - - @property - @util.block_while_none() - def accel(self) -> util.Vector3: - """:class:`anki_vector.util.Vector3`: The current accelerometer reading (x, y, z) - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_accel = robot.accel - """ - return self._accel - - @property - @util.block_while_none() - def gyro(self) -> util.Vector3: - """The current gyroscope reading (x, y, z) - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_gyro = robot.gyro - """ - return self._gyro - - @property - @util.block_while_none() - def carrying_object_id(self) -> int: - """The ID of the object currently being carried (-1 if none) - - .. testcode:: - - import anki_vector - from anki_vector.util import degrees - - # Set the robot so that he can see a cube. - with anki_vector.Robot() as robot: - robot.behavior.set_head_angle(degrees(0.0)) - robot.behavior.set_lift_height(0.0) - - robot.world.connect_cube() - - if robot.world.connected_light_cube: - robot.behavior.pickup_object(robot.world.connected_light_cube) - - print("carrying_object_id: ", robot.carrying_object_id) - """ - return self._carrying_object_id - - @property - @util.block_while_none() - def head_tracking_object_id(self) -> int: - """The ID of the object the head is tracking to (-1 if none) - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_head_tracking_object_id = robot.head_tracking_object_id - """ - return self._head_tracking_object_id - - @property - @util.block_while_none() - def localized_to_object_id(self) -> int: - """The ID of the object that the robot is localized to (-1 if none) - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_localized_to_object_id = robot.localized_to_object_id - """ - return self._localized_to_object_id - - # TODO Move to photos or somewhere else - @property - @util.block_while_none() - def last_image_time_stamp(self) -> int: - """The robot's timestamp for the last image seen. - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - current_last_image_time_stamp = robot.last_image_time_stamp - """ - return self._last_image_time_stamp - - @property - def status(self) -> status.RobotStatus: - """A property that exposes various status properties of the robot. - - This status provides a simple mechanism to, for example, detect if any - of Vector's motors are moving, determine if Vector is being held, or if - he is on the charger. The full list is available in the - :class:`RobotStatus ` class documentation. - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - if robot.status.is_being_held: - print("Vector is being held!") - else: - print("Vector is not being held.") - """ - return self._status - - @property - def enable_audio_feed(self) -> bool: - """The audio feed enabled/disabled - - :getter: Returns whether the audio feed is enabled - :setter: Enable/disable the audio feed - - .. code-block:: python - - import asyncio - import time - - import anki_vector - - with anki_vector.Robot(enable_audio_feed=True) as robot: - time.sleep(5) - robot.enable_audio_feed = False - time.sleep(5) - """ - # TODO When audio is ready, convert `.. code-block:: python` to `.. testcode::` - return self._enable_audio_feed - - @enable_audio_feed.setter - def enable_audio_feed(self, enable) -> None: - self._enable_audio_feed = enable - # TODO add audio feed enablement when ready - - # Unpack streamed data to robot's internal properties - def _unpack_robot_state(self, _robot, _event_type, msg): - self._pose = util.Pose(x=msg.pose.x, y=msg.pose.y, z=msg.pose.z, - q0=msg.pose.q0, q1=msg.pose.q1, - q2=msg.pose.q2, q3=msg.pose.q3, - origin_id=msg.pose.origin_id) - self._pose_angle_rad = msg.pose_angle_rad - self._pose_pitch_rad = msg.pose_pitch_rad - self._left_wheel_speed_mmps = msg.left_wheel_speed_mmps - self._right_wheel_speed_mmps = msg.right_wheel_speed_mmps - self._head_angle_rad = msg.head_angle_rad - self._lift_height_mm = msg.lift_height_mm - self._accel = util.Vector3(msg.accel.x, msg.accel.y, msg.accel.z) - self._gyro = util.Vector3(msg.gyro.x, msg.gyro.y, msg.gyro.z) - self._carrying_object_id = msg.carrying_object_id - self._head_tracking_object_id = msg.head_tracking_object_id - self._localized_to_object_id = msg.localized_to_object_id - self._last_image_time_stamp = msg.last_image_time_stamp - self._status.set(msg.status) - - def connect(self, timeout: int = 10) -> None: - """Start the connection to Vector. - - .. testcode:: - - import anki_vector - - robot = anki_vector.Robot() - robot.connect() - robot.anim.play_animation_trigger("GreetAfterLongTime") - robot.disconnect() - - :param timeout: The time to allow for a connection before a - :class:`anki_vector.exceptions.VectorTimeoutException` is raised. - """ - self.conn.connect(timeout=timeout) - self.events.start(self.conn) - - # Initialize components - self._anim = animation.AnimationComponent(self) - self._audio = audio.AudioComponent(self) - self._behavior = behavior.BehaviorComponent(self) - self._faces = faces.FaceComponent(self) - self._motors = motors.MotorComponent(self) - self._nav_map = nav_map.NavMapComponent(self) - self._screen = screen.ScreenComponent(self) - self._photos = photos.PhotographComponent(self) - self._proximity = proximity.ProximityComponent(self) - self._touch = touch.TouchComponent(self) - self._viewer = viewer.ViewerComponent(self) - self._viewer_3d = viewer.Viewer3DComponent(self) - self._vision = vision.VisionComponent(self) - self._world = world.World(self) - self._camera = camera.CameraComponent(self) - - if self.cache_animation_lists: - # Load animation triggers and animations so they are ready to play when requested - anim_request = self._anim.load_animation_list() - if isinstance(anim_request, concurrent.futures.Future): - anim_request.result() - anim_trigger_request = self._anim.load_animation_trigger_list() - if isinstance(anim_trigger_request, concurrent.futures.Future): - anim_trigger_request.result() - - # TODO enable audio feed when ready - - # Start rendering camera feed - if self._show_viewer: - self.camera.init_camera_feed() - self.viewer.show() - - if self._show_3d_viewer: - self.viewer_3d.show() - - if self._enable_nav_map_feed: - self.nav_map.init_nav_map_feed() - - # Enable face detection, to allow Vector to add faces to its world view - if self.conn.requires_behavior_control: - face_detection = self.vision.enable_face_detection(detect_faces=self.enable_face_detection, estimate_expression=self.estimate_facial_expression) - if isinstance(face_detection, concurrent.futures.Future): - face_detection.result() - object_detection = self.vision.enable_custom_object_detection(detect_custom_objects=self.enable_custom_object_detection) - if isinstance(object_detection, concurrent.futures.Future): - object_detection.result() - - # Subscribe to a callback that updates the robot's local properties - self.events.subscribe(self._unpack_robot_state, - events.Events.robot_state, - _on_connection_thread=True) - - # access the pose to prove it has gotten back from the event stream once - try: - if not self.pose: - pass - except VectorPropertyValueNotReadyException as e: - raise VectorUnreliableEventStreamException() from e - - def disconnect(self) -> None: - """Close the connection with Vector. - - .. testcode:: - - import anki_vector - robot = anki_vector.Robot() - robot.connect() - robot.anim.play_animation_trigger("GreetAfterLongTime") - robot.disconnect() - """ - if self.conn.requires_behavior_control: - self.vision.close() - - # Stop rendering video - self.viewer.close() - - # Stop rendering 3d video - self.viewer_3d.close() - - # Shutdown camera feed - self.camera.close_camera_feed() - - # TODO shutdown audio feed when available - - # Shutdown nav map feed - self.nav_map.close_nav_map_feed() - - # Close the world and cleanup its objects - self.world.close() - - self.proximity.close() - self.touch.close() - - self.events.close() - self.conn.close() - - def __enter__(self): - self.connect(self.behavior_activation_timeout) - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.disconnect() - - @on_connection_thread(requires_control=False) - async def get_battery_state(self) -> protocol.BatteryStateResponse: - """Check the current state of the robot and cube batteries. - - The robot is considered fully-charged above 4.1 volts. At 3.6V, the robot is approaching low charge. - - Robot battery level values are as follows: - - +-------+---------+---------------------------------------------------------------+ - | Value | Level | Description | - +=======+=========+===============================================================+ - | 1 | Low | 3.6V or less. If on charger, 4V or less. | - +-------+---------+---------------------------------------------------------------+ - | 2 | Nominal | Normal operating levels. | - +-------+---------+---------------------------------------------------------------+ - | 3 | Full | This state can only be achieved when Vector is on the charger | - +-------+---------+---------------------------------------------------------------+ - - Cube battery level values are shown below: - - +-------+---------+---------------------------------------------------------------+ - | Value | Level | Description | - +=======+=========+===============================================================+ - | 1 | Low | 1.1V or less. | - +-------+---------+---------------------------------------------------------------+ - | 2 | Normal | Normal operating levels. | - +-------+---------+---------------------------------------------------------------+ - - .. testcode:: - - import anki_vector - - with anki_vector.Robot() as robot: - print("Connecting to a cube...") - robot.world.connect_cube() - - battery_state = robot.get_battery_state() - if battery_state: - print("Robot battery voltage: {0}".format(battery_state.battery_volts)) - print("Robot battery Level: {0}".format(battery_state.battery_level)) - print("Robot battery is charging: {0}".format(battery_state.is_charging)) - print("Robot is on charger platform: {0}".format(battery_state.is_on_charger_platform)) - print("Robot suggested charger time: {0}".format(battery_state.suggested_charger_sec)) - print("Cube battery level: {0}".format(battery_state.cube_battery.level)) - print("Cube battery voltage: {0}".format(battery_state.cube_battery.battery_volts)) - print("Cube battery seconds since last reading: {0}".format(battery_state.cube_battery.time_since_last_reading_sec)) - print("Cube battery factory id: {0}".format(battery_state.cube_battery.factory_id)) - """ - get_battery_state_request = protocol.BatteryStateRequest() - return await self.conn.grpc_interface.BatteryState(get_battery_state_request) - - @on_connection_thread(requires_control=False) - async def get_version_state(self) -> protocol.VersionStateResponse: - """Get the versioning information for Vector, including Vector's os_version and engine_build_id. - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - version_state = robot.get_version_state() - if version_state: - print("Robot os_version: {0}".format(version_state.os_version)) - print("Robot engine_build_id: {0}".format(version_state.engine_build_id)) - """ - get_version_state_request = protocol.VersionStateRequest() - return await self.conn.grpc_interface.VersionState(get_version_state_request) - - -class AsyncRobot(Robot): - """The AsyncRobot object is just like the Robot object, but allows multiple commands - to be executed at the same time. To achieve this, all grpc function calls also - return a :class:`concurrent.futures.Future`. - - 1. Using :code:`with`: it works just like opening a file, and will close when - the :code:`with` block's indentation ends. - - .. testcode:: - - import anki_vector - from anki_vector.util import degrees - - # Create the robot connection - with anki_vector.AsyncRobot() as robot: - # Start saying text asynchronously - say_future = robot.behavior.say_text("Now is the time") - # Turn robot, wait for completion - turn_future = robot.behavior.turn_in_place(degrees(3*360)) - turn_future.result() - # Play greet animation trigger, wait for completion - greet_future = robot.anim.play_animation_trigger("GreetAfterLongTime") - greet_future.result() - # Make sure text has been spoken - say_future.result() - - 2. Using :func:`connect` and :func:`disconnect` to explicitly open and close the connection: - it allows the robot's connection to continue in the context in which it started. - - .. testcode:: - - import anki_vector - from anki_vector.util import degrees - - # Create a Robot object - robot = anki_vector.AsyncRobot() - # Connect to Vector - robot.connect() - # Start saying text asynchronously - say_future = robot.behavior.say_text("Now is the time") - # Turn robot, wait for completion - turn_future = robot.behavior.turn_in_place(degrees(3 * 360)) - turn_future.result() - # Play greet animation trigger, wait for completion - greet_future = robot.anim.play_animation_trigger("GreetAfterLongTime") - greet_future.result() - # Make sure text has been spoken - say_future.result() - # Disconnect from Vector - robot.disconnect() - - When getting callbacks from the event stream, it's important to understand that function calls - return a :class:`concurrent.futures.Future` and not an :class:`asyncio.Future`. This means any - async callback functions will need to use :func:`asyncio.wrap_future` to be able to await the - function's response. - - .. testcode:: - - import asyncio - import time - - import anki_vector - - async def callback(robot, event_type, event): - await asyncio.wrap_future(robot.anim.play_animation_trigger('GreetAfterLongTime')) - await asyncio.wrap_future(robot.behavior.set_head_angle(anki_vector.util.degrees(40))) - - if __name__ == "__main__": - args = anki_vector.util.parse_command_args() - with anki_vector.AsyncRobot(serial=args.serial, enable_face_detection=True) as robot: - robot.behavior.set_head_angle(anki_vector.util.degrees(40)) - robot.events.subscribe(callback, anki_vector.events.Events.robot_observed_face) - - # Waits 10 seconds. Show Vector your face. - time.sleep(10) - - :param serial: Vector's serial number. The robot's serial number (ex. 00e20100) is located on the underside of Vector, - or accessible from Vector's debug screen. Used to identify which Vector configuration to load. - :param ip: Vector's IP Address. (optional) - :param config: A custom :class:`dict` to override values in Vector's configuration. (optional) - Example: :code:`{"cert": "/path/to/file.cert", "name": "Vector-XXXX", "guid": ""}` - where :code:`cert` is the certificate to identify Vector, :code:`name` is the name on Vector's face - when his backpack is double-clicked on the charger, and :code:`guid` is the authorization token - that identifies the SDK user. Note: Never share your authentication credentials with anyone. - :param default_logging: Toggle default logging. - :param behavior_activation_timeout: The time to wait for control of the robot before failing. - :param cache_animation_lists: Get the list of animation triggers and animations available at startup. - :param enable_face_detection: Turn on face detection. - :param estimate_facial_expression: Turn estimating facial expression on/off. - :param enable_audio_feed: Turn audio feed on/off. - :param enable_custom_object_detection: Turn custom object detection on/off. - :param enable_nav_map_feed: Turn navigation map feed on/off. - :param show_viewer: Specifies whether to display a view of Vector's camera in a window. - :param show_3d_viewer: Specifies whether to display a 3D view of Vector's understanding of the world in a window. - :param behavior_control_level: Request control of Vector's behavior system at a specific level of control. Pass - :code:`None` if behavior control is not needed. - See :class:`ControlPriorityLevel` for more information.""" - - @functools.wraps(Robot.__init__) - def __init__(self, *args, **kwargs): - super(AsyncRobot, self).__init__(*args, **kwargs) - self._force_async = True +# Copyright (c) 2018 Anki, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License in the file LICENSE.txt or at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This contains the :class:`Robot` and :class:`AsyncRobot` classes for managing Vector. + +:class:`Robot` will run all behaviors in sequence and directly return the results. + +:class:`AsyncRobot` will instead provide a :class:`concurrent.futures.Future` which the +caller may use to obtain the result when they desire. +""" + +# __all__ should order by constants, event classes, other classes, functions. +__all__ = ['Robot', 'AsyncRobot'] + +import concurrent +import functools + +from . import (animation, audio, behavior, camera, + events, faces, motors, nav_map, screen, + photos, proximity, status, touch, + util, viewer, vision, world) +from .connection import (Connection, + on_connection_thread, + ControlPriorityLevel) +from .exceptions import (VectorNotReadyException, + VectorPropertyValueNotReadyException, + VectorUnreliableEventStreamException) +from .viewer import (ViewerComponent, Viewer3DComponent) +from .messaging import protocol +from .mdns import VectorMdns + + +class Robot: + """The Robot object is responsible for managing the state and connections + to a Vector, and is typically the entry-point to running the sdk. + + The majority of the robot will not work until it is properly connected + to Vector. There are two ways to get connected: + + 1. Using :code:`with`: it works just like opening a file, and will close when + the :code:`with` block's indentation ends. + + + .. testcode:: + + import anki_vector + + # Create the robot connection + with anki_vector.Robot() as robot: + # Run your commands + robot.anim.play_animation_trigger("GreetAfterLongTime") + + 2. Using :func:`connect` and :func:`disconnect` to explicitly open and close the connection: + it allows the robot's connection to continue in the context in which it started. + + .. testcode:: + + import anki_vector + + # Create a Robot object + robot = anki_vector.Robot() + # Connect to the Robot + robot.connect() + # Run your commands + robot.anim.play_animation_trigger("GreetAfterLongTime") + # Disconnect from Vector + robot.disconnect() + + :param serial: Vector's serial number. The robot's serial number (ex. 00e20100) is located on the underside of Vector, + or accessible from Vector's debug screen. Used to identify which Vector configuration to load. + :param ip: Vector's IP address. (optional) + :param name: Vector's name (in format :code:`"Vector-XXXX"`) to be used for mDNS discovery. If a Vector with the given name + is discovered, the :code:`ip` parameter (and config field) will be overridden. + :param config: A custom :class:`dict` to override values in Vector's configuration. (optional) + Example: :code:`{"cert": "/path/to/file.cert", "name": "Vector-XXXX", "guid": ""}` + where :code:`cert` is the certificate to identify Vector, :code:`name` is the name on Vector's face + when his backpack is double-clicked on the charger, and :code:`guid` is the authorization token + that identifies the SDK user. Note: Never share your authentication credentials with anyone. + :param default_logging: Toggle default logging. + :param behavior_activation_timeout: The time to wait for control of the robot before failing. + :param cache_animation_lists: Get the list of animation triggers and animations available at startup. + :param enable_face_detection: Turn on face detection. + :param estimate_facial_expression: Turn estimating facial expression on/off. Enabling :code:`estimate_facial_expression` + returns a facial expression, the expression values and the :class:`anki_vector.util.ImageRect` + for observed face regions (eyes, nose, and mouth) as part of the :code:`RobotObservedFace` event. + It is turned off by default as the number of :code:`RobotObservedFace` events + are reduced due to the increased processing time. + :param enable_audio_feed: Turn audio feed on/off. + :param enable_custom_object_detection: Turn custom object detection on/off. + :param enable_nav_map_feed: Turn navigation map feed on/off. + :param show_viewer: Specifies whether to display a view of Vector's camera in a window. + :param show_3d_viewer: Specifies whether to display a 3D view of Vector's understanding of the world in a window. + :param behavior_control_level: Request control of Vector's behavior system at a specific level of control. Pass + :code:`None` if behavior control is not needed. + See :class:`ControlPriorityLevel` for more information.""" + + def __init__(self, + serial: str = None, + ip: str = None, + name: str = None, + config: dict = None, + escape_pod: bool = None, + default_logging: bool = True, + behavior_activation_timeout: int = 10, + cache_animation_lists: bool = True, + enable_face_detection: bool = False, + estimate_facial_expression: bool = False, + enable_audio_feed: bool = False, + enable_custom_object_detection: bool = False, + enable_nav_map_feed: bool = None, + show_viewer: bool = False, + show_3d_viewer: bool = False, + behavior_control_level: ControlPriorityLevel = ControlPriorityLevel.DEFAULT_PRIORITY): + if default_logging: + util.setup_basic_logging() + self.logger = util.get_class_logger(__name__, self) + self._force_async = False + config = config if config is not None else {} + config = {**util.read_configuration(serial, name, self.logger, escape_pod or False), **config} + escape_pod = config.get("escape_pod", False) if escape_pod is None else escape_pod + + if name is not None: + vector_mdns = VectorMdns.find_vector(name) + + if vector_mdns is not None: + ip = vector_mdns['ipv4'] + + self._escape_pod = escape_pod + self._name = config["name"] if 'name' in config else None + self._cert_file = config["cert"] if 'cert' in config else None + self._guid = config["guid"] if 'guid' in config else None + self._port = config["port"] if 'port' in config else "443" + self._ip = ip or config.get("ip") + if self._ip is None and 'ip' in config: + self._ip = config["ip"] + + if (not escape_pod) and (self._name is None or self._ip is None or self._cert_file is None or self._guid is None): + raise ValueError("The Robot object requires a serial and for Vector to be logged in (using the app then running the `python3 -m anki_vector.configure`).\n" + "You may also provide the values necessary for connection through the config parameter. ex: " + '{"name":"Vector-XXXX", "ip":"XX.XX.XX.XX", "cert":"/path/to/cert_file", "guid":""}') + + if (escape_pod) and (self._ip is None): + raise ValueError('Could not find the sdk configuration file. Please run `python3 -m anki_vector.configure_pod` to set up your Vector for SDK usage.') + + #: :class:`anki_vector.connection.Connection`: The active connection to the robot. + self._conn = Connection(self._name, ':'.join([self._ip, self._port]), self._cert_file, self._guid, self._escape_pod, behavior_control_level=behavior_control_level) + self._events = events.EventHandler(self) + + # placeholders for components before they exist + self._anim: animation.AnimationComponent = None + self._audio: audio.AudioComponent = None + self._behavior: behavior.BehaviorComponent = None + self._camera: camera.CameraComponent = None + self._faces: faces.FaceComponent = None + self._motors: motors.MotorComponent = None + self._nav_map: nav_map.NavMapComponent = None + self._screen: screen.ScreenComponent = None + self._photos: photos.PhotographComponent = None + self._proximity: proximity.ProximityComponent = None + self._touch: touch.TouchComponent = None + self._viewer: viewer.ViewerComponent = None + self._viewer_3d: viewer.Viewer3DComponent = None + self._vision: vision.VisionComponent = None + self._world: world.World = None + + self.behavior_activation_timeout = behavior_activation_timeout + self.enable_face_detection = enable_face_detection + self.estimate_facial_expression = estimate_facial_expression + self.enable_custom_object_detection = enable_custom_object_detection + self.cache_animation_lists = cache_animation_lists + + # Robot state/sensor data + self._pose: util.Pose = None + self._pose_angle_rad: float = None + self._pose_pitch_rad: float = None + self._left_wheel_speed_mmps: float = None + self._right_wheel_speed_mmps: float = None + self._head_angle_rad: float = None + self._lift_height_mm: float = None + self._accel: util.Vector3 = None + self._gyro: util.Vector3 = None + self._carrying_object_id: float = None + self._head_tracking_object_id: float = None + self._localized_to_object_id: float = None + self._last_image_time_stamp: float = None + self._status: status.RobotStatus = status.RobotStatus() + + self._enable_audio_feed = enable_audio_feed + if enable_nav_map_feed is not None: + self._enable_nav_map_feed = enable_nav_map_feed + else: + self._enable_nav_map_feed = False + self._show_viewer = show_viewer + self._show_3d_viewer = show_3d_viewer + if show_3d_viewer and enable_nav_map_feed is None: + self.logger.warning("enable_nav_map_feed should be True for 3d viewer to render correctly.") + self._enable_nav_map_feed = True + + @property + def force_async(self) -> bool: + """A flag used to determine if this is a :class:`Robot` or :class:`AsyncRobot`.""" + return self._force_async + + @property + def conn(self) -> Connection: + """A reference to the :class:`~anki_vector.connection.Connection` instance.""" + return self._conn + + @property + def events(self) -> events.EventHandler: + """A reference to the :class:`~anki_vector.events.EventHandler` instance.""" + return self._events + + @property + def anim(self) -> animation.AnimationComponent: + """A reference to the :class:`~anki_vector.animation.AnimationComponent` instance.""" + if self._anim is None: + raise VectorNotReadyException("AnimationComponent is not yet initialized") + return self._anim + + @property + def audio(self) -> audio.AudioComponent: + """The audio instance used to control Vector's microphone feed and speaker playback.""" + + if self._audio is None: + raise VectorNotReadyException("AudioComponent is not yet initialized") + return self._audio + + @property + def behavior(self) -> behavior.BehaviorComponent: + """A reference to the :class:`~anki_vector.behavior.BehaviorComponent` instance.""" + return self._behavior + + @property + def camera(self) -> camera.CameraComponent: + """The :class:`~anki_vector.camera.CameraComponent` instance used to control Vector's camera feed. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + robot.camera.init_camera_feed() + image = robot.camera.latest_image + image.raw_image.show() + """ + if self._camera is None: + raise VectorNotReadyException("CameraComponent is not yet initialized") + return self._camera + + @property + def faces(self) -> faces.FaceComponent: + """A reference to the :class:`~anki_vector.faces.FaceComponent` instance.""" + if self._faces is None: + raise VectorNotReadyException("FaceComponent is not yet initialized") + return self._faces + + @property + def motors(self) -> motors.MotorComponent: + """A reference to the :class:`~anki_vector.motors.MotorComponent` instance.""" + if self._motors is None: + raise VectorNotReadyException("MotorComponent is not yet initialized") + return self._motors + + @property + def nav_map(self) -> nav_map.NavMapComponent: + """A reference to the :class:`~anki_vector.nav_map.NavMapComponent` instance.""" + if self._nav_map is None: + raise VectorNotReadyException("NavMapComponent is not yet initialized") + return self._nav_map + + @property + def screen(self) -> screen.ScreenComponent: + """A reference to the :class:`~anki_vector.screen.ScreenComponent` instance.""" + if self._screen is None: + raise VectorNotReadyException("ScreenComponent is not yet initialized") + return self._screen + + @property + def photos(self) -> photos.PhotographComponent: + """A reference to the :class:`~anki_vector.photos.PhotographComponent` instance.""" + if self._photos is None: + raise VectorNotReadyException("PhotographyComponent is not yet initialized") + return self._photos + + @property + def proximity(self) -> proximity.ProximityComponent: + """:class:`~anki_vector.proximity.ProximityComponent` containing state related to object proximity detection. + + .. code-block:: python + + import anki_vector + with anki_vector.Robot() as robot: + proximity_data = robot.proximity.last_sensor_reading + if proximity_data is not None: + print(proximity_data.distance) + """ + return self._proximity + + @property + def touch(self) -> touch.TouchComponent: + """:class:`~anki_vector.touch.TouchComponent` containing state related to object touch detection. + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + print('Robot is being touched: {0}'.format(robot.touch.last_sensor_reading.is_being_touched)) + """ + return self._touch + + @property + def viewer(self) -> ViewerComponent: + """The :class:`~anki_vector.viewer.ViewerComponent` instance used to render Vector's camera feed. + + .. testcode:: + + import time + + import anki_vector + + with anki_vector.Robot() as robot: + # Render video for 5 seconds + robot.viewer.show() + time.sleep(5) + + # Disable video render and camera feed for 5 seconds + robot.viewer.close() + """ + if self._viewer is None: + raise VectorNotReadyException("ViewerComponent is not yet initialized") + return self._viewer + + @property + def viewer_3d(self) -> Viewer3DComponent: + """The :class:`~anki_vector.viewer.Viewer3DComponent` instance used to render Vector's navigation map. + + .. testcode:: + + import time + + import anki_vector + + with anki_vector.Robot(show_3d_viewer=True, enable_nav_map_feed=True) as robot: + # Render 3D view of navigation map for 5 seconds + time.sleep(5) + """ + if self._viewer_3d is None: + raise VectorNotReadyException("Viewer3DComponent is not yet initialized") + return self._viewer_3d + + @property + def vision(self) -> vision.VisionComponent: + """:class:`~anki_vector.vision.VisionComponent` containing functionality related to vision based object detection. + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + robot.vision.enable_custom_object_detection() + """ + return self._vision + + @property + def world(self) -> world.World: + """A reference to the :class:`~anki_vector.world.World` instance, or None if the World is not yet initialized.""" + if self._world is None: + raise VectorNotReadyException("WorldComponent is not yet initialized") + return self._world + + @property + @util.block_while_none() + def pose(self) -> util.Pose: + """:class:`anki_vector.util.Pose`: The current pose (position and orientation) of Vector. + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_robot_pose = robot.pose + """ + return self._pose + + @property + @util.block_while_none() + def pose_angle_rad(self) -> float: + """Vector's pose angle (heading in X-Y plane). + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_pose_angle_rad = robot.pose_angle_rad + """ + return self._pose_angle_rad + + @property + @util.block_while_none() + def pose_pitch_rad(self) -> float: + """Vector's pose pitch (angle up/down). + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_pose_pitch_rad = robot.pose_pitch_rad + """ + return self._pose_pitch_rad + + @property + @util.block_while_none() + def left_wheel_speed_mmps(self) -> float: + """Vector's left wheel speed in mm/sec + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_left_wheel_speed_mmps = robot.left_wheel_speed_mmps + """ + return self._left_wheel_speed_mmps + + @property + @util.block_while_none() + def right_wheel_speed_mmps(self) -> float: + """Vector's right wheel speed in mm/sec + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_right_wheel_speed_mmps = robot.right_wheel_speed_mmps + """ + return self._right_wheel_speed_mmps + + @property + @util.block_while_none() + def head_angle_rad(self) -> float: + """Vector's head angle (up/down). + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_head_angle_rad = robot.head_angle_rad + """ + return self._head_angle_rad + + @property + @util.block_while_none() + def lift_height_mm(self) -> float: + """Height of Vector's lift from the ground. + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_lift_height_mm = robot.lift_height_mm + """ + return self._lift_height_mm + + @property + @util.block_while_none() + def accel(self) -> util.Vector3: + """:class:`anki_vector.util.Vector3`: The current accelerometer reading (x, y, z) + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_accel = robot.accel + """ + return self._accel + + @property + @util.block_while_none() + def gyro(self) -> util.Vector3: + """The current gyroscope reading (x, y, z) + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_gyro = robot.gyro + """ + return self._gyro + + @property + @util.block_while_none() + def carrying_object_id(self) -> int: + """The ID of the object currently being carried (-1 if none) + + .. testcode:: + + import anki_vector + from anki_vector.util import degrees + + # Set the robot so that he can see a cube. + with anki_vector.Robot() as robot: + robot.behavior.set_head_angle(degrees(0.0)) + robot.behavior.set_lift_height(0.0) + + robot.world.connect_cube() + + if robot.world.connected_light_cube: + robot.behavior.pickup_object(robot.world.connected_light_cube) + + print("carrying_object_id: ", robot.carrying_object_id) + """ + return self._carrying_object_id + + @property + @util.block_while_none() + def head_tracking_object_id(self) -> int: + """The ID of the object the head is tracking to (-1 if none) + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_head_tracking_object_id = robot.head_tracking_object_id + """ + return self._head_tracking_object_id + + @property + @util.block_while_none() + def localized_to_object_id(self) -> int: + """The ID of the object that the robot is localized to (-1 if none) + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_localized_to_object_id = robot.localized_to_object_id + """ + return self._localized_to_object_id + + # TODO Move to photos or somewhere else + @property + @util.block_while_none() + def last_image_time_stamp(self) -> int: + """The robot's timestamp for the last image seen. + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + current_last_image_time_stamp = robot.last_image_time_stamp + """ + return self._last_image_time_stamp + + @property + def status(self) -> status.RobotStatus: + """A property that exposes various status properties of the robot. + + This status provides a simple mechanism to, for example, detect if any + of Vector's motors are moving, determine if Vector is being held, or if + he is on the charger. The full list is available in the + :class:`RobotStatus ` class documentation. + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + if robot.status.is_being_held: + print("Vector is being held!") + else: + print("Vector is not being held.") + """ + return self._status + + @property + def enable_audio_feed(self) -> bool: + """The audio feed enabled/disabled + + :getter: Returns whether the audio feed is enabled + :setter: Enable/disable the audio feed + + .. code-block:: python + + import asyncio + import time + + import anki_vector + + with anki_vector.Robot(enable_audio_feed=True) as robot: + time.sleep(5) + robot.enable_audio_feed = False + time.sleep(5) + """ + # TODO When audio is ready, convert `.. code-block:: python` to `.. testcode::` + return self._enable_audio_feed + + @enable_audio_feed.setter + def enable_audio_feed(self, enable) -> None: + self._enable_audio_feed = enable + # TODO add audio feed enablement when ready + + # Unpack streamed data to robot's internal properties + def _unpack_robot_state(self, _robot, _event_type, msg): + self._pose = util.Pose(x=msg.pose.x, y=msg.pose.y, z=msg.pose.z, + q0=msg.pose.q0, q1=msg.pose.q1, + q2=msg.pose.q2, q3=msg.pose.q3, + origin_id=msg.pose.origin_id) + self._pose_angle_rad = msg.pose_angle_rad + self._pose_pitch_rad = msg.pose_pitch_rad + self._left_wheel_speed_mmps = msg.left_wheel_speed_mmps + self._right_wheel_speed_mmps = msg.right_wheel_speed_mmps + self._head_angle_rad = msg.head_angle_rad + self._lift_height_mm = msg.lift_height_mm + self._accel = util.Vector3(msg.accel.x, msg.accel.y, msg.accel.z) + self._gyro = util.Vector3(msg.gyro.x, msg.gyro.y, msg.gyro.z) + self._carrying_object_id = msg.carrying_object_id + self._head_tracking_object_id = msg.head_tracking_object_id + self._localized_to_object_id = msg.localized_to_object_id + self._last_image_time_stamp = msg.last_image_time_stamp + self._status.set(msg.status) + + def connect(self, timeout: int = 10) -> None: + """Start the connection to Vector. + + .. testcode:: + + import anki_vector + + robot = anki_vector.Robot() + robot.connect() + robot.anim.play_animation_trigger("GreetAfterLongTime") + robot.disconnect() + + :param timeout: The time to allow for a connection before a + :class:`anki_vector.exceptions.VectorTimeoutException` is raised. + """ + self.conn.connect(timeout=timeout) + self.events.start(self.conn) + + # Initialize components + self._anim = animation.AnimationComponent(self) + self._audio = audio.AudioComponent(self) + self._behavior = behavior.BehaviorComponent(self) + self._faces = faces.FaceComponent(self) + self._motors = motors.MotorComponent(self) + self._nav_map = nav_map.NavMapComponent(self) + self._screen = screen.ScreenComponent(self) + self._photos = photos.PhotographComponent(self) + self._proximity = proximity.ProximityComponent(self) + self._touch = touch.TouchComponent(self) + self._viewer = viewer.ViewerComponent(self) + self._viewer_3d = viewer.Viewer3DComponent(self) + self._vision = vision.VisionComponent(self) + self._world = world.World(self) + self._camera = camera.CameraComponent(self) + + if self.cache_animation_lists: + # Load animation triggers and animations so they are ready to play when requested + anim_request = self._anim.load_animation_list() + if isinstance(anim_request, concurrent.futures.Future): + anim_request.result() + anim_trigger_request = self._anim.load_animation_trigger_list() + if isinstance(anim_trigger_request, concurrent.futures.Future): + anim_trigger_request.result() + + # TODO enable audio feed when ready + + # Start rendering camera feed + if self._show_viewer: + self.camera.init_camera_feed() + self.viewer.show() + + if self._show_3d_viewer: + self.viewer_3d.show() + + if self._enable_nav_map_feed: + self.nav_map.init_nav_map_feed() + + # Enable face detection, to allow Vector to add faces to its world view + if self.conn.requires_behavior_control: + face_detection = self.vision.enable_face_detection(detect_faces=self.enable_face_detection, estimate_expression=self.estimate_facial_expression) + if isinstance(face_detection, concurrent.futures.Future): + face_detection.result() + object_detection = self.vision.enable_custom_object_detection(detect_custom_objects=self.enable_custom_object_detection) + if isinstance(object_detection, concurrent.futures.Future): + object_detection.result() + + # Subscribe to a callback that updates the robot's local properties + self.events.subscribe(self._unpack_robot_state, + events.Events.robot_state, + _on_connection_thread=True) + + # get the camera configuration from the robot + response = self._camera.get_camera_config() + if isinstance(response, concurrent.futures.Future): + response = response.result() + self._camera.set_config(response) + + # Subscribe to a callback for camera exposure settings + self.events.subscribe(self._camera.update_state, + events.Events.camera_settings_update, + _on_connection_thread=True) + + # access the pose to prove it has gotten back from the event stream once + try: + if not self.pose: + pass + except VectorPropertyValueNotReadyException as e: + raise VectorUnreliableEventStreamException() from e + + def disconnect(self) -> None: + """Close the connection with Vector. + + .. testcode:: + + import anki_vector + robot = anki_vector.Robot() + robot.connect() + robot.anim.play_animation_trigger("GreetAfterLongTime") + robot.disconnect() + """ + if self.conn.requires_behavior_control: + self.vision.close() + + # Stop rendering video + self.viewer.close() + + # Stop rendering 3d video + self.viewer_3d.close() + + # Shutdown camera feed + self.camera.close_camera_feed() + + # TODO shutdown audio feed when available + + # Shutdown nav map feed + self.nav_map.close_nav_map_feed() + + # Close the world and cleanup its objects + self.world.close() + + self.proximity.close() + self.touch.close() + + self.events.close() + self.conn.close() + + def __enter__(self): + self.connect(self.behavior_activation_timeout) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.disconnect() + + @on_connection_thread(requires_control=False) + async def get_battery_state(self) -> protocol.BatteryStateResponse: + """Check the current state of the robot and cube batteries. + + The robot is considered fully-charged above 4.1 volts. At 3.6V, the robot is approaching low charge. + + Robot battery level values are as follows: + + +-------+---------+---------------------------------------------------------------+ + | Value | Level | Description | + +=======+=========+===============================================================+ + | 1 | Low | 3.6V or less. If on charger, 4V or less. | + +-------+---------+---------------------------------------------------------------+ + | 2 | Nominal | Normal operating levels. | + +-------+---------+---------------------------------------------------------------+ + | 3 | Full | This state can only be achieved when Vector is on the charger | + +-------+---------+---------------------------------------------------------------+ + + Cube battery level values are shown below: + + +-------+---------+---------------------------------------------------------------+ + | Value | Level | Description | + +=======+=========+===============================================================+ + | 1 | Low | 1.1V or less. | + +-------+---------+---------------------------------------------------------------+ + | 2 | Normal | Normal operating levels. | + +-------+---------+---------------------------------------------------------------+ + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + print("Connecting to a cube...") + robot.world.connect_cube() + + battery_state = robot.get_battery_state() + if battery_state: + print("Robot battery voltage: {0}".format(battery_state.battery_volts)) + print("Robot battery Level: {0}".format(battery_state.battery_level)) + print("Robot battery is charging: {0}".format(battery_state.is_charging)) + print("Robot is on charger platform: {0}".format(battery_state.is_on_charger_platform)) + print("Robot suggested charger time: {0}".format(battery_state.suggested_charger_sec)) + print("Cube battery level: {0}".format(battery_state.cube_battery.level)) + print("Cube battery voltage: {0}".format(battery_state.cube_battery.battery_volts)) + print("Cube battery seconds since last reading: {0}".format(battery_state.cube_battery.time_since_last_reading_sec)) + print("Cube battery factory id: {0}".format(battery_state.cube_battery.factory_id)) + """ + get_battery_state_request = protocol.BatteryStateRequest() + return await self.conn.grpc_interface.BatteryState(get_battery_state_request) + + @on_connection_thread(requires_control=False) + async def get_version_state(self) -> protocol.VersionStateResponse: + """Get the versioning information for Vector, including Vector's os_version and engine_build_id. + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + version_state = robot.get_version_state() + if version_state: + print("Robot os_version: {0}".format(version_state.os_version)) + print("Robot engine_build_id: {0}".format(version_state.engine_build_id)) + """ + get_version_state_request = protocol.VersionStateRequest() + return await self.conn.grpc_interface.VersionState(get_version_state_request) + + +class AsyncRobot(Robot): + """The AsyncRobot object is just like the Robot object, but allows multiple commands + to be executed at the same time. To achieve this, all grpc function calls also + return a :class:`concurrent.futures.Future`. + + 1. Using :code:`with`: it works just like opening a file, and will close when + the :code:`with` block's indentation ends. + + .. testcode:: + + import anki_vector + from anki_vector.util import degrees + + # Create the robot connection + with anki_vector.AsyncRobot() as robot: + # Start saying text asynchronously + say_future = robot.behavior.say_text("Now is the time") + # Turn robot, wait for completion + turn_future = robot.behavior.turn_in_place(degrees(3*360)) + turn_future.result() + # Play greet animation trigger, wait for completion + greet_future = robot.anim.play_animation_trigger("GreetAfterLongTime") + greet_future.result() + # Make sure text has been spoken + say_future.result() + + 2. Using :func:`connect` and :func:`disconnect` to explicitly open and close the connection: + it allows the robot's connection to continue in the context in which it started. + + .. testcode:: + + import anki_vector + from anki_vector.util import degrees + + # Create a Robot object + robot = anki_vector.AsyncRobot() + # Connect to Vector + robot.connect() + # Start saying text asynchronously + say_future = robot.behavior.say_text("Now is the time") + # Turn robot, wait for completion + turn_future = robot.behavior.turn_in_place(degrees(3 * 360)) + turn_future.result() + # Play greet animation trigger, wait for completion + greet_future = robot.anim.play_animation_trigger("GreetAfterLongTime") + greet_future.result() + # Make sure text has been spoken + say_future.result() + # Disconnect from Vector + robot.disconnect() + + When getting callbacks from the event stream, it's important to understand that function calls + return a :class:`concurrent.futures.Future` and not an :class:`asyncio.Future`. This means any + async callback functions will need to use :func:`asyncio.wrap_future` to be able to await the + function's response. + + .. testcode:: + + import asyncio + import time + + import anki_vector + + async def callback(robot, event_type, event): + await asyncio.wrap_future(robot.anim.play_animation_trigger('GreetAfterLongTime')) + await asyncio.wrap_future(robot.behavior.set_head_angle(anki_vector.util.degrees(40))) + + if __name__ == "__main__": + args = anki_vector.util.parse_command_args() + with anki_vector.AsyncRobot(serial=args.serial, enable_face_detection=True) as robot: + robot.behavior.set_head_angle(anki_vector.util.degrees(40)) + robot.events.subscribe(callback, anki_vector.events.Events.robot_observed_face) + + # Waits 10 seconds. Show Vector your face. + time.sleep(10) + + :param serial: Vector's serial number. The robot's serial number (ex. 00e20100) is located on the underside of Vector, + or accessible from Vector's debug screen. Used to identify which Vector configuration to load. + :param ip: Vector's IP Address. (optional) + :param config: A custom :class:`dict` to override values in Vector's configuration. (optional) + Example: :code:`{"cert": "/path/to/file.cert", "name": "Vector-XXXX", "guid": ""}` + where :code:`cert` is the certificate to identify Vector, :code:`name` is the name on Vector's face + when his backpack is double-clicked on the charger, and :code:`guid` is the authorization token + that identifies the SDK user. Note: Never share your authentication credentials with anyone. + :param default_logging: Toggle default logging. + :param behavior_activation_timeout: The time to wait for control of the robot before failing. + :param cache_animation_lists: Get the list of animation triggers and animations available at startup. + :param enable_face_detection: Turn on face detection. + :param estimate_facial_expression: Turn estimating facial expression on/off. + :param enable_audio_feed: Turn audio feed on/off. + :param enable_custom_object_detection: Turn custom object detection on/off. + :param enable_nav_map_feed: Turn navigation map feed on/off. + :param show_viewer: Specifies whether to display a view of Vector's camera in a window. + :param show_3d_viewer: Specifies whether to display a 3D view of Vector's understanding of the world in a window. + :param behavior_control_level: Request control of Vector's behavior system at a specific level of control. Pass + :code:`None` if behavior control is not needed. + See :class:`ControlPriorityLevel` for more information.""" + + @functools.wraps(Robot.__init__) + def __init__(self, *args, **kwargs): + super(AsyncRobot, self).__init__(*args, **kwargs) + self._force_async = True diff --git a/anki_vector/util.py b/anki_vector/util.py index e5d6903..6d315bc 100644 --- a/anki_vector/util.py +++ b/anki_vector/util.py @@ -1,1132 +1,1134 @@ -# Copyright (c) 2018 Anki, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License in the file LICENSE.txt or at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Utility functions and classes for the Vector SDK. -""" - -# __all__ should order by constants, event classes, other classes, functions. -__all__ = ['Angle', - 'BaseOverlay', - 'Component', - 'Distance', - 'ImageRect', - 'Matrix44', - 'Pose', - 'Position', - 'Quaternion', - 'RectangleOverlay', - 'Speed', - 'Vector2', - 'Vector3', - 'angle_z_to_quaternion', - 'block_while_none', - 'degrees', - 'distance_mm', - 'distance_inches', - 'get_class_logger', - 'parse_command_args', - 'radians', - 'setup_basic_logging', - 'speed_mmps'] - -import argparse -import configparser -from functools import wraps -import logging -import math -import os -from pathlib import Path -import sys -import time -from typing import Callable, Union - -from .exceptions import VectorConfigurationException, VectorPropertyValueNotReadyException -from .messaging import protocol - -try: - from PIL import Image, ImageDraw -except ImportError: - sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") - - -def parse_command_args(parser: argparse.ArgumentParser = None): - """ - Parses command line arguments. - - Attempts to read the robot serial number from the command line arguments. If no serial number - is specified, we next attempt to read the robot serial number from environment variable ANKI_ROBOT_SERIAL. - If ANKI_ROBOT_SERIAL is specified, the value will be used as the robot's serial number. - - .. code-block:: python - - import anki_vector - - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument("--new_param") - args = anki_vector.util.parse_command_args(parser) - - :param parser: To add new command line arguments, - pass an argparse parser with the new options - already defined. Leave empty to use the defaults. - """ - if parser is None: - parser = argparse.ArgumentParser() - parser.add_argument("-s", "--serial", nargs='?', default=os.environ.get('ANKI_ROBOT_SERIAL', None)) - return parser.parse_args() - - -def block_while_none(interval: float = 0.1, max_iterations: int = 50): - """Use this to denote a property that may need some delay before it appears. - - :param interval: how often to check if the property is no longer None - :param max_iterations: how many times to check the property before raising an error - - This will raise a :class:`VectorControlTimeoutException` if the property cannot be retrieved - before :attr:`max_iterations`. - """ - def blocker(func: Callable): - @wraps(func) - def wrapped(*args, **kwargs): - iterations = 0 - result = func(*args, **kwargs) - while result is None: - time.sleep(interval) - iterations += 1 - if iterations > max_iterations: - raise VectorPropertyValueNotReadyException() - result = func(*args, **kwargs) - return result - return wrapped - return blocker - - -def setup_basic_logging(custom_handler: logging.Handler = None, - general_log_level: str = None, - target: object = None): - """Helper to perform basic setup of the Python logger. - - :param custom_handler: provide an external logger for custom logging locations - :param general_log_level: 'DEBUG', 'INFO', 'WARN', 'ERROR' or an equivalent - constant from the :mod:`logging` module. If None then a - value will be read from the VECTOR_LOG_LEVEL environment variable. - :param target: The stream to send the log data to; defaults to stderr - """ - if general_log_level is None: - general_log_level = os.environ.get('VECTOR_LOG_LEVEL', logging.INFO) - - handler = custom_handler - if handler is None: - handler = logging.StreamHandler(stream=target) - formatter = logging.Formatter("%(asctime)s.%(msecs)03d %(name)+25s %(levelname)+7s %(message)s", - "%H:%M:%S") - handler.setFormatter(formatter) - - class LogCleanup(logging.Filter): # pylint: disable=too-few-public-methods - def filter(self, record): - # Drop 'anki_vector' from log messages - record.name = '.'.join(record.name.split('.')[1:]) - # Indent past informational chunk - record.msg = record.msg.replace("\n", f"\n{'':48}") - return True - handler.addFilter(LogCleanup()) - - vector_logger = logging.getLogger('anki_vector') - if not vector_logger.handlers: - vector_logger.addHandler(handler) - vector_logger.setLevel(general_log_level) - - -def get_class_logger(module: str, obj: object) -> logging.Logger: - """Helper to create logger for a given class (and module). - - .. testcode:: - - import anki_vector - - logger = anki_vector.util.get_class_logger("module_name", "object_name") - - :param module: The name of the module to which the object belongs. - :param obj: the object that owns the logger. - """ - return logging.getLogger(".".join([module, type(obj).__name__])) - - -class Vector2: - """Represents a 2D Vector (type/units aren't specified). - - :param x: X component - :param y: Y component - """ - - __slots__ = ('_x', '_y') - - def __init__(self, x: float, y: float): - self._x = float(x) - self._y = float(y) - - def set_to(self, rhs): - """Copy the x and y components of the given Vector2 instance. - - :param rhs: The right-hand-side of this assignment - the - source Vector2 to copy into this Vector2 instance. - """ - self._x = float(rhs.x) - self._y = float(rhs.y) - - @property - def x(self) -> float: - """The x component.""" - return self._x - - @property - def y(self) -> float: - """The y component.""" - return self._y - - @property - def x_y(self): - """tuple (float, float): The X, Y elements of the Vector2 (x,y)""" - return self._x, self._y - - def __repr__(self): - return "<%s x: %.2f y: %.2f>" % (self.__class__.__name__, self.x, self.y) - - def __add__(self, other): - if not isinstance(other, Vector2): - raise TypeError("Unsupported operand for + expected Vector2") - return Vector2(self.x + other.x, self.y + other.y) - - def __sub__(self, other): - if not isinstance(other, Vector2): - raise TypeError("Unsupported operand for - expected Vector2") - return Vector2(self.x - other.x, self.y - other.y) - - def __mul__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported operand for * expected number") - return Vector2(self.x * other, self.y * other) - - def __truediv__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported operand for / expected number") - return Vector2(self.x / other, self.y / other) - - -class Vector3: - """Represents a 3D Vector (type/units aren't specified). - - :param x: X component - :param y: Y component - :param z: Z component - """ - - __slots__ = ('_x', '_y', '_z') - - def __init__(self, x: float, y: float, z: float): - self._x = float(x) - self._y = float(y) - self._z = float(z) - - def set_to(self, rhs): - """Copy the x, y and z components of the given Vector3 instance. - - :param rhs: The right-hand-side of this assignment - the - source Vector3 to copy into this Vector3 instance. - """ - self._x = float(rhs.x) - self._y = float(rhs.y) - self._z = float(rhs.z) - - @property - def x(self) -> float: - """The x component.""" - return self._x - - @property - def y(self) -> float: - """The y component.""" - return self._y - - @property - def z(self) -> float: - """The z component.""" - return self._z - - @property - def magnitude_squared(self) -> float: - """float: The magnitude of the Vector3 instance""" - return self._x**2 + self._y**2 + self._z**2 - - @property - def magnitude(self) -> float: - """The magnitude of the Vector3 instance""" - return math.sqrt(self.magnitude_squared) - - @property - def normalized(self): - """A Vector3 instance with the same direction and unit magnitude""" - mag = self.magnitude - if mag == 0: - return Vector3(0, 0, 0) - return Vector3(self._x / mag, self._y / mag, self._z / mag) - - def dot(self, other): - """The dot product of this and another Vector3 instance""" - if not isinstance(other, Vector3): - raise TypeError("Unsupported argument for dot product, expected Vector3") - return self._x * other.x + self._y * other.y + self._z * other.z - - def cross(self, other): - """The cross product of this and another Vector3 instance""" - if not isinstance(other, Vector3): - raise TypeError("Unsupported argument for cross product, expected Vector3") - - return Vector3( - self._y * other.z - self._z * other.y, - self._z * other.x - self._x * other.z, - self._x * other.y - self._y * other.x) - - @property - def x_y_z(self): - """tuple (float, float, float): The X, Y, Z elements of the Vector3 (x,y,z)""" - return self._x, self._y, self._z - - def __repr__(self): - return f"<{self.__class__.__name__} x: {self.x:.2f} y: {self.y:.2f} z: {self.z:.2f}>" - - def __add__(self, other): - if not isinstance(other, Vector3): - raise TypeError("Unsupported operand for +, expected Vector3") - return Vector3(self.x + other.x, self.y + other.y, self.z + other.z) - - def __sub__(self, other): - if not isinstance(other, Vector3): - raise TypeError("Unsupported operand for -, expected Vector3") - return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) - - def __mul__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported operand for * expected number") - return Vector3(self.x * other, self.y * other, self.z * other) - - def __truediv__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported operand for / expected number") - return Vector3(self.x / other, self.y / other, self.z / other) - - -class Angle: - """Represents an angle. - - Use the :func:`degrees` or :func:`radians` convenience methods to generate - an Angle instance. - - :param radians: The number of radians the angle should represent - (cannot be combined with ``degrees``) - :param degrees: The number of degress the angle should represent - (cannot be combined with ``radians``) - """ - - __slots__ = ('_radians') - - def __init__(self, radians: float = None, degrees: float = None): # pylint: disable=redefined-outer-name - if radians is None and degrees is None: - raise ValueError("Expected either the degrees or radians keyword argument") - if radians and degrees: - raise ValueError("Expected either the degrees or radians keyword argument, not both") - - if degrees is not None: - radians = degrees * math.pi / 180 - self._radians = float(radians) - - @property - def radians(self) -> float: # pylint: disable=redefined-outer-name - """The angle in radians.""" - return self._radians - - @property - def degrees(self) -> float: # pylint: disable=redefined-outer-name - """The angle in degrees.""" - return self._radians / math.pi * 180 - - def __repr__(self): - return f"<{self.__class__.__name__} Radians: {self.radians:.2f} Degrees: {self.degrees:.2f}>" - - def __add__(self, other): - if not isinstance(other, Angle): - raise TypeError("Unsupported type for + expected Angle") - return Angle(radians=(self.radians + other.radians)) - - def __sub__(self, other): - if not isinstance(other, Angle): - raise TypeError("Unsupported type for - expected Angle") - return Angle(radians=(self.radians - other.radians)) - - def __mul__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported type for * expected number") - return Angle(radians=(self.radians * other)) - - def __truediv__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported type for / expected number") - return radians(self.radians / other) - - def _cmp_int(self, other): - if not isinstance(other, Angle): - raise TypeError("Unsupported type for comparison expected Angle") - return self.radians - other.radians - - def __eq__(self, other): - return self._cmp_int(other) == 0 - - def __ne__(self, other): - return self._cmp_int(other) != 0 - - def __gt__(self, other): - return self._cmp_int(other) > 0 - - def __lt__(self, other): - return self._cmp_int(other) < 0 - - def __ge__(self, other): - return self._cmp_int(other) >= 0 - - def __le__(self, other): - return self._cmp_int(other) <= 0 - - @property - def abs_value(self): - """:class:`anki_vector.util.Angle`: The absolute value of the angle. - - If the Angle is positive then it returns a copy of this Angle, otherwise it returns -Angle. - """ - return Angle(radians=abs(self._radians)) - - -def angle_z_to_quaternion(angle_z: Angle): - """This function converts an angle in the z axis (Euler angle z component) to a quaternion. - - :param angle_z: The z axis angle. - - Returns: - q0, q1, q2, q3 (float, float, float, float): A tuple with all the members - of a quaternion defined by angle_z. - """ - - # Define the quaternion to be converted from a Euler angle (x,y,z) of 0,0,angle_z - # These equations have their original equations above, and simplified implemented - # q0 = cos(x/2)*cos(y/2)*cos(z/2) + sin(x/2)*sin(y/2)*sin(z/2) - q0 = math.cos(angle_z.radians / 2) - # q1 = sin(x/2)*cos(y/2)*cos(z/2) - cos(x/2)*sin(y/2)*sin(z/2) - q1 = 0 - # q2 = cos(x/2)*sin(y/2)*cos(z/2) + sin(x/2)*cos(y/2)*sin(z/2) - q2 = 0 - # q3 = cos(x/2)*cos(y/2)*sin(z/2) - sin(x/2)*sin(y/2)*cos(z/2) - q3 = math.sin(angle_z.radians / 2) - return q0, q1, q2, q3 - - -def degrees(degrees: float) -> Angle: # pylint: disable=redefined-outer-name - """An Angle instance set to the specified number of degrees.""" - return Angle(degrees=degrees) - - -def radians(radians: float) -> Angle: # pylint: disable=redefined-outer-name - """An Angle instance set to the specified number of radians.""" - return Angle(radians=radians) - - -class Matrix44: - """A 4x4 Matrix for representing the rotation and/or position of an object in the world. - - Can be generated from a :class:`Quaternion` for a pure rotation matrix, or - combined with a position for a full translation matrix, as done by - :meth:`Pose.to_matrix`. - """ - __slots__ = ('m00', 'm10', 'm20', 'm30', - 'm01', 'm11', 'm21', 'm31', - 'm02', 'm12', 'm22', 'm32', - 'm03', 'm13', 'm23', 'm33') - - def __init__(self, - m00: float, m10: float, m20: float, m30: float, - m01: float, m11: float, m21: float, m31: float, - m02: float, m12: float, m22: float, m32: float, - m03: float, m13: float, m23: float, m33: float): - self.m00 = float(m00) - self.m10 = float(m10) - self.m20 = float(m20) - self.m30 = float(m30) - - self.m01 = float(m01) - self.m11 = float(m11) - self.m21 = float(m21) - self.m31 = float(m31) - - self.m02 = float(m02) - self.m12 = float(m12) - self.m22 = float(m22) - self.m32 = float(m32) - - self.m03 = float(m03) - self.m13 = float(m13) - self.m23 = float(m23) - self.m33 = float(m33) - - def __repr__(self): - return ("<%s: " - "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f " - "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f>" % ( - self.__class__.__name__, *self.in_row_order)) - - @property - def tabulated_string(self) -> str: - """A multi-line string formatted with tabs to show the matrix contents.""" - return ("%.1f\t%.1f\t%.1f\t%.1f\n" - "%.1f\t%.1f\t%.1f\t%.1f\n" - "%.1f\t%.1f\t%.1f\t%.1f\n" - "%.1f\t%.1f\t%.1f\t%.1f" % self.in_row_order) - - @property - def in_row_order(self): - """tuple of 16 floats: The contents of the matrix in row order.""" - return self.m00, self.m01, self.m02, self.m03,\ - self.m10, self.m11, self.m12, self.m13,\ - self.m20, self.m21, self.m22, self.m23,\ - self.m30, self.m31, self.m32, self.m33 - - @property - def in_column_order(self): - """tuple of 16 floats: The contents of the matrix in column order.""" - return self.m00, self.m10, self.m20, self.m30,\ - self.m01, self.m11, self.m21, self.m31,\ - self.m02, self.m12, self.m22, self.m32,\ - self.m03, self.m13, self.m23, self.m33 - - @property - def forward_xyz(self): - """tuple of 3 floats: The x,y,z components representing the matrix's forward vector.""" - return self.m00, self.m01, self.m02 - - @property - def left_xyz(self): - """tuple of 3 floats: The x,y,z components representing the matrix's left vector.""" - return self.m10, self.m11, self.m12 - - @property - def up_xyz(self): - """tuple of 3 floats: The x,y,z components representing the matrix's up vector.""" - return self.m20, self.m21, self.m22 - - @property - def pos_xyz(self): - """tuple of 3 floats: The x,y,z components representing the matrix's position vector.""" - return self.m30, self.m31, self.m32 - - def set_forward(self, x: float, y: float, z: float): - """Set the x,y,z components representing the matrix's forward vector. - - :param x: The X component. - :param y: The Y component. - :param z: The Z component. - """ - self.m00 = float(x) - self.m01 = float(y) - self.m02 = float(z) - - def set_left(self, x: float, y: float, z: float): - """Set the x,y,z components representing the matrix's left vector. - - :param x: The X component. - :param y: The Y component. - :param z: The Z component. - """ - self.m10 = float(x) - self.m11 = float(y) - self.m12 = float(z) - - def set_up(self, x: float, y: float, z: float): - """Set the x,y,z components representing the matrix's up vector. - - :param x: The X component. - :param y: The Y component. - :param z: The Z component. - """ - self.m20 = float(x) - self.m21 = float(y) - self.m22 = float(z) - - def set_pos(self, x: float, y: float, z: float): - """Set the x,y,z components representing the matrix's position vector. - - :param x: The X component. - :param y: The Y component. - :param z: The Z component. - """ - self.m30 = float(x) - self.m31 = float(y) - self.m32 = float(z) - - -class Quaternion: - """Represents the rotation of an object in the world.""" - - __slots__ = ('_q0', '_q1', '_q2', '_q3') - - def __init__(self, q0: float = None, q1: float = None, q2: float = None, q3: float = None, angle_z: Angle = None): - is_quaternion = q0 is not None and q1 is not None and q2 is not None and q3 is not None - - if not is_quaternion and angle_z is None: - raise ValueError("Expected either the q0 q1 q2 and q3 or angle_z keyword arguments") - if is_quaternion and angle_z: - raise ValueError("Expected either the q0 q1 q2 and q3 or angle_z keyword argument," - "not both") - if angle_z is not None: - if not isinstance(angle_z, Angle): - raise TypeError("Unsupported type for angle_z expected Angle") - q0, q1, q2, q3 = angle_z_to_quaternion(angle_z) - - self._q0 = float(q0) - self._q1 = float(q1) - self._q2 = float(q2) - self._q3 = float(q3) - - @property - def q0(self) -> float: - """The q0 (w) value of the quaternion.""" - return self._q0 - - @property - def q1(self) -> float: - """The q1 (i) value of the quaternion.""" - return self._q1 - - @property - def q2(self) -> float: - """The q2 (j) value of the quaternion.""" - return self._q2 - - @property - def q3(self) -> float: - """The q3 (k) value of the quaternion.""" - return self._q3 - - @property - def angle_z(self) -> Angle: - """An Angle instance representing the z Euler component of the object's rotation. - - Defined as the rotation in the z axis. - """ - q0, q1, q2, q3 = self.q0_q1_q2_q3 - return Angle(radians=math.atan2(2 * (q1 * q2 + q0 * q3), 1 - 2 * (q2**2 + q3**2))) - - @property - def q0_q1_q2_q3(self): - """tuple of float: Contains all elements of the quaternion (q0,q1,q2,q3)""" - return self._q0, self._q1, self._q2, self._q3 - - def to_matrix(self, pos_x: float = 0.0, pos_y: float = 0.0, pos_z: float = 0.0): - """Convert the Quaternion to a 4x4 matrix representing this rotation. - - A position can also be provided to generate a full translation matrix. - - :param pos_x: The x component for the position. - :param pos_y: The y component for the position. - :param pos_z: The z component for the position. - - Returns: - :class:`anki_vector.util.Matrix44`: A matrix representing this Quaternion's - rotation, with the provided position (which defaults to 0,0,0). - """ - # See https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation - q0q0 = self.q0 * self.q0 - q1q1 = self.q1 * self.q1 - q2q2 = self.q2 * self.q2 - q3q3 = self.q3 * self.q3 - - q0x2 = self.q0 * 2.0 # saves 2 multiplies - q0q1x2 = q0x2 * self.q1 - q0q2x2 = q0x2 * self.q2 - q0q3x2 = q0x2 * self.q3 - q1x2 = self.q1 * 2.0 # saves 1 multiply - q1q2x2 = q1x2 * self.q2 - q1q3x2 = q1x2 * self.q3 - q2q3x2 = 2.0 * self.q2 * self.q3 - - m00 = (q0q0 + q1q1 - q2q2 - q3q3) - m01 = (q1q2x2 + q0q3x2) - m02 = (q1q3x2 - q0q2x2) - - m10 = (q1q2x2 - q0q3x2) - m11 = (q0q0 - q1q1 + q2q2 - q3q3) - m12 = (q0q1x2 + q2q3x2) - - m20 = (q0q2x2 + q1q3x2) - m21 = (q2q3x2 - q0q1x2) - m22 = (q0q0 - q1q1 - q2q2 + q3q3) - - return Matrix44(m00, m10, m20, float(pos_x), - m01, m11, m21, float(pos_y), - m02, m12, m22, float(pos_z), - 0.0, 0.0, 0.0, 1.0) - - def __repr__(self): - return (f"<{self.__class__.__name__} q0: {self.q0:.2f} q1: {self.q1:.2f}" - f" q2: {self.q2:.2f} q3: {self.q3:.2f} {self.angle_z}>") - - -class Position(Vector3): - """Represents the position of an object in the world. - - A position consists of its x, y and z values in millimeters. - - :param x: X position in millimeters - :param y: Y position in millimeters - :param z: Z position in millimeters - """ - __slots__ = () - - -class Pose: - """Represents where an object is in the world. - - Whenever Vector is delocalized (i.e. whenever Vector no longer knows - where he is - e.g. when he's picked up), Vector creates a new pose starting at - (0,0,0) with no rotation, with origin_id incremented to show that these poses - cannot be compared with earlier ones. As Vector drives around, his pose (and the - pose of other objects he observes - e.g. faces, his LightCube, charger, etc.) is relative to this - initial position and orientation. - - The coordinate space is relative to Vector, where Vector's origin is the - point on the ground between Vector's two front wheels. The X axis is Vector's forward direction, - the Y axis is to Vector's left, and the Z axis is up. - - Only poses of the same origin_id can safely be compared or operated on. - - .. testcode:: - - import anki_vector - from anki_vector.util import degrees, Pose - - with anki_vector.Robot() as robot: - pose = Pose(x=50, y=0, z=0, angle_z=anki_vector.util.Angle(degrees=0)) - robot.behavior.go_to_pose(pose) - """ - __slots__ = ('_position', '_rotation', '_origin_id') - - def __init__(self, x: float, y: float, z: float, q0: float = None, q1: float = None, q2: float = None, q3: float = None, - angle_z: Angle = None, origin_id: int = -1): - self._position = Position(x, y, z) - self._rotation = Quaternion(q0, q1, q2, q3, angle_z) - self._origin_id = origin_id - - @property - def position(self) -> Position: - """The position component of this pose.""" - return self._position - - @property - def rotation(self) -> Quaternion: - """The rotation component of this pose.""" - return self._rotation - - @property - def origin_id(self) -> int: - """An ID maintained by the robot which represents which coordinate frame this pose is in.""" - return self._origin_id - - def __repr__(self): - return (f"<{self.__class__.__name__}: {self._position}" - f" {self._rotation} >") - - def define_pose_relative_this(self, new_pose): - """Creates a new pose such that new_pose's origin is now at the location of this pose. - - :param anki_vector.util.Pose new_pose: The pose which origin is being changed. - - Returns: - A :class:`anki_vector.util.Pose` object for which the origin was this pose's origin. - """ - if not isinstance(new_pose, Pose): - raise TypeError("Unsupported type for new_origin, must be of type Pose") - x, y, z = self.position.x_y_z - angle_z = self.rotation.angle_z - new_x, new_y, new_z = new_pose.position.x_y_z - new_angle_z = new_pose.rotation.angle_z - - cos_angle = math.cos(angle_z.radians) - sin_angle = math.sin(angle_z.radians) - res_x = x + (cos_angle * new_x) - (sin_angle * new_y) - res_y = y + (sin_angle * new_x) + (cos_angle * new_y) - res_z = z + new_z - res_angle = angle_z + new_angle_z - return Pose(res_x, - res_y, - res_z, - angle_z=res_angle, - origin_id=self._origin_id) - - @property - def is_valid(self) -> bool: - """True if this is a valid, usable pose.""" - return self.origin_id >= 0 - - def is_comparable(self, other_pose) -> bool: - """Checks whether these two poses are comparable. - - Poses are comparable if they're valid and having matching origin IDs. - - :param other_pose: The other pose to compare against. Type is Pose. - - Returns: - True if the two poses are comparable, False otherwise. - """ - return (self.is_valid and other_pose.is_valid - and (self.origin_id == other_pose.origin_id)) - - def to_matrix(self) -> Matrix44: - """Convert the Pose to a Matrix44. - - Returns: - A matrix representing this Pose's position and rotation. - """ - return self.rotation.to_matrix(*self.position.x_y_z) - - def to_proto_pose_struct(self) -> protocol.PoseStruct: - """Converts the Pose into the robot's messaging pose format. - """ - return protocol.PoseStruct( - x=self._position.x, - y=self._position.y, - z=self._position.z, - q0=self._rotation.q0, - q1=self._rotation.q1, - q2=self._rotation.q2, - q3=self._rotation.q3, - origin_id=self._origin_id) - - -class ImageRect: - '''Defines a bounding box within an image frame. - - This is used when objects and faces are observed to denote where in - the robot's camera view the object or face actually appears. It's then - used by the annotate module to show an outline of a box around - the object or face. - ''' - - __slots__ = ('_x_top_left', '_y_top_left', '_width', '_height') - - def __init__(self, x_top_left: float, y_top_left: float, width: float, height: float): - self._x_top_left = float(x_top_left) - self._y_top_left = float(y_top_left) - self._width = float(width) - self._height = float(height) - - @property - def x_top_left(self) -> float: - """The top left x value of where the object was last visible within Vector's camera view.""" - return self._x_top_left - - @property - def y_top_left(self) -> float: - """The top left y value of where the object was last visible within Vector's camera view.""" - return self._y_top_left - - @property - def width(self) -> float: - """The width of the object from when it was last visible within Vector's camera view.""" - return self._width - - @property - def height(self) -> float: - """The height of the object from when it was last visible within Vector's camera view.""" - return self._height - - def scale_by(self, scale_multiplier: Union[int, float]) -> None: - """Scales the image rectangle by the multiplier provided.""" - if not isinstance(scale_multiplier, (int, float)): - raise TypeError("Unsupported operand for * expected number") - self._x_top_left *= scale_multiplier - self._y_top_left *= scale_multiplier - self._width *= scale_multiplier - self._height *= scale_multiplier - - -class Distance: - """Represents a distance. - - The class allows distances to be returned in either millimeters or inches. - - Use the :func:`distance_inches` or :func:`distance_mm` convenience methods to generate - a Distance instance. - - :param distance_mm: The number of millimeters the distance should - represent (cannot be combined with ``distance_inches``). - :param distance_inches: The number of inches the distance should - represent (cannot be combined with ``distance_mm``). - """ - - __slots__ = ('_distance_mm') - - def __init__(self, distance_mm: float = None, distance_inches: float = None): # pylint: disable=redefined-outer-name - if distance_mm is None and distance_inches is None: - raise ValueError("Expected either the distance_mm or distance_inches keyword argument") - if distance_mm and distance_inches: - raise ValueError("Expected either the distance_mm or distance_inches keyword argument, not both") - - if distance_inches is not None: - distance_mm = distance_inches * 25.4 - self._distance_mm = float(distance_mm) - - def __repr__(self): - return "<%s %.2f mm (%.2f inches)>" % (self.__class__.__name__, self.distance_mm, self.distance_inches) - - def __add__(self, other): - if not isinstance(other, Distance): - raise TypeError("Unsupported operand for + expected Distance") - return distance_mm(self.distance_mm + other.distance_mm) - - def __sub__(self, other): - if not isinstance(other, Distance): - raise TypeError("Unsupported operand for - expected Distance") - return distance_mm(self.distance_mm - other.distance_mm) - - def __mul__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported operand for * expected number") - return distance_mm(self.distance_mm * other) - - def __truediv__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported operand for / expected number") - return distance_mm(self.distance_mm / other) - - @property - def distance_mm(self) -> float: # pylint: disable=redefined-outer-name - """The distance in millimeters""" - return self._distance_mm - - @property - def distance_inches(self) -> float: # pylint: disable=redefined-outer-name - return self._distance_mm / 25.4 - - -def distance_mm(distance_mm: float): # pylint: disable=redefined-outer-name - """Returns an :class:`anki_vector.util.Distance` instance set to the specified number of millimeters.""" - return Distance(distance_mm=distance_mm) - - -def distance_inches(distance_inches: float): # pylint: disable=redefined-outer-name - """Returns an :class:`anki_vector.util.Distance` instance set to the specified number of inches.""" - return Distance(distance_inches=distance_inches) - - -class Speed: - """Represents a speed. - - This class allows speeds to be measured in millimeters per second. - - The maximum speed is 220 mm/s and is clamped internally. - - Use :func:`speed_mmps` convenience methods to generate - a Speed instance. - - :param speed_mmps: The number of millimeters per second the speed - should represent. - """ - - __slots__ = ('_speed_mmps') - - def __init__(self, speed_mmps: float = None): # pylint: disable=redefined-outer-name - if speed_mmps is None: - raise ValueError("Expected speed_mmps keyword argument") - self._speed_mmps = float(speed_mmps) - - def __repr__(self): - return "<%s %.2f mmps>" % (self.__class__.__name__, self.speed_mmps) - - def __add__(self, other): - if not isinstance(other, Speed): - raise TypeError("Unsupported operand for + expected Speed") - return speed_mmps(self.speed_mmps + other.speed_mmps) - - def __sub__(self, other): - if not isinstance(other, Speed): - raise TypeError("Unsupported operand for - expected Speed") - return speed_mmps(self.speed_mmps - other.speed_mmps) - - def __mul__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported operand for * expected number") - return speed_mmps(self.speed_mmps * other) - - def __truediv__(self, other): - if not isinstance(other, (int, float)): - raise TypeError("Unsupported operand for / expected number") - return speed_mmps(self.speed_mmps / other) - - @property - def speed_mmps(self: float) -> float: # pylint: disable=redefined-outer-name - """The speed in millimeters per second (mmps).""" - return self._speed_mmps - - -def speed_mmps(speed_mmps: float): # pylint: disable=redefined-outer-name - """:class:`anki_vector.util.Speed` instance set to the specified millimeters per second speed.""" - return Speed(speed_mmps=speed_mmps) - - -class BaseOverlay: - """A base overlay is used as a base class for other forms of overlays that can be drawn on top of an image. - - :param line_thickness: The thickness of the line being drawn. - :param line_color: The color of the line to be drawn. - """ - - def __init__(self, line_thickness: int, line_color: tuple): - self._line_thickness: int = line_thickness - self._line_color: tuple = line_color - - @property - def line_thickness(self) -> int: - """The thickness of the line being drawn.""" - return self._line_thickness - - @property - def line_color(self) -> tuple: - """The color of the line to be drawn.""" - return self._line_color - - -class RectangleOverlay(BaseOverlay): - """A rectangle that can be drawn on top of a given image. - - :param width: The width of the rectangle to be drawn. - :param height: The height of the rectangle to be drawn. - :param line_thickness: The thickness of the line being drawn. - :param line_color: The color of the line to be drawn. - """ - - # @TODO Implement overlay using an ImageRect rather than a raw width & height - def __init__(self, width: int, height: int, line_thickness: int = 5, line_color: tuple = (255, 0, 0)): - super().__init__(line_thickness, line_color) - self._width: int = width - self._height: int = height - - @property - def width(self) -> int: - """The width of the rectangle to be drawn.""" - return self._width - - @property - def height(self) -> int: - """The height of the rectangle to be drawn.""" - return self._height - - def apply_overlay(self, image: Image.Image) -> None: - """Draw a rectangle on top of the given image.""" - d = ImageDraw.Draw(image) - - image_width, image_height = image.size - remaining_width = image_width - self.width - remaining_height = image_height - self.height - x1, y1 = remaining_width // 2, remaining_height // 2 - x2, y2 = (image_width - (remaining_width // 2)), (image_height - (remaining_height // 2)) - - for i in range(0, self.line_thickness): - d.rectangle([x1 + i, y1 + i, x2 - i, y2 - i], outline=self.line_color) - - -class Component: - """ Base class for all components.""" - - def __init__(self, robot): - self.logger = get_class_logger(__name__, self) - self._robot = robot - - @property - def robot(self): - return self._robot - - @property - def conn(self): - return self._robot.conn - - @property - def force_async(self): - return self._robot.force_async - - @property - def grpc_interface(self): - """A direct reference to the connected aiogrpc interface. - """ - return self._robot.conn.grpc_interface - - -def read_configuration(serial: str, name: str, logger: logging.Logger) -> dict: - """Open the default conf file, and read it into a :class:`configparser.ConfigParser` - If :code:`serial is not None`, this method will try to find a configuration with serial - number :code:`serial`, and raise an exception otherwise. If :code:`serial is None` and - :code:`name is not None`, this method will try to find a configuration which matches - the provided name, and raise an exception otherwise. If both :code:`serial is None` and - :code:`name is None`, this method will return a configuration if exactly `1` exists, but - if multiple configurations exists, it will raise an exception. - - :param serial: Vector's serial number - :param name: Vector's name - """ - home = Path.home() / ".anki_vector" - conf_file = str(home / "sdk_config.ini") - parser = configparser.ConfigParser(strict=False) - parser.read(conf_file) - - sections = parser.sections() - if not sections: - raise VectorConfigurationException('Could not find the sdk configuration file. Please run `python3 -m anki_vector.configure` to set up your Vector for SDK usage.') - elif (serial is None) and (name is None): - if len(sections) == 1: - serial = sections[0] - logger.warning("No serial number or name provided. Automatically selecting {}".format(serial)) - else: - raise VectorConfigurationException("Found multiple robot serial numbers. " - "Please provide the serial number or name of the Robot you want to control.\n\n" - "Example: ./01_hello_world.py --serial {{robot_serial_number}}") - - config = {k.lower(): v for k, v in parser.items()} - - if serial is not None: - serial = serial.lower() - try: - return config[serial] - except KeyError: - raise VectorConfigurationException("Could not find matching robot info for given serial number: {}. " - "Please check your serial number is correct.\n\n" - "Example: ./01_hello_world.py --serial {{robot_serial_number}}", serial) - else: - for keySerial in config: - for key in config[keySerial]: - if config[keySerial][key] == name: - return config[keySerial] - if config[keySerial][key].lower() == name.lower(): - logger.warning("Using case-insensitive name match found in config. Set 'name' field to match 'Vector-A1B2' format.") - return config[keySerial] - - raise VectorConfigurationException("Could not find matching robot info for given name: {}. " - "Please check your name is correct.\n\n" - "Example: ./01_hello_world.py --name {{robot_name}}", name) +# Copyright (c) 2018 Anki, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License in the file LICENSE.txt or at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Utility functions and classes for the Vector SDK. +""" + +# __all__ should order by constants, event classes, other classes, functions. +__all__ = ['Angle', + 'BaseOverlay', + 'Component', + 'Distance', + 'ImageRect', + 'Matrix44', + 'Pose', + 'Position', + 'Quaternion', + 'RectangleOverlay', + 'Speed', + 'Vector2', + 'Vector3', + 'angle_z_to_quaternion', + 'block_while_none', + 'degrees', + 'distance_mm', + 'distance_inches', + 'get_class_logger', + 'parse_command_args', + 'radians', + 'setup_basic_logging', + 'speed_mmps'] + +import argparse +import configparser +from functools import wraps +import logging +import math +import os +from pathlib import Path +import sys +import time +from typing import Callable, Union + +from .exceptions import VectorConfigurationException, VectorPropertyValueNotReadyException +from .messaging import protocol + +try: + from PIL import Image, ImageDraw +except ImportError: + sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") + + +def parse_command_args(parser: argparse.ArgumentParser = None): + """ + Parses command line arguments. + + Attempts to read the robot serial number from the command line arguments. If no serial number + is specified, we next attempt to read the robot serial number from environment variable ANKI_ROBOT_SERIAL. + If ANKI_ROBOT_SERIAL is specified, the value will be used as the robot's serial number. + + .. code-block:: python + + import anki_vector + + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("--new_param") + args = anki_vector.util.parse_command_args(parser) + + :param parser: To add new command line arguments, + pass an argparse parser with the new options + already defined. Leave empty to use the defaults. + """ + if parser is None: + parser = argparse.ArgumentParser() + parser.add_argument("-s", "--serial", nargs='?', default=os.environ.get('ANKI_ROBOT_SERIAL', None)) + return parser.parse_args() + + +def block_while_none(interval: float = 0.1, max_iterations: int = 50): + """Use this to denote a property that may need some delay before it appears. + + :param interval: how often to check if the property is no longer None + :param max_iterations: how many times to check the property before raising an error + + This will raise a :class:`VectorControlTimeoutException` if the property cannot be retrieved + before :attr:`max_iterations`. + """ + def blocker(func: Callable): + @wraps(func) + def wrapped(*args, **kwargs): + iterations = 0 + result = func(*args, **kwargs) + while result is None: + time.sleep(interval) + iterations += 1 + if iterations > max_iterations: + raise VectorPropertyValueNotReadyException() + result = func(*args, **kwargs) + return result + return wrapped + return blocker + + +def setup_basic_logging(custom_handler: logging.Handler = None, + general_log_level: str = None, + target: object = None): + """Helper to perform basic setup of the Python logger. + + :param custom_handler: provide an external logger for custom logging locations + :param general_log_level: 'DEBUG', 'INFO', 'WARN', 'ERROR' or an equivalent + constant from the :mod:`logging` module. If None then a + value will be read from the VECTOR_LOG_LEVEL environment variable. + :param target: The stream to send the log data to; defaults to stderr + """ + if general_log_level is None: + general_log_level = os.environ.get('VECTOR_LOG_LEVEL', logging.INFO) + + handler = custom_handler + if handler is None: + handler = logging.StreamHandler(stream=target) + formatter = logging.Formatter("%(asctime)s.%(msecs)03d %(name)+25s %(levelname)+7s %(message)s", + "%H:%M:%S") + handler.setFormatter(formatter) + + class LogCleanup(logging.Filter): # pylint: disable=too-few-public-methods + def filter(self, record): + # Drop 'anki_vector' from log messages + record.name = '.'.join(record.name.split('.')[1:]) + # Indent past informational chunk + record.msg = record.msg.replace("\n", f"\n{'':48}") + return True + handler.addFilter(LogCleanup()) + + vector_logger = logging.getLogger('anki_vector') + if not vector_logger.handlers: + vector_logger.addHandler(handler) + vector_logger.setLevel(general_log_level) + + +def get_class_logger(module: str, obj: object) -> logging.Logger: + """Helper to create logger for a given class (and module). + + .. testcode:: + + import anki_vector + + logger = anki_vector.util.get_class_logger("module_name", "object_name") + + :param module: The name of the module to which the object belongs. + :param obj: the object that owns the logger. + """ + return logging.getLogger(".".join([module, type(obj).__name__])) + + +class Vector2: + """Represents a 2D Vector (type/units aren't specified). + + :param x: X component + :param y: Y component + """ + + __slots__ = ('_x', '_y') + + def __init__(self, x: float, y: float): + self._x = float(x) + self._y = float(y) + + def set_to(self, rhs): + """Copy the x and y components of the given Vector2 instance. + + :param rhs: The right-hand-side of this assignment - the + source Vector2 to copy into this Vector2 instance. + """ + self._x = float(rhs.x) + self._y = float(rhs.y) + + @property + def x(self) -> float: + """The x component.""" + return self._x + + @property + def y(self) -> float: + """The y component.""" + return self._y + + @property + def x_y(self): + """tuple (float, float): The X, Y elements of the Vector2 (x,y)""" + return self._x, self._y + + def __repr__(self): + return "<%s x: %.2f y: %.2f>" % (self.__class__.__name__, self.x, self.y) + + def __add__(self, other): + if not isinstance(other, Vector2): + raise TypeError("Unsupported operand for + expected Vector2") + return Vector2(self.x + other.x, self.y + other.y) + + def __sub__(self, other): + if not isinstance(other, Vector2): + raise TypeError("Unsupported operand for - expected Vector2") + return Vector2(self.x - other.x, self.y - other.y) + + def __mul__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported operand for * expected number") + return Vector2(self.x * other, self.y * other) + + def __truediv__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported operand for / expected number") + return Vector2(self.x / other, self.y / other) + + +class Vector3: + """Represents a 3D Vector (type/units aren't specified). + + :param x: X component + :param y: Y component + :param z: Z component + """ + + __slots__ = ('_x', '_y', '_z') + + def __init__(self, x: float, y: float, z: float): + self._x = float(x) + self._y = float(y) + self._z = float(z) + + def set_to(self, rhs): + """Copy the x, y and z components of the given Vector3 instance. + + :param rhs: The right-hand-side of this assignment - the + source Vector3 to copy into this Vector3 instance. + """ + self._x = float(rhs.x) + self._y = float(rhs.y) + self._z = float(rhs.z) + + @property + def x(self) -> float: + """The x component.""" + return self._x + + @property + def y(self) -> float: + """The y component.""" + return self._y + + @property + def z(self) -> float: + """The z component.""" + return self._z + + @property + def magnitude_squared(self) -> float: + """float: The magnitude of the Vector3 instance""" + return self._x**2 + self._y**2 + self._z**2 + + @property + def magnitude(self) -> float: + """The magnitude of the Vector3 instance""" + return math.sqrt(self.magnitude_squared) + + @property + def normalized(self): + """A Vector3 instance with the same direction and unit magnitude""" + mag = self.magnitude + if mag == 0: + return Vector3(0, 0, 0) + return Vector3(self._x / mag, self._y / mag, self._z / mag) + + def dot(self, other): + """The dot product of this and another Vector3 instance""" + if not isinstance(other, Vector3): + raise TypeError("Unsupported argument for dot product, expected Vector3") + return self._x * other.x + self._y * other.y + self._z * other.z + + def cross(self, other): + """The cross product of this and another Vector3 instance""" + if not isinstance(other, Vector3): + raise TypeError("Unsupported argument for cross product, expected Vector3") + + return Vector3( + self._y * other.z - self._z * other.y, + self._z * other.x - self._x * other.z, + self._x * other.y - self._y * other.x) + + @property + def x_y_z(self): + """tuple (float, float, float): The X, Y, Z elements of the Vector3 (x,y,z)""" + return self._x, self._y, self._z + + def __repr__(self): + return f"<{self.__class__.__name__} x: {self.x:.2f} y: {self.y:.2f} z: {self.z:.2f}>" + + def __add__(self, other): + if not isinstance(other, Vector3): + raise TypeError("Unsupported operand for +, expected Vector3") + return Vector3(self.x + other.x, self.y + other.y, self.z + other.z) + + def __sub__(self, other): + if not isinstance(other, Vector3): + raise TypeError("Unsupported operand for -, expected Vector3") + return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) + + def __mul__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported operand for * expected number") + return Vector3(self.x * other, self.y * other, self.z * other) + + def __truediv__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported operand for / expected number") + return Vector3(self.x / other, self.y / other, self.z / other) + + +class Angle: + """Represents an angle. + + Use the :func:`degrees` or :func:`radians` convenience methods to generate + an Angle instance. + + :param radians: The number of radians the angle should represent + (cannot be combined with ``degrees``) + :param degrees: The number of degress the angle should represent + (cannot be combined with ``radians``) + """ + + __slots__ = ('_radians') + + def __init__(self, radians: float = None, degrees: float = None): # pylint: disable=redefined-outer-name + if radians is None and degrees is None: + raise ValueError("Expected either the degrees or radians keyword argument") + if radians and degrees: + raise ValueError("Expected either the degrees or radians keyword argument, not both") + + if degrees is not None: + radians = degrees * math.pi / 180 + self._radians = float(radians) + + @property + def radians(self) -> float: # pylint: disable=redefined-outer-name + """The angle in radians.""" + return self._radians + + @property + def degrees(self) -> float: # pylint: disable=redefined-outer-name + """The angle in degrees.""" + return self._radians / math.pi * 180 + + def __repr__(self): + return f"<{self.__class__.__name__} Radians: {self.radians:.2f} Degrees: {self.degrees:.2f}>" + + def __add__(self, other): + if not isinstance(other, Angle): + raise TypeError("Unsupported type for + expected Angle") + return Angle(radians=(self.radians + other.radians)) + + def __sub__(self, other): + if not isinstance(other, Angle): + raise TypeError("Unsupported type for - expected Angle") + return Angle(radians=(self.radians - other.radians)) + + def __mul__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported type for * expected number") + return Angle(radians=(self.radians * other)) + + def __truediv__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported type for / expected number") + return radians(self.radians / other) + + def _cmp_int(self, other): + if not isinstance(other, Angle): + raise TypeError("Unsupported type for comparison expected Angle") + return self.radians - other.radians + + def __eq__(self, other): + return self._cmp_int(other) == 0 + + def __ne__(self, other): + return self._cmp_int(other) != 0 + + def __gt__(self, other): + return self._cmp_int(other) > 0 + + def __lt__(self, other): + return self._cmp_int(other) < 0 + + def __ge__(self, other): + return self._cmp_int(other) >= 0 + + def __le__(self, other): + return self._cmp_int(other) <= 0 + + @property + def abs_value(self): + """:class:`anki_vector.util.Angle`: The absolute value of the angle. + + If the Angle is positive then it returns a copy of this Angle, otherwise it returns -Angle. + """ + return Angle(radians=abs(self._radians)) + + +def angle_z_to_quaternion(angle_z: Angle): + """This function converts an angle in the z axis (Euler angle z component) to a quaternion. + + :param angle_z: The z axis angle. + + Returns: + q0, q1, q2, q3 (float, float, float, float): A tuple with all the members + of a quaternion defined by angle_z. + """ + + # Define the quaternion to be converted from a Euler angle (x,y,z) of 0,0,angle_z + # These equations have their original equations above, and simplified implemented + # q0 = cos(x/2)*cos(y/2)*cos(z/2) + sin(x/2)*sin(y/2)*sin(z/2) + q0 = math.cos(angle_z.radians / 2) + # q1 = sin(x/2)*cos(y/2)*cos(z/2) - cos(x/2)*sin(y/2)*sin(z/2) + q1 = 0 + # q2 = cos(x/2)*sin(y/2)*cos(z/2) + sin(x/2)*cos(y/2)*sin(z/2) + q2 = 0 + # q3 = cos(x/2)*cos(y/2)*sin(z/2) - sin(x/2)*sin(y/2)*cos(z/2) + q3 = math.sin(angle_z.radians / 2) + return q0, q1, q2, q3 + + +def degrees(degrees: float) -> Angle: # pylint: disable=redefined-outer-name + """An Angle instance set to the specified number of degrees.""" + return Angle(degrees=degrees) + + +def radians(radians: float) -> Angle: # pylint: disable=redefined-outer-name + """An Angle instance set to the specified number of radians.""" + return Angle(radians=radians) + + +class Matrix44: + """A 4x4 Matrix for representing the rotation and/or position of an object in the world. + + Can be generated from a :class:`Quaternion` for a pure rotation matrix, or + combined with a position for a full translation matrix, as done by + :meth:`Pose.to_matrix`. + """ + __slots__ = ('m00', 'm10', 'm20', 'm30', + 'm01', 'm11', 'm21', 'm31', + 'm02', 'm12', 'm22', 'm32', + 'm03', 'm13', 'm23', 'm33') + + def __init__(self, + m00: float, m10: float, m20: float, m30: float, + m01: float, m11: float, m21: float, m31: float, + m02: float, m12: float, m22: float, m32: float, + m03: float, m13: float, m23: float, m33: float): + self.m00 = float(m00) + self.m10 = float(m10) + self.m20 = float(m20) + self.m30 = float(m30) + + self.m01 = float(m01) + self.m11 = float(m11) + self.m21 = float(m21) + self.m31 = float(m31) + + self.m02 = float(m02) + self.m12 = float(m12) + self.m22 = float(m22) + self.m32 = float(m32) + + self.m03 = float(m03) + self.m13 = float(m13) + self.m23 = float(m23) + self.m33 = float(m33) + + def __repr__(self): + return ("<%s: " + "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f " + "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f>" % ( + self.__class__.__name__, *self.in_row_order)) + + @property + def tabulated_string(self) -> str: + """A multi-line string formatted with tabs to show the matrix contents.""" + return ("%.1f\t%.1f\t%.1f\t%.1f\n" + "%.1f\t%.1f\t%.1f\t%.1f\n" + "%.1f\t%.1f\t%.1f\t%.1f\n" + "%.1f\t%.1f\t%.1f\t%.1f" % self.in_row_order) + + @property + def in_row_order(self): + """tuple of 16 floats: The contents of the matrix in row order.""" + return self.m00, self.m01, self.m02, self.m03,\ + self.m10, self.m11, self.m12, self.m13,\ + self.m20, self.m21, self.m22, self.m23,\ + self.m30, self.m31, self.m32, self.m33 + + @property + def in_column_order(self): + """tuple of 16 floats: The contents of the matrix in column order.""" + return self.m00, self.m10, self.m20, self.m30,\ + self.m01, self.m11, self.m21, self.m31,\ + self.m02, self.m12, self.m22, self.m32,\ + self.m03, self.m13, self.m23, self.m33 + + @property + def forward_xyz(self): + """tuple of 3 floats: The x,y,z components representing the matrix's forward vector.""" + return self.m00, self.m01, self.m02 + + @property + def left_xyz(self): + """tuple of 3 floats: The x,y,z components representing the matrix's left vector.""" + return self.m10, self.m11, self.m12 + + @property + def up_xyz(self): + """tuple of 3 floats: The x,y,z components representing the matrix's up vector.""" + return self.m20, self.m21, self.m22 + + @property + def pos_xyz(self): + """tuple of 3 floats: The x,y,z components representing the matrix's position vector.""" + return self.m30, self.m31, self.m32 + + def set_forward(self, x: float, y: float, z: float): + """Set the x,y,z components representing the matrix's forward vector. + + :param x: The X component. + :param y: The Y component. + :param z: The Z component. + """ + self.m00 = float(x) + self.m01 = float(y) + self.m02 = float(z) + + def set_left(self, x: float, y: float, z: float): + """Set the x,y,z components representing the matrix's left vector. + + :param x: The X component. + :param y: The Y component. + :param z: The Z component. + """ + self.m10 = float(x) + self.m11 = float(y) + self.m12 = float(z) + + def set_up(self, x: float, y: float, z: float): + """Set the x,y,z components representing the matrix's up vector. + + :param x: The X component. + :param y: The Y component. + :param z: The Z component. + """ + self.m20 = float(x) + self.m21 = float(y) + self.m22 = float(z) + + def set_pos(self, x: float, y: float, z: float): + """Set the x,y,z components representing the matrix's position vector. + + :param x: The X component. + :param y: The Y component. + :param z: The Z component. + """ + self.m30 = float(x) + self.m31 = float(y) + self.m32 = float(z) + + +class Quaternion: + """Represents the rotation of an object in the world.""" + + __slots__ = ('_q0', '_q1', '_q2', '_q3') + + def __init__(self, q0: float = None, q1: float = None, q2: float = None, q3: float = None, angle_z: Angle = None): + is_quaternion = q0 is not None and q1 is not None and q2 is not None and q3 is not None + + if not is_quaternion and angle_z is None: + raise ValueError("Expected either the q0 q1 q2 and q3 or angle_z keyword arguments") + if is_quaternion and angle_z: + raise ValueError("Expected either the q0 q1 q2 and q3 or angle_z keyword argument," + "not both") + if angle_z is not None: + if not isinstance(angle_z, Angle): + raise TypeError("Unsupported type for angle_z expected Angle") + q0, q1, q2, q3 = angle_z_to_quaternion(angle_z) + + self._q0 = float(q0) + self._q1 = float(q1) + self._q2 = float(q2) + self._q3 = float(q3) + + @property + def q0(self) -> float: + """The q0 (w) value of the quaternion.""" + return self._q0 + + @property + def q1(self) -> float: + """The q1 (i) value of the quaternion.""" + return self._q1 + + @property + def q2(self) -> float: + """The q2 (j) value of the quaternion.""" + return self._q2 + + @property + def q3(self) -> float: + """The q3 (k) value of the quaternion.""" + return self._q3 + + @property + def angle_z(self) -> Angle: + """An Angle instance representing the z Euler component of the object's rotation. + + Defined as the rotation in the z axis. + """ + q0, q1, q2, q3 = self.q0_q1_q2_q3 + return Angle(radians=math.atan2(2 * (q1 * q2 + q0 * q3), 1 - 2 * (q2**2 + q3**2))) + + @property + def q0_q1_q2_q3(self): + """tuple of float: Contains all elements of the quaternion (q0,q1,q2,q3)""" + return self._q0, self._q1, self._q2, self._q3 + + def to_matrix(self, pos_x: float = 0.0, pos_y: float = 0.0, pos_z: float = 0.0): + """Convert the Quaternion to a 4x4 matrix representing this rotation. + + A position can also be provided to generate a full translation matrix. + + :param pos_x: The x component for the position. + :param pos_y: The y component for the position. + :param pos_z: The z component for the position. + + Returns: + :class:`anki_vector.util.Matrix44`: A matrix representing this Quaternion's + rotation, with the provided position (which defaults to 0,0,0). + """ + # See https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation + q0q0 = self.q0 * self.q0 + q1q1 = self.q1 * self.q1 + q2q2 = self.q2 * self.q2 + q3q3 = self.q3 * self.q3 + + q0x2 = self.q0 * 2.0 # saves 2 multiplies + q0q1x2 = q0x2 * self.q1 + q0q2x2 = q0x2 * self.q2 + q0q3x2 = q0x2 * self.q3 + q1x2 = self.q1 * 2.0 # saves 1 multiply + q1q2x2 = q1x2 * self.q2 + q1q3x2 = q1x2 * self.q3 + q2q3x2 = 2.0 * self.q2 * self.q3 + + m00 = (q0q0 + q1q1 - q2q2 - q3q3) + m01 = (q1q2x2 + q0q3x2) + m02 = (q1q3x2 - q0q2x2) + + m10 = (q1q2x2 - q0q3x2) + m11 = (q0q0 - q1q1 + q2q2 - q3q3) + m12 = (q0q1x2 + q2q3x2) + + m20 = (q0q2x2 + q1q3x2) + m21 = (q2q3x2 - q0q1x2) + m22 = (q0q0 - q1q1 - q2q2 + q3q3) + + return Matrix44(m00, m10, m20, float(pos_x), + m01, m11, m21, float(pos_y), + m02, m12, m22, float(pos_z), + 0.0, 0.0, 0.0, 1.0) + + def __repr__(self): + return (f"<{self.__class__.__name__} q0: {self.q0:.2f} q1: {self.q1:.2f}" + f" q2: {self.q2:.2f} q3: {self.q3:.2f} {self.angle_z}>") + + +class Position(Vector3): + """Represents the position of an object in the world. + + A position consists of its x, y and z values in millimeters. + + :param x: X position in millimeters + :param y: Y position in millimeters + :param z: Z position in millimeters + """ + __slots__ = () + + +class Pose: + """Represents where an object is in the world. + + Whenever Vector is delocalized (i.e. whenever Vector no longer knows + where he is - e.g. when he's picked up), Vector creates a new pose starting at + (0,0,0) with no rotation, with origin_id incremented to show that these poses + cannot be compared with earlier ones. As Vector drives around, his pose (and the + pose of other objects he observes - e.g. faces, his LightCube, charger, etc.) is relative to this + initial position and orientation. + + The coordinate space is relative to Vector, where Vector's origin is the + point on the ground between Vector's two front wheels. The X axis is Vector's forward direction, + the Y axis is to Vector's left, and the Z axis is up. + + Only poses of the same origin_id can safely be compared or operated on. + + .. testcode:: + + import anki_vector + from anki_vector.util import degrees, Pose + + with anki_vector.Robot() as robot: + pose = Pose(x=50, y=0, z=0, angle_z=anki_vector.util.Angle(degrees=0)) + robot.behavior.go_to_pose(pose) + """ + __slots__ = ('_position', '_rotation', '_origin_id') + + def __init__(self, x: float, y: float, z: float, q0: float = None, q1: float = None, q2: float = None, q3: float = None, + angle_z: Angle = None, origin_id: int = -1): + self._position = Position(x, y, z) + self._rotation = Quaternion(q0, q1, q2, q3, angle_z) + self._origin_id = origin_id + + @property + def position(self) -> Position: + """The position component of this pose.""" + return self._position + + @property + def rotation(self) -> Quaternion: + """The rotation component of this pose.""" + return self._rotation + + @property + def origin_id(self) -> int: + """An ID maintained by the robot which represents which coordinate frame this pose is in.""" + return self._origin_id + + def __repr__(self): + return (f"<{self.__class__.__name__}: {self._position}" + f" {self._rotation} >") + + def define_pose_relative_this(self, new_pose): + """Creates a new pose such that new_pose's origin is now at the location of this pose. + + :param anki_vector.util.Pose new_pose: The pose which origin is being changed. + + Returns: + A :class:`anki_vector.util.Pose` object for which the origin was this pose's origin. + """ + if not isinstance(new_pose, Pose): + raise TypeError("Unsupported type for new_origin, must be of type Pose") + x, y, z = self.position.x_y_z + angle_z = self.rotation.angle_z + new_x, new_y, new_z = new_pose.position.x_y_z + new_angle_z = new_pose.rotation.angle_z + + cos_angle = math.cos(angle_z.radians) + sin_angle = math.sin(angle_z.radians) + res_x = x + (cos_angle * new_x) - (sin_angle * new_y) + res_y = y + (sin_angle * new_x) + (cos_angle * new_y) + res_z = z + new_z + res_angle = angle_z + new_angle_z + return Pose(res_x, + res_y, + res_z, + angle_z=res_angle, + origin_id=self._origin_id) + + @property + def is_valid(self) -> bool: + """True if this is a valid, usable pose.""" + return self.origin_id >= 0 + + def is_comparable(self, other_pose) -> bool: + """Checks whether these two poses are comparable. + + Poses are comparable if they're valid and having matching origin IDs. + + :param other_pose: The other pose to compare against. Type is Pose. + + Returns: + True if the two poses are comparable, False otherwise. + """ + return (self.is_valid and other_pose.is_valid + and (self.origin_id == other_pose.origin_id)) + + def to_matrix(self) -> Matrix44: + """Convert the Pose to a Matrix44. + + Returns: + A matrix representing this Pose's position and rotation. + """ + return self.rotation.to_matrix(*self.position.x_y_z) + + def to_proto_pose_struct(self) -> protocol.PoseStruct: + """Converts the Pose into the robot's messaging pose format. + """ + return protocol.PoseStruct( + x=self._position.x, + y=self._position.y, + z=self._position.z, + q0=self._rotation.q0, + q1=self._rotation.q1, + q2=self._rotation.q2, + q3=self._rotation.q3, + origin_id=self._origin_id) + + +class ImageRect: + '''Defines a bounding box within an image frame. + + This is used when objects and faces are observed to denote where in + the robot's camera view the object or face actually appears. It's then + used by the annotate module to show an outline of a box around + the object or face. + ''' + + __slots__ = ('_x_top_left', '_y_top_left', '_width', '_height') + + def __init__(self, x_top_left: float, y_top_left: float, width: float, height: float): + self._x_top_left = float(x_top_left) + self._y_top_left = float(y_top_left) + self._width = float(width) + self._height = float(height) + + @property + def x_top_left(self) -> float: + """The top left x value of where the object was last visible within Vector's camera view.""" + return self._x_top_left + + @property + def y_top_left(self) -> float: + """The top left y value of where the object was last visible within Vector's camera view.""" + return self._y_top_left + + @property + def width(self) -> float: + """The width of the object from when it was last visible within Vector's camera view.""" + return self._width + + @property + def height(self) -> float: + """The height of the object from when it was last visible within Vector's camera view.""" + return self._height + + def scale_by(self, scale_multiplier: Union[int, float]) -> None: + """Scales the image rectangle by the multiplier provided.""" + if not isinstance(scale_multiplier, (int, float)): + raise TypeError("Unsupported operand for * expected number") + self._x_top_left *= scale_multiplier + self._y_top_left *= scale_multiplier + self._width *= scale_multiplier + self._height *= scale_multiplier + + +class Distance: + """Represents a distance. + + The class allows distances to be returned in either millimeters or inches. + + Use the :func:`distance_inches` or :func:`distance_mm` convenience methods to generate + a Distance instance. + + :param distance_mm: The number of millimeters the distance should + represent (cannot be combined with ``distance_inches``). + :param distance_inches: The number of inches the distance should + represent (cannot be combined with ``distance_mm``). + """ + + __slots__ = ('_distance_mm') + + def __init__(self, distance_mm: float = None, distance_inches: float = None): # pylint: disable=redefined-outer-name + if distance_mm is None and distance_inches is None: + raise ValueError("Expected either the distance_mm or distance_inches keyword argument") + if distance_mm and distance_inches: + raise ValueError("Expected either the distance_mm or distance_inches keyword argument, not both") + + if distance_inches is not None: + distance_mm = distance_inches * 25.4 + self._distance_mm = float(distance_mm) + + def __repr__(self): + return "<%s %.2f mm (%.2f inches)>" % (self.__class__.__name__, self.distance_mm, self.distance_inches) + + def __add__(self, other): + if not isinstance(other, Distance): + raise TypeError("Unsupported operand for + expected Distance") + return distance_mm(self.distance_mm + other.distance_mm) + + def __sub__(self, other): + if not isinstance(other, Distance): + raise TypeError("Unsupported operand for - expected Distance") + return distance_mm(self.distance_mm - other.distance_mm) + + def __mul__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported operand for * expected number") + return distance_mm(self.distance_mm * other) + + def __truediv__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported operand for / expected number") + return distance_mm(self.distance_mm / other) + + @property + def distance_mm(self) -> float: # pylint: disable=redefined-outer-name + """The distance in millimeters""" + return self._distance_mm + + @property + def distance_inches(self) -> float: # pylint: disable=redefined-outer-name + return self._distance_mm / 25.4 + + +def distance_mm(distance_mm: float): # pylint: disable=redefined-outer-name + """Returns an :class:`anki_vector.util.Distance` instance set to the specified number of millimeters.""" + return Distance(distance_mm=distance_mm) + + +def distance_inches(distance_inches: float): # pylint: disable=redefined-outer-name + """Returns an :class:`anki_vector.util.Distance` instance set to the specified number of inches.""" + return Distance(distance_inches=distance_inches) + + +class Speed: + """Represents a speed. + + This class allows speeds to be measured in millimeters per second. + + The maximum speed is 220 mm/s and is clamped internally. + + Use :func:`speed_mmps` convenience methods to generate + a Speed instance. + + :param speed_mmps: The number of millimeters per second the speed + should represent. + """ + + __slots__ = ('_speed_mmps') + + def __init__(self, speed_mmps: float = None): # pylint: disable=redefined-outer-name + if speed_mmps is None: + raise ValueError("Expected speed_mmps keyword argument") + self._speed_mmps = float(speed_mmps) + + def __repr__(self): + return "<%s %.2f mmps>" % (self.__class__.__name__, self.speed_mmps) + + def __add__(self, other): + if not isinstance(other, Speed): + raise TypeError("Unsupported operand for + expected Speed") + return speed_mmps(self.speed_mmps + other.speed_mmps) + + def __sub__(self, other): + if not isinstance(other, Speed): + raise TypeError("Unsupported operand for - expected Speed") + return speed_mmps(self.speed_mmps - other.speed_mmps) + + def __mul__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported operand for * expected number") + return speed_mmps(self.speed_mmps * other) + + def __truediv__(self, other): + if not isinstance(other, (int, float)): + raise TypeError("Unsupported operand for / expected number") + return speed_mmps(self.speed_mmps / other) + + @property + def speed_mmps(self: float) -> float: # pylint: disable=redefined-outer-name + """The speed in millimeters per second (mmps).""" + return self._speed_mmps + + +def speed_mmps(speed_mmps: float): # pylint: disable=redefined-outer-name + """:class:`anki_vector.util.Speed` instance set to the specified millimeters per second speed.""" + return Speed(speed_mmps=speed_mmps) + + +class BaseOverlay: + """A base overlay is used as a base class for other forms of overlays that can be drawn on top of an image. + + :param line_thickness: The thickness of the line being drawn. + :param line_color: The color of the line to be drawn. + """ + + def __init__(self, line_thickness: int, line_color: tuple): + self._line_thickness: int = line_thickness + self._line_color: tuple = line_color + + @property + def line_thickness(self) -> int: + """The thickness of the line being drawn.""" + return self._line_thickness + + @property + def line_color(self) -> tuple: + """The color of the line to be drawn.""" + return self._line_color + + +class RectangleOverlay(BaseOverlay): + """A rectangle that can be drawn on top of a given image. + + :param width: The width of the rectangle to be drawn. + :param height: The height of the rectangle to be drawn. + :param line_thickness: The thickness of the line being drawn. + :param line_color: The color of the line to be drawn. + """ + + # @TODO Implement overlay using an ImageRect rather than a raw width & height + def __init__(self, width: int, height: int, line_thickness: int = 5, line_color: tuple = (255, 0, 0)): + super().__init__(line_thickness, line_color) + self._width: int = width + self._height: int = height + + @property + def width(self) -> int: + """The width of the rectangle to be drawn.""" + return self._width + + @property + def height(self) -> int: + """The height of the rectangle to be drawn.""" + return self._height + + def apply_overlay(self, image: Image.Image) -> None: + """Draw a rectangle on top of the given image.""" + d = ImageDraw.Draw(image) + + image_width, image_height = image.size + remaining_width = image_width - self.width + remaining_height = image_height - self.height + x1, y1 = remaining_width // 2, remaining_height // 2 + x2, y2 = (image_width - (remaining_width // 2)), (image_height - (remaining_height // 2)) + + for i in range(0, self.line_thickness): + d.rectangle([x1 + i, y1 + i, x2 - i, y2 - i], outline=self.line_color) + + +class Component: + """ Base class for all components.""" + + def __init__(self, robot): + self.logger = get_class_logger(__name__, self) + self._robot = robot + + @property + def robot(self): + return self._robot + + @property + def conn(self): + return self._robot.conn + + @property + def force_async(self): + return self._robot.force_async + + @property + def grpc_interface(self): + """A direct reference to the connected aiogrpc interface. + """ + return self._robot.conn.grpc_interface + + +def read_configuration(serial: str, name: str, logger: logging.Logger, escape_pod:bool = False) -> dict: + """Open the default conf file, and read it into a :class:`configparser.ConfigParser` + If :code:`serial is not None`, this method will try to find a configuration with serial + number :code:`serial`, and raise an exception otherwise. If :code:`serial is None` and + :code:`name is not None`, this method will try to find a configuration which matches + the provided name, and raise an exception otherwise. If both :code:`serial is None` and + :code:`name is None`, this method will return a configuration if exactly `1` exists, but + if multiple configurations exists, it will raise an exception. + + :param serial: Vector's serial number + :param name: Vector's name + """ + home = Path.home() / ".anki_vector" + conf_file = str(home / "sdk_config.ini") + parser = configparser.ConfigParser(strict=False) + parser.read(conf_file) + + sections = parser.sections() + if not sections: + if escape_pod: + return {} + raise VectorConfigurationException('Could not find the sdk configuration file. Please run `python3 -m anki_vector.configure` or `python3 -m anki_vector.configure_pod` to set up your Vector for SDK usage.') + elif (serial is None) and (name is None): + if len(sections) == 1: + serial = sections[0] + logger.warning("No serial number or name provided. Automatically selecting {}".format(serial)) + else: + raise VectorConfigurationException("Found multiple robot serial numbers. " + "Please provide the serial number or name of the Robot you want to control.\n\n" + "Example: ./01_hello_world.py --serial {{robot_serial_number}}") + + config = {k.lower(): v for k, v in parser.items()} + + if serial is not None: + serial = serial.lower() + try: + return config[serial] + except KeyError: + raise VectorConfigurationException("Could not find matching robot info for given serial number: {}. " + "Please check your serial number is correct.\n\n" + "Example: ./01_hello_world.py --serial {{robot_serial_number}}", serial) + else: + for keySerial in config: + for key in config[keySerial]: + if config[keySerial][key] == name: + return config[keySerial] + if config[keySerial][key].lower() == name.lower(): + logger.warning("Using case-insensitive name match found in config. Set 'name' field to match 'Vector-A1B2' format.") + return config[keySerial] + + raise VectorConfigurationException("Could not find matching robot info for given name: {}. " + "Please check your name is correct.\n\n" + "Example: ./01_hello_world.py --name {{robot_name}}", name) diff --git a/anki_vector/version.py b/anki_vector/version.py index 252ecf8..ce4b97d 100644 --- a/anki_vector/version.py +++ b/anki_vector/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.6.1.dev0" +__version__ = "0.7.2.dev6" diff --git a/anki_vector/vision.py b/anki_vector/vision.py index d37ef6a..84eb27e 100644 --- a/anki_vector/vision.py +++ b/anki_vector/vision.py @@ -43,8 +43,7 @@ def __init__(self, robot): self._detect_faces = False self._detect_custom_objects = False - # TODO implement - # self._detect_motion = False + self._detect_motion = False self._display_camera_feed_on_face = False robot.events.subscribe(self._handle_mirror_mode_disabled_event, events.Events.mirror_mode_disabled) @@ -62,7 +61,7 @@ def _handle_mirror_mode_disabled_event(self, _robot, _event_type, _msg): def _handle_vision_modes_auto_disabled_event(self, _robot, _event_type, _msg): self._detect_faces = False self._detect_custom_objects = False - # self._detect_motion = False + self._detect_motion = False self._display_camera_feed_on_face = False @property @@ -73,10 +72,9 @@ def detect_faces(self): def detect_custom_objects(self): return self._detect_custom_objects - # TODO implement - # @property - # def detect_motion(self): - # return self._detect_motion + @property + def detect_motion(self): + return self._detect_motion @property def display_camera_feed_on_face(self): @@ -88,11 +86,12 @@ async def disable_all_vision_modes(self): await self.enable_face_detection(False, False) if self.detect_custom_objects: await self.enable_custom_object_detection(False) - # if self.detect_motion: - # await self.enable_motion_detection(False) + if self.detect_motion: + await self.enable_motion_detection(False) if self.display_camera_feed_on_face: await self.enable_display_camera_feed_on_face(False) + # TODO: add return type hint @connection.on_connection_thread() async def enable_custom_object_detection(self, detect_custom_objects: bool = True): """Enable custom object detection on the robot's camera. @@ -115,6 +114,7 @@ async def enable_custom_object_detection(self, detect_custom_objects: bool = Tru enable_marker_detection_request = protocol.EnableMarkerDetectionRequest(enable=detect_custom_objects) return await self.grpc_interface.EnableMarkerDetection(enable_marker_detection_request) + # TODO: add return type hint @connection.on_connection_thread() async def enable_face_detection( self, @@ -142,24 +142,44 @@ async def enable_face_detection( enable_gaze_detection=False) return await self.grpc_interface.EnableFaceDetection(enable_face_detection_request) - # TODO implement - # @connection.on_connection_thread() - # async def enable_motion_detection(self, detect_motion: bool = True): - # """Enable motion detection on the robot's camera - # - # :param detect_motion: Specify whether we want the robot to detect motion. - # - # .. testcode:: - # - # import anki_vector - # with anki_vector.Robot() as robot: - # robot.vision.enable_motion_detection(detect_motion=True) - # """ - # self._detect_motion = detect_motion - - # enable_motion_detection_request = protocol.EnableMotionDetectionRequest(enable=detect_motion) - # return await self.grpc_interface.EnableMotionDetection(enable_motion_detection_request) + @connection.on_connection_thread() + async def enable_motion_detection(self, detect_motion: bool = True): + """Enable motion detection on the robot's camera + + :param detect_motion: Specify whether we want the robot to detect motion. + + .. testcode:: + + import time + + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def on_robot_observed_motion(robot, event_type, event): + print("Robot observed motion") + + with anki_vector.Robot(show_viewer=True) as robot: + robot.events.subscribe(on_robot_observed_motion, Events.robot_observed_motion) + + # If necessary, move Vector's Head and Lift to make it easy to see his face + robot.behavior.set_head_angle(degrees(45.0)) + robot.behavior.set_lift_height(0.0) + + robot.vision.enable_motion_detection(detect_motion=True) + + print("Vector is waiting to see motion. Make some movement within Vector's camera view") + + time.sleep(3.0) + + robot.events.unsubscribe(on_robot_observed_motion, Events.robot_observed_motion) + """ + self._detect_motion = detect_motion + + enable_motion_detection_request = protocol.EnableMotionDetectionRequest(enable=detect_motion) + return await self.grpc_interface.EnableMotionDetection(enable_motion_detection_request) + # TODO: add return type hint @connection.on_connection_thread() async def enable_display_camera_feed_on_face(self, display_camera_feed_on_face: bool = True): """Display the robot's camera feed on its face along with any detections (if enabled) diff --git a/anki_vector/world.py b/anki_vector/world.py index 60485e0..8d1fb58 100644 --- a/anki_vector/world.py +++ b/anki_vector/world.py @@ -788,6 +788,7 @@ def create_custom_fixed_object(self, self._objects[fixed_custom_object.object_id] = fixed_custom_object return fixed_custom_object + # TODO: add return type hint @connection.on_connection_thread(requires_control=False) async def _create_custom_fixed_object(self, pose: util.Pose, diff --git a/examples/tutorials/00_hello_escapepod.py b/examples/tutorials/00_hello_escapepod.py new file mode 100644 index 0000000..b3e05db --- /dev/null +++ b/examples/tutorials/00_hello_escapepod.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2021 cyb3rdog +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License in the file LICENSE.txt or at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Hello Escape Pod + +Make Vector say 'Hello Escape Pod' in this simple Vector SDK example program. +""" + +import anki_vector + + +def main(): + with anki_vector.Robot(ip="192.168.0.148", escape_pod=True) as robot: + print("Say 'Hello Escape Pod'...") + robot.behavior.say_text("Hello Escape Pod") + + +if __name__ == "__main__": + main() diff --git a/examples/tutorials/10_play_audio.py b/examples/tutorials/10_play_audio.py index 499989f..3818a72 100755 --- a/examples/tutorials/10_play_audio.py +++ b/examples/tutorials/10_play_audio.py @@ -28,8 +28,8 @@ def main(): # https://github.com/anki/vector-python-sdk/blob/master/examples/sounds/vector_bell_whistle.wav # # Paste these two wav files next to this tutorial to play sounds. - robot.audio.stream_wav_file("vector_bell_whistle.wav", 75) - robot.audio.stream_wav_file("vector_alert.wav", 75) + robot.audio.stream_wav_file("../sounds/vector_bell_whistle.wav", 75) + robot.audio.stream_wav_file("../sounds/vector_alert.wav", 75) if __name__ == "__main__": diff --git a/setup.py b/setup.py index 9121cf6..5966abb 100644 --- a/setup.py +++ b/setup.py @@ -1,86 +1,98 @@ -# Copyright (c) 2018 Anki, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License in the file LICENSE.txt or at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -The Vector SDK gives you direct access to Vector's unprecedented set of advanced sensors, AI capabilities, and robotics technologies including computer vision, intelligent mapping and navigation, and a groundbreaking collection of expressive animations. - -It's powerful but easy to use, complex but not complicated, and versatile enough to be used across a wide range of domains including enterprise, research, and entertainment. Find out more at https://developer.anki.com - -Vector SDK documentation: https://developer.anki.com/vector/docs/ - -Official developer forum: https://forums.anki.com/ - -Requirements: - * Python 3.6.1 or later -""" - -import os.path -import sys -from setuptools import setup - -if sys.version_info < (3, 6, 1): - sys.exit('The Vector SDK requires Python 3.6.1 or later') - -HERE = os.path.abspath(os.path.dirname(__file__)) - -def fetch_version(): - """Get the version from the package""" - with open(os.path.join(HERE, 'anki_vector', 'version.py')) as version_file: - versions = {} - exec(version_file.read(), versions) - return versions - -VERSION_DATA = fetch_version() -VERSION = VERSION_DATA['__version__'] - -def get_requirements(): - """Load the requirements from requirements.txt into a list""" - reqs = [] - with open(os.path.join(HERE, 'requirements.txt')) as requirements_file: - for line in requirements_file: - reqs.append(line.strip()) - return reqs - -setup( - name='anki_vector', - version=VERSION, - description="The Vector SDK is a connected vision- and character-based robotics platform for everyone.", - long_description=__doc__, - url='https://developer.anki.com', - author='Anki, Inc', - author_email='developer@anki.com', - license='Apache License, Version 2.0', - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3.6', - ], - zip_safe=True, - keywords='anki vector robot robotics sdk ai vision'.split(), - packages=['anki_vector', 'anki_vector.camera_viewer', 'anki_vector.configure', 'anki_vector.messaging', 'anki_vector.opengl', 'anki_vector.reserve_control'], - package_data={ - 'anki_vector': ['LICENSE.txt', 'opengl/assets/*.obj', 'opengl/assets/*.mtl', 'opengl/assets/*.jpg', - 'opengl/assets/LICENSE.txt'] - }, - install_requires=get_requirements(), - extras_require={ - '3dviewer': ['PyOpenGL>=3.1'], - 'docs': ['sphinx', 'sphinx_rtd_theme', 'sphinx_autodoc_typehints'], - 'experimental': ['keras', 'scikit-learn', 'scipy', 'tensorflow'], - 'test': ['pytest', 'requests', 'requests_toolbelt'], - } -) +# Copyright (c) 2018 Anki, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License in the file LICENSE.txt or at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Anki/DDL Vector - Python SDK: + by cyb3rdog + +## With support for Production, EscapePod and OSKR robots! ## +This is the extended fork of the original Anki Vector Python SDK. + +For more information, please visit the project Github site: https://github.com/cyb3rdog/vector-python-sdk + +The Vector SDK gives you direct access to Vector's unprecedented set of advanced sensors, AI capabilities, and robotics technologies including computer vision, intelligent mapping and navigation, and a groundbreaking collection of expressive animations. + +It's powerful but easy to use, complex but not complicated, and versatile enough to be used across a wide range of domains including enterprise, research, and entertainment. Find out more at https://developer.anki.com + +Vector SDK documentation: https://developer.anki.com/vector/docs/ + +Official developer forum: https://forums.anki.com/ + +Requirements: + * Python 3.6.1 + * Python 3.7 + * Python 3.8 +""" + +import os.path +import sys +from setuptools import setup + +if sys.version_info < (3, 6, 1): + sys.exit('The Vector SDK requires Python 3.6.1 or later') + +HERE = os.path.abspath(os.path.dirname(__file__)) + +def fetch_version(): + """Get the version from the package""" + with open(os.path.join(HERE, 'anki_vector', 'version.py')) as version_file: + versions = {} + exec(version_file.read(), versions) + return versions + +VERSION_DATA = fetch_version() +VERSION = VERSION_DATA['__version__'] + +def get_requirements(): + """Load the requirements from requirements.txt into a list""" + reqs = [] + with open(os.path.join(HERE, 'requirements.txt')) as requirements_file: + for line in requirements_file: + reqs.append(line.strip()) + return reqs + +setup( + name='cyb3r_vector_sdk', + version=VERSION, + description="The Vector SDK is a connected vision- and character-based robotics platform for everyone.", + long_description=__doc__, + url='https://github.com/cyb3rdog/vector-python-sdk', + author='Anki, Inc', + author_email='developer@anki.com', + license='Apache License, Version 2.0', + # See https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Libraries', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + ], + zip_safe=True, + keywords='anki vector robot robotics sdk ai vision'.split(), + packages=['anki_vector', 'anki_vector.camera_viewer', 'anki_vector.configure', 'anki_vector.configure_pod', 'anki_vector.messaging', 'anki_vector.opengl', 'anki_vector.reserve_control'], + package_data={ + 'anki_vector': ['LICENSE.txt', 'opengl/assets/*.obj', 'opengl/assets/*.mtl', 'opengl/assets/*.jpg', + 'opengl/assets/LICENSE.txt'] + }, + install_requires=get_requirements(), + extras_require={ + '3dviewer': ['PyOpenGL>=3.1'], + 'docs': ['sphinx', 'sphinx_rtd_theme', 'sphinx_autodoc_typehints'], + 'experimental': ['keras', 'scikit-learn', 'scipy', 'tensorflow'], + 'test': ['pytest', 'requests', 'requests_toolbelt'], + } +)