Skip to content

Commit 807a618

Browse files
BruceVonKMichelle Sintov
authored andcommitted
Bruce/VIC-3793 Highest Level SDK Behavior Control (#117)
Updated the robot/connection logic for using the `Connection.CONTROL_PRIORITY_LEVEL` instead of a simple `bool` for requesting behavior control. **This will break people** who have existing `Robot` or `Connection` code that declines behavior control.
1 parent b65659c commit 807a618

7 files changed

Lines changed: 95 additions & 49 deletions

File tree

anki_vector/connection.py

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@
5252

5353
class CONTROL_PRIORITY_LEVEL(Enum):
5454
"""Enum used to specify the priority level for the program."""
55-
55+
#: Runs above mandatory physical reactions, will drive off table, perform while on a slope,
56+
#: in the dark, etc.
57+
OVERRIDE_BEHAVIORS_PRIORITY = protocol.ControlRequest.OVERRIDE_BEHAVIORS # pylint: disable=no-member
5658
#: Runs below Mandatory Physical Reactions such as tucking Vector's head and arms during a fall,
57-
#: yet above Trigger-Word Detection.
58-
TOP_PRIORITY_AI = protocol.ControlRequest.TOP_PRIORITY_AI # pylint: disable=no-member
59+
#: yet above Trigger-Word Detection. Default for normal operation.
60+
DEFAULT_PRIORITY = protocol.ControlRequest.DEFAULT # pylint: disable=no-member
5961

6062

6163
class _ControlEventManager:
@@ -109,7 +111,7 @@ def is_shutdown(self) -> bool:
109111
"""Detect if the behavior control stream is supposed to shut down."""
110112
return self._is_shutdown
111113

112-
def request(self, priority: CONTROL_PRIORITY_LEVEL = CONTROL_PRIORITY_LEVEL.TOP_PRIORITY_AI) -> None:
114+
def request(self, priority: CONTROL_PRIORITY_LEVEL = CONTROL_PRIORITY_LEVEL.DEFAULT_PRIORITY) -> None:
113115
"""Tell the behavior stream to request control via setting the :class:`request_event`.
114116
115117
This will signal Connection's :func:`_request_handler` generator to send a request control message on the BehaviorControl stream.
@@ -192,10 +194,11 @@ async def play_animation():
192194
:param host: The IP address and port of Vector in the format "XX.XX.XX.XX:443".
193195
:param cert_file: The location of the certificate file on disk.
194196
:param guid: Your robot's unique secret key.
195-
:param requires_behavior_control: True if the connection requires behavior control.
197+
:param behavior_control_level: pass one of :class:`CONTROL_PRIORITY_LEVEL` priority levels if the connection
198+
requires behavior control, or None to decline control.
196199
"""
197200

198-
def __init__(self, name: str, host: str, cert_file: str, guid: str, requires_behavior_control: bool = True):
201+
def __init__(self, name: str, host: str, cert_file: str, guid: str, behavior_control_level: CONTROL_PRIORITY_LEVEL = CONTROL_PRIORITY_LEVEL.DEFAULT_PRIORITY):
199202
if cert_file is None:
200203
raise VectorConfigurationException("Must provide a cert file to authenticate to Vector.")
201204
self._loop: asyncio.BaseEventLoop = None
@@ -213,7 +216,7 @@ def __init__(self, name: str, host: str, cert_file: str, guid: str, requires_beh
213216
self._ready_signal: threading.Event = threading.Event()
214217
self._done_signal: asyncio.Event = None
215218
self._conn_exception = False
216-
self._requires_behavior_control = requires_behavior_control
219+
self._behavior_control_level = behavior_control_level
217220
self.active_commands = []
218221

219222
@property
@@ -285,6 +288,27 @@ async def play_animation():
285288
"""
286289
return self._interface
287290

291+
@property
292+
def behavior_control_level(self) -> CONTROL_PRIORITY_LEVEL:
293+
"""Returns the specific :class:`CONTROL_PRIORITY_LEVEL` requested for behavior control.
294+
295+
To be able to directly control Vector's motors, override his screen, play an animation, etc.,
296+
the :class:`Connection` will need behavior control. This property identifies the enumerated
297+
level of behavior control that the SDK will maintain over the robot.
298+
299+
For more information about behavior control, see :ref:`behavior <behavior>`.
300+
301+
.. code-block:: python
302+
303+
import anki_vector
304+
305+
with anki_vector.Robot() as robot:
306+
print(robot.conn.behavior_control_level) # Will print CONTROL_PRIORITY_LEVEL.DEFAULT_PRIORITY
307+
robot.conn.release_control()
308+
print(robot.conn.behavior_control_level) # Will print None
309+
"""
310+
return self._behavior_control_level
311+
288312
@property
289313
def requires_behavior_control(self) -> bool:
290314
"""True if the :class:`Connection` requires behavior control.
@@ -308,14 +332,14 @@ def callback(robot, event_type, event):
308332
robot.anim.play_animation_trigger('GreetAfterLongTime')
309333
robot.conn.release_control()
310334
311-
with anki_vector.Robot(requires_behavior_control=False) as robot:
335+
with anki_vector.Robot(behavior_control_level=None) as robot:
312336
print(robot.conn.requires_behavior_control) # Will print False
313337
robot.events.subscribe(callback, anki_vector.events.Events.robot_observed_face)
314338
315339
# Waits 10 seconds. Show Vector your face.
316340
time.sleep(10)
317341
"""
318-
return self._requires_behavior_control
342+
return self._behavior_control_level is not None
319343

320344
@property
321345
def control_lost_event(self) -> asyncio.Event:
@@ -347,7 +371,7 @@ async def wait_for_control(conn: anki_vector.connection.Connection):
347371
"""
348372
return self._control_events.granted_event
349373

350-
def request_control(self, timeout: float = 10.0):
374+
def request_control(self, behavior_control_level: CONTROL_PRIORITY_LEVEL = CONTROL_PRIORITY_LEVEL.DEFAULT_PRIORITY, timeout: float = 10.0):
351375
"""Explicitly request behavior control. Typically used after detecting :func:`control_lost_event`.
352376
353377
To be able to directly control Vector's motors, override his screen, play an animation, etc.,
@@ -366,14 +390,18 @@ async def auto_reconnect(conn: anki_vector.connection.Connection):
366390
conn.request_control(timeout=5.0)
367391
368392
:param timeout: The time allotted to attempt a connection, in seconds.
393+
:param behavior_control_level: request control of Vector's behavior system at a specific level of control.
394+
See :class:`CONTROL_PRIORITY_LEVEL` for more information.
369395
"""
396+
if not isinstance(behavior_control_level, CONTROL_PRIORITY_LEVEL):
397+
raise TypeError("behavior_control_level must be of type CONTROL_PRIORITY_LEVEL")
370398
if self._thread is threading.current_thread():
371-
return asyncio.ensure_future(self._request_control(timeout=timeout), loop=self._loop)
372-
return self.run_coroutine(self._request_control(timeout=timeout))
399+
return asyncio.ensure_future(self._request_control(behavior_control_level=behavior_control_level, timeout=timeout), loop=self._loop)
400+
return self.run_coroutine(self._request_control(behavior_control_level=behavior_control_level, timeout=timeout))
373401

374-
async def _request_control(self, timeout: float = 10.0):
375-
self._requires_behavior_control = True
376-
self._control_events.request()
402+
async def _request_control(self, behavior_control_level: CONTROL_PRIORITY_LEVEL = CONTROL_PRIORITY_LEVEL.DEFAULT_PRIORITY, timeout: float = 10.0):
403+
self._behavior_control_level = behavior_control_level
404+
self._control_events.request(self._behavior_control_level)
377405
try:
378406
self._has_control = await asyncio.wait_for(self.control_granted_event.wait(), timeout)
379407
except futures.TimeoutError as e:
@@ -403,7 +431,7 @@ async def wait_for_control(conn: anki_vector.connection.Connection):
403431
return self.run_coroutine(self._release_control(timeout=timeout))
404432

405433
async def _release_control(self, timeout: float = 10.0):
406-
self._requires_behavior_control = False
434+
self._behavior_control_level = None
407435
self._control_events.release()
408436
try:
409437
self._has_control = await asyncio.wait_for(self.control_lost_event.wait(), timeout)
@@ -456,10 +484,10 @@ def _connect(self, timeout: float) -> None:
456484
self._loop = asyncio.new_event_loop()
457485
asyncio.set_event_loop(self._loop)
458486
self._done_signal = asyncio.Event()
459-
if not self._requires_behavior_control:
487+
if not self._behavior_control_level:
460488
self._control_events = _ControlEventManager(self._loop)
461489
else:
462-
self._control_events = _ControlEventManager(self._loop, priority=CONTROL_PRIORITY_LEVEL.TOP_PRIORITY_AI)
490+
self._control_events = _ControlEventManager(self._loop, priority=self._behavior_control_level)
463491
trusted_certs = None
464492
with open(self.cert_file, 'rb') as cert:
465493
trusted_certs = cert.read()
@@ -505,8 +533,8 @@ def _connect(self, timeout: float) -> None:
505533
cpu_version=cpu_version)
506534
self._loop.run_until_complete(self._interface.SDKInitialization(initialize))
507535

508-
if self._requires_behavior_control:
509-
self._loop.run_until_complete(self._request_control(timeout=timeout))
536+
if self._behavior_control_level:
537+
self._loop.run_until_complete(self._request_control(behavior_control_level=self._behavior_control_level, timeout=timeout))
510538
except Exception as e: # pylint: disable=broad-except
511539
# Propagate the errors to the calling thread
512540
setattr(self._ready_signal, "exception", e)

anki_vector/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class VectorControlException(VectorException):
6767

6868
def __init__(self, function):
6969
msg = (f"Unable to run '{function}' because it requires behavior control.\n\n"
70-
"Make sure to request control from Vector either by providing the 'enable_behavior_control' parameter to Robot, "
70+
"Make sure to request control from Vector either by providing the 'behavior_control_level' parameter to Robot, "
7171
"or directly call 'request_control()' on your connection.")
7272
super().__init__(msg)
7373

anki_vector/messaging/behavior.proto

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ message ControlRequest {
3232
enum Priority {
3333
// Unknown priority. Used for versions that don't understand old priority levels.
3434
UNKNOWN = 0;
35-
// Highest priority level. Directly under mandatory physical reactions.
36-
TOP_PRIORITY_AI = 20;
35+
// Highest priority level. Suppresses most automatic physical reactions, use with caution.
36+
OVERRIDE_BEHAVIORS = 10;
37+
// Normal priority level. Directly under mandatory physical reactions.
38+
DEFAULT = 20;
3739
}
3840
// Where in the behavior tree the SDK code should be executed.
3941
Priority priority = 1;

anki_vector/messaging/behavior_pb2.py

Lines changed: 16 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

anki_vector/robot.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@
3030
from pathlib import Path
3131

3232
from . import (animation, audio, behavior, camera,
33-
connection, events, faces, motors,
34-
nav_map, screen, photos, proximity,
35-
status, touch, util, viewer, vision,
36-
world)
33+
events, faces, motors, nav_map, screen,
34+
photos, proximity, status, touch, util,
35+
viewer, vision, world)
36+
from .connection import (Connection,
37+
on_connection_thread,
38+
CONTROL_PRIORITY_LEVEL)
3739
from .exceptions import (VectorConfigurationException,
3840
VectorNotReadyException,
3941
VectorPropertyValueNotReadyException,
@@ -95,7 +97,9 @@ class Robot:
9597
:param enable_nav_map_feed: Turn navigation map feed on/off.
9698
:param show_viewer: Specifies whether to display a view of Vector's camera in a window.
9799
:param show_3d_viewer: Specifies whether to display a 3D view of Vector's understanding of the world in a window.
98-
:param requires_behavior_control: Request control of Vector's behavior system."""
100+
:param behavior_control_level: Request control of Vector's behavior system at a specific level of control. Pass
101+
:code:`None` if behavior control is not needed.
102+
See :class:`CONTROL_PRIORITY_LEVEL` for more information."""
99103

100104
def __init__(self,
101105
serial: str = None,
@@ -110,7 +114,7 @@ def __init__(self,
110114
enable_nav_map_feed: bool = None,
111115
show_viewer: bool = False,
112116
show_3d_viewer: bool = False,
113-
requires_behavior_control: bool = True):
117+
behavior_control_level: CONTROL_PRIORITY_LEVEL = CONTROL_PRIORITY_LEVEL.DEFAULT_PRIORITY):
114118
if default_logging:
115119
util.setup_basic_logging()
116120
self.logger = util.get_class_logger(__name__, self)
@@ -133,7 +137,7 @@ def __init__(self,
133137
'{"name":"Vector-XXXX", "ip":"XX.XX.XX.XX", "cert":"/path/to/cert_file", "guid":"<secret_key>"}')
134138

135139
#: :class:`anki_vector.connection.Connection`: The active connection to the robot.
136-
self._conn = connection.Connection(self._name, ':'.join([self._ip, self._port]), self._cert_file, self._guid, requires_behavior_control=requires_behavior_control)
140+
self._conn = Connection(self._name, ':'.join([self._ip, self._port]), self._cert_file, self._guid, behavior_control_level=behavior_control_level)
137141
self._events = events.EventHandler(self)
138142

139143
# placeholders for components before they exist
@@ -224,7 +228,7 @@ def force_async(self) -> bool:
224228
return self._force_async
225229

226230
@property
227-
def conn(self) -> connection.Connection:
231+
def conn(self) -> Connection:
228232
"""A reference to the :class:`~anki_vector.connection.Connection` instance."""
229233
return self._conn
230234

@@ -756,7 +760,7 @@ def __enter__(self):
756760
def __exit__(self, exc_type, exc_val, exc_tb):
757761
self.disconnect()
758762

759-
@connection.on_connection_thread(requires_control=False)
763+
@on_connection_thread(requires_control=False)
760764
async def get_battery_state(self) -> protocol.BatteryStateResponse:
761765
"""Check the current state of the robot and cube batteries.
762766
@@ -789,7 +793,7 @@ async def get_battery_state(self) -> protocol.BatteryStateResponse:
789793
get_battery_state_request = protocol.BatteryStateRequest()
790794
return await self.conn.grpc_interface.BatteryState(get_battery_state_request)
791795

792-
@connection.on_connection_thread(requires_control=False)
796+
@on_connection_thread(requires_control=False)
793797
async def get_version_state(self) -> protocol.VersionStateResponse:
794798
"""Get the versioning information for Vector, including Vector's os_version and engine_build_id.
795799
@@ -897,7 +901,9 @@ async def callback(robot, event_type, event):
897901
:param enable_nav_map_feed: Turn navigation map feed on/off.
898902
:param show_viewer: Specifies whether to display a view of Vector's camera in a window.
899903
:param show_3d_viewer: Specifies whether to display a 3D view of Vector's understanding of the world in a window.
900-
:param requires_behavior_control: Request control of Vector's behavior system."""
904+
:param behavior_control_level: Request control of Vector's behavior system at a specific level of control. Pass
905+
:code:`None` if behavior control is not needed.
906+
See :class:`CONTROL_PRIORITY_LEVEL` for more information."""
901907

902908
@functools.wraps(Robot.__init__)
903909
def __init__(self, *args, **kwargs):

docs/source/proto.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,9 +1167,15 @@ <h3 id="Anki.Vector.external_interface.ControlRequest.Priority">ControlRequest.P
11671167
</tr>
11681168

11691169
<tr>
1170-
<td>TOP_PRIORITY_AI</td>
1170+
<td>OVERRIDE_BEHAVIORS</td>
1171+
<td>10</td>
1172+
<td><p>Highest priority level. Suppresses most automatic physical reactions, use with caution.</p></td>
1173+
</tr>
1174+
1175+
<tr>
1176+
<td>DEFAULT</td>
11711177
<td>20</td>
1172-
<td><p>Highest priority level. Directly under mandatory physical reactions.</p></td>
1178+
<td><p>Normal priority level. Directly under mandatory physical reactions.</p></td>
11731179
</tr>
11741180

11751181
</tbody>

examples/tutorials/12_wake_word_subscription.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def on_wake_word(robot, event_type, event, done):
4242
done.set()
4343

4444
args = anki_vector.util.parse_command_args()
45-
with anki_vector.Robot(args.serial, requires_behavior_control=False) as robot:
45+
with anki_vector.Robot(args.serial, behavior_control_level=None) as robot:
4646
done = threading.Event()
4747
robot.events.subscribe(on_wake_word, Events.wake_word, done)
4848

0 commit comments

Comments
 (0)