3636
3737__all__ = ["MAX_HEAD_ANGLE" , "MIN_HEAD_ANGLE" ,
3838 "MAX_LIFT_HEIGHT" , "MAX_LIFT_HEIGHT_MM" , "MIN_LIFT_HEIGHT" , "MIN_LIFT_HEIGHT_MM" ,
39- "BehaviorComponent" ]
39+ "BehaviorComponent" , "ReserveBehaviorControl" ]
4040
4141
4242from . import connection , faces , objects , util
@@ -69,9 +69,6 @@ class BehaviorComponent(util.Component):
6969
7070 _next_behavior_id = protocol .FIRST_SDK_TAG
7171
72- def __init__ (self , robot ):
73- super ().__init__ (robot )
74-
7572 @classmethod
7673 def _get_next_behavior_id (cls ):
7774 # Post increment _current_behavior_id (and loop within the SDK_TAG range)
@@ -555,7 +552,7 @@ async def set_lift_height(self,
555552 lift_future = robot.behavior.set_lift_height(0.0)
556553 time.sleep(1.0)
557554 lift_future = robot.behavior.set_lift_height(1.0)
558- lift_future.cancel()
555+ lift_future.cancel()
559556 """
560557 if height < 0.0 :
561558 self .logger .warning ("lift height %s too small, should be in 0..1 range - clamping" , height )
@@ -603,7 +600,7 @@ async def turn_towards_face(self,
603600
604601 with anki_vector.Robot() as robot:
605602 turn_towards_face_future = robot.behavior.turn_towards_face(1)
606- turn_towards_face_future.cancel()
603+ turn_towards_face_future.cancel()
607604 """
608605 turn_towards_face_request = protocol .TurnTowardsFaceRequest (face_id = face .face_id ,
609606 max_turn_angle_rad = util .degrees (180 ).radians ,
@@ -652,10 +649,10 @@ async def go_to_object(self,
652649
653650 @connection .on_connection_thread (is_cancellable_behavior = True )
654651 async def roll_cube (self ,
655- target_object : objects .LightCube ,
656- approach_angle : util .Angle = None ,
657- num_retries : int = 0 ,
658- _behavior_id : int = None ) -> protocol .RollObjectResponse :
652+ target_object : objects .LightCube ,
653+ approach_angle : util .Angle = None ,
654+ num_retries : int = 0 ,
655+ _behavior_id : int = None ) -> protocol .RollObjectResponse :
659656 """Tells Vector to roll a specified cube object.
660657
661658 :param target_object: The cube to roll.
@@ -685,23 +682,23 @@ async def roll_cube(self,
685682 approach_angle = util .degrees (0 )
686683 else :
687684 use_approach_angle = True
688- approach_angle = approach_angle
685+ approach_angle = approach_angle
689686
690687 roll_object_request = protocol .RollObjectRequest (object_id = target_object .object_id ,
691- approach_angle_rad = approach_angle .radians ,
692- use_approach_angle = use_approach_angle ,
693- use_pre_dock_pose = use_approach_angle ,
694- id_tag = _behavior_id ,
695- num_retries = num_retries )
688+ approach_angle_rad = approach_angle .radians ,
689+ use_approach_angle = use_approach_angle ,
690+ use_pre_dock_pose = use_approach_angle ,
691+ id_tag = _behavior_id ,
692+ num_retries = num_retries )
696693
697694 return await self .grpc_interface .RollObject (roll_object_request )
698695
699696 @connection .on_connection_thread (is_cancellable_behavior = True )
700697 async def pop_a_wheelie (self ,
701- target_object : objects .LightCube ,
702- approach_angle : util .Angle = None ,
703- num_retries : int = 0 ,
704- _behavior_id : int = None ) -> protocol .PopAWheelieResponse :
698+ target_object : objects .LightCube ,
699+ approach_angle : util .Angle = None ,
700+ num_retries : int = 0 ,
701+ _behavior_id : int = None ) -> protocol .PopAWheelieResponse :
705702 """Tells Vector to "pop a wheelie" using his light cube.
706703
707704 :param target_object: The cube to push down on with Vector's lift, to start the wheelie.
@@ -731,23 +728,23 @@ async def pop_a_wheelie(self,
731728 approach_angle = util .degrees (0 )
732729 else :
733730 use_approach_angle = True
734- approach_angle = approach_angle
731+ approach_angle = approach_angle
735732
736733 pop_a_wheelie_request = protocol .PopAWheelieRequest (object_id = target_object .object_id ,
737- approach_angle_rad = approach_angle .radians ,
738- use_approach_angle = use_approach_angle ,
739- use_pre_dock_pose = use_approach_angle ,
740- id_tag = _behavior_id ,
741- num_retries = num_retries )
734+ approach_angle_rad = approach_angle .radians ,
735+ use_approach_angle = use_approach_angle ,
736+ use_pre_dock_pose = use_approach_angle ,
737+ id_tag = _behavior_id ,
738+ num_retries = num_retries )
742739
743740 return await self .grpc_interface .PopAWheelie (pop_a_wheelie_request )
744741
745742 @connection .on_connection_thread (is_cancellable_behavior = True )
746743 async def pickup_object (self ,
747- target_object : objects .LightCube ,
748- use_pre_dock_pose : bool = True ,
749- num_retries : int = 0 ,
750- _behavior_id : int = None ) -> protocol .PickupObjectResponse :
744+ target_object : objects .LightCube ,
745+ use_pre_dock_pose : bool = True ,
746+ num_retries : int = 0 ,
747+ _behavior_id : int = None ) -> protocol .PickupObjectResponse :
751748 """Instruct the robot to pick up his LightCube.
752749
753750 While picking up the cube, Vector will use path planning.
@@ -786,8 +783,8 @@ async def pickup_object(self,
786783
787784 @connection .on_connection_thread (is_cancellable_behavior = True )
788785 async def place_object_on_ground_here (self ,
789- num_retries : int = 0 ,
790- _behavior_id : int = None ) -> protocol .PlaceObjectOnGroundHereResponse :
786+ num_retries : int = 0 ,
787+ _behavior_id : int = None ) -> protocol .PlaceObjectOnGroundHereResponse :
791788 """Ask Vector to place the object he is carrying on the ground at the current location.
792789
793790 :param num_retries: Number of times to reattempt action in case of a failure.
@@ -809,4 +806,96 @@ async def place_object_on_ground_here(self,
809806 place_object_on_ground_here_request = protocol .PlaceObjectOnGroundHereRequest (id_tag = _behavior_id ,
810807 num_retries = num_retries )
811808
812- return await self .grpc_interface .PlaceObjectOnGroundHere (place_object_on_ground_here_request )
809+ return await self .grpc_interface .PlaceObjectOnGroundHere (place_object_on_ground_here_request )
810+
811+
812+ class ReserveBehaviorControl ():
813+ """A ReserveBehaviorControl object can be used to suppress the ordinary idle behaviors of
814+ the Robot and keep Vector still between SDK control instances. Care must be taken when
815+ blocking background behaviors, as this may make Vector appear non-responsive.
816+
817+ This class is most easily used via a built-in SDK script, and can be called on the command-line
818+ via the executable module :class:`anki_vector.reserve_control`:
819+
820+ .. code-block:: bash
821+
822+ python3 -m anki_vector.reserve_control
823+
824+ As long as the script is running, background behaviors will not activate, keeping Vector
825+ still while other SDK scripts may take control. Highest-level behaviors like returning to
826+ the charger due to low battery will still activate.
827+
828+ System-specific shortcuts calling this executable module can be found in the examples/scripts
829+ folder. These scripts can be double-clicked to easily reserve behavior control for the current
830+ SDK default robot.
831+
832+ If there is a need to keep background behaviors from activating in a single script, the class
833+ may be used to reserve behavior control while in scope:
834+
835+ .. code-block:: python
836+
837+ import anki_vector
838+ from anki_vector import behavior
839+
840+ args = anki_vector.util.parse_command_args()
841+ with behavior.ReserveBehaviorControl(args.serial):
842+
843+ # At this point, Vector will remain still, even without
844+ # a Robot instance being in scope.
845+
846+ ...
847+
848+ # take control of the robot as usual
849+ with anki_vector.Robot() as robot:
850+
851+ robot.anim.play_animation("anim_turn_left_01")
852+
853+ # Robot will not perform idle behaviors until the script completes
854+
855+ ...
856+
857+ :param serial: Vector's serial number. The robot's serial number (ex. 00e20100) is located on
858+ the underside of Vector, or accessible from Vector's debug screen. Used to
859+ identify which Vector configuration to load.
860+ :param ip: Vector's IP address. (optional)
861+ :param config: A custom :class:`dict` to override values in Vector's configuration. (optional)
862+ Example: :code:`{"cert": "/path/to/file.cert", "name": "Vector-XXXX", "guid": "<secret_key>"}`
863+ where :code:`cert` is the certificate to identify Vector, :code:`name` is the
864+ name on Vector's face when his backpack is double-clicked on the charger, and
865+ :code:`guid` is the authorization token that identifies the SDK user.
866+ Note: Never share your authentication credentials with anyone.
867+ :param behavior_activation_timeout: The time to wait for control of the robot before failing.
868+ """
869+
870+ def __init__ (self ,
871+ serial : str = None ,
872+ ip : str = None ,
873+ config : dict = None ,
874+ behavior_activation_timeout : int = 10 ):
875+ config = config if config is not None else {}
876+ self .logger = util .get_class_logger (__name__ , self )
877+ config = {** util .read_configuration (serial , self .logger ), ** config }
878+ self ._name = config ["name" ]
879+ self ._ip = ip if ip is not None else config ["ip" ]
880+ self ._cert_file = config ["cert" ]
881+ self ._guid = config ["guid" ]
882+
883+ self ._port = "443"
884+ if 'port' in config :
885+ self ._port = config ["port" ]
886+
887+ if self ._name is None or self ._ip is None or self ._cert_file is None or self ._guid is None :
888+ 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 "
889+ "You may also provide the values necessary for connection through the config parameter. ex: "
890+ '{"name":"Vector-XXXX", "ip":"XX.XX.XX.XX", "cert":"/path/to/cert_file", "guid":"<secret_key>"}' )
891+
892+ self ._conn = connection .Connection (self ._name , ':' .join ([self ._ip , self ._port ]), self ._cert_file , self ._guid ,
893+ behavior_control_level = connection .CONTROL_PRIORITY_LEVEL .RESERVE_CONTROL )
894+ self ._behavior_activation_timeout = behavior_activation_timeout
895+
896+ def __enter__ (self ):
897+ self ._conn .connect (self ._behavior_activation_timeout )
898+ return self
899+
900+ def __exit__ (self , exc_type , exc_val , exc_tb ):
901+ self ._conn .close ()
0 commit comments