bosdyn-client 3.3.2__py3-none-any.whl → 4.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bosdyn/client/__init__.py +5 -6
- bosdyn/client/area_callback_region_handler_base.py +19 -4
- bosdyn/client/area_callback_service_servicer.py +29 -1
- bosdyn/client/area_callback_service_utils.py +45 -51
- bosdyn/client/auth.py +13 -28
- bosdyn/client/autowalk.py +3 -0
- bosdyn/client/channel.py +23 -26
- bosdyn/client/command_line.py +64 -13
- bosdyn/client/common.py +4 -4
- bosdyn/client/data_acquisition.py +47 -6
- bosdyn/client/data_acquisition_plugin.py +12 -2
- bosdyn/client/data_acquisition_plugin_service.py +33 -2
- bosdyn/client/data_acquisition_store.py +38 -0
- bosdyn/client/data_buffer.py +22 -8
- bosdyn/client/data_chunk.py +1 -0
- bosdyn/client/directory_registration.py +1 -14
- bosdyn/client/exceptions.py +0 -4
- bosdyn/client/frame_helpers.py +3 -1
- bosdyn/client/gps/NMEAParser.py +189 -0
- bosdyn/client/gps/__init__.py +6 -0
- bosdyn/client/gps/aggregator_client.py +56 -0
- bosdyn/client/gps/gps_listener.py +153 -0
- bosdyn/client/gps/registration_client.py +48 -0
- bosdyn/client/graph_nav.py +50 -20
- bosdyn/client/image.py +20 -7
- bosdyn/client/image_service_helpers.py +14 -14
- bosdyn/client/lease.py +27 -22
- bosdyn/client/lease_validator.py +5 -5
- bosdyn/client/manipulation_api_client.py +1 -1
- bosdyn/client/map_processing.py +10 -5
- bosdyn/client/math_helpers.py +21 -11
- bosdyn/client/metrics_logging.py +147 -0
- bosdyn/client/network_compute_bridge_client.py +6 -0
- bosdyn/client/power.py +40 -0
- bosdyn/client/recording.py +3 -3
- bosdyn/client/robot.py +15 -16
- bosdyn/client/robot_command.py +341 -203
- bosdyn/client/robot_id.py +6 -5
- bosdyn/client/robot_state.py +6 -0
- bosdyn/client/sdk.py +5 -11
- bosdyn/client/server_util.py +11 -11
- bosdyn/client/service_customization_helpers.py +776 -64
- bosdyn/client/signals_helpers.py +105 -0
- bosdyn/client/spot_cam/compositor.py +6 -2
- bosdyn/client/spot_cam/ptz.py +24 -14
- bosdyn/client/spot_check.py +160 -0
- bosdyn/client/time_sync.py +5 -5
- bosdyn/client/units_helpers.py +39 -0
- bosdyn/client/util.py +100 -64
- bosdyn/client/world_object.py +5 -5
- {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/METADATA +4 -3
- bosdyn_client-4.0.1.dist-info/RECORD +97 -0
- {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/WHEEL +1 -1
- bosdyn/client/log_annotation.py +0 -359
- bosdyn_client-3.3.2.dist-info/RECORD +0 -90
- {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Copyright (c) 2023 Boston Dynamics, Inc. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Downloading, reproducing, distributing or otherwise using the SDK Software
|
|
4
|
+
# is subject to the terms and conditions of the Boston Dynamics Software
|
|
5
|
+
# Development Kit License (20191101-BDSDK-SL).
|
|
6
|
+
|
|
7
|
+
"""Helpers for working with DAQ plugins and signals.proto."""
|
|
8
|
+
from bosdyn.api.alerts_pb2 import AlertData
|
|
9
|
+
from bosdyn.api.data_acquisition_pb2 import LiveDataResponse
|
|
10
|
+
from bosdyn.api.signals_pb2 import AlertConditionSpec, Signal, SignalData, SignalSpec
|
|
11
|
+
from bosdyn.client.units_helpers import units_to_string
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_max_alert_spec(value: float, severity: AlertData.SeverityLevel) -> AlertConditionSpec:
|
|
15
|
+
"""Builds a max AlertConditionSpec.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
value(float): Max threshold.
|
|
19
|
+
severity(str): Severity of alert.
|
|
20
|
+
Returns:
|
|
21
|
+
AlertConditionSpec
|
|
22
|
+
"""
|
|
23
|
+
alert = AlertConditionSpec()
|
|
24
|
+
alert.max = value
|
|
25
|
+
alert.alert_data.severity = severity
|
|
26
|
+
return alert
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_simple_signal(name: str, value: float, units: str, max_warning: float = None,
|
|
30
|
+
max_critical: float = None) -> Signal:
|
|
31
|
+
"""Builds a simple signal with a float value, string units, and optional max alerts.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
name(str): Name of the signal.
|
|
35
|
+
value(float): Signal data value.
|
|
36
|
+
units(str): Simple units.
|
|
37
|
+
max_warning(float): Max warning threshold.
|
|
38
|
+
max_critical(float): Max critical threshold.
|
|
39
|
+
Returns:
|
|
40
|
+
Signal
|
|
41
|
+
"""
|
|
42
|
+
# Bundle spec/data together as a signal.
|
|
43
|
+
signal = Signal()
|
|
44
|
+
|
|
45
|
+
# Setup signal specification.
|
|
46
|
+
signal_spec = signal.signal_spec
|
|
47
|
+
signal_spec.info.name = name
|
|
48
|
+
signal_spec.sensor.units.name = units
|
|
49
|
+
if max_warning:
|
|
50
|
+
alert_spec_1 = build_max_alert_spec(max_warning,
|
|
51
|
+
AlertData.SeverityLevel.SEVERITY_LEVEL_WARN)
|
|
52
|
+
signal_spec.alerts.append(alert_spec_1)
|
|
53
|
+
if max_critical:
|
|
54
|
+
alert_spec_2 = build_max_alert_spec(max_critical,
|
|
55
|
+
AlertData.SeverityLevel.SEVERITY_LEVEL_CRITICAL)
|
|
56
|
+
signal_spec.alerts.append(alert_spec_2)
|
|
57
|
+
|
|
58
|
+
# Add value to signal data.
|
|
59
|
+
signal.signal_data.data.double = value
|
|
60
|
+
|
|
61
|
+
return signal
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_capability_live_data(signals: dict,
|
|
65
|
+
capability_name: str) -> LiveDataResponse.CapabilityLiveData:
|
|
66
|
+
"""Takes a dictionary of signals and copies them into a CapabilityLiveData message.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
signals(dict[str, Signal]): A dictionary of signal id to Signal.
|
|
70
|
+
capability_name(str): The capability name.
|
|
71
|
+
Returns:
|
|
72
|
+
CapabilityLiveData
|
|
73
|
+
"""
|
|
74
|
+
capability_live_data = LiveDataResponse.CapabilityLiveData()
|
|
75
|
+
capability_live_data.name = capability_name
|
|
76
|
+
capability_live_data.status = LiveDataResponse.CapabilityLiveData.Status.STATUS_OK
|
|
77
|
+
for signal_id, signal in signals.items():
|
|
78
|
+
capability_live_data.signals[signal_id].CopyFrom(signal)
|
|
79
|
+
return capability_live_data
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def build_live_data_response(live_data_capabilities: list) -> LiveDataResponse:
|
|
83
|
+
"""Takes a list of CapabilityLiveData and adds them to a LiveDataResponse.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
live_data_capabilities(list[LiveDataResponse.CapabilityLiveData]): A list of CapabilityLiveData.
|
|
87
|
+
Returns:
|
|
88
|
+
LiveDataResponse
|
|
89
|
+
"""
|
|
90
|
+
response = LiveDataResponse()
|
|
91
|
+
response.live_data.extend(live_data_capabilities)
|
|
92
|
+
return response
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_data(signal_data: SignalData):
|
|
96
|
+
"""Checks type of SignalData and returns the value.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
signal_data(SignalData): Signal data.
|
|
100
|
+
Returns:
|
|
101
|
+
The data value.
|
|
102
|
+
"""
|
|
103
|
+
return getattr(signal_data.data, signal_data.data.WhichOneof('value'), None)
|
|
104
|
+
|
|
105
|
+
|
|
@@ -119,7 +119,9 @@ class CompositorClient(BaseClient):
|
|
|
119
119
|
kwargs: extra arguments for controlling RPC details.
|
|
120
120
|
"""
|
|
121
121
|
coords = compositor_pb2.IrMeterOverlay.NormalizedCoordinates(x=x, y=y)
|
|
122
|
-
|
|
122
|
+
# setting both coords and meter fields for backwards compatibility
|
|
123
|
+
overlay = compositor_pb2.IrMeterOverlay(enable=enable, coords=coords, meter=[coords],
|
|
124
|
+
unit=unit)
|
|
123
125
|
request = compositor_pb2.SetIrMeterOverlayRequest(overlay=overlay)
|
|
124
126
|
return self.call(self._stub.SetIrMeterOverlay, request, self._return_response,
|
|
125
127
|
self._compositor_error_from_response, copy_request=False, **kwargs)
|
|
@@ -127,7 +129,9 @@ class CompositorClient(BaseClient):
|
|
|
127
129
|
def set_ir_meter_overlay_async(self, x, y, enable, unit, **kwargs):
|
|
128
130
|
"""Async version of set_ir_meter_overlay()"""
|
|
129
131
|
coords = compositor_pb2.IrMeterOverlay.NormalizedCoordinates(x=x, y=y)
|
|
130
|
-
|
|
132
|
+
# setting both coords and meter fields for backwards compatibility
|
|
133
|
+
overlay = compositor_pb2.IrMeterOverlay(enable=enable, coords=coords, meter=[coords],
|
|
134
|
+
unit=unit)
|
|
131
135
|
request = compositor_pb2.SetIrMeterOverlayRequest(overlay=overlay)
|
|
132
136
|
return self.call_async(self._stub.SetIrMeterOverlay, request, self._return_response,
|
|
133
137
|
self._compositor_error_from_response, copy_request=False, **kwargs)
|
bosdyn/client/spot_cam/ptz.py
CHANGED
|
@@ -160,8 +160,18 @@ class PtzClient(BaseClient):
|
|
|
160
160
|
SetPtzFocusStateResponse indicating whether the call was successful
|
|
161
161
|
"""
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
if focus_position is not None:
|
|
164
|
+
focus_position_val = Int32Value(value=focus_position)
|
|
165
|
+
approx_distance = None
|
|
166
|
+
elif distance is not None:
|
|
167
|
+
approx_distance = FloatValue(value=distance)
|
|
168
|
+
focus_position_val = None
|
|
169
|
+
else:
|
|
170
|
+
raise ValueError("One of distance or focus_position must be specified.")
|
|
171
|
+
|
|
172
|
+
ptz_focus_state = ptz_pb2.PtzFocusState(mode=focus_mode, approx_distance=approx_distance,
|
|
173
|
+
focus_position=focus_position_val)
|
|
174
|
+
request = ptz_pb2.SetPtzFocusStateRequest(focus_state=ptz_focus_state)
|
|
165
175
|
return self.call(self._stub.SetPtzFocusState, request,
|
|
166
176
|
self._set_ptz_focus_state_from_response, common_header_errors,
|
|
167
177
|
copy_request=False, **kwargs)
|
|
@@ -169,22 +179,22 @@ class PtzClient(BaseClient):
|
|
|
169
179
|
def set_ptz_focus_state_async(self, focus_mode, distance=None, focus_position=None, **kwargs):
|
|
170
180
|
"""Async version of set_ptz_focus_state()"""
|
|
171
181
|
|
|
172
|
-
|
|
173
|
-
|
|
182
|
+
if focus_position is not None:
|
|
183
|
+
focus_position_val = Int32Value(value=focus_position)
|
|
184
|
+
approx_distance = None
|
|
185
|
+
elif distance is not None:
|
|
186
|
+
approx_distance = FloatValue(value=distance)
|
|
187
|
+
focus_position_val = None
|
|
188
|
+
else:
|
|
189
|
+
raise ValueError("One of distance or focus_position must be specified.")
|
|
190
|
+
|
|
191
|
+
ptz_focus_state = ptz_pb2.PtzFocusState(mode=focus_mode, approx_distance=approx_distance,
|
|
192
|
+
focus_position=focus_position_val)
|
|
193
|
+
request = ptz_pb2.SetPtzFocusStateRequest(focus_state=ptz_focus_state)
|
|
174
194
|
return self.call_async(self._stub.SetPtzFocusState, request,
|
|
175
195
|
self._set_ptz_focus_state_from_response, common_header_errors,
|
|
176
196
|
copy_request=False, **kwargs)
|
|
177
197
|
|
|
178
|
-
@staticmethod
|
|
179
|
-
def _make_set_ptz_focus_state_request(focus_mode, distance, focus_position):
|
|
180
|
-
request = ptz_pb2.SetPtzFocusStateRequest()
|
|
181
|
-
request.focus_state.mode = focus_mode
|
|
182
|
-
if focus_position is not None:
|
|
183
|
-
request.focus_state.focus_position.value = focus_position
|
|
184
|
-
elif distance is not None:
|
|
185
|
-
request.focus_state.approx_distance.value = distance
|
|
186
|
-
return request
|
|
187
|
-
|
|
188
198
|
@staticmethod
|
|
189
199
|
def _list_ptz_from_response(response):
|
|
190
200
|
return response.ptzs
|
bosdyn/client/spot_check.py
CHANGED
|
@@ -103,6 +103,46 @@ class CameraCalibrationTimedOutError(Exception):
|
|
|
103
103
|
"""Timed out waiting for SUCCESS response from calibration."""
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
class GripperCameraCalibrationResponseError(SpotCheckError):
|
|
107
|
+
"""General class of errors for gripper camera calibration routines."""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class GripperCameraCalibrationUserCanceledError(GripperCameraCalibrationResponseError):
|
|
111
|
+
"""API client canceled calibration."""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class GripperCameraCalibrationPowerError(GripperCameraCalibrationResponseError):
|
|
115
|
+
"""The robot is not powered on."""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class GripperCameraCalibrationLeaseError(GripperCameraCalibrationResponseError):
|
|
119
|
+
"""The Lease is invalid."""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class GripperCameraCalibrationTargetNotCenteredError(GripperCameraCalibrationResponseError):
|
|
123
|
+
"""Invalid starting configuration of robot."""
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class GripperCameraCalibrationTargetUpsideDownError(GripperCameraCalibrationResponseError):
|
|
127
|
+
"""The target is incorrectly oriented."""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class GripperCameraCalibrationCalibrationError(GripperCameraCalibrationResponseError):
|
|
131
|
+
"""Calibration algorithm failure occurred."""
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class GripperCameraCalibrationInitializationError(GripperCameraCalibrationResponseError):
|
|
135
|
+
"""Initialization error occurred ."""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class GripperCameraCalibrationInternalError(GripperCameraCalibrationResponseError):
|
|
139
|
+
"""Internal error occurred ."""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class GripperCameraCalibrationStuckError(GripperCameraCalibrationResponseError):
|
|
143
|
+
"""Timed out waiting for robot to reach goal pose."""
|
|
144
|
+
|
|
145
|
+
|
|
106
146
|
class SpotCheckClient(BaseClient):
|
|
107
147
|
"""A client for verifying robot health and running calibration routines."""
|
|
108
148
|
default_service_name = 'spot-check'
|
|
@@ -170,6 +210,42 @@ class SpotCheckClient(BaseClient):
|
|
|
170
210
|
return self.call_async(self._stub.CameraCalibrationFeedback, request, None,
|
|
171
211
|
_calibration_feedback_error_from_response, **kwargs)
|
|
172
212
|
|
|
213
|
+
def gripper_camera_calibration_command(self, request, **kwargs):
|
|
214
|
+
"""Issue a gripper camera calibration command to the robot.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
Error on header error or lease use result error.
|
|
218
|
+
"""
|
|
219
|
+
return self.call(self._stub.GripperCameraCalibrationCommand, request, None,
|
|
220
|
+
_gripper_calibration_command_error_from_response, **kwargs)
|
|
221
|
+
|
|
222
|
+
def gripper_camera_calibration_command_async(self, request, **kwargs):
|
|
223
|
+
"""Async version of gripper_camera_calibration_command()
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
Error on header error or lease use result error.
|
|
227
|
+
"""
|
|
228
|
+
return self.call_async(self._stub.GripperCameraCalibrationCommand, request, None,
|
|
229
|
+
_gripper_calibration_command_error_from_response, **kwargs)
|
|
230
|
+
|
|
231
|
+
def gripper_camera_calibration_feedback(self, request, **kwargs):
|
|
232
|
+
"""Check the current status of gripper camera calibration.
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
GripperCameraCalibrationResponseError on any feedback error.
|
|
236
|
+
"""
|
|
237
|
+
return self.call(self._stub.GripperCameraCalibrationFeedback, request, None,
|
|
238
|
+
_gripper_calibration_feedback_error_from_response, **kwargs)
|
|
239
|
+
|
|
240
|
+
def gripper_camera_calibration_feedback_async(self, request, **kwargs):
|
|
241
|
+
"""Async version of gripper_camera_calibration_feedback()
|
|
242
|
+
|
|
243
|
+
Raises:
|
|
244
|
+
GripperCameraCalibrationResponseError on any feedback error.
|
|
245
|
+
"""
|
|
246
|
+
return self.call_async(self._stub.GripperCameraCalibrationFeedback, request, None,
|
|
247
|
+
_gripper_calibration_feedback_error_from_response, **kwargs)
|
|
248
|
+
|
|
173
249
|
|
|
174
250
|
def run_spot_check(spot_check_client, lease, timeout_sec=212, update_frequency=0.25, verbose=False):
|
|
175
251
|
"""Run full spot check routine. The robot should be sitting on flat ground when this routine is
|
|
@@ -364,3 +440,87 @@ def _cal_status_error_from_response(response):
|
|
|
364
440
|
status_to_error=_CAL_STATUS_TO_ERROR)
|
|
365
441
|
|
|
366
442
|
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# Gripper Camera calibration error handlers.
|
|
446
|
+
@handle_common_header_errors
|
|
447
|
+
@handle_lease_use_result_errors
|
|
448
|
+
def _gripper_calibration_command_error_from_response(response):
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@handle_common_header_errors
|
|
453
|
+
def _gripper_calibration_feedback_error_from_response(response):
|
|
454
|
+
# Special handling of lease case.
|
|
455
|
+
if response.status == spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_LEASE_ERROR:
|
|
456
|
+
return LeaseUseError(response, None)
|
|
457
|
+
return _gcal_status_error_from_response(response)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
_GCAL_STATUS_TO_ERROR = collections.defaultdict(lambda:
|
|
461
|
+
(GripperCameraCalibrationResponseError, None))
|
|
462
|
+
_GCAL_STATUS_TO_ERROR.update({ # noqa
|
|
463
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_SUCCESS: (
|
|
464
|
+
None,
|
|
465
|
+
None,
|
|
466
|
+
),
|
|
467
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_PROCESSING: (
|
|
468
|
+
None,
|
|
469
|
+
None,
|
|
470
|
+
),
|
|
471
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_USER_CANCELED: (
|
|
472
|
+
GripperCameraCalibrationUserCanceledError,
|
|
473
|
+
GripperCameraCalibrationUserCanceledError.__doc__,
|
|
474
|
+
),
|
|
475
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_NEVER_RUN: (
|
|
476
|
+
None,
|
|
477
|
+
None,
|
|
478
|
+
),
|
|
479
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_POWER_ERROR: (
|
|
480
|
+
GripperCameraCalibrationPowerError,
|
|
481
|
+
GripperCameraCalibrationPowerError.__doc__,
|
|
482
|
+
),
|
|
483
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_LEASE_ERROR: (
|
|
484
|
+
GripperCameraCalibrationLeaseError,
|
|
485
|
+
GripperCameraCalibrationLeaseError.__doc__,
|
|
486
|
+
),
|
|
487
|
+
|
|
488
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_TARGET_NOT_CENTERED: (
|
|
489
|
+
GripperCameraCalibrationTargetNotCenteredError,
|
|
490
|
+
GripperCameraCalibrationTargetNotCenteredError.__doc__,
|
|
491
|
+
),
|
|
492
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_TARGET_NOT_IN_VIEW: (
|
|
493
|
+
GripperCameraCalibrationTargetNotCenteredError,
|
|
494
|
+
GripperCameraCalibrationTargetNotCenteredError.__doc__,
|
|
495
|
+
),
|
|
496
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_TARGET_UPSIDE_DOWN: (
|
|
497
|
+
GripperCameraCalibrationTargetUpsideDownError,
|
|
498
|
+
GripperCameraCalibrationTargetUpsideDownError.__doc__,
|
|
499
|
+
),
|
|
500
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_INITIALIZATION_ERROR: (
|
|
501
|
+
GripperCameraCalibrationInitializationError,
|
|
502
|
+
GripperCameraCalibrationInitializationError.__doc__,
|
|
503
|
+
),
|
|
504
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_INTERNAL_ERROR: (
|
|
505
|
+
GripperCameraCalibrationInternalError,
|
|
506
|
+
GripperCameraCalibrationInternalError.__doc__,
|
|
507
|
+
),
|
|
508
|
+
spot_check_pb2.GripperCameraCalibrationFeedbackResponse.STATUS_STUCK: (
|
|
509
|
+
GripperCameraCalibrationStuckError,
|
|
510
|
+
GripperCameraCalibrationStuckError.__doc__,
|
|
511
|
+
),
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@handle_unset_status_error(
|
|
516
|
+
unset="STATUS_UNKNOWN",
|
|
517
|
+
statustype=spot_check_pb2.GripperCameraCalibrationFeedbackResponse,
|
|
518
|
+
)
|
|
519
|
+
def _gcal_status_error_from_response(response):
|
|
520
|
+
"""Return a custom exception based on response, None if no error."""
|
|
521
|
+
return error_factory(
|
|
522
|
+
response,
|
|
523
|
+
response.status,
|
|
524
|
+
status_to_string=spot_check_pb2.GripperCameraCalibrationFeedbackResponse.Status.Name,
|
|
525
|
+
status_to_error=_GCAL_STATUS_TO_ERROR,
|
|
526
|
+
)
|
bosdyn/client/time_sync.py
CHANGED
|
@@ -18,7 +18,7 @@ from google.protobuf import duration_pb2
|
|
|
18
18
|
|
|
19
19
|
from bosdyn.api import time_sync_pb2, time_sync_service_pb2_grpc
|
|
20
20
|
from bosdyn.api.time_range_pb2 import TimeRange
|
|
21
|
-
from bosdyn.util import (RobotTimeConverter, now_nsec, nsec_to_timestamp, parse_timespan,
|
|
21
|
+
from bosdyn.util import (RobotTimeConverter, now_nsec, now_sec, nsec_to_timestamp, parse_timespan,
|
|
22
22
|
set_timestamp_from_nsec, timestamp_to_nsec)
|
|
23
23
|
|
|
24
24
|
from .common import BaseClient, common_header_errors
|
|
@@ -342,8 +342,8 @@ class TimeSyncThread:
|
|
|
342
342
|
# When time-sync service is not yet ready, poll it at this interval
|
|
343
343
|
TIME_SYNC_SERVICE_NOT_READY_INTERVAL_SEC = 5
|
|
344
344
|
|
|
345
|
-
def __init__(self, time_sync_client):
|
|
346
|
-
self._time_sync_endpoint = TimeSyncEndpoint(time_sync_client)
|
|
345
|
+
def __init__(self, time_sync_client, time_sync_endpoint=None):
|
|
346
|
+
self._time_sync_endpoint = time_sync_endpoint or TimeSyncEndpoint(time_sync_client)
|
|
347
347
|
self._lock = Lock()
|
|
348
348
|
self._locked_time_sync_interval_sec = self.DEFAULT_TIME_SYNC_INTERVAL_SEC
|
|
349
349
|
self._locked_should_exit = False # Used to tell the thread to stop running.
|
|
@@ -412,11 +412,11 @@ class TimeSyncThread:
|
|
|
412
412
|
"""
|
|
413
413
|
if self.has_established_time_sync:
|
|
414
414
|
return
|
|
415
|
-
end_time_sec =
|
|
415
|
+
end_time_sec = now_sec() + timeout_sec
|
|
416
416
|
while not self.stopped:
|
|
417
417
|
if self.endpoint.has_established_time_sync:
|
|
418
418
|
return
|
|
419
|
-
if
|
|
419
|
+
if now_sec() > end_time_sec:
|
|
420
420
|
raise TimedOutError
|
|
421
421
|
time.sleep(0.1)
|
|
422
422
|
thread_exc = self.thread_exception
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright (c) 2023 Boston Dynamics, Inc. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Downloading, reproducing, distributing or otherwise using the SDK Software
|
|
4
|
+
# is subject to the terms and conditions of the Boston Dynamics Software
|
|
5
|
+
# Development Kit License (20191101-BDSDK-SL).
|
|
6
|
+
|
|
7
|
+
"""Helpers for working with units.proto."""
|
|
8
|
+
|
|
9
|
+
from bosdyn.api import units_pb2
|
|
10
|
+
|
|
11
|
+
TEMPERATURE_NAMES = {
|
|
12
|
+
units_pb2.TEMPERATURE_KELVIN: "K",
|
|
13
|
+
units_pb2.TEMPERATURE_CELSIUS: "°C",
|
|
14
|
+
units_pb2.TEMPERATURE_FAHRENHEIT: "°F"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
PRESSURE_NAMES = {
|
|
18
|
+
units_pb2.PRESSURE_PSI: "psi",
|
|
19
|
+
units_pb2.PRESSURE_KPA: "kPa",
|
|
20
|
+
units_pb2.PRESSURE_BAR: "bar"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def units_to_string(units: units_pb2.Units):
|
|
25
|
+
"""Gets the units in string form to use for display. Ex: TEMPERATURE_KELVIN = "K"
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
units(Units): Populate units message.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
String
|
|
32
|
+
"""
|
|
33
|
+
if units.HasField("temp"):
|
|
34
|
+
return TEMPERATURE_NAMES.get(units.temp, "")
|
|
35
|
+
if units.HasField("press"):
|
|
36
|
+
return PRESSURE_NAMES.get(units.press, "")
|
|
37
|
+
if units.HasField("name"):
|
|
38
|
+
return units.name
|
|
39
|
+
return ""
|
bosdyn/client/util.py
CHANGED
|
@@ -11,11 +11,14 @@ import getpass
|
|
|
11
11
|
import glob
|
|
12
12
|
import logging
|
|
13
13
|
import os
|
|
14
|
+
import pathlib
|
|
14
15
|
import signal
|
|
15
16
|
import sys
|
|
16
17
|
import threading
|
|
17
18
|
import time
|
|
18
19
|
from concurrent import futures
|
|
20
|
+
from secrets import token_urlsafe
|
|
21
|
+
from uuid import uuid4
|
|
19
22
|
|
|
20
23
|
import google.protobuf.descriptor
|
|
21
24
|
import grpc
|
|
@@ -25,6 +28,7 @@ import bosdyn.client.server_util
|
|
|
25
28
|
from bosdyn.client.auth import InvalidLoginError, InvalidTokenError
|
|
26
29
|
from bosdyn.client.channel import generate_channel_options
|
|
27
30
|
from bosdyn.client.exceptions import Error
|
|
31
|
+
from bosdyn.deprecated import moved_to
|
|
28
32
|
|
|
29
33
|
_LOGGER = logging.getLogger(__name__)
|
|
30
34
|
|
|
@@ -254,7 +258,7 @@ def add_common_arguments(parser, credentials_no_warn=False):
|
|
|
254
258
|
|
|
255
259
|
|
|
256
260
|
def read_payload_credentials(filename):
|
|
257
|
-
"""Read the guid and secret from a file. The file should have the guid and secret
|
|
261
|
+
"""Read the guid and secret from a file that already exists. The file should have the guid and secret
|
|
258
262
|
as the first and second lines in the file.
|
|
259
263
|
|
|
260
264
|
Args:
|
|
@@ -275,6 +279,40 @@ def read_payload_credentials(filename):
|
|
|
275
279
|
return guid, secret
|
|
276
280
|
|
|
277
281
|
|
|
282
|
+
def read_or_create_payload_credentials(filename):
|
|
283
|
+
"""
|
|
284
|
+
Only for use when attempting to register a payload. If simply trying to authenticate,
|
|
285
|
+
use get_guid_or_secret or read_payload_credentials instead.
|
|
286
|
+
|
|
287
|
+
When registering, attempt to read the payload's guid and secret from the specified file.
|
|
288
|
+
If this file exists, it should have the guid and secret as the first and second lines in the
|
|
289
|
+
file. If the file does not exist, this function creates a valid credentials file at filename.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
filename: Name of the file to read. Its parent directories should already exist and
|
|
293
|
+
it should have the right permissions to be read by the payload registration
|
|
294
|
+
service. If running on a CORE I/O, also ensure that this location is mounted
|
|
295
|
+
as a volume to the CORE I/O's /data or /persist locations.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Tuple of (guid, secret)
|
|
299
|
+
|
|
300
|
+
Raises:
|
|
301
|
+
OSError if the credential file cannot be read.
|
|
302
|
+
ValueError if the guid or secret are missing from the file.
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
return read_payload_credentials(filename)
|
|
306
|
+
except (OSError, ValueError):
|
|
307
|
+
# If we can't read the credentials, write them assuming that parent directories exist.
|
|
308
|
+
guid = str(uuid4())
|
|
309
|
+
secret = token_urlsafe(16)
|
|
310
|
+
with open(filename, 'w') as credentials_file:
|
|
311
|
+
credentials_file.write(guid + "\n")
|
|
312
|
+
credentials_file.write(secret)
|
|
313
|
+
return guid, secret
|
|
314
|
+
|
|
315
|
+
|
|
278
316
|
def get_guid_and_secret(parsed_options):
|
|
279
317
|
"""Get the guid and secret for a payload, based on the options that were added
|
|
280
318
|
via add_payload_credentials_arguments().
|
|
@@ -289,12 +327,30 @@ def get_guid_and_secret(parsed_options):
|
|
|
289
327
|
OSError if the credential file cannot be read.
|
|
290
328
|
Exception if no applicable arguments are given.
|
|
291
329
|
"""
|
|
292
|
-
if parsed_options
|
|
330
|
+
if (hasattr(parsed_options, "guid") and
|
|
331
|
+
parsed_options.guid) or (hasattr(parsed_options, "secret") and parsed_options.secret):
|
|
293
332
|
return parsed_options.guid, parsed_options.secret
|
|
294
333
|
if parsed_options.payload_credentials_file:
|
|
295
334
|
return read_payload_credentials(parsed_options.payload_credentials_file)
|
|
296
335
|
raise Exception('No payload credentials provided. Use --guid and --secret'
|
|
297
|
-
' or --payload-credentials-file.'
|
|
336
|
+
' or --payload-credentials-file. The latter in conjunction with'
|
|
337
|
+
' read_or_create_payload_credentials is recommended for easy'
|
|
338
|
+
' management of unique per-robot credentials')
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def add_payload_credentials_file_argument(parser):
|
|
342
|
+
"""Add argument for payload_credentials_file to an ArgumentParser or argument group.
|
|
343
|
+
This file is where the payload's GUID and secret are stored. The GUID and secret can
|
|
344
|
+
be securely generated on a per-robot basis and written to this file with
|
|
345
|
+
read_or_create_payload_credentials(filename).
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
parser: Argument parser object
|
|
349
|
+
"""
|
|
350
|
+
parser.add_argument(
|
|
351
|
+
'--payload-credentials-file', help=
|
|
352
|
+
'File from which to read payload guid and secret. Preferred in conjunction with read_or_create_payload_credentials for easy management of unique per-robot credentials'
|
|
353
|
+
)
|
|
298
354
|
|
|
299
355
|
|
|
300
356
|
def add_payload_credentials_arguments(parser, required=True):
|
|
@@ -308,8 +364,7 @@ def add_payload_credentials_arguments(parser, required=True):
|
|
|
308
364
|
group = parser.add_mutually_exclusive_group(required=required)
|
|
309
365
|
group.add_argument('--guid', help='Unique GUID of the payload.')
|
|
310
366
|
parser.add_argument('--secret', help='Secret of the payload.')
|
|
311
|
-
group
|
|
312
|
-
help='File from which to read payload guid and secret')
|
|
367
|
+
add_payload_credentials_file_argument(group)
|
|
313
368
|
|
|
314
369
|
|
|
315
370
|
def add_service_hosting_arguments(parser):
|
|
@@ -336,11 +391,19 @@ def add_service_endpoint_arguments(parser):
|
|
|
336
391
|
' e.g. "192.168.50.5"')
|
|
337
392
|
|
|
338
393
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
394
|
+
def safe_pb_enum_to_string(value, pb_enum_obj):
|
|
395
|
+
"""Safe wrapper to convert a protobuf enum object to its string representation.
|
|
396
|
+
Avoids throughing an exception if the status is unknown by the enum object.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
value: The enum value to convert
|
|
400
|
+
pb_enum_obj: The protobuf enum object to decode the value
|
|
401
|
+
"""
|
|
402
|
+
try:
|
|
403
|
+
return pb_enum_obj.Name(value)
|
|
404
|
+
except ValueError:
|
|
405
|
+
pass
|
|
406
|
+
return '<unknown> (value: {})'.format(value)
|
|
344
407
|
|
|
345
408
|
|
|
346
409
|
@deprecated(reason='The GrpcServiceRunner class helper has moved to server_util.py. Please use '
|
|
@@ -408,57 +471,30 @@ class GrpcServiceRunner(object):
|
|
|
408
471
|
|
|
409
472
|
|
|
410
473
|
|
|
411
|
-
populate_response_header =
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
'server_util.py. Please use bosdyn.client.server_util.strip_local_grid_responses.',
|
|
439
|
-
version='3.0.0', action="always")(bosdyn.client.server_util.strip_local_grid_responses)
|
|
440
|
-
|
|
441
|
-
strip_store_image_request = deprecated(
|
|
442
|
-
reason='The strip_store_image_request helper has moved to '
|
|
443
|
-
'server_util.py. Please use bosdyn.client.server_util.strip_store_image_request.',
|
|
444
|
-
version='3.0.0', action="always")(bosdyn.client.server_util.strip_store_image_request)
|
|
445
|
-
|
|
446
|
-
strip_store_data_request = deprecated(
|
|
447
|
-
reason='The strip_store_data_request helper has moved to '
|
|
448
|
-
'server_util.py. Please use bosdyn.client.server_util.strip_store_data_request.',
|
|
449
|
-
version='3.0.0', action="always")(bosdyn.client.server_util.strip_store_data_request)
|
|
450
|
-
|
|
451
|
-
strip_record_signal_tick = deprecated(
|
|
452
|
-
reason='The strip_record_signal_tick helper has moved to '
|
|
453
|
-
'server_util.py. Please use bosdyn.client.server_util.strip_record_signal_tick.',
|
|
454
|
-
version='3.0.0', action="always")(bosdyn.client.server_util.strip_record_signal_tick)
|
|
455
|
-
|
|
456
|
-
strip_record_data_blob = deprecated(
|
|
457
|
-
reason='The strip_record_data_blob helper has moved to '
|
|
458
|
-
'server_util.py. Please use bosdyn.client.server_util.strip_record_data_blob.', version='3.0.0',
|
|
459
|
-
action="always")(bosdyn.client.server_util.strip_record_data_blob)
|
|
460
|
-
|
|
461
|
-
strip_log_annotation = deprecated(
|
|
462
|
-
reason='The strip_log_annotation helper has moved to '
|
|
463
|
-
'server_util.py. Please use bosdyn.client.server_util.strip_log_annotation.', version='3.0.0',
|
|
464
|
-
action="always")(bosdyn.client.server_util.strip_log_annotation)
|
|
474
|
+
populate_response_header = moved_to(bosdyn.client.server_util.populate_response_header,
|
|
475
|
+
version='3.0.0')
|
|
476
|
+
|
|
477
|
+
strip_large_bytes_fields = moved_to(bosdyn.client.server_util.strip_large_bytes_fields,
|
|
478
|
+
version='3.0.0')
|
|
479
|
+
|
|
480
|
+
get_bytes_field_whitelist = moved_to(bosdyn.client.server_util.get_bytes_field_allowlist,
|
|
481
|
+
version='3.0.0')
|
|
482
|
+
|
|
483
|
+
strip_image_response = moved_to(bosdyn.client.server_util.strip_image_response, version='3.0.0')
|
|
484
|
+
|
|
485
|
+
strip_get_image_response = moved_to(bosdyn.client.server_util.strip_get_image_response,
|
|
486
|
+
version='3.0.0')
|
|
487
|
+
|
|
488
|
+
strip_local_grid_responses = moved_to(bosdyn.client.server_util.strip_local_grid_responses,
|
|
489
|
+
version='3.0.0')
|
|
490
|
+
|
|
491
|
+
strip_store_image_request = moved_to(bosdyn.client.server_util.strip_store_image_request,
|
|
492
|
+
version='3.0.0')
|
|
493
|
+
|
|
494
|
+
strip_store_data_request = moved_to(bosdyn.client.server_util.strip_store_data_request,
|
|
495
|
+
version='3.0.0')
|
|
496
|
+
|
|
497
|
+
strip_record_signal_tick = moved_to(bosdyn.client.server_util.strip_record_signal_tick,
|
|
498
|
+
version='3.0.0')
|
|
499
|
+
|
|
500
|
+
strip_record_data_blob = moved_to(bosdyn.client.server_util.strip_record_data_blob, version='3.0.0')
|