diff --git a/README.md b/README.md index 4c37c1a..5caa72d 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,21 @@ ![Vector](docs/source/images/vector-sdk-alpha.jpg) -Learn more about Vector: https://www.anki.com/vector +Learn more about Vector: https://www.anki.com/en-us/vector Learn more about the SDK: https://developer.anki.com/ -SDK documentation: http://developer.anki.com/vector/docs/ +SDK documentation: https://developer.anki.com/vector/docs/index.html Forums: https://forums.anki.com/ ## Getting Started -You can follow steps [here](http://developer.anki.com/vector/docs/) to set up your Vector robot with the SDK. +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 diff --git a/anki_vector/animation.py b/anki_vector/animation.py index d7ad9b4..ddbdd3e 100644 --- a/anki_vector/animation.py +++ b/anki_vector/animation.py @@ -19,12 +19,13 @@ Animations can control the following tracks: head, lift, treads, face, audio and backpack lights. -There are two ways to play an animation on Vector: play_animation and play_animation_trigger (not yet implemented). When calling play_animation, -you select the specific animation you want the robot to run. For play_animation_trigger, you select a group of animations, and the robot -will choose which animation from the group to run when you execute the method. +There are two ways to play an animation on Vector: play_animation_trigger and play_animation. For play_animation_trigger, you select +a pre-defined group of animations, and the robot will choose which animation from the group to run when you execute the method. When +calling play_animation, you select the specific animation you want the robot to run. We advise you to use play_animation_trigger instead +of play_animation, since individual animations can be deleted between Vector OS versions. -By default, when an SDK program starts, the SDK will request a list of known animations from the robot, which will be loaded into anim_list -in the AnimationComponent. +By default, when an SDK program starts, the SDK will request a list of known animation triggers and animations from the robot, which will be loaded +and available from anim_list_triggers and anim_list, respectively, in the AnimationComponent. """ # __all__ should order by constants, event classes, other classes, functions. @@ -32,6 +33,8 @@ import concurrent +from google.protobuf import text_format + from . import connection, exceptions, util from .messaging import protocol @@ -42,6 +45,7 @@ class AnimationComponent(util.Component): def __init__(self, robot): super().__init__(robot) self._anim_dict = {} + self._anim_trigger_dict = {} @property def anim_list(self): @@ -51,6 +55,10 @@ def anim_list(self): Animation names are dynamically retrieved from the robot when the Python script connects to it. + Warning: Specific animations may be renamed or removed in future updates of the app. + If you want your program to work more reliably across all versions + we recommend using anim_trigger_list and :meth:`play_animation_trigger` instead. + .. testcode:: import anki_vector @@ -68,11 +76,48 @@ def anim_list(self): result.result() return list(self._anim_dict.keys()) + @property + def anim_trigger_list(self): + """ + Holds the set of animation trigger names (strings) returned from the robot. + + Animation trigger names are dynamically retrieved from the robot when the Python + script connects to it. + + Playing an animation trigger causes the robot to play an animation of a particular type. + + The robot may pick one of a number of actual animations to play based on + Vector's mood or emotion, or with random weighting. Thus playing the same + trigger twice may not result in the exact same underlying animation playing + twice. + + To play an exact animation, use :meth:`play_animation`. + + This property holds the set of defined animations triggers to pass to :meth:`play_animation_trigger`. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + print("List all animation trigger names:") + anim_trigger_names = robot.anim.anim_trigger_list + for anim_trigger_name in anim_trigger_names: + print(anim_trigger_name) + """ + if not self._anim_trigger_dict: + self.logger.warning("Anim trigger list was empty. Lazy-loading anim trigger list now.") + result = self.load_animation_trigger_list() + if isinstance(result, concurrent.futures.Future): + result.result() + return list(self._anim_trigger_dict.keys()) + async def _ensure_loaded(self): """ This is an optimization for the case where a user doesn't - need the animation_list. This way, connections aren't delayed - by the load_animation_list call. + need the animation_trigger_list or the animation_list. This way, + connections aren't delayed by the load_animation_triggers_list and + load_animation_list calls. If this is invoked inside another async function then we explicitly await the result. @@ -80,21 +125,35 @@ async def _ensure_loaded(self): if not self._anim_dict: self.logger.warning("Anim list was empty. Lazy-loading anim list now.") await self._load_animation_list() + if not self._anim_trigger_dict: + self.logger.warning("Anim trigger list was empty. Lazy-loading anim trigger list now.") + await self._load_animation_trigger_list() async def _load_animation_list(self): req = protocol.ListAnimationsRequest() result = await self.grpc_interface.ListAnimations(req) - self.logger.debug(f"status: {result.status}, number_of_animations:{len(result.animation_names)}") + 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) + 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 + @connection.on_connection_thread(log_messaging=False, requires_control=False) async def load_animation_list(self): - """Request the list of animations from the robot + """Request the list of animations from the robot. When the request has completed, anim_list will be populated with the list of animations the robot knows how to run. + Warning: Specific animations may be renamed or removed in future updates of the app. + If you want your program to work more reliably across all versions + we recommend using animation triggers instead. See :meth:`play_animation_trigger`. + .. testcode:: import anki_vector @@ -108,6 +167,66 @@ async def load_animation_list(self): """ return await self._load_animation_list() + @connection.on_connection_thread(log_messaging=False, requires_control=False) + async def load_animation_trigger_list(self): + """Request the list of animation triggers from the robot. + + When the request has completed, anim_trigger_list will be populated with + the list of animation triggers the robot knows how to run. + + Playing a trigger requests that an animation of a certain class starts playing, rather than an exact + animation name. + + .. testcode:: + + import anki_vector + + with anki_vector.AsyncRobot() as robot: + anim_trigger_request = robot.anim.load_animation_trigger_list() + anim_trigger_request.result() + anim_trigger_names = robot.anim.anim_trigger_list + for anim_trigger_name in anim_trigger_names: + print(anim_trigger_name) + """ + return await self._load_animation_trigger_list() + + @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. + + Playing a trigger requests that an animation of a certain class starts playing, rather than an exact + animation name. + + Vector must be off of the charger to play an animation. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + robot.anim.play_animation_trigger('GreetAfterLongTime') + + :param trigger: The animation trigger to play. Can be of type str or :class:`anki_vector.protocol.AnimationTrigger`. + :param loop_count: Number of times to play the animation. + :param use_lift_safe: True to automatically ignore the lift track if Vector is currently carrying an object. + :param ignore_body_track: True to ignore the animation track for Vector's body (i.e. the wheels / treads). + :param ignore_head_track: True to ignore the animation track for Vector's head. + :param ignore_lift_track: True to ignore the animation track for Vector's lift. + """ + animation_trigger = anim_trigger + if not isinstance(anim_trigger, protocol.AnimationTrigger): + await self._ensure_loaded() + if anim_trigger not in self.anim_trigger_list: + raise exceptions.VectorException(f"Unknown animation trigger: {anim_trigger}") + animation_trigger = self._anim_trigger_dict[anim_trigger] + req = protocol.PlayAnimationTriggerRequest(animation_trigger=animation_trigger, + loops=loop_count, + use_lift_safe=use_lift_safe, + ignore_body_track=ignore_body_track, + ignore_head_track=ignore_head_track, + ignore_lift_track=ignore_lift_track) + return await self.grpc_interface.PlayAnimationTrigger(req) + @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. @@ -116,7 +235,7 @@ async def play_animation(self, anim: str, loop_count: int = 1, ignore_body_track Warning: Specific animations may be renamed or removed in future updates of the app. If you want your program to work more reliably across all versions - we recommend using :meth:`play_animation_trigger` instead. (:meth:`play_animation_trigger` is still in development.) + we recommend using :meth:`play_animation_trigger` instead. .. testcode:: diff --git a/anki_vector/annotate.py b/anki_vector/annotate.py new file mode 100644 index 0000000..5a07bd8 --- /dev/null +++ b/anki_vector/annotate.py @@ -0,0 +1,587 @@ +# Copyright (c) 2019 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. + +"""Camera image annotation. + +.. image:: ../images/annotate.png + +This module defines an :class:`ImageAnnotator` class used by +:class:`anki_vector.camera.CameraImage` and +:class:`anki_vector.camera.CameraComponent` to add annotations +to camera images received by the robot. + +This can include the location of cubes and faces that the robot currently sees, +along with user-defined custom annotations. + +The ImageAnnotator instance can be accessed as +:attr:`anki_vector.camera.CameraComponent.image_annotator`. +""" + +# __all__ should order by constants, event classes, other classes, functions. +__all__ = ['DEFAULT_OBJECT_COLORS', + 'RESAMPLE_MODE_NEAREST', 'RESAMPLE_MODE_BILINEAR', + 'AnnotationPosition', 'ImageText', 'Annotator', + 'ObjectAnnotator', 'FaceAnnotator', 'TextAnnotator', 'ImageAnnotator', + 'add_img_box_to_image', 'add_polygon_to_image', 'annotator'] + + +from enum import Enum +import collections +import functools +import sys +from typing import Callable, Iterable, Tuple, Union + +try: + from PIL import Image, ImageDraw, ImageFont +except ImportError: + sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") +except SyntaxError: + sys.exit("SyntaxError: possible if accidentally importing old Python 2 version of PIL") + +from . import faces +from . import objects +from . import util + + +DEFAULT_OBJECT_COLORS = { + objects.LightCube: 'yellow', + objects.CustomObject: 'purple', + 'default': 'red' +} + +#: Fastest resampling mode, use nearest pixel +RESAMPLE_MODE_NEAREST = Image.NEAREST +#: Slower, but smoother, resampling mode - linear interpolation from 2x2 grid of pixels +RESAMPLE_MODE_BILINEAR = Image.BILINEAR + + +class AnnotationPosition(Enum): + """Specifies where the annotation must be rendered.""" + LEFT = 1 + RIGHT = 2 + TOP = 4 + BOTTOM = 8 + + #: Top left position + TOP_LEFT = TOP | LEFT + + #: Bottom left position + BOTTOM_LEFT = BOTTOM | LEFT + + #: Top right position + TOP_RIGHT = TOP | RIGHT + + #: Bottom right position + BOTTOM_RIGHT = BOTTOM | RIGHT + + +class ImageText: # pylint: disable=too-few-public-methods + """ImageText represents some text that can be applied to an image. + + The class allows the text to be placed at various positions inside a + bounding box within the image itself. + + .. testcode:: + + import time + + try: + from PIL import ImageDraw + except ImportError: + sys.exit("run `pip3 install --user Pillow numpy` to run this example") + + import anki_vector + from anki_vector import annotate + + + # Define an annotator using the annotator decorator + @annotate.annotator + def clock(image, scale, annotator=None, world=None, **kw): + d = ImageDraw.Draw(image) + bounds = (0, 0, image.width, image.height) + text = annotate.ImageText(time.strftime("%H:%m:%S"), + position=annotate.AnnotationPosition.TOP_LEFT, + outline_color="black") + text.render(d, bounds) + + with anki_vector.Robot(show_viewer=True, enable_face_detection=True, enable_custom_object_detection=True) as robot: + robot.camera.image_annotator.add_static_text("text", "Vec-Cam", position=annotate.AnnotationPosition.TOP_RIGHT) + robot.camera.image_annotator.add_annotator("clock", clock) + + time.sleep(3) + + + :param text: The text to display; may contain newlines + :param position: Where on the screen to render the text + - such as AnnotationPosition.TOP_LEFT or AnnotationPosition.BOTTOM_RIGHT + :param align: Text alignment for multi-line strings + :param color: Color to use for the text - see :mod:`PIL.ImageColor` + :param font: ImageFont to use (None for a default font) + :param line_spacing: The vertical spacing for multi-line strings + :param outline_color: Color to use for the outline - see + :mod:`PIL.ImageColor` - use None for no outline. + :param full_outline: True if the outline should surround the text, + otherwise a cheaper drop-shadow is displayed. Only relevant if + outline_color is specified. + """ + + 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): + self.text = text + self.position = position + self.align = align + self.color = color + self.font = font + self.line_spacing = line_spacing + self.outline_color = outline_color + self.full_outline = full_outline + + def render(self, draw: ImageDraw.ImageDraw, bounds: tuple) -> ImageDraw.ImageDraw: + """Renders the text onto an image within the specified bounding box. + + :param draw: The drawable surface to write on + :param bounds(top_left_x, top_left_y, bottom_right_x, bottom_right_y): bounding box + """ + (bx1, by1, bx2, by2) = bounds + text_width, text_height = draw.textsize(self.text, font=self.font) + + if self.position.value & AnnotationPosition.TOP.value: + y = by1 + else: + y = by2 - text_height + + if self.position.value & AnnotationPosition.LEFT.value: + x = bx1 + else: + x = bx2 - text_width + + # helper method for each draw call below + def _draw_text(pos, color): + draw.text(pos, self.text, font=self.font, fill=color, + align=self.align, spacing=self.line_spacing) + + if self.outline_color is not None: + # Pillow doesn't support outlined or shadowed text directly. + # We manually draw the text multiple times to achieve the effect. + if self.full_outline: + _draw_text((x - 1, y), self.outline_color) + _draw_text((x + 1, y), self.outline_color) + _draw_text((x, y - 1), self.outline_color) + _draw_text((x, y + 1), self.outline_color) + else: + # just draw a drop shadow (cheaper) + _draw_text((x + 1, y + 1), self.outline_color) + + _draw_text((x, y), self.color) + + return draw + + +def add_img_box_to_image(draw: ImageDraw.ImageDraw, box: util.ImageRect, color: str, text: Union[ImageText, Iterable[ImageText]] = None) -> None: + """Draw a box on an image and optionally add text. + + This will draw the outline of a rectangle to the passed in image + in the specified color and optionally add one or more pieces of text + along the inside edge of the rectangle. + + :param draw: The drawable surface to write on + :param box: The ImageBox defining the rectangle to draw + :param color: A color string suitable for use with PIL - see :mod:`PIL.ImageColor` + :param text: The text to display - may be a single ImageText instance, + or any iterable (eg a list of ImageText instances) to display multiple pieces of text. + """ + x1, y1 = box.x_top_left, box.y_top_left + x2, y2 = (box.x_top_left + box.width), (box.y_top_left + box.height) + draw.rectangle([x1, y1, x2, y2], outline=color) + if text is not None: + if isinstance(text, collections.Iterable): + for t in text: + t.render(draw, (x1, y1, x2, y2)) + else: + text.render(draw, (x1, y1, x2, y2)) + + +def add_polygon_to_image(draw: ImageDraw.ImageDraw, poly_points: list, scale: float, line_color: str, fill_color: str = None) -> None: + """Draw a polygon on an image + + This will draw a polygon on the passed-in image in the specified + colors and scale. + + :param draw: The drawable surface to write on + :param poly_points: A sequence of points representing the polygon, + where each point has float members (x, y) + :param scale: Scale to multiply each point to match the image scaling + :param line_color: The color for the outline of the polygon. The string value + must be a color string suitable for use with PIL - see :mod:`PIL.ImageColor` + :param fill_color: The color for the inside of the polygon. The string value + must be a color string suitable for use with PIL - see :mod:`PIL.ImageColor` + """ + if len(poly_points) < 2: + # Need at least 2 points to draw any lines + return + + # Convert poly_points to the PIL format and scale them to the image + pil_poly_points = [] + for pt in poly_points: + pil_poly_points.append((pt.x * scale, pt.y * scale)) + + draw.polygon(pil_poly_points, fill=fill_color, outline=line_color) + + +def _find_key_for_cls(d, cls): + for c in cls.__mro__: + result = d.get(c, None) + if result: + return result + return d['default'] + + +class Annotator: + """Annotation base class + + Subclasses of Annotator handle applying a single annotation to an image. + """ + #: int: The priority of the annotator - Annotators with higher numbered + #: priorities are applied first. + priority = 100 + + def __init__(self, img_annotator, priority=None): + #: :class:`ImageAnnotator`: The object managing camera annotations + self.img_annotator = img_annotator + + #: :class:`~anki_vector.world.World`: The world object for the robot who owns the camera + self.world = img_annotator.world + + #: bool: Set enabled to false to prevent the annotator being called + self.enabled = True + + if priority is not None: + self.priority = priority + + def apply(self, image: Image.Image, scale: float): + """Applies the annotation to the image.""" + # should be overriden by a subclass + raise NotImplementedError() + + def __hash__(self): + return id(self) + + +class ObjectAnnotator(Annotator): # pylint: disable=too-few-public-methods + """Adds object annotations to an Image. + + This handles :class:`anki_vector.objects.LightCube`, + :class:`anki_vector.objects.Charger` and + :class:`anki_vector.objects.CustomObject`. + """ + priority = 100 + object_colors = DEFAULT_OBJECT_COLORS + + def __init__(self, img_annotator, object_colors=None): + super().__init__(img_annotator) + if object_colors is not None: + self.object_colors = object_colors + + def apply(self, image: Image.Image, scale: float) -> None: + draw = ImageDraw.Draw(image) + for obj in self.world.visible_objects: + color = _find_key_for_cls(self.object_colors, obj.__class__) + text = self._label_for_obj(obj) + box = obj.last_observed_image_rect + if scale != 1: + box.scale_by(scale) + add_img_box_to_image(draw, box, color, text=text) + + def _label_for_obj(self, obj): # pylint: disable=no-self-use + """Fetch a label to display for the object. + + Override or replace to customize. + """ + return ImageText(obj.descriptive_name) + + +class FaceAnnotator(Annotator): # pylint: disable=too-few-public-methods + """Adds annotations of currently detected faces to a camera image. + + This handles the display of :class:`anki_vector.faces.Face` objects. + """ + priority = 100 + box_color = 'green' + + def __init__(self, img_annotator, box_color=None): + super().__init__(img_annotator) + if box_color is not None: + self.box_color = box_color + + def apply(self, image: Image.Image, scale: float) -> None: + draw = ImageDraw.Draw(image) + for obj in self.world.visible_faces: + text = self._label_for_face(obj) + box = obj.last_observed_image_rect + if scale != 1: + box.scale_by(scale) + add_img_box_to_image(draw, box, self.box_color, text=text) + add_polygon_to_image(draw, obj.left_eye, scale, self.box_color) + add_polygon_to_image(draw, obj.right_eye, scale, self.box_color) + add_polygon_to_image(draw, obj.nose, scale, self.box_color) + add_polygon_to_image(draw, obj.mouth, scale, self.box_color) + + def _label_for_face(self, obj): # pylint: disable=no-self-use + """Fetch a label to display for the face. + + Override or replace to customize. + """ + label_text = "" + expression = faces.Expression(obj.expression).name + + if obj.name: + label_text = f"Name:{obj.name}" + if expression != "UNKNOWN": + label_text += f"\nExpression:{expression}" + if obj.expression_score: + # if there is a specific known expression, then also show the score + # (display a % to make it clear the value is out of 100) + label_text += f"\nScore:{sum(obj.expression_score)}" + + return ImageText(label_text + "\n" + f"Face Id:{obj.face_id}") + + +class TextAnnotator(Annotator): # pylint: disable=too-few-public-methods + """Adds simple text annotations to a camera image. + """ + priority = 50 + + def __init__(self, img_annotator, text): + super().__init__(img_annotator) + self.text = text + + def apply(self, image: Image.Image, scale: int) -> None: + d = ImageDraw.Draw(image) + self.text.render(d, (0, 0, image.width, image.height)) + + +class _AnnotatorHelper(Annotator): # pylint: disable=too-few-public-methods + def __init__(self, img_annotator, wrapped): + super().__init__(img_annotator) + self._wrapped = wrapped + + def apply(self, image: Image.Image, scale: int) -> None: + self._wrapped(image, scale, world=self.world, img_annotator=self.img_annotator) + + +def annotator(f): + """A decorator for converting a regular function/method into an Annotator. + + The wrapped function should have a signature of + ``(image, scale, img_annotator=None, world=None, **kw)`` + """ + @functools.wraps(f) + def wrapper(img_annotator): + return _AnnotatorHelper(img_annotator, f) + return wrapper + + +class ImageAnnotator: + """ImageAnnotator applies annotations to the camera image received from the robot. + + This is instantiated by :class:`anki_vector.world.World` and is accessible as + :class:`anki_vector.camera.CameraComponent.image_annotator`. + + By default it defines two active annotators named ``objects`` and ``faces``. + + The ``objects`` annotator adds a box around each object (such as light cubes) + that the robot can see. The ``faces`` annotator adds a box around each person's + face that the robot can recognize. + + Custom annotations can be defined by calling :meth:`add_annotator` with + a name of your choosing and an instance of a :class:`Annotator` subclass, + or use a regular function wrapped with the :func:`annotator` decorator. + + Individual annotations can be disabled and re-enabled using the + :meth:`disable_annotator` and :meth:`enable_annotator` methods. + + All annotations can be disabled by setting the + :attr:`annotation_enabled` property to False. + + E.g. to disable face annotations, call + ``robot.camera.image_annotator.disable_annotator('faces')`` + + Annotators each have a priority number associated with them. Annotators + with a larger priority number are rendered first and may be overdrawn by those + with a lower/smaller priority number. + + + .. testcode:: + + from PIL import ImageDraw + + import anki_vector + from anki_vector import annotate + import time + + @annotate.annotator + def clock(image, scale, annotator=None, world=None, **kw): + d = ImageDraw.Draw(image) + bounds = (0, 0, image.width, image.height) + text = annotate.ImageText(time.strftime("%H:%m:%S"), + position=annotate.AnnotationPosition.TOP_LEFT, + outline_color="black") + text.render(d, bounds) + + with anki_vector.Robot(show_viewer=True) as robot: + # Add a custom annotator to the camera feed + robot.camera.image_annotator.add_annotator("custom-annotator", clock) + time.sleep(5) + # Disable the custom annotator + robot.camera.image_annotator.disable_annotator("custom-annotator") + time.sleep(5) + """ + + def __init__(self, world, **kw): + super().__init__(**kw) + #: :class:`anki_vector.world.World`: World object that created the annotator. + self.world = world + + self._annotators = {} + self._sorted_annotators = [] + self.add_annotator('objects', ObjectAnnotator(self)) + self.add_annotator('faces', FaceAnnotator(self)) + + #: If this attribute is set to false, the :meth:`annotate_image` method + #: will continue to provide a scaled image, but will not apply any annotations. + self.annotation_enabled = True + + def _sort_annotators(self): + self._sorted_annotators = sorted(self._annotators.values(), + key=lambda an: an.priority, reverse=True) + + def add_annotator(self, name: str, new_annotator: Union[Annotator, Callable[..., Annotator]]) -> None: + """Adds a new annotator for display. + + Annotators are enabled by default. + + :param name: An arbitrary name for the annotator; must not + already be defined + :param new_annotator: The annotator to add may either by an instance of Annotator, + or a factory callable that will return an instance of Annotator. + The callable will be called with an ImageAnnotator instance as its first argument. + + Raises: + :class:`ValueError` if the annotator is already defined. + """ + if name in self._annotators: + raise ValueError('Annotator "%s" is already defined' % (name)) + if not isinstance(new_annotator, Annotator): + new_annotator = new_annotator(self) + self._annotators[name] = new_annotator + self._sort_annotators() + + def remove_annotator(self, name: str) -> None: + """Remove an annotator. + + :param name: The name of the annotator to remove as passed to :meth:`add_annotator`. + + Raises: + KeyError if the annotator isn't registered + """ + del self._annotators[name] + self._sort_annotators() + + def get_annotator(self, name: str) -> None: + """Return a named annotator. + + :param name: The name of the annotator to return + + Raises: + KeyError if the annotator isn't registered + """ + return self._annotators[name] + + def disable_annotator(self, name: str) -> None: + """Disable a named annotator. + + Leaves the annotator as registered, but does not include its output + in the annotated image. + + :param name: The name of the annotator to disable + """ + if name in self._annotators: + self._annotators[name].enabled = False + + def enable_annotator(self, name: str) -> None: + """Enabled a named annotator. + + (re)enable an annotator if it was previously disabled. + + :param name: The name of the annotator to enable + """ + self._annotators[name].enabled = True + + def add_static_text(self, name: str, text: Union[str, ImageText], color: str = 'white', position: int = AnnotationPosition.TOP_LEFT) -> None: + """Add some static text to annotated images. + + This is a convenience method to create a :class:`TextAnnnotator` + and add it to the image. + + :param name: An arbitrary name for the annotator; must not + already be defined + :param text: The text to display + may be a plain string, or an ImageText instance + :param color: Used if text is a string; defaults to white + :param position: Used if text is a string; defaults to TOP_LEFT + """ + if isinstance(text, str): + text = ImageText(text, position=position, color=color) + self.add_annotator(name, TextAnnotator(self, text)) + + def annotate_image(self, image: Image.Image, scale: float = None, fit_size: Tuple[int, int] = None, resample_mode: int = RESAMPLE_MODE_NEAREST) -> Image.Image: + """Called by :class:`~anki_vector.camera.CameraComponent` to annotate camera images. + + :param image: The image to annotate + :param scale: If set then the base image will be scaled by the + supplied multiplier. Cannot be combined with fit_size + :param fit_size: If set, then scale the image to fit inside + the supplied (width, height) dimensions. The original aspect + ratio will be preserved. Cannot be combined with scale. + :param resample_mode: The resampling mode to use when scaling the + image. Should be either :attr:`RESAMPLE_MODE_NEAREST` (fast) or + :attr:`RESAMPLE_MODE_BILINEAR` (slower, but smoother). + """ + if scale is not None and scale != 1: + image = image.resize((int(image.width * scale), int(image.height * scale)), + resample=resample_mode) + + elif fit_size is not None and fit_size != (image.width, image.height): + img_ratio = image.width / image.height + fit_width, fit_height = fit_size + fit_ratio = fit_width / fit_height + if img_ratio > fit_ratio: + fit_height = int(fit_width / img_ratio) + elif img_ratio < fit_ratio: + fit_width = int(fit_height * img_ratio) + scale = fit_width / image.width + image = image.resize((fit_width, fit_height)) + + else: + scale = 1 + image = image.copy() + + if not self.annotation_enabled: + return image + + for an in self._sorted_annotators: + if an.enabled: + an.apply(image, scale) + + return image diff --git a/anki_vector/audio.py b/anki_vector/audio.py index 553e62c..58ef29c 100644 --- a/anki_vector/audio.py +++ b/anki_vector/audio.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Anki, Inc. +# Copyright (c) 2019 Anki, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,166 +12,197 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Support for Vector's audio. +"""Support for accessing Vector's audio. -Vector has multiple built-in microphones which can detect sound and the direction -it originated from. The robot then processing this data into a single-channel -audio feed available to the SDK. +Vector's speakers can be used for playing user-provided audio. The :class:`AudioComponent` class defined in this module is made available as -:attr:`anki_vector.robot.Robot.audio` and can be used to enable/disable audio -sending and process audio data being sent by the robot. +:attr:`anki_vector.robot.Robot.audio` and can be used to play audio data on the robot. """ -# TODO Not yet supported. Audio from robot does not yet reach Python. - # __all__ should order by constants, event classes, other classes, functions. __all__ = ['AudioComponent'] import asyncio -from concurrent.futures import CancelledError -import sys - +from concurrent import futures +from enum import Enum +import time +import wave +from google.protobuf.text_format import MessageToString from . import util -from .events import Events +from .connection import on_connection_thread +from .exceptions import VectorExternalAudioPlaybackException from .messaging import protocol -try: - import numpy as np -except ImportError: - sys.exit("Cannot import numpy: Do `pip3 install numpy` to install") - - -class AudioComponent(util.Component): - """Represents Vector's audio feed. - - The AudioComponent object receives audio data from Vector's microphones. - The :class:`anki_vector.robot.Robot` or :class:`anki_vector.robot.AsyncRobot` instance owns this audio component. +MAX_ROBOT_AUDIO_CHUNK_SIZE = 1024 # 1024 is maximum, larger sizes will fail +DEFAULT_FRAME_SIZE = MAX_ROBOT_AUDIO_CHUNK_SIZE // 2 - .. code-block:: python - import time +class RobotVolumeLevel(Enum): + """Use these values for setting the master audio volume. See :meth:`set_master_volume` - try: - from scipy.io import wavfile - except ImportError as exc: - sys.exit("Cannot import scipy: Do `pip3 install scipy` to install") - - with anki_vector.Robot(enable_audio_feed=True) as robot: - time.sleep(5.0) - wavfile.write("outputfile.wav", anki_vector.protocol.PROCESSED_SAMPLE_RATE, robot.audio.raw_audio_waveform_history) - - :param robot: A reference to the owner Robot object. + Note that muting the robot is not supported from the SDK. """ - # TODO When audio is ready, convert `.. code-block:: python` to `.. testcode::` - - def __init__(self, robot): - super().__init__(robot) + LOW = 0 + MEDIUM_LOW = 1 + MEDIUM = 2 + MEDIUM_HIGH = 3 + HIGH = 4 - # @TODO: Add a variable tracking the current audio send mode once that is implemented - self._raw_audio_waveform_history = np.empty(0, dtype=np.int16) - self._latest_sample_id = None - self._audio_processing_mode = None - - self._audio_feed_task: asyncio.Task = None +class AudioComponent(util.Component): + """Handles audio on Vector. - # Subscribe to callbacks related to objects - robot.events.subscribe(self._on_audio_send_mode_changed_event, - Events.audio_send_mode_changed) + The AudioComponent object plays audio data to Vector's speaker. - # @TODO: Implement audio history as ringbuffer, caching only recent audio - # @TODO: Add function to return a recent chunk of the audio history in a standardized python audio package format - # @TODO: Add hook for feeding audio stream into external voice processing library + The :class:`anki_vector.robot.Robot` or :class:`anki_vector.robot.AsyncRobot` instance + owns this audio component. - @property - def raw_audio_waveform_history(self) -> np.array: - """:class:`np.array`: The most recent processed image received from the robot. + .. testcode:: - Audio history as signed-16-bit-int waveform data + import anki_vector - The sample rate from the microphones is 15625, which the robot processes at 16khz. As a result the pitch is - altered by 2.4%. The last 16000 values of the array represent the last second of audio amplitude. + with anki_vector.Robot() as robot: + robot.audio.stream_wav_file('../examples/sounds/vector_alert.wav') + """ - .. code-block:: python + def __init__(self, robot): + super().__init__(robot) + self._is_shutdown = False + # don't create asyncio.Events here, they are not thread-safe + self._is_active_event = None + self._done_event = None - import time + @on_connection_thread(requires_control=False) + async def set_master_volume(self, volume: RobotVolumeLevel) -> protocol.MasterVolumeResponse: + """Sets Vector's master volume level. - try: - from scipy.io import wavfile - except ImportError as exc: - sys.exit("Cannot import scipy: Do `pip3 install scipy` to install") + Note that muting the robot is not supported from the SDK. - with anki_vector.Robot(enable_audio_feed=True) as robot: - time.sleep(5.0) - wavfile.write("outputfile.wav", anki_vector.protocol.PROCESSED_SAMPLE_RATE, robot.audio.raw_audio_waveform_history) + .. testcode:: - :getter: Returns the numpy array representing the audio history - """ - # TODO When audio is ready, convert `.. code-block:: python` to `.. testcode::` - return self._raw_audio_waveform_history + import anki_vector + from anki_vector import audio - @property - def latest_sample_id(self) -> int: - """The most recent received audio sample from the robot. + with anki_vector.Robot(behavior_control_level=None) as robot: + robot.audio.set_master_volume(audio.RobotVolumeLevel.MEDIUM_HIGH) - :getter: Returns the id for the latest audio sample + :param volume: the robot's desired volume """ - return self._latest_sample_id - @property - def audio_processing_mode(self) -> protocol.AudioProcessingMode: - """The current mode which the robot is processing audio. - - :getter: Returns the most recently received audio processing mode on the robot + volume_request = protocol.MasterVolumeRequest(volume_level=volume.value) + return await self.conn.grpc_interface.SetMasterVolume(volume_request) + + def _open_file(self, filename): + _reader = wave.open(filename, 'rb') + _params = _reader.getparams() + self.logger.info("Playing audio file %s", filename) + + if _params.sampwidth != 2 or _params.nchannels != 1 or _params.framerate > 16025 or _params.framerate < 8000: + raise VectorExternalAudioPlaybackException( + f"Audio format must be 8000-16025 hz, 16 bits, 1 channel. " + f"Found {_params.framerate} hz/{_params.sampwidth*8} bits/{_params.nchannels} channels") + + return _reader, _params + + async def _request_handler(self, reader, params, volume): + """Handles generating request messages for the AudioPlaybackStream.""" + frames = params.nframes # 16 bit samples, not bytes + + # send preparation message + msg = protocol.ExternalAudioStreamPrepare(audio_frame_rate=params.framerate, audio_volume=volume) + msg = protocol.ExternalAudioStreamRequest(audio_stream_prepare=msg) + + yield msg + await asyncio.sleep(0) # give event loop a chance to process messages + + # count of full and partial chunks + total_chunks = (frames + DEFAULT_FRAME_SIZE - 1) // DEFAULT_FRAME_SIZE + curr_chunk = 0 + start_time = time.time() + self.logger.debug("Starting stream time %f", start_time) + + while frames > 0 and not self._done_event.is_set(): + read_count = min(frames, DEFAULT_FRAME_SIZE) + audio_data = reader.readframes(read_count) + msg = protocol.ExternalAudioStreamChunk(audio_chunk_size_bytes=len(audio_data), audio_chunk_samples=audio_data) + msg = protocol.ExternalAudioStreamRequest(audio_stream_chunk=msg) + yield msg + await asyncio.sleep(0) + + # check if streaming is way ahead of audio playback time + elapsed = time.time() - start_time + expected_data_count = elapsed * params.framerate + time_ahead = (curr_chunk * DEFAULT_FRAME_SIZE - expected_data_count) / params.framerate + if time_ahead > 1.0: + self.logger.debug("waiting %f to catchup chunk %f", time_ahead - 0.5, curr_chunk) + await asyncio.sleep(time_ahead - 0.5) + frames = frames - read_count + curr_chunk += 1 + if curr_chunk == total_chunks: + # last chunk: time to stop stream + msg = protocol.ExternalAudioStreamComplete() + msg = protocol.ExternalAudioStreamRequest(audio_stream_complete=msg) + + yield msg + await asyncio.sleep(0) + + reader.close() + + # Need the done message from the robot + await self._done_event.wait() + self._done_event.clear() + + @on_connection_thread(requires_control=True) + async def stream_wav_file(self, filename, volume=50): + """ Plays audio using Vector's speakers. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + robot.audio.stream_wav_file('../examples/sounds/vector_alert.wav') + + :param filename: the filename/path to the .wav audio file + :param volume: the audio playback level (0-100) """ - return self._audio_processing_mode - - def init_audio_feed(self) -> None: - """Begin audio feed task""" - if not self._audio_feed_task or self._audio_feed_task.done(): - self._audio_feed_task = self.robot.loop.create_task(self._request_and_handle_audio()) - - def close_audio_feed(self) -> None: - """Cancel audio feed task""" - if self._audio_feed_task: - self._audio_feed_task.cancel() - self.robot.loop.run_until_complete(self._audio_feed_task) - - def _on_audio_send_mode_changed_event(self, msg: protocol.AudioSendModeChanged) -> None: - """Callback for changes in the robot's audio processing mode""" - self._audio_processing_mode = msg.mode - self.logger.info('Audio processing mode changed to {0}'.format(self._audio_processing_mode)) - - def _process_audio(self, msg: protocol.AudioFeedResponse) -> None: - """Processes raw data from the robot into a more more useful image structure.""" - audio_data = msg.signal_power - expected_size = protocol.SAMPLE_COUNTS_PER_SDK_MESSAGE - received_size = int(len(audio_data) / 2) - if expected_size != received_size: - self.logger.warning('WARNING: audio stream received {0} bytes while {1} were expected'.format(received_size, expected_size)) + # TODO make this support multiple simultaneous sound playback + if self._is_active_event is None: + self._is_active_event = asyncio.Event() - # Constuct numpy array out of source data - array = np.frombuffer(audio_data, dtype=np.int16, count=received_size, offset=0) + if self._is_active_event.is_set(): + raise VectorExternalAudioPlaybackException("Cannot start audio when another sound is playing") - # Append to audio history - self._raw_audio_waveform_history = np.append(self._raw_audio_waveform_history, array) + if volume < 0 or volume > 100: + raise VectorExternalAudioPlaybackException("Volume must be between 0 and 100") + _file_reader, _file_params = self._open_file(filename) + playback_error = None + self._is_active_event.set() - self._latest_sample_id = len(self._raw_audio_waveform_history) - 1 + if self._done_event is None: + self._done_event = asyncio.Event() - async def _request_and_handle_audio(self) -> None: - """Queries and listens for audio feed events from the robot. - Received events are parsed by a helper function.""" try: - req = protocol.AudioFeedRequest() - async for evt in self.grpc_interface.AudioFeed(req): - # If the audio feed is disabled after stream is setup, exit the stream - # (the audio feed on the robot is disabled internally on stream exit) - if not self.robot.enable_audio_feed: - self.logger.warning('Audio feed has been disabled. Enable the feed to start/continue receiving audio feed data') - return - self._process_audio(evt) - except CancelledError: - self.logger.debug('Audio feed task was cancelled. This is expected during disconnection.') + async for response in self.grpc_interface.ExternalAudioStreamPlayback(self._request_handler(_file_reader, _file_params, volume)): + self.logger.info("ExternalAudioStream %s", MessageToString(response, as_one_line=True)) + response_type = response.WhichOneof("audio_response_type") + if response_type == 'audio_stream_playback_complete': + playback_error = None + elif response_type == 'audio_stream_buffer_overrun': + playback_error = response_type + elif response_type == 'audio_stream_playback_failyer': + playback_error = response_type + self._done_event.set() + except asyncio.CancelledError: + self.logger.debug('Audio Stream future was cancelled.') + except futures.CancelledError: + self.logger.debug('Audio Stream handler task was cancelled.') + finally: + self._is_active_event = None + self._done_event = None + + if playback_error is not None: + raise VectorExternalAudioPlaybackException(f"Error reported during audio playback {playback_error}") diff --git a/anki_vector/behavior.py b/anki_vector/behavior.py index 4a9a4ef..d4555fc 100644 --- a/anki_vector/behavior.py +++ b/anki_vector/behavior.py @@ -20,10 +20,10 @@ Behaviors represent a complex task which requires Vector's internal logic to determine how long it will take. This may include combinations of animation, path planning or -other functionality. Examples include :meth:`drive_on_charger`, -:meth:`set_lift_height`, etc. +other functionality. Examples include drive_on_charger, +set_lift_height, etc. -For commands such as :meth:`go_to_pose`, :meth:`drive_on_charger` and :meth:`dock_with_cube`, +For commands such as go_to_pose, drive_on_charger and dock_with_cube, Vector uses path planning, which refers to the problem of navigating the robot from point A to B without collisions. Vector loads known obstacles from his map, creates a path to navigate @@ -36,11 +36,12 @@ __all__ = ["MAX_HEAD_ANGLE", "MIN_HEAD_ANGLE", "MAX_LIFT_HEIGHT", "MAX_LIFT_HEIGHT_MM", "MIN_LIFT_HEIGHT", "MIN_LIFT_HEIGHT_MM", - "BehaviorComponent"] + "BehaviorComponent", "ReserveBehaviorControl"] -from . import connection, objects, util +from . import connection, faces, objects, util from .messaging import protocol +from .exceptions import VectorException # Constants @@ -63,84 +64,30 @@ MAX_LIFT_HEIGHT = util.distance_mm(MAX_LIFT_HEIGHT_MM) -# TODO: Expose is_active and priority states of the SDK behavior control from this class. class BehaviorComponent(util.Component): """Run behaviors on Vector""" - _next_action_id = protocol.FIRST_SDK_TAG - - def __init__(self, robot): - super().__init__(robot) - self._motion_profile_map = {} - - # TODO Make the motion_profile_map into a class. - @property - def motion_profile_map(self) -> dict: - """Tells Vector how to drive when receiving navigation and movement actions - such as go_to_pose and dock_with_cube. - - motion_prof_map values are as follows: - | speed_mmps (float) - | accel_mmps2 (float) - | decel_mmps2 (float) - | point_turn_speed_rad_per_sec (float) - | point_turn_accel_rad_per_sec2 (float) - | point_turn_decel_rad_per_sec2 (float) - | dock_speed_mmps (float) - | dock_accel_mmps2 (float) - | dock_decel_mmps2 (float) - | reverse_speed_mmps (float) - | is_custom (bool) - - :getter: Returns the motion profile map - :setter: Sets the motion profile map - - :param motion_prof_map: Provide custom speed, acceleration and deceleration - values with which the robot goes to the given pose. - """ - return self._motion_profile_map - - @motion_profile_map.setter - def motion_profile_map(self, motion_profile_map: dict): - self._motion_profile_map = motion_profile_map - - def _motion_profile_for_proto(self) -> protocol.PathMotionProfile: - """Packages the current motion profile into a proto object - - Returns: - A profile object describing motion which can be passed with any proto action message. - """ - # TODO: This should be made into its own class - default_motion_profile = { - "speed_mmps": 100.0, - "accel_mmps2": 200.0, - "decel_mmps2": 500.0, - "point_turn_speed_rad_per_sec": 2.0, - "point_turn_accel_rad_per_sec2": 10.0, - "point_turn_decel_rad_per_sec2": 10.0, - "dock_speed_mmps": 60.0, - "dock_accel_mmps2": 200.0, - "dock_decel_mmps2": 500.0, - "reverse_speed_mmps": 80.0, - "is_custom": 1 if self._motion_profile_map else 0 - } - default_motion_profile.update(self._motion_profile_map) - - return protocol.PathMotionProfile(**default_motion_profile) + _next_behavior_id = protocol.FIRST_SDK_TAG @classmethod - 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 + 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 else: - cls._next_action_id += 1 - return next_action_id + cls._next_behavior_id += 1 + return next_behavior_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) + return await self.grpc_interface.CancelActionByIdTag(cancel_action_request) - # Navigation actions + # TODO Make this cancellable with is_cancellable_behavior @connection.on_connection_thread() - async def drive_off_charger(self): + async def drive_off_charger(self) -> protocol.DriveOffChargerResponse: """Drive Vector off the charger If Vector is on the charger, drives him off the charger. @@ -155,8 +102,9 @@ async def drive_off_charger(self): 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() - async def drive_on_charger(self): + async def drive_on_charger(self) -> protocol.DriveOnChargerResponse: """Drive Vector onto the charger Vector will attempt to find the charger and, if successful, he will @@ -175,10 +123,97 @@ async def drive_on_charger(self): 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() + async def find_faces(self) -> protocol.FindFacesResponse: + """Look around for faces + + Turn in place and move head to look for faces + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + robot.behavior.find_faces() + """ + 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() + async def look_around_in_place(self) -> protocol.LookAroundInPlaceResponse: + """Look around in place + + Turn in place and move head to see what's around Vector + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + robot.behavior.look_around_in_place() + """ + 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() + async def roll_visible_cube(self) -> protocol.RollBlockResponse: + """Roll a cube that is currently known to the robot + + This behavior will move into position as necessary based on relative + distance and orientation. + + Vector needs to see the block for this to succeed. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + robot.behavior.roll_visible_cube() + """ + roll_block_request = protocol.RollBlockRequest() + return await self.grpc_interface.RollBlock(roll_block_request) + + # TODO Make this cancellable with is_cancellable_behavior + @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. + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + robot.behavior.say_text("Hello World") + + :param text: The words for Vector to say. + :param use_vector_voice: Whether to use Vector's robot voice + (otherwise, he uses a generic human male voice). + :param duration_scalar: Adjust the relative duration of the + generated text to speech audio. + + :return: object that provides the status and utterance state + """ + say_text_request = protocol.SayTextRequest(text=text, + use_vector_voice=use_vector_voice, + duration_scalar=duration_scalar) + return await self.conn.grpc_interface.SayText(say_text_request) + + # TODO Make this cancellable with is_cancellable_behavior? @connection.on_connection_thread() async def set_eye_color(self, hue: float, saturation: float) -> protocol.SetEyeColorResponse: """Set Vector's eye color. + Eye color settings examples: + | Teal: Set hue to 0.42 and saturation to 1.00. + | Orange: Set hue to 0.05 and saturation to 0.95. + | Yellow: Set hue to 0.11 and saturation to 1.00. + | Lime: Set hue to 0.21 and saturation to 1.00. + | Sapphire: Set hue to 0.57 and saturation to 1.00. + | Purple: Set hue to 0.83 and saturation to 0.76. + .. testcode:: import anki_vector @@ -195,11 +230,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() + @connection.on_connection_thread(is_cancellable_behavior=True) async def go_to_pose(self, pose: util.Pose, relative_to_robot: bool = False, - num_retries: int = 0) -> protocol.GoToPoseResponse: + num_retries: int = 0, + _behavior_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. @@ -212,10 +248,14 @@ async def go_to_pose(self, applicable elements of pose in this situation are position.x position.y and rotation.angle_z. + Note that actions that use the wheels cannot be performed at the same time, + otherwise you may see a TRACKS_LOCKED error. Methods that use the wheels include + :meth:`go_to_pose`, :meth:`dock_with_cube`, :meth:`turn_in_place`, :meth:`drive_straight`, and :meth:`pickup_object`. + :param pose: The destination pose. :param relative_to_robot: Whether the given pose is relative to the robot's pose. - :param num_retries: Number of times to re-attempt action in case of a failure. + :param num_retries: Number of times to reattempt action in case of a failure. Returns: A response from the robot with status information sent when this request successfully completes or fails. @@ -223,45 +263,59 @@ async def go_to_pose(self, .. testcode:: import anki_vector - from anki_vector.util import degrees, Pose + from anki_vector.util import degrees, Angle, Pose with anki_vector.Robot() as robot: - pose = Pose(x=50, y=0, z=0, angle_z=anki_vector.util.Angle(degrees=0)) + pose = Pose(x=50, y=0, z=0, angle_z=Angle(degrees=0)) robot.behavior.go_to_pose(pose) + + Example of cancelling the :meth:`go_to_pose` behavior: + + .. testcode:: + + import anki_vector + from anki_vector.util import degrees, Angle, Pose + import time + + with anki_vector.AsyncRobot() as robot: + pose = Pose(x=50, y=0, z=0, angle_z=Angle(degrees=0)) + pose_future = robot.behavior.go_to_pose(pose) + time.sleep(3.0) + pose_future.cancel() """ if relative_to_robot and self.robot.pose: pose = self.robot.pose.define_pose_relative_this(pose) - motion_prof = self._motion_profile_for_proto() - # @TODO: the id_tag we supply can be used to cancel this action, - # however when we implement that we will need some way to get the id_tag - # out of this function. go_to_pose_request = protocol.GoToPoseRequest(x_mm=pose.position.x, y_mm=pose.position.y, rad=pose.rotation.angle_z.radians, - motion_prof=motion_prof, - id_tag=self._get_next_action_id(), + id_tag=_behavior_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() + @connection.on_connection_thread(is_cancellable_behavior=True) 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) -> protocol.DockWithCubeResponse: + num_retries: int = 0, + _behavior_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. + Note that actions that use the wheels cannot be performed at the same time, + otherwise you may see a TRACKS_LOCKED error. Methods that use the wheels include + :meth:`go_to_pose`, :meth:`dock_with_cube`, :meth:`turn_in_place`, :meth:`drive_straight`, and :meth:`pickup_object`. + :param target_object: The LightCube object to dock with. :param approach_angle: Angle to approach the dock with. :param alignment_type: Which part of the robot to align with the object. :param distance_from_marker: How far from the object to approach (0 to dock) - :param num_retries: Number of times to re-attempt action in case of a failure. + :param num_retries: Number of times to reattempt action in case of a failure. Returns: A response from the robot with status information sent when this request successfully completes or fails. @@ -274,21 +328,39 @@ async def dock_with_cube(self, robot.world.connect_cube() if robot.world.connected_light_cube: - dock_response = robot.behavior.dock_with_cube(robot.world.connected_light_cube) - docking_result = dock_response.result + robot.behavior.dock_with_cube(robot.world.connected_light_cube) + + Example of cancelling the :meth:`dock_with_cube` behavior: + + .. testcode:: + + import anki_vector + from anki_vector.util import degrees + import time + + with anki_vector.AsyncRobot() as robot: + # If necessary, move Vector's Head and Lift down + robot.behavior.set_head_angle(degrees(-5.0)) + robot.behavior.set_lift_height(0.0) + + robot.world.connect_cube() + + time.sleep(10.0) + + dock_future = robot.behavior.dock_with_cube( + robot.world.connected_light_cube, + num_retries=3) + time.sleep(3.0) + dock_future.cancel() + + robot.world.disconnect_cube() """ if target_object is None: - raise Exception("Must supply a target_object to dock_with_cube") - - motion_prof = self._motion_profile_for_proto() + raise VectorException("Must supply a target_object to dock_with_cube") - # @TODO: the id_tag we supply can be used to cancel this action, - # however when we implement that we will need some way to get the id_tag - # out of this function. dock_request = protocol.DockWithCubeRequest(object_id=target_object.object_id, alignment_type=alignment_type, - motion_prof=motion_prof, - id_tag=self._get_next_action_id(), + id_tag=_behavior_id, num_retries=num_retries) if approach_angle is not None: dock_request.use_approach_angle = True @@ -300,25 +372,30 @@ async def dock_with_cube(self, return await self.grpc_interface.DockWithCube(dock_request) # Movement actions - @connection.on_connection_thread() + @connection.on_connection_thread(is_cancellable_behavior=True) async def drive_straight(self, distance: util.Distance, speed: util.Speed, should_play_anim: bool = True, - num_retries: int = 0) -> protocol.DriveStraightResponse: + num_retries: int = 0, + _behavior_id: int = None) -> protocol.DriveStraightResponse: """Tells Vector to drive in a straight line. Vector will drive for the specified distance (forwards or backwards) Vector must be off of the charger for this movement action. + Note that actions that use the wheels cannot be performed at the same time, + otherwise you may see a TRACKS_LOCKED error. Methods that use the wheels include + :meth:`go_to_pose`, :meth:`dock_with_cube`, :meth:`turn_in_place`, :meth:`drive_straight`, and :meth:`pickup_object`. + :param distance: The distance to drive (>0 for forwards, <0 for backwards) :param speed: The speed to drive at (should always be >0, the abs(speed) is used internally) :param should_play_anim: Whether to play idle animations whilst driving (tilt head, hum, animated eyes, etc.) - :param num_retries: Number of times to re-attempt action in case of a failure. + :param num_retries: Number of times to reattempt action in case of a failure. Returns: A response from the robot with status information sent when this request successfully completes or fails. @@ -326,35 +403,49 @@ async def drive_straight(self, .. testcode:: import anki_vector - from anki_vector.util import degrees, distance_mm, speed_mmps + from anki_vector.util import distance_mm, speed_mmps with anki_vector.Robot() as robot: robot.behavior.drive_straight(distance_mm(200), speed_mmps(100)) - """ - # @TODO: the id_tag we supply can be used to cancel this action, - # however when we implement that we will need some way to get the id_tag - # out of this function. + Example of cancelling the :meth:`drive_straight` behavior: + + .. testcode:: + + import anki_vector + from anki_vector.util import distance_mm, speed_mmps + import time + + with anki_vector.AsyncRobot() as robot: + drive_future = robot.behavior.drive_straight(distance_mm(300), speed_mmps(50)) + time.sleep(2.0) + drive_future.cancel() + """ drive_straight_request = protocol.DriveStraightRequest(speed_mmps=speed.speed_mmps, dist_mm=distance.distance_mm, should_play_animation=should_play_anim, - id_tag=self._get_next_action_id(), + id_tag=_behavior_id, num_retries=num_retries) return await self.grpc_interface.DriveStraight(drive_straight_request) - @connection.on_connection_thread() + @connection.on_connection_thread(is_cancellable_behavior=True) async def turn_in_place(self, angle: util.Angle, speed: util.Angle = util.Angle(0.0), accel: util.Angle = util.Angle(0.0), angle_tolerance: util.Angle = util.Angle(0.0), is_absolute: bool = 0, - num_retries: int = 0) -> protocol.TurnInPlaceResponse: + num_retries: int = 0, + _behavior_id: int = None) -> protocol.TurnInPlaceResponse: """Turn the robot around its current position. Vector must be off of the charger for this movement action. + Note that actions that use the wheels cannot be performed at the same time, + otherwise you may see a TRACKS_LOCKED error. Methods that use the wheels include + :meth:`go_to_pose`, :meth:`dock_with_cube`, :meth:`turn_in_place`, :meth:`drive_straight`, and :meth:`pickup_object`. + :param angle: The angle to turn. Positive values turn to the left, negative values to the right. :param speed: Angular turn speed (per second). @@ -365,7 +456,7 @@ async def turn_in_place(self, of 2 degrees internally). :param is_absolute: True to turn to a specific angle, False to turn relative to the current pose. - :param num_retries: Number of times to re-attempt the turn in case of a failure. + :param num_retries: Number of times to reattempt the turn in case of a failure. Returns: A response from the robot with status information sent when this request successfully completes or fails. @@ -377,24 +468,38 @@ async def turn_in_place(self, with anki_vector.Robot() as robot: robot.behavior.turn_in_place(degrees(90)) + + Example of cancelling the :meth:`turn_in_place` behavior: + + .. testcode:: + + import anki_vector + from anki_vector.util import degrees + import time + + with anki_vector.AsyncRobot() as robot: + turn_future = robot.behavior.turn_in_place(degrees(360)) + time.sleep(0.5) + turn_future.cancel() """ turn_in_place_request = protocol.TurnInPlaceRequest(angle_rad=angle.radians, speed_rad_per_sec=speed.radians, accel_rad_per_sec2=accel.radians, tol_rad=angle_tolerance.radians, is_absolute=is_absolute, - id_tag=self._get_next_action_id(), + id_tag=_behavior_id, num_retries=num_retries) return await self.grpc_interface.TurnInPlace(turn_in_place_request) - @connection.on_connection_thread() + @connection.on_connection_thread(is_cancellable_behavior=True) 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) -> protocol.SetHeadAngleResponse: + num_retries: int = 0, + _behavior_id: int = None) -> protocol.SetHeadAngleResponse: """Tell Vector's head to move to a given angle. :param angle: Desired angle for Vector's head. @@ -404,7 +509,7 @@ async def set_head_angle(self, :param max_speed: Maximum speed of Vector's head in radians per second. :param duration: Time for Vector's head to move in seconds. A value of zero will make Vector try to do it as quickly as possible. - :param num_retries: Number of times to re-attempt the action in case of a failure. + :param num_retries: Number of times to reattempt the action in case of a failure. Returns: A response from the robot with status information sent when this request successfully completes or fails. @@ -421,6 +526,24 @@ async def set_head_angle(self, robot.behavior.set_head_angle(MAX_HEAD_ANGLE) # move head to middle robot.behavior.set_head_angle(degrees(35.0)) + + Example of cancelling the :meth:`set_head_angle` behavior: + + .. testcode:: + + import anki_vector + from anki_vector.behavior import MIN_HEAD_ANGLE, MAX_HEAD_ANGLE + import time + + with anki_vector.AsyncRobot() as robot: + # move head from minimum to maximum angle + robot.behavior.set_head_angle(MIN_HEAD_ANGLE) + time.sleep(1.0) + robot.behavior.set_head_angle(MAX_HEAD_ANGLE) + time.sleep(1.0) + # move head to middle + head_future = robot.behavior.set_head_angle(MIN_HEAD_ANGLE) + head_future.cancel() """ if angle < MIN_HEAD_ANGLE: self.logger.warning("head angle %s too small, should be in %f..%f range - clamping", @@ -435,18 +558,18 @@ async def set_head_angle(self, max_speed_rad_per_sec=max_speed, accel_rad_per_sec2=accel, duration_sec=duration, - id_tag=self._get_next_action_id(), + id_tag=_behavior_id, num_retries=num_retries) - return await self.grpc_interface.SetHeadAngle(set_head_angle_request) - @connection.on_connection_thread() + @connection.on_connection_thread(is_cancellable_behavior=True) 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) -> protocol.SetLiftHeightResponse: + num_retries: int = 0, + _behavior_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 @@ -456,7 +579,7 @@ async def set_lift_height(self, :param max_speed: Maximum speed of Vector's lift in radians per second. :param duration: Time for Vector's lift to move in seconds. A value of zero will make Vector try to do it as quickly as possible. - :param num_retries: Number of times to re-attempt the action in case of a failure. + :param num_retries: Number of times to reattempt the action in case of a failure. Returns: A response from the robot with status information sent when this request successfully completes or fails. @@ -467,8 +590,24 @@ async def set_lift_height(self, with anki_vector.Robot() as robot: robot.behavior.set_lift_height(1.0) - """ + robot.behavior.set_lift_height(0.0) + + Example of cancelling the :meth:`set_lift_height` behavior: + .. testcode:: + + import anki_vector + from anki_vector.behavior import MIN_LIFT_HEIGHT_MM, MAX_LIFT_HEIGHT_MM + import time + + with anki_vector.AsyncRobot() as robot: + robot.behavior.set_lift_height(1.0) + time.sleep(1.0) + lift_future = robot.behavior.set_lift_height(0.0) + time.sleep(1.0) + lift_future = robot.behavior.set_lift_height(1.0) + lift_future.cancel() + """ if height < 0.0: self.logger.warning("lift height %s too small, should be in 0..1 range - clamping", height) height = MIN_LIFT_HEIGHT_MM @@ -482,7 +621,330 @@ async def set_lift_height(self, max_speed_rad_per_sec=max_speed, accel_rad_per_sec2=accel, duration_sec=duration, - id_tag=self._get_next_action_id(), + id_tag=_behavior_id, num_retries=num_retries) return await self.grpc_interface.SetLiftHeight(set_lift_height_request) + + @connection.on_connection_thread(is_cancellable_behavior=True) + async def turn_towards_face(self, + face: faces.Face, + num_retries: int = 0, + _behavior_id: int = None) -> protocol.TurnTowardsFaceResponse: + """Tells Vector to turn towards this face. + + :param face_id: The face Vector will turn towards. + :param num_retries: Number of times to reattempt the action in case of a failure. + + Returns: + A response from the robot with status information sent when this request successfully completes or fails. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + robot.behavior.turn_towards_face(1) + + Example of cancelling the :meth:`turn_towards_face` behavior: + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + turn_towards_face_future = robot.behavior.turn_towards_face(1) + turn_towards_face_future.cancel() + """ + turn_towards_face_request = protocol.TurnTowardsFaceRequest(face_id=face.face_id, + max_turn_angle_rad=util.degrees(180).radians, + id_tag=_behavior_id, + num_retries=num_retries) + + return await self.grpc_interface.TurnTowardsFace(turn_towards_face_request) + + @connection.on_connection_thread(is_cancellable_behavior=True) + async def go_to_object(self, + target_object: objects.LightCube, + distance_from_object, + num_retries: int = 0, + _behavior_id: int = None) -> protocol.GoToObjectResponse: + """Tells Vector to drive to his Cube. + + :param target_object: The destination object. CustomObject instances are not supported. + :param distance_from_object: The distance from the object to stop. This is the distance + between the origins. For instance, the distance from the robot's origin + (between Vector's two front wheels) to the cube's origin (at the center of the cube) is ~40mm. + :param num_retries: Number of times to reattempt action in case of a failure. + + Returns: + A response from the robot with status information sent when this request successfully completes or fails. + + .. testcode:: + import anki_vector + from anki_vector.util import distance_mm + + with anki_vector.Robot() as robot: + robot.world.connect_cube() + + if robot.world.connected_light_cube: + robot.behavior.go_to_object(robot.world.connected_light_cube, distance_mm(70.0)) + """ + if target_object is None: + raise VectorException("Must supply a target_object of type LightCube to go_to_object") + + 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, + num_retries=num_retries) + + return await self.grpc_interface.GoToObject(go_to_object_request) + + @connection.on_connection_thread(is_cancellable_behavior=True) + async def roll_cube(self, + target_object: objects.LightCube, + approach_angle: util.Angle = None, + num_retries: int = 0, + _behavior_id: int = None) -> protocol.RollObjectResponse: + """Tells Vector to roll a specified cube object. + + :param target_object: The cube to roll. + :param approach_angle: The angle to approach the cube from. For example, 180 degrees will cause Vector to drive + past the cube and approach it from behind. + :param num_retries: Number of times to reattempt action in case of a failure. + + Returns: + A response from the robot with status information sent when this request successfully completes or fails. + + .. testcode:: + + import anki_vector + from anki_vector.util import distance_mm + + with anki_vector.Robot() as robot: + robot.world.connect_cube() + + if robot.world.connected_light_cube: + robot.behavior.roll_cube(robot.world.connected_light_cube) + """ + if target_object is None: + raise VectorException("Must supply a target_object of type LightCube to roll_cube") + + if approach_angle is None: + use_approach_angle = False + approach_angle = util.degrees(0) + else: + use_approach_angle = True + approach_angle = approach_angle + + roll_object_request = protocol.RollObjectRequest(object_id=target_object.object_id, + approach_angle_rad=approach_angle.radians, + use_approach_angle=use_approach_angle, + use_pre_dock_pose=use_approach_angle, + id_tag=_behavior_id, + num_retries=num_retries) + + return await self.grpc_interface.RollObject(roll_object_request) + + @connection.on_connection_thread(is_cancellable_behavior=True) + 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: + """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. + :param approach_angle: The angle to approach the cube from. For example, 180 degrees will cause Vector to drive + past the cube and approach it from behind. + :param num_retries: Number of times to reattempt action in case of a failure. + + Returns: + A response from the robot with status information sent when this request successfully completes or fails. + + .. testcode:: + + import anki_vector + from anki_vector.util import distance_mm + + with anki_vector.Robot() as robot: + robot.world.connect_cube() + + if robot.world.connected_light_cube: + robot.behavior.pop_a_wheelie(robot.world.connected_light_cube) + """ + if target_object is None: + raise VectorException("Must supply a target_object of type LightCube to pop_a_wheelie") + + if approach_angle is None: + use_approach_angle = False + approach_angle = util.degrees(0) + else: + use_approach_angle = True + approach_angle = approach_angle + + pop_a_wheelie_request = protocol.PopAWheelieRequest(object_id=target_object.object_id, + approach_angle_rad=approach_angle.radians, + use_approach_angle=use_approach_angle, + use_pre_dock_pose=use_approach_angle, + id_tag=_behavior_id, + num_retries=num_retries) + + return await self.grpc_interface.PopAWheelie(pop_a_wheelie_request) + + @connection.on_connection_thread(is_cancellable_behavior=True) + 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: + """Instruct the robot to pick up his LightCube. + + While picking up the cube, Vector will use path planning. + + Note that actions that use the wheels cannot be performed at the same time, + otherwise you may see a TRACKS_LOCKED error. Methods that use the wheels include + :meth:`go_to_pose`, :meth:`dock_with_cube`, :meth:`turn_in_place`, :meth:`drive_straight`, and :meth:`pickup_object`. + + :param target_object: The LightCube object to dock with. + :param use_pre_dock_pose: Whether or not to try to immediately pick + up an object or first position the robot next to the object. + :param num_retries: Number of times to reattempt action in case of a failure. + + Returns: + A response from the robot with status information sent when this request successfully completes or fails. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + robot.world.connect_cube() + + if robot.world.connected_light_cube: + robot.behavior.pickup_object(robot.world.connected_light_cube) + """ + if target_object is None: + raise VectorException("Must supply a target_object to dock_with_cube") + + pickup_object_request = protocol.PickupObjectRequest(object_id=target_object.object_id, + use_pre_dock_pose=use_pre_dock_pose, + id_tag=_behavior_id, + num_retries=num_retries) + + return await self.grpc_interface.PickupObject(pickup_object_request) + + @connection.on_connection_thread(is_cancellable_behavior=True) + async def place_object_on_ground_here(self, + num_retries: int = 0, + _behavior_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. + + Returns: + A response from the robot with status information sent when this request successfully completes or fails. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + robot.world.connect_cube() + + if robot.world.connected_light_cube: + 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, + num_retries=num_retries) + + return await self.grpc_interface.PlaceObjectOnGroundHere(place_object_on_ground_here_request) + + +class ReserveBehaviorControl(): + """A ReserveBehaviorControl object can be used to suppress the ordinary idle behaviors of + the Robot and keep Vector still between SDK control instances. Care must be taken when + blocking background behaviors, as this may make Vector appear non-responsive. + + This class is most easily used via a built-in SDK script, and can be called on the command-line + via the executable module :class:`anki_vector.reserve_control`: + + .. code-block:: bash + + python3 -m anki_vector.reserve_control + + As long as the script is running, background behaviors will not activate, keeping Vector + still while other SDK scripts may take control. Highest-level behaviors like returning to + the charger due to low battery will still activate. + + System-specific shortcuts calling this executable module can be found in the examples/scripts + folder. These scripts can be double-clicked to easily reserve behavior control for the current + SDK default robot. + + If there is a need to keep background behaviors from activating in a single script, the class + may be used to reserve behavior control while in scope: + + .. code-block:: python + + import anki_vector + from anki_vector import behavior + + with behavior.ReserveBehaviorControl(): + + # At this point, Vector will remain still, even without + # a Robot instance being in scope. + + # take control of the robot as usual + with anki_vector.Robot() as robot: + + robot.anim.play_animation("anim_turn_left_01") + + # Robot will not perform idle behaviors until the script completes + + :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 behavior_activation_timeout: The time to wait for control of the robot before failing. + """ + + def __init__(self, + serial: str = None, + ip: str = None, + config: dict = None, + behavior_activation_timeout: int = 10): + config = config if config is not None else {} + self.logger = util.get_class_logger(__name__, self) + config = {**util.read_configuration(serial, name=None, logger=self.logger), **config} + 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":""}') + + 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) + self._behavior_activation_timeout = behavior_activation_timeout + + def __enter__(self): + self._conn.connect(self._behavior_activation_timeout) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self._conn.close() diff --git a/anki_vector/camera.py b/anki_vector/camera.py index 64bf499..379c08d 100644 --- a/anki_vector/camera.py +++ b/anki_vector/camera.py @@ -18,24 +18,25 @@ The :class:`CameraComponent` class defined in this module is made available as :attr:`anki_vector.robot.Robot.camera` and can be used to enable/disable image -sending and observe images being sent by the robot. +sending and observe images being sent by the robot. It emits :class:`EvtNewRawCameraImage` +and :class:`EvtNewCameraImage` objects whenever a new camera image is available. The camera resolution is 1280 x 720 with a field of view of 90 deg (H) x 50 deg (V). """ # __all__ should order by constants, event classes, other classes, functions. -__all__ = ['CameraComponent'] +__all__ = ["EvtNewRawCameraImage", "EvtNewCameraImage", + "CameraComponent", "CameraImage"] import asyncio from concurrent.futures import CancelledError +import io +import time import sys -try: - import cv2 -except ImportError: - sys.exit("Cannot import opencv-python: Do `pip3 install opencv-python` to install") - -from . import util +from . import annotate, connection, util +from .events import Events +from .exceptions import VectorCameraFeedException from .messaging import protocol try: @@ -49,6 +50,91 @@ sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") +def _convert_to_pillow_image(image_data: bytes) -> Image.Image: + """Convert raw image bytes to a Pillow Image.""" + size = len(image_data) + + # Constuct numpy array out of source data + array = np.empty(size, dtype=np.uint8) + array[0:size] = list(image_data) + + # Decode compressed source data into uncompressed image data + image = Image.open(io.BytesIO(array)) + return image + + +class CameraImage: + """A single image from the robot's camera. + This wraps a raw image and provides an :meth:`annotate_image` method + that can resize and add dynamic annotations to the image, such as + marking up the location of objects and faces. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + image = robot.camera.capture_single_image() + print(f"Displaying image with id {image.image_id}, received at {image.image_recv_time}") + image.raw_image.show() + + :param raw_image: The raw unprocessed image from the camera. + :param image_annotator: The image annotation object. + :param image_id: An image number that increments on every new image received. + """ + + def __init__(self, raw_image: Image.Image, image_annotator: annotate.ImageAnnotator, image_id: int): + + self._raw_image = raw_image + self._image_annotator = image_annotator + self._image_id = image_id + self._image_recv_time = time.time() + + @property + def raw_image(self) -> Image.Image: + """The raw unprocessed image from the camera.""" + return self._raw_image + + @property + def image_id(self) -> int: + """An image number that increments on every new image received.""" + return self._image_id + + @property + def image_recv_time(self) -> float: + """The time the image was received and processed by the SDK.""" + return self._image_recv_time + + def annotate_image(self, scale: float = None, fit_size: tuple = None, resample_mode: int = annotate.RESAMPLE_MODE_NEAREST) -> Image.Image: + """Adds any enabled annotations to the image. + Optionally resizes the image prior to annotations being applied. The + aspect ratio of the resulting image always matches that of the raw image. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + image = robot.camera.capture_single_image() + annotated_image = image.annotate_image() + annotated_image.show() + + :param scale: If set then the base image will be scaled by the + supplied multiplier. Cannot be combined with fit_size + :param fit_size: If set, then scale the image to fit inside + the supplied (width, height) dimensions. The original aspect + ratio will be preserved. Cannot be combined with scale. + :param resample_mode: The resampling mode to use when scaling the + image. Should be either :attr:`~anki_vector.annotate.RESAMPLE_MODE_NEAREST` + (fast) or :attr:`~anki_vector.annotate.RESAMPLE_MODE_BILINEAR` (slower, + but smoother). + """ + return self._image_annotator.annotate_image(self._raw_image, + scale=scale, + fit_size=fit_size, + resample_mode=resample_mode) + + class CameraComponent(util.Component): """Represents Vector's camera. @@ -61,38 +147,47 @@ class CameraComponent(util.Component): import anki_vector - with anki_vector.Robot(enable_camera_feed=True) as robot: + with anki_vector.Robot() as robot: + robot.camera.init_camera_feed() image = robot.camera.latest_image - image.show() + image.raw_image.show() :param robot: A reference to the owner Robot object. """ + #: callable: The factory function that returns an + #: :class:`annotate.ImageAnnotator` class or subclass instance. + annotator_factory = annotate.ImageAnnotator + def __init__(self, robot): super().__init__(robot) - self._latest_image: Image.Image = None + self._image_annotator: annotate.ImageAnnotator = self.annotator_factory(self.robot.world) + self._latest_image: CameraImage = None self._latest_image_id: int = None self._camera_feed_task: asyncio.Task = None self._enabled = False @property @util.block_while_none() - def latest_image(self) -> Image.Image: + def latest_image(self) -> CameraImage: """:class:`Image.Image`: The most recently processed image received from the robot. + The resolution of latest_image is 640x360. + :getter: Returns the Pillow Image representing the latest image .. testcode:: import anki_vector - with anki_vector.Robot(enable_camera_feed=True) as robot: + with anki_vector.Robot() as robot: + robot.camera.init_camera_feed() image = robot.camera.latest_image - image.show() + image.raw_image.show() """ if not self._camera_feed_task: - raise Exception("Camera feed not open!") # TODO: Use a VectorException + raise VectorCameraFeedException() return self._latest_image @property @@ -108,17 +203,46 @@ def latest_image_id(self) -> int: import anki_vector - with anki_vector.Robot(enable_camera_feed=True) as robot: + with anki_vector.Robot() as robot: + robot.camera.init_camera_feed() image = robot.camera.latest_image - image.show() + image.raw_image.show() print(f"latest_image_id: {robot.camera.latest_image_id}") """ if not self._camera_feed_task: - raise Exception("Camera feed not open!") # TODO: Use a VectorException + raise VectorCameraFeedException() return self._latest_image_id + @property + def image_annotator(self) -> annotate.ImageAnnotator: + """The image annotator used to add annotations to the raw camera images. + + .. testcode:: + + import time + import anki_vector + + with anki_vector.Robot(show_viewer=True) as robot: + # Annotations (enabled by default) are displayed on the camera feed + time.sleep(5) + # Disable all annotations + robot.camera.image_annotator.annotation_enabled = False + time.sleep(5) + """ + return self._image_annotator + def init_camera_feed(self) -> None: - """Begin camera feed task.""" + """Begin camera feed task. + + .. 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 not self._camera_feed_task or self._camera_feed_task.done(): self._enabled = True self._camera_feed_task = self.conn.loop.create_task(self._request_and_handle_images()) @@ -129,25 +253,66 @@ def close_camera_feed(self) -> None: self._enabled = False self._camera_feed_task.cancel() future = self.conn.run_coroutine(self._camera_feed_task) - future.result() + try: + future.result() + except CancelledError: + self.logger.debug('Camera feed task was cancelled. This is expected during disconnection.') + # wait for streaming to end, up to 10 seconds + iterations = 0 + max_iterations = 100 + while self.image_streaming_enabled(): + time.sleep(0.1) + iterations += 1 + if iterations > max_iterations: + # leave loop, even if streaming is still enabled + # because other SDK functions will still work and + # the RPC should have had enough time to finish + # which means we _should_ be in a good state. + self.logger.info('Camera Feed closed, but streaming on' + ' robot remained enabled. This is unexpected.') + break self._camera_feed_task = None - def _unpack_image(self, msg: protocol.CameraFeedResponse) -> None: - """Processes raw data from the robot into a more more useful image structure.""" - size = len(msg.data) + async def _image_streaming_enabled(self) -> bool: + """request streaming enabled status from the robot""" + request = protocol.IsImageStreamingEnabledRequest() + response = await self.conn.grpc_interface.IsImageStreamingEnabled(request) + enabled = False + if response: + enabled = response.is_image_streaming_enabled + return enabled + + def image_streaming_enabled(self) -> bool: + """True if image streaming is enabled on the robot + + .. testcode:: - # Constuct numpy array out of source data - array = np.empty(size, dtype=np.uint8) - array[0:size] = list(msg.data) + import anki_vector + with anki_vector.Robot() as robot: + image_streaming_enabled = robot.camera.image_streaming_enabled() + if image_streaming_enabled: + print("Robot is streaming video") + else: + print("Robot is not streaming video") + """ + future = self.conn.run_coroutine(self._image_streaming_enabled()) + return future.result() - # Decode compressed source data into uncompressed image data - imageArray = cv2.imdecode(array, -1) - imageArray = cv2.cvtColor(imageArray, cv2.COLOR_BGR2RGB) + def _unpack_image(self, msg: protocol.CameraFeedResponse) -> None: + """Processes raw data from the robot into a more useful image structure.""" + image = _convert_to_pillow_image(msg.data) - # Convert to Pillow Image - self._latest_image = Image.fromarray(imageArray) + self._latest_image = CameraImage(image, self._image_annotator, msg.image_id) self._latest_image_id = msg.image_id - self.robot.viewer.enqueue_frame(self._latest_image) + + self.conn.run_soon(self.robot.events.dispatch_event(EvtNewRawCameraImage(image), + Events.new_raw_camera_image)) + self.conn.run_soon(self.robot.events.dispatch_event(EvtNewCameraImage(self._latest_image), + Events.new_camera_image)) + + if self._image_annotator.annotation_enabled: + image = self._image_annotator.annotate_image(image) + self.robot.viewer.enqueue_frame(image) async def _request_and_handle_images(self) -> None: """Queries and listens for camera feed events from the robot. @@ -163,3 +328,105 @@ async def _request_and_handle_images(self) -> None: self._unpack_image(evt) except CancelledError: self.logger.debug('Camera feed task was cancelled. This is expected during disconnection.') + + @connection.on_connection_thread() + async def capture_single_image(self) -> CameraImage: + """Request to capture a single image from the robot's camera. + + This call requests the robot to capture an image and returns the + received image, formatted as a Pillow image. This differs from `latest_image`, + which maintains the last image received from the camera feed (if enabled). + + Note that when the camera feed is enabled this call returns the `latest_image`. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + image = robot.camera.capture_single_image() + image.raw_image.show() + """ + if self._enabled: + return self._latest_image + req = protocol.CaptureSingleImageRequest() + res = await self.grpc_interface.CaptureSingleImage(req) + if res and res.data: + image = _convert_to_pillow_image(res.data) + return CameraImage(image, self._image_annotator, res.image_id) + + self.logger.error('Failed to capture a single image') + + +class EvtNewRawCameraImage: # pylint: disable=too-few-public-methods + """Dispatched when a new raw image is received from the robot's camera. + + See also :class:`~anki_vector.camera.EvtNewCameraImage` which provides access + to both the raw image and a scaled and annotated version. + + .. testcode:: + + import threading + + import anki_vector + from anki_vector import events + + def on_new_raw_camera_image(robot, event_type, event, done): + print("Display new camera image") + event.image.show() + done.set() + + with anki_vector.Robot() as robot: + robot.camera.init_camera_feed() + done = threading.Event() + robot.events.subscribe(on_new_raw_camera_image, events.Events.new_raw_camera_image, done) + + print("------ waiting for camera events, press ctrl+c to exit early ------") + + try: + if not done.wait(timeout=5): + print("------ Did not receive a new camera image! ------") + except KeyboardInterrupt: + pass + + :param image: A raw camera image. + """ + + def __init__(self, image: Image.Image): + self.image = image + + +class EvtNewCameraImage: # pylint: disable=too-few-public-methods + """Dispatched when a new camera image is received and processed from the robot's camera. + + .. testcode:: + + import threading + + import anki_vector + from anki_vector import events + + def on_new_camera_image(robot, event_type, event, done): + print(f"Display new annotated camera image with id {event.image.image_id}") + annotated_image = event.image.annotate_image() + annotated_image.show() + done.set() + + with anki_vector.Robot(enable_face_detection=True, enable_custom_object_detection=True) as robot: + robot.camera.init_camera_feed() + done = threading.Event() + robot.events.subscribe(on_new_camera_image, events.Events.new_camera_image, done) + + print("------ waiting for camera events, press ctrl+c to exit early ------") + + try: + if not done.wait(timeout=5): + print("------ Did not receive a new camera image! ------") + except KeyboardInterrupt: + pass + + :param: A wrapped camera image object that contains the raw image. + """ + + def __init__(self, image: CameraImage): + self.image = image diff --git a/anki_vector/camera_viewer/__init__.py b/anki_vector/camera_viewer/__init__.py new file mode 100644 index 0000000..aacabcc --- /dev/null +++ b/anki_vector/camera_viewer/__init__.py @@ -0,0 +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'] diff --git a/anki_vector/connection.py b/anki_vector/connection.py index abcb12f..472b0d8 100644 --- a/anki_vector/connection.py +++ b/anki_vector/connection.py @@ -17,7 +17,7 @@ """ # __all__ should order by constants, event classes, other classes, functions. -__all__ = ['CONTROL_PRIORITY_LEVEL', 'Connection', 'on_connection_thread'] +__all__ = ['ControlPriorityLevel', 'Connection', 'on_connection_thread'] import asyncio from concurrent import futures @@ -30,11 +30,15 @@ import threading from typing import Any, Awaitable, Callable, Coroutine, Dict, List +from google.protobuf.text_format import MessageToString import grpc import aiogrpc from . import util from .exceptions import (connection_error, + VectorAsyncException, + VectorBehaviorControlException, + VectorConfigurationException, VectorControlException, VectorControlTimeoutException, VectorInvalidVersionException, @@ -42,16 +46,18 @@ from .messaging import client, protocol from .version import __version__ -CLIENT_VERSION = 2 -MIN_HOST_VERSION = 0 - -class CONTROL_PRIORITY_LEVEL(Enum): +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, + #: ignore low battery state, work in the dark, etc. + OVERRIDE_BEHAVIORS_PRIORITY = protocol.ControlRequest.OVERRIDE_BEHAVIORS # pylint: disable=no-member #: Runs below Mandatory Physical Reactions such as tucking Vector's head and arms during a fall, - #: yet above Trigger-Word Detection. - TOP_PRIORITY_AI = protocol.ControlRequest.TOP_PRIORITY_AI # pylint: disable=no-member + #: yet above Trigger-Word Detection. Default for normal operation. + DEFAULT_PRIORITY = protocol.ControlRequest.DEFAULT # pylint: disable=no-member + #: Holds control of robot before/after other SDK connections + #: Used to disable idle behaviors. Not to be used for regular behavior control. + RESERVE_CONTROL = protocol.ControlRequest.RESERVE_CONTROL # pylint: disable=no-member class _ControlEventManager: @@ -67,7 +73,7 @@ class _ControlEventManager: :class:`request_event` Is a way of alerting :class:`Connection` to request control. """ - def __init__(self, loop: asyncio.BaseEventLoop = None, priority: CONTROL_PRIORITY_LEVEL = None): + def __init__(self, loop: asyncio.BaseEventLoop = None, priority: ControlPriorityLevel = None): self._granted_event = asyncio.Event(loop=loop) self._lost_event = asyncio.Event(loop=loop) self._request_event = asyncio.Event(loop=loop) @@ -96,7 +102,7 @@ def has_control(self) -> bool: return self._has_control @property - def priority(self) -> CONTROL_PRIORITY_LEVEL: + def priority(self) -> ControlPriorityLevel: """The currently desired priority for the SDK.""" return self._priority @@ -105,17 +111,17 @@ def is_shutdown(self) -> bool: """Detect if the behavior control stream is supposed to shut down.""" return self._is_shutdown - def request(self, priority: CONTROL_PRIORITY_LEVEL = CONTROL_PRIORITY_LEVEL.TOP_PRIORITY_AI) -> None: + def request(self, priority: ControlPriorityLevel = ControlPriorityLevel.DEFAULT_PRIORITY) -> None: """Tell the behavior stream to request control via setting the :class:`request_event`. This will signal Connection's :func:`_request_handler` generator to send a request control message on the BehaviorControl stream. This signal happens asynchronously, and can be tracked using the :class:`granted_event` parameter. :param priority: The level of control in the behavior system. This determines which actions are allowed to - interrupt the SDK execution. See :class:`CONTROL_PRIORITY_LEVEL` for more information. + interrupt the SDK execution. See :class:`ControlPriorityLevel` for more information. """ if priority is None: - raise Exception("Must provide a priority level to request. To disable control, use :func:`release()`.") + raise VectorBehaviorControlException("Must provide a priority level to request. To disable control, use {}.release().", self.__class__.__name__) self._priority = priority self._request_event.set() @@ -188,12 +194,13 @@ async def play_animation(): :param host: The IP address and port of Vector in the format "XX.XX.XX.XX:443". :param cert_file: The location of the certificate file on disk. :param guid: Your robot's unique secret key. - :param requires_behavior_control: True if the connection requires behavior control. + :param behavior_control_level: pass one of :class:`ControlPriorityLevel` priority levels if the connection + requires behavior control, or None to decline control. """ - def __init__(self, name: str, host: str, cert_file: str, guid: str, requires_behavior_control: bool = True): + 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 Exception("Must provide a cert file") + raise VectorConfigurationException("Must provide a cert file to authenticate to Vector.") self._loop: asyncio.BaseEventLoop = None self.name = name self.host = host @@ -209,7 +216,7 @@ def __init__(self, name: str, host: str, cert_file: str, guid: str, requires_beh self._ready_signal: threading.Event = threading.Event() self._done_signal: asyncio.Event = None self._conn_exception = False - self._requires_behavior_control = requires_behavior_control + self._behavior_control_level = behavior_control_level self.active_commands = [] @property @@ -231,7 +238,7 @@ async def connection_function(): :returns: The loop running inside the connection thread """ if self._loop is None: - raise Exception("Attempted to access the connection loop before it was ready") + raise VectorAsyncException("Attempted to access the connection loop before it was ready") return self._loop @property @@ -253,7 +260,7 @@ def thread(self) -> threading.Thread: :returns: The connection thread where all of the grpc messages are being processed. """ if self._thread is None: - raise Exception("Attempted to access the connection loop before it was ready") + raise VectorAsyncException("Attempted to access the connection loop before it was ready") return self._thread @property @@ -281,6 +288,27 @@ async def play_animation(): """ return self._interface + @property + def behavior_control_level(self) -> ControlPriorityLevel: + """Returns the specific :class:`ControlPriorityLevel` requested for behavior control. + + To be able to directly control Vector's motors, override his screen, play an animation, etc., + the :class:`Connection` will need behavior control. This property identifies the enumerated + level of behavior control that the SDK will maintain over the robot. + + For more information about behavior control, see :ref:`behavior `. + + .. code-block:: python + + import anki_vector + + with anki_vector.Robot() as robot: + print(robot.conn.behavior_control_level) # Will print ControlPriorityLevel.DEFAULT_PRIORITY + robot.conn.release_control() + print(robot.conn.behavior_control_level) # Will print None + """ + return self._behavior_control_level + @property def requires_behavior_control(self) -> bool: """True if the :class:`Connection` requires behavior control. @@ -298,20 +326,20 @@ def requires_behavior_control(self) -> bool: import anki_vector - with anki_vector.Robot(requires_behavior_control=False) as robot: - async def callback(event_type, event): - await robot.conn.request_control() - print(robot.conn.requires_behavior_control) # Will print True - await robot.anim.play_animation('anim_pounce_success_02') - await robot.conn.release_control() + def callback(robot, event_type, event): + robot.conn.request_control() + print(robot.conn.requires_behavior_control) # Will print True + robot.anim.play_animation_trigger('GreetAfterLongTime') + robot.conn.release_control() + with anki_vector.Robot(behavior_control_level=None) as robot: print(robot.conn.requires_behavior_control) # Will print False robot.events.subscribe(callback, anki_vector.events.Events.robot_observed_face) # Waits 10 seconds. Show Vector your face. time.sleep(10) """ - return self._requires_behavior_control + return self._behavior_control_level is not None @property def control_lost_event(self) -> asyncio.Event: @@ -343,7 +371,7 @@ async def wait_for_control(conn: anki_vector.connection.Connection): """ return self._control_events.granted_event - def request_control(self, timeout: float = 10.0): + def request_control(self, behavior_control_level: ControlPriorityLevel = ControlPriorityLevel.DEFAULT_PRIORITY, timeout: float = 10.0): """Explicitly request behavior control. Typically used after detecting :func:`control_lost_event`. To be able to directly control Vector's motors, override his screen, play an animation, etc., @@ -362,24 +390,28 @@ async def auto_reconnect(conn: anki_vector.connection.Connection): conn.request_control(timeout=5.0) :param timeout: The time allotted to attempt a connection, in seconds. + :param behavior_control_level: request control of Vector's behavior system at a specific level of control. + See :class:`ControlPriorityLevel` for more information. """ + if not isinstance(behavior_control_level, ControlPriorityLevel): + raise TypeError("behavior_control_level must be of type ControlPriorityLevel") if self._thread is threading.current_thread(): - return asyncio.ensure_future(self._request_control(timeout=timeout), loop=self._loop) - return self.run_coroutine(self._request_control(timeout=timeout)) + return asyncio.ensure_future(self._request_control(behavior_control_level=behavior_control_level, timeout=timeout), loop=self._loop) + return self.run_coroutine(self._request_control(behavior_control_level=behavior_control_level, timeout=timeout)) - async def _request_control(self, timeout: float = 10.0): - self._requires_behavior_control = True - self._control_events.request() + async def _request_control(self, behavior_control_level: ControlPriorityLevel = ControlPriorityLevel.DEFAULT_PRIORITY, timeout: float = 10.0): + self._behavior_control_level = behavior_control_level + self._control_events.request(self._behavior_control_level) try: self._has_control = await asyncio.wait_for(self.control_granted_event.wait(), timeout) except futures.TimeoutError as e: raise VectorControlTimeoutException(f"Surpassed timeout of {timeout}s") from e def release_control(self, timeout: float = 10.0): - """Explicitly request control. Typically used after detecting :func:`control_lost_event`. + """Explicitly release control. Typically used after detecting :func:`control_lost_event`. To be able to directly control Vector's motors, override his screen, play an animation, etc., - the :class:`Connection` will need behavior control. This function will acquire control + the :class:`Connection` will need behavior control. This function will release control of Vector's behavior system. This will raise a :class:`VectorControlTimeoutException` if it fails to receive a control_lost event before the timeout. @@ -399,7 +431,7 @@ async def wait_for_control(conn: anki_vector.connection.Connection): return self.run_coroutine(self._release_control(timeout=timeout)) async def _release_control(self, timeout: float = 10.0): - self._requires_behavior_control = False + self._behavior_control_level = None self._control_events.release() try: self._has_control = await asyncio.wait_for(self.control_lost_event.wait(), timeout) @@ -430,7 +462,7 @@ async def play_animation(): :param timeout: The time allotted to attempt a connection, in seconds. """ if self._thread: - raise Exception("\n\nRepeated connections made to open Connection.") + raise VectorAsyncException("\n\nRepeated connections made to open Connection.") self._ready_signal.clear() self._thread = threading.Thread(target=self._connect, args=(timeout,), daemon=True, name="gRPC Connection Handler Thread") self._thread.start() @@ -448,14 +480,14 @@ def _connect(self, timeout: float) -> None: """ try: if threading.main_thread() is threading.current_thread(): - raise Exception("\n\nConnection._connect must be run outside of the main thread.") + raise VectorAsyncException("\n\nConnection._connect must be run outside of the main thread.") self._loop = asyncio.new_event_loop() asyncio.set_event_loop(self._loop) self._done_signal = asyncio.Event() - if not self._requires_behavior_control: + if not self._behavior_control_level: self._control_events = _ControlEventManager(self._loop) else: - self._control_events = _ControlEventManager(self._loop, priority=CONTROL_PRIORITY_LEVEL.TOP_PRIORITY_AI) + 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() @@ -481,10 +513,10 @@ def _connect(self, timeout: float) -> None: self._interface = client.ExternalInterfaceStub(self._channel) # Verify Vector and the SDK have compatible protocol versions - version = protocol.ProtocolVersionRequest(client_version=CLIENT_VERSION, min_host_version=MIN_HOST_VERSION) + version = protocol.ProtocolVersionRequest(client_version=protocol.PROTOCOL_VERSION_CURRENT, min_host_version=protocol.PROTOCOL_VERSION_MINIMUM) protocol_version = self._loop.run_until_complete(self._interface.ProtocolVersion(version)) - if protocol_version.result != protocol.ProtocolVersionResponse.SUCCESS or MIN_HOST_VERSION > protocol_version.host_version: # pylint: disable=no-member - raise VectorInvalidVersionException(version, protocol_version) + if protocol_version.result != protocol.ProtocolVersionResponse.SUCCESS or protocol.PROTOCOL_VERSION_MINIMUM > protocol_version.host_version: # pylint: disable=no-member + raise VectorInvalidVersionException(protocol_version) self._control_stream_task = self._loop.create_task(self._open_connections()) @@ -501,8 +533,8 @@ def _connect(self, timeout: float) -> None: cpu_version=cpu_version) self._loop.run_until_complete(self._interface.SDKInitialization(initialize)) - if self._requires_behavior_control: - self._loop.run_until_complete(self._request_control(timeout=timeout)) + if self._behavior_control_level: + self._loop.run_until_complete(self._request_control(behavior_control_level=self._behavior_control_level, timeout=timeout)) except Exception as e: # pylint: disable=broad-except # Propagate the errors to the calling thread setattr(self._ready_signal, "exception", e) @@ -531,7 +563,7 @@ async def _request_handler(self): else: msg = protocol.ControlRequest(priority=priority.value) msg = protocol.BehaviorControlRequest(control_request=msg) - self._logger.debug(f"Sending: {msg}") + self._logger.debug(f"BehaviorControl {MessageToString(msg, as_one_line=True)}") yield msg async def _open_connections(self): @@ -540,11 +572,11 @@ async def _open_connections(self): async for response in self._interface.BehaviorControl(self._request_handler()): response_type = response.WhichOneof("response_type") if response_type == 'control_granted_response': - self._logger.info(response) + self._logger.info(f"BehaviorControl {MessageToString(response, as_one_line=True)}") self._control_events.update(True) elif response_type == 'control_lost_event': self._cancel_active() - self._logger.info(response) + self._logger.info(f"BehaviorControl {MessageToString(response, as_one_line=True)}") self._control_events.update(False) except futures.CancelledError: self._logger.debug('Behavior handler task was cancelled. This is expected during disconnection.') @@ -556,7 +588,7 @@ def _cancel_active(self): self.active_commands = [] def close(self): - """Cleanup the connection, and shutdown all the even handlers. + """Cleanup the connection, and shutdown all the event handlers. Usually this should be invoked by the Robot class when it closes. @@ -607,13 +639,13 @@ async def my_coroutine(): :param coro: The coroutine, task or any awaitable to schedule for execution on the connection thread. """ if coro is None or not inspect.isawaitable(coro): - raise Exception(f"\n\n{coro.__name__ if hasattr(coro, '__name__') else coro} is not awaitable, so cannot be ran with run_soon.\n") + raise VectorAsyncException(f"\n\n{coro.__name__ if hasattr(coro, '__name__') else coro} is not awaitable, so cannot be ran with run_soon.\n") def soon(): try: asyncio.ensure_future(coro) except TypeError as e: - raise Exception(f"\n\n{coro.__name__ if hasattr(coro, '__name__') else coro} could not be ensured as a future.\n") from e + raise VectorAsyncException(f"\n\n{coro.__name__ if hasattr(coro, '__name__') else coro} could not be ensured as a future.\n") from e if threading.current_thread() is self._thread: self._loop.call_soon(soon) else: @@ -638,8 +670,8 @@ async def my_coroutine(): :returns: The result of the awaitable's execution. """ if threading.current_thread() is self._thread: - raise Exception("Attempting to invoke async from same thread." - "Instead you may want to use 'run_soon'") + raise VectorAsyncException("Attempting to invoke async from same thread." + "Instead you may want to use 'run_soon'") if asyncio.iscoroutinefunction(coro) or asyncio.iscoroutine(coro): return self._run_coroutine(coro) if asyncio.isfuture(coro): @@ -650,14 +682,14 @@ async def future_coro(): async def wrapped_coro(): return coro() return self._run_coroutine(wrapped_coro()) - raise Exception("\n\nInvalid parameter to run_coroutine: {}\n" - "This function expects a coroutine, task, or awaitable.".format(type(coro))) + raise VectorAsyncException("\n\nInvalid parameter to run_coroutine: {}\n" + "This function expects a coroutine, task, or awaitable.".format(type(coro))) 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) -> Callable[[Coroutine[util.Component, Any, None]], Any]: +def on_connection_thread(log_messaging: bool = True, requires_control: bool = True, is_cancellable_behavior=False) -> 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. @@ -674,6 +706,7 @@ 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. :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. @@ -693,8 +726,8 @@ def _on_connection_thread_decorator(func: Coroutine) -> Any: called from the connection thread respectively. """ if not asyncio.iscoroutinefunction(func): - raise Exception("\n\nCannot define non-coroutine function '{}' to run on connection thread.\n" - "Make sure the function is defined using 'async def'.".format(func.__name__ if hasattr(func, "__name__") else func)) + raise VectorAsyncException("\n\nCannot define non-coroutine function '{}' to run on connection thread.\n" + "Make sure the function is defined using 'async def'.".format(func.__name__ if hasattr(func, "__name__") else func)) @functools.wraps(func) async def log_handler(conn: Connection, func: Coroutine, logger: logging.Logger, *args: List[Any], **kwargs: Dict[str, Any]) -> Coroutine: @@ -709,12 +742,15 @@ async def log_handler(conn: Connection, func: Coroutine, logger: logging.Logger, raise VectorControlException(func.__name__) logger.info(f"Delaying {func.__name__} until behavior control is granted") await asyncio.wait([conn.control_granted_event.wait()], timeout=10) - logger.debug(f'Outgoing {func.__name__}: {args[1:] if log_messaging else "size = {} bytes".format(sys.getsizeof(args[1:]))}') + message = args[1:] + outgoing = message if log_messaging else "size = {} bytes".format(sys.getsizeof(message)) + logger.debug(f'Outgoing {func.__name__}: {outgoing}') try: result = await func(*args, **kwargs) except grpc.RpcError as rpc_error: raise connection_error(rpc_error) from rpc_error - logger.debug(f'Incoming {func.__name__}: {type(result).__name__} ({str(result).strip() if log_messaging else "size = {} bytes".format(sys.getsizeof(result))})') + incoming = str(result).strip() if log_messaging else "size = {} bytes".format(sys.getsizeof(result)) + logger.debug(f'Incoming {func.__name__}: {type(result).__name__} {incoming}') return result @functools.wraps(func) @@ -732,13 +768,31 @@ def result(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: self = args[0] # Get the self reference from the function call # 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 + wrapped_coroutine = log_handler(self.conn, func, self.logger, *args, **kwargs) + if threading.current_thread() == self.conn.thread: if self.conn.loop.is_running(): return asyncio.ensure_future(wrapped_coroutine, loop=self.conn.loop) - raise Exception("\n\nThe connection thread loop is not running, but a " - "function '{}' is being invoked on that thread.\n".format(func.__name__ if hasattr(func, "__name__") else func)) + raise VectorAsyncException("\n\nThe connection thread loop is not running, but a " + "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: + return + + if fut.cancelled(): + self._abort(behavior_id) + + future.add_done_callback(user_cancelled) + if requires_control: self.conn.active_commands.append(future) diff --git a/anki_vector/events.py b/anki_vector/events.py index 9c50a7d..259bc28 100644 --- a/anki_vector/events.py +++ b/anki_vector/events.py @@ -52,6 +52,7 @@ class Events(Enum): robot_changed_observed_face_id = "robot_changed_observed_face_id" # : Robot event for when a known face changes its id. 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. @@ -62,12 +63,16 @@ class Events(Enum): object_disappeared = "object_disappeared" #: Python event triggered when an object has not received a robot_observed_object for a specified time. object_finished_move = "object_finished_move" #: Python event triggered in response to object_stopped_moving with duration data. nav_map_update = "nav_map_update" #: Python event containing nav map data. + new_raw_camera_image = "new_raw_camera_image" #: Python event containing a raw camera image + new_camera_image = "new_camera_image" #: Python event containing a processed camera image (:class:`anki_vector.camera.CameraImage` instance) class _EventCallback: - def __init__(self, callback, on_connection_thread: bool = False): + def __init__(self, callback, *args, _on_connection_thread: bool = False, **kwargs): + self._extra_args = args + self._extra_kwargs = kwargs self._callback = callback - self._on_connection_thread = on_connection_thread + self._on_connection_thread = _on_connection_thread @property def on_connection_thread(self): @@ -77,6 +82,14 @@ def on_connection_thread(self): def callback(self): return self._callback + @property + def extra_args(self): + return self._extra_args + + @property + def extra_kwargs(self): + return self._extra_kwargs + def __eq__(self, other): other_cb = other if hasattr(other, "callback"): @@ -90,8 +103,9 @@ def __hash__(self): class EventHandler: """Listen for Vector events.""" - def __init__(self): + def __init__(self, robot): self.logger = util.get_class_logger(__name__, self) + self._robot = robot self._conn = None self._conn_id = None self.listening_for_events = False @@ -148,30 +162,39 @@ def _notify(self, event_callback, event_name, event_data): if event_callback.on_connection_thread: loop = self._conn.loop thread = self._conn.thread + callback = event_callback.callback + args = event_callback.extra_args + kwargs = event_callback.extra_kwargs + + if asyncio.iscoroutinefunction(callback): + callback = callback(self._robot, event_name, event_data, *args, **kwargs) + elif not asyncio.iscoroutine(callback): + async def call_async(fn, *args, **kwargs): + fn(*args, **kwargs) + callback = call_async(callback, self._robot, event_name, event_data, *args, **kwargs) + if threading.current_thread() is thread: - if asyncio.iscoroutinefunction(callback): - asyncio.ensure_future(callback(event_name, event_data), loop=loop) - elif asyncio.iscoroutine(callback): - asyncio.ensure_future(callback, loop=loop) - else: - loop.call_soon(callback, event_name, event_data) + future = asyncio.ensure_future(callback, loop=loop) else: - if asyncio.iscoroutinefunction(callback): - asyncio.run_coroutine_threadsafe(callback(event_name, event_data), loop=loop) - elif asyncio.iscoroutine(callback): - asyncio.run_coroutine_threadsafe(callback, loop=loop) - else: - loop.call_soon_threadsafe(callback, event_name, event_data) + future = asyncio.run_coroutine_threadsafe(callback, loop=loop) + future.add_done_callback(self._done_callback) - async def dispatch_event_by_name(self, event_data, event_name: str = None): + def _done_callback(self, completed_future): + exc = completed_future.exception() + if exc: + self.logger.error("Event callback exception: %s", exc) + if isinstance(exc, TypeError) and "positional arguments but" in str(exc): + self.logger.error("The subscribed function may be missing parameters in its definition. Make sure it has robot, event_type and event positional parameters.") + + async def dispatch_event_by_name(self, event_data, event_name: str): """Dispatches event to event listeners by name. .. testcode:: import anki_vector - def event_listener(name, msg): + def event_listener(robot, name, msg): print(name) # will print 'my_event' print(msg) # will print 'my_event dispatched' @@ -228,14 +251,14 @@ async def _handle_event_stream(self): except CancelledError: self.logger.debug('Event handler task was cancelled. This is expected during disconnection.') - def subscribe_by_name(self, func: Callable, event_name: str = None, **kwargs): + def subscribe_by_name(self, func: Callable, event_name: str, *args, **kwargs): """Receive a method call when the specified event occurs. .. testcode:: import anki_vector - def event_listener(name, msg): + def event_listener(robot, name, msg): print(name) # will print 'my_event' print(msg) # will print 'my_event dispatched' @@ -245,16 +268,17 @@ def event_listener(name, msg): :param func: A method implemented in your code that will be called when the event is fired. :param event_name: The name of the event that will result in func being called. + :param args: Additional positional arguments to this function will be passed through to the callback in the provided order. + :param kwargs: Additional keyword arguments to this function will be passed through to the callback. """ if not event_name: self.logger.error('Bad event_name in subscribe.') - on_connection_thread = kwargs["on_connection_thread"] if "on_connection_thread" in kwargs else False if event_name not in self.subscribers.keys(): self.subscribers[event_name] = set() - self.subscribers[event_name].add(_EventCallback(func, on_connection_thread)) + self.subscribers[event_name].add(_EventCallback(func, *args, **kwargs)) - def subscribe(self, func: Callable, event_type: Events = None, **kwargs): + def subscribe(self, func: Callable, event_type: Events, *args, **kwargs): """Receive a method call when the specified event occurs. .. testcode:: @@ -262,29 +286,26 @@ def subscribe(self, func: Callable, event_type: Events = None, **kwargs): import anki_vector from anki_vector.events import Events from anki_vector.util import degrees - import functools import threading said_text = False - evt = threading.Event() - def on_robot_observed_face(robot, event_type, event): + def on_robot_observed_face(robot, event_type, event, evt): print("Vector sees a face") global said_text if not said_text: said_text = True - robot.say_text("I see a face!") + robot.behavior.say_text("I see a face!") evt.set() - args = anki_vector.util.parse_command_args() 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) - on_robot_observed_face = functools.partial(on_robot_observed_face, robot) - robot.events.subscribe(on_robot_observed_face, Events.robot_observed_face) + evt = threading.Event() + robot.events.subscribe(on_robot_observed_face, Events.robot_observed_face, evt) print("------ waiting for face events, press ctrl+c to exit early ------") @@ -298,15 +319,17 @@ def on_robot_observed_face(robot, event_type, event): :param func: A method implemented in your code that will be called when the event is fired. :param event_type: The enum type of the event that will result in func being called. + :param args: Additional positional arguments to this function will be passed through to the callback in the provided order. + :param kwargs: Additional keyword arguments to this function will be passed through to the callback. """ if not event_type: self.logger.error('Bad event_type in subscribe.') event_name = event_type.value - self.subscribe_by_name(func, event_name, **kwargs) + self.subscribe_by_name(func, event_name, *args, **kwargs) - def unsubscribe_by_name(self, func: Callable, event_name: str = None): + def unsubscribe_by_name(self, func: Callable, event_name: str): """Unregister a previously subscribed method from an event. .. testcode:: @@ -339,7 +362,7 @@ def event_listener(name, msg): self.logger.error(f"Cannot unsubscribe from event_type '{event_name}'. " "It has no subscribers.") - def unsubscribe(self, func: Callable, event_type: Events = None): + def unsubscribe(self, func: Callable, event_type: Events): """Unregister a previously subscribed method from an event. .. testcode:: @@ -347,29 +370,26 @@ def unsubscribe(self, func: Callable, event_type: Events = None): import anki_vector from anki_vector.events import Events from anki_vector.util import degrees - import functools import threading said_text = False - evt = threading.Event() - def on_robot_observed_face(robot, event_type, event): + def on_robot_observed_face(robot, event_type, event, evt): print("Vector sees a face") global said_text if not said_text: said_text = True - robot.say_text("I see a face!") + robot.behavior.say_text("I see a face!") evt.set() - args = anki_vector.util.parse_command_args() 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) - on_robot_observed_face = functools.partial(on_robot_observed_face, robot) - robot.events.subscribe(on_robot_observed_face, Events.robot_observed_face) + evt = threading.Event() + robot.events.subscribe(on_robot_observed_face, Events.robot_observed_face, evt) print("------ waiting for face events, press ctrl+c to exit early ------") diff --git a/anki_vector/exceptions.py b/anki_vector/exceptions.py index 6c25130..309ddbf 100644 --- a/anki_vector/exceptions.py +++ b/anki_vector/exceptions.py @@ -18,8 +18,13 @@ from grpc import RpcError, StatusCode +from .messaging import protocol + # __all__ should order by constants, event classes, other classes, functions. -__all__ = ['VectorCameraFeedDisabledException', +__all__ = ['VectorAsyncException', + 'VectorBehaviorControlException', + 'VectorCameraFeedException', + 'VectorConfigurationException', 'VectorConnectionException', 'VectorControlException', 'VectorControlTimeoutException', @@ -27,10 +32,12 @@ 'VectorInvalidVersionException', 'VectorNotFoundException', 'VectorNotReadyException', + 'VectorPropertyValueNotReadyException', 'VectorTimeoutException', 'VectorUnauthenticatedException', 'VectorUnavailableException', 'VectorUnimplementedException', + 'VectorExternalAudioPlaybackException', 'connection_error'] @@ -41,17 +48,17 @@ class VectorException(Exception): class VectorInvalidVersionException(VectorException): """Your SDK version is not compatible with Vector's version.""" - def __init__(self, version_request, version_response): + def __init__(self, version_response): host = version_response.host_version - min_host = version_request.min_host_version - client = version_request.client_version + min_host = protocol.PROTOCOL_VERSION_MINIMUM + client = protocol.PROTOCOL_VERSION_CURRENT if min_host > host: error_message = (f"{self.__class__.__doc__}\n\n" - f"Your Vector is an older version that is not supported by the SDK: min={min_host} > host={host}\n" + f"Your Vector is an older version that is not supported by the SDK: Vector={host}, SDK minimum={min_host}\n" f"Use your app to make sure that Vector is on the internet, and able to download the latest update.") else: error_message = (f"{self.__class__.__doc__}\n\n" - f"Your SDK is an older version that is not supported by Vector: {host} > {client}\n" + f"Your SDK is an older version that is not supported by Vector: Vector={host}, SDK={client}\n" f"Please install the latest SDK to continue.") super().__init__(error_message) @@ -61,7 +68,7 @@ class VectorControlException(VectorException): def __init__(self, function): msg = (f"Unable to run '{function}' because it requires behavior control.\n\n" - "Make sure to request control from Vector either by providing the 'enable_behavior_control' parameter to Robot, " + "Make sure to request control from Vector either by providing the 'behavior_control_level' parameter to Robot, " "or directly call 'request_control()' on your connection.") super().__init__(msg) @@ -118,9 +125,34 @@ def connection_error(rpc_error: RpcError) -> VectorConnectionException: class _VectorGenericException(VectorException): - def __init__(self, cause=None): - msg = (f"{self.__class__.__doc__}\n{cause if cause is not None else ''}") - super().__init__(msg) + def __init__(self, _cause=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg + msg = (f"{self.__class__.__doc__}\n\n{_cause if _cause is not None else ''}") + super().__init__(msg.format(*args, **kwargs)) + + +class VectorAsyncException(_VectorGenericException): + """Invalid asynchronous action attempted.""" + + +class VectorBehaviorControlException(_VectorGenericException): + """Invalid behavior control action attempted.""" + + +class VectorCameraFeedException(_VectorGenericException): + """The camera feed is not open. + +Make sure to enable the camera feed either using Robot(show_viewer=True), or robot.camera.init_camera_feed()""" + + +class VectorConfigurationException(_VectorGenericException): + """Invalid or missing configuration data.""" + + +class VectorControlTimeoutException(_VectorGenericException): + """Failed to get control of Vector. + +Please verify that Vector is connected to the internet, is on a flat surface, and is fully charged. +""" class VectorNotFoundException(_VectorGenericException): @@ -134,12 +166,15 @@ class VectorNotReadyException(_VectorGenericException): """Vector tried to do something before it was ready.""" -class VectorControlTimeoutException(_VectorGenericException): - """Failed to get control of Vector. +class VectorPropertyValueNotReadyException(_VectorGenericException): + """Failed to retrieve the value for this property.""" -Please verify that Vector is connected to the internet, is on a flat surface, and is fully charged. -""" + +class VectorUnreliableEventStreamException(VectorException): + """The robot event stream is currently unreliable. + +Please ensure the app is not connected. If this persists, reboot Vector and try again.""" -class VectorCameraFeedDisabledException(VectorException): - """Failed to render video because camera feed was disabled.""" +class VectorExternalAudioPlaybackException(VectorException): + """Failed to play external audio on Vector.""" diff --git a/anki_vector/faces.py b/anki_vector/faces.py index c8d276f..d8d0e6f 100644 --- a/anki_vector/faces.py +++ b/anki_vector/faces.py @@ -21,7 +21,7 @@ knows about, along with those that are currently visible to the camera. Each face is assigned a :class:`Face` object, which generates a number of -observable events whenever the face is observed, has its ID updated. +observable events whenever the face is observed or when the face id is updated. """ # __all__ should order by constants, event classes, other classes, functions. @@ -33,6 +33,152 @@ from . import connection, util, objects, events from .messaging import protocol +#: Length of time in seconds to go without receiving an observed event before +#: assuming that Vector can no longer see a face. +FACE_VISIBILITY_TIMEOUT = objects.OBJECT_VISIBILITY_TIMEOUT + + +class EvtFaceObserved(): # pylint: disable=too-few-public-methods + """Triggered whenever a face is visually identified by the robot. + + A stream of these events are produced while a face is visible to the robot. + Each event has an updated image_rect field. + + See EvtFaceAppeared if you only want to know when a face first + becomes visible. + + .. testcode:: + + import time + + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def handle_face_observed(robot, event_type, event): + # This will be called whenever an EvtFaceObserved is dispatched - + # whenever an face comes into view. + print(f"--------- Vector observed an face --------- \\n{event.face}") + + with anki_vector.Robot(enable_face_detection = True, + show_viewer=True) as robot: + robot.events.subscribe(handle_face_observed, Events.face_observed) + + # If necessary, move Vector's Head and Lift in position to see a face + robot.behavior.set_lift_height(0.0) + robot.behavior.set_head_angle(degrees(45.0)) + + time.sleep(5.0) + + :param face: The Face instance that was observed + :param image_rect: An :class:`anki_vector.util.ImageRect`: defining where the face is within Vector's camera view + :param name: The name of the face + :param pose: The :class:`anki_vector.util.Pose`: defining the position and rotation of the face + """ + + def __init__(self, face, image_rect: util.ImageRect, name, pose: util.Pose): + self.face = face + self.image_rect = image_rect + self.name = name + self.pose = pose + + +class EvtFaceAppeared(): # pylint: disable=too-few-public-methods + """Triggered whenever a face is first visually identified by a robot. + + This differs from EvtFaceObserved in that it's only triggered when + a face initially becomes visible. If it disappears for more than + FACE_VISIBILITY_TIMEOUT seconds and then is seen again, a + EvtFaceDisappeared will be dispatched, followed by another + EvtFaceAppeared event. + + For continuous tracking information about a visible face, see + EvtFaceObserved. + + .. testcode:: + + import time + + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def handle_face_appeared(robot, event_type, event): + # This will be called whenever an EvtFaceAppeared is dispatched - + # whenever an face comes into view. + print(f"--------- Vector started seeing an face --------- \\n{event.face}") + + + def handle_face_disappeared(robot, event_type, event): + # This will be called whenever an EvtFaceDisappeared is dispatched - + # whenever an face goes out of view. + print(f"--------- Vector stopped seeing an face --------- \\n{event.face}") + + + with anki_vector.Robot(enable_face_detection = True, + show_viewer=True) as robot: + robot.events.subscribe(handle_face_appeared, Events.face_appeared) + robot.events.subscribe(handle_face_disappeared, Events.face_disappeared) + + # If necessary, move Vector's Head and Lift in position to see a face + robot.behavior.set_lift_height(0.0) + robot.behavior.set_head_angle(degrees(45.0)) + + time.sleep(5.0) + + :param face:'The Face instance that appeared + :param image_rect: An :class:`anki_vector.util.ImageRect`: defining where the face is within Vector's camera view + :param name: The name of the face + :param pose: The :class:`anki_vector.util.Pose`: defining the position and rotation of the face + """ + + def __init__(self, face, image_rect: util.ImageRect, name, pose: util.Pose): + self.face = face + self.image_rect = image_rect + self.name = name + self.pose = pose + + +class EvtFaceDisappeared(): # pylint: disable=too-few-public-methods + """Triggered whenever a face that was previously being observed is no longer visible. + + .. testcode:: + + import time + + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def handle_face_appeared(robot, event_type, event): + # This will be called whenever an EvtFaceAppeared is dispatched - + # whenever an face comes into view. + print(f"--------- Vector started seeing an face --------- \\n{event.face}") + + + def handle_face_disappeared(robot, event_type, event): + # This will be called whenever an EvtFaceDisappeared is dispatched - + # whenever an face goes out of view. + print(f"--------- Vector stopped seeing an face --------- \\n{event.face}") + + + with anki_vector.Robot(enable_face_detection = True, + show_viewer=True) as robot: + robot.events.subscribe(handle_face_appeared, Events.face_appeared) + robot.events.subscribe(handle_face_disappeared, Events.face_disappeared) + + # If necessary, move Vector's Head and Lift in position to see a face + robot.behavior.set_lift_height(0.0) + robot.behavior.set_head_angle(degrees(45.0)) + + time.sleep(5.0) + + :param face: The Face instance that is no longer being observed + """ + + def __init__(self, face): + self.face = face + class Expression(Enum): """Facial expressions that Vector can distinguish. @@ -98,13 +244,11 @@ def __init__(self, self._on_observed(pose, image_rect, instantiation_timestamp) - self._robot.events.subscribe( - self._on_face_observed, - events.Events.robot_observed_face) + self._robot.events.subscribe(self._on_face_observed, + events.Events.robot_observed_face) - self._robot.events.subscribe( - self._on_face_id_changed, - events.Events.robot_changed_observed_face_id) + self._robot.events.subscribe(self._on_face_id_changed, + events.Events.robot_changed_observed_face_id) def __repr__(self): return (f"<{self.__class__.__name__} Face id: {self.face_id} " @@ -113,13 +257,11 @@ def __repr__(self): def teardown(self): """All faces will be torn down by the world when no longer needed.""" - self._robot.events.unsubscribe( - self._on_face_observed, - events.Events.robot_observed_face) + self._robot.events.unsubscribe(self._on_face_observed, + events.Events.robot_observed_face) - self._robot.events.unsubscribe( - self._on_face_id_changed, - events.Events.robot_changed_observed_face_id) + self._robot.events.unsubscribe(self._on_face_id_changed, + events.Events.robot_changed_observed_face_id) @property def face_id(self) -> int: @@ -132,12 +274,31 @@ def face_id(self) -> int: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: - # Print the visible face ids for face in robot.world.visible_faces: - print(f"Visible id: {face.face_id}") + print(f"Visible face id: {face.face_id}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ return self._face_id if self._updated_face_id is None else self._updated_face_id @@ -153,11 +314,31 @@ def has_updated_face_id(self) -> bool: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: for face in robot.world.visible_faces: was_face_originally_unrecognized_but_is_now_recognized = face.has_updated_face_id + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ return self._updated_face_id is not None @@ -167,11 +348,31 @@ def updated_face_id(self) -> int: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: for face in robot.world.visible_faces: - print(f"updated_face_id: {face.updated_face_id}") + print(f"Updated face id: {face.updated_face_id}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ if self._updated_face_id: return self._updated_face_id @@ -185,11 +386,31 @@ def name(self) -> str: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: for face in robot.world.visible_faces: print(f"Face name: {face.name}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ return self._name @@ -206,11 +427,31 @@ def expression(self) -> str: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: for face in robot.world.visible_faces: - print(f"expression: {face.expression}") + print(f"Expression: {face.expression}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ return self._expression @@ -224,11 +465,31 @@ def expression_score(self) -> List[int]: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: for face in robot.world.visible_faces: - print(f"expression_score: {face.expression_score}") + print(f"Expression score: {face.expression_score}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ return self._expression_score @@ -238,11 +499,31 @@ def left_eye(self) -> List[protocol.CladPoint]: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: for face in robot.world.visible_faces: - print(f"left_eye: {face.left_eye}") + print(f"Left eye: {face.left_eye}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ return self._left_eye @@ -252,11 +533,31 @@ def right_eye(self) -> List[protocol.CladPoint]: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: for face in robot.world.visible_faces: - print(f"right_eye: {face.right_eye}") + print(f"Right eye: {face.right_eye}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ return self._right_eye @@ -266,11 +567,31 @@ def nose(self) -> List[protocol.CladPoint]: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: for face in robot.world.visible_faces: - print(f"nose: {face.nose}") + print(f"Nose: {face.nose}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ return self._nose @@ -280,17 +601,37 @@ def mouth(self) -> List[protocol.CladPoint]: .. testcode:: + import time + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") - with anki_vector.Robot(enable_face_detection=True) as robot: for face in robot.world.visible_faces: - print(f"mouth: {face.mouth}") + print(f"Mouth: {face.mouth}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() """ return self._mouth #### Private Event Handlers #### - def _on_face_observed(self, _, msg): + 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: @@ -313,7 +654,7 @@ def _on_face_observed(self, _, msg): self._mouth = msg.mouth self._on_observed(pose, image_rect, msg.timestamp) - def _on_face_id_changed(self, _, msg): + def _on_face_id_changed(self, _robot, _event_type, msg): """Updates the face id when a tracked face (negative ID) is recognized and receives a positive ID or when face records get merged""" if self._face_id == msg.old_id: diff --git a/anki_vector/lights.py b/anki_vector/lights.py index 8ea84e6..58bb162 100755 --- a/anki_vector/lights.py +++ b/anki_vector/lights.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Helper routines for dealing with Vector's lights and colors.""" +"""Helper routines for dealing with Vector's Cube lights and colors.""" # __all__ should order by constants, event classes, other classes, functions. __all__ = ['MAX_COLOR_PROFILE', 'WHITE_BALANCED_CUBE_PROFILE', @@ -24,7 +24,7 @@ class ColorProfile: - """Applies transforms to make Vector's lights and colors appear as + """Applies transforms to make Vector's Cube lights and colors appear as intended, by limiting maximum channel intensity. :param red_multiplier: Scaling value for the brightness of red Lights diff --git a/anki_vector/mdns.py b/anki_vector/mdns.py new file mode 100644 index 0000000..11bd542 --- /dev/null +++ b/anki_vector/mdns.py @@ -0,0 +1,127 @@ +# Copyright (c) 2019 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:`VectorMdns` class for discovering Vector (without already knowing +the IP address) on a LAN (Local Area Network) over mDNS. + +mDNS (multicast DNS) is a protocol for sending UDP packets containing a DNS query to all +devices on your Local Area Network. If a device knows how to answer the DNS query, it +will respond by multicasting a UDP packet containing the relevant DNS records. +""" +from threading import Condition +import sys + + +class VectorMdns: # pylint: disable=too-few-public-methods + """`VectorMdns` provides a static method for discovering a Vector on the same LAN as + the SDK program and retrieving its IP address. + """ + + @staticmethod + def find_vector(name: str, timeout=5): + """ + :param name: A name like `"Vector-A1B2"`. If :code:`None`, will search for any Vector. + :param timeout: The discovery will timeout in :code:`timeout` seconds. Default value is :code:`5`. + :returns: **dict** or **None** -- if Vector found, **dict** contains keys `"name"` and `"ipv4"` + + .. testcode:: + + import anki_vector + vector_mdns = anki_vector.mdns.VectorMdns.find_vector("Vector-A1B2") + + if vector_mdns is not None: + print(vector_mdns['ipv4']) + else: + print("No Vector found on your local network!") + """ + + # synchronously search for Vector for up to 5 seconds + vector_name = name # should be like 'Vector-V3C7' + return VectorMdns._start_mdns_listener(vector_name, timeout) + + @staticmethod + def _start_mdns_listener(name, timeout): + try: + from zeroconf import ServiceBrowser, Zeroconf + except ImportError: + sys.exit("Cannot import from Zeroconf: Do `pip3 install --user zeroconf` to install") + + # create a Condition object and acquire the underlying lock + cond = Condition() + cond.acquire() + + # instantiate zeroconf and our MdnsListner object for listening to events + zeroconf = Zeroconf() + vector_fullname = None + + if name is not None: + vector_fullname = name + ".local." + + listener = _MdnsListener(vector_fullname, cond) + + # browse for the _ankivector TCP MDNS service, sending events to our listener + ServiceBrowser(zeroconf, "_ankivector._tcp.local.", listener) + + # block until 'timeout' seconds or until we discover vector + cond.wait(timeout) + + # close zeroconf + zeroconf.close() + + # return an IPv4 string (or None) + if listener.ipv4 is None: + return None + + return {'ipv4': listener.ipv4, 'name': listener.name} + + +class _MdnsListener: + """_MdnsListener is an internal helper class which listens for mDNS messages. + + :param name_filter: A String to filter the mDNS responses by name (e.g., `"Vector-A1B2"`). + :param condition: A Condition object to be used for signaling to caller when robot has been discovered. + """ + + def __init__(self, name_filter: str, condition): + self.name_filter = name_filter + self.cond = condition + self.ipv4 = None + self.name = "" + + @staticmethod + def _bytes_to_str_ipv4(ip_bytes): + return str(ip_bytes[0]) + "." + \ + str(ip_bytes[1]) + "." + \ + str(ip_bytes[2]) + "." + \ + str(ip_bytes[3]) + + def remove_service(self, zeroconf, mdns_type, name): + # detect service removal + pass + + def add_service(self, zeroconf, mdns_type, name): + # detect service + info = zeroconf.get_service_info(mdns_type, name) + + if (self.name_filter is None) or (info.server.lower() == self.name_filter.lower()): + # found a match for our filter or there is no filter + self.cond.acquire() + self.ipv4 = _MdnsListener._bytes_to_str_ipv4(info.address) # info.address is IPv4 (DNS record type 'A') + self.name = info.server + + # cause anything waiting for this condition to end waiting + # and release so the other thread can continue + self.cond.notify() + self.cond.release() diff --git a/anki_vector/messaging/__init__.py b/anki_vector/messaging/__init__.py index a08083b..5beb98e 100644 --- a/anki_vector/messaging/__init__.py +++ b/anki_vector/messaging/__init__.py @@ -13,7 +13,30 @@ # limitations under the License. """ -Protobuf and gRPC messages exposed to the Vector Python SDK +Protobuf and gRPC messages exposed to the Vector Python SDK. + +.. warning:: + + This package is provided to understand the messages passed between the SDK and Vector, + and it should not be necessary for writing code that uses the SDK. + +.. code-block:: + python + + from anki_vector.messaging import client, protocol + + async def send_version_request(interface: client.ExternalInterfaceStub, client_version, min_host_version): + \"\"\"This function needs to be executed and awaited in the same event loop + as the interface is created. + \"\"\" + # Create a protocol version request message + version = protocol.ProtocolVersionRequest(client_version=client_version, + min_host_version=min_host_version) + + # Send the protocol version to the external interface and await the result + protocol_version = await interface.ProtocolVersion(version) + +For information about individual messages and their parameters, see :doc:`the protobuf documentation `. """ from . import protocol diff --git a/anki_vector/messaging/behavior.proto b/anki_vector/messaging/behavior.proto index 2d1faf7..9498268 100644 --- a/anki_vector/messaging/behavior.proto +++ b/anki_vector/messaging/behavior.proto @@ -20,17 +20,26 @@ package Anki.Vector.external_interface; import "anki_vector/messaging/messages.proto"; +// Tell the behavior stream to release control from the SDK. +// The stream may stay alive, but Vector will be allowed to run +// his normal behaviors. message ControlRelease {} -// Request control of the behavior system at a given priority +// Request control of the behavior system at a given priority. +// Currently there is only one priority level. message ControlRequest { + // Where in the behavior tree the SDK code should be executed. enum Priority { + // Unknown priority. Used for versions that don't understand old priority levels. UNKNOWN = 0; - // Request control above mandatory physical reactions. Not recommended. - OVERRIDE_ALL = 10; - // Highest priority level. Directly under mandatory physical reactions. - TOP_PRIORITY_AI = 20; + // Highest priority level. Suppresses most automatic physical reactions, use with caution. + OVERRIDE_BEHAVIORS = 10; + // Normal priority level. Directly under mandatory physical reactions. + DEFAULT = 20; + // Enable long-running SDK control between script execution. Not to be used for regular behavior control. + RESERVE_CONTROL = 30; } + // Where in the behavior tree the SDK code should be executed. Priority priority = 1; } @@ -38,7 +47,9 @@ message ControlRequest { // requesting or releasing control. message BehaviorControlRequest { oneof request_type { + // Release control of the behavior system back to Vector. ControlRelease control_release = 1; + // Request control of the behavior system for the SDK. ControlRequest control_request = 2; } } @@ -54,11 +65,20 @@ message ControlGrantedResponse {} // can be as a result of conditions passed to the original ControlRequest. message ControlLostResponse {} +// The ability to reserve control before/after SDK scripts has been lost. +// This control can be regained through another ControlRequest. +message ReservedControlLostResponse {} + // Responses from the behavior stream. message BehaviorControlResponse { oneof response_type { + // The SDK user is now free to directly control Vector. ControlGrantedResponse control_granted_response = 1; + // Control of the behavior system has been lost to a higher priority behavior. ControlLostResponse control_lost_event = 2; + // Used by Vector to verify the connection is still alive. KeepAlivePing keep_alive = 3; + // Behavior system lock has been lost to another connection + ReservedControlLostResponse reserved_control_lost_event = 4; } } \ No newline at end of file diff --git a/anki_vector/messaging/behavior_pb2.py b/anki_vector/messaging/behavior_pb2.py index 0e708b0..d6b6db7 100644 --- a/anki_vector/messaging/behavior_pb2.py +++ b/anki_vector/messaging/behavior_pb2.py @@ -20,7 +20,7 @@ name='anki_vector/messaging/behavior.proto', package='Anki.Vector.external_interface', syntax='proto3', - serialized_pb=_b('\n$anki_vector/messaging/behavior.proto\x12\x1e\x41nki.Vector.external_interface\x1a$anki_vector/messaging/messages.proto\"\x10\n\x0e\x43ontrolRelease\"\x9b\x01\n\x0e\x43ontrolRequest\x12I\n\x08priority\x18\x01 \x01(\x0e\x32\x37.Anki.Vector.external_interface.ControlRequest.Priority\">\n\x08Priority\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x10\n\x0cOVERRIDE_ALL\x10\n\x12\x13\n\x0fTOP_PRIORITY_AI\x10\x14\"\xbe\x01\n\x16\x42\x65haviorControlRequest\x12I\n\x0f\x63ontrol_release\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ControlReleaseH\x00\x12I\n\x0f\x63ontrol_request\x18\x02 \x01(\x0b\x32..Anki.Vector.external_interface.ControlRequestH\x00\x42\x0e\n\x0crequest_type\"\x18\n\x16\x43ontrolGrantedResponse\"\x15\n\x13\x43ontrolLostResponse\"\x9e\x02\n\x17\x42\x65haviorControlResponse\x12Z\n\x18\x63ontrol_granted_response\x18\x01 \x01(\x0b\x32\x36.Anki.Vector.external_interface.ControlGrantedResponseH\x00\x12Q\n\x12\x63ontrol_lost_event\x18\x02 \x01(\x0b\x32\x33.Anki.Vector.external_interface.ControlLostResponseH\x00\x12\x43\n\nkeep_alive\x18\x03 \x01(\x0b\x32-.Anki.Vector.external_interface.KeepAlivePingH\x00\x42\x0f\n\rresponse_typeb\x06proto3') + serialized_pb=_b('\n$anki_vector/messaging/behavior.proto\x12\x1e\x41nki.Vector.external_interface\x1a$anki_vector/messaging/messages.proto\"\x10\n\x0e\x43ontrolRelease\"\xae\x01\n\x0e\x43ontrolRequest\x12I\n\x08priority\x18\x01 \x01(\x0e\x32\x37.Anki.Vector.external_interface.ControlRequest.Priority\"Q\n\x08Priority\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x16\n\x12OVERRIDE_BEHAVIORS\x10\n\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x14\x12\x13\n\x0fRESERVE_CONTROL\x10\x1e\"\xbe\x01\n\x16\x42\x65haviorControlRequest\x12I\n\x0f\x63ontrol_release\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ControlReleaseH\x00\x12I\n\x0f\x63ontrol_request\x18\x02 \x01(\x0b\x32..Anki.Vector.external_interface.ControlRequestH\x00\x42\x0e\n\x0crequest_type\"\x18\n\x16\x43ontrolGrantedResponse\"\x15\n\x13\x43ontrolLostResponse\"\x1d\n\x1bReservedControlLostResponse\"\x82\x03\n\x17\x42\x65haviorControlResponse\x12Z\n\x18\x63ontrol_granted_response\x18\x01 \x01(\x0b\x32\x36.Anki.Vector.external_interface.ControlGrantedResponseH\x00\x12Q\n\x12\x63ontrol_lost_event\x18\x02 \x01(\x0b\x32\x33.Anki.Vector.external_interface.ControlLostResponseH\x00\x12\x43\n\nkeep_alive\x18\x03 \x01(\x0b\x32-.Anki.Vector.external_interface.KeepAlivePingH\x00\x12\x62\n\x1breserved_control_lost_event\x18\x04 \x01(\x0b\x32;.Anki.Vector.external_interface.ReservedControlLostResponseH\x00\x42\x0f\n\rresponse_typeb\x06proto3') , dependencies=[anki__vector_dot_messaging_dot_messages__pb2.DESCRIPTOR,]) @@ -37,18 +37,22 @@ options=None, type=None), _descriptor.EnumValueDescriptor( - name='OVERRIDE_ALL', index=1, number=10, + name='OVERRIDE_BEHAVIORS', index=1, number=10, options=None, type=None), _descriptor.EnumValueDescriptor( - name='TOP_PRIORITY_AI', index=2, number=20, + name='DEFAULT', index=2, number=20, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='RESERVE_CONTROL', index=3, number=30, options=None, type=None), ], containing_type=None, options=None, serialized_start=222, - serialized_end=284, + serialized_end=303, ) _sym_db.RegisterEnumDescriptor(_CONTROLREQUEST_PRIORITY) @@ -105,7 +109,7 @@ oneofs=[ ], serialized_start=129, - serialized_end=284, + serialized_end=303, ) @@ -145,8 +149,8 @@ name='request_type', full_name='Anki.Vector.external_interface.BehaviorControlRequest.request_type', index=0, containing_type=None, fields=[]), ], - serialized_start=287, - serialized_end=477, + serialized_start=306, + serialized_end=496, ) @@ -169,8 +173,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=479, - serialized_end=503, + serialized_start=498, + serialized_end=522, ) @@ -193,8 +197,32 @@ extension_ranges=[], oneofs=[ ], - serialized_start=505, - serialized_end=526, + serialized_start=524, + serialized_end=545, +) + + +_RESERVEDCONTROLLOSTRESPONSE = _descriptor.Descriptor( + name='ReservedControlLostResponse', + full_name='Anki.Vector.external_interface.ReservedControlLostResponse', + 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=547, + serialized_end=576, ) @@ -226,6 +254,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='reserved_control_lost_event', full_name='Anki.Vector.external_interface.BehaviorControlResponse.reserved_control_lost_event', index=3, + number=4, 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=[ ], @@ -241,8 +276,8 @@ name='response_type', full_name='Anki.Vector.external_interface.BehaviorControlResponse.response_type', index=0, containing_type=None, fields=[]), ], - serialized_start=529, - serialized_end=815, + serialized_start=579, + serialized_end=965, ) _CONTROLREQUEST.fields_by_name['priority'].enum_type = _CONTROLREQUEST_PRIORITY @@ -258,6 +293,7 @@ _BEHAVIORCONTROLRESPONSE.fields_by_name['control_granted_response'].message_type = _CONTROLGRANTEDRESPONSE _BEHAVIORCONTROLRESPONSE.fields_by_name['control_lost_event'].message_type = _CONTROLLOSTRESPONSE _BEHAVIORCONTROLRESPONSE.fields_by_name['keep_alive'].message_type = anki__vector_dot_messaging_dot_messages__pb2._KEEPALIVEPING +_BEHAVIORCONTROLRESPONSE.fields_by_name['reserved_control_lost_event'].message_type = _RESERVEDCONTROLLOSTRESPONSE _BEHAVIORCONTROLRESPONSE.oneofs_by_name['response_type'].fields.append( _BEHAVIORCONTROLRESPONSE.fields_by_name['control_granted_response']) _BEHAVIORCONTROLRESPONSE.fields_by_name['control_granted_response'].containing_oneof = _BEHAVIORCONTROLRESPONSE.oneofs_by_name['response_type'] @@ -267,11 +303,15 @@ _BEHAVIORCONTROLRESPONSE.oneofs_by_name['response_type'].fields.append( _BEHAVIORCONTROLRESPONSE.fields_by_name['keep_alive']) _BEHAVIORCONTROLRESPONSE.fields_by_name['keep_alive'].containing_oneof = _BEHAVIORCONTROLRESPONSE.oneofs_by_name['response_type'] +_BEHAVIORCONTROLRESPONSE.oneofs_by_name['response_type'].fields.append( + _BEHAVIORCONTROLRESPONSE.fields_by_name['reserved_control_lost_event']) +_BEHAVIORCONTROLRESPONSE.fields_by_name['reserved_control_lost_event'].containing_oneof = _BEHAVIORCONTROLRESPONSE.oneofs_by_name['response_type'] DESCRIPTOR.message_types_by_name['ControlRelease'] = _CONTROLRELEASE DESCRIPTOR.message_types_by_name['ControlRequest'] = _CONTROLREQUEST DESCRIPTOR.message_types_by_name['BehaviorControlRequest'] = _BEHAVIORCONTROLREQUEST DESCRIPTOR.message_types_by_name['ControlGrantedResponse'] = _CONTROLGRANTEDRESPONSE DESCRIPTOR.message_types_by_name['ControlLostResponse'] = _CONTROLLOSTRESPONSE +DESCRIPTOR.message_types_by_name['ReservedControlLostResponse'] = _RESERVEDCONTROLLOSTRESPONSE DESCRIPTOR.message_types_by_name['BehaviorControlResponse'] = _BEHAVIORCONTROLRESPONSE _sym_db.RegisterFileDescriptor(DESCRIPTOR) @@ -310,6 +350,13 @@ )) _sym_db.RegisterMessage(ControlLostResponse) +ReservedControlLostResponse = _reflection.GeneratedProtocolMessageType('ReservedControlLostResponse', (_message.Message,), dict( + DESCRIPTOR = _RESERVEDCONTROLLOSTRESPONSE, + __module__ = 'anki_vector.messaging.behavior_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ReservedControlLostResponse) + )) +_sym_db.RegisterMessage(ReservedControlLostResponse) + BehaviorControlResponse = _reflection.GeneratedProtocolMessageType('BehaviorControlResponse', (_message.Message,), dict( DESCRIPTOR = _BEHAVIORCONTROLRESPONSE, __module__ = 'anki_vector.messaging.behavior_pb2' diff --git a/anki_vector/messaging/cube.proto b/anki_vector/messaging/cube.proto index 7fced85..3abd66e 100644 --- a/anki_vector/messaging/cube.proto +++ b/anki_vector/messaging/cube.proto @@ -121,10 +121,10 @@ enum ObjectType // Light Cube (a.k.a. Active Block) // BLOCK_LIGHTCUBE1 = 2; + + CHARGER_BASIC = 6; FIRST_CUSTOM_OBJECT_TYPE = 15; - - OBJECT_TYPE_COUNT = 38; } enum CustomType @@ -157,6 +157,7 @@ enum CustomType CUSTOM_TYPE_COUNT = 20; } +// Note: ObjectFamily is deprecated and will be removed in future releases enum ObjectFamily { option allow_alias = true; @@ -217,11 +218,11 @@ message ObjectTapped { } // RobotObservedObject for signaling that an object -// with specified ID/Type/Family was seen at a particular location in the image +// with specified ID/Type was seen at a particular location in the image // and the world message RobotObservedObject { uint32 timestamp = 1; - ObjectFamily object_family = 2; + ObjectFamily object_family = 2 [deprecated=true]; // ObjectFamily is deprecated. Use ObjectType instead to reason about groupings of objects. ObjectType object_type = 3; int32 object_id = 4; // signed to match U2G::PickAndPlaceObject which has the option to have objectID<0 CladRect img_rect = 5; // position in image coords diff --git a/anki_vector/messaging/cube_pb2.py b/anki_vector/messaging/cube_pb2.py index c50f9ac..22d6ad5 100644 --- a/anki_vector/messaging/cube_pb2.py +++ b/anki_vector/messaging/cube_pb2.py @@ -22,7 +22,7 @@ name='anki_vector/messaging/cube.proto', package='Anki.Vector.external_interface', syntax='proto3', - serialized_pb=_b('\n anki_vector/messaging/cube.proto\x12\x1e\x41nki.Vector.external_interface\x1a$anki_vector/messaging/messages.proto\x1a+anki_vector/messaging/response_status.proto\"\x14\n\x12\x43onnectCubeRequest\"\x8d\x01\n\x13\x43onnectCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x11\n\tobject_id\x18\x03 \x01(\r\x12\x12\n\nfactory_id\x18\x04 \x01(\t\"\x17\n\x15\x43ubesAvailableRequest\"m\n\x16\x43ubesAvailableResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x13\n\x0b\x66\x61\x63tory_ids\x18\x02 \x03(\t\"\x17\n\x15\x44isconnectCubeRequest\"X\n\x16\x44isconnectCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x18\n\x16\x46lashCubeLightsRequest\"Y\n\x17\x46lashCubeLightsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x1c\n\x1a\x46orgetPreferredCubeRequest\"]\n\x1b\x46orgetPreferredCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"-\n\x17SetPreferredCubeRequest\x12\x12\n\nfactory_id\x18\x01 \x01(\t\"Z\n\x18SetPreferredCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\xb0\x03\n\x14SetCubeLightsRequest\x12\x11\n\tobject_id\x18\x01 \x01(\r\x12\x10\n\x08on_color\x18\x02 \x03(\r\x12\x11\n\toff_color\x18\x03 \x03(\r\x12\x14\n\x0con_period_ms\x18\x04 \x03(\r\x12\x15\n\roff_period_ms\x18\x05 \x03(\r\x12\x1f\n\x17transition_on_period_ms\x18\x06 \x03(\r\x12 \n\x18transition_off_period_ms\x18\x07 \x03(\r\x12\x0e\n\x06offset\x18\x08 \x03(\x05\x12\x15\n\rrelative_to_x\x18\t \x01(\x02\x12\x15\n\rrelative_to_y\x18\n \x01(\x02\x12\x0e\n\x06rotate\x18\x0b \x01(\x08\x12\\\n\rmake_relative\x18\x0c \x01(\x0e\x32\x45.Anki.Vector.external_interface.SetCubeLightsRequest.MakeRelativeMode\"D\n\x10MakeRelativeMode\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x07\n\x03OFF\x10\x01\x12\r\n\tBY_CORNER\x10\x02\x12\x0b\n\x07\x42Y_SIDE\x10\x03\"W\n\x15SetCubeLightsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"%\n\x0fObjectAvailable\x12\x12\n\nfactory_id\x18\x01 \x01(\t\"\x92\x01\n\x15ObjectConnectionState\x12\x11\n\tobject_id\x18\x01 \x01(\r\x12\x12\n\nfactory_id\x18\x02 \x01(\t\x12?\n\x0bobject_type\x18\x03 \x01(\x0e\x32*.Anki.Vector.external_interface.ObjectType\x12\x11\n\tconnected\x18\x04 \x01(\x08\"3\n\x0bObjectMoved\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x11\n\tobject_id\x18\x02 \x01(\r\";\n\x13ObjectStoppedMoving\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x11\n\tobject_id\x18\x02 \x01(\r\"t\n\x13ObjectUpAxisChanged\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x11\n\tobject_id\x18\x02 \x01(\r\x12\x37\n\x07up_axis\x18\x03 \x01(\x0e\x32&.Anki.Vector.external_interface.UpAxis\"4\n\x0cObjectTapped\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x11\n\tobject_id\x18\x02 \x01(\r\"\xec\x02\n\x13RobotObservedObject\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x43\n\robject_family\x18\x02 \x01(\x0e\x32,.Anki.Vector.external_interface.ObjectFamily\x12?\n\x0bobject_type\x18\x03 \x01(\x0e\x32*.Anki.Vector.external_interface.ObjectType\x12\x11\n\tobject_id\x18\x04 \x01(\x05\x12:\n\x08img_rect\x18\x05 \x01(\x0b\x32(.Anki.Vector.external_interface.CladRect\x12\x38\n\x04pose\x18\x06 \x01(\x0b\x32*.Anki.Vector.external_interface.PoseStruct\x12 \n\x18top_face_orientation_rad\x18\x07 \x01(\x02\x12\x11\n\tis_active\x18\x08 \x01(\r\"\x14\n\x12\x43ubeConnectionLost\"d\n\x1a\x44\x65leteCustomObjectsRequest\x12\x46\n\x04mode\x18\x01 \x01(\x0e\x32\x38.Anki.Vector.external_interface.CustomObjectDeletionMode\"]\n\x1b\x44\x65leteCustomObjectsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x93\x01\n\x1e\x43reateFixedCustomObjectRequest\x12\x38\n\x04pose\x18\x01 \x01(\x0b\x32*.Anki.Vector.external_interface.PoseStruct\x12\x11\n\tx_size_mm\x18\x02 \x01(\x02\x12\x11\n\ty_size_mm\x18\x03 \x01(\x02\x12\x11\n\tz_size_mm\x18\x04 \x01(\x02\"t\n\x1f\x43reateFixedCustomObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x11\n\tobject_id\x18\x02 \x01(\r\"\xba\x04\n\x13\x43ustomBoxDefinition\x12H\n\x0cmarker_front\x18\x01 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12G\n\x0bmarker_back\x18\x02 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12\x46\n\nmarker_top\x18\x03 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12I\n\rmarker_bottom\x18\x04 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12G\n\x0bmarker_left\x18\x05 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12H\n\x0cmarker_right\x18\x06 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12\x11\n\tx_size_mm\x18\x07 \x01(\x02\x12\x11\n\ty_size_mm\x18\x08 \x01(\x02\x12\x11\n\tz_size_mm\x18\t \x01(\x02\x12\x17\n\x0fmarker_width_mm\x18\n \x01(\x02\x12\x18\n\x10marker_height_mm\x18\x0b \x01(\x02\"\x9e\x01\n\x14\x43ustomCubeDefinition\x12\x42\n\x06marker\x18\x01 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12\x0f\n\x07size_mm\x18\x02 \x01(\x02\x12\x17\n\x0fmarker_width_mm\x18\x03 \x01(\x02\x12\x18\n\x10marker_height_mm\x18\x04 \x01(\x02\"\xb2\x01\n\x14\x43ustomWallDefinition\x12\x42\n\x06marker\x18\x01 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12\x10\n\x08width_mm\x18\x02 \x01(\x02\x12\x11\n\theight_mm\x18\x03 \x01(\x02\x12\x17\n\x0fmarker_width_mm\x18\x04 \x01(\x02\x12\x18\n\x10marker_height_mm\x18\x05 \x01(\x02\"\xf0\x02\n\x19\x44\x65\x66ineCustomObjectRequest\x12?\n\x0b\x63ustom_type\x18\x01 \x01(\x0e\x32*.Anki.Vector.external_interface.CustomType\x12\x11\n\tis_unique\x18\x02 \x01(\x08\x12I\n\ncustom_box\x18\x03 \x01(\x0b\x32\x33.Anki.Vector.external_interface.CustomBoxDefinitionH\x00\x12K\n\x0b\x63ustom_cube\x18\x04 \x01(\x0b\x32\x34.Anki.Vector.external_interface.CustomCubeDefinitionH\x00\x12K\n\x0b\x63ustom_wall\x18\x05 \x01(\x0b\x32\x34.Anki.Vector.external_interface.CustomWallDefinitionH\x00\x42\x1a\n\x18\x63ustom_object_definition\"m\n\x1a\x44\x65\x66ineCustomObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\"\xac\x05\n\x0bObjectEvent\x12K\n\x10object_available\x18\x01 \x01(\x0b\x32/.Anki.Vector.external_interface.ObjectAvailableH\x00\x12X\n\x17object_connection_state\x18\x02 \x01(\x0b\x32\x35.Anki.Vector.external_interface.ObjectConnectionStateH\x00\x12\x43\n\x0cobject_moved\x18\x03 \x01(\x0b\x32+.Anki.Vector.external_interface.ObjectMovedH\x00\x12T\n\x15object_stopped_moving\x18\x04 \x01(\x0b\x32\x33.Anki.Vector.external_interface.ObjectStoppedMovingH\x00\x12U\n\x16object_up_axis_changed\x18\x05 \x01(\x0b\x32\x33.Anki.Vector.external_interface.ObjectUpAxisChangedH\x00\x12\x45\n\robject_tapped\x18\x06 \x01(\x0b\x32,.Anki.Vector.external_interface.ObjectTappedH\x00\x12T\n\x15robot_observed_object\x18\x07 \x01(\x0b\x32\x33.Anki.Vector.external_interface.RobotObservedObjectH\x00\x12R\n\x14\x63ube_connection_lost\x18\x08 \x01(\x0b\x32\x32.Anki.Vector.external_interface.CubeConnectionLostH\x00\x42\x13\n\x11object_event_type*\x7f\n\nObjectType\x12\x12\n\x0eINVALID_OBJECT\x10\x00\x12\x12\n\x0eUNKNOWN_OBJECT\x10\x01\x12\x14\n\x10\x42LOCK_LIGHTCUBE1\x10\x02\x12\x1c\n\x18\x46IRST_CUSTOM_OBJECT_TYPE\x10\x0f\x12\x15\n\x11OBJECT_TYPE_COUNT\x10&*\xd0\x03\n\nCustomType\x12\x17\n\x13INVALID_CUSTOM_TYPE\x10\x00\x12\x12\n\x0e\x43USTOM_TYPE_00\x10\x01\x12\x12\n\x0e\x43USTOM_TYPE_01\x10\x02\x12\x12\n\x0e\x43USTOM_TYPE_02\x10\x03\x12\x12\n\x0e\x43USTOM_TYPE_03\x10\x04\x12\x12\n\x0e\x43USTOM_TYPE_04\x10\x05\x12\x12\n\x0e\x43USTOM_TYPE_05\x10\x06\x12\x12\n\x0e\x43USTOM_TYPE_06\x10\x07\x12\x12\n\x0e\x43USTOM_TYPE_07\x10\x08\x12\x12\n\x0e\x43USTOM_TYPE_08\x10\t\x12\x12\n\x0e\x43USTOM_TYPE_09\x10\n\x12\x12\n\x0e\x43USTOM_TYPE_10\x10\x0b\x12\x12\n\x0e\x43USTOM_TYPE_11\x10\x0c\x12\x12\n\x0e\x43USTOM_TYPE_12\x10\r\x12\x12\n\x0e\x43USTOM_TYPE_13\x10\x0e\x12\x12\n\x0e\x43USTOM_TYPE_14\x10\x0f\x12\x12\n\x0e\x43USTOM_TYPE_15\x10\x10\x12\x12\n\x0e\x43USTOM_TYPE_16\x10\x11\x12\x12\n\x0e\x43USTOM_TYPE_17\x10\x12\x12\x12\n\x0e\x43USTOM_TYPE_18\x10\x13\x12\x12\n\x0e\x43USTOM_TYPE_19\x10\x14\x12\x15\n\x11\x43USTOM_TYPE_COUNT\x10\x14\x1a\x02\x10\x01*\x8e\x01\n\x0cObjectFamily\x12\x12\n\x0eINVALID_FAMILY\x10\x00\x12\x12\n\x0eUNKNOWN_FAMILY\x10\x01\x12\t\n\x05\x42LOCK\x10\x02\x12\x0e\n\nLIGHT_CUBE\x10\x03\x12\x0b\n\x07\x43HARGER\x10\x04\x12\x11\n\rCUSTOM_OBJECT\x10\x07\x12\x17\n\x13OBJECT_FAMILY_COUNT\x10\x07\x1a\x02\x10\x01*\x88\x01\n\x06UpAxis\x12\x10\n\x0cINVALID_AXIS\x10\x00\x12\x0e\n\nX_NEGATIVE\x10\x01\x12\x0e\n\nX_POSITIVE\x10\x02\x12\x0e\n\nY_NEGATIVE\x10\x03\x12\x0e\n\nY_POSITIVE\x10\x04\x12\x0e\n\nZ_NEGATIVE\x10\x05\x12\x0e\n\nZ_POSITIVE\x10\x06\x12\x0c\n\x08NUM_AXES\x10\x07*P\n\x0fObjectConstants\x12\x19\n\x15OBJECT_CONSTANTS_NULL\x10\x00\x12\"\n\x1e\x46IXED_CUSTOM_WALL_THICKNESS_MM\x10\n*\xac\x04\n\x12\x43ustomObjectMarker\x12\x19\n\x15\x43USTOM_MARKER_UNKNOWN\x10\x00\x12\x1b\n\x17\x43USTOM_MARKER_CIRCLES_2\x10\x01\x12\x1b\n\x17\x43USTOM_MARKER_CIRCLES_3\x10\x02\x12\x1b\n\x17\x43USTOM_MARKER_CIRCLES_4\x10\x03\x12\x1b\n\x17\x43USTOM_MARKER_CIRCLES_5\x10\x04\x12\x1c\n\x18\x43USTOM_MARKER_DIAMONDS_2\x10\x05\x12\x1c\n\x18\x43USTOM_MARKER_DIAMONDS_3\x10\x06\x12\x1c\n\x18\x43USTOM_MARKER_DIAMONDS_4\x10\x07\x12\x1c\n\x18\x43USTOM_MARKER_DIAMONDS_5\x10\x08\x12\x1c\n\x18\x43USTOM_MARKER_HEXAGONS_2\x10\t\x12\x1c\n\x18\x43USTOM_MARKER_HEXAGONS_3\x10\n\x12\x1c\n\x18\x43USTOM_MARKER_HEXAGONS_4\x10\x0b\x12\x1c\n\x18\x43USTOM_MARKER_HEXAGONS_5\x10\x0c\x12\x1d\n\x19\x43USTOM_MARKER_TRIANGLES_2\x10\r\x12\x1d\n\x19\x43USTOM_MARKER_TRIANGLES_3\x10\x0e\x12\x1d\n\x19\x43USTOM_MARKER_TRIANGLES_4\x10\x0f\x12\x1d\n\x19\x43USTOM_MARKER_TRIANGLES_5\x10\x10\x12\x17\n\x13\x43USTOM_MARKER_COUNT\x10\x10\x1a\x02\x10\x01*\xa4\x01\n\x18\x43ustomObjectDeletionMode\x12\x19\n\x15\x44\x45LETION_MASK_UNKNOWN\x10\x00\x12&\n\"DELETION_MASK_FIXED_CUSTOM_OBJECTS\x10\x01\x12\'\n#DELETION_MASK_CUSTOM_MARKER_OBJECTS\x10\x02\x12\x1c\n\x18\x44\x45LETION_MASK_ARCHETYPES\x10\x03\x62\x06proto3') + serialized_pb=_b('\n anki_vector/messaging/cube.proto\x12\x1e\x41nki.Vector.external_interface\x1a$anki_vector/messaging/messages.proto\x1a+anki_vector/messaging/response_status.proto\"\x14\n\x12\x43onnectCubeRequest\"\x8d\x01\n\x13\x43onnectCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x11\n\tobject_id\x18\x03 \x01(\r\x12\x12\n\nfactory_id\x18\x04 \x01(\t\"\x17\n\x15\x43ubesAvailableRequest\"m\n\x16\x43ubesAvailableResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x13\n\x0b\x66\x61\x63tory_ids\x18\x02 \x03(\t\"\x17\n\x15\x44isconnectCubeRequest\"X\n\x16\x44isconnectCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x18\n\x16\x46lashCubeLightsRequest\"Y\n\x17\x46lashCubeLightsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x1c\n\x1a\x46orgetPreferredCubeRequest\"]\n\x1b\x46orgetPreferredCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"-\n\x17SetPreferredCubeRequest\x12\x12\n\nfactory_id\x18\x01 \x01(\t\"Z\n\x18SetPreferredCubeResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\xb0\x03\n\x14SetCubeLightsRequest\x12\x11\n\tobject_id\x18\x01 \x01(\r\x12\x10\n\x08on_color\x18\x02 \x03(\r\x12\x11\n\toff_color\x18\x03 \x03(\r\x12\x14\n\x0con_period_ms\x18\x04 \x03(\r\x12\x15\n\roff_period_ms\x18\x05 \x03(\r\x12\x1f\n\x17transition_on_period_ms\x18\x06 \x03(\r\x12 \n\x18transition_off_period_ms\x18\x07 \x03(\r\x12\x0e\n\x06offset\x18\x08 \x03(\x05\x12\x15\n\rrelative_to_x\x18\t \x01(\x02\x12\x15\n\rrelative_to_y\x18\n \x01(\x02\x12\x0e\n\x06rotate\x18\x0b \x01(\x08\x12\\\n\rmake_relative\x18\x0c \x01(\x0e\x32\x45.Anki.Vector.external_interface.SetCubeLightsRequest.MakeRelativeMode\"D\n\x10MakeRelativeMode\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x07\n\x03OFF\x10\x01\x12\r\n\tBY_CORNER\x10\x02\x12\x0b\n\x07\x42Y_SIDE\x10\x03\"W\n\x15SetCubeLightsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"%\n\x0fObjectAvailable\x12\x12\n\nfactory_id\x18\x01 \x01(\t\"\x92\x01\n\x15ObjectConnectionState\x12\x11\n\tobject_id\x18\x01 \x01(\r\x12\x12\n\nfactory_id\x18\x02 \x01(\t\x12?\n\x0bobject_type\x18\x03 \x01(\x0e\x32*.Anki.Vector.external_interface.ObjectType\x12\x11\n\tconnected\x18\x04 \x01(\x08\"3\n\x0bObjectMoved\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x11\n\tobject_id\x18\x02 \x01(\r\";\n\x13ObjectStoppedMoving\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x11\n\tobject_id\x18\x02 \x01(\r\"t\n\x13ObjectUpAxisChanged\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x11\n\tobject_id\x18\x02 \x01(\r\x12\x37\n\x07up_axis\x18\x03 \x01(\x0e\x32&.Anki.Vector.external_interface.UpAxis\"4\n\x0cObjectTapped\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x11\n\tobject_id\x18\x02 \x01(\r\"\xf0\x02\n\x13RobotObservedObject\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12G\n\robject_family\x18\x02 \x01(\x0e\x32,.Anki.Vector.external_interface.ObjectFamilyB\x02\x18\x01\x12?\n\x0bobject_type\x18\x03 \x01(\x0e\x32*.Anki.Vector.external_interface.ObjectType\x12\x11\n\tobject_id\x18\x04 \x01(\x05\x12:\n\x08img_rect\x18\x05 \x01(\x0b\x32(.Anki.Vector.external_interface.CladRect\x12\x38\n\x04pose\x18\x06 \x01(\x0b\x32*.Anki.Vector.external_interface.PoseStruct\x12 \n\x18top_face_orientation_rad\x18\x07 \x01(\x02\x12\x11\n\tis_active\x18\x08 \x01(\r\"\x14\n\x12\x43ubeConnectionLost\"d\n\x1a\x44\x65leteCustomObjectsRequest\x12\x46\n\x04mode\x18\x01 \x01(\x0e\x32\x38.Anki.Vector.external_interface.CustomObjectDeletionMode\"]\n\x1b\x44\x65leteCustomObjectsResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"\x93\x01\n\x1e\x43reateFixedCustomObjectRequest\x12\x38\n\x04pose\x18\x01 \x01(\x0b\x32*.Anki.Vector.external_interface.PoseStruct\x12\x11\n\tx_size_mm\x18\x02 \x01(\x02\x12\x11\n\ty_size_mm\x18\x03 \x01(\x02\x12\x11\n\tz_size_mm\x18\x04 \x01(\x02\"t\n\x1f\x43reateFixedCustomObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x11\n\tobject_id\x18\x02 \x01(\r\"\xba\x04\n\x13\x43ustomBoxDefinition\x12H\n\x0cmarker_front\x18\x01 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12G\n\x0bmarker_back\x18\x02 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12\x46\n\nmarker_top\x18\x03 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12I\n\rmarker_bottom\x18\x04 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12G\n\x0bmarker_left\x18\x05 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12H\n\x0cmarker_right\x18\x06 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12\x11\n\tx_size_mm\x18\x07 \x01(\x02\x12\x11\n\ty_size_mm\x18\x08 \x01(\x02\x12\x11\n\tz_size_mm\x18\t \x01(\x02\x12\x17\n\x0fmarker_width_mm\x18\n \x01(\x02\x12\x18\n\x10marker_height_mm\x18\x0b \x01(\x02\"\x9e\x01\n\x14\x43ustomCubeDefinition\x12\x42\n\x06marker\x18\x01 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12\x0f\n\x07size_mm\x18\x02 \x01(\x02\x12\x17\n\x0fmarker_width_mm\x18\x03 \x01(\x02\x12\x18\n\x10marker_height_mm\x18\x04 \x01(\x02\"\xb2\x01\n\x14\x43ustomWallDefinition\x12\x42\n\x06marker\x18\x01 \x01(\x0e\x32\x32.Anki.Vector.external_interface.CustomObjectMarker\x12\x10\n\x08width_mm\x18\x02 \x01(\x02\x12\x11\n\theight_mm\x18\x03 \x01(\x02\x12\x17\n\x0fmarker_width_mm\x18\x04 \x01(\x02\x12\x18\n\x10marker_height_mm\x18\x05 \x01(\x02\"\xf0\x02\n\x19\x44\x65\x66ineCustomObjectRequest\x12?\n\x0b\x63ustom_type\x18\x01 \x01(\x0e\x32*.Anki.Vector.external_interface.CustomType\x12\x11\n\tis_unique\x18\x02 \x01(\x08\x12I\n\ncustom_box\x18\x03 \x01(\x0b\x32\x33.Anki.Vector.external_interface.CustomBoxDefinitionH\x00\x12K\n\x0b\x63ustom_cube\x18\x04 \x01(\x0b\x32\x34.Anki.Vector.external_interface.CustomCubeDefinitionH\x00\x12K\n\x0b\x63ustom_wall\x18\x05 \x01(\x0b\x32\x34.Anki.Vector.external_interface.CustomWallDefinitionH\x00\x42\x1a\n\x18\x63ustom_object_definition\"m\n\x1a\x44\x65\x66ineCustomObjectResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x0f\n\x07success\x18\x02 \x01(\x08\"\xac\x05\n\x0bObjectEvent\x12K\n\x10object_available\x18\x01 \x01(\x0b\x32/.Anki.Vector.external_interface.ObjectAvailableH\x00\x12X\n\x17object_connection_state\x18\x02 \x01(\x0b\x32\x35.Anki.Vector.external_interface.ObjectConnectionStateH\x00\x12\x43\n\x0cobject_moved\x18\x03 \x01(\x0b\x32+.Anki.Vector.external_interface.ObjectMovedH\x00\x12T\n\x15object_stopped_moving\x18\x04 \x01(\x0b\x32\x33.Anki.Vector.external_interface.ObjectStoppedMovingH\x00\x12U\n\x16object_up_axis_changed\x18\x05 \x01(\x0b\x32\x33.Anki.Vector.external_interface.ObjectUpAxisChangedH\x00\x12\x45\n\robject_tapped\x18\x06 \x01(\x0b\x32,.Anki.Vector.external_interface.ObjectTappedH\x00\x12T\n\x15robot_observed_object\x18\x07 \x01(\x0b\x32\x33.Anki.Vector.external_interface.RobotObservedObjectH\x00\x12R\n\x14\x63ube_connection_lost\x18\x08 \x01(\x0b\x32\x32.Anki.Vector.external_interface.CubeConnectionLostH\x00\x42\x13\n\x11object_event_type*{\n\nObjectType\x12\x12\n\x0eINVALID_OBJECT\x10\x00\x12\x12\n\x0eUNKNOWN_OBJECT\x10\x01\x12\x14\n\x10\x42LOCK_LIGHTCUBE1\x10\x02\x12\x11\n\rCHARGER_BASIC\x10\x06\x12\x1c\n\x18\x46IRST_CUSTOM_OBJECT_TYPE\x10\x0f*\xd0\x03\n\nCustomType\x12\x17\n\x13INVALID_CUSTOM_TYPE\x10\x00\x12\x12\n\x0e\x43USTOM_TYPE_00\x10\x01\x12\x12\n\x0e\x43USTOM_TYPE_01\x10\x02\x12\x12\n\x0e\x43USTOM_TYPE_02\x10\x03\x12\x12\n\x0e\x43USTOM_TYPE_03\x10\x04\x12\x12\n\x0e\x43USTOM_TYPE_04\x10\x05\x12\x12\n\x0e\x43USTOM_TYPE_05\x10\x06\x12\x12\n\x0e\x43USTOM_TYPE_06\x10\x07\x12\x12\n\x0e\x43USTOM_TYPE_07\x10\x08\x12\x12\n\x0e\x43USTOM_TYPE_08\x10\t\x12\x12\n\x0e\x43USTOM_TYPE_09\x10\n\x12\x12\n\x0e\x43USTOM_TYPE_10\x10\x0b\x12\x12\n\x0e\x43USTOM_TYPE_11\x10\x0c\x12\x12\n\x0e\x43USTOM_TYPE_12\x10\r\x12\x12\n\x0e\x43USTOM_TYPE_13\x10\x0e\x12\x12\n\x0e\x43USTOM_TYPE_14\x10\x0f\x12\x12\n\x0e\x43USTOM_TYPE_15\x10\x10\x12\x12\n\x0e\x43USTOM_TYPE_16\x10\x11\x12\x12\n\x0e\x43USTOM_TYPE_17\x10\x12\x12\x12\n\x0e\x43USTOM_TYPE_18\x10\x13\x12\x12\n\x0e\x43USTOM_TYPE_19\x10\x14\x12\x15\n\x11\x43USTOM_TYPE_COUNT\x10\x14\x1a\x02\x10\x01*\x8e\x01\n\x0cObjectFamily\x12\x12\n\x0eINVALID_FAMILY\x10\x00\x12\x12\n\x0eUNKNOWN_FAMILY\x10\x01\x12\t\n\x05\x42LOCK\x10\x02\x12\x0e\n\nLIGHT_CUBE\x10\x03\x12\x0b\n\x07\x43HARGER\x10\x04\x12\x11\n\rCUSTOM_OBJECT\x10\x07\x12\x17\n\x13OBJECT_FAMILY_COUNT\x10\x07\x1a\x02\x10\x01*\x88\x01\n\x06UpAxis\x12\x10\n\x0cINVALID_AXIS\x10\x00\x12\x0e\n\nX_NEGATIVE\x10\x01\x12\x0e\n\nX_POSITIVE\x10\x02\x12\x0e\n\nY_NEGATIVE\x10\x03\x12\x0e\n\nY_POSITIVE\x10\x04\x12\x0e\n\nZ_NEGATIVE\x10\x05\x12\x0e\n\nZ_POSITIVE\x10\x06\x12\x0c\n\x08NUM_AXES\x10\x07*P\n\x0fObjectConstants\x12\x19\n\x15OBJECT_CONSTANTS_NULL\x10\x00\x12\"\n\x1e\x46IXED_CUSTOM_WALL_THICKNESS_MM\x10\n*\xac\x04\n\x12\x43ustomObjectMarker\x12\x19\n\x15\x43USTOM_MARKER_UNKNOWN\x10\x00\x12\x1b\n\x17\x43USTOM_MARKER_CIRCLES_2\x10\x01\x12\x1b\n\x17\x43USTOM_MARKER_CIRCLES_3\x10\x02\x12\x1b\n\x17\x43USTOM_MARKER_CIRCLES_4\x10\x03\x12\x1b\n\x17\x43USTOM_MARKER_CIRCLES_5\x10\x04\x12\x1c\n\x18\x43USTOM_MARKER_DIAMONDS_2\x10\x05\x12\x1c\n\x18\x43USTOM_MARKER_DIAMONDS_3\x10\x06\x12\x1c\n\x18\x43USTOM_MARKER_DIAMONDS_4\x10\x07\x12\x1c\n\x18\x43USTOM_MARKER_DIAMONDS_5\x10\x08\x12\x1c\n\x18\x43USTOM_MARKER_HEXAGONS_2\x10\t\x12\x1c\n\x18\x43USTOM_MARKER_HEXAGONS_3\x10\n\x12\x1c\n\x18\x43USTOM_MARKER_HEXAGONS_4\x10\x0b\x12\x1c\n\x18\x43USTOM_MARKER_HEXAGONS_5\x10\x0c\x12\x1d\n\x19\x43USTOM_MARKER_TRIANGLES_2\x10\r\x12\x1d\n\x19\x43USTOM_MARKER_TRIANGLES_3\x10\x0e\x12\x1d\n\x19\x43USTOM_MARKER_TRIANGLES_4\x10\x0f\x12\x1d\n\x19\x43USTOM_MARKER_TRIANGLES_5\x10\x10\x12\x17\n\x13\x43USTOM_MARKER_COUNT\x10\x10\x1a\x02\x10\x01*\xa4\x01\n\x18\x43ustomObjectDeletionMode\x12\x19\n\x15\x44\x45LETION_MASK_UNKNOWN\x10\x00\x12&\n\"DELETION_MASK_FIXED_CUSTOM_OBJECTS\x10\x01\x12\'\n#DELETION_MASK_CUSTOM_MARKER_OBJECTS\x10\x02\x12\x1c\n\x18\x44\x45LETION_MASK_ARCHETYPES\x10\x03\x62\x06proto3') , dependencies=[anki__vector_dot_messaging_dot_messages__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_response__status__pb2.DESCRIPTOR,]) @@ -45,17 +45,17 @@ options=None, type=None), _descriptor.EnumValueDescriptor( - name='FIRST_CUSTOM_OBJECT_TYPE', index=3, number=15, + name='CHARGER_BASIC', index=3, number=6, options=None, type=None), _descriptor.EnumValueDescriptor( - name='OBJECT_TYPE_COUNT', index=4, number=38, + name='FIRST_CUSTOM_OBJECT_TYPE', index=4, number=15, options=None, type=None), ], containing_type=None, options=None, - serialized_start=4885, + serialized_start=4889, serialized_end=5012, ) _sym_db.RegisterEnumDescriptor(_OBJECTTYPE) @@ -398,8 +398,8 @@ INVALID_OBJECT = 0 UNKNOWN_OBJECT = 1 BLOCK_LIGHTCUBE1 = 2 +CHARGER_BASIC = 6 FIRST_CUSTOM_OBJECT_TYPE = 15 -OBJECT_TYPE_COUNT = 38 INVALID_CUSTOM_TYPE = 0 CUSTOM_TYPE_00 = 1 CUSTOM_TYPE_01 = 2 @@ -1261,7 +1261,7 @@ 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), + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')), file=DESCRIPTOR), _descriptor.FieldDescriptor( name='object_type', full_name='Anki.Vector.external_interface.RobotObservedObject.object_type', index=2, number=3, type=14, cpp_type=8, label=1, @@ -1317,7 +1317,7 @@ oneofs=[ ], serialized_start=1948, - serialized_end=2312, + serialized_end=2316, ) @@ -1340,8 +1340,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2314, - serialized_end=2334, + serialized_start=2318, + serialized_end=2338, ) @@ -1371,8 +1371,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2336, - serialized_end=2436, + serialized_start=2340, + serialized_end=2440, ) @@ -1402,8 +1402,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2438, - serialized_end=2531, + serialized_start=2442, + serialized_end=2535, ) @@ -1454,8 +1454,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2534, - serialized_end=2681, + serialized_start=2538, + serialized_end=2685, ) @@ -1492,8 +1492,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2683, - serialized_end=2799, + serialized_start=2687, + serialized_end=2803, ) @@ -1593,8 +1593,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2802, - serialized_end=3372, + serialized_start=2806, + serialized_end=3376, ) @@ -1645,8 +1645,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3375, - serialized_end=3533, + serialized_start=3379, + serialized_end=3537, ) @@ -1704,8 +1704,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3536, - serialized_end=3714, + serialized_start=3540, + serialized_end=3718, ) @@ -1766,8 +1766,8 @@ name='custom_object_definition', full_name='Anki.Vector.external_interface.DefineCustomObjectRequest.custom_object_definition', index=0, containing_type=None, fields=[]), ], - serialized_start=3717, - serialized_end=4085, + serialized_start=3721, + serialized_end=4089, ) @@ -1804,8 +1804,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4087, - serialized_end=4196, + serialized_start=4091, + serialized_end=4200, ) @@ -1887,8 +1887,8 @@ name='object_event_type', full_name='Anki.Vector.external_interface.ObjectEvent.object_event_type', index=0, containing_type=None, fields=[]), ], - serialized_start=4199, - serialized_end=4883, + serialized_start=4203, + serialized_end=4887, ) _CONNECTCUBERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS @@ -2236,4 +2236,6 @@ _OBJECTFAMILY._options = _descriptor._ParseOptions(descriptor_pb2.EnumOptions(), _b('\020\001')) _CUSTOMOBJECTMARKER.has_options = True _CUSTOMOBJECTMARKER._options = _descriptor._ParseOptions(descriptor_pb2.EnumOptions(), _b('\020\001')) +_ROBOTOBSERVEDOBJECT.fields_by_name['object_family'].has_options = True +_ROBOTOBSERVEDOBJECT.fields_by_name['object_family']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\030\001')) # @@protoc_insertion_point(module_scope) diff --git a/anki_vector/messaging/external_interface.proto b/anki_vector/messaging/external_interface.proto index 2c60104..aeaedb3 100644 --- a/anki_vector/messaging/external_interface.proto +++ b/anki_vector/messaging/external_interface.proto @@ -25,6 +25,18 @@ import "anki_vector/messaging/messages.proto"; import "anki_vector/messaging/nav_map.proto"; import "anki_vector/messaging/shared.proto"; +//

The valid versions of the protocol.

+//

Protocol versions are updated when messages change significantly: +// new ones are added and removed, fields deprecated, etc.

+//

The goal is to support as many old versions as possible, only +// bumping the minimum when there is no way to handle a prior version.

+enum ProtocolVersion { + option allow_alias = true; + PROTOCOL_VERSION_UNKNOWN = 0; // Required zero value for enums + PROTOCOL_VERSION_MINIMUM = 0; // Minimum supported version + PROTOCOL_VERSION_CURRENT = 5; // Current version (incremented on 2019.03.12 for streaming update status support.) +} + // The grpc-defined connection between the SDK and Anki's Vector robot. service ExternalInterface { // Checks the supported protocol version by passing in the client version and @@ -47,6 +59,9 @@ service ExternalInterface { // Sets the speed and acceleration for Vector's wheel motors. rpc DriveWheels(DriveWheelsRequest) returns (DriveWheelsResponse) {} + // Requests that Vector play an animation trigger. + rpc PlayAnimationTrigger(PlayAnimationTriggerRequest) returns (PlayAnimationResponse) {} + // Requests that Vector play an animation. rpc PlayAnimation(PlayAnimationRequest) returns (PlayAnimationResponse) {} @@ -58,12 +73,23 @@ service ExternalInterface { }; } + // Constructs and returns a list of animation triggers. + rpc ListAnimationTriggers(ListAnimationTriggersRequest) returns (ListAnimationTriggersResponse) { + option (google.api.http) = { + post: "/v1/list_animation_triggers", + body: "*" + }; + } + // Moves Vector's head. rpc MoveHead(MoveHeadRequest) returns (MoveHeadResponse) {} // Moves Vector's lift. rpc MoveLift(MoveLiftRequest) returns (MoveLiftResponse) {} + // Stop all motor commands requested: MoveHead, MoveLift and DriveWheels. + rpc StopAllMotors(StopAllMotorsRequest) returns (StopAllMotorsResponse) {} + // Sets screen (Vector's face) to a solid color. rpc DisplayFaceImageRGB(DisplayFaceImageRGBRequest) returns (DisplayFaceImageRGBResponse) { option (google.api.http) = { @@ -83,6 +109,10 @@ service ExternalInterface { }; } + // Play audio using Vector's speaker + rpc ExternalAudioStreamPlayback(stream ExternalAudioStreamRequest) returns (stream ExternalAudioStreamResponse) { + } + // Integrate with and acquire control of Vector's AI system. rpc BehaviorControl(stream BehaviorControlRequest) returns (stream BehaviorControlResponse) { } @@ -154,28 +184,43 @@ service ExternalInterface { body: "*" }; } - + rpc EnableMotionDetection(EnableMotionDetectionRequest) returns (EnableMotionDetectionResponse) { option (google.api.http) = { post: "/v1/enable_motion_detection", body: "*" }; } - + rpc EnableMirrorMode(EnableMirrorModeRequest) returns (EnableMirrorModeResponse) { option (google.api.http) = { post: "/v1/enable_mirror_mode", body: "*" }; } - + rpc EnableImageStreaming(EnableImageStreamingRequest) returns (EnableImageStreamingResponse) { option (google.api.http) = { post: "/v1/enable_image_streaming", body: "*" }; } - + + rpc IsImageStreamingEnabled(IsImageStreamingEnabledRequest) returns (IsImageStreamingEnabledResponse) { + option (google.api.http) = { + post: "/v1/is_image_streaming_enabled", + body: "*" + }; + } + + // Cancel action by id + rpc CancelActionByIdTag(CancelActionByIdTagRequest) returns (CancelActionByIdTagResponse) { + option (google.api.http) = { + post: "/v1/cancel_action_by_id_tag", + body: "*" + }; + } + // Tells Vector to drive to the specified pose and orientation. rpc GoToPose(GoToPoseRequest) returns (GoToPoseResponse) { option (google.api.http) = { @@ -208,6 +253,30 @@ service ExternalInterface { }; } + // Make Vector look for faces by turning and angling his head + rpc FindFaces(FindFacesRequest) returns (FindFacesResponse) { + option (google.api.http) = { + post: "/v1/find_faces", + body: "*" + }; + } + + // Make Vector look around in place by turning and moving his head + rpc LookAroundInPlace(LookAroundInPlaceRequest) returns (LookAroundInPlaceResponse) { + option (google.api.http) = { + post: "/v1/look_around_in_place", + body: "*" + }; + } + + // Make Vector roll his block, regardless of relative position and orientation + rpc RollBlock(RollBlockRequest) returns (RollBlockResponse) { + option (google.api.http) = { + post: "/v1/roll_block", + body: "*" + }; + } + // Get the photos info. rpc PhotosInfo(PhotosInfoRequest) returns (PhotosInfoResponse) { option (google.api.http) = { @@ -252,6 +321,27 @@ service ExternalInterface { // Tell Vector's lift to move to a given height. rpc SetLiftHeight(SetLiftHeightRequest) returns (SetLiftHeightResponse) {} + // Tell Vector to turn towards this face. + rpc TurnTowardsFace(TurnTowardsFaceRequest) returns (TurnTowardsFaceResponse) {} + + // Tell Vector to drive to the specified object. + rpc GoToObject(GoToObjectRequest) returns (GoToObjectResponse) {} + + // Tell Vector to roll his cube. + rpc RollObject(RollObjectRequest) returns (RollObjectResponse) {} + + // Tell Vector to "pop a wheelie" using his cube. + rpc PopAWheelie(PopAWheelieRequest) returns (PopAWheelieResponse) {} + + // Instruct the robot to pick up the supplied object. + rpc PickupObject(PickupObjectRequest) returns (PickupObjectResponse) {} + + // Ask Vector to place the object he is carrying on the ground at the current location. + rpc PlaceObjectOnGroundHere(PlaceObjectOnGroundHereRequest) returns (PlaceObjectOnGroundHereResponse) {} + + // Set the Robot's master volume + rpc SetMasterVolume(MasterVolumeRequest) returns (MasterVolumeResponse) {} + rpc UserAuthentication(UserAuthenticationRequest) returns (UserAuthenticationResponse) { option (google.api.http) = { post: "/v1/user_authentication", @@ -275,14 +365,6 @@ service ExternalInterface { }; } - // Get the network information for Vector. - rpc NetworkState(NetworkStateRequest) returns (NetworkStateResponse) { - option (google.api.http) = { - post: "/v1/network_state", - body: "*" - }; - } - // Make Vector speak text. rpc SayText(SayTextRequest) returns (SayTextResponse) { option (google.api.http) = { @@ -390,6 +472,14 @@ service ExternalInterface { }; } + // Request a single image to be captured and sent from the robot. + rpc CaptureSingleImage(CaptureSingleImageRequest) returns (CaptureSingleImageResponse) { + option (google.api.http) = { + post: "/v1/capture_single_image", + body: "*" + }; + } + // Set Vector's eye color. rpc SetEyeColor(SetEyeColorRequest) returns (SetEyeColorResponse) { option (google.api.http) = { @@ -398,11 +488,12 @@ service ExternalInterface { }; } + // Stream navigation map data. rpc NavMapFeed(NavMapFeedRequest) returns (stream NavMapFeedResponse) { option (google.api.http) = { post: "/v1/nav_map_feed", body: "*" }; - } + } } \ No newline at end of file diff --git a/anki_vector/messaging/external_interface_pb2.py b/anki_vector/messaging/external_interface_pb2.py index 7ceff3b..1a9e771 100644 --- a/anki_vector/messaging/external_interface_pb2.py +++ b/anki_vector/messaging/external_interface_pb2.py @@ -3,6 +3,7 @@ import sys _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection @@ -25,15 +26,48 @@ 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.proto2\xff\x41\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~\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*\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\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\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\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\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\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\x97\x01\n\x0cNetworkState\x12\x33.Anki.Vector.external_interface.NetworkStateRequest\x1a\x34.Anki.Vector.external_interface.NetworkStateResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/network_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\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\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') , 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,]) +_PROTOCOLVERSION = _descriptor.EnumDescriptor( + name='ProtocolVersion', + full_name='Anki.Vector.external_interface.ProtocolVersion', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='PROTOCOL_VERSION_UNKNOWN', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='PROTOCOL_VERSION_MINIMUM', index=1, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='PROTOCOL_VERSION_CURRENT', index=2, number=5, + options=None, + type=None), + ], + containing_type=None, + options=_descriptor._ParseOptions(descriptor_pb2.EnumOptions(), _b('\020\001')), + serialized_start=295, + serialized_end=406, +) +_sym_db.RegisterEnumDescriptor(_PROTOCOLVERSION) + +ProtocolVersion = enum_type_wrapper.EnumTypeWrapper(_PROTOCOLVERSION) +PROTOCOL_VERSION_UNKNOWN = 0 +PROTOCOL_VERSION_MINIMUM = 0 +PROTOCOL_VERSION_CURRENT = 5 +DESCRIPTOR.enum_types_by_name['ProtocolVersion'] = _PROTOCOLVERSION _sym_db.RegisterFileDescriptor(DESCRIPTOR) +_PROTOCOLVERSION.has_options = True +_PROTOCOLVERSION._options = _descriptor._ParseOptions(descriptor_pb2.EnumOptions(), _b('\020\001')) _EXTERNALINTERFACE = _descriptor.ServiceDescriptor( name='ExternalInterface', @@ -41,8 +75,8 @@ file=DESCRIPTOR, index=0, options=None, - serialized_start=296, - serialized_end=8743, + serialized_start=409, + serialized_end=11254, methods=[ _descriptor.MethodDescriptor( name='ProtocolVersion', @@ -71,10 +105,19 @@ output_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVEWHEELSRESPONSE, options=None, ), + _descriptor.MethodDescriptor( + name='PlayAnimationTrigger', + full_name='Anki.Vector.external_interface.ExternalInterface.PlayAnimationTrigger', + index=3, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._PLAYANIMATIONTRIGGERREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._PLAYANIMATIONRESPONSE, + options=None, + ), _descriptor.MethodDescriptor( name='PlayAnimation', full_name='Anki.Vector.external_interface.ExternalInterface.PlayAnimation', - index=3, + index=4, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._PLAYANIMATIONREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._PLAYANIMATIONRESPONSE, @@ -83,16 +126,25 @@ _descriptor.MethodDescriptor( name='ListAnimations', full_name='Anki.Vector.external_interface.ExternalInterface.ListAnimations', - index=4, + index=5, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._LISTANIMATIONSREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._LISTANIMATIONSRESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\030\"\023/v1/list_animations:\001*')), ), + _descriptor.MethodDescriptor( + name='ListAnimationTriggers', + full_name='Anki.Vector.external_interface.ExternalInterface.ListAnimationTriggers', + index=6, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._LISTANIMATIONTRIGGERSREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._LISTANIMATIONTRIGGERSRESPONSE, + options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002 \"\033/v1/list_animation_triggers:\001*')), + ), _descriptor.MethodDescriptor( name='MoveHead', full_name='Anki.Vector.external_interface.ExternalInterface.MoveHead', - index=5, + index=7, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._MOVEHEADREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._MOVEHEADRESPONSE, @@ -101,16 +153,25 @@ _descriptor.MethodDescriptor( name='MoveLift', full_name='Anki.Vector.external_interface.ExternalInterface.MoveLift', - index=6, + index=8, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._MOVELIFTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._MOVELIFTRESPONSE, options=None, ), + _descriptor.MethodDescriptor( + name='StopAllMotors', + full_name='Anki.Vector.external_interface.ExternalInterface.StopAllMotors', + index=9, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._STOPALLMOTORSREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._STOPALLMOTORSRESPONSE, + options=None, + ), _descriptor.MethodDescriptor( name='DisplayFaceImageRGB', full_name='Anki.Vector.external_interface.ExternalInterface.DisplayFaceImageRGB', - index=7, + index=10, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DISPLAYFACEIMAGERGBREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DISPLAYFACEIMAGERGBRESPONSE, @@ -119,16 +180,25 @@ _descriptor.MethodDescriptor( name='EventStream', full_name='Anki.Vector.external_interface.ExternalInterface.EventStream', - index=8, + index=11, containing_service=None, input_type=anki__vector_dot_messaging_dot_shared__pb2._EVENTREQUEST, output_type=anki__vector_dot_messaging_dot_shared__pb2._EVENTRESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002)\"\020/v1/event_stream:\001*Z\022\022\020/v1/event_stream')), ), + _descriptor.MethodDescriptor( + name='ExternalAudioStreamPlayback', + full_name='Anki.Vector.external_interface.ExternalInterface.ExternalAudioStreamPlayback', + index=12, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._EXTERNALAUDIOSTREAMREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._EXTERNALAUDIOSTREAMRESPONSE, + options=None, + ), _descriptor.MethodDescriptor( name='BehaviorControl', full_name='Anki.Vector.external_interface.ExternalInterface.BehaviorControl', - index=9, + index=13, containing_service=None, input_type=anki__vector_dot_messaging_dot_behavior__pb2._BEHAVIORCONTROLREQUEST, output_type=anki__vector_dot_messaging_dot_behavior__pb2._BEHAVIORCONTROLRESPONSE, @@ -137,7 +207,7 @@ _descriptor.MethodDescriptor( name='AssumeBehaviorControl', full_name='Anki.Vector.external_interface.ExternalInterface.AssumeBehaviorControl', - index=10, + 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, @@ -146,7 +216,7 @@ _descriptor.MethodDescriptor( name='CancelFaceEnrollment', full_name='Anki.Vector.external_interface.ExternalInterface.CancelFaceEnrollment', - index=11, + index=15, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._CANCELFACEENROLLMENTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._CANCELFACEENROLLMENTRESPONSE, @@ -155,7 +225,7 @@ _descriptor.MethodDescriptor( name='RequestEnrolledNames', full_name='Anki.Vector.external_interface.ExternalInterface.RequestEnrolledNames', - index=12, + index=16, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._REQUESTENROLLEDNAMESREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._REQUESTENROLLEDNAMESRESPONSE, @@ -164,7 +234,7 @@ _descriptor.MethodDescriptor( name='UpdateEnrolledFaceByID', full_name='Anki.Vector.external_interface.ExternalInterface.UpdateEnrolledFaceByID', - index=13, + index=17, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._UPDATEENROLLEDFACEBYIDREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._UPDATEENROLLEDFACEBYIDRESPONSE, @@ -173,7 +243,7 @@ _descriptor.MethodDescriptor( name='EraseEnrolledFaceByID', full_name='Anki.Vector.external_interface.ExternalInterface.EraseEnrolledFaceByID', - index=14, + index=18, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ERASEENROLLEDFACEBYIDREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ERASEENROLLEDFACEBYIDRESPONSE, @@ -182,7 +252,7 @@ _descriptor.MethodDescriptor( name='EraseAllEnrolledFaces', full_name='Anki.Vector.external_interface.ExternalInterface.EraseAllEnrolledFaces', - index=15, + index=19, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ERASEALLENROLLEDFACESREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ERASEALLENROLLEDFACESRESPONSE, @@ -191,7 +261,7 @@ _descriptor.MethodDescriptor( name='SetFaceToEnroll', full_name='Anki.Vector.external_interface.ExternalInterface.SetFaceToEnroll', - index=16, + index=20, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SETFACETOENROLLREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SETFACETOENROLLRESPONSE, @@ -200,7 +270,7 @@ _descriptor.MethodDescriptor( name='EnableMarkerDetection', full_name='Anki.Vector.external_interface.ExternalInterface.EnableMarkerDetection', - index=17, + index=21, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEMARKERDETECTIONREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEMARKERDETECTIONRESPONSE, @@ -209,7 +279,7 @@ _descriptor.MethodDescriptor( name='EnableFaceDetection', full_name='Anki.Vector.external_interface.ExternalInterface.EnableFaceDetection', - index=18, + index=22, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEFACEDETECTIONREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEFACEDETECTIONRESPONSE, @@ -218,7 +288,7 @@ _descriptor.MethodDescriptor( name='EnableMotionDetection', full_name='Anki.Vector.external_interface.ExternalInterface.EnableMotionDetection', - index=19, + index=23, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEMOTIONDETECTIONREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEMOTIONDETECTIONRESPONSE, @@ -227,7 +297,7 @@ _descriptor.MethodDescriptor( name='EnableMirrorMode', full_name='Anki.Vector.external_interface.ExternalInterface.EnableMirrorMode', - index=20, + index=24, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEMIRRORMODEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEMIRRORMODERESPONSE, @@ -236,16 +306,34 @@ _descriptor.MethodDescriptor( name='EnableImageStreaming', full_name='Anki.Vector.external_interface.ExternalInterface.EnableImageStreaming', - index=21, + index=25, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEIMAGESTREAMINGREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._ENABLEIMAGESTREAMINGRESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\037\"\032/v1/enable_image_streaming:\001*')), ), + _descriptor.MethodDescriptor( + name='IsImageStreamingEnabled', + full_name='Anki.Vector.external_interface.ExternalInterface.IsImageStreamingEnabled', + index=26, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._ISIMAGESTREAMINGENABLEDREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._ISIMAGESTREAMINGENABLEDRESPONSE, + options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002#\"\036/v1/is_image_streaming_enabled:\001*')), + ), + _descriptor.MethodDescriptor( + name='CancelActionByIdTag', + full_name='Anki.Vector.external_interface.ExternalInterface.CancelActionByIdTag', + index=27, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._CANCELACTIONBYIDTAGREQUEST, + 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='GoToPose', full_name='Anki.Vector.external_interface.ExternalInterface.GoToPose', - index=22, + index=28, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._GOTOPOSEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._GOTOPOSERESPONSE, @@ -254,7 +342,7 @@ _descriptor.MethodDescriptor( name='DockWithCube', full_name='Anki.Vector.external_interface.ExternalInterface.DockWithCube', - index=23, + index=29, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DOCKWITHCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DOCKWITHCUBERESPONSE, @@ -263,7 +351,7 @@ _descriptor.MethodDescriptor( name='DriveOffCharger', full_name='Anki.Vector.external_interface.ExternalInterface.DriveOffCharger', - index=24, + index=30, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVEOFFCHARGERREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVEOFFCHARGERRESPONSE, @@ -272,16 +360,43 @@ _descriptor.MethodDescriptor( name='DriveOnCharger', full_name='Anki.Vector.external_interface.ExternalInterface.DriveOnCharger', - index=25, + index=31, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVEONCHARGERREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVEONCHARGERRESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\031\"\024/v1/drive_on_charger:\001*')), ), + _descriptor.MethodDescriptor( + name='FindFaces', + full_name='Anki.Vector.external_interface.ExternalInterface.FindFaces', + index=32, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._FINDFACESREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._FINDFACESRESPONSE, + options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\023\"\016/v1/find_faces:\001*')), + ), + _descriptor.MethodDescriptor( + name='LookAroundInPlace', + full_name='Anki.Vector.external_interface.ExternalInterface.LookAroundInPlace', + index=33, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._LOOKAROUNDINPLACEREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._LOOKAROUNDINPLACERESPONSE, + options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\035\"\030/v1/look_around_in_place:\001*')), + ), + _descriptor.MethodDescriptor( + name='RollBlock', + full_name='Anki.Vector.external_interface.ExternalInterface.RollBlock', + index=34, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._ROLLBLOCKREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._ROLLBLOCKRESPONSE, + options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\023\"\016/v1/roll_block:\001*')), + ), _descriptor.MethodDescriptor( name='PhotosInfo', full_name='Anki.Vector.external_interface.ExternalInterface.PhotosInfo', - index=26, + index=35, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._PHOTOSINFOREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._PHOTOSINFORESPONSE, @@ -290,7 +405,7 @@ _descriptor.MethodDescriptor( name='Photo', full_name='Anki.Vector.external_interface.ExternalInterface.Photo', - index=27, + index=36, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._PHOTOREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._PHOTORESPONSE, @@ -299,7 +414,7 @@ _descriptor.MethodDescriptor( name='Thumbnail', full_name='Anki.Vector.external_interface.ExternalInterface.Thumbnail', - index=28, + index=37, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._THUMBNAILREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._THUMBNAILRESPONSE, @@ -308,7 +423,7 @@ _descriptor.MethodDescriptor( name='DeletePhoto', full_name='Anki.Vector.external_interface.ExternalInterface.DeletePhoto', - index=29, + index=38, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DELETEPHOTOREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DELETEPHOTORESPONSE, @@ -317,7 +432,7 @@ _descriptor.MethodDescriptor( name='DriveStraight', full_name='Anki.Vector.external_interface.ExternalInterface.DriveStraight', - index=30, + index=39, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVESTRAIGHTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._DRIVESTRAIGHTRESPONSE, @@ -326,7 +441,7 @@ _descriptor.MethodDescriptor( name='TurnInPlace', full_name='Anki.Vector.external_interface.ExternalInterface.TurnInPlace', - index=31, + index=40, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._TURNINPLACEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._TURNINPLACERESPONSE, @@ -335,7 +450,7 @@ _descriptor.MethodDescriptor( name='SetHeadAngle', full_name='Anki.Vector.external_interface.ExternalInterface.SetHeadAngle', - index=32, + index=41, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SETHEADANGLEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SETHEADANGLERESPONSE, @@ -344,16 +459,79 @@ _descriptor.MethodDescriptor( name='SetLiftHeight', full_name='Anki.Vector.external_interface.ExternalInterface.SetLiftHeight', - index=33, + index=42, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SETLIFTHEIGHTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SETLIFTHEIGHTRESPONSE, options=None, ), + _descriptor.MethodDescriptor( + name='TurnTowardsFace', + full_name='Anki.Vector.external_interface.ExternalInterface.TurnTowardsFace', + index=43, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._TURNTOWARDSFACEREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._TURNTOWARDSFACERESPONSE, + options=None, + ), + _descriptor.MethodDescriptor( + name='GoToObject', + full_name='Anki.Vector.external_interface.ExternalInterface.GoToObject', + index=44, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._GOTOOBJECTREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._GOTOOBJECTRESPONSE, + options=None, + ), + _descriptor.MethodDescriptor( + name='RollObject', + full_name='Anki.Vector.external_interface.ExternalInterface.RollObject', + index=45, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._ROLLOBJECTREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._ROLLOBJECTRESPONSE, + options=None, + ), + _descriptor.MethodDescriptor( + name='PopAWheelie', + full_name='Anki.Vector.external_interface.ExternalInterface.PopAWheelie', + index=46, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._POPAWHEELIEREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._POPAWHEELIERESPONSE, + options=None, + ), + _descriptor.MethodDescriptor( + name='PickupObject', + full_name='Anki.Vector.external_interface.ExternalInterface.PickupObject', + index=47, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._PICKUPOBJECTREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._PICKUPOBJECTRESPONSE, + options=None, + ), + _descriptor.MethodDescriptor( + name='PlaceObjectOnGroundHere', + full_name='Anki.Vector.external_interface.ExternalInterface.PlaceObjectOnGroundHere', + index=48, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._PLACEOBJECTONGROUNDHEREREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._PLACEOBJECTONGROUNDHERERESPONSE, + options=None, + ), + _descriptor.MethodDescriptor( + name='SetMasterVolume', + full_name='Anki.Vector.external_interface.ExternalInterface.SetMasterVolume', + index=49, + containing_service=None, + input_type=anki__vector_dot_messaging_dot_messages__pb2._MASTERVOLUMEREQUEST, + output_type=anki__vector_dot_messaging_dot_messages__pb2._MASTERVOLUMERESPONSE, + options=None, + ), _descriptor.MethodDescriptor( name='UserAuthentication', full_name='Anki.Vector.external_interface.ExternalInterface.UserAuthentication', - index=34, + index=50, containing_service=None, input_type=anki__vector_dot_messaging_dot_shared__pb2._USERAUTHENTICATIONREQUEST, output_type=anki__vector_dot_messaging_dot_shared__pb2._USERAUTHENTICATIONRESPONSE, @@ -362,7 +540,7 @@ _descriptor.MethodDescriptor( name='BatteryState', full_name='Anki.Vector.external_interface.ExternalInterface.BatteryState', - index=35, + index=51, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._BATTERYSTATEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._BATTERYSTATERESPONSE, @@ -371,25 +549,16 @@ _descriptor.MethodDescriptor( name='VersionState', full_name='Anki.Vector.external_interface.ExternalInterface.VersionState', - index=36, + index=52, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._VERSIONSTATEREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._VERSIONSTATERESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\026\"\021/v1/version_state:\001*')), ), - _descriptor.MethodDescriptor( - name='NetworkState', - full_name='Anki.Vector.external_interface.ExternalInterface.NetworkState', - index=37, - containing_service=None, - input_type=anki__vector_dot_messaging_dot_messages__pb2._NETWORKSTATEREQUEST, - output_type=anki__vector_dot_messaging_dot_messages__pb2._NETWORKSTATERESPONSE, - options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\026\"\021/v1/network_state:\001*')), - ), _descriptor.MethodDescriptor( name='SayText', full_name='Anki.Vector.external_interface.ExternalInterface.SayText', - index=38, + index=53, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SAYTEXTREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SAYTEXTRESPONSE, @@ -398,7 +567,7 @@ _descriptor.MethodDescriptor( name='ConnectCube', full_name='Anki.Vector.external_interface.ExternalInterface.ConnectCube', - index=39, + index=54, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._CONNECTCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._CONNECTCUBERESPONSE, @@ -407,7 +576,7 @@ _descriptor.MethodDescriptor( name='DisconnectCube', full_name='Anki.Vector.external_interface.ExternalInterface.DisconnectCube', - index=40, + index=55, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._DISCONNECTCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._DISCONNECTCUBERESPONSE, @@ -416,7 +585,7 @@ _descriptor.MethodDescriptor( name='CubesAvailable', full_name='Anki.Vector.external_interface.ExternalInterface.CubesAvailable', - index=41, + index=56, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._CUBESAVAILABLEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._CUBESAVAILABLERESPONSE, @@ -425,7 +594,7 @@ _descriptor.MethodDescriptor( name='FlashCubeLights', full_name='Anki.Vector.external_interface.ExternalInterface.FlashCubeLights', - index=42, + index=57, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._FLASHCUBELIGHTSREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._FLASHCUBELIGHTSRESPONSE, @@ -434,7 +603,7 @@ _descriptor.MethodDescriptor( name='ForgetPreferredCube', full_name='Anki.Vector.external_interface.ExternalInterface.ForgetPreferredCube', - index=43, + index=58, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._FORGETPREFERREDCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._FORGETPREFERREDCUBERESPONSE, @@ -443,7 +612,7 @@ _descriptor.MethodDescriptor( name='SetPreferredCube', full_name='Anki.Vector.external_interface.ExternalInterface.SetPreferredCube', - index=44, + index=59, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._SETPREFERREDCUBEREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._SETPREFERREDCUBERESPONSE, @@ -452,7 +621,7 @@ _descriptor.MethodDescriptor( name='DeleteCustomObjects', full_name='Anki.Vector.external_interface.ExternalInterface.DeleteCustomObjects', - index=45, + index=60, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._DELETECUSTOMOBJECTSREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._DELETECUSTOMOBJECTSRESPONSE, @@ -461,7 +630,7 @@ _descriptor.MethodDescriptor( name='CreateFixedCustomObject', full_name='Anki.Vector.external_interface.ExternalInterface.CreateFixedCustomObject', - index=46, + index=61, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._CREATEFIXEDCUSTOMOBJECTREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._CREATEFIXEDCUSTOMOBJECTRESPONSE, @@ -470,7 +639,7 @@ _descriptor.MethodDescriptor( name='DefineCustomObject', full_name='Anki.Vector.external_interface.ExternalInterface.DefineCustomObject', - index=47, + index=62, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._DEFINECUSTOMOBJECTREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._DEFINECUSTOMOBJECTRESPONSE, @@ -479,7 +648,7 @@ _descriptor.MethodDescriptor( name='SetCubeLights', full_name='Anki.Vector.external_interface.ExternalInterface.SetCubeLights', - index=48, + index=63, containing_service=None, input_type=anki__vector_dot_messaging_dot_cube__pb2._SETCUBELIGHTSREQUEST, output_type=anki__vector_dot_messaging_dot_cube__pb2._SETCUBELIGHTSRESPONSE, @@ -488,7 +657,7 @@ _descriptor.MethodDescriptor( name='AudioFeed', full_name='Anki.Vector.external_interface.ExternalInterface.AudioFeed', - index=49, + index=64, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._AUDIOFEEDREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._AUDIOFEEDRESPONSE, @@ -497,16 +666,25 @@ _descriptor.MethodDescriptor( name='CameraFeed', full_name='Anki.Vector.external_interface.ExternalInterface.CameraFeed', - index=50, + index=65, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._CAMERAFEEDREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._CAMERAFEEDRESPONSE, options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\024\"\017/v1/camera_feed:\001*')), ), + _descriptor.MethodDescriptor( + name='CaptureSingleImage', + full_name='Anki.Vector.external_interface.ExternalInterface.CaptureSingleImage', + index=66, + 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='SetEyeColor', full_name='Anki.Vector.external_interface.ExternalInterface.SetEyeColor', - index=51, + index=67, containing_service=None, input_type=anki__vector_dot_messaging_dot_messages__pb2._SETEYECOLORREQUEST, output_type=anki__vector_dot_messaging_dot_messages__pb2._SETEYECOLORRESPONSE, @@ -515,7 +693,7 @@ _descriptor.MethodDescriptor( name='NavMapFeed', full_name='Anki.Vector.external_interface.ExternalInterface.NavMapFeed', - index=52, + index=68, 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, diff --git a/anki_vector/messaging/external_interface_pb2_grpc.py b/anki_vector/messaging/external_interface_pb2_grpc.py index 6f3ed72..a41ae3e 100644 --- a/anki_vector/messaging/external_interface_pb2_grpc.py +++ b/anki_vector/messaging/external_interface_pb2_grpc.py @@ -33,6 +33,11 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.DriveWheelsRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.DriveWheelsResponse.FromString, ) + self.PlayAnimationTrigger = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/PlayAnimationTrigger', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.PlayAnimationTriggerRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PlayAnimationResponse.FromString, + ) self.PlayAnimation = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/PlayAnimation', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.PlayAnimationRequest.SerializeToString, @@ -43,6 +48,11 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.ListAnimationsRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.ListAnimationsResponse.FromString, ) + self.ListAnimationTriggers = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/ListAnimationTriggers', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.ListAnimationTriggersRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.ListAnimationTriggersResponse.FromString, + ) self.MoveHead = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/MoveHead', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.MoveHeadRequest.SerializeToString, @@ -53,6 +63,11 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.MoveLiftRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.MoveLiftResponse.FromString, ) + self.StopAllMotors = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/StopAllMotors', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.StopAllMotorsRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.StopAllMotorsResponse.FromString, + ) self.DisplayFaceImageRGB = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/DisplayFaceImageRGB', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.DisplayFaceImageRGBRequest.SerializeToString, @@ -63,6 +78,11 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_shared__pb2.EventRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_shared__pb2.EventResponse.FromString, ) + self.ExternalAudioStreamPlayback = channel.stream_stream( + '/Anki.Vector.external_interface.ExternalInterface/ExternalAudioStreamPlayback', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.ExternalAudioStreamRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.ExternalAudioStreamResponse.FromString, + ) self.BehaviorControl = channel.stream_stream( '/Anki.Vector.external_interface.ExternalInterface/BehaviorControl', request_serializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlRequest.SerializeToString, @@ -128,6 +148,16 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.EnableImageStreamingRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.EnableImageStreamingResponse.FromString, ) + self.IsImageStreamingEnabled = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/IsImageStreamingEnabled', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.IsImageStreamingEnabledRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.IsImageStreamingEnabledResponse.FromString, + ) + self.CancelActionByIdTag = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/CancelActionByIdTag', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.CancelActionByIdTagRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CancelActionByIdTagResponse.FromString, + ) self.GoToPose = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/GoToPose', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.GoToPoseRequest.SerializeToString, @@ -148,6 +178,21 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.DriveOnChargerRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.DriveOnChargerResponse.FromString, ) + self.FindFaces = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/FindFaces', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.FindFacesRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.FindFacesResponse.FromString, + ) + self.LookAroundInPlace = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/LookAroundInPlace', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.LookAroundInPlaceRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.LookAroundInPlaceResponse.FromString, + ) + self.RollBlock = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/RollBlock', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.RollBlockRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.RollBlockResponse.FromString, + ) self.PhotosInfo = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/PhotosInfo', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.PhotosInfoRequest.SerializeToString, @@ -188,6 +233,41 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.SetLiftHeightRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.SetLiftHeightResponse.FromString, ) + self.TurnTowardsFace = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/TurnTowardsFace', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.TurnTowardsFaceRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.TurnTowardsFaceResponse.FromString, + ) + self.GoToObject = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/GoToObject', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.GoToObjectRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.GoToObjectResponse.FromString, + ) + self.RollObject = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/RollObject', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.RollObjectRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.RollObjectResponse.FromString, + ) + self.PopAWheelie = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/PopAWheelie', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.PopAWheelieRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PopAWheelieResponse.FromString, + ) + self.PickupObject = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/PickupObject', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.PickupObjectRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PickupObjectResponse.FromString, + ) + self.PlaceObjectOnGroundHere = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/PlaceObjectOnGroundHere', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.PlaceObjectOnGroundHereRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PlaceObjectOnGroundHereResponse.FromString, + ) + self.SetMasterVolume = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/SetMasterVolume', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.MasterVolumeRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.MasterVolumeResponse.FromString, + ) self.UserAuthentication = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/UserAuthentication', request_serializer=anki__vector_dot_messaging_dot_shared__pb2.UserAuthenticationRequest.SerializeToString, @@ -203,11 +283,6 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.VersionStateRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.VersionStateResponse.FromString, ) - self.NetworkState = channel.unary_unary( - '/Anki.Vector.external_interface.ExternalInterface/NetworkState', - request_serializer=anki__vector_dot_messaging_dot_messages__pb2.NetworkStateRequest.SerializeToString, - response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.NetworkStateResponse.FromString, - ) self.SayText = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/SayText', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.SayTextRequest.SerializeToString, @@ -273,6 +348,11 @@ def __init__(self, channel): request_serializer=anki__vector_dot_messaging_dot_messages__pb2.CameraFeedRequest.SerializeToString, response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CameraFeedResponse.FromString, ) + self.CaptureSingleImage = channel.unary_unary( + '/Anki.Vector.external_interface.ExternalInterface/CaptureSingleImage', + request_serializer=anki__vector_dot_messaging_dot_messages__pb2.CaptureSingleImageRequest.SerializeToString, + response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CaptureSingleImageResponse.FromString, + ) self.SetEyeColor = channel.unary_unary( '/Anki.Vector.external_interface.ExternalInterface/SetEyeColor', request_serializer=anki__vector_dot_messaging_dot_messages__pb2.SetEyeColorRequest.SerializeToString, @@ -311,6 +391,13 @@ def DriveWheels(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def PlayAnimationTrigger(self, request, context): + """Requests that Vector play an animation trigger. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def PlayAnimation(self, request, context): """Requests that Vector play an animation. """ @@ -325,6 +412,13 @@ def ListAnimations(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def ListAnimationTriggers(self, request, context): + """Constructs and returns a list of animation triggers. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def MoveHead(self, request, context): """Moves Vector's head. """ @@ -339,6 +433,13 @@ def MoveLift(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def StopAllMotors(self, request, context): + """Stop all motor commands requested: MoveHead, MoveLift and DriveWheels. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def DisplayFaceImageRGB(self, request, context): """Sets screen (Vector's face) to a solid color. """ @@ -353,6 +454,13 @@ def EventStream(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def ExternalAudioStreamPlayback(self, request_iterator, context): + """Play audio using Vector's speaker + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def BehaviorControl(self, request_iterator, context): """Integrate with and acquire control of Vector's AI system. """ @@ -444,6 +552,20 @@ def EnableImageStreaming(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def IsImageStreamingEnabled(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CancelActionByIdTag(self, request, context): + """Cancel action by id + """ + 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. """ @@ -472,6 +594,27 @@ def DriveOnCharger(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def FindFaces(self, request, context): + """Make Vector look for faces by turning and angling his head + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def LookAroundInPlace(self, request, context): + """Make Vector look around in place by turning and moving his head + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RollBlock(self, request, context): + """Make Vector roll his block, regardless of relative position and orientation + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def PhotosInfo(self, request, context): """Get the photos info. """ @@ -528,6 +671,55 @@ def SetLiftHeight(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def TurnTowardsFace(self, request, context): + """Tell Vector to turn towards this face. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GoToObject(self, request, context): + """Tell Vector to drive to the specified object. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RollObject(self, request, context): + """Tell Vector to roll his cube. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PopAWheelie(self, request, context): + """Tell Vector to "pop a wheelie" using his cube. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PickupObject(self, request, context): + """Instruct the robot to pick up the supplied object. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PlaceObjectOnGroundHere(self, request, context): + """Ask Vector to place the object he is carrying on the ground at the current location. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetMasterVolume(self, request, context): + """Set the Robot's master volume + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def UserAuthentication(self, request, context): # missing associated documentation comment in .proto file pass @@ -549,13 +741,6 @@ def VersionState(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def NetworkState(self, request, context): - """Get the network information for Vector. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - def SayText(self, request, context): """Make Vector speak text. """ @@ -656,6 +841,13 @@ def CameraFeed(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def CaptureSingleImage(self, request, context): + """Request a single image to be captured and sent from the robot. + """ + 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. """ @@ -664,8 +856,8 @@ def SetEyeColor(self, request, context): raise NotImplementedError('Method not implemented!') def NavMapFeed(self, request, context): - # missing associated documentation comment in .proto file - pass + """Stream navigation map data. + """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') @@ -688,6 +880,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.DriveWheelsRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.DriveWheelsResponse.SerializeToString, ), + 'PlayAnimationTrigger': grpc.unary_unary_rpc_method_handler( + servicer.PlayAnimationTrigger, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PlayAnimationTriggerRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.PlayAnimationResponse.SerializeToString, + ), 'PlayAnimation': grpc.unary_unary_rpc_method_handler( servicer.PlayAnimation, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PlayAnimationRequest.FromString, @@ -698,6 +895,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.ListAnimationsRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.ListAnimationsResponse.SerializeToString, ), + 'ListAnimationTriggers': grpc.unary_unary_rpc_method_handler( + servicer.ListAnimationTriggers, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.ListAnimationTriggersRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.ListAnimationTriggersResponse.SerializeToString, + ), 'MoveHead': grpc.unary_unary_rpc_method_handler( servicer.MoveHead, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.MoveHeadRequest.FromString, @@ -708,6 +910,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.MoveLiftRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.MoveLiftResponse.SerializeToString, ), + 'StopAllMotors': grpc.unary_unary_rpc_method_handler( + servicer.StopAllMotors, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.StopAllMotorsRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.StopAllMotorsResponse.SerializeToString, + ), 'DisplayFaceImageRGB': grpc.unary_unary_rpc_method_handler( servicer.DisplayFaceImageRGB, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.DisplayFaceImageRGBRequest.FromString, @@ -718,6 +925,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_shared__pb2.EventRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_shared__pb2.EventResponse.SerializeToString, ), + 'ExternalAudioStreamPlayback': grpc.stream_stream_rpc_method_handler( + servicer.ExternalAudioStreamPlayback, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.ExternalAudioStreamRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.ExternalAudioStreamResponse.SerializeToString, + ), 'BehaviorControl': grpc.stream_stream_rpc_method_handler( servicer.BehaviorControl, request_deserializer=anki__vector_dot_messaging_dot_behavior__pb2.BehaviorControlRequest.FromString, @@ -783,6 +995,16 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.EnableImageStreamingRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.EnableImageStreamingResponse.SerializeToString, ), + 'IsImageStreamingEnabled': grpc.unary_unary_rpc_method_handler( + servicer.IsImageStreamingEnabled, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.IsImageStreamingEnabledRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.IsImageStreamingEnabledResponse.SerializeToString, + ), + 'CancelActionByIdTag': grpc.unary_unary_rpc_method_handler( + servicer.CancelActionByIdTag, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CancelActionByIdTagRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.CancelActionByIdTagResponse.SerializeToString, + ), 'GoToPose': grpc.unary_unary_rpc_method_handler( servicer.GoToPose, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.GoToPoseRequest.FromString, @@ -803,6 +1025,21 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.DriveOnChargerRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.DriveOnChargerResponse.SerializeToString, ), + 'FindFaces': grpc.unary_unary_rpc_method_handler( + servicer.FindFaces, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.FindFacesRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.FindFacesResponse.SerializeToString, + ), + 'LookAroundInPlace': grpc.unary_unary_rpc_method_handler( + servicer.LookAroundInPlace, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.LookAroundInPlaceRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.LookAroundInPlaceResponse.SerializeToString, + ), + 'RollBlock': grpc.unary_unary_rpc_method_handler( + servicer.RollBlock, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.RollBlockRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.RollBlockResponse.SerializeToString, + ), 'PhotosInfo': grpc.unary_unary_rpc_method_handler( servicer.PhotosInfo, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PhotosInfoRequest.FromString, @@ -843,6 +1080,41 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.SetLiftHeightRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.SetLiftHeightResponse.SerializeToString, ), + 'TurnTowardsFace': grpc.unary_unary_rpc_method_handler( + servicer.TurnTowardsFace, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.TurnTowardsFaceRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.TurnTowardsFaceResponse.SerializeToString, + ), + 'GoToObject': grpc.unary_unary_rpc_method_handler( + servicer.GoToObject, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.GoToObjectRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.GoToObjectResponse.SerializeToString, + ), + 'RollObject': grpc.unary_unary_rpc_method_handler( + servicer.RollObject, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.RollObjectRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.RollObjectResponse.SerializeToString, + ), + 'PopAWheelie': grpc.unary_unary_rpc_method_handler( + servicer.PopAWheelie, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PopAWheelieRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.PopAWheelieResponse.SerializeToString, + ), + 'PickupObject': grpc.unary_unary_rpc_method_handler( + servicer.PickupObject, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PickupObjectRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.PickupObjectResponse.SerializeToString, + ), + 'PlaceObjectOnGroundHere': grpc.unary_unary_rpc_method_handler( + servicer.PlaceObjectOnGroundHere, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.PlaceObjectOnGroundHereRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.PlaceObjectOnGroundHereResponse.SerializeToString, + ), + 'SetMasterVolume': grpc.unary_unary_rpc_method_handler( + servicer.SetMasterVolume, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.MasterVolumeRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.MasterVolumeResponse.SerializeToString, + ), 'UserAuthentication': grpc.unary_unary_rpc_method_handler( servicer.UserAuthentication, request_deserializer=anki__vector_dot_messaging_dot_shared__pb2.UserAuthenticationRequest.FromString, @@ -858,11 +1130,6 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.VersionStateRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.VersionStateResponse.SerializeToString, ), - 'NetworkState': grpc.unary_unary_rpc_method_handler( - servicer.NetworkState, - request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.NetworkStateRequest.FromString, - response_serializer=anki__vector_dot_messaging_dot_messages__pb2.NetworkStateResponse.SerializeToString, - ), 'SayText': grpc.unary_unary_rpc_method_handler( servicer.SayText, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.SayTextRequest.FromString, @@ -928,6 +1195,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server): request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CameraFeedRequest.FromString, response_serializer=anki__vector_dot_messaging_dot_messages__pb2.CameraFeedResponse.SerializeToString, ), + 'CaptureSingleImage': grpc.unary_unary_rpc_method_handler( + servicer.CaptureSingleImage, + request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.CaptureSingleImageRequest.FromString, + response_serializer=anki__vector_dot_messaging_dot_messages__pb2.CaptureSingleImageResponse.SerializeToString, + ), 'SetEyeColor': grpc.unary_unary_rpc_method_handler( servicer.SetEyeColor, request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.SetEyeColorRequest.FromString, diff --git a/anki_vector/messaging/messages.proto b/anki_vector/messaging/messages.proto index 5792cbe..37bb0d8 100644 --- a/anki_vector/messaging/messages.proto +++ b/anki_vector/messaging/messages.proto @@ -25,10 +25,19 @@ import "anki_vector/messaging/extensions.proto"; // still connected. message KeepAlivePing {} +// An animation trigger object. +message AnimationTrigger { + // The name of a given animation trigger. + string name = 1; +} + +// An animation object. message Animation { + // The name of a given animation. string name = 1; } +// See the DriveWheels rpc for more details. message DriveWheelsRequest { float left_wheel_mmps = 1; float right_wheel_mmps = 2; @@ -36,53 +45,118 @@ message DriveWheelsRequest { float right_wheel_mmps2 = 4; } +// See the DriveWheels rpc for more details. message DriveWheelsResponse { + // A generic status. ResponseStatus status = 1; } +// See the MoveHead rpc for more details. message MoveHeadRequest { float speed_rad_per_sec = 1; } +// See the MoveHead rpc for more details. message MoveHeadResponse { + // A generic status. ResponseStatus status = 1; } +// See the MoveLift rpc for more details. message MoveLiftRequest { float speed_rad_per_sec = 1; } +// See the MoveLift rpc for more details. message MoveLiftResponse { + // A generic status. + ResponseStatus status = 1; +} + +// See the StopAllMotors rpc for more details. +message StopAllMotorsRequest { +} + +// See the StopAllMotors rpc for more details. +message StopAllMotorsResponse { ResponseStatus status = 1; } +// See PlayAnimationTrigger rpc for more details. +message PlayAnimationTriggerRequest { + // The animation trigger to play. + AnimationTrigger animation_trigger = 1; + // The number of times to play the animation in a row. + uint32 loops = 2; + // Automatically ignore the lift track if Vector is currently carrying an object. + bool use_lift_safe = 3; + // Ignore any movement of Vector's body when playing the animation. + bool ignore_body_track = 4; + // Ignore any movement of Vector's head when playing the animation. + bool ignore_head_track = 5; + // Ignore any movement of Vector's lift when playing the animation. + bool ignore_lift_track = 6; +} + +// See PlayAnimation rpc for more details. message PlayAnimationRequest { + // The animation to play. Animation animation = 1; + // The number of times to play the animation in a row. uint32 loops = 2; + // Ignore any movement of Vector's body in the given animation. bool ignore_body_track = 3; + // Ignore any movement of Vector's head in the given animation. bool ignore_head_track = 4; + // Ignore any movement of Vector's lift in the given animation. bool ignore_lift_track = 5; } +// See PlayAnimation rpc for more details. message PlayAnimationResponse { + // A generic status. ResponseStatus status = 1; + // Information on whether the animation played successfully. BehaviorResults result = 2; + // The animation that the robot executed. + Animation animation = 3; } +// See ListAnimations rpc for more details. message ListAnimationsRequest {} +// See ListAnimations rpc for more details. message ListAnimationsResponse { + // A generic status. ResponseStatus status = 1; + // The animations that Vector knows. repeated Animation animation_names = 2; } +// See ListAnimationTriggers rpc for more details. +message ListAnimationTriggersRequest {} + +// See ListAnimationTriggers rpc for more details. +message ListAnimationTriggersResponse { + // A generic status. + ResponseStatus status = 1; + // The animation triggers that Vector knows. + repeated AnimationTrigger animation_trigger_names = 2; +} + +// See DisplayFaceImageRGB rpc for more details. message DisplayFaceImageRGBRequest { + // The image to render. bytes face_data = 1; + // How long to display the image on the face. uint32 duration_ms = 2; + // If this image should overwrite any current images on the face. bool interrupt_running = 3; } +// See DisplayFaceImageRGB rpc for more details. message DisplayFaceImageRGBResponse { + // A generic status. ResponseStatus status = 1; } @@ -129,14 +203,16 @@ message PoseStruct { uint32 origin_id = 8; // Which coordinate frame this pose is in (0 for none or unknown) } +// All values are in mm/s^2. message AccelData { - float x = 1; // mm/s^2 + float x = 1; float y = 2; float z = 3; } +// All values are in rad/s. message GyroData { - float x = 1; // rad/s + float x = 1; float y = 2; float z = 3; } @@ -144,10 +220,9 @@ message GyroData { message ProxData { uint32 distance_mm = 1; float signal_quality = 2; - bool is_in_valid_range = 3; // Distance is within valid range - bool is_valid_signal_quality = 4; // Signal quality is sufficiently strong to trust that something was detected + bool unobstructed = 3; // The sensor has confirmed it has not detected anything up to its max range + bool found_object = 4; // The sensor detected an object in the valid operating range bool is_lift_in_fov = 5; // Lift (or object on lift) is occluding the sensor - bool is_too_pitched = 6; // Robot is too far pitched up or down } message TouchData { @@ -255,11 +330,6 @@ message RobotChangedObservedFaceID { int32 new_id = 2; } -message AppIntentRequest { - string intent = 1; - string param = 2; -} - enum FaceEnrollmentResult { SUCCESS = 0; @@ -276,12 +346,6 @@ enum FaceEnrollmentResult UNKNOWN_FAILURE = 9; } -message AppDisconnected{} - -message AppIntentResponse { - ResponseStatus status = 1; -} - message FaceEnrollmentCompleted { FaceEnrollmentResult result = 1; int32 face_id = 2; @@ -380,6 +444,30 @@ message DriveOnChargerResponse { BehaviorResults result = 2; } +message FindFacesRequest { +} + +message FindFacesResponse { + ResponseStatus status = 1; + BehaviorResults result = 2; +} + +message LookAroundInPlaceRequest { +} + +message LookAroundInPlaceResponse { + ResponseStatus status = 1; + BehaviorResults result = 2; +} + +//this triggers a Vector Behavior, different from RollObjectRequest which triggers an Action +message RollBlockRequest { +} + +message RollBlockResponse { + ResponseStatus status = 1; + BehaviorResults result = 2; +} message WakeWordBegin { } @@ -389,6 +477,11 @@ message WakeWordEnd { string intent_json = 2; } +message UserIntent { + uint32 intent_id = 1; + string json_data = 2; +} + message PhotoInfo { uint32 photo_id = 1; uint32 timestamp_utc = 2; @@ -463,65 +556,66 @@ message PathMotionProfile { bool is_custom = 11; } -// These codes are duplicated from actionResults.clad file and need to be kept in sync -message ActionResult { +// The possible results of running an action. + message ActionResult { + // The possible results of running an action. enum ActionResultCode { - ACTION_RESULT_SUCCESS = 0; - ACTION_RESULT_RUNNING = 16777216; - - ACTION_RESULT_CANCELLED_WHILE_RUNNING = 33554432; - NOT_STARTED = 33554433; - - ABORT = 50331648; - ANIM_ABORTED = 50331649; - BAD_MARKER = 50331650; - BAD_MESSAGE_TAG = 50331651; - BAD_OBJECT = 50331652; - BAD_POSE = 50331653; - BAD_TAG = 50331654; - CHARGER_UNPLUGGED_ABORT = 50331655; + ACTION_RESULT_SUCCESS = 0; // Action completed successfully. + ACTION_RESULT_RUNNING = 16777216; // Action is still running. + + ACTION_RESULT_CANCELLED_WHILE_RUNNING = 33554432; // Action was cancelled by SDK request + NOT_STARTED = 33554433; // Initial state of an Action to indicate it has not yet started. + + ABORT = 50331648; // Action aborted itself (e.g. had invalid attributes, or a runtime failure). + ANIM_ABORTED = 50331649; // Animation Action aborted itself (e.g. there was an error playing the animation). + BAD_MARKER = 50331650; // There was an error related to vision markers. + BAD_MESSAGE_TAG = 50331651; // There was a problem related to a subscribed or unsupported message tag + BAD_OBJECT = 50331652; // There was a problem with the Object ID provided (e.g. there is no Object with that ID). + BAD_POSE = 50331653; // There was a problem with the Pose provided. + BAD_TAG = 50331654; // The SDK-provided tag was bad. + CHARGER_UNPLUGGED_ABORT = 50331655; // Vector is on the charger but cannot sense the contacts. Charger may be unplugged. CLIFF_ALIGN_FAILED_TIMEOUT = 50331656; CLIFF_ALIGN_FAILED_NO_TURNING = 50331657; CLIFF_ALIGN_FAILED_OVER_TURNING = 50331658; CLIFF_ALIGN_FAILED_NO_WHITE = 50331659; CLIFF_ALIGN_FAILED_STOPPED = 50331660; - FAILED_SETTING_CALIBRATION = 50331661; - FOLLOWING_PATH_BUT_NOT_TRAVERSING = 50331662; - INTERRUPTED = 50331663; - INVALID_OFF_TREADS_STATE = 50331664; - MISMATCHED_UP_AXIS = 50331665; - NO_ANIM_NAME = 50331666; - NO_DISTANCE_SET = 50331667; - NO_FACE = 50331668; - NO_GOAL_SET = 50331669; - NO_PREACTION_POSES = 50331670; - NOT_CARRYING_OBJECT_ABORT = 50331671; - NOT_ON_CHARGER_ABORT = 50331672; - NULL_SUBACTION = 50331673; - PATH_PLANNING_FAILED_ABORT = 50331674; - PICKUP_OBJECT_UNEXPECTEDLY_MOVING = 50331675; - SEND_MESSAGE_TO_ROBOT_FAILED = 50331676; - STILL_CARRYING_OBJECT = 50331677; - TIMEOUT = 50331678; - TRACKS_LOCKED = 50331679; - UNEXPECTED_DOCK_ACTION = 50331680; - UNKNOWN_TOOL_CODE = 50331681; - UPDATE_DERIVED_FAILED = 50331682; - VISUAL_OBSERVATION_FAILED = 50331683; - SHOULDNT_DRIVE_ON_CHARGER = 50331684; - - RETRY = 67108864; - DID_NOT_REACH_PREACTION_POSE = 67108865; - FAILED_TRAVERSING_PATH = 67108866; - LAST_PICK_AND_PLACE_FAILED = 67108867; - MOTOR_STOPPED_MAKING_PROGRESS = 67108868; - NOT_CARRYING_OBJECT_RETRY = 67108869; - NOT_ON_CHARGER_RETRY = 67108870; - PATH_PLANNING_FAILED_RETRY = 67108871; - PLACEMENT_GOAL_NOT_FREE = 67108872; - PICKUP_OBJECT_UNEXPECTEDLY_NOT_MOVING = 67108873; - STILL_ON_CHARGER = 67108874; - UNEXPECTED_PITCH_ANGLE = 67108875; + FAILED_SETTING_CALIBRATION = 50331661; // Shouldn't occur outside of factory. + FOLLOWING_PATH_BUT_NOT_TRAVERSING = 50331662; // There was an error following the planned path. + INTERRUPTED = 50331663; // The action was interrupted by another Action or Behavior. + INVALID_OFF_TREADS_STATE = 50331664; // The robot ended up in an "off treads state" not valid for this action (e.g. the robot was placed on its back while executing a turn). + MISMATCHED_UP_AXIS = 50331665; // The Up Axis of a carried object doesn't match the desired placement pose. + NO_ANIM_NAME = 50331666; // No valid Animation name was found. + NO_DISTANCE_SET = 50331667; // An invalid distance value was given. + NO_FACE = 50331668; // There was a problem with the Face ID (e.g. Vector doesn't no where it is). + NO_GOAL_SET = 50331669; // No goal pose was set. + NO_PREACTION_POSES = 50331670; // No pre-action poses were found (e.g. could not get into position). + NOT_CARRYING_OBJECT_ABORT = 50331671; // No object is being carried, but the action requires one. + NOT_ON_CHARGER_ABORT = 50331672; // Vector is expected to be on the charger, but is not. + NULL_SUBACTION = 50331673; // No sub-action was provided. + PATH_PLANNING_FAILED_ABORT = 50331674; // Vector was unable to plan a path. + PICKUP_OBJECT_UNEXPECTEDLY_MOVING = 50331675; // The object that Vector is attempting to pickup is unexpectedly moving (e.g it is being moved by someone else). + SEND_MESSAGE_TO_ROBOT_FAILED = 50331676; // Shouldn't occur in SDK usage. + STILL_CARRYING_OBJECT = 50331677; // Vector is unexpectedly still carrying an object. + TIMEOUT = 50331678; // The Action timed out before completing correctly. + TRACKS_LOCKED = 50331679; // One or more movement tracks (Head, Lift, Body, Face, Backpack Lights, Audio) are already being used by another Action. + UNEXPECTED_DOCK_ACTION = 50331680; // There was an internal error related to an unexpected type of dock action. + UNKNOWN_TOOL_CODE = 50331681; // Shouldn't occur outside of factory. + UPDATE_DERIVED_FAILED = 50331682; // There was a problem in the subclass's update on the robot. + VISUAL_OBSERVATION_FAILED = 50331683; // Vector did not see the expected result (e.g. unable to see cube in the expected position after a related action). + SHOULDNT_DRIVE_ON_CHARGER = 50331684; // Action is not permitted on the charger. + + RETRY = 67108864; // The Action failed, but may succeed if retried. + DID_NOT_REACH_PREACTION_POSE = 67108865; // Failed to get into position. + FAILED_TRAVERSING_PATH = 67108866; // Failed to follow the planned path. + LAST_PICK_AND_PLACE_FAILED = 67108867; // The previous attempt to pick and place an object failed. + MOTOR_STOPPED_MAKING_PROGRESS = 67108868; // The required motor isn't moving so the action cannot complete. + NOT_CARRYING_OBJECT_RETRY = 67108869; // Not carrying an object when it was expected, but may succeed if the action is retried. + NOT_ON_CHARGER_RETRY = 67108870; // Driving onto the charger failed, but may succeed if the action is retried. + PATH_PLANNING_FAILED_RETRY = 67108871; // Vector was unable to plan a path, but may succeed if the action is retried. + PLACEMENT_GOAL_NOT_FREE = 67108872; // There is no room to place the object at the desired destination. + PICKUP_OBJECT_UNEXPECTEDLY_NOT_MOVING = 67108873; // The object that Vector thought he was lifting didn't start moving, so he must have missed. + STILL_ON_CHARGER = 67108874; // Vector failed to drive off the charger. + UNEXPECTED_PITCH_ANGLE = 67108875; // Vector's pitch is at an unexpected angle for the Action. } ActionResultCode code = 1; } @@ -532,6 +626,17 @@ enum ActionTagConstants { LAST_SDK_TAG = 3000000; } +// Cancel a previously-requested action. Action requests include GoToPoseRequest, DockWithCubeRequest, DriveStraightRequest, TurnInPlaceRequest, SetHeadAngleRequest, and SetLiftHeightRequest. +message CancelActionByIdTagRequest { + uint32 id_tag = 1; // Use the id_tag provided to the action request +} + +// Response from the robot to CancelActionByIdTagRequest. +message CancelActionByIdTagResponse { + // A generic status. + ResponseStatus status = 1; +} + // GotoPose message GoToPoseRequest { float x_mm = 1; @@ -550,10 +655,10 @@ message GoToPoseResponse { // This is a uint8 in CLAD enum AlignmentType { ALIGNMENT_TYPE_UNKNOWN = 0; - ALIGNMENT_TYPE_LIFT_FINGER = 1; // Fingers aligned with object - ALIGNMENT_TYPE_LIFT_PLATE = 2; // Lift plate aligned with object - ALIGNMENT_TYPE_BODY = 3; // Front of body aligned with object - ALIGNMENT_TYPE_CUSTOM = 4; + ALIGNMENT_TYPE_LIFT_FINGER = 1; // Align the tips of the lift fingers with the target object + ALIGNMENT_TYPE_LIFT_PLATE = 2; // Align the flat part of the lift with the object (useful for getting the fingers in the cube's grooves) + ALIGNMENT_TYPE_BODY = 3; // Align the front of Vector's body (useful for when the lift is up) + ALIGNMENT_TYPE_CUSTOM = 4; // For use with distanceFromMarker parameter } message DockWithCubeRequest { @@ -636,6 +741,93 @@ message SetLiftHeightResponse { ActionResult result = 2; } +// TurnTowardsFace +message TurnTowardsFaceRequest { + int32 face_id = 1; + float max_turn_angle_rad = 2; + int32 id_tag = 3; + int32 num_retries = 4; +} + +message TurnTowardsFaceResponse { + ResponseStatus status = 1; + ActionResult result = 2; +} + +// GoToObject +message GoToObjectRequest { + int32 object_id = 1; + PathMotionProfile motion_prof = 2; + float distance_from_object_origin_mm = 3; + bool use_pre_dock_pose = 4; + int32 id_tag = 5; + int32 num_retries = 6; +} + +message GoToObjectResponse { + ResponseStatus status = 1; + ActionResult result = 2; +} + +// RollObject +message RollObjectRequest { + int32 object_id = 1; // negative value means "currently selected object" + PathMotionProfile motion_prof = 2; + float approach_angle_rad = 3; + bool use_approach_angle = 4; + bool use_pre_dock_pose = 5; + int32 id_tag = 6; + int32 num_retries = 7; +} + +message RollObjectResponse { + ResponseStatus status = 1; + ActionResult result = 2; +} + +// PopAWheelie +message PopAWheelieRequest { + int32 object_id = 1; // negative value means "currently selected object" + PathMotionProfile motion_prof = 2; + float approach_angle_rad = 3; + bool use_approach_angle = 4; + bool use_pre_dock_pose = 5; + int32 id_tag = 6; + int32 num_retries = 7; +} + +message PopAWheelieResponse { + ResponseStatus status = 1; + ActionResult result = 2; +} + +// PickupObject +message PickupObjectRequest { + int32 object_id = 1; // negative value means "currently selected object" + PathMotionProfile motion_prof = 2; + float approach_angle_rad = 3; + bool use_approach_angle = 4; + bool use_pre_dock_pose = 5; + int32 id_tag = 6; + int32 num_retries = 7; +} + +message PickupObjectResponse { + ResponseStatus status = 1; + ActionResult result = 2; +} + +// PlaceObjectOnGroundHere +message PlaceObjectOnGroundHereRequest { + int32 id_tag = 1; + int32 num_retries = 2; +} + +message PlaceObjectOnGroundHereResponse { + ResponseStatus status = 1; + ActionResult result = 2; +} + enum BatteryLevel { BATTERY_LEVEL_UNKNOWN = 0; BATTERY_LEVEL_LOW = 1; @@ -643,21 +835,6 @@ enum BatteryLevel { BATTERY_LEVEL_FULL = 3; } -message NetworkStats { - int32 g_net_stat1_num_connections = 1; - float g_net_stat2_latency_avg = 2; - float g_net_stat3_latency_sd = 3; - float g_net_stat4_latency_min = 4; - float g_net_stat5_latency_max = 5; - float g_net_stat6_ping_arrived_pc = 6; - float g_net_stat7_ext_queued_avg_ms = 7; - float g_net_stat8_ext_queued_min_ms = 8; - float g_net_stat9_ext_queued_max_ms = 9; - float g_net_stata_queued_avg_ms = 10; - float g_net_statb_queued_min_ms = 11; - float g_net_statc_queued_max_ms = 12; -} - message BatteryStateRequest {} message BatteryStateResponse { @@ -689,13 +866,6 @@ message VersionStateResponse { string engine_build_id = 3; } -message NetworkStateRequest {} - -message NetworkStateResponse { - ResponseStatus status = 1; - NetworkStats network_stats = 2; -} - message SayTextRequest { string text = 1; bool use_vector_voice = 2; @@ -754,7 +924,7 @@ message AudioSendModeChanged { AudioProcessingMode mode = 1; } -// One frame of audio data and associated metadata +// One frame of robot audio data and associated metadata message AudioChunk { uint32 robot_time_stamp = 1; // robot time at the final chunk of this audio sample group transmission uint32 group_id = 2; // the id of this sample transmission group @@ -783,6 +953,77 @@ message AudioFeedResponse { uint32 noise_floor_power = 7; // power value, convert to db with log_10(value) } +// Preflight and initialization for audio streaming to robot +message ExternalAudioStreamPrepare { + uint32 audio_frame_rate = 1; // 8000 - 16025 + uint32 audio_volume = 2; // 0 - 100 +} + +// Send chunk of audio data to stream on robot +message ExternalAudioStreamChunk { + uint32 audio_chunk_size_bytes = 1; //current engine maximum of 1024 bytes + bytes audio_chunk_samples = 2; //16-bit little-endian PCM audio data +} + +// Send notification of last chunk of audio sent to robot +message ExternalAudioStreamComplete { +} + +// Cancel a playing external robot audio stream +message ExternalAudioStreamCancel { +} + +// Request to stream audio to the robot +message ExternalAudioStreamRequest { + oneof audio_request_type { + ExternalAudioStreamPrepare audio_stream_prepare = 1; + ExternalAudioStreamChunk audio_stream_chunk = 2; + ExternalAudioStreamComplete audio_stream_complete = 3; + ExternalAudioStreamCancel audio_stream_cancel = 4; + } +} + +// Audio has been played on the Robot +message ExternalAudioStreamPlaybackComplete { +} + +// Audio playback failure +message ExternalAudioStreamPlaybackFailure { +} + +// Audio has been sent to robot that would overrun the memory buffer +message ExternalAudioStreamBufferOverrun { + uint32 audio_samples_sent = 1; + uint32 audio_samples_played = 2; +} + +// Response from streaming audio to robot +message ExternalAudioStreamResponse { + oneof audio_response_type { + ExternalAudioStreamPlaybackComplete audio_stream_playback_complete = 1; + ExternalAudioStreamBufferOverrun audio_stream_buffer_overrun = 2; + ExternalAudioStreamPlaybackFailure audio_stream_playback_failyer = 3; + } +} + +enum MasterVolumeLevel +{ + VOLUME_LOW = 0; + VOLUME_MEDIUM_LOW = 1; + VOLUME_MEDIUM = 2; + VOLUME_MEDIUM_HIGH = 3; + VOLUME_HIGH = 4; +} + +message MasterVolumeRequest +{ + MasterVolumeLevel volume_level = 1; +} + +message MasterVolumeResponse +{ + ResponseStatus status = 1; +} // When enabled, RobotObservedObject messages will be produced message EnableMarkerDetectionRequest @@ -852,6 +1093,15 @@ message EnableImageStreamingResponse ResponseStatus status = 1; } +// Request whether or not image streaming is enabled on the robot +message IsImageStreamingEnabledRequest { +} + +// Indicates whether or not image streaming is enabled on the robot +message IsImageStreamingEnabledResponse { + bool is_image_streaming_enabled = 1; +} + // Sent when vision modes are automatically disabled due to the SDK no longer having control // of the robot message VisionModesAutoDisabled @@ -897,6 +1147,16 @@ message CameraFeedResponse { bytes data = 4; } +message CaptureSingleImageRequest {} + +message CaptureSingleImageResponse { + ResponseStatus status = 1; + uint32 frame_time_stamp = 2; + uint32 image_id = 3; + ImageChunk.ImageEncoding image_encoding = 4; + bytes data = 5; +} + message SetEyeColorRequest { float hue = 1; float saturation = 2; @@ -918,3 +1178,4 @@ message SDKInitializationResponse { ResponseStatus status = 1; } + diff --git a/anki_vector/messaging/messages_pb2.py b/anki_vector/messaging/messages_pb2.py index 7133b77..c30261a 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\"\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\"\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\"\x98\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\"\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\"_\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\"\xa3\x01\n\x08ProxData\x12\x13\n\x0b\x64istance_mm\x18\x01 \x01(\r\x12\x16\n\x0esignal_quality\x18\x02 \x01(\x02\x12\x19\n\x11is_in_valid_range\x18\x03 \x01(\x08\x12\x1f\n\x17is_valid_signal_quality\x18\x04 \x01(\x08\x12\x16\n\x0eis_lift_in_fov\x18\x05 \x01(\x08\x12\x16\n\x0eis_too_pitched\x18\x06 \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\"1\n\x10\x41ppIntentRequest\x12\x0e\n\x06intent\x18\x01 \x01(\t\x12\r\n\x05param\x18\x02 \x01(\t\"\x11\n\x0f\x41ppDisconnected\"S\n\x11\x41ppIntentResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\"~\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\"\x0f\n\rWakeWordBegin\"8\n\x0bWakeWordEnd\x12\x14\n\x0cintent_heard\x18\x01 \x01(\x08\x12\x13\n\x0bintent_json\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 \"\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\"\xb9\x03\n\x0cNetworkStats\x12#\n\x1bg_net_stat1_num_connections\x18\x01 \x01(\x05\x12\x1f\n\x17g_net_stat2_latency_avg\x18\x02 \x01(\x02\x12\x1e\n\x16g_net_stat3_latency_sd\x18\x03 \x01(\x02\x12\x1f\n\x17g_net_stat4_latency_min\x18\x04 \x01(\x02\x12\x1f\n\x17g_net_stat5_latency_max\x18\x05 \x01(\x02\x12#\n\x1bg_net_stat6_ping_arrived_pc\x18\x06 \x01(\x02\x12%\n\x1dg_net_stat7_ext_queued_avg_ms\x18\x07 \x01(\x02\x12%\n\x1dg_net_stat8_ext_queued_min_ms\x18\x08 \x01(\x02\x12%\n\x1dg_net_stat9_ext_queued_max_ms\x18\t \x01(\x02\x12!\n\x19g_net_stata_queued_avg_ms\x18\n \x01(\x02\x12!\n\x19g_net_statb_queued_min_ms\x18\x0b \x01(\x02\x12!\n\x19g_net_statc_queued_max_ms\x18\x0c \x01(\x02\"\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\"\x15\n\x13NetworkStateRequest\"\x9b\x01\n\x14NetworkStateResponse\x12>\n\x06status\x18\x01 \x01(\x0b\x32..Anki.Vector.external_interface.ResponseStatus\x12\x43\n\rnetwork_stats\x18\x02 \x01(\x0b\x32,.Anki.Vector.external_interface.NetworkStats\"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\".\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\"\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\"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\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\"\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') , dependencies=[anki__vector_dot_messaging_dot_response__status__pb2.DESCRIPTOR,anki__vector_dot_messaging_dot_extensions__pb2.DESCRIPTOR,]) @@ -107,8 +107,8 @@ ], containing_type=None, options=None, - serialized_start=15131, - serialized_end=15735, + serialized_start=19443, + serialized_end=20047, ) _sym_db.RegisterEnumDescriptor(_ROBOTSTATUS) @@ -150,8 +150,8 @@ ], containing_type=None, options=_descriptor._ParseOptions(descriptor_pb2.EnumOptions(), _b('\020\001')), - serialized_start=15738, - serialized_end=15927, + serialized_start=20050, + serialized_end=20239, ) _sym_db.RegisterEnumDescriptor(_FACIALEXPRESSION) @@ -205,8 +205,8 @@ ], containing_type=None, options=None, - serialized_start=15930, - serialized_end=16134, + serialized_start=20242, + serialized_end=20446, ) _sym_db.RegisterEnumDescriptor(_FACEENROLLMENTRESULT) @@ -232,8 +232,8 @@ ], containing_type=None, options=None, - serialized_start=16136, - serialized_end=16244, + serialized_start=20448, + serialized_end=20556, ) _sym_db.RegisterEnumDescriptor(_BEHAVIORRESULTS) @@ -259,8 +259,8 @@ ], containing_type=None, options=None, - serialized_start=16246, - serialized_end=16329, + serialized_start=20558, + serialized_end=20641, ) _sym_db.RegisterEnumDescriptor(_ACTIONTAGCONSTANTS) @@ -294,8 +294,8 @@ ], containing_type=None, options=None, - serialized_start=16332, - serialized_end=16490, + serialized_start=20644, + serialized_end=20802, ) _sym_db.RegisterEnumDescriptor(_ALIGNMENTTYPE) @@ -325,8 +325,8 @@ ], containing_type=None, options=None, - serialized_start=16492, - serialized_end=16607, + serialized_start=20804, + serialized_end=20919, ) _sym_db.RegisterEnumDescriptor(_BATTERYLEVEL) @@ -364,8 +364,8 @@ ], containing_type=None, options=None, - serialized_start=16610, - serialized_end=16814, + serialized_start=20922, + serialized_end=21126, ) _sym_db.RegisterEnumDescriptor(_AUDIOCONSTANTS) @@ -399,12 +399,47 @@ ], containing_type=None, options=None, - serialized_start=16817, - serialized_end=16950, + serialized_start=21129, + serialized_end=21262, ) _sym_db.RegisterEnumDescriptor(_AUDIOPROCESSINGMODE) AudioProcessingMode = enum_type_wrapper.EnumTypeWrapper(_AUDIOPROCESSINGMODE) +_MASTERVOLUMELEVEL = _descriptor.EnumDescriptor( + name='MasterVolumeLevel', + full_name='Anki.Vector.external_interface.MasterVolumeLevel', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='VOLUME_LOW', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='VOLUME_MEDIUM_LOW', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='VOLUME_MEDIUM', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='VOLUME_MEDIUM_HIGH', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='VOLUME_HIGH', index=4, number=4, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=21264, + serialized_end=21382, +) +_sym_db.RegisterEnumDescriptor(_MASTERVOLUMELEVEL) + +MasterVolumeLevel = enum_type_wrapper.EnumTypeWrapper(_MASTERVOLUMELEVEL) ROBOT_STATUS_NONE = 0 ROBOT_STATUS_IS_MOVING = 1 ROBOT_STATUS_IS_CARRYING_BLOCK = 2 @@ -466,6 +501,11 @@ AUDIO_FAST_MODE = 2 AUDIO_DIRECTIONAL_MODE = 3 AUDIO_VOICE_DETECT_MODE = 4 +VOLUME_LOW = 0 +VOLUME_MEDIUM_LOW = 1 +VOLUME_MEDIUM = 2 +VOLUME_MEDIUM_HIGH = 3 +VOLUME_HIGH = 4 _ACTIONRESULT_ACTIONRESULTCODE = _descriptor.EnumDescriptor( @@ -689,8 +729,8 @@ ], containing_type=None, options=None, - serialized_start=7112, - serialized_end=8674, + serialized_start=8147, + serialized_end=9709, ) _sym_db.RegisterEnumDescriptor(_ACTIONRESULT_ACTIONRESULTCODE) @@ -711,8 +751,8 @@ ], containing_type=None, options=None, - serialized_start=11684, - serialized_end=11723, + serialized_start=14429, + serialized_end=14468, ) _sym_db.RegisterEnumDescriptor(_CUBEBATTERY_CUBEBATTERYLEVEL) @@ -745,8 +785,8 @@ ], containing_type=None, options=None, - serialized_start=12309, - serialized_end=12392, + serialized_start=14873, + serialized_end=14956, ) _sym_db.RegisterEnumDescriptor(_SAYTEXTRESPONSE_UTTERANCESTATE) @@ -803,8 +843,8 @@ ], containing_type=None, options=None, - serialized_start=14339, - serialized_end=14550, + serialized_start=18387, + serialized_end=18598, ) _sym_db.RegisterEnumDescriptor(_IMAGECHUNK_IMAGEENCODING) @@ -833,6 +873,37 @@ ) +_ANIMATIONTRIGGER = _descriptor.Descriptor( + name='AnimationTrigger', + full_name='Anki.Vector.external_interface.AnimationTrigger', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='Anki.Vector.external_interface.AnimationTrigger.name', index=0, + number=1, 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=174, + serialized_end=206, +) + + _ANIMATION = _descriptor.Descriptor( name='Animation', full_name='Anki.Vector.external_interface.Animation', @@ -859,8 +930,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=174, - serialized_end=199, + serialized_start=208, + serialized_end=233, ) @@ -911,8 +982,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=201, - serialized_end=325, + serialized_start=235, + serialized_end=359, ) @@ -942,8 +1013,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=327, - serialized_end=412, + serialized_start=361, + serialized_end=446, ) @@ -973,8 +1044,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=414, - serialized_end=458, + serialized_start=448, + serialized_end=492, ) @@ -1004,8 +1075,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=460, - serialized_end=542, + serialized_start=494, + serialized_end=576, ) @@ -1035,8 +1106,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=544, - serialized_end=588, + serialized_start=578, + serialized_end=622, ) @@ -1066,8 +1137,129 @@ extension_ranges=[], oneofs=[ ], - serialized_start=590, - serialized_end=672, + serialized_start=624, + serialized_end=706, +) + + +_STOPALLMOTORSREQUEST = _descriptor.Descriptor( + name='StopAllMotorsRequest', + full_name='Anki.Vector.external_interface.StopAllMotorsRequest', + 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=708, + serialized_end=730, +) + + +_STOPALLMOTORSRESPONSE = _descriptor.Descriptor( + name='StopAllMotorsResponse', + full_name='Anki.Vector.external_interface.StopAllMotorsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.StopAllMotorsResponse.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=732, + serialized_end=819, +) + + +_PLAYANIMATIONTRIGGERREQUEST = _descriptor.Descriptor( + name='PlayAnimationTriggerRequest', + full_name='Anki.Vector.external_interface.PlayAnimationTriggerRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='animation_trigger', full_name='Anki.Vector.external_interface.PlayAnimationTriggerRequest.animation_trigger', 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='loops', full_name='Anki.Vector.external_interface.PlayAnimationTriggerRequest.loops', 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='use_lift_safe', full_name='Anki.Vector.external_interface.PlayAnimationTriggerRequest.use_lift_safe', 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), + _descriptor.FieldDescriptor( + name='ignore_body_track', full_name='Anki.Vector.external_interface.PlayAnimationTriggerRequest.ignore_body_track', index=3, + number=4, 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), + _descriptor.FieldDescriptor( + name='ignore_head_track', full_name='Anki.Vector.external_interface.PlayAnimationTriggerRequest.ignore_head_track', index=4, + number=5, 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), + _descriptor.FieldDescriptor( + name='ignore_lift_track', full_name='Anki.Vector.external_interface.PlayAnimationTriggerRequest.ignore_lift_track', index=5, + number=6, 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=822, + serialized_end=1047, ) @@ -1125,8 +1317,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=675, - serialized_end=855, + serialized_start=1050, + serialized_end=1230, ) @@ -1151,6 +1343,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='animation', full_name='Anki.Vector.external_interface.PlayAnimationResponse.animation', index=2, + number=3, 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=[ ], @@ -1163,8 +1362,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=858, - serialized_end=1010, + serialized_start=1233, + serialized_end=1447, ) @@ -1187,8 +1386,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1012, - serialized_end=1035, + serialized_start=1449, + serialized_end=1472, ) @@ -1225,8 +1424,70 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1038, - serialized_end=1194, + serialized_start=1475, + serialized_end=1631, +) + + +_LISTANIMATIONTRIGGERSREQUEST = _descriptor.Descriptor( + name='ListAnimationTriggersRequest', + full_name='Anki.Vector.external_interface.ListAnimationTriggersRequest', + 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=1633, + serialized_end=1663, +) + + +_LISTANIMATIONTRIGGERSRESPONSE = _descriptor.Descriptor( + name='ListAnimationTriggersResponse', + full_name='Anki.Vector.external_interface.ListAnimationTriggersResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.ListAnimationTriggersResponse.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='animation_trigger_names', full_name='Anki.Vector.external_interface.ListAnimationTriggersResponse.animation_trigger_names', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + 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=1666, + serialized_end=1844, ) @@ -1270,8 +1531,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1196, - serialized_end=1291, + serialized_start=1846, + serialized_end=1941, ) @@ -1301,8 +1562,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1293, - serialized_end=1386, + serialized_start=1943, + serialized_end=2036, ) @@ -1325,8 +1586,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1388, - serialized_end=1415, + serialized_start=2038, + serialized_end=2065, ) @@ -1349,8 +1610,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1417, - serialized_end=1445, + serialized_start=2067, + serialized_end=2095, ) @@ -1397,8 +1658,8 @@ name='status_type', full_name='Anki.Vector.external_interface.Status.status_type', index=0, containing_type=None, fields=[]), ], - serialized_start=1448, - serialized_end=1767, + serialized_start=2098, + serialized_end=2417, ) @@ -1438,8 +1699,8 @@ name='wake_word_type', full_name='Anki.Vector.external_interface.WakeWord.wake_word_type', index=0, containing_type=None, fields=[]), ], - serialized_start=1770, - serialized_end=1942, + serialized_start=2420, + serialized_end=2592, ) @@ -1476,8 +1737,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1944, - serialized_end=2042, + serialized_start=2594, + serialized_end=2692, ) @@ -1556,8 +1817,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2044, - serialized_end=2156, + serialized_start=2694, + serialized_end=2806, ) @@ -1601,8 +1862,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2158, - serialized_end=2202, + serialized_start=2808, + serialized_end=2852, ) @@ -1646,8 +1907,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2204, - serialized_end=2247, + serialized_start=2854, + serialized_end=2897, ) @@ -1673,14 +1934,14 @@ is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='is_in_valid_range', full_name='Anki.Vector.external_interface.ProxData.is_in_valid_range', index=2, + name='unobstructed', full_name='Anki.Vector.external_interface.ProxData.unobstructed', 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), _descriptor.FieldDescriptor( - name='is_valid_signal_quality', full_name='Anki.Vector.external_interface.ProxData.is_valid_signal_quality', index=3, + name='found_object', full_name='Anki.Vector.external_interface.ProxData.found_object', index=3, number=4, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, @@ -1693,13 +1954,6 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='is_too_pitched', full_name='Anki.Vector.external_interface.ProxData.is_too_pitched', index=5, - number=6, 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=[ ], @@ -1712,8 +1966,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2250, - serialized_end=2413, + serialized_start=2899, + serialized_end=3022, ) @@ -1750,8 +2004,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2415, - serialized_end=2477, + serialized_start=3024, + serialized_end=3086, ) @@ -1893,8 +2147,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2480, - serialized_end=3122, + serialized_start=3089, + serialized_end=3731, ) @@ -1931,8 +2185,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3124, - serialized_end=3157, + serialized_start=3733, + serialized_end=3766, ) @@ -1983,8 +2237,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3159, - serialized_end=3240, + serialized_start=3768, + serialized_end=3849, ) @@ -2084,8 +2338,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3243, - serialized_end=3765, + serialized_start=3852, + serialized_end=4374, ) @@ -2122,28 +2376,35 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3767, - serialized_end=3827, + serialized_start=4376, + serialized_end=4436, ) -_APPINTENTREQUEST = _descriptor.Descriptor( - name='AppIntentRequest', - full_name='Anki.Vector.external_interface.AppIntentRequest', +_FACEENROLLMENTCOMPLETED = _descriptor.Descriptor( + name='FaceEnrollmentCompleted', + full_name='Anki.Vector.external_interface.FaceEnrollmentCompleted', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='intent', full_name='Anki.Vector.external_interface.AppIntentRequest.intent', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + name='result', full_name='Anki.Vector.external_interface.FaceEnrollmentCompleted.result', index=0, + number=1, 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='param', full_name='Anki.Vector.external_interface.AppIntentRequest.param', index=1, - number=2, type=9, cpp_type=9, label=1, + name='face_id', full_name='Anki.Vector.external_interface.FaceEnrollmentCompleted.face_id', index=1, + number=2, 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.FaceEnrollmentCompleted.name', index=2, + number=3, 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, @@ -2160,14 +2421,14 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3829, - serialized_end=3878, + serialized_start=4438, + serialized_end=4564, ) -_APPDISCONNECTED = _descriptor.Descriptor( - name='AppDisconnected', - full_name='Anki.Vector.external_interface.AppDisconnected', +_CANCELFACEENROLLMENTREQUEST = _descriptor.Descriptor( + name='CancelFaceEnrollmentRequest', + full_name='Anki.Vector.external_interface.CancelFaceEnrollmentRequest', filename=None, file=DESCRIPTOR, containing_type=None, @@ -2184,20 +2445,20 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3880, - serialized_end=3897, + serialized_start=4566, + serialized_end=4595, ) -_APPINTENTRESPONSE = _descriptor.Descriptor( - name='AppIntentResponse', - full_name='Anki.Vector.external_interface.AppIntentResponse', +_CANCELFACEENROLLMENTRESPONSE = _descriptor.Descriptor( + name='CancelFaceEnrollmentResponse', + full_name='Anki.Vector.external_interface.CancelFaceEnrollmentResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='status', full_name='Anki.Vector.external_interface.AppIntentResponse.status', index=0, + name='status', full_name='Anki.Vector.external_interface.CancelFaceEnrollmentResponse.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, @@ -2215,114 +2476,14 @@ extension_ranges=[], oneofs=[ ], - serialized_start=3899, - serialized_end=3982, + serialized_start=4597, + serialized_end=4691, ) -_FACEENROLLMENTCOMPLETED = _descriptor.Descriptor( - name='FaceEnrollmentCompleted', - full_name='Anki.Vector.external_interface.FaceEnrollmentCompleted', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='Anki.Vector.external_interface.FaceEnrollmentCompleted.result', index=0, - number=1, 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='face_id', full_name='Anki.Vector.external_interface.FaceEnrollmentCompleted.face_id', index=1, - number=2, 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.FaceEnrollmentCompleted.name', index=2, - number=3, 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=3984, - serialized_end=4110, -) - - -_CANCELFACEENROLLMENTREQUEST = _descriptor.Descriptor( - name='CancelFaceEnrollmentRequest', - full_name='Anki.Vector.external_interface.CancelFaceEnrollmentRequest', - 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=4112, - serialized_end=4141, -) - - -_CANCELFACEENROLLMENTRESPONSE = _descriptor.Descriptor( - name='CancelFaceEnrollmentResponse', - full_name='Anki.Vector.external_interface.CancelFaceEnrollmentResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='status', full_name='Anki.Vector.external_interface.CancelFaceEnrollmentResponse.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=4143, - serialized_end=4237, -) - - -_REQUESTENROLLEDNAMESREQUEST = _descriptor.Descriptor( - name='RequestEnrolledNamesRequest', - full_name='Anki.Vector.external_interface.RequestEnrolledNamesRequest', +_REQUESTENROLLEDNAMESREQUEST = _descriptor.Descriptor( + name='RequestEnrolledNamesRequest', + full_name='Anki.Vector.external_interface.RequestEnrolledNamesRequest', filename=None, file=DESCRIPTOR, containing_type=None, @@ -2339,8 +2500,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4239, - serialized_end=4268, + serialized_start=4693, + serialized_end=4722, ) @@ -2405,8 +2566,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4271, - serialized_end=4465, + serialized_start=4725, + serialized_end=4919, ) @@ -2443,8 +2604,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4467, - serialized_end=4524, + serialized_start=4921, + serialized_end=4978, ) @@ -2481,8 +2642,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4527, - serialized_end=4685, + serialized_start=4981, + serialized_end=5139, ) @@ -2526,8 +2687,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4687, - serialized_end=4771, + serialized_start=5141, + serialized_end=5225, ) @@ -2557,8 +2718,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4773, - serialized_end=4869, + serialized_start=5227, + serialized_end=5323, ) @@ -2588,8 +2749,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4871, - serialized_end=4918, + serialized_start=5325, + serialized_end=5372, ) @@ -2619,8 +2780,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=4920, - serialized_end=5015, + serialized_start=5374, + serialized_end=5469, ) @@ -2643,8 +2804,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5017, - serialized_end=5047, + serialized_start=5471, + serialized_end=5501, ) @@ -2674,8 +2835,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5049, - serialized_end=5144, + serialized_start=5503, + serialized_end=5598, ) @@ -2740,8 +2901,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5147, - serialized_end=5283, + serialized_start=5601, + serialized_end=5737, ) @@ -2771,8 +2932,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5285, - serialized_end=5374, + serialized_start=5739, + serialized_end=5828, ) @@ -2795,8 +2956,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5376, - serialized_end=5400, + serialized_start=5830, + serialized_end=5854, ) @@ -2833,8 +2994,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5403, - serialized_end=5557, + serialized_start=5857, + serialized_end=6011, ) @@ -2857,8 +3018,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5559, - serialized_end=5582, + serialized_start=6013, + serialized_end=6036, ) @@ -2895,14 +3056,14 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5585, - serialized_end=5738, + serialized_start=6039, + serialized_end=6192, ) -_WAKEWORDBEGIN = _descriptor.Descriptor( - name='WakeWordBegin', - full_name='Anki.Vector.external_interface.WakeWordBegin', +_FINDFACESREQUEST = _descriptor.Descriptor( + name='FindFacesRequest', + full_name='Anki.Vector.external_interface.FindFacesRequest', filename=None, file=DESCRIPTOR, containing_type=None, @@ -2919,29 +3080,29 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5740, - serialized_end=5755, + serialized_start=6194, + serialized_end=6212, ) -_WAKEWORDEND = _descriptor.Descriptor( - name='WakeWordEnd', - full_name='Anki.Vector.external_interface.WakeWordEnd', +_FINDFACESRESPONSE = _descriptor.Descriptor( + name='FindFacesResponse', + full_name='Anki.Vector.external_interface.FindFacesResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='intent_heard', full_name='Anki.Vector.external_interface.WakeWordEnd.intent_heard', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, + name='status', full_name='Anki.Vector.external_interface.FindFacesResponse.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='intent_json', full_name='Anki.Vector.external_interface.WakeWordEnd.intent_json', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + name='result', full_name='Anki.Vector.external_interface.FindFacesResponse.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), @@ -2957,46 +3118,56 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5757, - serialized_end=5813, + serialized_start=6215, + serialized_end=6363, ) -_PHOTOINFO = _descriptor.Descriptor( - name='PhotoInfo', - full_name='Anki.Vector.external_interface.PhotoInfo', +_LOOKAROUNDINPLACEREQUEST = _descriptor.Descriptor( + name='LookAroundInPlaceRequest', + full_name='Anki.Vector.external_interface.LookAroundInPlaceRequest', + 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=6365, + serialized_end=6391, +) + + +_LOOKAROUNDINPLACERESPONSE = _descriptor.Descriptor( + name='LookAroundInPlaceResponse', + full_name='Anki.Vector.external_interface.LookAroundInPlaceResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='photo_id', full_name='Anki.Vector.external_interface.PhotoInfo.photo_id', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, + name='status', full_name='Anki.Vector.external_interface.LookAroundInPlaceResponse.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='timestamp_utc', full_name='Anki.Vector.external_interface.PhotoInfo.timestamp_utc', index=1, - number=2, type=13, cpp_type=3, label=1, + name='result', full_name='Anki.Vector.external_interface.LookAroundInPlaceResponse.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), - _descriptor.FieldDescriptor( - name='photo_copied_to_app', full_name='Anki.Vector.external_interface.PhotoInfo.photo_copied_to_app', 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), - _descriptor.FieldDescriptor( - name='thumb_copied_to_app', full_name='Anki.Vector.external_interface.PhotoInfo.thumb_copied_to_app', index=3, - number=4, 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=[ ], @@ -3009,14 +3180,14 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5815, - serialized_end=5925, + serialized_start=6394, + serialized_end=6550, ) -_PHOTOSINFOREQUEST = _descriptor.Descriptor( - name='PhotosInfoRequest', - full_name='Anki.Vector.external_interface.PhotosInfoRequest', +_ROLLBLOCKREQUEST = _descriptor.Descriptor( + name='RollBlockRequest', + full_name='Anki.Vector.external_interface.RollBlockRequest', filename=None, file=DESCRIPTOR, containing_type=None, @@ -3033,29 +3204,29 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5927, - serialized_end=5946, + serialized_start=6552, + serialized_end=6570, ) -_PHOTOSINFORESPONSE = _descriptor.Descriptor( - name='PhotosInfoResponse', - full_name='Anki.Vector.external_interface.PhotosInfoResponse', +_ROLLBLOCKRESPONSE = _descriptor.Descriptor( + name='RollBlockResponse', + full_name='Anki.Vector.external_interface.RollBlockResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='status', full_name='Anki.Vector.external_interface.PhotosInfoResponse.status', index=0, + name='status', full_name='Anki.Vector.external_interface.RollBlockResponse.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='photo_infos', full_name='Anki.Vector.external_interface.PhotosInfoResponse.photo_infos', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], + name='result', full_name='Anki.Vector.external_interface.RollBlockResponse.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), @@ -3071,25 +3242,18 @@ extension_ranges=[], oneofs=[ ], - serialized_start=5949, - serialized_end=6097, + serialized_start=6573, + serialized_end=6721, ) -_PHOTOREQUEST = _descriptor.Descriptor( - name='PhotoRequest', - full_name='Anki.Vector.external_interface.PhotoRequest', +_WAKEWORDBEGIN = _descriptor.Descriptor( + name='WakeWordBegin', + full_name='Anki.Vector.external_interface.WakeWordBegin', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - _descriptor.FieldDescriptor( - name='photo_id', full_name='Anki.Vector.external_interface.PhotoRequest.photo_id', 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), ], extensions=[ ], @@ -3102,27 +3266,27 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6099, - serialized_end=6131, + serialized_start=6723, + serialized_end=6738, ) -_PHOTOPATHMESSAGE = _descriptor.Descriptor( - name='PhotoPathMessage', - full_name='Anki.Vector.external_interface.PhotoPathMessage', +_WAKEWORDEND = _descriptor.Descriptor( + name='WakeWordEnd', + full_name='Anki.Vector.external_interface.WakeWordEnd', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='success', full_name='Anki.Vector.external_interface.PhotoPathMessage.success', index=0, + name='intent_heard', full_name='Anki.Vector.external_interface.WakeWordEnd.intent_heard', 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), _descriptor.FieldDescriptor( - name='full_path', full_name='Anki.Vector.external_interface.PhotoPathMessage.full_path', index=1, + name='intent_json', full_name='Anki.Vector.external_interface.WakeWordEnd.intent_json', 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, @@ -3140,36 +3304,29 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6133, - serialized_end=6187, + serialized_start=6740, + serialized_end=6796, ) -_PHOTORESPONSE = _descriptor.Descriptor( - name='PhotoResponse', - full_name='Anki.Vector.external_interface.PhotoResponse', +_USERINTENT = _descriptor.Descriptor( + name='UserIntent', + full_name='Anki.Vector.external_interface.UserIntent', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='status', full_name='Anki.Vector.external_interface.PhotoResponse.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='success', full_name='Anki.Vector.external_interface.PhotoResponse.success', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, + name='intent_id', full_name='Anki.Vector.external_interface.UserIntent.intent_id', 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='image', full_name='Anki.Vector.external_interface.PhotoResponse.image', index=2, - number=3, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + name='json_data', full_name='Anki.Vector.external_interface.UserIntent.json_data', 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), @@ -3185,25 +3342,46 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6189, - serialized_end=6300, + serialized_start=6798, + serialized_end=6848, ) -_THUMBNAILREQUEST = _descriptor.Descriptor( - name='ThumbnailRequest', - full_name='Anki.Vector.external_interface.ThumbnailRequest', +_PHOTOINFO = _descriptor.Descriptor( + name='PhotoInfo', + full_name='Anki.Vector.external_interface.PhotoInfo', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='photo_id', full_name='Anki.Vector.external_interface.ThumbnailRequest.photo_id', index=0, + name='photo_id', full_name='Anki.Vector.external_interface.PhotoInfo.photo_id', 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='timestamp_utc', full_name='Anki.Vector.external_interface.PhotoInfo.timestamp_utc', 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='photo_copied_to_app', full_name='Anki.Vector.external_interface.PhotoInfo.photo_copied_to_app', 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), + _descriptor.FieldDescriptor( + name='thumb_copied_to_app', full_name='Anki.Vector.external_interface.PhotoInfo.thumb_copied_to_app', index=3, + number=4, 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=[ ], @@ -3216,32 +3394,18 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6302, - serialized_end=6338, + serialized_start=6850, + serialized_end=6960, ) -_THUMBNAILPATHMESSAGE = _descriptor.Descriptor( - name='ThumbnailPathMessage', - full_name='Anki.Vector.external_interface.ThumbnailPathMessage', +_PHOTOSINFOREQUEST = _descriptor.Descriptor( + name='PhotosInfoRequest', + full_name='Anki.Vector.external_interface.PhotosInfoRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - _descriptor.FieldDescriptor( - name='success', full_name='Anki.Vector.external_interface.ThumbnailPathMessage.success', 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), - _descriptor.FieldDescriptor( - name='full_path', full_name='Anki.Vector.external_interface.ThumbnailPathMessage.full_path', 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=[ ], @@ -3254,30 +3418,251 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6340, - serialized_end=6398, + serialized_start=6962, + serialized_end=6981, ) -_THUMBNAILRESPONSE = _descriptor.Descriptor( - name='ThumbnailResponse', - full_name='Anki.Vector.external_interface.ThumbnailResponse', +_PHOTOSINFORESPONSE = _descriptor.Descriptor( + name='PhotosInfoResponse', + full_name='Anki.Vector.external_interface.PhotosInfoResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='status', full_name='Anki.Vector.external_interface.ThumbnailResponse.status', index=0, + name='status', full_name='Anki.Vector.external_interface.PhotosInfoResponse.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='success', full_name='Anki.Vector.external_interface.ThumbnailResponse.success', 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, + name='photo_infos', full_name='Anki.Vector.external_interface.PhotosInfoResponse.photo_infos', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + 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=6984, + serialized_end=7132, +) + + +_PHOTOREQUEST = _descriptor.Descriptor( + name='PhotoRequest', + full_name='Anki.Vector.external_interface.PhotoRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='photo_id', full_name='Anki.Vector.external_interface.PhotoRequest.photo_id', 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7134, + serialized_end=7166, +) + + +_PHOTOPATHMESSAGE = _descriptor.Descriptor( + name='PhotoPathMessage', + full_name='Anki.Vector.external_interface.PhotoPathMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='success', full_name='Anki.Vector.external_interface.PhotoPathMessage.success', 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), + _descriptor.FieldDescriptor( + name='full_path', full_name='Anki.Vector.external_interface.PhotoPathMessage.full_path', 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=7168, + serialized_end=7222, +) + + +_PHOTORESPONSE = _descriptor.Descriptor( + name='PhotoResponse', + full_name='Anki.Vector.external_interface.PhotoResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.PhotoResponse.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='success', full_name='Anki.Vector.external_interface.PhotoResponse.success', 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), + _descriptor.FieldDescriptor( + name='image', full_name='Anki.Vector.external_interface.PhotoResponse.image', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + 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=7224, + serialized_end=7335, +) + + +_THUMBNAILREQUEST = _descriptor.Descriptor( + name='ThumbnailRequest', + full_name='Anki.Vector.external_interface.ThumbnailRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='photo_id', full_name='Anki.Vector.external_interface.ThumbnailRequest.photo_id', 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7337, + serialized_end=7373, +) + + +_THUMBNAILPATHMESSAGE = _descriptor.Descriptor( + name='ThumbnailPathMessage', + full_name='Anki.Vector.external_interface.ThumbnailPathMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='success', full_name='Anki.Vector.external_interface.ThumbnailPathMessage.success', 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), + _descriptor.FieldDescriptor( + name='full_path', full_name='Anki.Vector.external_interface.ThumbnailPathMessage.full_path', 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=7375, + serialized_end=7433, +) + + +_THUMBNAILRESPONSE = _descriptor.Descriptor( + name='ThumbnailResponse', + full_name='Anki.Vector.external_interface.ThumbnailResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.ThumbnailResponse.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='success', full_name='Anki.Vector.external_interface.ThumbnailResponse.success', 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), _descriptor.FieldDescriptor( @@ -3299,8 +3684,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6400, - serialized_end=6515, + serialized_start=7435, + serialized_end=7550, ) @@ -3330,8 +3715,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6517, - serialized_end=6555, + serialized_start=7552, + serialized_end=7590, ) @@ -3368,8 +3753,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6557, - serialized_end=6659, + serialized_start=7592, + serialized_end=7694, ) @@ -3399,8 +3784,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6661, - serialized_end=6691, + serialized_start=7696, + serialized_end=7726, ) @@ -3500,8 +3885,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=6694, - serialized_end=7015, + serialized_start=7729, + serialized_end=8050, ) @@ -3532,56 +3917,21 @@ extension_ranges=[], oneofs=[ ], - serialized_start=7018, - serialized_end=8674, + serialized_start=8053, + serialized_end=9709, ) -_GOTOPOSEREQUEST = _descriptor.Descriptor( - name='GoToPoseRequest', - full_name='Anki.Vector.external_interface.GoToPoseRequest', +_CANCELACTIONBYIDTAGREQUEST = _descriptor.Descriptor( + name='CancelActionByIdTagRequest', + full_name='Anki.Vector.external_interface.CancelActionByIdTagRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='x_mm', full_name='Anki.Vector.external_interface.GoToPoseRequest.x_mm', 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='y_mm', full_name='Anki.Vector.external_interface.GoToPoseRequest.y_mm', 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='rad', full_name='Anki.Vector.external_interface.GoToPoseRequest.rad', 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='motion_prof', full_name='Anki.Vector.external_interface.GoToPoseRequest.motion_prof', index=3, - number=4, 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='id_tag', full_name='Anki.Vector.external_interface.GoToPoseRequest.id_tag', index=4, - number=5, 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='num_retries', full_name='Anki.Vector.external_interface.GoToPoseRequest.num_retries', index=5, - number=6, type=5, cpp_type=1, label=1, + name='id_tag', full_name='Anki.Vector.external_interface.CancelActionByIdTagRequest.id_tag', 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, @@ -3598,13 +3948,110 @@ extension_ranges=[], oneofs=[ ], - serialized_start=8677, - serialized_end=8844, + serialized_start=9711, + serialized_end=9755, ) -_GOTOPOSERESPONSE = _descriptor.Descriptor( - name='GoToPoseResponse', +_CANCELACTIONBYIDTAGRESPONSE = _descriptor.Descriptor( + name='CancelActionByIdTagResponse', + full_name='Anki.Vector.external_interface.CancelActionByIdTagResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.CancelActionByIdTagResponse.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=9757, + serialized_end=9850, +) + + +_GOTOPOSEREQUEST = _descriptor.Descriptor( + name='GoToPoseRequest', + full_name='Anki.Vector.external_interface.GoToPoseRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='x_mm', full_name='Anki.Vector.external_interface.GoToPoseRequest.x_mm', 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='y_mm', full_name='Anki.Vector.external_interface.GoToPoseRequest.y_mm', 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='rad', full_name='Anki.Vector.external_interface.GoToPoseRequest.rad', 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='motion_prof', full_name='Anki.Vector.external_interface.GoToPoseRequest.motion_prof', index=3, + number=4, 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='id_tag', full_name='Anki.Vector.external_interface.GoToPoseRequest.id_tag', index=4, + number=5, 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='num_retries', full_name='Anki.Vector.external_interface.GoToPoseRequest.num_retries', 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=9853, + serialized_end=10020, +) + + +_GOTOPOSERESPONSE = _descriptor.Descriptor( + name='GoToPoseResponse', full_name='Anki.Vector.external_interface.GoToPoseResponse', filename=None, file=DESCRIPTOR, @@ -3636,8 +4083,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=8847, - serialized_end=8991, + serialized_start=10023, + serialized_end=10167, ) @@ -3723,8 +4170,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=8994, - serialized_end=9330, + serialized_start=10170, + serialized_end=10506, ) @@ -3761,8 +4208,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=9333, - serialized_end=9481, + serialized_start=10509, + serialized_end=10657, ) @@ -3820,8 +4267,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=9483, - serialized_end=9610, + serialized_start=10659, + serialized_end=10786, ) @@ -3858,8 +4305,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=9613, - serialized_end=9762, + serialized_start=10789, + serialized_end=10938, ) @@ -3931,8 +4378,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=9765, - serialized_end=9934, + serialized_start=10941, + serialized_end=11110, ) @@ -3969,8 +4416,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=9937, - serialized_end=10084, + serialized_start=11113, + serialized_end=11260, ) @@ -4035,8 +4482,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10087, - serialized_end=10245, + serialized_start=11263, + serialized_end=11421, ) @@ -4073,8 +4520,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10248, - serialized_end=10396, + serialized_start=11424, + serialized_end=11572, ) @@ -4139,8 +4586,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10399, - serialized_end=10558, + serialized_start=11575, + serialized_end=11734, ) @@ -4177,99 +4624,147 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10561, - serialized_end=10710, + serialized_start=11737, + serialized_end=11886, ) -_NETWORKSTATS = _descriptor.Descriptor( - name='NetworkStats', - full_name='Anki.Vector.external_interface.NetworkStats', +_TURNTOWARDSFACEREQUEST = _descriptor.Descriptor( + name='TurnTowardsFaceRequest', + full_name='Anki.Vector.external_interface.TurnTowardsFaceRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='g_net_stat1_num_connections', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stat1_num_connections', index=0, + name='face_id', full_name='Anki.Vector.external_interface.TurnTowardsFaceRequest.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='g_net_stat2_latency_avg', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stat2_latency_avg', index=1, + name='max_turn_angle_rad', full_name='Anki.Vector.external_interface.TurnTowardsFaceRequest.max_turn_angle_rad', 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='g_net_stat3_latency_sd', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stat3_latency_sd', index=2, - number=3, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='id_tag', full_name='Anki.Vector.external_interface.TurnTowardsFaceRequest.id_tag', 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='g_net_stat4_latency_min', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stat4_latency_min', index=3, - number=4, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='num_retries', full_name='Anki.Vector.external_interface.TurnTowardsFaceRequest.num_retries', 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=11888, + serialized_end=11994, +) + + +_TURNTOWARDSFACERESPONSE = _descriptor.Descriptor( + name='TurnTowardsFaceResponse', + full_name='Anki.Vector.external_interface.TurnTowardsFaceResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ _descriptor.FieldDescriptor( - name='g_net_stat5_latency_max', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stat5_latency_max', index=4, - number=5, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='status', full_name='Anki.Vector.external_interface.TurnTowardsFaceResponse.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='g_net_stat6_ping_arrived_pc', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stat6_ping_arrived_pc', index=5, - number=6, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='result', full_name='Anki.Vector.external_interface.TurnTowardsFaceResponse.result', index=1, + number=2, 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=11997, + serialized_end=12148, +) + + +_GOTOOBJECTREQUEST = _descriptor.Descriptor( + name='GoToObjectRequest', + full_name='Anki.Vector.external_interface.GoToObjectRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ _descriptor.FieldDescriptor( - name='g_net_stat7_ext_queued_avg_ms', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stat7_ext_queued_avg_ms', index=6, - number=7, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='object_id', full_name='Anki.Vector.external_interface.GoToObjectRequest.object_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='g_net_stat8_ext_queued_min_ms', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stat8_ext_queued_min_ms', index=7, - number=8, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='motion_prof', full_name='Anki.Vector.external_interface.GoToObjectRequest.motion_prof', index=1, + number=2, 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='g_net_stat9_ext_queued_max_ms', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stat9_ext_queued_max_ms', index=8, - number=9, type=2, cpp_type=6, label=1, + name='distance_from_object_origin_mm', full_name='Anki.Vector.external_interface.GoToObjectRequest.distance_from_object_origin_mm', 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='g_net_stata_queued_avg_ms', full_name='Anki.Vector.external_interface.NetworkStats.g_net_stata_queued_avg_ms', index=9, - number=10, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='use_pre_dock_pose', full_name='Anki.Vector.external_interface.GoToObjectRequest.use_pre_dock_pose', index=3, + number=4, 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), _descriptor.FieldDescriptor( - name='g_net_statb_queued_min_ms', full_name='Anki.Vector.external_interface.NetworkStats.g_net_statb_queued_min_ms', index=10, - number=11, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='id_tag', full_name='Anki.Vector.external_interface.GoToObjectRequest.id_tag', index=4, + number=5, 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='g_net_statc_queued_max_ms', full_name='Anki.Vector.external_interface.NetworkStats.g_net_statc_queued_max_ms', index=11, - number=12, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='num_retries', full_name='Anki.Vector.external_interface.GoToObjectRequest.num_retries', 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), @@ -4285,18 +4780,32 @@ extension_ranges=[], oneofs=[ ], - serialized_start=10713, - serialized_end=11154, + serialized_start=12151, + serialized_end=12365, ) -_BATTERYSTATEREQUEST = _descriptor.Descriptor( - name='BatteryStateRequest', - full_name='Anki.Vector.external_interface.BatteryStateRequest', +_GOTOOBJECTRESPONSE = _descriptor.Descriptor( + name='GoToObjectResponse', + full_name='Anki.Vector.external_interface.GoToObjectResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.GoToObjectResponse.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.GoToObjectResponse.result', index=1, + number=2, 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=[ ], @@ -4309,64 +4818,64 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11156, - serialized_end=11177, + serialized_start=12368, + serialized_end=12514, ) -_BATTERYSTATERESPONSE = _descriptor.Descriptor( - name='BatteryStateResponse', - full_name='Anki.Vector.external_interface.BatteryStateResponse', +_ROLLOBJECTREQUEST = _descriptor.Descriptor( + name='RollObjectRequest', + full_name='Anki.Vector.external_interface.RollObjectRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='status', full_name='Anki.Vector.external_interface.BatteryStateResponse.status', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, + name='object_id', full_name='Anki.Vector.external_interface.RollObjectRequest.object_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='battery_level', full_name='Anki.Vector.external_interface.BatteryStateResponse.battery_level', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, + name='motion_prof', full_name='Anki.Vector.external_interface.RollObjectRequest.motion_prof', index=1, + number=2, 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='battery_volts', full_name='Anki.Vector.external_interface.BatteryStateResponse.battery_volts', index=2, + name='approach_angle_rad', full_name='Anki.Vector.external_interface.RollObjectRequest.approach_angle_rad', 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='is_charging', full_name='Anki.Vector.external_interface.BatteryStateResponse.is_charging', index=3, + name='use_approach_angle', full_name='Anki.Vector.external_interface.RollObjectRequest.use_approach_angle', index=3, number=4, 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), _descriptor.FieldDescriptor( - name='is_on_charger_platform', full_name='Anki.Vector.external_interface.BatteryStateResponse.is_on_charger_platform', index=4, + name='use_pre_dock_pose', full_name='Anki.Vector.external_interface.RollObjectRequest.use_pre_dock_pose', index=4, number=5, 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), _descriptor.FieldDescriptor( - name='suggested_charger_sec', full_name='Anki.Vector.external_interface.BatteryStateResponse.suggested_charger_sec', index=5, - number=6, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='id_tag', full_name='Anki.Vector.external_interface.RollObjectRequest.id_tag', 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='cube_battery', full_name='Anki.Vector.external_interface.BatteryStateResponse.cube_battery', index=6, - number=7, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, + name='num_retries', full_name='Anki.Vector.external_interface.RollObjectRequest.num_retries', 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), @@ -4382,43 +4891,29 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11180, - serialized_end=11509, + serialized_start=12517, + serialized_end=12747, ) -_CUBEBATTERY = _descriptor.Descriptor( - name='CubeBattery', - full_name='Anki.Vector.external_interface.CubeBattery', +_ROLLOBJECTRESPONSE = _descriptor.Descriptor( + name='RollObjectResponse', + full_name='Anki.Vector.external_interface.RollObjectResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='level', full_name='Anki.Vector.external_interface.CubeBattery.level', index=0, - number=1, 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='factory_id', full_name='Anki.Vector.external_interface.CubeBattery.factory_id', 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), - _descriptor.FieldDescriptor( - name='battery_volts', full_name='Anki.Vector.external_interface.CubeBattery.battery_volts', index=2, - number=3, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='status', full_name='Anki.Vector.external_interface.RollObjectResponse.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='time_since_last_reading_sec', full_name='Anki.Vector.external_interface.CubeBattery.time_since_last_reading_sec', index=3, - number=4, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='result', full_name='Anki.Vector.external_interface.RollObjectResponse.result', index=1, + number=2, 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), @@ -4427,7 +4922,6 @@ ], nested_types=[], enum_types=[ - _CUBEBATTERY_CUBEBATTERYLEVEL, ], options=None, is_extendable=False, @@ -4435,60 +4929,64 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11512, - serialized_end=11723, + serialized_start=12750, + serialized_end=12896, ) -_VERSIONSTATEREQUEST = _descriptor.Descriptor( - name='VersionStateRequest', - full_name='Anki.Vector.external_interface.VersionStateRequest', - 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=11725, - serialized_end=11746, -) - - -_VERSIONSTATERESPONSE = _descriptor.Descriptor( - name='VersionStateResponse', - full_name='Anki.Vector.external_interface.VersionStateResponse', +_POPAWHEELIEREQUEST = _descriptor.Descriptor( + name='PopAWheelieRequest', + full_name='Anki.Vector.external_interface.PopAWheelieRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='status', full_name='Anki.Vector.external_interface.VersionStateResponse.status', index=0, - number=1, type=11, cpp_type=10, label=1, + name='object_id', full_name='Anki.Vector.external_interface.PopAWheelieRequest.object_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='motion_prof', full_name='Anki.Vector.external_interface.PopAWheelieRequest.motion_prof', index=1, + number=2, 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='os_version', full_name='Anki.Vector.external_interface.VersionStateResponse.os_version', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + name='approach_angle_rad', full_name='Anki.Vector.external_interface.PopAWheelieRequest.approach_angle_rad', 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='engine_build_id', full_name='Anki.Vector.external_interface.VersionStateResponse.engine_build_id', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + name='use_approach_angle', full_name='Anki.Vector.external_interface.PopAWheelieRequest.use_approach_angle', index=3, + number=4, 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), + _descriptor.FieldDescriptor( + name='use_pre_dock_pose', full_name='Anki.Vector.external_interface.PopAWheelieRequest.use_pre_dock_pose', index=4, + number=5, 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), + _descriptor.FieldDescriptor( + name='id_tag', full_name='Anki.Vector.external_interface.PopAWheelieRequest.id_tag', 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='num_retries', full_name='Anki.Vector.external_interface.PopAWheelieRequest.num_retries', 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), @@ -4504,51 +5002,27 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11749, - serialized_end=11880, -) - - -_NETWORKSTATEREQUEST = _descriptor.Descriptor( - name='NetworkStateRequest', - full_name='Anki.Vector.external_interface.NetworkStateRequest', - 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=11882, - serialized_end=11903, + serialized_start=12899, + serialized_end=13130, ) -_NETWORKSTATERESPONSE = _descriptor.Descriptor( - name='NetworkStateResponse', - full_name='Anki.Vector.external_interface.NetworkStateResponse', +_POPAWHEELIERESPONSE = _descriptor.Descriptor( + name='PopAWheelieResponse', + full_name='Anki.Vector.external_interface.PopAWheelieResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='status', full_name='Anki.Vector.external_interface.NetworkStateResponse.status', index=0, + name='status', full_name='Anki.Vector.external_interface.PopAWheelieResponse.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='network_stats', full_name='Anki.Vector.external_interface.NetworkStateResponse.network_stats', index=1, + name='result', full_name='Anki.Vector.external_interface.PopAWheelieResponse.result', index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, @@ -4566,39 +5040,67 @@ extension_ranges=[], oneofs=[ ], - serialized_start=11906, - serialized_end=12061, + serialized_start=13133, + serialized_end=13280, ) -_SAYTEXTREQUEST = _descriptor.Descriptor( - name='SayTextRequest', - full_name='Anki.Vector.external_interface.SayTextRequest', +_PICKUPOBJECTREQUEST = _descriptor.Descriptor( + name='PickupObjectRequest', + full_name='Anki.Vector.external_interface.PickupObjectRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='text', full_name='Anki.Vector.external_interface.SayTextRequest.text', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + name='object_id', full_name='Anki.Vector.external_interface.PickupObjectRequest.object_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='use_vector_voice', full_name='Anki.Vector.external_interface.SayTextRequest.use_vector_voice', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, + name='motion_prof', full_name='Anki.Vector.external_interface.PickupObjectRequest.motion_prof', index=1, + number=2, 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='duration_scalar', full_name='Anki.Vector.external_interface.SayTextRequest.duration_scalar', index=2, + name='approach_angle_rad', full_name='Anki.Vector.external_interface.PickupObjectRequest.approach_angle_rad', 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='use_approach_angle', full_name='Anki.Vector.external_interface.PickupObjectRequest.use_approach_angle', index=3, + number=4, 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), + _descriptor.FieldDescriptor( + name='use_pre_dock_pose', full_name='Anki.Vector.external_interface.PickupObjectRequest.use_pre_dock_pose', index=4, + number=5, 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), + _descriptor.FieldDescriptor( + name='id_tag', full_name='Anki.Vector.external_interface.PickupObjectRequest.id_tag', 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='num_retries', full_name='Anki.Vector.external_interface.PickupObjectRequest.num_retries', 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), ], extensions=[ ], @@ -4611,29 +5113,29 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12063, - serialized_end=12144, + serialized_start=13283, + serialized_end=13515, ) -_SAYTEXTRESPONSE = _descriptor.Descriptor( - name='SayTextResponse', - full_name='Anki.Vector.external_interface.SayTextResponse', +_PICKUPOBJECTRESPONSE = _descriptor.Descriptor( + name='PickupObjectResponse', + full_name='Anki.Vector.external_interface.PickupObjectResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='status', full_name='Anki.Vector.external_interface.SayTextResponse.status', index=0, + name='status', full_name='Anki.Vector.external_interface.PickupObjectResponse.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='state', full_name='Anki.Vector.external_interface.SayTextResponse.state', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, + name='result', full_name='Anki.Vector.external_interface.PickupObjectResponse.result', index=1, + number=2, 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), @@ -4642,7 +5144,6 @@ ], nested_types=[], enum_types=[ - _SAYTEXTRESPONSE_UTTERANCESTATE, ], options=None, is_extendable=False, @@ -4650,64 +5151,29 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12147, - serialized_end=12392, + serialized_start=13518, + serialized_end=13666, ) -_STIMULATIONINFO = _descriptor.Descriptor( - name='StimulationInfo', - full_name='Anki.Vector.external_interface.StimulationInfo', +_PLACEOBJECTONGROUNDHEREREQUEST = _descriptor.Descriptor( + name='PlaceObjectOnGroundHereRequest', + full_name='Anki.Vector.external_interface.PlaceObjectOnGroundHereRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='emotion_events', full_name='Anki.Vector.external_interface.StimulationInfo.emotion_events', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='Anki.Vector.external_interface.StimulationInfo.value', 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='velocity', full_name='Anki.Vector.external_interface.StimulationInfo.velocity', 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='accel', full_name='Anki.Vector.external_interface.StimulationInfo.accel', 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='value_before_event', full_name='Anki.Vector.external_interface.StimulationInfo.value_before_event', 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='min_value', full_name='Anki.Vector.external_interface.StimulationInfo.min_value', index=5, - number=6, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='id_tag', full_name='Anki.Vector.external_interface.PlaceObjectOnGroundHereRequest.id_tag', 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='max_value', full_name='Anki.Vector.external_interface.StimulationInfo.max_value', index=6, - number=7, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), + name='num_retries', full_name='Anki.Vector.external_interface.PlaceObjectOnGroundHereRequest.num_retries', index=1, + number=2, 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), @@ -4723,22 +5189,29 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12395, - serialized_end=12550, + serialized_start=13668, + serialized_end=13737, ) -_AUDIOSENDMODEREQUEST = _descriptor.Descriptor( - name='AudioSendModeRequest', - full_name='Anki.Vector.external_interface.AudioSendModeRequest', +_PLACEOBJECTONGROUNDHERERESPONSE = _descriptor.Descriptor( + name='PlaceObjectOnGroundHereResponse', + full_name='Anki.Vector.external_interface.PlaceObjectOnGroundHereResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='mode', full_name='Anki.Vector.external_interface.AudioSendModeRequest.mode', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, + name='status', full_name='Anki.Vector.external_interface.PlaceObjectOnGroundHereResponse.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.PlaceObjectOnGroundHereResponse.result', index=1, + number=2, 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), @@ -4754,25 +5227,18 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12552, - serialized_end=12641, + serialized_start=13740, + serialized_end=13899, ) -_AUDIOSENDMODECHANGED = _descriptor.Descriptor( - name='AudioSendModeChanged', - full_name='Anki.Vector.external_interface.AudioSendModeChanged', +_BATTERYSTATEREQUEST = _descriptor.Descriptor( + name='BatteryStateRequest', + full_name='Anki.Vector.external_interface.BatteryStateRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - _descriptor.FieldDescriptor( - name='mode', full_name='Anki.Vector.external_interface.AudioSendModeChanged.mode', index=0, - number=1, 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=[ ], @@ -4785,78 +5251,64 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12643, - serialized_end=12732, + serialized_start=13901, + serialized_end=13922, ) -_AUDIOCHUNK = _descriptor.Descriptor( - name='AudioChunk', - full_name='Anki.Vector.external_interface.AudioChunk', +_BATTERYSTATERESPONSE = _descriptor.Descriptor( + name='BatteryStateResponse', + full_name='Anki.Vector.external_interface.BatteryStateResponse', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='robot_time_stamp', full_name='Anki.Vector.external_interface.AudioChunk.robot_time_stamp', 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='group_id', full_name='Anki.Vector.external_interface.AudioChunk.group_id', 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='chunk_id', full_name='Anki.Vector.external_interface.AudioChunk.chunk_id', index=2, - number=3, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, + name='status', full_name='Anki.Vector.external_interface.BatteryStateResponse.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='audio_chunk_count', full_name='Anki.Vector.external_interface.AudioChunk.audio_chunk_count', index=3, - number=4, type=13, cpp_type=3, label=1, + name='battery_level', full_name='Anki.Vector.external_interface.BatteryStateResponse.battery_level', 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='signal_power', full_name='Anki.Vector.external_interface.AudioChunk.signal_power', index=4, - number=5, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + name='battery_volts', full_name='Anki.Vector.external_interface.BatteryStateResponse.battery_volts', 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='direction_strengths', full_name='Anki.Vector.external_interface.AudioChunk.direction_strengths', index=5, - number=6, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + name='is_charging', full_name='Anki.Vector.external_interface.BatteryStateResponse.is_charging', index=3, + number=4, 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), _descriptor.FieldDescriptor( - name='source_direction', full_name='Anki.Vector.external_interface.AudioChunk.source_direction', index=6, - number=7, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, + name='is_on_charger_platform', full_name='Anki.Vector.external_interface.BatteryStateResponse.is_on_charger_platform', index=4, + number=5, 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), _descriptor.FieldDescriptor( - name='source_confidence', full_name='Anki.Vector.external_interface.AudioChunk.source_confidence', index=7, - number=8, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, + name='suggested_charger_sec', full_name='Anki.Vector.external_interface.BatteryStateResponse.suggested_charger_sec', 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='noise_floor_power', full_name='Anki.Vector.external_interface.AudioChunk.noise_floor_power', index=8, - number=9, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, + name='cube_battery', full_name='Anki.Vector.external_interface.BatteryStateResponse.cube_battery', index=6, + number=7, 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), @@ -4872,87 +5324,859 @@ extension_ranges=[], oneofs=[ ], - serialized_start=12735, - serialized_end=12967, + serialized_start=13925, + serialized_end=14254, ) -_AUDIOFEEDREQUEST = _descriptor.Descriptor( - name='AudioFeedRequest', - full_name='Anki.Vector.external_interface.AudioFeedRequest', +_CUBEBATTERY = _descriptor.Descriptor( + name='CubeBattery', + full_name='Anki.Vector.external_interface.CubeBattery', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, + _descriptor.FieldDescriptor( + name='level', full_name='Anki.Vector.external_interface.CubeBattery.level', index=0, + number=1, 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='factory_id', full_name='Anki.Vector.external_interface.CubeBattery.factory_id', 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), + _descriptor.FieldDescriptor( + name='battery_volts', full_name='Anki.Vector.external_interface.CubeBattery.battery_volts', 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='time_since_last_reading_sec', full_name='Anki.Vector.external_interface.CubeBattery.time_since_last_reading_sec', 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _CUBEBATTERY_CUBEBATTERYLEVEL, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=14257, + serialized_end=14468, +) + + +_VERSIONSTATEREQUEST = _descriptor.Descriptor( + name='VersionStateRequest', + full_name='Anki.Vector.external_interface.VersionStateRequest', + 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=14470, + serialized_end=14491, +) + + +_VERSIONSTATERESPONSE = _descriptor.Descriptor( + name='VersionStateResponse', + full_name='Anki.Vector.external_interface.VersionStateResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.VersionStateResponse.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='os_version', full_name='Anki.Vector.external_interface.VersionStateResponse.os_version', 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), + _descriptor.FieldDescriptor( + name='engine_build_id', full_name='Anki.Vector.external_interface.VersionStateResponse.engine_build_id', index=2, + number=3, 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=14494, + serialized_end=14625, +) + + +_SAYTEXTREQUEST = _descriptor.Descriptor( + name='SayTextRequest', + full_name='Anki.Vector.external_interface.SayTextRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='text', full_name='Anki.Vector.external_interface.SayTextRequest.text', index=0, + number=1, 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), + _descriptor.FieldDescriptor( + name='use_vector_voice', full_name='Anki.Vector.external_interface.SayTextRequest.use_vector_voice', 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), + _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, + 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=14627, + serialized_end=14708, +) + + +_SAYTEXTRESPONSE = _descriptor.Descriptor( + name='SayTextResponse', + full_name='Anki.Vector.external_interface.SayTextResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.SayTextResponse.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='state', full_name='Anki.Vector.external_interface.SayTextResponse.state', 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=[ + _SAYTEXTRESPONSE_UTTERANCESTATE, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=14711, + serialized_end=14956, +) + + +_STIMULATIONINFO = _descriptor.Descriptor( + name='StimulationInfo', + full_name='Anki.Vector.external_interface.StimulationInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='emotion_events', full_name='Anki.Vector.external_interface.StimulationInfo.emotion_events', index=0, + number=1, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='Anki.Vector.external_interface.StimulationInfo.value', 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='velocity', full_name='Anki.Vector.external_interface.StimulationInfo.velocity', 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='accel', full_name='Anki.Vector.external_interface.StimulationInfo.accel', 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='value_before_event', full_name='Anki.Vector.external_interface.StimulationInfo.value_before_event', 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='min_value', full_name='Anki.Vector.external_interface.StimulationInfo.min_value', 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='max_value', full_name='Anki.Vector.external_interface.StimulationInfo.max_value', index=6, + number=7, 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=14959, + serialized_end=15114, +) + + +_AUDIOSENDMODEREQUEST = _descriptor.Descriptor( + name='AudioSendModeRequest', + full_name='Anki.Vector.external_interface.AudioSendModeRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='mode', full_name='Anki.Vector.external_interface.AudioSendModeRequest.mode', index=0, + number=1, 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=15116, + serialized_end=15205, +) + + +_AUDIOSENDMODECHANGED = _descriptor.Descriptor( + name='AudioSendModeChanged', + full_name='Anki.Vector.external_interface.AudioSendModeChanged', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='mode', full_name='Anki.Vector.external_interface.AudioSendModeChanged.mode', index=0, + number=1, 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=15207, + serialized_end=15296, +) + + +_AUDIOCHUNK = _descriptor.Descriptor( + name='AudioChunk', + full_name='Anki.Vector.external_interface.AudioChunk', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='robot_time_stamp', full_name='Anki.Vector.external_interface.AudioChunk.robot_time_stamp', 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='group_id', full_name='Anki.Vector.external_interface.AudioChunk.group_id', 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='chunk_id', full_name='Anki.Vector.external_interface.AudioChunk.chunk_id', index=2, + number=3, 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='audio_chunk_count', full_name='Anki.Vector.external_interface.AudioChunk.audio_chunk_count', index=3, + number=4, 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='signal_power', full_name='Anki.Vector.external_interface.AudioChunk.signal_power', index=4, + number=5, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='direction_strengths', full_name='Anki.Vector.external_interface.AudioChunk.direction_strengths', index=5, + number=6, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='source_direction', full_name='Anki.Vector.external_interface.AudioChunk.source_direction', 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='source_confidence', full_name='Anki.Vector.external_interface.AudioChunk.source_confidence', 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='noise_floor_power', full_name='Anki.Vector.external_interface.AudioChunk.noise_floor_power', index=8, + number=9, 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=15299, + serialized_end=15531, +) + + +_AUDIOFEEDREQUEST = _descriptor.Descriptor( + name='AudioFeedRequest', + full_name='Anki.Vector.external_interface.AudioFeedRequest', + 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=15533, + serialized_end=15551, +) + + +_AUDIOFEEDRESPONSE = _descriptor.Descriptor( + name='AudioFeedResponse', + full_name='Anki.Vector.external_interface.AudioFeedResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='robot_time_stamp', full_name='Anki.Vector.external_interface.AudioFeedResponse.robot_time_stamp', 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='group_id', full_name='Anki.Vector.external_interface.AudioFeedResponse.group_id', 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='signal_power', full_name='Anki.Vector.external_interface.AudioFeedResponse.signal_power', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='direction_strengths', full_name='Anki.Vector.external_interface.AudioFeedResponse.direction_strengths', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='source_direction', full_name='Anki.Vector.external_interface.AudioFeedResponse.source_direction', index=4, + number=5, 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='source_confidence', full_name='Anki.Vector.external_interface.AudioFeedResponse.source_confidence', index=5, + number=6, 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='noise_floor_power', full_name='Anki.Vector.external_interface.AudioFeedResponse.noise_floor_power', 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('\200\246\035\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=15554, + serialized_end=15754, +) + + +_EXTERNALAUDIOSTREAMPREPARE = _descriptor.Descriptor( + name='ExternalAudioStreamPrepare', + full_name='Anki.Vector.external_interface.ExternalAudioStreamPrepare', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='audio_frame_rate', full_name='Anki.Vector.external_interface.ExternalAudioStreamPrepare.audio_frame_rate', 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='audio_volume', full_name='Anki.Vector.external_interface.ExternalAudioStreamPrepare.audio_volume', 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=12969, - serialized_end=12987, + serialized_start=15756, + serialized_end=15832, ) -_AUDIOFEEDRESPONSE = _descriptor.Descriptor( - name='AudioFeedResponse', - full_name='Anki.Vector.external_interface.AudioFeedResponse', +_EXTERNALAUDIOSTREAMCHUNK = _descriptor.Descriptor( + name='ExternalAudioStreamChunk', + full_name='Anki.Vector.external_interface.ExternalAudioStreamChunk', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='robot_time_stamp', full_name='Anki.Vector.external_interface.AudioFeedResponse.robot_time_stamp', index=0, + name='audio_chunk_size_bytes', full_name='Anki.Vector.external_interface.ExternalAudioStreamChunk.audio_chunk_size_bytes', 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='group_id', full_name='Anki.Vector.external_interface.AudioFeedResponse.group_id', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, + name='audio_chunk_samples', full_name='Anki.Vector.external_interface.ExternalAudioStreamChunk.audio_chunk_samples', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + 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=15834, + serialized_end=15921, +) + + +_EXTERNALAUDIOSTREAMCOMPLETE = _descriptor.Descriptor( + name='ExternalAudioStreamComplete', + full_name='Anki.Vector.external_interface.ExternalAudioStreamComplete', + 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=15923, + serialized_end=15952, +) + + +_EXTERNALAUDIOSTREAMCANCEL = _descriptor.Descriptor( + name='ExternalAudioStreamCancel', + full_name='Anki.Vector.external_interface.ExternalAudioStreamCancel', + 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=15954, + serialized_end=15981, +) + + +_EXTERNALAUDIOSTREAMREQUEST = _descriptor.Descriptor( + name='ExternalAudioStreamRequest', + full_name='Anki.Vector.external_interface.ExternalAudioStreamRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='audio_stream_prepare', full_name='Anki.Vector.external_interface.ExternalAudioStreamRequest.audio_stream_prepare', 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='signal_power', full_name='Anki.Vector.external_interface.AudioFeedResponse.signal_power', index=2, - number=3, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + name='audio_stream_chunk', full_name='Anki.Vector.external_interface.ExternalAudioStreamRequest.audio_stream_chunk', index=1, + number=2, 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='direction_strengths', full_name='Anki.Vector.external_interface.AudioFeedResponse.direction_strengths', index=3, - number=4, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + name='audio_stream_complete', full_name='Anki.Vector.external_interface.ExternalAudioStreamRequest.audio_stream_complete', index=2, + number=3, 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='source_direction', full_name='Anki.Vector.external_interface.AudioFeedResponse.source_direction', index=4, - number=5, type=13, cpp_type=3, label=1, + name='audio_stream_cancel', full_name='Anki.Vector.external_interface.ExternalAudioStreamRequest.audio_stream_cancel', index=3, + number=4, 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=[ + _descriptor.OneofDescriptor( + 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, +) + + +_EXTERNALAUDIOSTREAMPLAYBACKCOMPLETE = _descriptor.Descriptor( + name='ExternalAudioStreamPlaybackComplete', + full_name='Anki.Vector.external_interface.ExternalAudioStreamPlaybackComplete', + 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=16400, + serialized_end=16437, +) + + +_EXTERNALAUDIOSTREAMPLAYBACKFAILURE = _descriptor.Descriptor( + name='ExternalAudioStreamPlaybackFailure', + full_name='Anki.Vector.external_interface.ExternalAudioStreamPlaybackFailure', + 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=16439, + serialized_end=16475, +) + + +_EXTERNALAUDIOSTREAMBUFFEROVERRUN = _descriptor.Descriptor( + name='ExternalAudioStreamBufferOverrun', + full_name='Anki.Vector.external_interface.ExternalAudioStreamBufferOverrun', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='audio_samples_sent', full_name='Anki.Vector.external_interface.ExternalAudioStreamBufferOverrun.audio_samples_sent', 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='source_confidence', full_name='Anki.Vector.external_interface.AudioFeedResponse.source_confidence', index=5, - number=6, type=13, cpp_type=3, label=1, + name='audio_samples_played', full_name='Anki.Vector.external_interface.ExternalAudioStreamBufferOverrun.audio_samples_played', 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=16477, + serialized_end=16569, +) + + +_EXTERNALAUDIOSTREAMRESPONSE = _descriptor.Descriptor( + name='ExternalAudioStreamResponse', + full_name='Anki.Vector.external_interface.ExternalAudioStreamResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='audio_stream_playback_complete', full_name='Anki.Vector.external_interface.ExternalAudioStreamResponse.audio_stream_playback_complete', 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='noise_floor_power', full_name='Anki.Vector.external_interface.AudioFeedResponse.noise_floor_power', index=6, - number=7, type=13, cpp_type=3, label=1, + name='audio_stream_buffer_overrun', full_name='Anki.Vector.external_interface.ExternalAudioStreamResponse.audio_stream_buffer_overrun', index=1, + number=2, 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='audio_stream_playback_failyer', full_name='Anki.Vector.external_interface.ExternalAudioStreamResponse.audio_stream_playback_failyer', index=2, + number=3, 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=[ + _descriptor.OneofDescriptor( + 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, +) + + +_MASTERVOLUMEREQUEST = _descriptor.Descriptor( + name='MasterVolumeRequest', + full_name='Anki.Vector.external_interface.MasterVolumeRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='volume_level', full_name='Anki.Vector.external_interface.MasterVolumeRequest.volume_level', index=0, + number=1, 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, @@ -4963,14 +6187,45 @@ nested_types=[], enum_types=[ ], - options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('\200\246\035\001')), + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=16951, + serialized_end=17045, +) + + +_MASTERVOLUMERESPONSE = _descriptor.Descriptor( + name='MasterVolumeResponse', + full_name='Anki.Vector.external_interface.MasterVolumeResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.MasterVolumeResponse.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=12990, - serialized_end=13190, + serialized_start=17047, + serialized_end=17133, ) @@ -5000,8 +6255,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13192, - serialized_end=13238, + serialized_start=17135, + serialized_end=17181, ) @@ -5031,8 +6286,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13240, - serialized_end=13335, + serialized_start=17183, + serialized_end=17278, ) @@ -5090,8 +6345,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13338, - serialized_end=13515, + serialized_start=17281, + serialized_end=17458, ) @@ -5121,8 +6376,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13517, - serialized_end=13610, + serialized_start=17460, + serialized_end=17553, ) @@ -5152,8 +6407,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13612, - serialized_end=13658, + serialized_start=17555, + serialized_end=17601, ) @@ -5183,8 +6438,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13660, - serialized_end=13755, + serialized_start=17603, + serialized_end=17698, ) @@ -5214,8 +6469,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13757, - serialized_end=13798, + serialized_start=17700, + serialized_end=17741, ) @@ -5245,8 +6500,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13800, - serialized_end=13890, + serialized_start=17743, + serialized_end=17833, ) @@ -5269,8 +6524,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13892, - serialized_end=13912, + serialized_start=17835, + serialized_end=17855, ) @@ -5300,8 +6555,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13914, - serialized_end=13959, + serialized_start=17857, + serialized_end=17902, ) @@ -5331,8 +6586,63 @@ extension_ranges=[], oneofs=[ ], - serialized_start=13961, - serialized_end=14055, + serialized_start=17904, + serialized_end=17998, +) + + +_ISIMAGESTREAMINGENABLEDREQUEST = _descriptor.Descriptor( + name='IsImageStreamingEnabledRequest', + full_name='Anki.Vector.external_interface.IsImageStreamingEnabledRequest', + 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=18000, + serialized_end=18032, +) + + +_ISIMAGESTREAMINGENABLEDRESPONSE = _descriptor.Descriptor( + name='IsImageStreamingEnabledResponse', + full_name='Anki.Vector.external_interface.IsImageStreamingEnabledResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='is_image_streaming_enabled', full_name='Anki.Vector.external_interface.IsImageStreamingEnabledResponse.is_image_streaming_enabled', 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=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=18034, + serialized_end=18103, ) @@ -5355,8 +6665,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14057, - serialized_end=14082, + serialized_start=18105, + serialized_end=18130, ) @@ -5443,8 +6753,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14085, - serialized_end=14550, + serialized_start=18133, + serialized_end=18598, ) @@ -5467,8 +6777,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14552, - serialized_end=14571, + serialized_start=18600, + serialized_end=18619, ) @@ -5519,8 +6829,91 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14574, - serialized_end=14740, + serialized_start=18622, + serialized_end=18788, +) + + +_CAPTURESINGLEIMAGEREQUEST = _descriptor.Descriptor( + name='CaptureSingleImageRequest', + full_name='Anki.Vector.external_interface.CaptureSingleImageRequest', + 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=18790, + serialized_end=18817, +) + + +_CAPTURESINGLEIMAGERESPONSE = _descriptor.Descriptor( + name='CaptureSingleImageResponse', + full_name='Anki.Vector.external_interface.CaptureSingleImageResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='Anki.Vector.external_interface.CaptureSingleImageResponse.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='frame_time_stamp', full_name='Anki.Vector.external_interface.CaptureSingleImageResponse.frame_time_stamp', 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='image_id', full_name='Anki.Vector.external_interface.CaptureSingleImageResponse.image_id', index=2, + number=3, 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='image_encoding', full_name='Anki.Vector.external_interface.CaptureSingleImageResponse.image_encoding', index=3, + number=4, 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='data', full_name='Anki.Vector.external_interface.CaptureSingleImageResponse.data', index=4, + number=5, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + 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=18820, + serialized_end=19052, ) @@ -5557,8 +6950,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14742, - serialized_end=14795, + serialized_start=19054, + serialized_end=19107, ) @@ -5588,8 +6981,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14797, - serialized_end=14882, + serialized_start=19109, + serialized_end=19194, ) @@ -5647,8 +7040,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=14885, - serialized_end=15035, + serialized_start=19197, + serialized_end=19347, ) @@ -5678,18 +7071,23 @@ extension_ranges=[], oneofs=[ ], - serialized_start=15037, - serialized_end=15128, + serialized_start=19349, + serialized_end=19440, ) _DRIVEWHEELSRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _MOVEHEADRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _MOVELIFTRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_STOPALLMOTORSRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_PLAYANIMATIONTRIGGERREQUEST.fields_by_name['animation_trigger'].message_type = _ANIMATIONTRIGGER _PLAYANIMATIONREQUEST.fields_by_name['animation'].message_type = _ANIMATION _PLAYANIMATIONRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _PLAYANIMATIONRESPONSE.fields_by_name['result'].enum_type = _BEHAVIORRESULTS +_PLAYANIMATIONRESPONSE.fields_by_name['animation'].message_type = _ANIMATION _LISTANIMATIONSRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _LISTANIMATIONSRESPONSE.fields_by_name['animation_names'].message_type = _ANIMATION +_LISTANIMATIONTRIGGERSRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_LISTANIMATIONTRIGGERSRESPONSE.fields_by_name['animation_trigger_names'].message_type = _ANIMATIONTRIGGER _DISPLAYFACEIMAGERGBRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _STATUS.fields_by_name['meet_victor_face_scan_started'].message_type = _MEETVICTORFACESCANSTARTED _STATUS.fields_by_name['meet_victor_face_scan_complete'].message_type = _MEETVICTORFACESCANCOMPLETE @@ -5724,7 +7122,6 @@ _ROBOTOBSERVEDFACE.fields_by_name['right_eye'].message_type = _CLADPOINT _ROBOTOBSERVEDFACE.fields_by_name['nose'].message_type = _CLADPOINT _ROBOTOBSERVEDFACE.fields_by_name['mouth'].message_type = _CLADPOINT -_APPINTENTRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _FACEENROLLMENTCOMPLETED.fields_by_name['result'].enum_type = _FACEENROLLMENTRESULT _CANCELFACEENROLLMENTRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _REQUESTENROLLEDNAMESRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS @@ -5737,6 +7134,12 @@ _DRIVEOFFCHARGERRESPONSE.fields_by_name['result'].enum_type = _BEHAVIORRESULTS _DRIVEONCHARGERRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _DRIVEONCHARGERRESPONSE.fields_by_name['result'].enum_type = _BEHAVIORRESULTS +_FINDFACESRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_FINDFACESRESPONSE.fields_by_name['result'].enum_type = _BEHAVIORRESULTS +_LOOKAROUNDINPLACERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_LOOKAROUNDINPLACERESPONSE.fields_by_name['result'].enum_type = _BEHAVIORRESULTS +_ROLLBLOCKRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_ROLLBLOCKRESPONSE.fields_by_name['result'].enum_type = _BEHAVIORRESULTS _PHOTOSINFORESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _PHOTOSINFORESPONSE.fields_by_name['photo_infos'].message_type = _PHOTOINFO _PHOTORESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS @@ -5744,6 +7147,7 @@ _DELETEPHOTORESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _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 _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 @@ -5759,19 +7163,63 @@ _SETHEADANGLERESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT _SETLIFTHEIGHTRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _SETLIFTHEIGHTRESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT +_TURNTOWARDSFACERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_TURNTOWARDSFACERESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT +_GOTOOBJECTREQUEST.fields_by_name['motion_prof'].message_type = _PATHMOTIONPROFILE +_GOTOOBJECTRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_GOTOOBJECTRESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT +_ROLLOBJECTREQUEST.fields_by_name['motion_prof'].message_type = _PATHMOTIONPROFILE +_ROLLOBJECTRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_ROLLOBJECTRESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT +_POPAWHEELIEREQUEST.fields_by_name['motion_prof'].message_type = _PATHMOTIONPROFILE +_POPAWHEELIERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_POPAWHEELIERESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT +_PICKUPOBJECTREQUEST.fields_by_name['motion_prof'].message_type = _PATHMOTIONPROFILE +_PICKUPOBJECTRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_PICKUPOBJECTRESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT +_PLACEOBJECTONGROUNDHERERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS +_PLACEOBJECTONGROUNDHERERESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT _BATTERYSTATERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _BATTERYSTATERESPONSE.fields_by_name['battery_level'].enum_type = _BATTERYLEVEL _BATTERYSTATERESPONSE.fields_by_name['cube_battery'].message_type = _CUBEBATTERY _CUBEBATTERY.fields_by_name['level'].enum_type = _CUBEBATTERY_CUBEBATTERYLEVEL _CUBEBATTERY_CUBEBATTERYLEVEL.containing_type = _CUBEBATTERY _VERSIONSTATERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS -_NETWORKSTATERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS -_NETWORKSTATERESPONSE.fields_by_name['network_stats'].message_type = _NETWORKSTATS _SAYTEXTRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _SAYTEXTRESPONSE.fields_by_name['state'].enum_type = _SAYTEXTRESPONSE_UTTERANCESTATE _SAYTEXTRESPONSE_UTTERANCESTATE.containing_type = _SAYTEXTRESPONSE _AUDIOSENDMODEREQUEST.fields_by_name['mode'].enum_type = _AUDIOPROCESSINGMODE _AUDIOSENDMODECHANGED.fields_by_name['mode'].enum_type = _AUDIOPROCESSINGMODE +_EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_prepare'].message_type = _EXTERNALAUDIOSTREAMPREPARE +_EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_chunk'].message_type = _EXTERNALAUDIOSTREAMCHUNK +_EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_complete'].message_type = _EXTERNALAUDIOSTREAMCOMPLETE +_EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_cancel'].message_type = _EXTERNALAUDIOSTREAMCANCEL +_EXTERNALAUDIOSTREAMREQUEST.oneofs_by_name['audio_request_type'].fields.append( + _EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_prepare']) +_EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_prepare'].containing_oneof = _EXTERNALAUDIOSTREAMREQUEST.oneofs_by_name['audio_request_type'] +_EXTERNALAUDIOSTREAMREQUEST.oneofs_by_name['audio_request_type'].fields.append( + _EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_chunk']) +_EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_chunk'].containing_oneof = _EXTERNALAUDIOSTREAMREQUEST.oneofs_by_name['audio_request_type'] +_EXTERNALAUDIOSTREAMREQUEST.oneofs_by_name['audio_request_type'].fields.append( + _EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_complete']) +_EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_complete'].containing_oneof = _EXTERNALAUDIOSTREAMREQUEST.oneofs_by_name['audio_request_type'] +_EXTERNALAUDIOSTREAMREQUEST.oneofs_by_name['audio_request_type'].fields.append( + _EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_cancel']) +_EXTERNALAUDIOSTREAMREQUEST.fields_by_name['audio_stream_cancel'].containing_oneof = _EXTERNALAUDIOSTREAMREQUEST.oneofs_by_name['audio_request_type'] +_EXTERNALAUDIOSTREAMRESPONSE.fields_by_name['audio_stream_playback_complete'].message_type = _EXTERNALAUDIOSTREAMPLAYBACKCOMPLETE +_EXTERNALAUDIOSTREAMRESPONSE.fields_by_name['audio_stream_buffer_overrun'].message_type = _EXTERNALAUDIOSTREAMBUFFEROVERRUN +_EXTERNALAUDIOSTREAMRESPONSE.fields_by_name['audio_stream_playback_failyer'].message_type = _EXTERNALAUDIOSTREAMPLAYBACKFAILURE +_EXTERNALAUDIOSTREAMRESPONSE.oneofs_by_name['audio_response_type'].fields.append( + _EXTERNALAUDIOSTREAMRESPONSE.fields_by_name['audio_stream_playback_complete']) +_EXTERNALAUDIOSTREAMRESPONSE.fields_by_name['audio_stream_playback_complete'].containing_oneof = _EXTERNALAUDIOSTREAMRESPONSE.oneofs_by_name['audio_response_type'] +_EXTERNALAUDIOSTREAMRESPONSE.oneofs_by_name['audio_response_type'].fields.append( + _EXTERNALAUDIOSTREAMRESPONSE.fields_by_name['audio_stream_buffer_overrun']) +_EXTERNALAUDIOSTREAMRESPONSE.fields_by_name['audio_stream_buffer_overrun'].containing_oneof = _EXTERNALAUDIOSTREAMRESPONSE.oneofs_by_name['audio_response_type'] +_EXTERNALAUDIOSTREAMRESPONSE.oneofs_by_name['audio_response_type'].fields.append( + _EXTERNALAUDIOSTREAMRESPONSE.fields_by_name['audio_stream_playback_failyer']) +_EXTERNALAUDIOSTREAMRESPONSE.fields_by_name['audio_stream_playback_failyer'].containing_oneof = _EXTERNALAUDIOSTREAMRESPONSE.oneofs_by_name['audio_response_type'] +_MASTERVOLUMEREQUEST.fields_by_name['volume_level'].enum_type = _MASTERVOLUMELEVEL +_MASTERVOLUMERESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _ENABLEMARKERDETECTIONRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _ENABLEFACEDETECTIONRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS _ENABLEMOTIONDETECTIONRESPONSE.fields_by_name['status'].message_type = anki__vector_dot_messaging_dot_response__status__pb2._RESPONSESTATUS @@ -5780,9 +7228,12 @@ _IMAGECHUNK.fields_by_name['image_encoding'].enum_type = _IMAGECHUNK_IMAGEENCODING _IMAGECHUNK_IMAGEENCODING.containing_type = _IMAGECHUNK _CAMERAFEEDRESPONSE.fields_by_name['image_encoding'].enum_type = _IMAGECHUNK_IMAGEENCODING +_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 _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 DESCRIPTOR.message_types_by_name['Animation'] = _ANIMATION DESCRIPTOR.message_types_by_name['DriveWheelsRequest'] = _DRIVEWHEELSREQUEST DESCRIPTOR.message_types_by_name['DriveWheelsResponse'] = _DRIVEWHEELSRESPONSE @@ -5790,10 +7241,15 @@ DESCRIPTOR.message_types_by_name['MoveHeadResponse'] = _MOVEHEADRESPONSE DESCRIPTOR.message_types_by_name['MoveLiftRequest'] = _MOVELIFTREQUEST DESCRIPTOR.message_types_by_name['MoveLiftResponse'] = _MOVELIFTRESPONSE +DESCRIPTOR.message_types_by_name['StopAllMotorsRequest'] = _STOPALLMOTORSREQUEST +DESCRIPTOR.message_types_by_name['StopAllMotorsResponse'] = _STOPALLMOTORSRESPONSE +DESCRIPTOR.message_types_by_name['PlayAnimationTriggerRequest'] = _PLAYANIMATIONTRIGGERREQUEST DESCRIPTOR.message_types_by_name['PlayAnimationRequest'] = _PLAYANIMATIONREQUEST DESCRIPTOR.message_types_by_name['PlayAnimationResponse'] = _PLAYANIMATIONRESPONSE DESCRIPTOR.message_types_by_name['ListAnimationsRequest'] = _LISTANIMATIONSREQUEST DESCRIPTOR.message_types_by_name['ListAnimationsResponse'] = _LISTANIMATIONSRESPONSE +DESCRIPTOR.message_types_by_name['ListAnimationTriggersRequest'] = _LISTANIMATIONTRIGGERSREQUEST +DESCRIPTOR.message_types_by_name['ListAnimationTriggersResponse'] = _LISTANIMATIONTRIGGERSRESPONSE DESCRIPTOR.message_types_by_name['DisplayFaceImageRGBRequest'] = _DISPLAYFACEIMAGERGBREQUEST DESCRIPTOR.message_types_by_name['DisplayFaceImageRGBResponse'] = _DISPLAYFACEIMAGERGBRESPONSE DESCRIPTOR.message_types_by_name['MeetVictorFaceScanStarted'] = _MEETVICTORFACESCANSTARTED @@ -5811,9 +7267,6 @@ DESCRIPTOR.message_types_by_name['CladRect'] = _CLADRECT DESCRIPTOR.message_types_by_name['RobotObservedFace'] = _ROBOTOBSERVEDFACE DESCRIPTOR.message_types_by_name['RobotChangedObservedFaceID'] = _ROBOTCHANGEDOBSERVEDFACEID -DESCRIPTOR.message_types_by_name['AppIntentRequest'] = _APPINTENTREQUEST -DESCRIPTOR.message_types_by_name['AppDisconnected'] = _APPDISCONNECTED -DESCRIPTOR.message_types_by_name['AppIntentResponse'] = _APPINTENTRESPONSE DESCRIPTOR.message_types_by_name['FaceEnrollmentCompleted'] = _FACEENROLLMENTCOMPLETED DESCRIPTOR.message_types_by_name['CancelFaceEnrollmentRequest'] = _CANCELFACEENROLLMENTREQUEST DESCRIPTOR.message_types_by_name['CancelFaceEnrollmentResponse'] = _CANCELFACEENROLLMENTRESPONSE @@ -5833,8 +7286,15 @@ DESCRIPTOR.message_types_by_name['DriveOffChargerResponse'] = _DRIVEOFFCHARGERRESPONSE DESCRIPTOR.message_types_by_name['DriveOnChargerRequest'] = _DRIVEONCHARGERREQUEST DESCRIPTOR.message_types_by_name['DriveOnChargerResponse'] = _DRIVEONCHARGERRESPONSE +DESCRIPTOR.message_types_by_name['FindFacesRequest'] = _FINDFACESREQUEST +DESCRIPTOR.message_types_by_name['FindFacesResponse'] = _FINDFACESRESPONSE +DESCRIPTOR.message_types_by_name['LookAroundInPlaceRequest'] = _LOOKAROUNDINPLACEREQUEST +DESCRIPTOR.message_types_by_name['LookAroundInPlaceResponse'] = _LOOKAROUNDINPLACERESPONSE +DESCRIPTOR.message_types_by_name['RollBlockRequest'] = _ROLLBLOCKREQUEST +DESCRIPTOR.message_types_by_name['RollBlockResponse'] = _ROLLBLOCKRESPONSE DESCRIPTOR.message_types_by_name['WakeWordBegin'] = _WAKEWORDBEGIN DESCRIPTOR.message_types_by_name['WakeWordEnd'] = _WAKEWORDEND +DESCRIPTOR.message_types_by_name['UserIntent'] = _USERINTENT DESCRIPTOR.message_types_by_name['PhotoInfo'] = _PHOTOINFO DESCRIPTOR.message_types_by_name['PhotosInfoRequest'] = _PHOTOSINFOREQUEST DESCRIPTOR.message_types_by_name['PhotosInfoResponse'] = _PHOTOSINFORESPONSE @@ -5849,6 +7309,8 @@ DESCRIPTOR.message_types_by_name['PhotoTaken'] = _PHOTOTAKEN DESCRIPTOR.message_types_by_name['PathMotionProfile'] = _PATHMOTIONPROFILE 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['GoToPoseRequest'] = _GOTOPOSEREQUEST DESCRIPTOR.message_types_by_name['GoToPoseResponse'] = _GOTOPOSERESPONSE DESCRIPTOR.message_types_by_name['DockWithCubeRequest'] = _DOCKWITHCUBEREQUEST @@ -5861,14 +7323,23 @@ DESCRIPTOR.message_types_by_name['SetHeadAngleResponse'] = _SETHEADANGLERESPONSE DESCRIPTOR.message_types_by_name['SetLiftHeightRequest'] = _SETLIFTHEIGHTREQUEST DESCRIPTOR.message_types_by_name['SetLiftHeightResponse'] = _SETLIFTHEIGHTRESPONSE -DESCRIPTOR.message_types_by_name['NetworkStats'] = _NETWORKSTATS +DESCRIPTOR.message_types_by_name['TurnTowardsFaceRequest'] = _TURNTOWARDSFACEREQUEST +DESCRIPTOR.message_types_by_name['TurnTowardsFaceResponse'] = _TURNTOWARDSFACERESPONSE +DESCRIPTOR.message_types_by_name['GoToObjectRequest'] = _GOTOOBJECTREQUEST +DESCRIPTOR.message_types_by_name['GoToObjectResponse'] = _GOTOOBJECTRESPONSE +DESCRIPTOR.message_types_by_name['RollObjectRequest'] = _ROLLOBJECTREQUEST +DESCRIPTOR.message_types_by_name['RollObjectResponse'] = _ROLLOBJECTRESPONSE +DESCRIPTOR.message_types_by_name['PopAWheelieRequest'] = _POPAWHEELIEREQUEST +DESCRIPTOR.message_types_by_name['PopAWheelieResponse'] = _POPAWHEELIERESPONSE +DESCRIPTOR.message_types_by_name['PickupObjectRequest'] = _PICKUPOBJECTREQUEST +DESCRIPTOR.message_types_by_name['PickupObjectResponse'] = _PICKUPOBJECTRESPONSE +DESCRIPTOR.message_types_by_name['PlaceObjectOnGroundHereRequest'] = _PLACEOBJECTONGROUNDHEREREQUEST +DESCRIPTOR.message_types_by_name['PlaceObjectOnGroundHereResponse'] = _PLACEOBJECTONGROUNDHERERESPONSE DESCRIPTOR.message_types_by_name['BatteryStateRequest'] = _BATTERYSTATEREQUEST DESCRIPTOR.message_types_by_name['BatteryStateResponse'] = _BATTERYSTATERESPONSE DESCRIPTOR.message_types_by_name['CubeBattery'] = _CUBEBATTERY DESCRIPTOR.message_types_by_name['VersionStateRequest'] = _VERSIONSTATEREQUEST DESCRIPTOR.message_types_by_name['VersionStateResponse'] = _VERSIONSTATERESPONSE -DESCRIPTOR.message_types_by_name['NetworkStateRequest'] = _NETWORKSTATEREQUEST -DESCRIPTOR.message_types_by_name['NetworkStateResponse'] = _NETWORKSTATERESPONSE DESCRIPTOR.message_types_by_name['SayTextRequest'] = _SAYTEXTREQUEST DESCRIPTOR.message_types_by_name['SayTextResponse'] = _SAYTEXTRESPONSE DESCRIPTOR.message_types_by_name['StimulationInfo'] = _STIMULATIONINFO @@ -5877,6 +7348,17 @@ DESCRIPTOR.message_types_by_name['AudioChunk'] = _AUDIOCHUNK DESCRIPTOR.message_types_by_name['AudioFeedRequest'] = _AUDIOFEEDREQUEST DESCRIPTOR.message_types_by_name['AudioFeedResponse'] = _AUDIOFEEDRESPONSE +DESCRIPTOR.message_types_by_name['ExternalAudioStreamPrepare'] = _EXTERNALAUDIOSTREAMPREPARE +DESCRIPTOR.message_types_by_name['ExternalAudioStreamChunk'] = _EXTERNALAUDIOSTREAMCHUNK +DESCRIPTOR.message_types_by_name['ExternalAudioStreamComplete'] = _EXTERNALAUDIOSTREAMCOMPLETE +DESCRIPTOR.message_types_by_name['ExternalAudioStreamCancel'] = _EXTERNALAUDIOSTREAMCANCEL +DESCRIPTOR.message_types_by_name['ExternalAudioStreamRequest'] = _EXTERNALAUDIOSTREAMREQUEST +DESCRIPTOR.message_types_by_name['ExternalAudioStreamPlaybackComplete'] = _EXTERNALAUDIOSTREAMPLAYBACKCOMPLETE +DESCRIPTOR.message_types_by_name['ExternalAudioStreamPlaybackFailure'] = _EXTERNALAUDIOSTREAMPLAYBACKFAILURE +DESCRIPTOR.message_types_by_name['ExternalAudioStreamBufferOverrun'] = _EXTERNALAUDIOSTREAMBUFFEROVERRUN +DESCRIPTOR.message_types_by_name['ExternalAudioStreamResponse'] = _EXTERNALAUDIOSTREAMRESPONSE +DESCRIPTOR.message_types_by_name['MasterVolumeRequest'] = _MASTERVOLUMEREQUEST +DESCRIPTOR.message_types_by_name['MasterVolumeResponse'] = _MASTERVOLUMERESPONSE DESCRIPTOR.message_types_by_name['EnableMarkerDetectionRequest'] = _ENABLEMARKERDETECTIONREQUEST DESCRIPTOR.message_types_by_name['EnableMarkerDetectionResponse'] = _ENABLEMARKERDETECTIONRESPONSE DESCRIPTOR.message_types_by_name['EnableFaceDetectionRequest'] = _ENABLEFACEDETECTIONREQUEST @@ -5888,10 +7370,14 @@ DESCRIPTOR.message_types_by_name['MirrorModeDisabled'] = _MIRRORMODEDISABLED DESCRIPTOR.message_types_by_name['EnableImageStreamingRequest'] = _ENABLEIMAGESTREAMINGREQUEST DESCRIPTOR.message_types_by_name['EnableImageStreamingResponse'] = _ENABLEIMAGESTREAMINGRESPONSE +DESCRIPTOR.message_types_by_name['IsImageStreamingEnabledRequest'] = _ISIMAGESTREAMINGENABLEDREQUEST +DESCRIPTOR.message_types_by_name['IsImageStreamingEnabledResponse'] = _ISIMAGESTREAMINGENABLEDRESPONSE DESCRIPTOR.message_types_by_name['VisionModesAutoDisabled'] = _VISIONMODESAUTODISABLED DESCRIPTOR.message_types_by_name['ImageChunk'] = _IMAGECHUNK DESCRIPTOR.message_types_by_name['CameraFeedRequest'] = _CAMERAFEEDREQUEST DESCRIPTOR.message_types_by_name['CameraFeedResponse'] = _CAMERAFEEDRESPONSE +DESCRIPTOR.message_types_by_name['CaptureSingleImageRequest'] = _CAPTURESINGLEIMAGEREQUEST +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['SDKInitializationRequest'] = _SDKINITIALIZATIONREQUEST @@ -5905,6 +7391,7 @@ DESCRIPTOR.enum_types_by_name['BatteryLevel'] = _BATTERYLEVEL DESCRIPTOR.enum_types_by_name['AudioConstants'] = _AUDIOCONSTANTS DESCRIPTOR.enum_types_by_name['AudioProcessingMode'] = _AUDIOPROCESSINGMODE +DESCRIPTOR.enum_types_by_name['MasterVolumeLevel'] = _MASTERVOLUMELEVEL _sym_db.RegisterFileDescriptor(DESCRIPTOR) KeepAlivePing = _reflection.GeneratedProtocolMessageType('KeepAlivePing', (_message.Message,), dict( @@ -5914,6 +7401,13 @@ )) _sym_db.RegisterMessage(KeepAlivePing) +AnimationTrigger = _reflection.GeneratedProtocolMessageType('AnimationTrigger', (_message.Message,), dict( + DESCRIPTOR = _ANIMATIONTRIGGER, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.AnimationTrigger) + )) +_sym_db.RegisterMessage(AnimationTrigger) + Animation = _reflection.GeneratedProtocolMessageType('Animation', (_message.Message,), dict( DESCRIPTOR = _ANIMATION, __module__ = 'anki_vector.messaging.messages_pb2' @@ -5963,6 +7457,27 @@ )) _sym_db.RegisterMessage(MoveLiftResponse) +StopAllMotorsRequest = _reflection.GeneratedProtocolMessageType('StopAllMotorsRequest', (_message.Message,), dict( + DESCRIPTOR = _STOPALLMOTORSREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.StopAllMotorsRequest) + )) +_sym_db.RegisterMessage(StopAllMotorsRequest) + +StopAllMotorsResponse = _reflection.GeneratedProtocolMessageType('StopAllMotorsResponse', (_message.Message,), dict( + DESCRIPTOR = _STOPALLMOTORSRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.StopAllMotorsResponse) + )) +_sym_db.RegisterMessage(StopAllMotorsResponse) + +PlayAnimationTriggerRequest = _reflection.GeneratedProtocolMessageType('PlayAnimationTriggerRequest', (_message.Message,), dict( + DESCRIPTOR = _PLAYANIMATIONTRIGGERREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.PlayAnimationTriggerRequest) + )) +_sym_db.RegisterMessage(PlayAnimationTriggerRequest) + PlayAnimationRequest = _reflection.GeneratedProtocolMessageType('PlayAnimationRequest', (_message.Message,), dict( DESCRIPTOR = _PLAYANIMATIONREQUEST, __module__ = 'anki_vector.messaging.messages_pb2' @@ -5991,6 +7506,20 @@ )) _sym_db.RegisterMessage(ListAnimationsResponse) +ListAnimationTriggersRequest = _reflection.GeneratedProtocolMessageType('ListAnimationTriggersRequest', (_message.Message,), dict( + DESCRIPTOR = _LISTANIMATIONTRIGGERSREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ListAnimationTriggersRequest) + )) +_sym_db.RegisterMessage(ListAnimationTriggersRequest) + +ListAnimationTriggersResponse = _reflection.GeneratedProtocolMessageType('ListAnimationTriggersResponse', (_message.Message,), dict( + DESCRIPTOR = _LISTANIMATIONTRIGGERSRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ListAnimationTriggersResponse) + )) +_sym_db.RegisterMessage(ListAnimationTriggersResponse) + DisplayFaceImageRGBRequest = _reflection.GeneratedProtocolMessageType('DisplayFaceImageRGBRequest', (_message.Message,), dict( DESCRIPTOR = _DISPLAYFACEIMAGERGBREQUEST, __module__ = 'anki_vector.messaging.messages_pb2' @@ -6110,27 +7639,6 @@ )) _sym_db.RegisterMessage(RobotChangedObservedFaceID) -AppIntentRequest = _reflection.GeneratedProtocolMessageType('AppIntentRequest', (_message.Message,), dict( - DESCRIPTOR = _APPINTENTREQUEST, - __module__ = 'anki_vector.messaging.messages_pb2' - # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.AppIntentRequest) - )) -_sym_db.RegisterMessage(AppIntentRequest) - -AppDisconnected = _reflection.GeneratedProtocolMessageType('AppDisconnected', (_message.Message,), dict( - DESCRIPTOR = _APPDISCONNECTED, - __module__ = 'anki_vector.messaging.messages_pb2' - # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.AppDisconnected) - )) -_sym_db.RegisterMessage(AppDisconnected) - -AppIntentResponse = _reflection.GeneratedProtocolMessageType('AppIntentResponse', (_message.Message,), dict( - DESCRIPTOR = _APPINTENTRESPONSE, - __module__ = 'anki_vector.messaging.messages_pb2' - # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.AppIntentResponse) - )) -_sym_db.RegisterMessage(AppIntentResponse) - FaceEnrollmentCompleted = _reflection.GeneratedProtocolMessageType('FaceEnrollmentCompleted', (_message.Message,), dict( DESCRIPTOR = _FACEENROLLMENTCOMPLETED, __module__ = 'anki_vector.messaging.messages_pb2' @@ -6264,6 +7772,48 @@ )) _sym_db.RegisterMessage(DriveOnChargerResponse) +FindFacesRequest = _reflection.GeneratedProtocolMessageType('FindFacesRequest', (_message.Message,), dict( + DESCRIPTOR = _FINDFACESREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.FindFacesRequest) + )) +_sym_db.RegisterMessage(FindFacesRequest) + +FindFacesResponse = _reflection.GeneratedProtocolMessageType('FindFacesResponse', (_message.Message,), dict( + DESCRIPTOR = _FINDFACESRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.FindFacesResponse) + )) +_sym_db.RegisterMessage(FindFacesResponse) + +LookAroundInPlaceRequest = _reflection.GeneratedProtocolMessageType('LookAroundInPlaceRequest', (_message.Message,), dict( + DESCRIPTOR = _LOOKAROUNDINPLACEREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.LookAroundInPlaceRequest) + )) +_sym_db.RegisterMessage(LookAroundInPlaceRequest) + +LookAroundInPlaceResponse = _reflection.GeneratedProtocolMessageType('LookAroundInPlaceResponse', (_message.Message,), dict( + DESCRIPTOR = _LOOKAROUNDINPLACERESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.LookAroundInPlaceResponse) + )) +_sym_db.RegisterMessage(LookAroundInPlaceResponse) + +RollBlockRequest = _reflection.GeneratedProtocolMessageType('RollBlockRequest', (_message.Message,), dict( + DESCRIPTOR = _ROLLBLOCKREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.RollBlockRequest) + )) +_sym_db.RegisterMessage(RollBlockRequest) + +RollBlockResponse = _reflection.GeneratedProtocolMessageType('RollBlockResponse', (_message.Message,), dict( + DESCRIPTOR = _ROLLBLOCKRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.RollBlockResponse) + )) +_sym_db.RegisterMessage(RollBlockResponse) + WakeWordBegin = _reflection.GeneratedProtocolMessageType('WakeWordBegin', (_message.Message,), dict( DESCRIPTOR = _WAKEWORDBEGIN, __module__ = 'anki_vector.messaging.messages_pb2' @@ -6278,6 +7828,13 @@ )) _sym_db.RegisterMessage(WakeWordEnd) +UserIntent = _reflection.GeneratedProtocolMessageType('UserIntent', (_message.Message,), dict( + DESCRIPTOR = _USERINTENT, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.UserIntent) + )) +_sym_db.RegisterMessage(UserIntent) + PhotoInfo = _reflection.GeneratedProtocolMessageType('PhotoInfo', (_message.Message,), dict( DESCRIPTOR = _PHOTOINFO, __module__ = 'anki_vector.messaging.messages_pb2' @@ -6376,6 +7933,20 @@ )) _sym_db.RegisterMessage(ActionResult) +CancelActionByIdTagRequest = _reflection.GeneratedProtocolMessageType('CancelActionByIdTagRequest', (_message.Message,), dict( + DESCRIPTOR = _CANCELACTIONBYIDTAGREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.CancelActionByIdTagRequest) + )) +_sym_db.RegisterMessage(CancelActionByIdTagRequest) + +CancelActionByIdTagResponse = _reflection.GeneratedProtocolMessageType('CancelActionByIdTagResponse', (_message.Message,), dict( + DESCRIPTOR = _CANCELACTIONBYIDTAGRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.CancelActionByIdTagResponse) + )) +_sym_db.RegisterMessage(CancelActionByIdTagResponse) + GoToPoseRequest = _reflection.GeneratedProtocolMessageType('GoToPoseRequest', (_message.Message,), dict( DESCRIPTOR = _GOTOPOSEREQUEST, __module__ = 'anki_vector.messaging.messages_pb2' @@ -6460,12 +8031,89 @@ )) _sym_db.RegisterMessage(SetLiftHeightResponse) -NetworkStats = _reflection.GeneratedProtocolMessageType('NetworkStats', (_message.Message,), dict( - DESCRIPTOR = _NETWORKSTATS, +TurnTowardsFaceRequest = _reflection.GeneratedProtocolMessageType('TurnTowardsFaceRequest', (_message.Message,), dict( + DESCRIPTOR = _TURNTOWARDSFACEREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.TurnTowardsFaceRequest) + )) +_sym_db.RegisterMessage(TurnTowardsFaceRequest) + +TurnTowardsFaceResponse = _reflection.GeneratedProtocolMessageType('TurnTowardsFaceResponse', (_message.Message,), dict( + DESCRIPTOR = _TURNTOWARDSFACERESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.TurnTowardsFaceResponse) + )) +_sym_db.RegisterMessage(TurnTowardsFaceResponse) + +GoToObjectRequest = _reflection.GeneratedProtocolMessageType('GoToObjectRequest', (_message.Message,), dict( + DESCRIPTOR = _GOTOOBJECTREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.GoToObjectRequest) + )) +_sym_db.RegisterMessage(GoToObjectRequest) + +GoToObjectResponse = _reflection.GeneratedProtocolMessageType('GoToObjectResponse', (_message.Message,), dict( + DESCRIPTOR = _GOTOOBJECTRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.GoToObjectResponse) + )) +_sym_db.RegisterMessage(GoToObjectResponse) + +RollObjectRequest = _reflection.GeneratedProtocolMessageType('RollObjectRequest', (_message.Message,), dict( + DESCRIPTOR = _ROLLOBJECTREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.RollObjectRequest) + )) +_sym_db.RegisterMessage(RollObjectRequest) + +RollObjectResponse = _reflection.GeneratedProtocolMessageType('RollObjectResponse', (_message.Message,), dict( + DESCRIPTOR = _ROLLOBJECTRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.RollObjectResponse) + )) +_sym_db.RegisterMessage(RollObjectResponse) + +PopAWheelieRequest = _reflection.GeneratedProtocolMessageType('PopAWheelieRequest', (_message.Message,), dict( + DESCRIPTOR = _POPAWHEELIEREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.PopAWheelieRequest) + )) +_sym_db.RegisterMessage(PopAWheelieRequest) + +PopAWheelieResponse = _reflection.GeneratedProtocolMessageType('PopAWheelieResponse', (_message.Message,), dict( + DESCRIPTOR = _POPAWHEELIERESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.PopAWheelieResponse) + )) +_sym_db.RegisterMessage(PopAWheelieResponse) + +PickupObjectRequest = _reflection.GeneratedProtocolMessageType('PickupObjectRequest', (_message.Message,), dict( + DESCRIPTOR = _PICKUPOBJECTREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.PickupObjectRequest) + )) +_sym_db.RegisterMessage(PickupObjectRequest) + +PickupObjectResponse = _reflection.GeneratedProtocolMessageType('PickupObjectResponse', (_message.Message,), dict( + DESCRIPTOR = _PICKUPOBJECTRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.PickupObjectResponse) + )) +_sym_db.RegisterMessage(PickupObjectResponse) + +PlaceObjectOnGroundHereRequest = _reflection.GeneratedProtocolMessageType('PlaceObjectOnGroundHereRequest', (_message.Message,), dict( + DESCRIPTOR = _PLACEOBJECTONGROUNDHEREREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.PlaceObjectOnGroundHereRequest) + )) +_sym_db.RegisterMessage(PlaceObjectOnGroundHereRequest) + +PlaceObjectOnGroundHereResponse = _reflection.GeneratedProtocolMessageType('PlaceObjectOnGroundHereResponse', (_message.Message,), dict( + DESCRIPTOR = _PLACEOBJECTONGROUNDHERERESPONSE, __module__ = 'anki_vector.messaging.messages_pb2' - # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.NetworkStats) + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.PlaceObjectOnGroundHereResponse) )) -_sym_db.RegisterMessage(NetworkStats) +_sym_db.RegisterMessage(PlaceObjectOnGroundHereResponse) BatteryStateRequest = _reflection.GeneratedProtocolMessageType('BatteryStateRequest', (_message.Message,), dict( DESCRIPTOR = _BATTERYSTATEREQUEST, @@ -6502,20 +8150,6 @@ )) _sym_db.RegisterMessage(VersionStateResponse) -NetworkStateRequest = _reflection.GeneratedProtocolMessageType('NetworkStateRequest', (_message.Message,), dict( - DESCRIPTOR = _NETWORKSTATEREQUEST, - __module__ = 'anki_vector.messaging.messages_pb2' - # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.NetworkStateRequest) - )) -_sym_db.RegisterMessage(NetworkStateRequest) - -NetworkStateResponse = _reflection.GeneratedProtocolMessageType('NetworkStateResponse', (_message.Message,), dict( - DESCRIPTOR = _NETWORKSTATERESPONSE, - __module__ = 'anki_vector.messaging.messages_pb2' - # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.NetworkStateResponse) - )) -_sym_db.RegisterMessage(NetworkStateResponse) - SayTextRequest = _reflection.GeneratedProtocolMessageType('SayTextRequest', (_message.Message,), dict( DESCRIPTOR = _SAYTEXTREQUEST, __module__ = 'anki_vector.messaging.messages_pb2' @@ -6572,6 +8206,83 @@ )) _sym_db.RegisterMessage(AudioFeedResponse) +ExternalAudioStreamPrepare = _reflection.GeneratedProtocolMessageType('ExternalAudioStreamPrepare', (_message.Message,), dict( + DESCRIPTOR = _EXTERNALAUDIOSTREAMPREPARE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ExternalAudioStreamPrepare) + )) +_sym_db.RegisterMessage(ExternalAudioStreamPrepare) + +ExternalAudioStreamChunk = _reflection.GeneratedProtocolMessageType('ExternalAudioStreamChunk', (_message.Message,), dict( + DESCRIPTOR = _EXTERNALAUDIOSTREAMCHUNK, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ExternalAudioStreamChunk) + )) +_sym_db.RegisterMessage(ExternalAudioStreamChunk) + +ExternalAudioStreamComplete = _reflection.GeneratedProtocolMessageType('ExternalAudioStreamComplete', (_message.Message,), dict( + DESCRIPTOR = _EXTERNALAUDIOSTREAMCOMPLETE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ExternalAudioStreamComplete) + )) +_sym_db.RegisterMessage(ExternalAudioStreamComplete) + +ExternalAudioStreamCancel = _reflection.GeneratedProtocolMessageType('ExternalAudioStreamCancel', (_message.Message,), dict( + DESCRIPTOR = _EXTERNALAUDIOSTREAMCANCEL, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ExternalAudioStreamCancel) + )) +_sym_db.RegisterMessage(ExternalAudioStreamCancel) + +ExternalAudioStreamRequest = _reflection.GeneratedProtocolMessageType('ExternalAudioStreamRequest', (_message.Message,), dict( + DESCRIPTOR = _EXTERNALAUDIOSTREAMREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ExternalAudioStreamRequest) + )) +_sym_db.RegisterMessage(ExternalAudioStreamRequest) + +ExternalAudioStreamPlaybackComplete = _reflection.GeneratedProtocolMessageType('ExternalAudioStreamPlaybackComplete', (_message.Message,), dict( + DESCRIPTOR = _EXTERNALAUDIOSTREAMPLAYBACKCOMPLETE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ExternalAudioStreamPlaybackComplete) + )) +_sym_db.RegisterMessage(ExternalAudioStreamPlaybackComplete) + +ExternalAudioStreamPlaybackFailure = _reflection.GeneratedProtocolMessageType('ExternalAudioStreamPlaybackFailure', (_message.Message,), dict( + DESCRIPTOR = _EXTERNALAUDIOSTREAMPLAYBACKFAILURE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ExternalAudioStreamPlaybackFailure) + )) +_sym_db.RegisterMessage(ExternalAudioStreamPlaybackFailure) + +ExternalAudioStreamBufferOverrun = _reflection.GeneratedProtocolMessageType('ExternalAudioStreamBufferOverrun', (_message.Message,), dict( + DESCRIPTOR = _EXTERNALAUDIOSTREAMBUFFEROVERRUN, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ExternalAudioStreamBufferOverrun) + )) +_sym_db.RegisterMessage(ExternalAudioStreamBufferOverrun) + +ExternalAudioStreamResponse = _reflection.GeneratedProtocolMessageType('ExternalAudioStreamResponse', (_message.Message,), dict( + DESCRIPTOR = _EXTERNALAUDIOSTREAMRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.ExternalAudioStreamResponse) + )) +_sym_db.RegisterMessage(ExternalAudioStreamResponse) + +MasterVolumeRequest = _reflection.GeneratedProtocolMessageType('MasterVolumeRequest', (_message.Message,), dict( + DESCRIPTOR = _MASTERVOLUMEREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.MasterVolumeRequest) + )) +_sym_db.RegisterMessage(MasterVolumeRequest) + +MasterVolumeResponse = _reflection.GeneratedProtocolMessageType('MasterVolumeResponse', (_message.Message,), dict( + DESCRIPTOR = _MASTERVOLUMERESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.MasterVolumeResponse) + )) +_sym_db.RegisterMessage(MasterVolumeResponse) + EnableMarkerDetectionRequest = _reflection.GeneratedProtocolMessageType('EnableMarkerDetectionRequest', (_message.Message,), dict( DESCRIPTOR = _ENABLEMARKERDETECTIONREQUEST, __module__ = 'anki_vector.messaging.messages_pb2' @@ -6649,6 +8360,20 @@ )) _sym_db.RegisterMessage(EnableImageStreamingResponse) +IsImageStreamingEnabledRequest = _reflection.GeneratedProtocolMessageType('IsImageStreamingEnabledRequest', (_message.Message,), dict( + DESCRIPTOR = _ISIMAGESTREAMINGENABLEDREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.IsImageStreamingEnabledRequest) + )) +_sym_db.RegisterMessage(IsImageStreamingEnabledRequest) + +IsImageStreamingEnabledResponse = _reflection.GeneratedProtocolMessageType('IsImageStreamingEnabledResponse', (_message.Message,), dict( + DESCRIPTOR = _ISIMAGESTREAMINGENABLEDRESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.IsImageStreamingEnabledResponse) + )) +_sym_db.RegisterMessage(IsImageStreamingEnabledResponse) + VisionModesAutoDisabled = _reflection.GeneratedProtocolMessageType('VisionModesAutoDisabled', (_message.Message,), dict( DESCRIPTOR = _VISIONMODESAUTODISABLED, __module__ = 'anki_vector.messaging.messages_pb2' @@ -6677,6 +8402,20 @@ )) _sym_db.RegisterMessage(CameraFeedResponse) +CaptureSingleImageRequest = _reflection.GeneratedProtocolMessageType('CaptureSingleImageRequest', (_message.Message,), dict( + DESCRIPTOR = _CAPTURESINGLEIMAGEREQUEST, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.CaptureSingleImageRequest) + )) +_sym_db.RegisterMessage(CaptureSingleImageRequest) + +CaptureSingleImageResponse = _reflection.GeneratedProtocolMessageType('CaptureSingleImageResponse', (_message.Message,), dict( + DESCRIPTOR = _CAPTURESINGLEIMAGERESPONSE, + __module__ = 'anki_vector.messaging.messages_pb2' + # @@protoc_insertion_point(class_scope:Anki.Vector.external_interface.CaptureSingleImageResponse) + )) +_sym_db.RegisterMessage(CaptureSingleImageResponse) + SetEyeColorRequest = _reflection.GeneratedProtocolMessageType('SetEyeColorRequest', (_message.Message,), dict( DESCRIPTOR = _SETEYECOLORREQUEST, __module__ = 'anki_vector.messaging.messages_pb2' diff --git a/anki_vector/messaging/response_status.proto b/anki_vector/messaging/response_status.proto index 3ac0fc3..f8a658f 100644 --- a/anki_vector/messaging/response_status.proto +++ b/anki_vector/messaging/response_status.proto @@ -18,15 +18,24 @@ syntax = "proto3"; package Anki.Vector.external_interface; +// A shared response message sent back as part of most requests. +// This will indicate the generic state of the request. message ResponseStatus { enum StatusCode { UNKNOWN = 0; - RESPONSE_RECEIVED = 1; // The message has completed as expected - REQUEST_PROCESSING = 2; // The message has been sent to the robot + // The message has completed as expected. + RESPONSE_RECEIVED = 1; + // The message has been sent to the robot. + REQUEST_PROCESSING = 2; + // The message has been handled successfully at the interface level. OK = 3; - FORBIDDEN = 100; // The user was not authorizied - NOT_FOUND = 101; // The requested attribute was not found - ERROR_UPDATE_IN_PROGRESS = 102; // Currently updating values from another call + // The user was not authorizied. + FORBIDDEN = 100; + // The requested attribute was not found. + NOT_FOUND = 101; + // Currently updating values from another call. + ERROR_UPDATE_IN_PROGRESS = 102; } + // The generic status code to give high-level insight into the progress of a given message. StatusCode code = 1; } diff --git a/anki_vector/messaging/settings.proto b/anki_vector/messaging/settings.proto index d3d35cf..36fc97e 100644 --- a/anki_vector/messaging/settings.proto +++ b/anki_vector/messaging/settings.proto @@ -19,17 +19,3 @@ syntax = "proto3"; package Anki.Vector.external_interface; -enum ApiVersion { - INVALID = 0; - LATEST = 1; -} - -enum Volume { - MUTE = 0; - LOW = 1; - MEDIUM_LOW = 2; - MEDIUM = 3; - MEDIUM_HIGH = 4; - HIGH = 5; -} - diff --git a/anki_vector/messaging/settings_pb2.py b/anki_vector/messaging/settings_pb2.py index f5dff10..b59b818 100644 --- a/anki_vector/messaging/settings_pb2.py +++ b/anki_vector/messaging/settings_pb2.py @@ -3,7 +3,6 @@ import sys _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf.internal import enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection @@ -20,83 +19,11 @@ name='anki_vector/messaging/settings.proto', package='Anki.Vector.external_interface', syntax='proto3', - serialized_pb=_b('\n$anki_vector/messaging/settings.proto\x12\x1e\x41nki.Vector.external_interface*%\n\nApiVersion\x12\x0b\n\x07INVALID\x10\x00\x12\n\n\x06LATEST\x10\x01*R\n\x06Volume\x12\x08\n\x04MUTE\x10\x00\x12\x07\n\x03LOW\x10\x01\x12\x0e\n\nMEDIUM_LOW\x10\x02\x12\n\n\x06MEDIUM\x10\x03\x12\x0f\n\x0bMEDIUM_HIGH\x10\x04\x12\x08\n\x04HIGH\x10\x05\x62\x06proto3') + serialized_pb=_b('\n$anki_vector/messaging/settings.proto\x12\x1e\x41nki.Vector.external_interfaceb\x06proto3') ) -_APIVERSION = _descriptor.EnumDescriptor( - name='ApiVersion', - full_name='Anki.Vector.external_interface.ApiVersion', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='INVALID', index=0, number=0, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='LATEST', index=1, number=1, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=72, - serialized_end=109, -) -_sym_db.RegisterEnumDescriptor(_APIVERSION) - -ApiVersion = enum_type_wrapper.EnumTypeWrapper(_APIVERSION) -_VOLUME = _descriptor.EnumDescriptor( - name='Volume', - full_name='Anki.Vector.external_interface.Volume', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='MUTE', index=0, number=0, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='LOW', index=1, number=1, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MEDIUM_LOW', index=2, number=2, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MEDIUM', index=3, number=3, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MEDIUM_HIGH', index=4, number=4, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='HIGH', index=5, number=5, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=111, - serialized_end=193, -) -_sym_db.RegisterEnumDescriptor(_VOLUME) - -Volume = enum_type_wrapper.EnumTypeWrapper(_VOLUME) -INVALID = 0 -LATEST = 1 -MUTE = 0 -LOW = 1 -MEDIUM_LOW = 2 -MEDIUM = 3 -MEDIUM_HIGH = 4 -HIGH = 5 -DESCRIPTOR.enum_types_by_name['ApiVersion'] = _APIVERSION -DESCRIPTOR.enum_types_by_name['Volume'] = _VOLUME _sym_db.RegisterFileDescriptor(DESCRIPTOR) diff --git a/anki_vector/messaging/shared.proto b/anki_vector/messaging/shared.proto index f8e130a..d193d80 100644 --- a/anki_vector/messaging/shared.proto +++ b/anki_vector/messaging/shared.proto @@ -56,10 +56,12 @@ 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; ConnectionResponse connection_response = 13; MirrorModeDisabled mirror_mode_disabled = 16; VisionModesAutoDisabled vision_modes_auto_disabled = 17; + UserIntent user_intent = 19; } } diff --git a/anki_vector/messaging/shared_pb2.py b/anki_vector/messaging/shared_pb2.py index 0310af8..a713482 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\"\x86\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\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\"\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') , 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=2319, - serialized_end=2359, + serialized_start=2386, + serialized_end=2426, ) _sym_db.RegisterEnumDescriptor(_USERAUTHENTICATIONRESPONSE_CODE) @@ -289,6 +289,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='user_intent', full_name='Anki.Vector.external_interface.Event.user_intent', index=13, + number=19, 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=[ ], @@ -305,7 +312,7 @@ index=0, containing_type=None, fields=[]), ], serialized_start=656, - serialized_end=1686, + serialized_end=1753, ) @@ -335,8 +342,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1688, - serialized_end=1714, + serialized_start=1755, + serialized_end=1781, ) @@ -383,8 +390,8 @@ name='list_type', full_name='Anki.Vector.external_interface.EventRequest.list_type', index=0, containing_type=None, fields=[]), ], - serialized_start=1717, - serialized_end=1899, + serialized_start=1784, + serialized_end=1966, ) @@ -421,8 +428,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1902, - serialized_end=2041, + serialized_start=1969, + serialized_end=2108, ) @@ -459,8 +466,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2043, - serialized_end=2116, + serialized_start=2110, + serialized_end=2183, ) @@ -505,8 +512,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2119, - serialized_end=2359, + serialized_start=2186, + serialized_end=2426, ) _PROTOCOLVERSIONRESPONSE.fields_by_name['result'].enum_type = _PROTOCOLVERSIONRESPONSE_RESULT @@ -525,6 +532,7 @@ _EVENT.fields_by_name['connection_response'].message_type = _CONNECTIONRESPONSE _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.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'] @@ -564,6 +572,9 @@ _EVENT.oneofs_by_name['event_type'].fields.append( _EVENT.fields_by_name['vision_modes_auto_disabled']) _EVENT.fields_by_name['vision_modes_auto_disabled'].containing_oneof = _EVENT.oneofs_by_name['event_type'] +_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'] _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/motors.py b/anki_vector/motors.py index 0816b6a..4f38642 100644 --- a/anki_vector/motors.py +++ b/anki_vector/motors.py @@ -34,7 +34,7 @@ async def set_wheel_motors(self, """Tell Vector to move his wheels / treads at a given speed. The wheels will continue to move at that speed until commanded to drive - at a new speed. + at a new speed, or if :meth:`stop_all_motors` is called. To unlock the wheel track, call `set_wheel_motors(0, 0)`. @@ -105,3 +105,23 @@ async def set_lift_motor(self, """ set_lift_request = protocol.MoveLiftRequest(speed_rad_per_sec=speed) return await self.grpc_interface.MoveLift(set_lift_request) + + @connection.on_connection_thread() + async def stop_all_motors(self): + """Tell Vector to stop all motors. + + .. testcode:: + + import anki_vector + import time + + with anki_vector.Robot() as robot: + robot.motors.set_wheel_motors(25, 50) + + # wait a short time to observe the motors moving + time.sleep(0.5) + + robot.motors.stop_all_motors() + """ + stop_all_motors_request = protocol.StopAllMotorsRequest() + return await self.grpc_interface.StopAllMotors(stop_all_motors_request) diff --git a/anki_vector/nav_map.py b/anki_vector/nav_map.py index 3db4c34..9d77fc1 100755 --- a/anki_vector/nav_map.py +++ b/anki_vector/nav_map.py @@ -17,6 +17,9 @@ Vector builds a memory map of the navigable world around him as he drives around. This is mostly based on where objects are seen (the cubes, charger, and any custom objects), and also includes where Vector detects cliffs/drops, and +visible edges (e.g. sudden changes in color). + +This differs from a standard occupancy map in that it doesn't deal with probabilities of occupancy, but instead encodes what type of content is there. To use the map you must first call :meth:`anki_vector.nav_map.NavMapComponent.init_nav_map_feed` @@ -36,6 +39,7 @@ from . import util from .events import Events +from .exceptions import VectorException from .messaging import protocol @@ -344,7 +348,7 @@ class NavMapComponent(util.Component): # Make sure Vector drives around so the nav map will update robot.behavior.drive_off_charger() robot.motors.set_wheel_motors(-100, 100) - latest_nav_map = robot.nav_map.latest_nav_map + latest_nav_map = robot.nav_map.latest_nav_map :param robot: A reference to the owner Robot object. """ @@ -371,7 +375,7 @@ def latest_nav_map(self) -> NavMapGrid: latest_nav_map = robot.nav_map.latest_nav_map """ if not self._nav_map_feed_task or self._nav_map_feed_task.done(): - raise Exception("Nav map not initialized") + raise VectorException("Nav map not initialized. Check that Robot parameter enable_nav_map_feed is set to True.") return self._latest_nav_map def init_nav_map_feed(self, frequency: float = 0.5) -> None: diff --git a/anki_vector/objects.py b/anki_vector/objects.py index 4888df0..cf44929 100644 --- a/anki_vector/objects.py +++ b/anki_vector/objects.py @@ -33,7 +33,7 @@ markers to your own objects for Vector to recognize by printing them out from the online documentation. They will be detected as :class:`CustomObject` instances. -Vector connects to his Light Cubes with BLE. +Vector connects to his Light Cube with BLE. """ # __all__ should order by constants, event classes, other classes, functions. @@ -43,7 +43,7 @@ 'FixedCustomObject', 'LightCube', 'ObservableObject'] # TODO Curious why events like the following aren't listed? At least some do seem to be supported in other parts of anki_vector. -# EvtObjectTapped, EvtObjectConnectChanged, EvtObjectConnected, EvtObjectLocated, EvtObjectMoving, EvtObjectMovingStarted +# EvtObjectTapped, EvtObjectConnectChanged, EvtObjectConnected, EvtObjectLocated, EvtObjectMoving, EvtObjectMovingStarted, EvtObjectMovingStopped import collections @@ -69,6 +69,33 @@ class EvtObjectObserved(): # pylint: disable=too-few-public-methods See EvtObjectAppeared if you only want to know when an object first becomes visible. + .. testcode:: + + import time + + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def handle_object_observed(robot, event_type, event): + # This will be called whenever an EvtObjectObserved is dispatched - + # whenever an Object comes into view. + print(f"--------- Vector observed an object --------- \\n{event.obj}") + + with anki_vector.Robot(default_logging=False, + show_viewer=True, + show_3d_viewer=True, + enable_nav_map_feed=True) as robot: + # Place Vector's cube where he can see it + + robot.events.subscribe(handle_object_observed, Events.object_observed) + + # If necessary, move Vector's Head and Lift down + robot.behavior.set_lift_height(0.0) + robot.behavior.set_head_angle(degrees(0.0)) + + time.sleep(3.0) + :param obj: The object that was observed :param image_rect: An :class:`anki_vector.util.ImageRect`: defining where the object is within Vector's camera view :param pose: The :class:`anki_vector.util.Pose`: defining the position and rotation of the object @@ -92,6 +119,33 @@ class EvtObjectAppeared(): # pylint: disable=too-few-public-methods For continuous tracking information about a visible object, see EvtObjectObserved. + .. testcode:: + + import time + + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def handle_object_appeared(robot, event_type, event): + # This will be called whenever an EvtObjectAppeared is dispatched - + # whenever an Object comes into view. + print(f"--------- Vector started seeing an object --------- \\n{event.obj}") + + with anki_vector.Robot(default_logging=False, + show_viewer=True, + show_3d_viewer=True, + enable_nav_map_feed=True) as robot: + # Place Vector's cube where he can see it + + robot.events.subscribe(handle_object_appeared, Events.object_appeared) + + # If necessary, move Vector's Head and Lift down + robot.behavior.set_lift_height(0.0) + robot.behavior.set_head_angle(degrees(0.0)) + + time.sleep(3.0) + :param obj: The object that is starting to be observed :param image_rect: An :class:`anki_vector.util.ImageRect`: defining where the object is within Vector's camera view :param pose: The :class:`anki_vector.util.Pose`: defining the position and rotation of the object @@ -106,6 +160,33 @@ def __init__(self, obj, image_rect: util.ImageRect, pose: util.Pose): class EvtObjectDisappeared(): # pylint: disable=too-few-public-methods """Triggered whenever an object that was previously being observed is no longer visible. + .. testcode:: + + import time + + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def handle_object_disappeared(robot, event_type, event): + # This will be called whenever an EvtObjectDisappeared is dispatched - + # whenever an Object goes out of view. + print(f"--------- Vector stopped seeing an object --------- \\n{event.obj}") + + with anki_vector.Robot(default_logging=False, + show_viewer=True, + show_3d_viewer=True, + enable_nav_map_feed=True) as robot: + # Place Vector's cube where he can see it + + robot.events.subscribe(handle_object_disappeared, Events.object_disappeared) + + # If necessary, move Vector's Head and Lift down + robot.behavior.set_lift_height(0.0) + robot.behavior.set_head_angle(degrees(0.0)) + + time.sleep(3.0) + :param obj: The object that is no longer being observed """ @@ -354,65 +435,51 @@ def __init__(self, robot, **kw): self._factory_id: str = None #: Subscribe to relevant events - self.robot.events.subscribe( - self._on_object_connection_state_changed, - Events.object_connection_state) + self.robot.events.subscribe(self._on_object_connection_state_changed, + Events.object_connection_state) - self.robot.events.subscribe( - self._on_object_moved, - Events.object_moved) + self.robot.events.subscribe(self._on_object_moved, + Events.object_moved) - self.robot.events.subscribe( - self._on_object_stopped_moving, - Events.object_stopped_moving) + self.robot.events.subscribe(self._on_object_stopped_moving, + Events.object_stopped_moving) - self.robot.events.subscribe( - self._on_object_up_axis_changed, - Events.object_up_axis_changed) + self.robot.events.subscribe(self._on_object_up_axis_changed, + Events.object_up_axis_changed) - self.robot.events.subscribe( - self._on_object_tapped, - Events.object_tapped) + self.robot.events.subscribe(self._on_object_tapped, + Events.object_tapped) - self.robot.events.subscribe( - self._on_object_observed, - Events.robot_observed_object) + self.robot.events.subscribe(self._on_object_observed, + Events.robot_observed_object) - self.robot.events.subscribe( - self._on_object_connection_lost, - Events.cube_connection_lost) + self.robot.events.subscribe(self._on_object_connection_lost, + Events.cube_connection_lost) #### Public Methods #### def teardown(self): """All faces will be torn down by the world when no longer needed.""" - self.robot.events.unsubscribe( - self._on_object_connection_state_changed, - Events.object_connection_state) + self.robot.events.unsubscribe(self._on_object_connection_state_changed, + Events.object_connection_state) - self.robot.events.unsubscribe( - self._on_object_moved, - Events.object_moved) + self.robot.events.unsubscribe(self._on_object_moved, + Events.object_moved) - self.robot.events.unsubscribe( - self._on_object_stopped_moving, - Events.object_stopped_moving) + self.robot.events.unsubscribe(self._on_object_stopped_moving, + Events.object_stopped_moving) - self.robot.events.unsubscribe( - self._on_object_up_axis_changed, - Events.object_up_axis_changed) + self.robot.events.unsubscribe(self._on_object_up_axis_changed, + Events.object_up_axis_changed) - self.robot.events.unsubscribe( - self._on_object_tapped, - Events.object_tapped) + self.robot.events.unsubscribe(self._on_object_tapped, + Events.object_tapped) - self.robot.events.unsubscribe( - self._on_object_observed, - Events.robot_observed_object) + self.robot.events.unsubscribe(self._on_object_observed, + Events.robot_observed_object) - self.robot.events.unsubscribe( - self._on_object_connection_lost, - Events.cube_connection_lost) + self.robot.events.unsubscribe(self._on_object_connection_lost, + Events.cube_connection_lost) @connection.on_connection_thread() async def set_light_corners(self, @@ -875,7 +942,7 @@ def descriptive_name(self) -> str: cube = robot.world.connected_light_cube print(f"{cube.descriptive_name}") """ - return "{0} id={1} factory_id={2} is_connected={3}".format(self.__class__.__name__, self._object_id, self._factory_id, self._is_connected) + return f"{self.__class__.__name__}\nid={self._object_id}\nfactory_id={self._factory_id}\nis_connected={self._is_connected}" @property def object_id(self) -> int: @@ -908,7 +975,7 @@ def object_id(self, value: str): #### Private Event Handlers #### - def _on_object_connection_state_changed(self, _, msg): + def _on_object_connection_state_changed(self, _robot, _event_type, msg): if msg.object_type == LIGHT_CUBE_1_TYPE: self._object_id = msg.object_id @@ -923,7 +990,7 @@ def _on_object_connection_state_changed(self, _, msg): self.logger.debug('Object disconnected: %s', self) self._is_connected = msg.connected - def _on_object_moved(self, _, msg): + def _on_object_moved(self, _robot, _event_type, msg): if msg.object_id == self._object_id: now = time.time() started_moving = not self._is_moving @@ -938,7 +1005,7 @@ def _on_object_moved(self, _, msg): else: self.logger.warning('An object not currently tracked by the world moved with id {0}'.format(msg.object_id)) - async def _on_object_stopped_moving(self, _, msg): + async def _on_object_stopped_moving(self, _robot, _event_type, msg): if msg.object_id == self._object_id: now = time.time() self._last_event_time = now @@ -954,7 +1021,7 @@ async def _on_object_stopped_moving(self, _, msg): else: self.logger.warning('An object not currently tracked by the world stopped moving with id {0}'.format(msg.object_id)) - def _on_object_up_axis_changed(self, _, msg): + def _on_object_up_axis_changed(self, _robot, _event_type, msg): if msg.object_id == self._object_id: now = time.time() @@ -965,7 +1032,7 @@ def _on_object_up_axis_changed(self, _, msg): else: self.logger.warning('Up Axis changed on an object not currently tracked by the world with id {0}'.format(msg.object_id)) - def _on_object_tapped(self, _, msg): + def _on_object_tapped(self, _robot, _event_type, msg): if msg.object_id == self._object_id: now = time.time() @@ -975,7 +1042,7 @@ def _on_object_tapped(self, _, msg): else: self.logger.warning('Tapped an object not currently tracked by the world with id {0}'.format(msg.object_id)) - def _on_object_observed(self, _, msg): + def _on_object_observed(self, _robot, _event_type, msg): if msg.object_id == self._object_id: pose = util.Pose(x=msg.pose.x, y=msg.pose.y, z=msg.pose.z, @@ -990,7 +1057,7 @@ def _on_object_observed(self, _, msg): self._on_observed(pose, image_rect, msg.timestamp) - def _on_object_connection_lost(self, _, msg): + def _on_object_connection_lost(self, _robot, _event_type, msg): if msg.object_id == self._object_id: self._is_connected = False @@ -1018,18 +1085,16 @@ def __init__(self, robot, object_id: int, **kw): self._object_id = object_id - self.robot.events.subscribe( - self._on_object_observed, - Events.robot_observed_object) + self.robot.events.subscribe(self._on_object_observed, + Events.robot_observed_object) #### Public Methods #### def teardown(self): """All objects will be torn down by the world when the world closes.""" - self.robot.events.unsubscribe( - self._on_object_observed, - Events.robot_observed_object) + self.robot.events.unsubscribe(self._on_object_observed, + Events.robot_observed_object) #### Properties #### @property @@ -1061,9 +1126,26 @@ def object_id(self, value: str): self.logger.debug("Setting object_id for %s to %s", self.__class__, value) self._object_id = value + @property + def descriptive_name(self) -> str: + """A descriptive name for this ObservableObject instance. + + Note: Sub-classes should override this to add any other relevant info + for that object type. + + .. testcode:: + + import anki_vector + + with anki_vector.Robot() as robot: + if robot.world.charger: + print(f"{robot.world.charger.descriptive_name}") + """ + return f"{self.__class__.__name__} id={self._object_id}" + #### Private Methods #### - def _on_object_observed(self, _, msg): + def _on_object_observed(self, _robot, _event_type, msg): if msg.object_id == self._object_id: pose = util.Pose(x=msg.pose.x, y=msg.pose.y, z=msg.pose.z, @@ -1084,6 +1166,8 @@ class CustomObjectArchetype(): This defined object is given a size in the x,y and z axis. The dimensions of the markers on the object are also defined. + See :class:`CustomObjectMarkers`. + When the robot observes custom objects, they will be linked to these archetypes. These can be created using the methods :meth:`~anki_vector.world.World.define_custom_box`, @@ -1112,7 +1196,9 @@ def __init__(self, @property def custom_type(self) -> protocol.CustomType: - """id of this archetype on the robot + """id of this archetype on the robot. + + See :class:`CustomObjectMarkers`. .. testcode:: @@ -1127,6 +1213,8 @@ def custom_type(self) -> protocol.CustomType: def x_size_mm(self) -> float: """Size of this object in its X axis, in millimeters. + See :class:`CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -1140,6 +1228,8 @@ def x_size_mm(self) -> float: def y_size_mm(self) -> float: """Size of this object in its Y axis, in millimeters. + See :class:`CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -1153,6 +1243,8 @@ def y_size_mm(self) -> float: def z_size_mm(self) -> float: """Size of this object in its Z axis, in millimeters. + See :class:`CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -1166,6 +1258,8 @@ def z_size_mm(self) -> float: def marker_width_mm(self) -> float: """Width in millimeters of the marker on this object. + See :class:`CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -1179,6 +1273,8 @@ def marker_width_mm(self) -> float: def marker_height_mm(self) -> float: """Height in millimeters of the marker on this object. + See :class:`CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -1190,7 +1286,10 @@ def marker_height_mm(self) -> float: @property def is_unique(self) -> bool: - """True if there should only be one of this object type in the world.""" + """True if there should only be one of this object type in the world. + + See :class:`CustomObjectMarkers`. + """ return self._is_unique #### Private Methods #### @@ -1215,6 +1314,8 @@ class CustomObject(ObservableObject): :meth:`~anki_vector.world.World.define_custom_box`. :meth:`~anki_vector.world.World.define_custom_cube`, or :meth:`~anki_vector.world.World.define_custom_wall` + + See :class:`CustomObjectMarkers`. """ def __init__(self, @@ -1226,18 +1327,19 @@ def __init__(self, self._object_id = object_id self._archetype = archetype - self.robot.events.subscribe( - self._on_object_observed, - Events.robot_observed_object) + self.robot.events.subscribe(self._on_object_observed, + Events.robot_observed_object) #### Public Methods #### def teardown(self): - """All objects will be torn down by the world when no longer needed.""" + """All objects will be torn down by the world when no longer needed. + + See :class:`CustomObjectMarkers`. + """ - self.robot.events.unsubscribe( - self._on_object_observed, - Events.robot_observed_object) + self.robot.events.unsubscribe(self._on_object_observed, + Events.robot_observed_object) #### Properties #### @@ -1247,6 +1349,8 @@ def object_id(self) -> int: This value can only be assigned once as it is static on the robot. + See :class:`CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -1280,6 +1384,8 @@ def object_id(self, value: str): def archetype(self) -> CustomObjectArchetype: """Archetype defining this custom object's properties. + See :class:`CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -1302,6 +1408,8 @@ def archetype(self) -> CustomObjectArchetype: def descriptive_name(self) -> str: """A descriptive name for this CustomObject instance. + See :class:`CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -1318,8 +1426,7 @@ def descriptive_name(self) -> str: for obj in robot.world.visible_custom_objects: print('custom object seen with name: {0}'.format(obj.descriptive_name)) """ - # Specialization of ObservableObject's method to include the object type. - return "%s id=%d" % (self.archetype.object_type.name, self.object_id) + return "%s id=%d" % (self.__class__.__name__, self.object_id) #### Private Methods #### @@ -1330,7 +1437,7 @@ def _repr_values(self): 'z_size_mm={archetype.z_size_mm:.1f} ' 'is_unique={archetype.is_unique}'.format(archetype=self._archetype)) - def _on_object_observed(self, _, msg): + def _on_object_observed(self, _robot, _event_type, msg): if msg.object_id == self._object_id: pose = util.Pose(x=msg.pose.x, y=msg.pose.y, z=msg.pose.z, @@ -1364,6 +1471,8 @@ class CustomObjectTypes(): # pylint: disable=too-few-public-methods :meth:`anki_vector.world.World.define_custom_cube`, and :meth:`anki_vector.world.World.define_custom_wall` + See :class:`CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -1456,6 +1565,8 @@ class CustomObjectMarkers(): # pylint: disable=too-few-public-methods :meth:`anki_vector.world.World.define_custom_cube`, and :meth:`anki_vector.world.World.define_custom_wall` + See :class:`CustomObject`. + .. testcode:: import anki_vector diff --git a/anki_vector/opengl/__init__.py b/anki_vector/opengl/__init__.py index 0db3dc8..bc9c542 100644 --- a/anki_vector/opengl/__init__.py +++ b/anki_vector/opengl/__init__.py @@ -49,7 +49,8 @@ def main(close_event: mp.Event, nav_map_queue: mp.Queue, world_frame_queue: mp.Queue, extra_render_function_queue: mp.Queue, - user_data_queue: mp.Queue): + user_data_queue: mp.Queue, + show_viewer_controls: bool = True): """Run the 3D Viewer window. This is intended to run on a background process. .. code-block:: python @@ -87,13 +88,15 @@ def main(close_event: mp.Event, :type extra_render_function_queue: multiprocessing.Queue :param user_data_queue: A queue that may be used outside the SDK to pass information to the viewer process. May be used by ``extra_render_function_queue`` functions. + :param show_viewer_controls: Specifies whether to draw controls on the view. """ viewer = opengl_viewer.OpenGLViewer(close_event, input_intent_queue, nav_map_queue, world_frame_queue, extra_render_function_queue, - user_data_queue) + user_data_queue, + show_viewer_controls=show_viewer_controls) viewer.run() diff --git a/anki_vector/opengl/opengl.py b/anki_vector/opengl/opengl.py index b950578..b387e07 100644 --- a/anki_vector/opengl/opengl.py +++ b/anki_vector/opengl/opengl.py @@ -50,6 +50,7 @@ from anki_vector import util +# TODO Move to exceptions.py class InvalidOpenGLGlutImplementation(ImportError): """Raised by OpenGLViewer if no valid GLUT implementation available.""" @@ -59,10 +60,9 @@ def raise_opengl_or_pillow_import_error(opengl_import_exc): if isinstance(opengl_import_exc, InvalidOpenGLGlutImplementation): raise NotImplementedError('GLUT (OpenGL Utility Toolkit) is not available:\n%s' % opengl_import_exc) - else: - 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) + 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) try: diff --git a/anki_vector/opengl/opengl_vector.py b/anki_vector/opengl/opengl_vector.py index 138fab4..702cb27 100644 --- a/anki_vector/opengl/opengl_vector.py +++ b/anki_vector/opengl/opengl_vector.py @@ -74,7 +74,7 @@ LIFT_PIVOT_HEIGHT_MM = 45.0 #: Angle of the lift in the object's initial default pose. -LIFT_ANGLE_IN_DEFAULT_POSE = -11.36 +LIFT_ANGLE_IN_DEFAULT_POSE = -0.1136 #: Pivot offset for where the fork rotates around itself FORK_PIVOT_X = 3.0 @@ -321,6 +321,7 @@ def display(self, pose: util.Pose, head_angle: util.Angle, lift_position: util.D angle_radians = math.asin(sin_angle) lift_angle = -(angle_radians - LIFT_ANGLE_IN_DEFAULT_POSE) + lift_angle_degrees = math.degrees(lift_angle) glPushMatrix() glEnable(GL_LIGHTING) @@ -334,7 +335,7 @@ def display(self, pose: util.Pose, head_angle: util.Angle, lift_position: util.D glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) self._display_vector_body() - self._display_vector_lift(lift_angle) + self._display_vector_lift(lift_angle_degrees) self._display_vector_head(head_angle_degrees) glDisable(GL_LIGHTING) @@ -550,9 +551,15 @@ def __init__(self, custom_object, is_fixed: bool): super().__init__(custom_object) self.is_fixed = is_fixed - self.x_size_mm = custom_object.archetype.x_size_mm - self.y_size_mm = custom_object.archetype.y_size_mm - self.z_size_mm = custom_object.archetype.z_size_mm + + if self.is_fixed: + self.x_size_mm = custom_object.x_size_mm + self.y_size_mm = custom_object.y_size_mm + self.z_size_mm = custom_object.z_size_mm + else: + self.x_size_mm = custom_object.archetype.x_size_mm + self.y_size_mm = custom_object.archetype.y_size_mm + self.z_size_mm = custom_object.archetype.z_size_mm class RobotRenderFrame(): # pylint: disable=too-few-public-methods @@ -580,8 +587,10 @@ class WorldRenderFrame(): # pylint: disable=too-few-public-methods defined in it's world class. """ - def __init__(self, robot): + def __init__(self, robot, connecting_to_cube): + self.connected_cube = robot.world.connected_light_cube is not None + self.connecting_to_cube = connecting_to_cube self.robot_frame = RobotRenderFrame(robot) self.cube_frames: List[CubeRenderFrame] = [] @@ -601,3 +610,11 @@ def __init__(self, robot): is_fixed = isinstance(obj, FixedCustomObject) if is_custom or is_fixed: self.custom_object_frames.append(CustomObjectRenderFrame(obj, is_fixed)) + + def cube_connected(self): + '''Is there a light cube connected to Vector''' + return self.connected_cube + + def cube_connecting(self): + '''Is there a current attempt to connect to a light cube''' + return self.connecting_to_cube diff --git a/anki_vector/opengl/opengl_viewer.py b/anki_vector/opengl/opengl_viewer.py index 43a38ec..e18f9b1 100644 --- a/anki_vector/opengl/opengl_viewer.py +++ b/anki_vector/opengl/opengl_viewer.py @@ -26,7 +26,6 @@ with anki_vector.Robot(show_viewer=True, show_3d_viewer=True, - enable_camera_feed=True, enable_face_detection=True, enable_custom_object_detection=True, enable_nav_map_feed=True) as robot: @@ -94,11 +93,12 @@ class _RobotControlIntents(): # pylint: disable=too-few-public-methods """ def __init__(self, left_wheel_speed=0.0, right_wheel_speed=0.0, - lift_speed=0.0, head_speed=0.0): + lift_speed=0.0, head_speed=0.0, connect_to_light_block=False): self.left_wheel_speed = left_wheel_speed self.right_wheel_speed = right_wheel_speed self.lift_speed = lift_speed self.head_speed = head_speed + self.connect_to_light_block = connect_to_light_block def _draw_text(font, input_str, x, y, line_height=16, r=1.0, g=1.0, b=1.0): @@ -316,8 +316,8 @@ def _on_mouse_move(self, x, y): left_button = self._is_mouse_down.get(GLUT_LEFT_BUTTON, False) # For laptop and other 1-button mouse users, treat 'x' key as a right mouse button too - right_button = (self._is_mouse_down.get(GLUT_RIGHT_BUTTON, False) or - self._is_key_pressed.get(b'x', False)) + right_button = (self._is_mouse_down.get(GLUT_RIGHT_BUTTON, False) + or self._is_key_pressed.get(b'x', False)) MOUSE_SPEED_SCALAR = 1.0 # general scalar for all mouse movement sensitivity MOUSE_ROTATE_SCALAR = 0.025 # additional scalar for rotation sensitivity @@ -369,8 +369,10 @@ def get_intent_direction(key1, key2): lift_speed = 4.0 * lift_dir * speed_scalar head_speed = head_dir * speed_scalar + connect_block = self._is_key_pressed.get(b'c', False) + control_intents = _RobotControlIntents(left_wheel_speed, right_wheel_speed, - lift_speed, head_speed) + lift_speed, head_speed, connect_block) self._input_intent_queue.put(control_intents, True) def _idle(self): @@ -488,6 +490,8 @@ def __init__(self, 'R, F: Lift up, down', 'T, G: Head up, down', '', + 'C: Connect to LightCube', + '', 'LMB: Rotate camera', 'RMB: Move camera', 'LMB + RMB: Move camera up/down', @@ -587,15 +591,21 @@ def _render_world_frame(self, world_frame: opengl_vector.WorldRenderFrame): robot_view.display(robot_frame.pose, robot_frame.head_angle, robot_frame.lift_position) if self.show_controls: - self._draw_controls() + self._draw_controls(world_frame.cube_connected(), world_frame.cube_connecting()) - def _draw_controls(self): + def _draw_controls(self, cube_connected, cube_connecting): try: GLUT_BITMAP_9_BY_15 except NameError: pass else: _draw_text(GLUT_BITMAP_9_BY_15, self._instructions, x=10, y=10) + if cube_connecting: + _draw_text(GLUT_BITMAP_9_BY_15, "", x=600, y=10, r=0.75, g=0.5, b=0.0) + elif cube_connected: + _draw_text(GLUT_BITMAP_9_BY_15, "", x=600, y=10, r=0.0, g=0.85, b=0.0) + else: + _draw_text(GLUT_BITMAP_9_BY_15, "", x=600, y=10, r=0.75, g=0.75, b=0.75) def _render_3d_view(self, window: opengl.OpenGLWindow): """Renders 3d objects to an openGL window diff --git a/anki_vector/proximity.py b/anki_vector/proximity.py index 9f93937..be0404e 100755 --- a/anki_vector/proximity.py +++ b/anki_vector/proximity.py @@ -44,14 +44,13 @@ class ProximitySensorData: def __init__(self, proto_data: protocol.ProxData): self._distance = util.Distance(distance_mm=proto_data.distance_mm) self._signal_quality = proto_data.signal_quality - self._is_in_valid_range = proto_data.is_in_valid_range - self._is_valid_signal_quality = proto_data.is_valid_signal_quality + self._unobstructed = proto_data.unobstructed + self._found_object = proto_data.found_object self._is_lift_in_fov = proto_data.is_lift_in_fov - self._is_too_pitched = proto_data.is_too_pitched @property @util.block_while_none() - def distance(self) -> float: + def distance(self) -> util.Distance: """The distance between the sensor and a detected object .. testcode:: @@ -83,34 +82,31 @@ def signal_quality(self) -> float: @property @util.block_while_none() - def is_in_valid_range(self) -> bool: - """Whether or not the engine considers the detected signal is close enough - to be considered useful. Past a certain threshold, distance readings - become unreliable. + def unobstructed(self) -> bool: + """The sensor has confirmed it has not detected anything up to its max range. .. testcode:: import anki_vector with anki_vector.Robot() as robot: - is_in_valid_range = robot.proximity.last_sensor_reading.is_in_valid_range + unobstructed = robot.proximity.last_sensor_reading.unobstructed """ - return self._is_in_valid_range + return self._unobstructed @property @util.block_while_none() - def is_valid_signal_quality(self) -> bool: - """Whether the engine considers the detected signal to be reliable enough - to be considered an object in proximity. + def found_object(self) -> bool: + """The sensor detected an object in the valid operating range. .. testcode:: import anki_vector with anki_vector.Robot() as robot: - is_valid_signal_quality = robot.proximity.last_sensor_reading.is_valid_signal_quality + found_object = robot.proximity.last_sensor_reading.found_object """ - return self._is_valid_signal_quality + return self._found_object @property @util.block_while_none() @@ -128,37 +124,6 @@ def is_lift_in_fov(self) -> bool: """ return self._is_lift_in_fov - @property - @util.block_while_none() - def is_too_pitched(self) -> bool: - """Whether the engine considers the robot to be tilted too much up or down - for the time-of-flight data to usefully describe obstacles in the driving - plane. - - .. testcode:: - - import anki_vector - - with anki_vector.Robot() as robot: - is_too_pitched = robot.proximity.last_sensor_reading.is_too_pitched - """ - return self._is_too_pitched - - @property - @util.block_while_none() - def is_valid(self) -> bool: - """Comprehensive judgment of whether the reported distance is useful for - object proximity detection. - - .. testcode:: - - import anki_vector - - with anki_vector.Robot() as robot: - is_valid = robot.proximity.last_sensor_reading.is_valid - """ - return self._is_in_valid_range and self._is_valid_signal_quality and not self._is_lift_in_fov and not self._is_too_pitched - class ProximityComponent(util.Component): """Maintains the most recent proximity sensor data @@ -176,18 +141,17 @@ class ProximityComponent(util.Component): with anki_vector.Robot() as robot: proximity_data = robot.proximity.last_sensor_reading if proximity_data is not None: - print('Proximity distance: {0}, engine considers useful: {1}'.format(proximity_data.distance, proximity_data.is_valid)) + print('Proximity distance: {0}'.format(proximity_data.distance)) """ def __init__(self, robot): super().__init__(robot) - self._last_valid_sensor_reading = None self._last_sensor_reading = None # Subscribe to a callback that updates the robot's local properties - which includes proximity data. self._robot.events.subscribe(self._on_robot_state, Events.robot_state, - on_connection_thread=True) + _on_connection_thread=True) def close(self): """Closing the touch component will unsubscribe from robot state updates.""" @@ -208,22 +172,5 @@ def last_sensor_reading(self) -> ProximitySensorData: """ return self._last_sensor_reading - @property - @util.block_while_none() - def last_valid_sensor_reading(self) -> ProximitySensorData: - """:class:`anki_vector.proximity.ProximitySensorData`: The last reported sensor data - which is considered useful for object detection. - - ..code-block :: - - import anki_vector - - with anki_vector.Robot() as robot: - last_valid_sensor_reading = robot.proximity.last_valid_sensor_reading - """ - return self._last_valid_sensor_reading - - def _on_robot_state(self, _, msg): + def _on_robot_state(self, _robot, _event_type, msg): self._last_sensor_reading = ProximitySensorData(msg.prox_data) - if self._last_sensor_reading.is_valid: - self._last_valid_sensor_reading = self._last_sensor_reading diff --git a/anki_vector/reserve_control/__main__.py b/anki_vector/reserve_control/__main__.py new file mode 100755 index 0000000..364904f --- /dev/null +++ b/anki_vector/reserve_control/__main__.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +""" Reserve SDK Behavior Control + +While this script runs, other SDK scripts may run and Vector will not perform most +default behaviors before/after they complete. This will keep Vector still. + +High priority behaviors like returning to the charger in a low battery situation, +or retreating from a cliff will still take precedence. +""" + +from anki_vector import behavior, util + + +def hold_control(): + args = util.parse_command_args() + with behavior.ReserveBehaviorControl(args.serial): + input("Vector behavior control reserved for SDK. Hit 'Enter' to release control.") + + +if __name__ == "__main__": + hold_control() diff --git a/anki_vector/robot.py b/anki_vector/robot.py index 12fbeb1..77ccae8 100755 --- a/anki_vector/robot.py +++ b/anki_vector/robot.py @@ -13,23 +13,33 @@ # limitations under the License. """ -The main robot class for managing Vector. +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__ = ['AsyncRobot', 'Robot'] +__all__ = ['Robot', 'AsyncRobot'] import concurrent -import configparser import functools -from pathlib import Path from . import (animation, audio, behavior, camera, - connection, events, exceptions, faces, - motors, nav_map, screen, photos, proximity, - status, touch, util, viewer, vision, world) + 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: @@ -50,7 +60,7 @@ class Robot: # Create the robot connection with anki_vector.Robot() as robot: # Run your commands - robot.anim.play_animation("anim_turn_left_01") + 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. @@ -64,13 +74,15 @@ class Robot: # Connect to the Robot robot.connect() # Run your commands - robot.anim.play_animation("anim_turn_left_01") + 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 @@ -78,37 +90,50 @@ class Robot: 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_list: Get the list of animations available at startup. + :param cache_animation_lists: Get the list of animation triggers and animations available at startup. :param enable_face_detection: Turn on face detection. - :param enable_camera_feed: Turn camera feed on/off. + :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: Render camera feed on/off. - :param show_3d_viewer: Render camera feed on/off. - :param requires_behavior_control: Request control of Vector's behavior system.""" + :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_list: bool = True, + cache_animation_lists: bool = True, enable_face_detection: bool = False, - enable_camera_feed: 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, - requires_behavior_control: bool = True): + 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 = {**self._read_configuration(serial), **config} + 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"] @@ -125,8 +150,8 @@ def __init__(self, '{"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.Connection(self._name, ':'.join([self._ip, self._port]), self._cert_file, self._guid, requires_behavior_control=requires_behavior_control) - self._events = events.EventHandler() + 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 @@ -147,8 +172,9 @@ def __init__(self, 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_list = cache_animation_list + self.cache_animation_lists = cache_animation_lists # Robot state/sensor data self._pose: util.Pose = None @@ -165,150 +191,114 @@ def __init__(self, self._localized_to_object_id: float = None self._last_image_time_stamp: float = None self._status: status.RobotStatus = status.RobotStatus() - self.pending = [] - self._enable_camera_feed = enable_camera_feed 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 - if show_viewer and not enable_camera_feed: - self.logger.warning("enable_camera_feed should be True for viewer to render correctly.") - self._enable_camera_feed = True 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 - def _read_configuration(self, serial: str) -> dict: - """Open the default conf file, and read it into a :class:`configparser.ConfigParser` - - :param serial: Vector's serial number - """ - 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 Exception('\n\nCould 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 len(sections) == 1: - serial = sections[0] - self.logger.warning("No serial number provided. Automatically selecting {}".format(serial)) - elif serial is None: - raise Exception('\n\nFound multiple robot serial numbers. Please provide the serial number of the Robot you want to control.\n' - 'Example: ./01_hello_world.py --serial {robot_serial_number}') - - serial = serial.lower() - config = {k.lower(): v for k, v in parser.items()} - try: - dict_entry = config[serial] - except KeyError: - raise Exception('\n\nCould not find matching robot info for given serial number: {}. Please check your serial number is correct.\n' - 'Example: ./01_hello_world.py --serial {{robot_serial_number}}'.format(serial)) - - return dict_entry - @property def force_async(self) -> bool: - """A reference to the Robot object instance.""" + """A flag used to determine if this is a :class:`Robot` or :class:`AsyncRobot`.""" return self._force_async @property - def conn(self) -> connection.Connection: - """A reference to the Connection instance.""" + 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 EventHandler instance.""" + """A reference to the :class:`~anki_vector.events.EventHandler` instance.""" return self._events @property def anim(self) -> animation.AnimationComponent: - """A reference to the AnimationComponent instance.""" + """A reference to the :class:`~anki_vector.animation.AnimationComponent` instance.""" if self._anim is None: - raise exceptions.VectorNotReadyException("AnimationComponent is not yet initialized") + raise VectorNotReadyException("AnimationComponent is not yet initialized") return self._anim @property def audio(self) -> audio.AudioComponent: - """The audio instance used to control Vector's audio feed.""" - - print("\n\nNote: Audio stream is not yet supported and does not yet come from Vector's microphones.\n\n") + """The audio instance used to control Vector's microphone feed and speaker playback.""" if self._audio is None: - raise exceptions.VectorNotReadyException("AudioComponent is not yet initialized") + raise VectorNotReadyException("AudioComponent is not yet initialized") return self._audio @property def behavior(self) -> behavior.BehaviorComponent: - """A reference to the BehaviorComponent instance.""" + """A reference to the :class:`~anki_vector.behavior.BehaviorComponent` instance.""" return self._behavior @property def camera(self) -> camera.CameraComponent: - """The camera instance used to control Vector's camera feed. + """The :class:`~anki_vector.camera.CameraComponent` instance used to control Vector's camera feed. .. testcode:: import anki_vector - with anki_vector.Robot(enable_camera_feed=True) as robot: + with anki_vector.Robot() as robot: + robot.camera.init_camera_feed() image = robot.camera.latest_image - image.show() + image.raw_image.show() """ if self._camera is None: - raise exceptions.VectorNotReadyException("CameraComponent is not yet initialized") + raise VectorNotReadyException("CameraComponent is not yet initialized") return self._camera @property def faces(self) -> faces.FaceComponent: - """A reference to the FaceComponent instance.""" + """A reference to the :class:`~anki_vector.faces.FaceComponent` instance.""" if self._faces is None: - raise exceptions.VectorNotReadyException("FaceComponent is not yet initialized") + raise VectorNotReadyException("FaceComponent is not yet initialized") return self._faces @property def motors(self) -> motors.MotorComponent: - """A reference to the MotorComponent instance.""" + """A reference to the :class:`~anki_vector.motors.MotorComponent` instance.""" if self._motors is None: - raise exceptions.VectorNotReadyException("MotorComponent is not yet initialized") + raise VectorNotReadyException("MotorComponent is not yet initialized") return self._motors @property def nav_map(self) -> nav_map.NavMapComponent: - """A reference to the NavMapComponent instance.""" + """A reference to the :class:`~anki_vector.nav_map.NavMapComponent` instance.""" if self._nav_map is None: - raise exceptions.VectorNotReadyException("NavMapComponent is not yet initialized") + raise VectorNotReadyException("NavMapComponent is not yet initialized") return self._nav_map @property def screen(self) -> screen.ScreenComponent: - """A reference to the ScreenComponent instance.""" + """A reference to the :class:`~anki_vector.screen.ScreenComponent` instance.""" if self._screen is None: - raise exceptions.VectorNotReadyException("ScreenComponent is not yet initialized") + raise VectorNotReadyException("ScreenComponent is not yet initialized") return self._screen @property def photos(self) -> photos.PhotographComponent: - """A reference to the PhotographComponent instance.""" + """A reference to the :class:`~anki_vector.photos.PhotographComponent` instance.""" if self._photos is None: - raise exceptions.VectorNotReadyException("PhotographyComponent is not yet initialized") + raise VectorNotReadyException("PhotographyComponent is not yet initialized") return self._photos @property def proximity(self) -> proximity.ProximityComponent: - """Component containing state related to object proximity detection. + """:class:`~anki_vector.proximity.ProximityComponent` containing state related to object proximity detection. - ..code-block :: + .. code-block:: python import anki_vector with anki_vector.Robot() as robot: - proximity_data = robot.proximity.last_valid_sensor_reading + proximity_data = robot.proximity.last_sensor_reading if proximity_data is not None: print(proximity_data.distance) """ @@ -316,7 +306,7 @@ def proximity(self) -> proximity.ProximityComponent: @property def touch(self) -> touch.TouchComponent: - """Component containing state related to object touch detection. + """:class:`~anki_vector.touch.TouchComponent` containing state related to object touch detection. .. testcode:: @@ -328,7 +318,7 @@ def touch(self) -> touch.TouchComponent: @property def viewer(self) -> ViewerComponent: - """The viewer instance used to render Vector's camera feed. + """The :class:`~anki_vector.viewer.ViewerComponent` instance used to render Vector's camera feed. .. testcode:: @@ -336,21 +326,21 @@ def viewer(self) -> ViewerComponent: import anki_vector - with anki_vector.Robot(show_viewer=True) as robot: + with anki_vector.Robot() as robot: # Render video for 5 seconds - robot.viewer.show_video() + robot.viewer.show() time.sleep(5) # Disable video render and camera feed for 5 seconds - robot.viewer.stop_video() + robot.viewer.close() """ if self._viewer is None: - raise exceptions.VectorNotReadyException("ViewerComponent is not yet initialized") + raise VectorNotReadyException("ViewerComponent is not yet initialized") return self._viewer @property def viewer_3d(self) -> Viewer3DComponent: - """The 3D viewer instance used to render Vector's navigation map. + """The :class:`~anki_vector.viewer.Viewer3DComponent` instance used to render Vector's navigation map. .. testcode:: @@ -363,12 +353,12 @@ def viewer_3d(self) -> Viewer3DComponent: time.sleep(5) """ if self._viewer_3d is None: - raise exceptions.VectorNotReadyException("Viewer3DComponent is not yet initialized") + raise VectorNotReadyException("Viewer3DComponent is not yet initialized") return self._viewer_3d @property def vision(self) -> vision.VisionComponent: - """Component containing functionality related to vision based object detection. + """:class:`~anki_vector.vision.VisionComponent` containing functionality related to vision based object detection. .. testcode:: @@ -380,9 +370,9 @@ def vision(self) -> vision.VisionComponent: @property def world(self) -> world.World: - """A reference to the World instance, or None if the WorldComponent is not yet initialized.""" + """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 exceptions.VectorNotReadyException("WorldComponent is not yet initialized") + raise VectorNotReadyException("WorldComponent is not yet initialized") return self._world @property @@ -510,8 +500,19 @@ def carrying_object_id(self) -> int: .. 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: - current_carrying_object_id = robot.carrying_object_id + 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 @@ -600,40 +601,10 @@ def enable_audio_feed(self) -> bool: @enable_audio_feed.setter def enable_audio_feed(self, enable) -> None: self._enable_audio_feed = enable - if self.enable_audio_feed: - self.audio.init_audio_feed() - - @property - def enable_camera_feed(self) -> bool: - """The camera feed enabled/disabled - - :getter: Returns whether the camera feed is enabled - :setter: Enable/disable the camera feed - - .. testcode:: - - import asyncio - import time - - import anki_vector - - with anki_vector.Robot(enable_camera_feed=True) as robot: - time.sleep(5) - robot.enable_camera_feed = False - time.sleep(5) - """ - return self._enable_camera_feed - - @enable_camera_feed.setter - def enable_camera_feed(self, enable) -> None: - self._enable_camera_feed = enable - if self.enable_camera_feed: - self.camera.init_camera_feed() - else: - self.camera.close_camera_feed() + # TODO add audio feed enablement when ready # Unpack streamed data to robot's internal properties - def _unpack_robot_state(self, _, msg): + 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, @@ -661,7 +632,7 @@ def connect(self, timeout: int = 10) -> None: robot = anki_vector.Robot() robot.connect() - robot.anim.play_animation("anim_turn_left_01") + robot.anim.play_animation_trigger("GreetAfterLongTime") robot.disconnect() :param timeout: The time to allow for a connection before a @@ -672,9 +643,8 @@ def connect(self, timeout: int = 10) -> None: # Initialize components self._anim = animation.AnimationComponent(self) - # self._audio = audio.AudioComponent(self) # TODO turn on + self._audio = audio.AudioComponent(self) self._behavior = behavior.BehaviorComponent(self) - self._camera = camera.CameraComponent(self) self._faces = faces.FaceComponent(self) self._motors = motors.MotorComponent(self) self._nav_map = nav_map.NavMapComponent(self) @@ -686,24 +656,23 @@ def connect(self, timeout: int = 10) -> None: 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_list: - # Load animations so they are ready to play when requested + 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() - # Start audio feed - if self.enable_audio_feed: - self.audio.init_audio_feed() - - # Start camera feed - if self.enable_camera_feed: - self.camera.init_camera_feed() + # TODO enable audio feed when ready # Start rendering camera feed if self._show_viewer: - self.viewer.show_video() + self.camera.init_camera_feed() + self.viewer.show() if self._show_3d_viewer: self.viewer_3d.show() @@ -713,7 +682,7 @@ def connect(self, timeout: int = 10) -> None: # 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=False) + 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) @@ -723,11 +692,14 @@ def connect(self, timeout: int = 10) -> None: # 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) + _on_connection_thread=True) - # access the pose to prove it has gotten back from the event stream once. - while not self.pose: - pass + # 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. @@ -737,24 +709,26 @@ def disconnect(self) -> None: import anki_vector robot = anki_vector.Robot() robot.connect() - robot.anim.play_animation("anim_turn_left_01") + robot.anim.play_animation_trigger("GreetAfterLongTime") robot.disconnect() """ if self.conn.requires_behavior_control: self.vision.close() # Stop rendering video - self.viewer.stop_video() + self.viewer.close() + # Stop rendering 3d video self.viewer_3d.close() + # Shutdown camera feed self.camera.close_camera_feed() - self._enable_camera_feed = False - # Shutdown audio feed - if self._audio is not None: - self._audio.close_audio_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() @@ -771,33 +745,58 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.disconnect() - @connection.on_connection_thread(requires_control=False) + @on_connection_thread(requires_control=False) async def get_battery_state(self) -> protocol.BatteryStateResponse: """Check the current state of the robot and cube batteries. - Vector is considered fully-charged above 4.1 volts. At 3.6V, the robot is approaching low charge. + 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: - Battery_level values are as follows: - | Low = 1: 3.6V or less. If on charger, 4V or less. - | Nominal = 2 - | Full = 3: This state can only be achieved when Vector is on the charger. + +-------+---------+---------------------------------------------------------------+ + | 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's suggested charger time: {0}".format(battery_state.suggested_charger_sec)) + 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) - @connection.on_connection_thread(requires_control=False) + @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. @@ -806,46 +805,13 @@ async def get_version_state(self) -> protocol.VersionStateResponse: 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) - @connection.on_connection_thread(requires_control=False) - async def get_network_state(self) -> protocol.NetworkStateResponse: - """Get the network information for Vector. - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - network_state = robot.get_network_state() - """ - get_network_state_request = protocol.NetworkStateRequest() - return await self.conn.grpc_interface.NetworkState(get_network_state_request) - - @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. - - .. testcode:: - - import anki_vector - with anki_vector.Robot() as robot: - robot.say_text("Hello World") - - :param text: The words for Vector to say. - :param use_vector_voice: Whether to use Vector's robot voice - (otherwise, he uses a generic human male voice). - :param duration_scalar: Adjust the relative duration of the - generated text to speech audio. - - :return: object that provides the status and utterance state - """ - say_text_request = protocol.SayTextRequest(text=text, - use_vector_voice=use_vector_voice, - duration_scalar=duration_scalar) - return await self.conn.grpc_interface.SayText(say_text_request) - class AsyncRobot(Robot): """The AsyncRobot object is just like the Robot object, but allows multiple commands @@ -858,10 +824,20 @@ class AsyncRobot(Robot): .. testcode:: import anki_vector + from anki_vector.util import degrees + # Create the robot connection with anki_vector.AsyncRobot() as robot: - # Run your commands - robot.anim.play_animation("anim_turn_left_01").result() + # 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. @@ -869,16 +845,52 @@ class AsyncRobot(Robot): .. testcode:: import anki_vector + from anki_vector.util import degrees + # Create a Robot object robot = anki_vector.AsyncRobot() # Connect to Vector robot.connect() - # Run your commands - robot.anim.play_animation("anim_turn_left_01").result() + # 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() - :param serial: Vector's serial number. Used to identify which Vector configuration to load. + 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": ""}` @@ -887,21 +899,19 @@ class AsyncRobot(Robot): 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 enable_camera_feed: Turn camera feed on/off. + :param estimate_facial_expression: Turn estimating facial expression on/off. :param enable_audio_feed: Turn audio feed on/off. - :param show_viewer: Render camera feed on/off. - :param requires_behavior_control: Request control of Vector's behavior system.""" + :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 - - # TODO Should be private? Better method name? If not private, Add docstring and sample code - def add_pending(self, task): - self.pending += [task] - - # TODO Should be private? Better method name? If not private, Add docstring and sample code - def remove_pending(self, task): - self.pending = [x for x in self.pending if x is not task] diff --git a/anki_vector/screen.py b/anki_vector/screen.py index 0f4f019..7e938fd 100644 --- a/anki_vector/screen.py +++ b/anki_vector/screen.py @@ -143,6 +143,7 @@ async def set_screen_with_image_data(self, image_data: bytes, duration_sec: floa .. testcode:: import anki_vector + import time try: from PIL import Image @@ -155,7 +156,10 @@ async def set_screen_with_image_data(self, image_data: bytes, duration_sec: floa # Convert the image to the format used by the Screen screen_data = anki_vector.screen.convert_image_to_screen_data(image_file) - robot.screen.set_screen_with_image_data(screen_data, 4.0) + + duration_s = 4.0 + robot.screen.set_screen_with_image_data(screen_data, duration_s) + time.sleep(duration_s) :param image_data: A :class:`bytes` object representing all of the pixels (16bit color in rgb565 format) :param duration_sec: The number of seconds the image should remain on Vector's face. @@ -182,9 +186,12 @@ def set_screen_to_color(self, solid_color: color.Color, duration_sec: float, int .. testcode:: import anki_vector + import time with anki_vector.Robot() as robot: - robot.screen.set_screen_to_color(anki_vector.color.Color(rgb=[255, 128, 0]), duration_sec=1.0) + duration_s = 4.0 + robot.screen.set_screen_to_color(anki_vector.color.Color(rgb=[255, 128, 0]), duration_sec=duration_s) + time.sleep(duration_s) :param solid_color: Desired color to set Vector's Screen. :param duration_sec: The number of seconds the color should remain on Vector's face. diff --git a/anki_vector/status.py b/anki_vector/status.py index f3166bb..95f2ad9 100644 --- a/anki_vector/status.py +++ b/anki_vector/status.py @@ -116,6 +116,11 @@ def is_docking_to_marker(self) -> bool: def is_picked_up(self) -> bool: """True if Vector is currently picked up (in the air). + If :py:attr:`is_being_held` is true, then :py:attr:`is_picked_up` is always True. + + :py:attr:`is_picked_up` uses the IMU data to determine if the robot is not on a stable surface with his treads down. + If the robot is on its side, :py:attr:`is_picked_up` is True. + .. testcode:: import anki_vector @@ -287,6 +292,9 @@ def are_wheels_moving(self) -> bool: def is_being_held(self) -> bool: """True if Vector is being held. + :py:attr:`is_being_held` uses the IMU to look for tiny motions + that suggest the robot is actively being held in someone's hand. + .. testcode:: import anki_vector diff --git a/anki_vector/touch.py b/anki_vector/touch.py index f0faed6..fd5c5cc 100755 --- a/anki_vector/touch.py +++ b/anki_vector/touch.py @@ -95,7 +95,7 @@ def __init__(self, robot): # Subscribe to a callback that updates the robot's local properties - which includes touch data. self._robot.events.subscribe(self._on_robot_state, Events.robot_state, - on_connection_thread=True) + _on_connection_thread=True) def close(self): """Closing the touch component will unsubscribe from robot state updates.""" @@ -115,5 +115,5 @@ def last_sensor_reading(self) -> TouchSensorData: """ return self._last_sensor_reading - def _on_robot_state(self, _, msg): + def _on_robot_state(self, _robot, _event_type, msg): self._last_sensor_reading = TouchSensorData(msg.touch_data) diff --git a/anki_vector/user_intent.py b/anki_vector/user_intent.py new file mode 100755 index 0000000..ca7c533 --- /dev/null +++ b/anki_vector/user_intent.py @@ -0,0 +1,186 @@ +# Copyright (c) 2019 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. + +""" +Class and enumeration related to voice commands received by Vector. + +When under SDK behavior control, recognized voice commands will be sent as +events. SDK users can respond with their own scripted actions. + +""" + +# __all__ should order by constants, event classes, other classes, functions. +__all__ = ['UserIntentEvent', 'UserIntent'] + +from enum import Enum + + +class UserIntentEvent(Enum): + """List of UserIntent events available to the SDK. + + Vector's voice recognition allows for variation in + grammar and word selection, so the examples are not + the only way to invoke the voice commands. + + This list reflect only the voice commands available + to the SDK, as some are not available for development + use.""" + #: example "How old are you?" + character_age = 0 + #: example "Check the timer." + check_timer = 1 + #: example "Go explore." + explore_start = 2 + #: example "Stop the timer." + global_stop = 3 + #: example "Goodbye!" + greeting_goodbye = 4 + #: example "Good morning!" + greeting_goodmorning = 5 + #: example "Hello!" + greeting_hello = 6 + #: example "I hate you." + imperative_abuse = 7 + #: example "Yes." + imperative_affirmative = 8 + #: example "I'm sorry." + imperative_apology = 9 + #: example "Come here." + imperative_come = 10 + #: example "Dance." + imperative_dance = 11 + #: example "Fetch your cube." + imperative_fetchcube = 12 + #: example "Find your cube." + imperative_findcube = 13 + #: example "Look at me." + imperative_lookatme = 14 + #: example "I love you." + imperative_love = 15 + #: example "Good Robot." + imperative_praise = 16 + #: example "No." + imperative_negative = 17 + #: example "Bad Robot." + imperative_scold = 18 + #: example "Volume 2." + imperative_volumelevel = 19 + #: example "Volume up." + imperative_volumeup = 20 + #: example "Volume down." + imperative_volumedown = 21 + #: example "Go forward." + movement_forward = 22 + #: example "Go backward." + movement_backward = 23 + #: example "Turn left." + movement_turnleft = 24 + #: example "Turn right." + movement_turnright = 25 + #: example "Turn around." + movement_turnaround = 26 + #: example "I have a question." + knowledge_question = 27 + #: example "What's my name?" + names_ask = 28 + #: example "Play a game." + play_anygame = 29 + #: example "Play a trick." + play_anytrick = 30 + #: example "Let's play Blackjack." + play_blackjack = 31 + #: example "Fist bump." + play_fistbump = 32 + #: example "Pick up your cube." + play_pickupcube = 33 + #: example "Pop a wheelie." + play_popawheelie = 34 + #: example "Roll your cube." + play_rollcube = 35 + #: example "Happy holidays!" + seasonal_happyholidays = 36 + #: example "Happy new year!" + seasonal_happynewyear = 37 + #: example "Set timer for 10 minutes" + set_timer = 38 + #: example "What time is it?" + show_clock = 39 + #: example "Take a selfie." + take_a_photo = 40 + #: example "What is the weather report?" + weather_response = 41 + + +class UserIntent: + """Class for containing voice command information from the event stream. + This class, and the contained :class:`UserIntentEvent` include all of the + voice commands that the SDK can intercept. + + Some UserIntents include information returned from the cloud and used + when evaluating the voice commands. This information can be parsed as + a JSON formatted string. + + .. testcode:: + + import json + import threading + + import anki_vector + from anki_vector.events import Events + from anki_vector.user_intent import UserIntent, UserIntentEvent + + def on_user_intent(robot, event_type, event, done): + user_intent = UserIntent(event) + if user_intent.intent_event is UserIntentEvent.weather_response: + data = json.loads(user_intent.intent_data) + print(f"Weather report for {data['speakableLocationString']}: " + f"{data['condition']}, temperature {data['temperature']} degrees") + done.set() + + with anki_vector.Robot() as robot: + done = threading.Event() + robot.events.subscribe(on_user_intent, Events.user_intent, done) + + print('------ Vector is waiting to be asked "Hey Vector! What is the weather report?" Press ctrl+c to exit early ------') + + try: + if not done.wait(timeout=10): + print('------ Vector never heard a request for the weather report ------') + except KeyboardInterrupt: + pass + + :param event: an event containing UserIntent data + """ + + def __init__(self, event): + self._intent_event = UserIntentEvent(event.intent_id) + self._intent_data = event.json_data + + @property + def intent_event(self) -> UserIntentEvent: + """ This returns the voice command event as a UserIntentEvent""" + return self._intent_event + + @property + def intent_data(self) -> str: + """ + This gives access to any voice command specific data in JSON format. + + Some voice commands contain information from processing. For example, asking Vector + "Hey Vector, what is the weather?" will return the current location and the weather + forecast. + + Voice commands without additional information will have an empty intent_data. + """ + return self._intent_data diff --git a/anki_vector/util.py b/anki_vector/util.py index e8c4817..e5d6903 100644 --- a/anki_vector/util.py +++ b/anki_vector/util.py @@ -42,14 +42,17 @@ '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 +from typing import Callable, Union +from .exceptions import VectorConfigurationException, VectorPropertyValueNotReadyException from .messaging import protocol try: @@ -57,8 +60,6 @@ except ImportError: sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") -# TODO Move to the robot class - def parse_command_args(parser: argparse.ArgumentParser = None): """ @@ -93,6 +94,9 @@ def block_while_none(interval: float = 0.1, max_iterations: int = 50): :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) @@ -103,7 +107,7 @@ def wrapped(*args, **kwargs): time.sleep(interval) iterations += 1 if iterations > max_iterations: - raise Exception("Value not ready") + raise VectorPropertyValueNotReadyException() result = func(*args, **kwargs) return result return wrapped @@ -127,9 +131,19 @@ def setup_basic_logging(custom_handler: logging.Handler = None, handler = custom_handler if handler is None: handler = logging.StreamHandler(stream=target) - formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s') + 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) @@ -782,8 +796,8 @@ def is_comparable(self, other_pose) -> bool: 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)) + 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. @@ -808,7 +822,13 @@ def to_proto_pose_struct(self) -> protocol.PoseStruct: class ImageRect: - """Image coordinates and size""" + '''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') @@ -838,6 +858,15 @@ 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. @@ -913,6 +942,8 @@ class 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. @@ -1015,8 +1046,8 @@ def apply_overlay(self, image: Image.Image) -> None: image_width, image_height = image.size remaining_width = image_width - self.width remaining_height = image_height - self.height - x1, y1 = remaining_height // 2, remaining_width // 2 - x2, y2 = (image_height - (remaining_height // 2)), (image_width - (remaining_width // 2)) + 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) @@ -1046,3 +1077,56 @@ 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) diff --git a/anki_vector/version.py b/anki_vector/version.py index 829dce5..252ecf8 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.5.2.dev0" +__version__ = "0.6.1.dev0" diff --git a/anki_vector/viewer.py b/anki_vector/viewer.py index ed2e1f0..56fa095 100644 --- a/anki_vector/viewer.py +++ b/anki_vector/viewer.py @@ -19,20 +19,9 @@ __all__ = ['ViewerComponent', 'Viewer3DComponent'] import multiprocessing as mp -import os import sys import threading -try: - import cv2 -except ImportError as exc: - sys.exit("Cannot import opencv-python: Do `pip3 install opencv-python` to install") - -try: - import numpy as np -except ImportError as exc: - sys.exit("Cannot import numpy: Do `pip3 install numpy` to install") - try: from PIL import Image except ImportError: @@ -44,10 +33,10 @@ class ViewerComponent(util.Component): """This component opens a window and renders the images obtained from Vector's camera. - This viewer window is run in a separate process spawned by :func:`~ViewerComponent.show_video`. + This viewer window is run in a separate process spawned by :func:`~ViewerComponent.show`. Being on a separate process means the rendering of the camera does not block the main thread of the calling code, and allows the viewer to have its own ui thread which it can operate on. - :func:`~ViewerComponent.stop_video` will stop the viewer process. + :func:`~ViewerComponent.close` will stop the viewer process. .. testcode:: @@ -55,7 +44,7 @@ class ViewerComponent(util.Component): import time - with anki_vector.Robot(enable_camera_feed=True, show_viewer=True) as robot: + with anki_vector.Robot(show_viewer=True) as robot: time.sleep(5) :param robot: A reference to the owner Robot object. (May be :class:`None`) @@ -68,37 +57,40 @@ def __init__(self, robot): self._frame_queue: mp.Queue = None self._process = None - def show_video(self, timeout: float = 10.0) -> None: + def show(self, timeout: float = 10.0, force_on_top: bool = True) -> None: """Render a video stream using the images obtained from Vector's camera feed. - Be sure to create your Robot object with the camera feed enabled - by using "show_viewer=True" and "enable_camera_feed=True". - .. testcode:: import anki_vector import time - with anki_vector.Robot(enable_camera_feed=True) as robot: - robot.viewer.show_video() + with anki_vector.Robot() as robot: + robot.viewer.show() time.sleep(10) - :param timeout: Render video for the given time. (Renders forever, if timeout not given) + :param timeout: Render video for the given time. (Renders forever, if timeout not given.) + :param force_on_top: Specifies whether the window should be forced on top of all others. """ + from . import camera_viewer + + self.robot.camera.init_camera_feed() + ctx = mp.get_context('spawn') self._close_event = ctx.Event() self._frame_queue = ctx.Queue(maxsize=4) - self._process = ctx.Process(target=ViewerComponent._render_frames, + self._process = ctx.Process(target=camera_viewer.main, args=(self._frame_queue, self._close_event, self.overlays, - timeout), + timeout, + force_on_top), daemon=True, name="Camera Viewer Process") self._process.start() - def stop_video(self) -> None: + def close(self) -> None: """Stop rendering video of Vector's camera feed and close the viewer process. .. testcode:: @@ -108,7 +100,7 @@ def stop_video(self) -> None: with anki_vector.Robot(show_viewer=True) as robot: time.sleep(10) - robot.viewer.stop_video() + robot.viewer.close() """ if self._close_event: self._close_event.set() @@ -132,7 +124,8 @@ def enqueue_frame(self, image: Image.Image): .. note:: This function will be called automatically from the camera feed when the - :class:`~anki_vector.robot.Robot` object is created with ``enable_camera_feed=True``. + :class:`~anki_vector.robot.Robot` or :class:`~anki_vector.robot.AsyncRobot` + object is created with ``show_viewer=True``. .. code-block:: python @@ -158,41 +151,6 @@ def _apply_overlays(self, image: Image.Image) -> None: overlay.apply_overlay(image) return image - @staticmethod - def _render_frames(queue: mp.Queue, event: mp.Event, overlays: list = None, timeout: float = 10.0) -> 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 main thread and other process. - :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. - """ - is_windows = os.name == 'nt' - window_name = "Vector Camera Feed" - cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) - try: - image = queue.get(True, timeout=timeout) - while image: - if event.is_set(): - break - if overlays: - for overlay in overlays: - overlay.apply_overlay(image) - image = cv2.cvtColor(np.array(image), cv2.COLOR_BGR2RGB) - cv2.imshow(window_name, image) - cv2.waitKey(1) - if not is_windows and cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE) < 1: - break - image = queue.get(True, timeout=timeout) - except TimeoutError: - pass - except KeyboardInterrupt: - pass - finally: - event.set() - cv2.destroyWindow(window_name) - cv2.waitKey(1) - class _ExternalRenderCallFunctor(): # pylint: disable=too-few-public-methods """Externally specified OpenGL render function. @@ -243,8 +201,9 @@ def __init__(self, robot): self._process: mp.process.BaseProcess = None self._update_thread: threading.Thread = None self._last_robot_control_intents = None + self.connecting_to_cube = False - def show(self): + def show(self, show_viewer_controls: bool = True): """Spawns a background process that shows the navigation map in a 3D view. .. testcode:: @@ -257,6 +216,8 @@ def show(self): robot.viewer_3d.show() time.sleep(5) robot.viewer_3d.close() + + :param show_viewer_controls: Specifies whether to draw controls on the view. """ from . import opengl ctx = mp.get_context('spawn') @@ -277,7 +238,8 @@ def show(self): self._nav_map_queue, self._world_frame_queue, self._extra_render_function_queue, - self._user_data_queue), + self._user_data_queue, + show_viewer_controls), daemon=True, name="3D Viewer Process") self._process.start() @@ -350,6 +312,16 @@ def close(self): self._process.terminate() self._process = None + def connect_to_cube(self): + '''Connect to light cube''' + if self.connecting_to_cube: + return + + self.connecting_to_cube = True + self.robot.world.connect_cube() + self.connecting_to_cube = False + return + def _update(self): """Reads most recently stored user-triggered intents, and sends motor messages to the robot if the intents should effect the robot's @@ -384,11 +356,15 @@ def _update(self): if not old_intents or old_intents.head_speed != input_intents.head_speed: self.robot.motors.set_head_motor(input_intents.head_speed, _return_future=True) + + if input_intents.connect_to_light_block and (old_intents is None or not old_intents.connect_to_light_block): + threading.Thread(target=self.connect_to_cube).start() + except mp.queues.Empty: pass close_event = self._close_event - def _on_robot_state_update(self, *_): + def _on_robot_state_update(self, robot, *_): """Called from SDK process whenever the robot state is updated (so i.e. every engine tick). Note: @@ -400,16 +376,15 @@ def _on_robot_state_update(self, *_): (main) process via a multiprocessing queue. """ from .opengl import opengl_vector - world_frame = opengl_vector.WorldRenderFrame(self.robot) + world_frame = opengl_vector.WorldRenderFrame(robot, self.connecting_to_cube) queue = self._world_frame_queue if queue: try: queue.put(world_frame, False) except mp.queues.Full: pass - # self._view_controller.update(self.robot) # TODO: <- sounds like this has something to do with keyboard input... - def _on_nav_map_update(self, _, msg): + def _on_nav_map_update(self, _robot, _event_type, msg): """Called from SDK process whenever the nav map is updated. Note: diff --git a/anki_vector/vision.py b/anki_vector/vision.py index 29172c5..d37ef6a 100644 --- a/anki_vector/vision.py +++ b/anki_vector/vision.py @@ -56,10 +56,10 @@ def close(self): if isinstance(vision_mode, futures.Future): vision_mode.result() - def _handle_mirror_mode_disabled_event(self, _, _msg): + def _handle_mirror_mode_disabled_event(self, _robot, _event_type, _msg): self._display_camera_feed_on_face = False - def _handle_vision_modes_auto_disabled_event(self, _, _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 @@ -95,7 +95,12 @@ async def disable_all_vision_modes(self): @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 + """Enable custom object detection on the robot's camera. + + If custom object detection is being turned off, the robot may still choose to keep it on + if another subscriber (including one internal to the robot) requests this vision mode be active. + + See :class:`objects.CustomObjectMarkers`. :param detect_custom_objects: Specify whether we want the robot to detect custom objects. @@ -141,11 +146,11 @@ async def enable_face_detection( # @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) @@ -160,6 +165,16 @@ async def enable_display_camera_feed_on_face(self, display_camera_feed_on_face: """Display the robot's camera feed on its face along with any detections (if enabled) :param display_camera_feed_on_face: Specify whether we want to display the robot's camera feed on its face. + + .. testcode:: + + import anki_vector + + import time + + with anki_vector.Robot() as robot: + robot.vision.enable_display_camera_feed_on_face() + time.sleep(10.0) """ self._display_camera_feed_on_face = display_camera_feed_on_face diff --git a/anki_vector/world.py b/anki_vector/world.py index 43e6b3a..60485e0 100644 --- a/anki_vector/world.py +++ b/anki_vector/world.py @@ -77,11 +77,11 @@ def __init__(self, robot): # Subscribe to callbacks that updates the world view self._robot.events.subscribe(self._on_face_observed, Events.robot_observed_face, - on_connection_thread=True) + _on_connection_thread=True) self._robot.events.subscribe(self._on_object_observed, Events.robot_observed_object, - on_connection_thread=True) + _on_connection_thread=True) #### Public Properties #### @@ -111,24 +111,44 @@ def visible_faces(self) -> Iterable[faces.Face]: .. testcode:: - # Print the visible face's attributes + import time + import anki_vector - with anki_vector.Robot() as robot: + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + print(f"Subscriber called for: {event_type} = {event}") + for face in robot.world.visible_faces: - print("Face attributes:") + print("--- Face attributes ---") print(f"Face id: {face.face_id}") print(f"Updated face id: {face.updated_face_id}") print(f"Name: {face.name}") print(f"Expression: {face.expression}") - print(f"Timestamp: {face.timestamp}") + print(f"Timestamp: {face.last_observed_time}") print(f"Pose: {face.pose}") - print(f"Image Rect: {face.face_rect}") + print(f"Image Rect: {face.last_observed_image_rect}") print(f"Expression score: {face.expression_score}") print(f"Left eye: {face.left_eye}") print(f"Right eye: {face.right_eye}") print(f"Nose: {face.nose}") print(f"Mouth: {face.mouth}") + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(10) + except KeyboardInterrupt: + robot.disconnect() + Returns: A generator yielding :class:`anki_vector.faces.Face` instances. """ @@ -140,6 +160,8 @@ def visible_faces(self) -> Iterable[faces.Face]: def custom_object_archetypes(self) -> Iterable[objects.CustomObjectArchetype]: """generator: yields each custom object archetype that Vector will look for. + See :class:`objects.CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -157,6 +179,8 @@ def custom_object_archetypes(self) -> Iterable[objects.CustomObjectArchetype]: def visible_custom_objects(self) -> Iterable[objects.CustomObject]: """generator: yields each custom object that Vector can currently see. + See :class:`objects.CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -171,6 +195,24 @@ def visible_custom_objects(self) -> Iterable[objects.CustomObject]: if obj.is_visible: yield obj + @property + def visible_objects(self) -> Iterable[objects.ObservableObject]: + """generator: yields each object that Vector can currently see. + + .. testcode:: + + import anki_vector + with anki_vector.Robot() as robot: + for obj in robot.world.visible_objects: + print(obj) + + Returns: + A generator yielding Charger, LightCube and CustomObject instances + """ + for obj in self._objects.values(): + if obj.is_visible: + yield obj + @property def connected_light_cube(self) -> objects.LightCube: """A light cube connected to Vector, if any. @@ -226,7 +268,7 @@ def charger(self) -> objects.Charger: # First, place Vector directly in front of his charger so he can observe it. with anki_vector.Robot() as robot: - print('most recently observed charger: {0}.'.format(robot.world.charger)) + print('Most recently observed charger: {0}'.format(robot.world.charger)) """ if self._charger is not None: return self._charger @@ -272,12 +314,30 @@ def get_face(self, face_id: int) -> faces.Face: .. testcode:: - with anki_vector.Robot() as robot: - # First get an existing face_id, for instance: - previously_observed_face_id = 1 - # Then use the face_id to retrieve the Face instance - my_face = robot.world.get_face(previously_observed_face_id) + import time + + import anki_vector + from anki_vector.events import Events + from anki_vector.util import degrees + + def test_subscriber(robot, event_type, event): + for face in robot.world.visible_faces: + print(f"Face id: {face.face_id}") + + 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) + + robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id) + robot.events.subscribe(test_subscriber, Events.robot_observed_face) + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + time.sleep(5) + except KeyboardInterrupt: + robot.disconnect() """ return self._faces.get(face_id) @@ -322,6 +382,7 @@ async def disconnect_cube(self) -> protocol.DisconnectCubeResponse: req = protocol.DisconnectCubeRequest() return await self.grpc_interface.DisconnectCube(req) + # TODO move out of world.py and into lights.py? @connection.on_connection_thread() async def flash_cube_lights(self) -> protocol.FlashCubeLightsResponse: """Flash cube lights @@ -338,6 +399,7 @@ async def flash_cube_lights(self) -> protocol.FlashCubeLightsResponse: req = protocol.FlashCubeLightsRequest() return await self.grpc_interface.FlashCubeLights(req) + # TODO move out of world.py and into objects.py? @connection.on_connection_thread(requires_control=False) async def forget_preferred_cube(self) -> protocol.ForgetPreferredCubeResponse: """Forget preferred cube. @@ -356,6 +418,7 @@ async def forget_preferred_cube(self) -> protocol.ForgetPreferredCubeResponse: req = protocol.ForgetPreferredCubeRequest() return await self.grpc_interface.ForgetPreferredCube(req) + # TODO move out of world.py and into objects.py? @connection.on_connection_thread(requires_control=False) async def set_preferred_cube(self, factory_id: str) -> protocol.SetPreferredCubeResponse: """Set preferred cube. @@ -378,6 +441,7 @@ async def set_preferred_cube(self, factory_id: str) -> protocol.SetPreferredCube req = protocol.SetPreferredCubeRequest(factory_id=factory_id) return await self.grpc_interface.SetPreferredCube(req) + # TODO better place to put this method than world.py? @connection.on_connection_thread(requires_control=False) async def delete_custom_objects(self, delete_custom_marker_objects: bool = True, @@ -385,6 +449,8 @@ async def delete_custom_objects(self, delete_custom_object_archetypes: bool = True): """Causes the robot to forget about custom objects it currently knows about. + See :class:`objects.CustomObjectMarkers`. + .. testcode:: import anki_vector @@ -412,6 +478,7 @@ async def delete_custom_objects(self, return last_blocking_call + # TODO better place to put this method than world.py? @connection.on_connection_thread(requires_control=False) async def define_custom_box(self, custom_object_type: objects.CustomObjectTypes, @@ -433,6 +500,8 @@ async def define_custom_box(self, object_observed message when they are seen. The markers must be placed in the center of their respective sides. All 6 markers must be unique. + See :class:`objects.CustomObjectMarkers`. + :param custom_object_type: the object type you are binding this custom object to. :param marker_front: the marker affixed to the front of the object. :param marker_back: the marker affixed to the back of the object. @@ -511,6 +580,7 @@ async def define_custom_box(self, self.logger.error("Failed to define Custom Object %s", custom_object_archetype) return None + # TODO better place to put this method than world.py? @connection.on_connection_thread(requires_control=False) async def define_custom_cube(self, custom_object_type: objects.CustomObjectTypes, @@ -525,6 +595,8 @@ async def define_custom_cube(self, object_observed message when they are seen. The markers must be placed in the center of their respective sides. + See :class:`objects.CustomObjectMarkers`. + :param custom_object_type: the object type you are binding this custom object to. :param marker: the marker affixed to every side of the cube. :param size_mm: size of each side of the cube (in millimeters). @@ -578,6 +650,7 @@ async def define_custom_cube(self, self.logger.error("Failed to define Custom Object %s", custom_object_archetype) return None + # TODO better place to put this method than world.py? @connection.on_connection_thread(requires_control=False) async def define_custom_wall(self, custom_object_type: objects.CustomObjectTypes, @@ -593,6 +666,8 @@ async def define_custom_wall(self, object_observed message when they are seen. The markers must be placed in the center of their respective sides. + See :class:`objects.CustomObjectMarkers`. + :param custom_object_type: the object type you are binding this custom object to. :param marker: the marker affixed to the front and back of the wall. :param width_mm: width of the object (in millimeters). (Y axis). @@ -652,6 +727,7 @@ async def define_custom_wall(self, self.logger.error("Failed to define Custom Object %s", custom_object_archetype) return None + # TODO better place to put this method than world.py? def create_custom_fixed_object(self, pose: util.Pose, x_size_mm: float, @@ -661,6 +737,8 @@ def create_custom_fixed_object(self, use_robot_origin: bool = True) -> objects.FixedCustomObject: """Defines a cuboid of custom size and places it in the world. It cannot be observed. + See :class:`objects.CustomObjectMarkers`. + :param pose: The pose of the object we are creating. :param x_size_mm: size of the object (in millimeters) in the x axis. :param y_size_mm: size of the object (in millimeters) in the y axis. @@ -780,9 +858,9 @@ def _remove_all_fixed_custom_object_instances(self): #### Private Event Handlers #### - def _on_face_observed(self, _, msg): + def _on_face_observed(self, _robot, _event_type, msg): """Adds a newly observed face to the world view.""" - if msg.face_id not in self._faces or msg.face_id not in self._objects: + if msg.face_id not in self._faces: 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, @@ -796,22 +874,24 @@ def _on_face_observed(self, _, msg): msg.left_eye, msg.right_eye, msg.nose, msg.mouth, msg.timestamp) if face: self._faces[face.face_id] = face - self._objects[face.face_id] = face - def _on_object_observed(self, _, msg): + def _on_object_observed(self, _robot, _event_type, msg): """Adds a newly observed custom object to the world view.""" - if msg.object_family == protocol.ObjectFamily.Value("LIGHT_CUBE"): + first_custom_type = protocol.ObjectType.Value("FIRST_CUSTOM_OBJECT_TYPE") + if msg.object_type == objects.LIGHT_CUBE_1_TYPE: if msg.object_id not in self._objects: - if self.light_cube: - self._objects[msg.object_id] = self.light_cube + light_cube = self._light_cube.get(objects.LIGHT_CUBE_1_TYPE) + if light_cube: + light_cube.object_id = msg.object_id + self._objects[msg.object_id] = light_cube - if msg.object_family == protocol.ObjectFamily.Value("CHARGER"): + elif msg.object_type == protocol.ObjectType.Value("CHARGER_BASIC"): if msg.object_id not in self._objects: charger = self._allocate_charger(msg) if charger: self._objects[msg.object_id] = charger - if msg.object_family == protocol.ObjectFamily.Value("CUSTOM_OBJECT"): + elif first_custom_type <= msg.object_type < (first_custom_type + protocol.CustomType.Value("CUSTOM_TYPE_COUNT")): if msg.object_id not in self._objects: custom_object = self._allocate_custom_marker_object(msg) if custom_object: diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2bfb8cf --- /dev/null +++ b/docs/README.md @@ -0,0 +1,35 @@ +# Vector Python SDK Documentation + +The scripts and source here will build the documentation for the SDK. + +The majority of the documentation is built from inline text included in the SDK Python source, +so changes to the source code will be reflected in docs builds. + +To update and build the docs, follow the steps below. + +## Installing sphinx + +The +[Sphinx Documetation Generator](https://www.sphinx-doc.org/en/master/) +is used to build the docs. You'll need to have it installed on your +system with `pip install -r requirements.txt` using the +`requirements.txt` file in this directory and not the main project directory. + +## Updating the Docs + +There are a few files that are not automatically generated and reside in `source`. For example, +the top-level list of API elements are in ```source/api.rst``` and will need to be updated whenever +a new user-facing class is added to the SDK. + +## Building the Docs + +The makefile can be used to build different documentation targets. The usual usage is to make +the html version of the docs. + +```bash +make clean +make html +``` + +You will now have an offline copy of the documetation that can be +accessed by opening `.build/html/index.html` diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..51674ac --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +sphinx +sphinx-autodoc-typehints +sphinx_rtd_theme diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html index 7d57882..b4396d2 100644 --- a/docs/source/_templates/layout.html +++ b/docs/source/_templates/layout.html @@ -64,17 +64,6 @@ window.addEventListener("message", receiveMessage, false); })(window); - - /** - * Google Analytics - * @TODO: enable google analytics - */ - // if (window.location.hostname.toLowerCase() == 'cozmosdk.anki.com') { - // window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date; - // ga('create', 'UA-40941061-1', 'auto'); - // ga('send', 'pageview'); - // } - + + + +

Table of Contents

+ +
+ +
+ + + +
+

anki_vector/messaging/behavior.proto

Top +
+

+ + +

BehaviorControlRequest

+ Messages that can be sent to the behavior stream. Explicitly +requesting or releasing control. +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
control_releaseControlRelease

Release control of the behavior system back to Vector.

control_requestControlRequest

Request control of the behavior system for the SDK.

+

+ + + + +

BehaviorControlResponse

+ Responses from the behavior stream. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
control_granted_responseControlGrantedResponse

The SDK user is now free to directly control Vector.

control_lost_eventControlLostResponse

Control of the behavior system has been lost to a higher priority behavior.

keep_aliveKeepAlivePing

Used by Vector to verify the connection is still alive.

reserved_control_lost_eventReservedControlLostResponse

Behavior system lock has been lost to another connection

+

+ + + + +

ControlGrantedResponse

+ The SDK user is now free to run any actions and behaviors they like. +Until a ControlLostResponse is received, they are directly in control +of Vector's behavior system. +

+ + + + + +

ControlLostResponse

+ This informs the user that they lost control of the behavior system. +All direct actions will be unavailable via the sdk until control is regained. +Regaining control can be either through a call to ControlRequest, or +can be as a result of conditions passed to the original ControlRequest. +

+ + + + + +

ControlRelease

+ Tell the behavior stream to release control from the SDK. +The stream may stay alive, but Vector will be allowed to run +his normal behaviors. +

+ + + + + +

ControlRequest

+ Request control of the behavior system at a given priority. +Currently there is only one priority level. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
priorityControlRequest.Priority

Where in the behavior tree the SDK code should be executed.

+

+ + + + +

ReservedControlLostResponse

+ The ability to reserve control before/after SDK scripts has been lost. +This control can be regained through another ControlRequest. +

+ + + + + + + +

ControlRequest.Priority

+ Where in the behavior tree the SDK code should be executed.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
UNKNOWN0

Unknown priority. Used for versions that don't understand old priority levels.

OVERRIDE_BEHAVIORS10

Highest priority level. Suppresses most automatic physical reactions, use with caution.

DEFAULT20

Normal priority level. Directly under mandatory physical reactions.

RESERVE_CONTROL30

Enable long-running SDK control between script execution. Not to be used for regular behavior control.

+

+ + + + + + + +
+

anki_vector/messaging/cube.proto

Top +
+

+ + +

ConnectCubeRequest

+ Attempt to connect to a cube. If a cube is currently connected, +this will do nothing. +

+ + + + + +

ConnectCubeResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

successbool

object_iduint32

factory_idstring

+

+ + + + +

CreateFixedCustomObjectRequest

+ Creates a CustomObject at the specific pose with no markers associated with it +Since this object has no markers with which to be observed, it will remain in the +specified pose as an obstacle forever (or until deleted with a deletion message above) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
posePoseStruct

x_size_mmfloat

y_size_mmfloat

z_size_mmfloat

+

+ + + + +

CreateFixedCustomObjectResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

object_iduint32

+

+ + + + +

CubeConnectionLost

+ Indicates that a the connection subscribed through ConnectCube has +been lost. +

+ + + + + +

CubesAvailableRequest

+ +

+ + + + + +

CubesAvailableResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

factory_idsstring repeated

+

+ + + + +

CustomBoxDefinition

+ Defines a custom object of the given size with the given markers centered on each side +If isUnique=true, there is guaranteed to be no more than one object of this type present in the world +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
marker_frontCustomObjectMarker

marker_backCustomObjectMarker

marker_topCustomObjectMarker

marker_bottomCustomObjectMarker

marker_leftCustomObjectMarker

marker_rightCustomObjectMarker

x_size_mmfloat

y_size_mmfloat

z_size_mmfloat

marker_width_mmfloat

marker_height_mmfloat

+

+ + + + +

CustomCubeDefinition

+ Defines a custom cube of the given size. The cube will have the same marker centered on all faces. +If isUnique=true, there is guaranteed to be no more than one cube of this type present in the world at a time. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
markerCustomObjectMarker

size_mmfloat

marker_width_mmfloat

marker_height_mmfloat

+

+ + + + +

CustomWallDefinition

+ Defines a custom wall of the given height and width, with the same marker centered on both sides (front and back). +The wall's thickness is assumed to be 1cm (and thus there are no markers on its left, right, top, or bottom) +If isUnique=true, there is guaranteed to be no more than one wall of this type present in the world at a time. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
markerCustomObjectMarker

width_mmfloat

height_mmfloat

marker_width_mmfloat

marker_height_mmfloat

+

+ + + + +

DefineCustomObjectRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
custom_typeCustomType

is_uniquebool

custom_boxCustomBoxDefinition

custom_cubeCustomCubeDefinition

custom_wallCustomWallDefinition

+

+ + + + +

DefineCustomObjectResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

successbool

+

+ + + + +

DeleteCustomObjectsRequest

+ Deletes all custom objects matching a given deletion mode. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
modeCustomObjectDeletionMode

+

+ + + + +

DeleteCustomObjectsResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

DisconnectCubeRequest

+ Requests a disconnection from the currently connected cube. +

+ + + + + +

DisconnectCubeResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

FlashCubeLightsRequest

+ Plays the default cube connection animation on the currently +connected cube's lights. This is intended for app level user +surfacing of cube connectivity, not for sdk cube light control. +

+ + + + + +

FlashCubeLightsResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

ForgetPreferredCubeRequest

+ Forget the robot's preferred cube. This will cause the robot to +connect to the cube with the highest RSSI (signal strength) next +time a connection is requested. Saves this preference to disk. +The next cube that the robot connects to will become its +preferred cube. +

+ + + + + +

ForgetPreferredCubeResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

ObjectAvailable

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
factory_idstring

+

+ + + + +

ObjectConnectionState

+ Indicates that a cube has connected or disconnected to the robot. +This message will be sent for any connects or disconnects regardless +of whether it originated from us or underlying robot behavior. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
object_iduint32

factory_idstring

object_typeObjectType

connectedbool

+

+ + + + +

ObjectEvent

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
object_availableObjectAvailable

object_connection_stateObjectConnectionState

object_movedObjectMoved

object_stopped_movingObjectStoppedMoving

object_up_axis_changedObjectUpAxisChanged

object_tappedObjectTapped

robot_observed_objectRobotObservedObject

cube_connection_lostCubeConnectionLost

+

+ + + + +

ObjectMoved

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
timestampuint32

object_iduint32

+

+ + + + +

ObjectStoppedMoving

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
timestampuint32

object_iduint32

+

+ + + + +

ObjectTapped

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
timestampuint32

object_iduint32

+

+ + + + +

ObjectUpAxisChanged

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
timestampuint32

object_iduint32

up_axisUpAxis

+

+ + + + +

RobotObservedObject

+ RobotObservedObject for signaling that an object + with specified ID/Type was seen at a particular location in the image + and the world +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
timestampuint32

object_familyObjectFamily

ObjectFamily is deprecated. Use ObjectType instead to reason about groupings of objects.

object_typeObjectType

object_idint32

signed to match U2G::PickAndPlaceObject which has the option to have objectID<0

img_rectCladRect

position in image coords

posePoseStruct

top_face_orientation_radfloat

absolute orienation of top face, iff isActive==true

is_activeuint32

+

+ + + + +

SetCubeLightsRequest

+ Sets each LED on victor's cube. Two states are specified +designated 'on' and 'off', each with a color, duration, +and state transition time +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
object_iduint32

on_coloruint32 repeated

off_coloruint32 repeated

on_period_msuint32 repeated

off_period_msuint32 repeated

transition_on_period_msuint32 repeated

transition_off_period_msuint32 repeated

offsetint32 repeated

relative_to_xfloat

relative_to_yfloat

rotatebool

make_relativeSetCubeLightsRequest.MakeRelativeMode

+

+ + + + +

SetCubeLightsResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

SetPreferredCubeRequest

+ Set the robot's preferred cube and save it to disk. The robot +will always attempt to connect to this cube if it is available. +This is only used in simulation for now. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
factory_idstring

+

+ + + + +

SetPreferredCubeResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + + + +

CustomObjectDeletionMode

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
DELETION_MASK_UNKNOWN0

DELETION_MASK_FIXED_CUSTOM_OBJECTS1

DELETION_MASK_CUSTOM_MARKER_OBJECTS2

DELETION_MASK_ARCHETYPES3

+

+ +

CustomObjectMarker

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
CUSTOM_MARKER_UNKNOWN0

CUSTOM_MARKER_CIRCLES_21

CUSTOM_MARKER_CIRCLES_32

CUSTOM_MARKER_CIRCLES_43

CUSTOM_MARKER_CIRCLES_54

CUSTOM_MARKER_DIAMONDS_25

CUSTOM_MARKER_DIAMONDS_36

CUSTOM_MARKER_DIAMONDS_47

CUSTOM_MARKER_DIAMONDS_58

CUSTOM_MARKER_HEXAGONS_29

CUSTOM_MARKER_HEXAGONS_310

CUSTOM_MARKER_HEXAGONS_411

CUSTOM_MARKER_HEXAGONS_512

CUSTOM_MARKER_TRIANGLES_213

CUSTOM_MARKER_TRIANGLES_314

CUSTOM_MARKER_TRIANGLES_415

CUSTOM_MARKER_TRIANGLES_516

CUSTOM_MARKER_COUNT16

+

+ +

CustomType

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
INVALID_CUSTOM_TYPE0

CUSTOM_TYPE_001

CUSTOM_TYPE_012

CUSTOM_TYPE_023

CUSTOM_TYPE_034

CUSTOM_TYPE_045

CUSTOM_TYPE_056

CUSTOM_TYPE_067

CUSTOM_TYPE_078

CUSTOM_TYPE_089

CUSTOM_TYPE_0910

CUSTOM_TYPE_1011

CUSTOM_TYPE_1112

CUSTOM_TYPE_1213

CUSTOM_TYPE_1314

CUSTOM_TYPE_1415

CUSTOM_TYPE_1516

CUSTOM_TYPE_1617

CUSTOM_TYPE_1718

CUSTOM_TYPE_1819

CUSTOM_TYPE_1920

CUSTOM_TYPE_COUNT20

+

+ +

ObjectConstants

+ Constants associated with the audio feed

+ + + + + + + + + + + + + + + + + + + +
NameNumberDescription
OBJECT_CONSTANTS_NULL0

error value

FIXED_CUSTOM_WALL_THICKNESS_MM10

The depth of custom walls used in the engine

+

+ +

ObjectFamily

+ Note: ObjectFamily is deprecated and will be removed in future releases

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
INVALID_FAMILY0

UNKNOWN_FAMILY1

BLOCK2

LIGHT_CUBE3

CHARGER4

CUSTOM_OBJECT7

OBJECT_FAMILY_COUNT7

+

+ +

ObjectType

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
INVALID_OBJECT0

UNKNOWN_OBJECT1

BLOCK_LIGHTCUBE12

Light Cube (a.k.a. Active Block)

CHARGER_BASIC6

FIRST_CUSTOM_OBJECT_TYPE15

+

+ +

SetCubeLightsRequest.MakeRelativeMode

+ This is a uint_8 internally.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
UNKNOWN0

OFF1

BY_CORNER2

BY_SIDE3

+

+ +

UpAxis

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
INVALID_AXIS0

X_NEGATIVE1

X_POSITIVE2

Y_NEGATIVE3

Y_POSITIVE4

Z_NEGATIVE5

Z_POSITIVE6

NUM_AXES7

+

+ + + + + + + +
+

anki_vector/messaging/extensions.proto

Top +
+

+ + + + + + +

File-level Extensions

+ + + + + + + + + + + + + + + +
ExtensionTypeBaseNumberDescription
streamedbool.google.protobuf.MessageOptions60000

+

+ + + + + +
+

anki_vector/messaging/external_interface.proto

Top +
+

+ + + + +

ProtocolVersion

+

The valid versions of the protocol.

+

Protocol versions are updated when messages change significantly: +new ones are added and removed, fields deprecated, etc.

+

The goal is to support as many old versions as possible, only +bumping the minimum when there is no way to handle a prior version.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
PROTOCOL_VERSION_UNKNOWN0

Required zero value for enums

PROTOCOL_VERSION_MINIMUM0

Minimum supported version

PROTOCOL_VERSION_CURRENT5

Current version (incremented on 2019.03.12 for streaming update status support.)

+

+ + + + + +

ExternalInterface

+ The grpc-defined connection between the SDK and Anki's Vector robot.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Method NameDescription
ProtocolVersion

Checks the supported protocol version by passing in the client version and +minimum host version and receiving a response to see whether the versions are supported.

SDKInitialization

SDK-only message to pass version info for device OS, Python version, etc.

DriveWheels

Sets the speed and acceleration for Vector's wheel motors.

PlayAnimationTrigger

Requests that Vector play an animation trigger.

PlayAnimation

Requests that Vector play an animation.

ListAnimations

Constructs and returns a list of animations.

ListAnimationTriggers

Constructs and returns a list of animation triggers.

MoveHead

Moves Vector's head.

MoveLift

Moves Vector's lift.

StopAllMotors

Stop all motor commands requested: MoveHead, MoveLift and DriveWheels.

DisplayFaceImageRGB

Sets screen (Vector's face) to a solid color.

EventStream

Streaming events endpoint

ExternalAudioStreamPlayback

Play audio using Vector's speaker

BehaviorControl

Integrate with and acquire control of Vector's AI system.

AssumeBehaviorControl

Acquire control of Vector's AI system.

CancelFaceEnrollment

RequestEnrolledNames

Get a list of names and their IDs.

UpdateEnrolledFaceByID

Update the name enrolled for a given face.

EraseEnrolledFaceByID

Erase the enrollment (name) record for the face with this ID.

EraseAllEnrolledFaces

Erase the enrollment (name) records for all faces.

SetFaceToEnroll

EnableMarkerDetection

EnableFaceDetection

EnableMotionDetection

EnableMirrorMode

EnableImageStreaming

IsImageStreamingEnabled

CancelActionByIdTag

Cancel action by id

GoToPose

Tells Vector to drive to the specified pose and orientation.

DockWithCube

Tells Vector to dock with a light cube with a given approach angle and distance.

DriveOffCharger

Drive Vector off the charger.

DriveOnCharger

Drive Vector onto the charger.

FindFaces

Make Vector look for faces by turning and angling his head

LookAroundInPlace

Make Vector look around in place by turning and moving his head

RollBlock

Make Vector roll his block, regardless of relative position and orientation

PhotosInfo

Get the photos info.

Photo

Get a photo by ID.

Thumbnail

Get a thumbnail by ID.

DeletePhoto

Delete a photo by ID.

DriveStraight

Tells Vector to drive in a straight line.

TurnInPlace

Turn the robot around its current position.

SetHeadAngle

Tell Vector's head to move to a given angle.

SetLiftHeight

Tell Vector's lift to move to a given height.

TurnTowardsFace

Tell Vector to turn towards this face.

GoToObject

Tell Vector to drive to the specified object.

RollObject

Tell Vector to roll his cube.

PopAWheelie

Tell Vector to "pop a wheelie" using his cube.

PickupObject

Instruct the robot to pick up the supplied object.

PlaceObjectOnGroundHere

Ask Vector to place the object he is carrying on the ground at the current location.

SetMasterVolume

Set the Robot's master volume

UserAuthentication

BatteryState

Check the current state of the battery.

VersionState

Get the versioning information for Vector.

SayText

Make Vector speak text.

ConnectCube

Attempt to connect to a cube. If a cube is currently connected, +this will do nothing.

DisconnectCube

Requests a disconnection from the currently connected cube.

CubesAvailable

FlashCubeLights

Signal a connected cube to flash its lights using the default cube +flash animation.

ForgetPreferredCube

Forget the robot's preferred cube. This will cause the robot to +connect to the cube with the highest RSSI (signal strength) next +time a connection is requested. Saves this preference to disk. +The next cube that the robot connects to will become its +preferred cube.

SetPreferredCube

Set the robot's preferred cube and save it to disk. The robot +will always attempt to connect to this cube if it is available. +This is only used in simulation for now.

DeleteCustomObjects

Causes the robot to forget about custom objects it currently knows about.

CreateFixedCustomObject

Creates a permanent custom object instance in the robot's world, with no connection to the vision system.

DefineCustomObject

Creates a custom object with distinct custom marker(s).

SetCubeLights

Set each of the lights on the currently connected cube based on two +rgb values each and timing data for how to transition between them.

AudioFeed

Request an audio feed from the robot.

CameraFeed

Request a camera feed from the robot.

CaptureSingleImage

Request a single image to be captured and sent from the robot.

SetEyeColor

Set Vector's eye color.

NavMapFeed

Stream navigation map data.

+

+ + + +
+

anki_vector/messaging/messages.proto

Top +
+

+ + +

AccelData

+ All values are in mm/s^2. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
xfloat

yfloat

zfloat

+

+ + + + +

ActionResult

+ The possible results of running an action. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
codeActionResult.ActionResultCode

+

+ + + + +

Animation

+ An animation object. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
namestring

The name of a given animation.

+

+ + + + +

AnimationTrigger

+ An animation trigger object. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
namestring

The name of a given animation trigger.

+

+ + + + +

AudioChunk

+ One frame of robot audio data and associated metadata +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
robot_time_stampuint32

robot time at the final chunk of this audio sample group transmission

group_iduint32

the id of this sample transmission group

chunk_iduint32

the current batched chunk id within in this group id

audio_chunk_countuint32

number of chunks batched within this group id

signal_powerbytes

mono audio amplitude samples

direction_strengthsbytes

histogram data of which directions this audio chunk came from

source_directionuint32

0-11, with 12 representing "invalid"

source_confidenceuint32

accuracy of the calculated source_direction

noise_floor_poweruint32

power value, convert to db with log_10(value)

+

+ + + + +

AudioFeedRequest

+ Request the robot to send audio data +

+ + + + + +

AudioFeedResponse

+ Streamed audio data from the robot +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
robot_time_stampuint32

robot time at the transmission of this audio sample group

group_iduint32

the index of this audio feed response

signal_powerbytes

mono audio amplitude samples

direction_strengthsbytes

histogram data of which directions this audio chunk came from

source_directionuint32

0-11, with 12 representing "invalid"

source_confidenceuint32

accuracy of the calculated source_direction

noise_floor_poweruint32

power value, convert to db with log_10(value)

+

+ + + + +

AudioSendModeChanged

+ Event sent when the robot changes the mode it's processing and sending audio +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
modeAudioProcessingMode

+

+ + + + +

AudioSendModeRequest

+ Request how the robot should process and send audio +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
modeAudioProcessingMode

+

+ + + + +

BatteryStateRequest

+ +

+ + + + + +

BatteryStateResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

battery_levelBatteryLevel

battery_voltsfloat

is_chargingbool

is_on_charger_platformbool

suggested_charger_secfloat

cube_batteryCubeBattery

+

+ + + + +

CameraFeedRequest

+ +

+ + + + + +

CameraFeedResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
frame_time_stampuint32

image_iduint32

image_encodingImageChunk.ImageEncoding

databytes

+

+ + + + +

CancelActionByIdTagRequest

+ Cancel a previously-requested action. Action requests include GoToPoseRequest, DockWithCubeRequest, DriveStraightRequest, TurnInPlaceRequest, SetHeadAngleRequest, and SetLiftHeightRequest. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
id_taguint32

Use the id_tag provided to the action request

+

+ + + + +

CancelActionByIdTagResponse

+ Response from the robot to CancelActionByIdTagRequest. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

A generic status.

+

+ + + + +

CancelFaceEnrollmentRequest

+ +

+ + + + + +

CancelFaceEnrollmentResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

CaptureSingleImageRequest

+ +

+ + + + + +

CaptureSingleImageResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

frame_time_stampuint32

image_iduint32

image_encodingImageChunk.ImageEncoding

databytes

+

+ + + + +

CladPoint

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
xfloat

yfloat

+

+ + + + +

CladRect

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
x_top_leftfloat

y_top_leftfloat

widthfloat

heightfloat

+

+ + + + +

CubeBattery

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
levelCubeBattery.CubeBatteryLevel

factory_idstring

battery_voltsfloat

time_since_last_reading_secfloat

+

+ + + + +

DeletePhotoRequest

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
photo_iduint32

+

+ + + + +

DeletePhotoResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

successbool

+

+ + + + +

DisplayFaceImageRGBRequest

+ See DisplayFaceImageRGB rpc for more details. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
face_databytes

The image to render.

duration_msuint32

How long to display the image on the face.

interrupt_runningbool

If this image should overwrite any current images on the face.

+

+ + + + +

DisplayFaceImageRGBResponse

+ See DisplayFaceImageRGB rpc for more details. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

A generic status.

+

+ + + + +

DockWithCubeRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
object_idint32

distance_from_marker_mmfloat

approach_angle_radfloat

alignment_typeAlignmentType

use_approach_anglebool

use_pre_dock_posebool

motion_profPathMotionProfile

id_tagint32

num_retriesint32

+

+ + + + +

DockWithCubeResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

DriveOffChargerRequest

+ +

+ + + + + +

DriveOffChargerResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultBehaviorResults

+

+ + + + +

DriveOnChargerRequest

+ +

+ + + + + +

DriveOnChargerResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultBehaviorResults

+

+ + + + +

DriveStraightRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
speed_mmpsfloat

Speed should be positive

dist_mmfloat

Use +ve for forward, -ve for backward

should_play_animationbool

id_tagint32

num_retriesint32

+

+ + + + +

DriveStraightResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

DriveWheelsRequest

+ See the DriveWheels rpc for more details. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
left_wheel_mmpsfloat

right_wheel_mmpsfloat

left_wheel_mmps2float

right_wheel_mmps2float

+

+ + + + +

DriveWheelsResponse

+ See the DriveWheels rpc for more details. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

A generic status.

+

+ + + + +

EnableFaceDetectionRequest

+ When enabled, RobotObservedFace messages will be produced +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
enablebool

enable_smile_detectionbool

enable_expression_estimationbool

enable_blink_detectionbool

enable_gaze_detectionbool

+

+ + + + +

EnableFaceDetectionResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

EnableImageStreamingRequest

+ Toggle image streaming at the given resolution +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
enablebool

+

+ + + + +

EnableImageStreamingResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

EnableMarkerDetectionRequest

+ When enabled, RobotObservedObject messages will be produced +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
enablebool

+

+ + + + +

EnableMarkerDetectionResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

EnableMirrorModeRequest

+ When enabled, camera feed will appear on the robot's face, along with any +detections that are enabled from above messages +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
enablebool

+

+ + + + +

EnableMirrorModeResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

EnableMotionDetectionRequest

+ When enabled, RobotObservedMotion messages will be produced +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
enablebool

+

+ + + + +

EnableMotionDetectionResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

EraseAllEnrolledFacesRequest

+ +

+ + + + + +

EraseAllEnrolledFacesResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

EraseEnrolledFaceByIDRequest

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
face_idint32

+

+ + + + +

EraseEnrolledFaceByIDResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

ExternalAudioStreamBufferOverrun

+ Audio has been sent to robot that would overrun the memory buffer +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
audio_samples_sentuint32

audio_samples_playeduint32

+

+ + + + +

ExternalAudioStreamCancel

+ Cancel a playing external robot audio stream +

+ + + + + +

ExternalAudioStreamChunk

+ Send chunk of audio data to stream on robot +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
audio_chunk_size_bytesuint32

current engine maximum of 1024 bytes

audio_chunk_samplesbytes

16-bit little-endian PCM audio data

+

+ + + + +

ExternalAudioStreamComplete

+ Send notification of last chunk of audio sent to robot +

+ + + + + +

ExternalAudioStreamPlaybackComplete

+ Audio has been played on the Robot +

+ + + + + +

ExternalAudioStreamPlaybackFailure

+ Audio playback failure +

+ + + + + +

ExternalAudioStreamPrepare

+ Preflight and initialization for audio streaming to robot +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
audio_frame_rateuint32

8000 - 16025

audio_volumeuint32

0 - 100

+

+ + + + +

ExternalAudioStreamRequest

+ Request to stream audio to the robot +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
audio_stream_prepareExternalAudioStreamPrepare

audio_stream_chunkExternalAudioStreamChunk

audio_stream_completeExternalAudioStreamComplete

audio_stream_cancelExternalAudioStreamCancel

+

+ + + + +

ExternalAudioStreamResponse

+ Response from streaming audio to robot +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
audio_stream_playback_completeExternalAudioStreamPlaybackComplete

audio_stream_buffer_overrunExternalAudioStreamBufferOverrun

audio_stream_playback_failyerExternalAudioStreamPlaybackFailure

+

+ + + + +

FaceEnrollmentCompleted

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
resultFaceEnrollmentResult

face_idint32

namestring

+

+ + + + +

FindFacesRequest

+ +

+ + + + + +

FindFacesResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultBehaviorResults

+

+ + + + +

GoToObjectRequest

+ GoToObject +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
object_idint32

motion_profPathMotionProfile

distance_from_object_origin_mmfloat

use_pre_dock_posebool

id_tagint32

num_retriesint32

+

+ + + + +

GoToObjectResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

GoToPoseRequest

+ GotoPose +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
x_mmfloat

y_mmfloat

radfloat

motion_profPathMotionProfile

id_tagint32

num_retriesint32

+

+ + + + +

GoToPoseResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

GyroData

+ All values are in rad/s. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
xfloat

yfloat

zfloat

+

+ + + + +

ImageChunk

+ One frame of image data and associated metadata +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
frame_time_stampuint32

image_iduint32

widthuint32

heightuint32

image_encodingImageChunk.ImageEncoding

display_indexuint32

image_chunk_countuint32

chunk_iduint32

databytes

+

+ + + + +

IsImageStreamingEnabledRequest

+ Request whether or not image streaming is enabled on the robot +

+ + + + + +

IsImageStreamingEnabledResponse

+ Indicates whether or not image streaming is enabled on the robot +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
is_image_streaming_enabledbool

+

+ + + + +

KeepAlivePing

+ A null message used by streams to verify that the client is +still connected. +

+ + + + + +

ListAnimationTriggersRequest

+ See ListAnimationTriggers rpc for more details. +

+ + + + + +

ListAnimationTriggersResponse

+ See ListAnimationTriggers rpc for more details. +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

A generic status.

animation_trigger_namesAnimationTrigger repeated

The animation triggers that Vector knows.

+

+ + + + +

ListAnimationsRequest

+ See ListAnimations rpc for more details. +

+ + + + + +

ListAnimationsResponse

+ See ListAnimations rpc for more details. +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

A generic status.

animation_namesAnimation repeated

The animations that Vector knows.

+

+ + + + +

LoadedKnownFace

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
seconds_since_first_enrolledint64

seconds_since_last_updatedint64

seconds_since_last_seenint64

last_seen_seconds_since_epochint64

face_idint32

namestring

+

+ + + + +

LookAroundInPlaceRequest

+ +

+ + + + + +

LookAroundInPlaceResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultBehaviorResults

+

+ + + + +

MasterVolumeRequest

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
volume_levelMasterVolumeLevel

+

+ + + + +

MasterVolumeResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

MeetVictorFaceScanComplete

+ +

+ + + + + +

MeetVictorFaceScanStarted

+ +

+ + + + + +

MirrorModeDisabled

+ Sent if MirrorMode (camera feed displayed on face) is currently enabled but is automatically +being disabled +

+ + + + + +

MoveHeadRequest

+ See the MoveHead rpc for more details. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
speed_rad_per_secfloat

+

+ + + + +

MoveHeadResponse

+ See the MoveHead rpc for more details. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

A generic status.

+

+ + + + +

MoveLiftRequest

+ See the MoveLift rpc for more details. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
speed_rad_per_secfloat

+

+ + + + +

MoveLiftResponse

+ See the MoveLift rpc for more details. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

A generic status.

+

+ + + + +

PathMotionProfile

+ Struct containing all the information relevant to how a +path should be modified or traversed. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
speed_mmpsfloat

accel_mmps2float

decel_mmps2float

point_turn_speed_rad_per_secfloat

point_turn_accel_rad_per_sec2float

point_turn_decel_rad_per_sec2float

dock_speed_mmpsfloat

dock_accel_mmps2float

dock_decel_mmps2float

reverse_speed_mmpsfloat

is_custombool

+

+ + + + +

PhotoInfo

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
photo_iduint32

timestamp_utcuint32

photo_copied_to_appbool

thumb_copied_to_appbool

+

+ + + + +

PhotoPathMessage

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
successbool

full_pathstring

+

+ + + + +

PhotoRequest

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
photo_iduint32

+

+ + + + +

PhotoResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

successbool

imagebytes

+

+ + + + +

PhotoTaken

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
photo_iduint32

+

+ + + + +

PhotosInfoRequest

+ +

+ + + + + +

PhotosInfoResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

photo_infosPhotoInfo repeated

+

+ + + + +

PickupObjectRequest

+ PickupObject +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
object_idint32

negative value means "currently selected object"

motion_profPathMotionProfile

approach_angle_radfloat

use_approach_anglebool

use_pre_dock_posebool

id_tagint32

num_retriesint32

+

+ + + + +

PickupObjectResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

PlaceObjectOnGroundHereRequest

+ PlaceObjectOnGroundHere +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
id_tagint32

num_retriesint32

+

+ + + + +

PlaceObjectOnGroundHereResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

PlayAnimationRequest

+ See PlayAnimation rpc for more details. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
animationAnimation

The animation to play.

loopsuint32

The number of times to play the animation in a row.

ignore_body_trackbool

Ignore any movement of Vector's body in the given animation.

ignore_head_trackbool

Ignore any movement of Vector's head in the given animation.

ignore_lift_trackbool

Ignore any movement of Vector's lift in the given animation.

+

+ + + + +

PlayAnimationResponse

+ See PlayAnimation rpc for more details. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

A generic status.

resultBehaviorResults

Information on whether the animation played successfully.

animationAnimation

The animation that the robot executed.

+

+ + + + +

PlayAnimationTriggerRequest

+ See PlayAnimationTrigger rpc for more details. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
animation_triggerAnimationTrigger

The animation trigger to play.

loopsuint32

The number of times to play the animation in a row.

use_lift_safebool

Automatically ignore the lift track if Vector is currently carrying an object.

ignore_body_trackbool

Ignore any movement of Vector's body when playing the animation.

ignore_head_trackbool

Ignore any movement of Vector's head when playing the animation.

ignore_lift_trackbool

Ignore any movement of Vector's lift when playing the animation.

+

+ + + + +

PopAWheelieRequest

+ PopAWheelie +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
object_idint32

negative value means "currently selected object"

motion_profPathMotionProfile

approach_angle_radfloat

use_approach_anglebool

use_pre_dock_posebool

id_tagint32

num_retriesint32

+

+ + + + +

PopAWheelieResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

PoseStruct

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
xfloat

Translation

yfloat

zfloat

q0float

Rotation quaternion

q1float

q2float

q3float

origin_iduint32

Which coordinate frame this pose is in (0 for none or unknown)

+

+ + + + +

ProxData

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
distance_mmuint32

signal_qualityfloat

unobstructedbool

The sensor has confirmed it has not detected anything up to its max range

found_objectbool

The sensor detected an object in the valid operating range

is_lift_in_fovbool

Lift (or object on lift) is occluding the sensor

+

+ + + + +

RequestEnrolledNamesRequest

+ +

+ + + + + +

RequestEnrolledNamesResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

facesLoadedKnownFace repeated

+

+ + + + +

RobotChangedObservedFaceID

+ RobotChangedObservedFaceID + This generally happens when a tracked face (negative ID) is recognized and + receives a positive ID or when face records get merged +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
old_idint32

new_idint32

+

+ + + + +

RobotObservedFace

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
face_idint32

negative: tracked but not recognized; positive: recognized face

timestampuint32

posePoseStruct

img_rectCladRect

position in image coords

namestring

Empty if none assigned yet

expressionFacialExpression

expression_valuesuint32 repeated

Individual expression values histogram, sums to 100 (Exception: all zero if expression=Unknown)

left_eyeCladPoint repeated

Face landmarks

right_eyeCladPoint repeated

noseCladPoint repeated

mouthCladPoint repeated

+

+ + + + +

RobotRenamedEnrolledFace

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
face_idint32

namestring

+

+ + + + +

RobotState

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
posePoseStruct

pose_angle_radfloat

pose_pitch_radfloat

left_wheel_speed_mmpsfloat

right_wheel_speed_mmpsfloat

head_angle_radfloat

lift_height_mmfloat

accelAccelData

gyroGyroData

carrying_object_idint32

carrying_object_on_top_idint32

Not supported by engine

head_tracking_object_idint32

localized_to_object_idint32

last_image_time_stampuint32

statusuint32

prox_dataProxData

touch_dataTouchData

+

+ + + + +

RollBlockRequest

+ this triggers a Vector Behavior, different from RollObjectRequest which triggers an Action +

+ + + + + +

RollBlockResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultBehaviorResults

+

+ + + + +

RollObjectRequest

+ RollObject +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
object_idint32

negative value means "currently selected object"

motion_profPathMotionProfile

approach_angle_radfloat

use_approach_anglebool

use_pre_dock_posebool

id_tagint32

num_retriesint32

+

+ + + + +

RollObjectResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

SDKInitializationRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
sdk_module_versionstring

python_versionstring

python_implementationstring

os_versionstring

cpu_versionstring

+

+ + + + +

SDKInitializationResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

SayTextRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
textstring

use_vector_voicebool

duration_scalarfloat

+

+ + + + +

SayTextResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

stateSayTextResponse.UtteranceState

+

+ + + + +

SetEyeColorRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
huefloat

saturationfloat

+

+ + + + +

SetEyeColorResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

SetFaceToEnrollRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
namestring

observed_idint32

The ID of a specific observed face to enroll (0 for next one we see)

save_idint32

The ID of an existing face to merge final enrollment into (0 for none,

save_to_robotbool

Save to robot's NVStorage when done (NOTE: will (re)save everyone enrolled!)

say_namebool

Play say-name/celebration animations on success before completing

use_musicbool

Starts special music during say-name animations (will leave music playing!)

+

+ + + + +

SetFaceToEnrollResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

SetHeadAngleRequest

+ SetHeadAngle +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
angle_radfloat

max_speed_rad_per_secfloat

accel_rad_per_sec2float

duration_secfloat

id_tagint32

num_retriesint32

+

+ + + + +

SetHeadAngleResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

SetLiftHeightRequest

+ SetLiftHeight +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
height_mmfloat

max_speed_rad_per_secfloat

accel_rad_per_sec2float

duration_secfloat

id_tagint32

num_retriesint32

+

+ + + + +

SetLiftHeightResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

Status

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
meet_victor_face_scan_startedMeetVictorFaceScanStarted

meet_victor_face_scan_completeMeetVictorFaceScanComplete

face_enrollment_completedFaceEnrollmentCompleted

+

+ + + + +

StimulationInfo

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
emotion_eventsstring repeated

may be empty

valuefloat

velocityfloat

value per second

accelfloat

value per sec per sec

value_before_eventfloat

matches value if there were no emotion events

min_valuefloat

max_valuefloat

+

+ + + + +

StopAllMotorsRequest

+ See the StopAllMotors rpc for more details. +

+ + + + + +

StopAllMotorsResponse

+ See the StopAllMotors rpc for more details. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

ThumbnailPathMessage

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
successbool

full_pathstring

+

+ + + + +

ThumbnailRequest

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
photo_iduint32

+

+ + + + +

ThumbnailResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

successbool

imagebytes

+

+ + + + +

TimeStampedStatus

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusStatus

timestamp_utcuint32

+

+ + + + +

TouchData

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
raw_touch_valueuint32

Raw input from the touch sensor

is_being_touchedbool

Robot's context aware evaluation of whether it currently is or isn't being touched

+

+ + + + +

TurnInPlaceRequest

+ TurnInPlace +If isAbsolute == 0, turns the specified number of radians, relative to the robot's current heading* +If isAbsolute != 0, turns to the specified absolute orientation. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
angle_radfloat

relative mode: positive turns left negative turns right

speed_rad_per_secfloat

accel_rad_per_sec2float

tol_radfloat

This should be no smaller than POINT_TURN_ANGLE_TOL (DEG_TO_RAD(2.f)).

is_absoluteuint32

Smaller values are ignored except 0 which is interpreted to mean +default tolerance of POINT_TURN_ANGLE_TOL. + +0: angle_rad is relative to current orientation, 1: angle_rad is absolute angle to turn to

id_tagint32

num_retriesint32

+

+ + + + +

TurnInPlaceResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

TurnTowardsFaceRequest

+ TurnTowardsFace +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
face_idint32

max_turn_angle_radfloat

id_tagint32

num_retriesint32

+

+ + + + +

TurnTowardsFaceResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

resultActionResult

+

+ + + + +

UpdateEnrolledFaceByIDRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
face_idint32

old_namestring

new_namestring

+

+ + + + +

UpdateEnrolledFaceByIDResponse

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

+

+ + + + +

UserIntent

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
intent_iduint32

json_datastring

+

+ + + + +

VersionStateRequest

+ +

+ + + + + +

VersionStateResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

os_versionstring

engine_build_idstring

+

+ + + + +

VisionModesAutoDisabled

+ Sent when vision modes are automatically disabled due to the SDK no longer having control +of the robot +

+ + + + + +

WakeWord

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
wake_word_beginWakeWordBegin

wake_word_endWakeWordEnd

+

+ + + + +

WakeWordBegin

+ +

+ + + + + +

WakeWordEnd

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
intent_heardbool

intent_jsonstring

+

+ + + + + + +

ActionResult.ActionResultCode

+ The possible results of running an action.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
ACTION_RESULT_SUCCESS0

Action completed successfully.

ACTION_RESULT_RUNNING16777216

Action is still running.

ACTION_RESULT_CANCELLED_WHILE_RUNNING33554432

Action was cancelled by SDK request

NOT_STARTED33554433

Initial state of an Action to indicate it has not yet started.

ABORT50331648

Action aborted itself (e.g. had invalid attributes, or a runtime failure).

ANIM_ABORTED50331649

Animation Action aborted itself (e.g. there was an error playing the animation).

BAD_MARKER50331650

There was an error related to vision markers.

BAD_MESSAGE_TAG50331651

There was a problem related to a subscribed or unsupported message tag

BAD_OBJECT50331652

There was a problem with the Object ID provided (e.g. there is no Object with that ID).

BAD_POSE50331653

There was a problem with the Pose provided.

BAD_TAG50331654

The SDK-provided tag was bad.

CHARGER_UNPLUGGED_ABORT50331655

Vector is on the charger but cannot sense the contacts. Charger may be unplugged.

CLIFF_ALIGN_FAILED_TIMEOUT50331656

CLIFF_ALIGN_FAILED_NO_TURNING50331657

CLIFF_ALIGN_FAILED_OVER_TURNING50331658

CLIFF_ALIGN_FAILED_NO_WHITE50331659

CLIFF_ALIGN_FAILED_STOPPED50331660

FAILED_SETTING_CALIBRATION50331661

Shouldn't occur outside of factory.

FOLLOWING_PATH_BUT_NOT_TRAVERSING50331662

There was an error following the planned path.

INTERRUPTED50331663

The action was interrupted by another Action or Behavior.

INVALID_OFF_TREADS_STATE50331664

The robot ended up in an "off treads state" not valid for this action (e.g. the robot was placed on its back while executing a turn).

MISMATCHED_UP_AXIS50331665

The Up Axis of a carried object doesn't match the desired placement pose.

NO_ANIM_NAME50331666

No valid Animation name was found.

NO_DISTANCE_SET50331667

An invalid distance value was given.

NO_FACE50331668

There was a problem with the Face ID (e.g. Vector doesn't no where it is).

NO_GOAL_SET50331669

No goal pose was set.

NO_PREACTION_POSES50331670

No pre-action poses were found (e.g. could not get into position).

NOT_CARRYING_OBJECT_ABORT50331671

No object is being carried, but the action requires one.

NOT_ON_CHARGER_ABORT50331672

Vector is expected to be on the charger, but is not.

NULL_SUBACTION50331673

No sub-action was provided.

PATH_PLANNING_FAILED_ABORT50331674

Vector was unable to plan a path.

PICKUP_OBJECT_UNEXPECTEDLY_MOVING50331675

The object that Vector is attempting to pickup is unexpectedly moving (e.g it is being moved by someone else).

SEND_MESSAGE_TO_ROBOT_FAILED50331676

Shouldn't occur in SDK usage.

STILL_CARRYING_OBJECT50331677

Vector is unexpectedly still carrying an object.

TIMEOUT50331678

The Action timed out before completing correctly.

TRACKS_LOCKED50331679

One or more movement tracks (Head, Lift, Body, Face, Backpack Lights, Audio) are already being used by another Action.

UNEXPECTED_DOCK_ACTION50331680

There was an internal error related to an unexpected type of dock action.

UNKNOWN_TOOL_CODE50331681

Shouldn't occur outside of factory.

UPDATE_DERIVED_FAILED50331682

There was a problem in the subclass's update on the robot.

VISUAL_OBSERVATION_FAILED50331683

Vector did not see the expected result (e.g. unable to see cube in the expected position after a related action).

SHOULDNT_DRIVE_ON_CHARGER50331684

Action is not permitted on the charger.

RETRY67108864

The Action failed, but may succeed if retried.

DID_NOT_REACH_PREACTION_POSE67108865

Failed to get into position.

FAILED_TRAVERSING_PATH67108866

Failed to follow the planned path.

LAST_PICK_AND_PLACE_FAILED67108867

The previous attempt to pick and place an object failed.

MOTOR_STOPPED_MAKING_PROGRESS67108868

The required motor isn't moving so the action cannot complete.

NOT_CARRYING_OBJECT_RETRY67108869

Not carrying an object when it was expected, but may succeed if the action is retried.

NOT_ON_CHARGER_RETRY67108870

Driving onto the charger failed, but may succeed if the action is retried.

PATH_PLANNING_FAILED_RETRY67108871

Vector was unable to plan a path, but may succeed if the action is retried.

PLACEMENT_GOAL_NOT_FREE67108872

There is no room to place the object at the desired destination.

PICKUP_OBJECT_UNEXPECTEDLY_NOT_MOVING67108873

The object that Vector thought he was lifting didn't start moving, so he must have missed.

STILL_ON_CHARGER67108874

Vector failed to drive off the charger.

UNEXPECTED_PITCH_ANGLE67108875

Vector's pitch is at an unexpected angle for the Action.

+

+ +

ActionTagConstants

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
INVALID_SDK_TAG0

FIRST_SDK_TAG2000001

LAST_SDK_TAG3000000

+

+ +

AlignmentType

+ This is a uint8 in CLAD

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
ALIGNMENT_TYPE_UNKNOWN0

ALIGNMENT_TYPE_LIFT_FINGER1

Align the tips of the lift fingers with the target object

ALIGNMENT_TYPE_LIFT_PLATE2

Align the flat part of the lift with the object (useful for getting the fingers in the cube's grooves)

ALIGNMENT_TYPE_BODY3

Align the front of Vector's body (useful for when the lift is up)

ALIGNMENT_TYPE_CUSTOM4

For use with distanceFromMarker parameter

+

+ +

AudioConstants

+ Constants associated with the audio feed

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
AUDIO_CONSTANTS_NULL0

error value

MIC_DETECTION_DIRECTIONS12

The number of audio directions the four microphones can isolate

SAMPLE_COUNTS_PER_ENGINE_MESSAGE160

The number of audio samples in each internal audio chunk

SAMPLE_COUNTS_PER_SDK_MESSAGE1600

The number of audio samples delivered in each audio feed response

MICROPHONE_SAMPLE_RATE15625

The sampling rate victor's microphones record at

PROCESSED_SAMPLE_RATE16000

The sampling rate the robot processes audio at

+

+ +

AudioProcessingMode

+ The robot can process audio in a variety of ways

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
AUDIO_UNKNOWN0

error value

AUDIO_OFF1

deactivates audio SDK mode

AUDIO_FAST_MODE2

unprocessed single microphone data - most performant on robot

AUDIO_DIRECTIONAL_MODE3

beamforming support for focusing on specific direction - sounds cleanest

AUDIO_VOICE_DETECT_MODE4

multi-microphone non-beamforming - best for voice detection programs

+

+ +

BatteryLevel

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
BATTERY_LEVEL_UNKNOWN0

BATTERY_LEVEL_LOW1

BATTERY_LEVEL_NOMINAL2

BATTERY_LEVEL_FULL3

+

+ +

BehaviorResults

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
BEHAVIOR_INVALID_STATE0

BEHAVIOR_COMPLETE_STATE1

BEHAVIOR_WONT_ACTIVATE_STATE2

+

+ +

CubeBattery.CubeBatteryLevel

+

+ + + + + + + + + + + + + + + + + + + +
NameNumberDescription
Low0

Normal1

+

+ +

FaceEnrollmentResult

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
SUCCESS0

SAW_WRONG_FACE1

Failures:

SAW_MULTIPLE_FACES2

TIMED_OUT3

SAVE_FAILED4

INCOMPLETE5

CANCELLED6

NAME_IN_USE7

NAMED_STORAGE_FULL8

UNKNOWN_FAILURE9

+

+ +

FacialExpression

+ This is an int8 on the clad side. +Proto field names are prefixed with "EXPRESSION_"

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
EXPRESSION_UNKNOWN0

e.g. expression estimation disabled.

EXPRESSION_NEUTRAL1

EXPRESSION_HAPPINESS2

EXPRESSION_SURPRISE3

EXPRESSION_ANGER4

EXPRESSION_SADNESS5

EXPRESSION_COUNT5

Clad does not account for "Unknown" in the count.

+

+ +

ImageChunk.ImageEncoding

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
NONE_IMAGE_ENCODING0

RAW_GRAY1

no compression

RAW_RGB2

no compression, just [RGBRGBRG...]

YUYV3

YUV420SP4

BAYER5

JPEG_GRAY6

JPEG_COLOR7

JPEG_COLOR_HALF_WIDTH8

JPEG_MINIMIZED_GRAY9

Minimized grayscale JPEG - no header, no footer, no byte stuffing

JPEG_MINIMIZED_COLOR10

Minimized grayscale JPEG - no header, no footer, no byte stuffing, with added color data

+

+ +

MasterVolumeLevel

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
VOLUME_LOW0

VOLUME_MEDIUM_LOW1

VOLUME_MEDIUM2

VOLUME_MEDIUM_HIGH3

VOLUME_HIGH4

+

+ +

RobotStatus

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
ROBOT_STATUS_NONE0

ROBOT_STATUS_IS_MOVING1

ROBOT_STATUS_IS_CARRYING_BLOCK2

ROBOT_STATUS_IS_PICKING_OR_PLACING4

ROBOT_STATUS_IS_PICKED_UP8

ROBOT_STATUS_IS_BUTTON_PRESSED16

ROBOT_STATUS_IS_FALLING32

ROBOT_STATUS_IS_ANIMATING64

ROBOT_STATUS_IS_PATHING128

ROBOT_STATUS_LIFT_IN_POS256

ROBOT_STATUS_HEAD_IN_POS512

ROBOT_STATUS_CALM_POWER_MODE1024

ROBOT_STATUS_IS_ON_CHARGER4096

ROBOT_STATUS_IS_CHARGING8192

ROBOT_STATUS_CLIFF_DETECTED16384

ROBOT_STATUS_ARE_WHEELS_MOVING32768

ROBOT_STATUS_IS_BEING_HELD65536

ROBOT_STATUS_IS_MOTION_DETECTED131072

+

+ +

SayTextResponse.UtteranceState

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
INVALID0

GENERATING1

READY2

PLAYING3

FINISHED4

+

+ + + + + + + +
+

anki_vector/messaging/nav_map.proto

Top +
+

+ + +

NavMapFeedRequest

+ Requests nav map data from the robot at a specified maximum update frequency. +Responses in the nav map stream may be sent less frequently if the robot does +not consider there to be relevant new information. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
frequencyfloat

+

+ + + + +

NavMapFeedResponse

+ A full nav map sent from the robot. It contains an origin_id that +which can be compared against the robot's current origin_id, general +info about the map, and a collection of quads representing the map's +content. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
origin_iduint32

map_infoNavMapInfo

quad_infosNavMapQuadInfo repeated

+

+ + + + +

NavMapInfo

+ General information about the nav map as a whole. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
root_depthint32

root_size_mmfloat

root_center_xfloat

root_center_yfloat

root_center_zfloat

+

+ + + + +

NavMapQuadInfo

+ An individual sample of vector's nav map. This quad's size will vary and +depends on the resolution the map requires to effectively identify +boundaries in the environment. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
contentNavNodeContentType

depthuint32

color_rgbauint32

+

+ + + + + + +

NavNodeContentType

+ Every tile in the nav map will be tagged with a content key referring to +the different environmental elements that Vector can identify.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
NAV_NODE_UNKNOWN0

NAV_NODE_CLEAR_OF_OBSTACLE1

NAV_NODE_CLEAR_OF_CLIFF2

NAV_NODE_OBSTACLE_CUBE3

NAV_NODE_OBSTACLE_PROXIMITY4

NAV_NODE_OBSTACLE_PROXIMITY_EXPLORED5

NAV_NODE_OBSTACLE_UNRECOGNIZED6

NAV_NODE_CLIFF7

NAV_NODE_INTERESTING_EDGE8

NAV_NODE_NON_INTERESTING_EDGE9

+

+ + + + + + + +
+

anki_vector/messaging/response_status.proto

Top +
+

+ + +

ResponseStatus

+ A shared response message sent back as part of most requests. +This will indicate the generic state of the request. +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
codeResponseStatus.StatusCode

The generic status code to give high-level insight into the progress of a given message.

+

+ + + + + + +

ResponseStatus.StatusCode

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
UNKNOWN0

RESPONSE_RECEIVED1

The message has completed as expected.

REQUEST_PROCESSING2

The message has been sent to the robot.

OK3

The message has been handled successfully at the interface level.

FORBIDDEN100

The user was not authorizied.

NOT_FOUND101

The requested attribute was not found.

ERROR_UPDATE_IN_PROGRESS102

Currently updating values from another call.

+

+ + + + + + + +
+

anki_vector/messaging/shared.proto

Top +
+

+ + +

ConnectionResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

is_primarybool

+

+ + + + +

Event

+ Messages originating from the engine +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
time_stamped_statusTimeStampedStatus

wake_wordWakeWord

robot_observed_faceRobotObservedFace

robot_changed_observed_face_idRobotChangedObservedFaceID

object_eventObjectEvent

stimulation_infoStimulationInfo

photo_takenPhotoTaken

robot_stateRobotState

cube_batteryCubeBattery

keep_aliveKeepAlivePing

Used by Vector to verify the connection is still alive.

connection_responseConnectionResponse

mirror_mode_disabledMirrorModeDisabled

vision_modes_auto_disabledVisionModesAutoDisabled

user_intentUserIntent

+

+ + + + +

EventRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
white_listFilterList

black_listFilterList

connection_idstring

+

+ + + + +

EventResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

eventEvent

+

+ + + + +

FilterList

+ +

+ + + + + + + + + + + + + + + +
FieldTypeDescription
liststring repeated

+

+ + + + +

ProtocolVersionRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
client_versionint64

min_host_versionint64

+

+ + + + +

ProtocolVersionResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
resultProtocolVersionResponse.Result

host_versionint64

+

+ + + + +

UserAuthenticationRequest

+ +

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
user_session_idbytes

client_namebytes

+

+ + + + +

UserAuthenticationResponse

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusResponseStatus

codeUserAuthenticationResponse.Code

client_token_guidbytes

+

+ + + + + + +

ProtocolVersionResponse.Result

+

+ + + + + + + + + + + + + + + + + + + +
NameNumberDescription
UNSUPPORTED0

SUCCESS1

+

+ +

UserAuthenticationResponse.Code

+

+ + + + + + + + + + + + + + + + + + + +
NameNumberDescription
UNAUTHORIZED0

AUTHORIZED1

+

+ + + + + + + +

Scalar Value Types

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
.proto TypeNotesPython Type
doublefloat
floatfloat
int32Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.int
int64Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.int/long
uint32Uses variable-length encoding.int/long
uint64Uses variable-length encoding.int/long
sint32Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.int
sint64Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.int/long
fixed32Always four bytes. More efficient than uint32 if values are often greater than 2^28.int
fixed64Always eight bytes. More efficient than uint64 if values are often greater than 2^56.int/long
sfixed32Always four bytes.int
sfixed64Always eight bytes.int/long
boolboolean
stringA string must always contain UTF-8 encoded or 7-bit ASCII text.str/unicode
bytesMay contain any arbitrary sequence of bytes.str
+ + + diff --git a/docs/source/proto.rst b/docs/source/proto.rst new file mode 100644 index 0000000..aa4a460 --- /dev/null +++ b/docs/source/proto.rst @@ -0,0 +1,15 @@ +:orphan: + +Protobuf Documentation +====================== + +**PLEASE NOTE: The Vector Python SDK is supported by Anki. Using protocol buffer messages directly is not supported. The proto messages are subject to change without warning. Use with caution.** + +.. raw:: html + :file: proto.html + +---- + +`Terms and Conditions `_ and `Privacy Policy `_ + +`Click here to return to the Anki Developer website. `_ \ No newline at end of file diff --git a/docs/source/template.tmpl b/docs/source/template.tmpl new file mode 100644 index 0000000..ea3daa4 --- /dev/null +++ b/docs/source/template.tmpl @@ -0,0 +1,310 @@ + + + + + + + + + + +

Table of Contents

+ +
+ +
+ + {{range .Files}} + {{$file_name := .Name}} +
+

{{.Name}}

Top +
+ {{.Description}}

+ + {{range .Messages}} +

{{.LongName}}

+ {{.Description}} +

+ + {{if .HasFields}} + + + + + + {{range .Fields}} + + + + + + {{end}} + +
FieldTypeDescription
{{.Name}}{{.LongType}} {{.Label}}

{{.Description}} {{if .DefaultValue}}Default: {{.DefaultValue}}{{end}}

+

+ {{end}} + + {{if .HasExtensions}} +
+ + + + + + {{range .Extensions}} + + + + + + + + {{end}} + +
ExtensionTypeBaseNumberDescription
{{.Name}}{{.LongType}}{{.ContainingLongType}}{{.Number}}

{{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}}

+ {{end}} + {{end}} + + {{range .Enums}} +

{{.LongName}}

+ {{.Description}}

+ + + + + + {{range .Values}} + + + + + + {{end}} + +
NameNumberDescription
{{.Name}}{{.Number}}

{{.Description}}

+

+ {{end}} + + {{if .HasExtensions}} +

File-level Extensions

+ + + + + + {{range .Extensions}} + + + + + + + + {{end}} + +
ExtensionTypeBaseNumberDescription
{{.Name}}{{.LongType}}{{.ContainingLongType}}{{.Number}}

{{.Description}}{{if .DefaultValue}} Default: {{.DefaultValue}}{{end}}

+

+ {{end}} + + {{range .Services}} +

{{.Name}}

+ {{.Description}}

+ + + + + + {{range .Methods}} + + + + + {{end}} + +
Method NameDescription
{{.Name}}

{{.Description}}

+

+ {{end}} + {{end}} + +

Scalar Value Types

+ + + + + + {{range .Scalars}} + + + + + + {{end}} + +
.proto TypeNotesPython Type
{{.ProtoType}}{{.Notes}}{{.PythonType}}
+ + + diff --git a/examples/apps/3d_viewer/3d_viewer.py b/examples/apps/3d_viewer/3d_viewer.py index 125158b..7245c72 100755 --- a/examples/apps/3d_viewer/3d_viewer.py +++ b/examples/apps/3d_viewer/3d_viewer.py @@ -29,7 +29,6 @@ def main(): args = anki_vector.util.parse_command_args() with anki_vector.Robot(args.serial, show_viewer=True, - enable_camera_feed=True, show_3d_viewer=True, enable_face_detection=True, enable_custom_object_detection=True, diff --git a/examples/apps/interactive_shell/interactive_shell.py b/examples/apps/interactive_shell/interactive_shell.py index a9d3cbb..a9fc5be 100755 --- a/examples/apps/interactive_shell/interactive_shell.py +++ b/examples/apps/interactive_shell/interactive_shell.py @@ -41,7 +41,7 @@ You can even call the functions that send messages to Vector, and he'll respond just like he would in a script. Try it out! Type: - robot.anim.play_animation('anim_pounce_success_02') + robot.anim.play_animation_trigger('GreetAfterLongTime') """ args = anki_vector.util.parse_command_args() @@ -51,7 +51,6 @@ if __name__ == "__main__": with anki_vector.Robot(args.serial, - enable_camera_feed=True, show_viewer=True) as robot: # Invoke the ipython shell while connected to Vector ipyshell(usage) diff --git a/examples/apps/proximity_mapper/lib/proximity_mapper_state.py b/examples/apps/proximity_mapper/lib/proximity_mapper_state.py index 5bcd1c1..28f6fd5 100755 --- a/examples/apps/proximity_mapper/lib/proximity_mapper_state.py +++ b/examples/apps/proximity_mapper/lib/proximity_mapper_state.py @@ -27,7 +27,6 @@ import multiprocessing as mp from typing import List -from anki_vector.opengl import opengl from anki_vector.util import Vector3 try: @@ -37,6 +36,7 @@ glPointSize, glPolygonMode, glVertex3f) except ImportError as import_exc: + from anki_vector.opengl import opengl opengl.raise_opengl_or_pillow_import_error(import_exc) diff --git a/examples/apps/proximity_mapper/proximity_mapper.py b/examples/apps/proximity_mapper/proximity_mapper.py index f67b0f0..8757bc1 100755 --- a/examples/apps/proximity_mapper/proximity_mapper.py +++ b/examples/apps/proximity_mapper/proximity_mapper.py @@ -167,16 +167,13 @@ async def add_proximity_non_contact_to_state(node_position: Vector3, state: MapS #: Modifies the map state with the details of a proximity reading async def analyze_proximity_sample(reading: anki_vector.proximity.ProximitySensorData, robot: anki_vector.robot.Robot, state: MapState): # Check if the reading meets the engine's metrics for valid, and that its within our specified distance threshold. - reading_contacted = reading.is_valid and reading.distance.distance_mm < PROXIMITY_SCAN_DISTANCE_THRESHOLD_MM + reading_contacted = reading.found_object and reading.distance.distance_mm < PROXIMITY_SCAN_DISTANCE_THRESHOLD_MM if reading_contacted: - # The distance will either be the reading data, or our threshold distance if the reading is considered uncontacted. - reading_distance = reading.distance.distance_mm if reading_contacted else PROXIMITY_SCAN_DISTANCE_THRESHOLD_MM - # Convert the distance to a 3d position in worldspace. reading_position = Vector3( - robot.pose.position.x + cos(robot.pose_angle_rad) * reading_distance, - robot.pose.position.y + sin(robot.pose_angle_rad) * reading_distance, + robot.pose.position.x + cos(robot.pose_angle_rad) * reading.distance.distance_mm, + robot.pose.position.y + sin(robot.pose_angle_rad) * reading.distance.distance_mm, robot.pose.position.z) await add_proximity_contact_to_state(reading_position, state) @@ -305,10 +302,9 @@ def open_point_sort_func(position: Vector3): # Connect to the robot args = parse_command_args() with anki_vector.Robot(args.serial, - enable_camera_feed=True, show_viewer=True, - enable_nav_map_feed=False, - show_3d_viewer=True) as robotInstance: + enable_nav_map_feed=False) as robotInstance: + robotInstance.viewer_3d.show(False) robotInstance.behavior.drive_off_charger() loop = asyncio.get_event_loop() loop.run_until_complete(map_explorer(robotInstance)) diff --git a/examples/apps/remote_control/remote_control.py b/examples/apps/remote_control/remote_control.py index 99e5529..f87646c 100755 --- a/examples/apps/remote_control/remote_control.py +++ b/examples/apps/remote_control/remote_control.py @@ -23,11 +23,12 @@ import json import sys import time +from enum import Enum from lib import flask_helpers import anki_vector from anki_vector import util - +from anki_vector import annotate try: from flask import Flask, request @@ -35,7 +36,7 @@ sys.exit("Cannot import from flask: Do `pip3 install --user flask` to install") try: - from PIL import Image + from PIL import Image, ImageDraw except ImportError: sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") @@ -72,11 +73,49 @@ def remap_to_range(x, x_min, x_max, out_min, out_max): return out_min + ratio * (out_max - out_min) +class DebugAnnotations(Enum): + DISABLED = 0 + ENABLED_VISION = 1 + ENABLED_ALL = 2 + + +# Annotator for displaying RobotState (position, etc.) on top of the camera feed +class RobotStateDisplay(annotate.Annotator): + def apply(self, image, scale): + d = ImageDraw.Draw(image) + + bounds = [3, 0, image.width, image.height] + + def print_line(text_line): + text = annotate.ImageText(text_line, position=annotate.AnnotationPosition.TOP_LEFT, outline_color='black', color='lightblue') + text.render(d, bounds) + TEXT_HEIGHT = 11 + bounds[1] += TEXT_HEIGHT + + robot = self.world.robot # type: robot.Robot + + # Display the Pose info for the robot + pose = robot.pose + print_line('Pose: Pos = <%.1f, %.1f, %.1f>' % pose.position.x_y_z) + print_line('Pose: Rot quat = <%.1f, %.1f, %.1f, %.1f>' % pose.rotation.q0_q1_q2_q3) + print_line('Pose: angle_z = %.1f' % pose.rotation.angle_z.degrees) + print_line('Pose: origin_id: %s' % pose.origin_id) + + # Display the Accelerometer and Gyro data for the robot + print_line('Accelmtr: <%.1f, %.1f, %.1f>' % robot.accel.x_y_z) + print_line('Gyro: <%.1f, %.1f, %.1f>' % robot.gyro.x_y_z) + + class RemoteControlVector: def __init__(self, robot): self.vector = robot + # don't send motor messages if it matches the last setting + self.last_lift = None + self.last_head = None + self.last_wheels = None + self.drive_forwards = 0 self.drive_back = 0 self.turn_left = 0 @@ -127,6 +166,19 @@ def __init__(self, robot): self.anim_index_for_key[kI] = anim_idx kI += 1 + all_anim_trigger_names = self.vector.anim.anim_trigger_list + self.anim_trigger_names = [] + + bad_anim_trigger_names = [ + "InvalidAnimTrigger", + "UnitTestAnim"] + + for anim_trigger_name in all_anim_trigger_names: + if anim_trigger_name not in bad_anim_trigger_names: + self.anim_trigger_names.append(anim_trigger_name) + + self.selected_anim_trigger_name = self.anim_trigger_names[0] + self.action_queue = [] self.text_to_say = "Hi I'm Vector" @@ -145,6 +197,9 @@ def handle_mouse(self, mouse_x, mouse_y): desired_head_angle = remap_to_range(mouse_y, 0.0, 1.0, 45, -25) head_angle_delta = desired_head_angle - util.radians(self.vector.head_angle_rad).degrees head_vel = head_angle_delta * 0.03 + if self.last_head and head_vel == self.last_head: + return + self.last_head = head_vel self.vector.motors.set_head_motor(head_vel) def set_mouse_look_enabled(self, is_mouse_look_enabled): @@ -231,7 +286,9 @@ def handle_key(self, key_code, is_shift_down, is_alt_down, is_key_down): anim_name = self.key_code_to_anim_name(key_code) self.queue_action((self.vector.anim.play_animation, anim_name)) elif key_code == ord(' '): - self.queue_action((self.vector.say_text, self.text_to_say)) + self.queue_action((self.vector.behavior.say_text, self.text_to_say)) + elif key_code == ord('X'): + self.queue_action((self.vector.anim.play_animation_trigger, self.selected_anim_trigger_name)) def key_code_to_anim_name(self, key_code): key_num = key_code - ord('0') @@ -240,7 +297,7 @@ def key_code_to_anim_name(self, key_code): return anim_name def func_to_name(self, func): - if func == self.vector.say_text: + if func == self.vector.behavior.say_text: return "say_text" if func == self.vector.anim.play_animation: return "play_anim" @@ -281,12 +338,18 @@ def pick_speed(self, fast_speed, mid_speed, slow_speed): def update_lift(self): lift_speed = self.pick_speed(8, 4, 2) lift_vel = (self.lift_up - self.lift_down) * lift_speed + if self.last_lift and lift_vel == self.last_lift: + return + self.last_lift = lift_vel self.vector.motors.set_lift_motor(lift_vel) def update_head(self): if not self.is_mouse_look_enabled: head_speed = self.pick_speed(2, 1, 0.5) head_vel = (self.head_up - self.head_down) * head_speed + if self.last_head and head_vel == self.last_head: + return + self.last_head = head_vel self.vector.motors.set_head_motor(head_vel) def update_mouse_driving(self): @@ -303,7 +366,11 @@ def update_mouse_driving(self): l_wheel_speed = (drive_dir * forward_speed) + (turn_speed * turn_dir) r_wheel_speed = (drive_dir * forward_speed) - (turn_speed * turn_dir) - self.vector.motors.set_wheel_motors(l_wheel_speed, r_wheel_speed, l_wheel_speed * 4, r_wheel_speed * 4) + wheel_params = (l_wheel_speed, r_wheel_speed, l_wheel_speed * 4, r_wheel_speed * 4) + if self.last_wheels and wheel_params == self.last_wheels: + return + self.last_wheels = wheel_params + self.vector.motors.set_wheel_motors(*wheel_params) def get_anim_sel_drop_down(selectorIndex): @@ -326,6 +393,13 @@ def get_anim_sel_drop_downs(): html_text += str(key) + """: """ + get_anim_sel_drop_down(key) + """
""" return html_text +def get_anim_trigger_sel_drop_down(): + html_text = "x: " # Add keyboard selector + html_text += """""" + return html_text def to_js_bool_string(bool_value): return "true" if bool_value else "false" @@ -370,15 +444,18 @@ def handle_index_page(): Shift : Hold to Move Faster (Driving, Head and Lift)
Alt : Hold to Move Slower (Driving, Head and Lift)
P : Toggle Free Play mode:
+ O : Toggle Debug Annotations:

Play Animations

0 .. 9 : Play Animation mapped to that key

Talk

- Space : Say + Space : Say

Animation key mappings:

""" + get_anim_sel_drop_downs() + """
+

Animation Triggers:

+ """ + get_anim_trigger_sel_drop_down() + """

@@ -387,6 +464,7 @@ def handle_index_page(): var gLastClientX = -1 var gLastClientY = -1 var gIsMouseLookEnabled = """ + to_js_bool_string(_is_mouse_look_enabled_by_default) + """ + var gAreDebugAnnotationsEnabled = """+ str(flask_app.display_debug_annotations) + """ var gIsFreeplayEnabled = false var gUserAgent = window.navigator.userAgent; var gIsMicrosoftBrowser = gUserAgent.indexOf('MSIE ') > 0 || gUserAgent.indexOf('Trident/') > 0 || gUserAgent.indexOf('Edge/') > 0; @@ -439,6 +517,37 @@ def handle_index_page(): postHttpRequest("setMouseLookEnabled", {isMouseLookEnabled}) } + function updateDebugAnnotationButtonEnabledText(button, isEnabled) + { + switch(gAreDebugAnnotationsEnabled) + { + case 0: + button.firstChild.data = "Disabled"; + break; + case 1: + button.firstChild.data = "Enabled (vision)"; + break; + case 2: + button.firstChild.data = "Enabled (all)"; + break; + default: + button.firstChild.data = "ERROR"; + break; + } + } + + function onDebugAnnotationsButtonClicked(button) + { + gAreDebugAnnotationsEnabled += 1; + if (gAreDebugAnnotationsEnabled > 2) + { + gAreDebugAnnotationsEnabled = 0 + } + updateDebugAnnotationButtonEnabledText(button, gAreDebugAnnotationsEnabled) + areDebugAnnotationsEnabled = gAreDebugAnnotationsEnabled + postHttpRequest("setAreDebugAnnotationsEnabled", {areDebugAnnotationsEnabled}) + } + function onFreeplayButtonClicked(button) { gIsFreeplayEnabled = !gIsFreeplayEnabled; @@ -449,6 +558,7 @@ def handle_index_page(): updateButtonEnabledText(document.getElementById("mouseLookId"), gIsMouseLookEnabled); updateButtonEnabledText(document.getElementById("freeplayId"), gIsFreeplayEnabled); + updateDebugAnnotationButtonEnabledText(document.getElementById("debugAnnotationsId"), gAreDebugAnnotationsEnabled); function handleDropDownSelect(selectObject) { @@ -457,6 +567,12 @@ def handle_index_page(): postHttpRequest("dropDownSelect", {selectedIndex, itemName}); } + function handleAnimTriggerDropDownSelect(selectObject) + { + animTriggerName = selectObject.value + postHttpRequest("animTriggerDropDownSelect", {animTriggerName}); + } + function handleKeyActivity (e, actionType) { var keyCode = (e.keyCode ? e.keyCode : e.which); @@ -466,7 +582,12 @@ def handle_index_page(): if (actionType=="keyup") { - if (keyCode == 80) // 'P' + if (keyCode == 79) // 'O' + { + // Simulate a click of the debug annotations button + onDebugAnnotationsButtonClicked(document.getElementById("debugAnnotationsId")) + } + else if (keyCode == 80) // 'P' { // Simulate a click of the freeplay button onFreeplayButtonClicked(document.getElementById("freeplayId")) @@ -531,12 +652,10 @@ def handle_index_page(): def get_annotated_image(): - # TODO: Update to use annotated image (add annotate module) image = flask_app.remote_control_vector.vector.camera.latest_image - if image is None: - return _default_camera_image - - return image + if flask_app.display_debug_annotations != DebugAnnotations.DISABLED.value: + return image.annotate_image() + return image.raw_image def streaming_video(): @@ -601,14 +720,30 @@ def handle_setMouseLookEnabled(): return "" +@flask_app.route('/setAreDebugAnnotationsEnabled', methods=['POST']) +def handle_setAreDebugAnnotationsEnabled(): + """Called from Javascript whenever debug-annotations mode is toggled""" + message = json.loads(request.data.decode("utf-8")) + flask_app.display_debug_annotations = message['areDebugAnnotationsEnabled'] + if flask_app.remote_control_vector: + if flask_app.display_debug_annotations == DebugAnnotations.ENABLED_ALL.value: + flask_app.remote_control_vector.vector.camera.image_annotator.enable_annotator('robotState') + else: + flask_app.remote_control_vector.vector.camera.image_annotator.disable_annotator('robotState') + return "" + + @flask_app.route('/setFreeplayEnabled', methods=['POST']) def handle_setFreeplayEnabled(): """Called from Javascript whenever freeplay mode is toggled on/off""" message = json.loads(request.data.decode("utf-8")) + isFreeplayEnabled = message['isFreeplayEnabled'] if flask_app.remote_control_vector: - isFreeplayEnabled = message['isFreeplayEnabled'] connection = flask_app.remote_control_vector.vector.conn - connection.request_control(enable=(not isFreeplayEnabled)) + if isFreeplayEnabled: + connection.release_control() + else: + connection.request_control() return "" @@ -638,6 +773,13 @@ def handle_dropDownSelect(): return "" +@flask_app.route('/animTriggerDropDownSelect', methods=['POST']) +def handle_animTriggerDropDownSelect(): + """Called from Javascript whenever the animTriggerSelector dropdown menu is selected (i.e. modified)""" + message = json.loads(request.data.decode("utf-8")) + selected_anim_trigger_name = message['animTriggerName'] + flask_app.remote_control_vector.selected_anim_trigger_name = selected_anim_trigger_name + return "" @flask_app.route('/sayText', methods=['POST']) def handle_sayText(): @@ -661,14 +803,16 @@ def handle_updateVector(): return "Action Queue:
" + action_queue_text + "\n" return "" - def run(): args = util.parse_command_args() - with anki_vector.AsyncRobot(args.serial, enable_camera_feed=True) as robot: + with anki_vector.AsyncRobot(args.serial, enable_face_detection=True, enable_custom_object_detection=True) as robot: flask_app.remote_control_vector = RemoteControlVector(robot) + flask_app.display_debug_annotations = DebugAnnotations.ENABLED_ALL.value + robot.camera.init_camera_feed() robot.behavior.drive_off_charger() + robot.camera.image_annotator.add_annotator('robotState', RobotStateDisplay) flask_helpers.run_flask(flask_app) diff --git a/examples/experimental/sign_language_recognition_system/README.md b/examples/experimental/sign_language_recognition_system/README.md new file mode 100644 index 0000000..d7d2428 --- /dev/null +++ b/examples/experimental/sign_language_recognition_system/README.md @@ -0,0 +1,93 @@ +# Overview: + +This project passes the robot's camera feed through a [Convolutional Neural Network](https://en.wikipedia.org/wiki/Convolutional_neural_network) (CNN) built to recognize American Sign Language hand signs. The network is built using [Keras](https://keras.io/) with a [TensorFlow](https://www.tensorflow.org/guide/keras) Backend. It predicts the sign visible (if any) per frame and therefore cannot predict more complex sign language gestures. + +>Network Architecture: +>ConvLayer -> MaxPoolLayer -> ConvLayer -> MaxPoolLayer -> ConvLayer -> Dropout -> Flatten -> Dense -> Dropout -> Dense + +The network is built as a Keras [Sequential](https://keras.io/getting-started/sequential-model-guide/) model which consists of a linear stack of layers of the following types: + +ConvLayer (Convolutional layer): https://keras.io/layers/convolutional/ + +MaxPoolLayer (Max Pooling layer): https://keras.io/layers/pooling/ + +Flatten, Dense, Dropout layers: https://keras.io/layers/core/ + +The `data_gen.py` script can be used to build/expand the dataset to train and test the model. Each image captured is used to generate a multiplier number of other images to expand the dataset. The images are translated to 200x200 black and white images to reduce the complexity of the network. While capturing images from the feed ensure that your hand is positioned within the red square which represents the cropped image dimensions. + +>Note: This project has not been tested on Windows and Linux environments. Currently the `data_gen.py` script will not run on Windows due to its dependency on the `curses` library. + + +# Additional Dependencies: + +Install the additional dependencies required for the project: +``` +pip3 install keras +pip3 install numpy +pip3 install scipy +pip3 install scikit-learn +pip3 install tensorflow +``` + +# Dataset: + +### Import Dataset: + +This project includes a sample dataset that can be used as a starting point to train and test the neural network. + +Unpack the dataset: +``` +unzip dataset.zip +``` + +>Note: This dataset contains 200x200 black-and-white images of two hand signs ("a", "b"). Additionally, the `stats.json` file provides the number of images of each alphabet type. + +### Generate Dataset: + +![hand-sign](./example.png) + +Use the following command to capture more images and expand the dataset. Expanding the dataset to include more images can help improve the network's performance since: + + 1. The network may not have seen any reference images to help distinguish your background. Especially if you are testing against a noisy background. + + 2. The dataset only contains images of the same hand and might not identify others that look significantly different. + + +```python3 data_gen.py --dataset_root_folder ``` + +>Note: In order to capture an image, display the hand sign within the red frame on the camera feed displayed and press the key corresponding to the label representing the hand sign. Dimensions of images in the dataset are 200x200. + +>Note: Along with capturing images of hand signs it is also important to capture images of the background without any hand sign in the frame. This ensures that while predicting a frame without any hand signs, it is correctly classified. Use the space bar on your keyboard to record a background image. + + +# Run Project: + +### Project Phases: + +There are two main phases: `train` and `predict`. In the training phase, the neural network is given a set of labeled hand sign images. Once the network has been trained to classify the images we move to the prediction phase. The `predict` option launches the robot's camera feed and looks to classify any visible hand signs. If a frame is classified the recognized letter is spoken out loud. + +### Training: + +Train the neural network using an image dataset to classify different hand signs. + +``` +python3 recognizer.py --train --dataset_root_folder [--model_config ] [--model_weights ] +``` + +>Note: Use the `model_config` and `model_weights` flags to save the model's configurations after it has been trained. This way the model does not need to be re-trained before predicting. + +### Prediction: + +Use a trained neural network to predict any visible hand signs in the robot's field of view (within the region of interest marked with a red square). + +``` +python3 recognizer.py --predict --model_config --model_weights +``` + +>Note: Use the `model_config` and `model_weights` flags to load an existing model's configuration. If not using an existing model's configuration, train the model first. + +### Train and Predict: + +``` +python3 recognizer.py --train --predict --dataset_root_folder +``` \ No newline at end of file diff --git a/examples/experimental/sign_language_recognition_system/data_gen.py b/examples/experimental/sign_language_recognition_system/data_gen.py new file mode 100755 index 0000000..da30bf1 --- /dev/null +++ b/examples/experimental/sign_language_recognition_system/data_gen.py @@ -0,0 +1,157 @@ +# Copyright (c) 2019 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. + +"""Data generation script to build a training and test dataset. + +A sample dataset is included in the project ("dataset.zip"). Unzip the folder and use the +--dataset_root_folder option to specify the file path and expand this dataset. + +Use this script to build/expand the data needed to train the sign language recognition system. +""" + +from concurrent.futures import CancelledError +import curses +import json +import os +import platform +from pathlib import Path +import random +import sys +import tempfile +import time + +try: + import numpy as np +except ImportError as exc: + sys.exit("Cannot import numpy: Do `pip3 install numpy` to install") + +try: + from PIL import Image +except ImportError: + sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") + +try: + from scipy import ndimage +except ImportError as exc: + sys.exit("Cannot import scipy: Do `pip3 install scipy` to install") + +import anki_vector +import util + + +def data_capture(camera: anki_vector.camera.CameraComponent, stats: dict, root_folder: str) -> None: + """Build an image dataset using the camera feed from Vector. + + This method uses an image from the camera and generates a multiplier number of images by + rotating the original image. The keystroke used to initiate the image capture and processing + is used to label the image. + """ + + try: + # TODO: curses works well with Mac OS and Linux, explore msvcrt for Windows + terminal = curses.initscr() + curses.cbreak() + curses.noecho() + terminal.nodelay(True) + + # The number of images to generate using the image captured as a seed + image_multiplier = 10 + # The maximum amount of rotation by which to rotate the original image to generate more images + min_rotation = -10 + max_rotation = 10 + + print("------ capturing hand signs dataset, press ctrl+c to exit ------") + while True: + key = terminal.getch() + if (ord("a") <= key <= ord("z")) or (key == ord(" ")): + + # Represents background images, filenames are switched to be prefixed with "background" instead of " " + if key == ord(" "): + key = "background" + else: + key = chr(key) + + # Pull image from camera + original_image = camera.latest_image.raw_image + if original_image: + # Convert image to black and white + black_white_image = original_image.convert("L") + rotation_axes = [1, 1, 0] + + # Generate more images with random rotation + for rotation in random.sample(range(min_rotation, max_rotation), image_multiplier): + # Randomly define which axis to rotate the image by + random.shuffle(rotation_axes) + x_axis_rotation_enabled, y_axis_rotation_enabled = rotation_axes[:2] + rotated_image_array = ndimage.rotate(black_white_image, + rotation, + axes=(x_axis_rotation_enabled, y_axis_rotation_enabled), + reshape=False) + + # Convert to a 200*200 image + rotated_image = Image.fromarray(rotated_image_array) + cropped_image = util.crop_image(rotated_image, util.NetworkConstants.IMAGE_WIDTH, util.NetworkConstants.IMAGE_HEIGHT) + + # Save the image + image_filename = key + "_" + str(stats.get(key, 0)) + ".png" + stats[key] = stats.get(key, 0) + 1 + cropped_image.save(os.path.join(root_folder, image_filename)) + + # Character + print(f"Recorded images for {key}\n\r") + except (CancelledError, KeyboardInterrupt): + pass + finally: + curses.nocbreak() + curses.echo() + curses.endwin() + + +def main(): + stats = {} + + args = util.parse_command_args() + if not args.dataset_root_folder: + args.dataset_root_folder = str(Path(tempfile.gettempdir(), "dataset")) + print(f"No data folder defined, saving to {args.dataset_root_folder}") + os.makedirs(args.dataset_root_folder, exist_ok=True) + time.sleep(2) + + # Read existing stats or set new stats up + if os.path.isfile(os.path.join(args.dataset_root_folder, "stats.json")): + with open(os.path.join(args.dataset_root_folder, "stats.json"), "r") as stats_file: + stats = json.load(stats_file) + else: + stats = {} + + with anki_vector.Robot(args.serial) as robot: + try: + # Add a rectangular overlay describing the portion of image that is used after cropping. + # TODO: The rectangle overlay should feed in a full rect, not just a size + frame_of_interest = anki_vector.util.RectangleOverlay(util.NetworkConstants.IMAGE_WIDTH, util.NetworkConstants.IMAGE_HEIGHT) + robot.viewer.overlays.append(frame_of_interest) + robot.camera.init_camera_feed() + robot.viewer.show() + data_capture(robot.camera, stats, args.dataset_root_folder) + finally: + with open(os.path.join(args.dataset_root_folder, "stats.json"), "w") as stats_file: + # Save the stats of expanded dataset + json.dump(stats, stats_file) + + # Reset the terminal + print(f"Data collection done!\nData stored in {args.dataset_root_folder}") + + +if __name__ == '__main__': + main() diff --git a/examples/experimental/sign_language_recognition_system/dataset.zip b/examples/experimental/sign_language_recognition_system/dataset.zip new file mode 100644 index 0000000..7ae1605 Binary files /dev/null and b/examples/experimental/sign_language_recognition_system/dataset.zip differ diff --git a/examples/experimental/sign_language_recognition_system/example.png b/examples/experimental/sign_language_recognition_system/example.png new file mode 100644 index 0000000..8161232 Binary files /dev/null and b/examples/experimental/sign_language_recognition_system/example.png differ diff --git a/examples/experimental/sign_language_recognition_system/recognizer.py b/examples/experimental/sign_language_recognition_system/recognizer.py new file mode 100755 index 0000000..829c04b --- /dev/null +++ b/examples/experimental/sign_language_recognition_system/recognizer.py @@ -0,0 +1,300 @@ +# Copyright (c) 2019 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. + +"""Sign language recognition system using the camera feed from Vector.""" + +import asyncio +from concurrent.futures import CancelledError +import os +import sys + +try: + import keras + from keras.layers import Conv2D, Dense, Dropout, Flatten, MaxPooling2D + from keras.models import Sequential, model_from_json + from keras.preprocessing.image import img_to_array +except ImportError as exc: + sys.exit("Cannot import keras: Do `pip3 install --user keras` to install") + +try: + import numpy as np +except ImportError as exc: + sys.exit("Cannot import numpy: Do `pip3 install --user numpy` to install") + +try: + from PIL import Image +except ImportError: + sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") + +try: + from sklearn.model_selection import train_test_split +except ImportError as exc: + sys.exit("Cannot import scikit: Do `pip3 install --user scikit-learn` to install") + +try: + import tensorflow as tf +except ImportError: + sys.exit("Cannot import from tensorflow: Do `pip3 install --user tensorflow` to install") + +import anki_vector +import util + + +class SignLanguageRecognizer(): + """Recognize sign language hand signals using Vector's camera feed. + + A convolutional neural network is used to predict the hand signs. + The network is built with a Keras Sequential model with a TensorFlow backend. + """ + + def __init__(self): + self.training_images: np.ndarray = None + self.training_labels: np.ndarray = None + self.test_images: np.ndarray = None + self.test_labels: np.ndarray = None + self.model: keras.engine.sequential.Sequential = None + self.graph: tf.python.framework.ops.Graph = tf.get_default_graph() + + def load_datasets(self, dataset_root_folder: str) -> None: + """Load the training and test datasets required to train the model. + A sample dataset is included in the project ("dataset.zip"). Unzip the + folder to use it to train the model. + + .. code-block:: python + + recognizer = SignLanguageRecognizer() + recognizer.load_datasets("/path/to/dataset_root_folder") + """ + + if not dataset_root_folder: + sys.exit("Cannot load dataset. Provide valid path with `--dataset_root_folder`") + + images = [] + labels = [] + + for filename in os.listdir(dataset_root_folder): + if filename.endswith(".png"): + # Read black and white image + image = Image.open(os.path.join(dataset_root_folder, filename)) + # Convert image to an array with shape (image_width, image_height, 1) + image_data = img_to_array(image) + images.append(image_data) + + label = filename[0] + if filename.startswith("background"): + # Use the last class to denote an unknown/background image + label = util.NetworkConstants.NUM_CLASSES - 1 + else: + # Use ordinal value offsets to denote labels for all alphabets + label = ord(label) - 97 + labels.append(label) + + # Normalize the image data + images = np.array(images, dtype="float") / 255.0 + # Convert labels to a numpy array + labels = np.array(labels) + + # Split data read in to training and test segments + self.training_images, self.test_images, self.training_labels, self.test_labels = train_test_split(images, labels, test_size=util.NetworkConstants.TEST_SPLIT) + + # Convert array of labels in to binary classs matrix + self.training_labels = keras.utils.to_categorical(self.training_labels, num_classes=util.NetworkConstants.NUM_CLASSES) + self.test_labels = keras.utils.to_categorical(self.test_labels, num_classes=util.NetworkConstants.NUM_CLASSES) + + def create_model(self) -> None: + """Creates a convolutional neural network model with the following architecture: + + ConvLayer -> MaxPoolLayer -> ConvLayer -> MaxPoolLayer -> ConvLayer -> + Dropout -> Flatten -> Dense -> Dropout -> Dense + + .. code-block:: python + + recognizer = SignLanguageRecognizer() + recognizer.load_datasets("/path/to/dataset_root_folder") + recognizer.create_model() + """ + self.model = Sequential() + self.model.add(Conv2D(32, kernel_size=(3, 3), activation="relu", input_shape=(util.NetworkConstants.IMAGE_WIDTH, util.NetworkConstants.IMAGE_HEIGHT, 1))) + self.model.add(MaxPooling2D(pool_size=(2, 2))) + self.model.add(Conv2D(64, kernel_size=(3, 3), activation="relu")) + self.model.add(MaxPooling2D(pool_size=(2, 2))) + self.model.add(Conv2D(64, kernel_size=(3, 3), activation="relu")) + + self.model.add(Dropout(0.25)) + self.model.add(Flatten()) + + self.model.add(Dense(128, activation="relu")) + self.model.add(Dropout(0.5)) + self.model.add(Dense(util.NetworkConstants.NUM_CLASSES, activation="softmax")) + + self.model.compile(loss=keras.losses.categorical_crossentropy, + optimizer=keras.optimizers.Adadelta(), + metrics=['accuracy']) + + def train_model(self, epochs: int = util.NetworkConstants.EPOCHS, verbosity: int = 1) -> None: + """Trains the model off of the training and test data provided + + .. code-block:: python + + recognizer = SignLanguageRecognizer() + recognizer.load_datasets("/path/to/dataset_root_folder") + recognizer.create_model() + recognizer.train_model() + """ + if self.training_images.size == 0 or self.training_labels.size == 0: + sys.exit("Training dataset is empty. Build a dataset with `data_gen.py` before training the model.") + self.model.fit(self.training_images, + self.training_labels, + epochs=epochs, + verbose=verbosity, + validation_split=util.NetworkConstants.VALIDATION_SPLIT) + + def load_model(self, model_config_filename: str, model_weights_filename: str) -> None: + """Loads a saved model's config and weights to rebuild the model rather than create + a new model and re-train. + + .. code-block:: python + + recognizer = SignLanguageRecognizer() + recognizer.load_model("/path/to/model_config_filename", "/path/to/model_weights_filename") + """ + if not model_config_filename or not model_weights_filename: + sys.exit("Cannot load model. Provide valid paths with --model_config and --model_weights.") + json_model = None + with open(model_config_filename, "r") as file: + json_model = file.read() + # Load the network architecture + self.model = model_from_json(json_model) + # Load the weight information and apply it to the model + self.model.load_weights(model_weights_filename) + + self.model.compile(loss=keras.losses.categorical_crossentropy, + optimizer=keras.optimizers.Adadelta(), + metrics=['accuracy']) + + def save_model(self, model_config_filename: str, model_weights_filename: str) -> None: + """Saves a model's config and weights for latter use. + + .. code-block:: python + + recognizer = SignLanguageRecognizer() + recognizer.load_datasets(args.dataset_root_folder) + recognizer.create_model() + recognizer.train_model() + recognizer.save_model("/path/to/model_config_filename", "/path/to/model_weights_filename") + """ + json_model = self.model.to_json() + # Save the network architecture + with open(model_config_filename, "w") as file: + file.write(json_model) + # Save the model's assigned weights + self.model.save_weights(model_weights_filename) + + async def predict_with_camera_feed(self, robot: anki_vector.Robot) -> None: + """Use the camera feed from Vector to detect sign language hand signs by applying a trained + convolutional neural network on to images received from the camera feed. + + .. code-block:: python + + recognizer = SignLanguageRecognizer() + recognizer.load_model("/path/to/model_config_filename", + "/path/to/model_weights_filename") + with anki_vector.Robot(show_viewer=True) as robot: + print("------ predicting hand signs, press ctrl+c to exit early ------") + try: + robot.conn.run_coroutine(recognizer.predict_with_camera_feed(robot)) + except KeyboardInterrupt: + print("------ predicting done ------") + """ + with self.graph.as_default(): + while True: + await asyncio.sleep(2) + + # Get the latest image from the robot's camera feed + camera_image = robot.camera.latest_image.raw_image + + # - Image pre-processing - + # Convert the image into black and white using Pillow + black_white_image = camera_image.convert("L") + # Crop the image to reduce the complexity of the network + cropped_image = util.crop_image(black_white_image, util.NetworkConstants.IMAGE_WIDTH, util.NetworkConstants.IMAGE_HEIGHT) + # Convert image to an array with shape (image_width, image_height, 1) + image = img_to_array(cropped_image) + # Normalize the image data + image = image.astype("float") / 255.0 + # Expand array shape to add an axis to denote the number of images fed as input + image = np.expand_dims(image, axis=0) + + prediction = self.model.predict(image)[0] + prediction = enumerate(prediction) + prediction = sorted(prediction, key=lambda x: x[1], reverse=True)[0] + label = prediction[0] + if label == (util.NetworkConstants.NUM_CLASSES - 1): + label = "No Sign Displayed" + else: + label = chr(label + 97) + prediction = (label, prediction[1] * 100) + print(f"Prediction: {prediction[0]} Confidence: {prediction[1]:.2f}%") + if prediction[0] != "No Sign Displayed": + # If valid prediction is available, use Vector's text-to-speech system to say the + # recognized alphabet out loud + await robot.behavior.say_text(prediction[0]) + + +def main(): + + recognizer = SignLanguageRecognizer() + args = util.parse_command_args() + + if not args.train and not args.predict: + sys.exit("Use flags `--train` or `--predict` to enable the corresponding actions") + + if args.train: + recognizer.load_datasets(args.dataset_root_folder) + recognizer.create_model() + recognizer.train_model() + + # Save the model's configs and weights if the corresponding paths are given + if args.model_config and args.model_weights: + recognizer.save_model(args.model_config, args.model_weights) + + test_score = recognizer.model.evaluate(recognizer.test_images, recognizer.test_labels, verbose=1) + print(f"{recognizer.model.metrics_names[1].capitalize()}: {test_score[1] * 100}%") + + if args.predict: + if not recognizer.model: + # Load the model's configs and weights if the corresponding paths are given + recognizer.load_model(args.model_config, args.model_weights) + + with anki_vector.Robot(args.serial) as robot: + print("------ predicting hand signs, press ctrl+c to exit early ------") + try: + # Add a rectangular overlay describing the portion of image that is used after cropping. + # @TODO: The rectangle overlay should feed in a full rect, not just a size + frame_of_interest = anki_vector.util.RectangleOverlay(util.NetworkConstants.IMAGE_WIDTH, util.NetworkConstants.IMAGE_HEIGHT) + robot.viewer.overlays.append(frame_of_interest) + robot.camera.init_camera_feed() + robot.viewer.show() + + future = robot.conn.run_coroutine(recognizer.predict_with_camera_feed(robot)) + future.result() + except KeyboardInterrupt: + future.cancel() + print("------ predicting done ------") + except CancelledError: + print("------ predicting task cancelled ------") + + +if __name__ == '__main__': + main() diff --git a/examples/experimental/sign_language_recognition_system/util.py b/examples/experimental/sign_language_recognition_system/util.py new file mode 100644 index 0000000..c55508c --- /dev/null +++ b/examples/experimental/sign_language_recognition_system/util.py @@ -0,0 +1,74 @@ +# Copyright (c) 2019 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 sign language recognition system.""" + +import argparse + +from anki_vector import util + +try: + from PIL import Image +except ImportError: + sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install") + + +class NetworkConstants(): # pylint: disable=too-few-public-methods + """Constant values used as image and network parameters.""" + + # Width of images passed to the network + IMAGE_WIDTH: int = 200 + + # Height of images passed to the network + IMAGE_HEIGHT: int = 200 + + # Currently set to 2 alphabet images and 1 background image class + # Number of classes that the network can categorize + NUM_CLASSES: int = 27 + + # The fraction of images passed to the network during training that should + # be used as a validation set. Range: 0 to 1 + VALIDATION_SPLIT: float = 0.1 + + # The fraction of images passed to the network during training that should + # be used as a test set. Range: 0 to 1 + TEST_SPLIT: float = 0.2 + + # Number of epochs on which to train the network + EPOCHS: int = 5 + + +def parse_command_args(): + """Parses command line args""" + parser = argparse.ArgumentParser() + parser.add_argument("--train", action="store_true") + parser.add_argument("--predict", action="store_true") + parser.add_argument("--dataset_root_folder", nargs='?', default=None) + parser.add_argument("--model_config", nargs='?', default=None) + parser.add_argument("--model_weights", nargs='?', default=None) + + return util.parse_command_args(parser) + + +def crop_image(image: Image.Image, target_width: int, target_height: int) -> Image.Image: + """Crops an image to the target width and height""" + image_width, image_height = image.size + + remaining_width = image_width - target_width + remaining_height = image_height - target_height + + return image.crop(((remaining_width // 2), + (remaining_height // 2), + (image_width - (remaining_width // 2)), + (image_height - (remaining_height // 2)))) diff --git a/examples/scripts/take_control.bat b/examples/scripts/take_control.bat new file mode 100755 index 0000000..c8097e0 --- /dev/null +++ b/examples/scripts/take_control.bat @@ -0,0 +1,10 @@ +:: This file is provided as a convenience for initiating reserved behavior +:: control for SDK scripts. Windows users can double-click on this file in +:: Windows Explorer to open a cmd.exe window and execute a Python script so +:: that most default behaviors will be suppressed before/after other SDK +:: scripts execute. This will keep Vector still between SDK scripts. +:: +:: Allowing the script to complete or closing the cmd.exe window will +:: restore behavior control to the robot. + +start python -m anki_vector.reserve_control diff --git a/examples/scripts/take_control.command b/examples/scripts/take_control.command new file mode 100755 index 0000000..8e32673 --- /dev/null +++ b/examples/scripts/take_control.command @@ -0,0 +1,11 @@ +#!/bin/bash +# This file is provided as a convenience for initiating reserved behavior +# control for SDK scripts. Mac users can double-click on this file in +# the Finder to open a Terminal window and execute a Python script so +# that most default behaviors will be suppressed before/after other SDK +# scripts execute. This will keep Vector still between SDK scripts. +# +# Allowing the script to complete or closing the Terminal window will +# restore behavior control to the robot. + +python3 -m anki_vector.reserve_control diff --git a/examples/sounds/vector_alert.wav b/examples/sounds/vector_alert.wav new file mode 100644 index 0000000..29ad112 Binary files /dev/null and b/examples/sounds/vector_alert.wav differ diff --git a/examples/sounds/vector_bell_whistle.wav b/examples/sounds/vector_bell_whistle.wav new file mode 100644 index 0000000..8822b0d Binary files /dev/null and b/examples/sounds/vector_bell_whistle.wav differ diff --git a/examples/tutorials/01_hello_world.py b/examples/tutorials/01_hello_world.py index d7d57ce..322d87d 100755 --- a/examples/tutorials/01_hello_world.py +++ b/examples/tutorials/01_hello_world.py @@ -26,7 +26,7 @@ def main(): args = anki_vector.util.parse_command_args() with anki_vector.Robot(args.serial) as robot: print("Say 'Hello World'...") - robot.say_text("Hello World") + robot.behavior.say_text("Hello World") if __name__ == "__main__": diff --git a/examples/tutorials/04_animation.py b/examples/tutorials/04_animation.py index 614b231..3b20a85 100755 --- a/examples/tutorials/04_animation.py +++ b/examples/tutorials/04_animation.py @@ -14,7 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Play an animation on Vector +"""Play animations on Vector + +Play an animation using a trigger, and then another animation by name. """ import anki_vector @@ -25,10 +27,21 @@ def main(): with anki_vector.Robot(args.serial) as robot: robot.behavior.drive_off_charger() + # Play an animation via a trigger. + # A trigger can pick from several appropriate animations for variety. + print("Playing Animation Trigger 1:") + robot.anim.play_animation_trigger('GreetAfterLongTime') + + # Play the same trigger, but this time ignore the track that plays on the + # body (i.e. don't move the wheels). See the play_animation_trigger documentation + # for other available settings. + print("Playing Animation Trigger 2: (Ignoring the body track)") + robot.anim.play_animation_trigger('GreetAfterLongTime', ignore_body_track=True) + # Play an animation via its name. # # Warning: Future versions of the app might change these, so for future-proofing - # we recommend using play_animation_trigger when it becomes available. + # we recommend using play_animation_trigger above instead. # # See the remote_control.py example in apps for an easy way to see # the available animations. diff --git a/examples/tutorials/05_play_behaviors.py b/examples/tutorials/05_drive_on_off_charger.py similarity index 100% rename from examples/tutorials/05_play_behaviors.py rename to examples/tutorials/05_drive_on_off_charger.py diff --git a/examples/tutorials/06_face_image.py b/examples/tutorials/06_face_image.py index 6106a9c..4502900 100755 --- a/examples/tutorials/06_face_image.py +++ b/examples/tutorials/06_face_image.py @@ -47,8 +47,10 @@ def main(): # Convert the image to the format used by the Screen print("Display image on Vector's face...") screen_data = anki_vector.screen.convert_image_to_screen_data(image_file) - robot.screen.set_screen_with_image_data(screen_data, 4.0) - time.sleep(5) + + duration_s = 4.0 + robot.screen.set_screen_with_image_data(screen_data, duration_s) + time.sleep(duration_s) if __name__ == "__main__": diff --git a/examples/tutorials/09_show_photo.py b/examples/tutorials/08_show_photo.py similarity index 100% rename from examples/tutorials/09_show_photo.py rename to examples/tutorials/08_show_photo.py diff --git a/examples/tutorials/10_eye_color.py b/examples/tutorials/09_eye_color.py similarity index 100% rename from examples/tutorials/10_eye_color.py rename to examples/tutorials/09_eye_color.py diff --git a/examples/tutorials/10_play_audio.py b/examples/tutorials/10_play_audio.py new file mode 100755 index 0000000..499989f --- /dev/null +++ b/examples/tutorials/10_play_audio.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# 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 isvi 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. + +"""Play audio files through Vector's speaker. +""" + +import anki_vector + + +def main(): + args = anki_vector.util.parse_command_args() + with anki_vector.Robot(args.serial) as robot: + # You can find these sounds files here: + # https://github.com/anki/vector-python-sdk/blob/master/examples/sounds/vector_alert.wav + # 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) + + +if __name__ == "__main__": + main() diff --git a/examples/tutorials/08_drive_to_cliff_and_back_up.py b/examples/tutorials/11_drive_to_cliff_and_back_up.py similarity index 100% rename from examples/tutorials/08_drive_to_cliff_and_back_up.py rename to examples/tutorials/11_drive_to_cliff_and_back_up.py diff --git a/examples/tutorials/12_control_priority_level.py b/examples/tutorials/12_control_priority_level.py new file mode 100755 index 0000000..8cbf20f --- /dev/null +++ b/examples/tutorials/12_control_priority_level.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +"""Vector maintains SDK behavior control after being picked up + +During normal operation, SDK programs cannot maintain control over Vector +when he is at a cliff, stuck on an edge or obstacle, tilted or inclined, +picked up, in darkness, etc. + +This script demonstrates how to use the highest level SDK behavior control +ControlPriorityLevel.OVERRIDE_BEHAVIORS_PRIORITY to make Vector perform +actions that normally take priority over the SDK. These commands will not +succeed at ControlPriorityLevel.DEFAULT. +""" + +import sys +import time +import anki_vector +from anki_vector.connection import ControlPriorityLevel + + +def main(): + args = anki_vector.util.parse_command_args() + with anki_vector.Robot(args.serial, behavior_control_level=ControlPriorityLevel.OVERRIDE_BEHAVIORS_PRIORITY) as robot: + robot.behavior.say_text("Pick me up!") + pickup_countdown = 20 + + print("------ waiting for Vector to be picked up, press ctrl+c to exit early ------") + + try: + while not robot.status.is_picked_up and pickup_countdown: + time.sleep(0.5) + pickup_countdown -= 1 + + if not pickup_countdown: + print("Did not get picked up") + sys.exit() + + print("Vector is picked up...") + robot.behavior.say_text("Hold on tight") + print("Setting wheel motors...") + robot.motors.set_wheel_motors(75, -75) + time.sleep(0.5) + robot.motors.set_wheel_motors(-75, 75) + time.sleep(0.5) + robot.motors.set_wheel_motors(0, 0) + + except KeyboardInterrupt: + pass + + +if __name__ == '__main__': + main() diff --git a/examples/tutorials/12_wake_word_subscription.py b/examples/tutorials/12_wake_word_subscription.py deleted file mode 100755 index 7ef8ca3..0000000 --- a/examples/tutorials/12_wake_word_subscription.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 - -# 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. - -"""Wait for Vector to hear "Hey Vector!" and then play an animation. - -The wake_word event only is dispatched when the SDK program has -not requested behavior control. After the robot hears "Hey Vector!" -and the event is received, you can then request behavior control -and control the robot. See the 'requires_behavior_control' method in -connection.py for more information. -""" - -import functools -import threading - -import anki_vector -from anki_vector.events import Events - -wake_word_heard = False - - -def main(): - evt = threading.Event() - - def on_wake_word(robot, event_type, event): - robot.conn.request_control() - - global wake_word_heard - if not wake_word_heard: - wake_word_heard = True - robot.say_text("Hello") - evt.set() - - args = anki_vector.util.parse_command_args() - with anki_vector.Robot(args.serial, requires_behavior_control=False, cache_animation_list=False) as robot: - on_wake_word = functools.partial(on_wake_word, robot) - robot.events.subscribe(on_wake_word, Events.wake_word) - - print('------ Vector is waiting to hear "Hey Vector!" Press ctrl+c to exit early ------') - - try: - if not evt.wait(timeout=10): - print('------ Vector never heard "Hey Vector!" ------') - except KeyboardInterrupt: - pass - - -if __name__ == '__main__': - main() diff --git a/examples/tutorials/13_user_intent_subscription.py b/examples/tutorials/13_user_intent_subscription.py new file mode 100755 index 0000000..5a0b2a8 --- /dev/null +++ b/examples/tutorials/13_user_intent_subscription.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +"""Return information about a voice commands given to Vector + +The user_intent event is only dispatched when the SDK program has +requested behavior control and Vector gets a voice command. + +After the robot hears "Hey Vector! ..." and a valid voice command is given +(for example "...what time is it?") the event will be dispatched and displayed. +""" + +import threading + +import anki_vector +from anki_vector.events import Events +from anki_vector.user_intent import UserIntent + + +def main(): + def on_user_intent(robot, event_type, event, done): + user_intent = UserIntent(event) + print(f"Received {user_intent.intent_event}") + print(user_intent.intent_data) + done.set() + + args = anki_vector.util.parse_command_args() + with anki_vector.Robot(args.serial) as robot: + done = threading.Event() + robot.events.subscribe(on_user_intent, Events.user_intent, done) + + print('------ Vector is waiting for a voice command like "Hey Vector! What time is it?" Press ctrl+c to exit early ------') + + try: + if not done.wait(timeout=10): + print('------ Vector never heard a voice command ------') + except KeyboardInterrupt: + pass + + +if __name__ == '__main__': + main() diff --git a/examples/tutorials/11_face_event_subscription.py b/examples/tutorials/14_face_event_subscription.py similarity index 86% rename from examples/tutorials/11_face_event_subscription.py rename to examples/tutorials/14_face_event_subscription.py index 0391303..b0e6002 100755 --- a/examples/tutorials/11_face_event_subscription.py +++ b/examples/tutorials/14_face_event_subscription.py @@ -23,7 +23,6 @@ he finishes speaking. """ -import functools import threading import anki_vector @@ -34,15 +33,13 @@ def main(): - evt = threading.Event() - - def on_robot_observed_face(robot, event_type, event): + def on_robot_observed_face(robot, event_type, event, done): print("Vector sees a face") global said_text if not said_text: said_text = True - robot.say_text("I see a face!") - evt.set() + robot.behavior.say_text("I see a face!") + done.set() args = anki_vector.util.parse_command_args() with anki_vector.Robot(args.serial, enable_face_detection=True) as robot: @@ -51,13 +48,13 @@ def on_robot_observed_face(robot, event_type, event): robot.behavior.set_head_angle(degrees(45.0)) robot.behavior.set_lift_height(0.0) - on_robot_observed_face = functools.partial(on_robot_observed_face, robot) - robot.events.subscribe(on_robot_observed_face, Events.robot_observed_face) + done = threading.Event() + robot.events.subscribe(on_robot_observed_face, Events.robot_observed_face, done) print("------ waiting for face events, press ctrl+c to exit early ------") try: - if not evt.wait(timeout=5): + if not done.wait(timeout=5): print("------ Vector never saw your face! ------") except KeyboardInterrupt: pass diff --git a/examples/tutorials/15_face_follower.py b/examples/tutorials/15_face_follower.py new file mode 100755 index 0000000..b7c9165 --- /dev/null +++ b/examples/tutorials/15_face_follower.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +"""Make Vector turn toward a face. + +This script shows off the turn_towards_face behavior. It will wait for a face +and then constantly turn towards it to keep it in frame. +""" + +import anki_vector +from anki_vector.events import Events +from anki_vector.util import degrees +import time + +def main(): + args = anki_vector.util.parse_command_args() + with anki_vector.AsyncRobot(args.serial, enable_face_detection=True, show_viewer=True) as robot: + robot.behavior.drive_off_charger() + + # 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) + + face_to_follow = None + + print("------ show vector your face, press ctrl+c to exit early ------") + try: + while True: + turn_future = None + if face_to_follow: + # start turning towards the face + turn_future = robot.behavior.turn_towards_face(face_to_follow) + + if not (face_to_follow and face_to_follow.is_visible): + # find a visible face, timeout if nothing found after a short while + for face in robot.world.visible_faces: + face_to_follow = face + break + + if turn_future != None: + # Complete the turn action if one was in progress + turn_future.result() + + time.sleep(.1) + except KeyboardInterrupt: + pass + +if __name__ == "__main__": + main() diff --git a/examples/tutorials/13_custom_objects.py b/examples/tutorials/16_custom_objects.py similarity index 97% rename from examples/tutorials/13_custom_objects.py rename to examples/tutorials/16_custom_objects.py index a7398a8..7d119b4 100755 --- a/examples/tutorials/13_custom_objects.py +++ b/examples/tutorials/16_custom_objects.py @@ -30,13 +30,13 @@ from anki_vector.objects import CustomObjectMarkers, CustomObjectTypes -def handle_object_appeared(event_type, event): +def handle_object_appeared(robot, event_type, event): # This will be called whenever an EvtObjectAppeared is dispatched - # whenever an Object comes into view. print(f"--------- Vector started seeing an object --------- \n{event.obj}") -def handle_object_disappeared(event_type, event): +def handle_object_disappeared(robot, event_type, event): # This will be called whenever an EvtObjectDisappeared is dispatched - # whenever an Object goes out of view. print(f"--------- Vector stopped seeing an object --------- \n{event.obj}") @@ -48,7 +48,6 @@ def main(): default_logging=False, show_viewer=True, show_3d_viewer=True, - enable_camera_feed=True, enable_custom_object_detection=True, enable_nav_map_feed=True) as robot: # Add event handlers for whenever Vector sees a new object diff --git a/examples/tutorials/17_create_wall.py b/examples/tutorials/17_create_wall.py new file mode 100755 index 0000000..8cda6c2 --- /dev/null +++ b/examples/tutorials/17_create_wall.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# 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. + +"""Use custom objects to create a wall in front of Vector. + +This example demonstrates how you can create custom objects in the world, and +automatically have Vector path around them as if they are real obstacles. + +It creates a wall in front of Vector and tells him to drive around it. +He will plan a path to drive 200mm in front of himself after these objects are created. + +The `show_3d_viewer=True` argument causes the 3D visualizer to open in a new +window - this shows where Vector believes this imaginary object is. +""" + +import anki_vector +from anki_vector.util import Pose, degrees + + +def main(): + args = anki_vector.util.parse_command_args() + with anki_vector.Robot(args.serial, + show_3d_viewer=True, + enable_nav_map_feed=True) as robot: + robot.behavior.drive_off_charger() + + fixed_object = robot.world.create_custom_fixed_object(Pose(100, 0, 0, angle_z=degrees(0)), + 10, 100, 100, relative_to_robot=True) + if fixed_object: + print("fixed custom object created successfully") + + robot.behavior.go_to_pose(Pose(200, 0, 0, angle_z=degrees(0)), relative_to_robot=True) + robot.world.delete_custom_objects() + + +if __name__ == "__main__": + main() diff --git a/examples/tutorials/18_annotate.py b/examples/tutorials/18_annotate.py new file mode 100755 index 0000000..2afe4c7 --- /dev/null +++ b/examples/tutorials/18_annotate.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +'''Display a GUI window showing an annotated camera view. + +Note: + This example requires Python to have Tkinter installed to display the GUI. + +This example uses tkinter to display the annotated camera feed on the screen +and adds a couple of custom annotations of its own using two different methods. +''' + +import asyncio +import sys +import time + +from PIL import ImageDraw + +import anki_vector +from anki_vector import annotate + + +# Define an annotator using the annotator decorator +@annotate.annotator +def clock(image, scale, annotator=None, world=None, **kw): + d = ImageDraw.Draw(image) + bounds = (0, 0, image.width, image.height) + text = annotate.ImageText(time.strftime("%H:%m:%S"), + position=annotate.AnnotationPosition.TOP_LEFT, + outline_color="black") + text.render(d, bounds) + +# Define a new annotator by inheriting the base annotator class +class Battery(annotate.Annotator): + def __init__(self, img_annotator, box_color=None): + super().__init__(img_annotator) + self.battery_state = None + self.battery_state_task = None + if box_color is not None: + self.box_color = box_color + + def apply(self, image, scale): + d = ImageDraw.Draw(image) + bounds = (0, 0, image.width, image.height) + + if not self.battery_state_task: + self.battery_state_task = self.world.robot.get_battery_state() + + if asyncio.isfuture(self.battery_state_task) and self.battery_state_task.done(): + self.battery_state = self.battery_state_task.result() + self.battery_state_task = self.world.robot.get_battery_state() + + if self.battery_state: + batt = self.battery_state.battery_volts + text = annotate.ImageText(f"BATT {batt:.1f}v", color="green", outline_color="black") + text.render(d, bounds) + + +def main(): + args = anki_vector.util.parse_command_args() + with anki_vector.Robot(args.serial, show_viewer=True, enable_face_detection=True) as robot: + robot.camera.image_annotator.add_static_text("text", "Vec-Cam", position=annotate.AnnotationPosition.TOP_RIGHT) + robot.camera.image_annotator.add_annotator("clock", clock) + robot.camera.image_annotator.add_annotator("battery", Battery) + + time.sleep(10) + + print("Turning off all annotations for 5 seconds") + robot.camera.image_annotator.annotation_enabled = False + time.sleep(5) + + print("Re-enabling all annotations") + robot.camera.image_annotator.annotation_enabled = True + + print("------ Press ctrl+c to exit early ------") + + try: + # Shutdown the program after 30 seconds + time.sleep(30) + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt index b015a67..509c1f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,5 @@ cryptography flask googleapis-common-protos numpy>=1.11 -opencv-python>=3.4 Pillow>=3.3 requests \ No newline at end of file diff --git a/setup.py b/setup.py index bc8de24..9121cf6 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ def get_requirements(): ], zip_safe=True, keywords='anki vector robot robotics sdk ai vision'.split(), - packages=['anki_vector', 'anki_vector.configure', 'anki_vector.messaging', 'anki_vector.opengl'], + 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']