Skip to content

Commit ae55ef4

Browse files
committed
Merge branch 'main' into modular-graphnav
2 parents 9b0eedf + dca6c69 commit ae55ef4

5 files changed

Lines changed: 192 additions & 53 deletions

File tree

spot_wrapper/spot_arm.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from bosdyn.client.time_sync import TimeSyncEndpoint
2222
from bosdyn.util import seconds_to_duration
2323

24-
from spot_wrapper.wrapper_helpers import RobotState
24+
from spot_wrapper.wrapper_helpers import RobotState, ClaimAndPowerDecorator
2525

2626

2727
class SpotArm:
@@ -34,13 +34,19 @@ def __init__(
3434
manipulation_api_client: ManipulationApiClient,
3535
robot_state_client: RobotStateClient,
3636
max_command_duration: float,
37+
claim_and_power_decorator: ClaimAndPowerDecorator,
3738
) -> None:
3839
"""
3940
Constructor for SpotArm class.
4041
Args:
4142
robot: Robot object
4243
logger: Logger object
44+
robot_state: Object containing the robot's state as controlled by the wrapper
45+
robot_command_client: Command client to use to send commands to the robot
46+
manipulation_api_client: Command client to send manipulation commands to the robot
47+
robot_state_client: Client to retrieve state of the robot
4348
max_command_duration: Maximum duration for commands when using the manipulation command method
49+
claim_and_power_decorator: Object to use to decorate the functions on this object
4450
"""
4551
self._robot = robot
4652
self._logger = logger
@@ -49,6 +55,23 @@ def __init__(
4955
self._robot_command_client = robot_command_client
5056
self._manipulation_api_client = manipulation_api_client
5157
self._robot_state_client = robot_state_client
58+
self._claim_and_power_decorator = claim_and_power_decorator
59+
self._claim_and_power_decorator.decorate_functions(
60+
self,
61+
decorated_funcs=[
62+
self.ensure_arm_power_and_stand,
63+
self.arm_stow,
64+
self.arm_unstow,
65+
self.arm_carry,
66+
self.arm_joint_move,
67+
self.force_trajectory,
68+
self.gripper_open,
69+
self.gripper_close,
70+
self.gripper_angle_open,
71+
self.hand_pose,
72+
self.grasp_3d,
73+
],
74+
)
5275

5376
def _manipulation_request(
5477
self,

spot_wrapper/spot_docking.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
from bosdyn.client.docking import DockingClient, blocking_dock_robot, blocking_undock
77
from bosdyn.client.robot import Robot
88

9-
from spot_wrapper.wrapper_helpers import RobotState, RobotCommandData
9+
from spot_wrapper.wrapper_helpers import (
10+
RobotState,
11+
RobotCommandData,
12+
ClaimAndPowerDecorator,
13+
)
1014

1115

1216
class SpotDocking:
@@ -22,13 +26,20 @@ def __init__(
2226
command_data: RobotCommandData,
2327
docking_client: DockingClient,
2428
robot_command_client: robot_command.RobotCommandClient,
29+
claim_and_power_decorator: ClaimAndPowerDecorator,
2530
) -> None:
2631
self._robot = robot
2732
self._logger = logger
2833
self._command_data = command_data
2934
self._docking_client: DockingClient = docking_client
3035
self._robot_command_client = robot_command_client
3136
self._robot_state = robot_state
37+
self._claim_and_power_decorator = claim_and_power_decorator
38+
# Decorate the functions so that they take the lease. Dock function needs to power on because it might have
39+
# to move the robot, the undock
40+
self._claim_and_power_decorator.decorate_functions(
41+
self, decorated_funcs=[self.dock, self.undock]
42+
)
3243

3344
def dock(self, dock_id: int) -> typing.Tuple[bool, str]:
3445
"""Dock the robot to the docking station with fiducial ID [dock_id]."""

spot_wrapper/spot_images.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,12 @@ class ImageQualityConfig:
114114
Dataclass to store configuration of image quality. Default values are the default for the build_image_request
115115
"""
116116

117-
DEFAULT_QUALITY = 75
117+
DEFAULT_QUALITY = 75.0
118118

119-
robot_depth_quality: int = DEFAULT_QUALITY
120-
robot_image_quality: int = DEFAULT_QUALITY
121-
hand_image_quality: int = DEFAULT_QUALITY
122-
hand_depth_quality: int = DEFAULT_QUALITY
119+
robot_depth_quality: float = DEFAULT_QUALITY
120+
robot_image_quality: float = DEFAULT_QUALITY
121+
hand_image_quality: float = DEFAULT_QUALITY
122+
hand_depth_quality: float = DEFAULT_QUALITY
123123

124124

125125
class SpotImages:

spot_wrapper/wrapper.py

Lines changed: 41 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
from .spot_images import SpotImages
8282
from .spot_world_objects import SpotWorldObjects
8383

84-
from .wrapper_helpers import RobotCommandData, RobotState
84+
from .wrapper_helpers import RobotCommandData, RobotState, ClaimAndPowerDecorator
8585

8686
"""Service name for getting pointcloud of VLP16 connected to Spot Core"""
8787
point_cloud_sources = ["velodyne-point-cloud"]
@@ -376,38 +376,6 @@ def _start_query(self):
376376
pass
377377

378378

379-
def try_claim(func=None, *, power_on=False):
380-
"""
381-
Decorator which tries to acquire the lease before executing the wrapped function
382-
383-
the _func=None and * args are required to allow this decorator to be used with or without arguments
384-
385-
Args:
386-
func: Function that is being wrapped
387-
power_on: If true, power on after claiming the lease
388-
389-
Returns:
390-
Decorator which will wrap the decorated function
391-
"""
392-
# If this decorator is being used without the power_on arg, return it as if it was called with that arg specified
393-
if func is None:
394-
return functools.partial(try_claim, power_on=power_on)
395-
396-
@functools.wraps(func)
397-
def wrapper_try_claim(self, *args, **kwargs):
398-
if self._get_lease_on_action:
399-
if power_on:
400-
# Power on is also wrapped by this decorator so if we request power on the lease will also be claimed
401-
response = self.power_on()
402-
else:
403-
response = self.claim()
404-
if not response[0]:
405-
return response
406-
return func(self, *args, **kwargs)
407-
408-
return wrapper_try_claim
409-
410-
411379
class SpotWrapper:
412380
"""Generic wrapper class to encompass release 1.1.4 API features as well as maintaining leases automatically"""
413381

@@ -456,7 +424,10 @@ def __init__(
456424
self._rates = rates or {}
457425
self._callbacks = callbacks or {}
458426
self._use_take_lease = use_take_lease
459-
self._get_lease_on_action = get_lease_on_action
427+
self._claim_decorator = ClaimAndPowerDecorator(
428+
self.power_on, self.claim, get_lease_on_action
429+
)
430+
self.decorate_functions()
460431
self._continually_try_stand = continually_try_stand
461432
self._rgb_cameras = rgb_cameras
462433
self._frame_prefix = ""
@@ -650,6 +621,7 @@ def __init__(
650621
self._manipulation_api_client,
651622
self._robot_state_client,
652623
MAX_COMMAND_DURATION,
624+
self._claim_decorator,
653625
)
654626
else:
655627
self._spot_arm = None
@@ -663,6 +635,7 @@ def __init__(
663635
self._command_data,
664636
self._docking_client,
665637
self._robot_command_client,
638+
self._claim_decorator,
666639
)
667640

668641
self._spot_graph_nav = SpotGraphNav(
@@ -707,6 +680,40 @@ def __init__(
707680
self._robot_id = None
708681
self._lease = None
709682

683+
def decorate_functions(self):
684+
"""
685+
Many of the functions in the wrapper need to have the lease claimed and the robot powered on before they will
686+
function. The TryClaimDecorator object includes a decorator which is the mechanism we use to make sure that
687+
is the case, assuming the get_lease_on_action variable is true. Otherwise, it is up to the user to ensure
688+
that the lease is claimed and the power is on before running commands, otherwise the commands will fail.
689+
"""
690+
decorated_funcs = [
691+
self.stop,
692+
self.self_right,
693+
self.sit,
694+
self.simple_stand,
695+
self.stand,
696+
self.battery_change_pose,
697+
self.velocity_cmd,
698+
self.trajectory_cmd,
699+
self.navigate_to,
700+
self._navigate_to,
701+
self._navigate_route,
702+
self.execute_dance,
703+
self._robot_command,
704+
self._manipulation_request,
705+
]
706+
decorated_funcs_no_power = [
707+
self.stop,
708+
self.power_on,
709+
self.safe_power_off,
710+
self.toggle_power,
711+
]
712+
713+
self._claim_decorator.decorate_functions(
714+
self, decorated_funcs, decorated_funcs_no_power
715+
)
716+
710717
@staticmethod
711718
def authenticate(
712719
robot: Robot, username: str, password: str, logger: logging.Logger
@@ -1147,7 +1154,6 @@ def _manipulation_request(
11471154
self._logger.error(f"Unable to execute manipulation command: {e}")
11481155
return False, str(e), None
11491156

1150-
@try_claim
11511157
def stop(self) -> typing.Tuple[bool, str]:
11521158
"""
11531159
Stop any action the robot is currently doing.
@@ -1159,7 +1165,6 @@ def stop(self) -> typing.Tuple[bool, str]:
11591165
response = self._robot_command(RobotCommandBuilder.stop_command())
11601166
return response[0], response[1]
11611167

1162-
@try_claim(power_on=True)
11631168
def self_right(self) -> typing.Tuple[bool, str]:
11641169
"""
11651170
Have the robot self-right.
@@ -1170,7 +1175,6 @@ def self_right(self) -> typing.Tuple[bool, str]:
11701175
response = self._robot_command(RobotCommandBuilder.selfright_command())
11711176
return response[0], response[1]
11721177

1173-
@try_claim(power_on=True)
11741178
def sit(self) -> typing.Tuple[bool, str]:
11751179
"""
11761180
Stop the robot's motion and sit down if able.
@@ -1183,7 +1187,6 @@ def sit(self) -> typing.Tuple[bool, str]:
11831187
self.last_sit_command = response[2]
11841188
return response[0], response[1]
11851189

1186-
@try_claim(power_on=True)
11871190
def simple_stand(self, monitor_command: bool = True) -> typing.Tuple[bool, str]:
11881191
"""
11891192
If the e-stop is enabled, and the motor power is enabled, stand the robot up.
@@ -1198,7 +1201,6 @@ def simple_stand(self, monitor_command: bool = True) -> typing.Tuple[bool, str]:
11981201
self.last_stand_command = response[2]
11991202
return response[0], response[1]
12001203

1201-
@try_claim(power_on=True)
12021204
def stand(
12031205
self,
12041206
monitor_command: bool = True,
@@ -1242,7 +1244,6 @@ def stand(
12421244
self.last_stand_command = response[2]
12431245
return response[0], response[1]
12441246

1245-
@try_claim(power_on=True)
12461247
def battery_change_pose(self, dir_hint: int = 1) -> typing.Tuple[bool, str]:
12471248
"""
12481249
Put the robot into the battery change pose
@@ -1260,7 +1261,6 @@ def battery_change_pose(self, dir_hint: int = 1) -> typing.Tuple[bool, str]:
12601261
return response[0], response[1]
12611262
return False, "Call sit before trying to roll over"
12621263

1263-
@try_claim
12641264
def safe_power_off(self) -> typing.Tuple[bool, str]:
12651265
"""
12661266
Stop the robot's motion and sit if possible. Once sitting, disable motor power.
@@ -1288,7 +1288,6 @@ def clear_behavior_fault(
12881288
except Exception as e:
12891289
return False, f"Exception while clearing behavior fault: {e}", None
12901290

1291-
@try_claim
12921291
def power_on(self) -> typing.Tuple[bool, str]:
12931292
"""
12941293
Enable the motor power if e-stop is enabled.
@@ -1325,7 +1324,6 @@ def get_mobility_params(self) -> spot_command_pb2.MobilityParams:
13251324
"""Get mobility params"""
13261325
return self._mobility_params
13271326

1328-
@try_claim
13291327
def velocity_cmd(
13301328
self, v_x: float, v_y: float, v_rot: float, cmd_duration: float = 0.125
13311329
) -> typing.Tuple[bool, str]:
@@ -1353,7 +1351,6 @@ def velocity_cmd(
13531351
self.last_velocity_command_time = end_time
13541352
return response[0], response[1]
13551353

1356-
@try_claim
13571354
def trajectory_cmd(
13581355
self,
13591356
goal_x: float,
@@ -1462,7 +1459,6 @@ def get_manipulation_command_feedback(self, cmd_id):
14621459
manipulation_api_feedback_request=feedback_request
14631460
)
14641461

1465-
@try_claim
14661462
def toggle_power(self, should_power_on):
14671463
"""Power the robot on/off dependent on the current power state."""
14681464
is_powered_on = self.check_is_powered_on()
@@ -1500,7 +1496,6 @@ def check_is_powered_on(self) -> bool:
15001496
self._powered_on = power_state.motor_power_state == power_state.STATE_ON
15011497
return self._powered_on
15021498

1503-
@try_claim
15041499
def execute_dance(self, data):
15051500
if self._is_licensed_for_choreography:
15061501
return self._spot_dance.execute_dance(data)

0 commit comments

Comments
 (0)