bosdyn-client 5.0.0__py3-none-any.whl → 5.0.1.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/audio_visual.py +517 -0
- bosdyn/client/audio_visual_helpers.py +112 -0
- bosdyn/client/directory_registration.py +34 -5
- bosdyn/client/error_callback_result.py +29 -0
- bosdyn/client/gripper_camera_param.py +9 -3
- bosdyn/client/keepalive.py +37 -8
- bosdyn/client/math_helpers.py +5 -1
- bosdyn/client/payload_registration.py +40 -6
- bosdyn/client/robot.py +4 -0
- bosdyn/client/sdk.py +2 -0
- bosdyn/client/service_customization_helpers.py +1 -1
- bosdyn/client/token_manager.py +56 -27
- {bosdyn_client-5.0.0.dist-info → bosdyn_client-5.0.1.1.dist-info}/METADATA +3 -3
- {bosdyn_client-5.0.0.dist-info → bosdyn_client-5.0.1.1.dist-info}/RECORD +16 -13
- {bosdyn_client-5.0.0.dist-info → bosdyn_client-5.0.1.1.dist-info}/WHEEL +0 -0
- {bosdyn_client-5.0.0.dist-info → bosdyn_client-5.0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,517 @@
|
|
|
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
|
+
import collections
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
from bosdyn.api import audio_visual_pb2, audio_visual_service_pb2_grpc
|
|
11
|
+
from bosdyn.client.common import (BaseClient, common_header_errors, error_factory, error_pair,
|
|
12
|
+
handle_common_header_errors, handle_unset_status_error)
|
|
13
|
+
|
|
14
|
+
from .exceptions import Error as BaseError
|
|
15
|
+
from .exceptions import ResponseError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AudioVisualResponseError(ResponseError):
|
|
19
|
+
"""General class of errors for AudioVisual service."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Error(BaseError):
|
|
23
|
+
"""Base class for non-response errors in this module."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NoTimeSyncError(BaseError):
|
|
27
|
+
"""Client has not done timesync with robot."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DoesNotExistError(AudioVisualResponseError):
|
|
31
|
+
"""The specified behavior does not exist."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PermanentBehaviorError(AudioVisualResponseError):
|
|
35
|
+
"""Permanent behaviors cannot be modified or deleted."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BehaviorExpiredError(AudioVisualResponseError):
|
|
39
|
+
"""The specified end_time has already expired."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class InvalidBehaviorError(AudioVisualResponseError):
|
|
43
|
+
"""The request contained a behavior with invalid fields."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class InvalidClientError(AudioVisualResponseError):
|
|
47
|
+
"""The behavior cannot be stopped because a different client is running it."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AudioVisualClient(BaseClient):
|
|
51
|
+
"""Client for calling the Audio Visual Service."""
|
|
52
|
+
default_service_name = 'audio-visual'
|
|
53
|
+
service_type = 'bosdyn.api.AudioVisualService'
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
super(AudioVisualClient,
|
|
57
|
+
self).__init__(audio_visual_service_pb2_grpc.AudioVisualServiceStub)
|
|
58
|
+
self.timesync_endpoint = None
|
|
59
|
+
|
|
60
|
+
def update_from(self, other):
|
|
61
|
+
"""Update instance from another object.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
other: The object where to copy from.
|
|
65
|
+
"""
|
|
66
|
+
super(AudioVisualClient, self).update_from(other)
|
|
67
|
+
|
|
68
|
+
# Grab a timesync endpoint if it is available.
|
|
69
|
+
try:
|
|
70
|
+
self.timesync_endpoint = other.time_sync.endpoint
|
|
71
|
+
except AttributeError:
|
|
72
|
+
pass # other doesn't have a time_sync accessor
|
|
73
|
+
|
|
74
|
+
def run_behavior(self, name, end_time_secs, restart=False, timesync_endpoint=None, **kwargs):
|
|
75
|
+
"""Run a behavior on the robot.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
name: The name of the behavior to run.
|
|
79
|
+
end_time_secs: The time that this behavior should stop.
|
|
80
|
+
restart: If this behavior is already running, should we restart it from the beginning.
|
|
81
|
+
timesync_endpoint: Timesync endpoint.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
RpcError: Problem communicating with the robot.
|
|
85
|
+
DoesNotExistError: The behavior name specified has not been added to the system.
|
|
86
|
+
BehaviorExpiredError: The specified end_time has already expired.
|
|
87
|
+
NoTimeSyncError: Time sync has not been established with the robot yet.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
end_time = self._timestamp_to_robot_time(end_time_secs, timesync_endpoint)
|
|
91
|
+
req = audio_visual_pb2.RunBehaviorRequest(name=name, end_time=end_time, restart=restart)
|
|
92
|
+
return self.call(self._stub.RunBehavior, req, error_from_response=_run_behavior_error,
|
|
93
|
+
copy_request=False, **kwargs)
|
|
94
|
+
|
|
95
|
+
def run_behavior_async(self, name, end_time_secs, restart=False, timesync_endpoint=None,
|
|
96
|
+
**kwargs):
|
|
97
|
+
"""Async version of run_behavior().
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
name: The name of the behavior to run.
|
|
101
|
+
end_time_secs: The time that this behavior should stop.
|
|
102
|
+
restart: If this behavior is already running, should we restart it from the beginning.
|
|
103
|
+
timesync_endpoint: Timesync endpoint.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
RpcError: Problem communicating with the robot.
|
|
107
|
+
DoesNotExistError: The behavior name specified has not been added to the system.
|
|
108
|
+
BehaviorExpiredError: The specified end_time has already expired.
|
|
109
|
+
NoTimeSyncError: Time sync has not been established with the robot yet.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
end_time = self._timestamp_to_robot_time(end_time_secs, timesync_endpoint)
|
|
113
|
+
req = audio_visual_pb2.RunBehaviorRequest(name=name, end_time=end_time, restart=restart)
|
|
114
|
+
return self.call_async(self._stub.RunBehavior, req, error_from_response=_run_behavior_error,
|
|
115
|
+
copy_request=False, **kwargs)
|
|
116
|
+
|
|
117
|
+
def stop_behavior(self, name, **kwargs):
|
|
118
|
+
"""Stop a behavior that is currently running.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
name: The name of the behavior to stop.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
RpcError: Problem communicating with the robot.
|
|
125
|
+
InvalidClientError: A different client is running this behavior."""
|
|
126
|
+
|
|
127
|
+
req = audio_visual_pb2.StopBehaviorRequest(behavior_name=name)
|
|
128
|
+
|
|
129
|
+
return self.call(self._stub.StopBehavior, req, error_from_response=_stop_behavior_error,
|
|
130
|
+
copy_request=False, **kwargs)
|
|
131
|
+
|
|
132
|
+
def stop_behavior_async(self, name, **kwargs):
|
|
133
|
+
"""Async version of stop_behavior().
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
name: The name of the behavior to stop.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
RpcError: Problem communicating with the robot.
|
|
140
|
+
InvalidClientError: A different client is running this behavior."""
|
|
141
|
+
|
|
142
|
+
req = audio_visual_pb2.StopBehaviorRequest(behavior_name=name)
|
|
143
|
+
|
|
144
|
+
return self.call_async(self._stub.StopBehavior, req,
|
|
145
|
+
error_from_response=_stop_behavior_error, copy_request=False,
|
|
146
|
+
**kwargs)
|
|
147
|
+
|
|
148
|
+
def add_or_modify_behavior(self, name, behavior, **kwargs):
|
|
149
|
+
"""Add or modify an AudioVisualBehavior.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
name: The name of the behavior to add.
|
|
153
|
+
behavior: The AudioVisualBehavior proto to add.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
The LiveAudioVisualBehavior proto that was just added or modified.
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
RpcError: Problem communicating with the robot.
|
|
160
|
+
PermanentBehaviorError: The behavior specified is permanent and cannot be modified.
|
|
161
|
+
InvalidBehaviorError: The request contained a behavior with invalid fields.
|
|
162
|
+
"""
|
|
163
|
+
# Clamp and normalize colors in the behavior before sending the request.
|
|
164
|
+
led_sequence_group = getattr(behavior, "led_sequence_group", None)
|
|
165
|
+
if led_sequence_group is not None:
|
|
166
|
+
behavior.led_sequence_group.CopyFrom(check_color(led_sequence_group))
|
|
167
|
+
|
|
168
|
+
req = audio_visual_pb2.AddOrModifyBehaviorRequest(name=name, behavior=behavior)
|
|
169
|
+
return self.call(self._stub.AddOrModifyBehavior, req,
|
|
170
|
+
value_from_response=_get_live_behavior,
|
|
171
|
+
error_from_response=_add_or_modify_behavior_error, copy_request=False,
|
|
172
|
+
**kwargs)
|
|
173
|
+
|
|
174
|
+
def add_or_modify_behavior_async(self, name, behavior, **kwargs):
|
|
175
|
+
"""Add or modify an AudioVisualBehavior.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
name: The name of the behavior to add.
|
|
179
|
+
behavior: The AudioVisualBehavior proto to add.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
The LiveAudioVisualBehavior proto that was just added or modified.
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
RpcError: Problem communicating with the robot.
|
|
186
|
+
PermanentBehaviorError: The behavior specified is permanent and cannot be modified.
|
|
187
|
+
InvalidBehaviorError: The request contained a behavior with invalid fields.
|
|
188
|
+
"""
|
|
189
|
+
# Clamp and normalize colors in the behavior before sending the request.
|
|
190
|
+
led_sequence_group = getattr(behavior, "led_sequence_group", None)
|
|
191
|
+
if led_sequence_group is not None:
|
|
192
|
+
behavior.led_sequence_group.CopyFrom(check_color(led_sequence_group))
|
|
193
|
+
|
|
194
|
+
req = audio_visual_pb2.AddOrModifyBehaviorRequest(name=name, behavior=behavior)
|
|
195
|
+
return self.call_async(self._stub.AddOrModifyBehavior, req,
|
|
196
|
+
value_from_response=_get_live_behavior,
|
|
197
|
+
error_from_response=_add_or_modify_behavior_error,
|
|
198
|
+
copy_request=False, **kwargs)
|
|
199
|
+
|
|
200
|
+
def delete_behaviors(self, behavior_names, **kwargs):
|
|
201
|
+
"""Delete an AudioVisualBehavior.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
behavior_names: A list of behavior names to delete.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
A list of LiveAudioVisualBehavior protos that were deleted.
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
RpcError: Problem communicating with the robot.
|
|
211
|
+
DoesNotExistError: A specified behavior name has not been added to the system.
|
|
212
|
+
PermanentBehaviorError: A specified behavior is permanent and cannot be deleted.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
req = audio_visual_pb2.DeleteBehaviorsRequest(behavior_names=behavior_names)
|
|
216
|
+
return self.call(self._stub.DeleteBehaviors, req,
|
|
217
|
+
value_from_response=_get_deleted_behaviors,
|
|
218
|
+
error_from_response=_delete_behaviors_error, copy_request=False, **kwargs)
|
|
219
|
+
|
|
220
|
+
def delete_behaviors_async(self, behavior_names, **kwargs):
|
|
221
|
+
"""Async version of delete_behaviors().
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
behavior_names: A list of behavior names to delete.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
A list of LiveAudioVisualBehavior protos that were deleted.
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
RpcError: Problem communicating with the robot.
|
|
231
|
+
DoesNotExistError: A specified behavior name has not been added to the system.
|
|
232
|
+
PermanentBehaviorError: A specified behavior is permanent and cannot be deleted.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
req = audio_visual_pb2.DeleteBehaviorsRequest(behavior_names=behavior_names)
|
|
236
|
+
return self.call_async(self._stub.DeleteBehaviors, req,
|
|
237
|
+
value_from_response=_get_deleted_behaviors,
|
|
238
|
+
error_from_response=_delete_behaviors_error, copy_request=False,
|
|
239
|
+
**kwargs)
|
|
240
|
+
|
|
241
|
+
def list_behaviors(self, **kwargs):
|
|
242
|
+
"""List all currently added AudioVisualBehaviors.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
A list of all LiveAudioVisualBehavior protos.
|
|
246
|
+
|
|
247
|
+
Raises:
|
|
248
|
+
RpcError: Problem communicating with the robot.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
req = audio_visual_pb2.ListBehaviorsRequest()
|
|
252
|
+
return self.call(self._stub.ListBehaviors, req, value_from_response=_get_behavior_list,
|
|
253
|
+
error_from_response=common_header_errors, copy_request=False, **kwargs)
|
|
254
|
+
|
|
255
|
+
def list_behaviors_async(self, **kwargs):
|
|
256
|
+
"""Async version of list_behaviors().
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
A list of all LiveAudioVisualBehavior protos.
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
RpcError: Problem communicating with the robot.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
req = audio_visual_pb2.ListBehaviorsRequest()
|
|
266
|
+
return self.call_async(self._stub.ListBehaviors, req,
|
|
267
|
+
value_from_response=_get_behavior_list,
|
|
268
|
+
error_from_response=common_header_errors, copy_request=False,
|
|
269
|
+
**kwargs)
|
|
270
|
+
|
|
271
|
+
def get_system_params(self, **kwargs):
|
|
272
|
+
"""Get the current system params.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
An AudioVisualSystemParams proto containing the current system param values.
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
RpcError: Problem communicating with the robot.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
req = audio_visual_pb2.GetSystemParamsRequest()
|
|
282
|
+
return self.call(self._stub.GetSystemParams, req, error_from_response=common_header_errors,
|
|
283
|
+
copy_request=False, **kwargs)
|
|
284
|
+
|
|
285
|
+
def get_system_params_async(self, **kwargs):
|
|
286
|
+
"""Async version of get_system_params().
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
An AudioVisualSystemParams proto containing the current system param values.
|
|
290
|
+
|
|
291
|
+
Raises:
|
|
292
|
+
RpcError: Problem communicating with the robot.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
req = audio_visual_pb2.GetSystemParamsRequest()
|
|
296
|
+
return self.call_async(self._stub.GetSystemParams, req,
|
|
297
|
+
error_from_response=common_header_errors, copy_request=False,
|
|
298
|
+
**kwargs)
|
|
299
|
+
|
|
300
|
+
def set_system_params(self, enabled=None, max_brightness=None, buzzer_max_volume=None,
|
|
301
|
+
speaker_max_volume=None, normal_color_association=None,
|
|
302
|
+
warning_color_association=None, danger_color_association=None, **kwargs):
|
|
303
|
+
"""Set the system params.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
enabled: [optional] System is enabled or disabled (boolean).
|
|
307
|
+
max_brightness: [optional] New max_brightness value [0, 1].
|
|
308
|
+
buzzer_max_volume: [optional] New buzzer_max_volume value [0, 1].
|
|
309
|
+
speaker_max_volume: [optional] New speaker_max_volume value [0, 1].
|
|
310
|
+
normal_color_association: [optional] The color to associate with the normal color preset.
|
|
311
|
+
warning_color_association: [optional] The color to associate with the warning color preset.
|
|
312
|
+
danger_color_association: [optional] The color to associate with the danger color preset.
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
RpcError: Problem communicating with the robot.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
req = audio_visual_pb2.SetSystemParamsRequest()
|
|
320
|
+
if (enabled is not None):
|
|
321
|
+
req.enabled.value = enabled
|
|
322
|
+
if (max_brightness is not None):
|
|
323
|
+
req.max_brightness.value = max_brightness
|
|
324
|
+
if (buzzer_max_volume is not None):
|
|
325
|
+
req.buzzer_max_volume.value = buzzer_max_volume
|
|
326
|
+
if (speaker_max_volume is not None):
|
|
327
|
+
req.speaker_max_volume.value = speaker_max_volume
|
|
328
|
+
if (normal_color_association is not None):
|
|
329
|
+
req.normal_color_association.CopyFrom(normal_color_association)
|
|
330
|
+
if (warning_color_association is not None):
|
|
331
|
+
req.warning_color_association.CopyFrom(warning_color_association)
|
|
332
|
+
if (danger_color_association is not None):
|
|
333
|
+
req.danger_color_association.CopyFrom(danger_color_association)
|
|
334
|
+
return self.call(self._stub.SetSystemParams, req, error_from_response=common_header_errors,
|
|
335
|
+
copy_request=False, **kwargs)
|
|
336
|
+
|
|
337
|
+
def set_system_params_async(self, enabled=None, max_brightness=None, buzzer_max_volume=None,
|
|
338
|
+
speaker_max_volume=None, normal_color_association=None,
|
|
339
|
+
warning_color_association=None, danger_color_association=None,
|
|
340
|
+
**kwargs):
|
|
341
|
+
"""Async version of set_system_params().
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
enabled: [optional] System is enabled or disabled (boolean).
|
|
345
|
+
max_brightness: [optional] New max_brightness value [0, 1].
|
|
346
|
+
buzzer_max_volume: [optional] New buzzer_max_volume value [0, 1].
|
|
347
|
+
speaker_max_volume: [optional] New speaker_max_volume value [0, 1].
|
|
348
|
+
normal_color_association: [optional] The color to associate with the normal color preset.
|
|
349
|
+
warning_color_association: [optional] The color to associate with the warning color preset.
|
|
350
|
+
danger_color_association: [optional] The color to associate with the danger color preset.
|
|
351
|
+
|
|
352
|
+
Raises:
|
|
353
|
+
RpcError: Problem communicating with the robot.
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
req = audio_visual_pb2.SetSystemParamsRequest()
|
|
357
|
+
if (enabled is not None):
|
|
358
|
+
req.enabled.value = enabled
|
|
359
|
+
if (max_brightness is not None):
|
|
360
|
+
req.max_brightness.value = max_brightness
|
|
361
|
+
if (buzzer_max_volume is not None):
|
|
362
|
+
req.buzzer_max_volume.value = buzzer_max_volume
|
|
363
|
+
if (speaker_max_volume is not None):
|
|
364
|
+
req.speaker_max_volume.value = speaker_max_volume
|
|
365
|
+
if (normal_color_association is not None):
|
|
366
|
+
req.normal_color_association.CopyFrom(normal_color_association)
|
|
367
|
+
if (warning_color_association is not None):
|
|
368
|
+
req.warning_color_association.CopyFrom(warning_color_association)
|
|
369
|
+
if (danger_color_association is not None):
|
|
370
|
+
req.danger_color_association.CopyFrom(danger_color_association)
|
|
371
|
+
return self.call_async(self._stub.SetSystemParams, req,
|
|
372
|
+
error_from_response=common_header_errors, copy_request=False,
|
|
373
|
+
**kwargs)
|
|
374
|
+
|
|
375
|
+
def _timestamp_to_robot_time(self, timestamp, timesync_endpoint=None):
|
|
376
|
+
# Create a time converter to convert timestamp to robot time
|
|
377
|
+
time_converter = None
|
|
378
|
+
if (timesync_endpoint):
|
|
379
|
+
time_converter = timesync_endpoint.get_robot_time_converter()
|
|
380
|
+
elif (self.timesync_endpoint):
|
|
381
|
+
time_converter = self.timesync_endpoint.get_robot_time_converter()
|
|
382
|
+
else:
|
|
383
|
+
raise NoTimeSyncError("No timesync endpoint was passed to audio visual client.")
|
|
384
|
+
|
|
385
|
+
return time_converter.robot_timestamp_from_local_secs(timestamp)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _get_behavior_list(response):
|
|
389
|
+
return response.behaviors
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _get_live_behavior(response):
|
|
393
|
+
return response.live_behavior
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _get_deleted_behaviors(response):
|
|
397
|
+
return response.deleted_behaviors
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
_AUDIO_VISUAL_RUN_BEHAVIOR_STATUS_TO_ERROR = collections.defaultdict(
|
|
401
|
+
lambda: (AudioVisualResponseError, None))
|
|
402
|
+
_AUDIO_VISUAL_RUN_BEHAVIOR_STATUS_TO_ERROR.update({
|
|
403
|
+
audio_visual_pb2.RunBehaviorResponse.STATUS_SUCCESS: (None, None),
|
|
404
|
+
audio_visual_pb2.RunBehaviorResponse.STATUS_DOES_NOT_EXIST: error_pair(DoesNotExistError),
|
|
405
|
+
audio_visual_pb2.RunBehaviorResponse.STATUS_EXPIRED: error_pair(BehaviorExpiredError),
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
_AUDIO_VISUAL_STOP_BEHAVIOR_STATUS_TO_ERROR = collections.defaultdict(
|
|
409
|
+
lambda: (AudioVisualResponseError, None))
|
|
410
|
+
_AUDIO_VISUAL_STOP_BEHAVIOR_STATUS_TO_ERROR.update({
|
|
411
|
+
audio_visual_pb2.StopBehaviorResponse.STATUS_SUCCESS: (None, None),
|
|
412
|
+
audio_visual_pb2.StopBehaviorResponse.STATUS_INVALID_CLIENT: error_pair(InvalidClientError)
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
_AUDIO_VISUAL_ADD_OR_MODIFY_BEHAVIOR_STATUS_TO_ERROR = collections.defaultdict(
|
|
416
|
+
lambda: (AudioVisualResponseError, None))
|
|
417
|
+
_AUDIO_VISUAL_ADD_OR_MODIFY_BEHAVIOR_STATUS_TO_ERROR.update({
|
|
418
|
+
audio_visual_pb2.AddOrModifyBehaviorResponse.STATUS_SUCCESS: (None, None),
|
|
419
|
+
audio_visual_pb2.AddOrModifyBehaviorResponse.STATUS_INVALID:
|
|
420
|
+
error_pair(InvalidBehaviorError),
|
|
421
|
+
audio_visual_pb2.AddOrModifyBehaviorResponse.STATUS_MODIFY_PERMANENT:
|
|
422
|
+
error_pair(PermanentBehaviorError),
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
_AUDIO_VISUAL_DELETE_BEHAVIORS_STATUS_TO_ERROR = collections.defaultdict(
|
|
426
|
+
lambda: (AudioVisualResponseError, None))
|
|
427
|
+
_AUDIO_VISUAL_DELETE_BEHAVIORS_STATUS_TO_ERROR.update({
|
|
428
|
+
audio_visual_pb2.DeleteBehaviorsResponse.STATUS_SUCCESS: (None, None),
|
|
429
|
+
audio_visual_pb2.DeleteBehaviorsResponse.STATUS_DOES_NOT_EXIST:
|
|
430
|
+
error_pair(DoesNotExistError),
|
|
431
|
+
audio_visual_pb2.DeleteBehaviorsResponse.STATUS_DELETE_PERMANENT:
|
|
432
|
+
error_pair(PermanentBehaviorError),
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@handle_common_header_errors
|
|
437
|
+
@handle_unset_status_error(unset='STATUS_UNKNOWN')
|
|
438
|
+
def _run_behavior_error(response):
|
|
439
|
+
"""RunBehaviorResponse response to exception."""
|
|
440
|
+
return error_factory(response, response.status,
|
|
441
|
+
status_to_string=audio_visual_pb2.RunBehaviorResponse.Status.Name,
|
|
442
|
+
status_to_error=_AUDIO_VISUAL_RUN_BEHAVIOR_STATUS_TO_ERROR)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@handle_common_header_errors
|
|
446
|
+
@handle_unset_status_error(unset='STATUS_UNKNOWN')
|
|
447
|
+
def _stop_behavior_error(response):
|
|
448
|
+
"""StopBehaviorResponse response to exception."""
|
|
449
|
+
return error_factory(response, response.status,
|
|
450
|
+
status_to_string=audio_visual_pb2.StopBehaviorResponse.Status.Name,
|
|
451
|
+
status_to_error=_AUDIO_VISUAL_STOP_BEHAVIOR_STATUS_TO_ERROR)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@handle_common_header_errors
|
|
455
|
+
@handle_unset_status_error(unset='STATUS_UNKNOWN')
|
|
456
|
+
def _add_or_modify_behavior_error(response):
|
|
457
|
+
"""AddOrModifyBehaviorResponse response to exception."""
|
|
458
|
+
return error_factory(response, response.status,
|
|
459
|
+
status_to_string=audio_visual_pb2.AddOrModifyBehaviorResponse.Status.Name,
|
|
460
|
+
status_to_error=_AUDIO_VISUAL_ADD_OR_MODIFY_BEHAVIOR_STATUS_TO_ERROR)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@handle_common_header_errors
|
|
464
|
+
@handle_unset_status_error(unset='STATUS_UNKNOWN')
|
|
465
|
+
def _delete_behaviors_error(response):
|
|
466
|
+
"""DeleteBehaviorResponse response to exception."""
|
|
467
|
+
return error_factory(response, response.status,
|
|
468
|
+
status_to_string=audio_visual_pb2.DeleteBehaviorsResponse.Status.Name,
|
|
469
|
+
status_to_error=_AUDIO_VISUAL_DELETE_BEHAVIORS_STATUS_TO_ERROR)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def check_color(led_sequence_group):
|
|
473
|
+
# Check every LED
|
|
474
|
+
leds = ["center", "front_left", "front_right", "hind_left", "hind_right"]
|
|
475
|
+
for led in leds:
|
|
476
|
+
# Get the LED sequence by location
|
|
477
|
+
led_sequence = getattr(led_sequence_group, led, None)
|
|
478
|
+
if led_sequence is not None:
|
|
479
|
+
# Now, normalize the color in the LED sequence by location
|
|
480
|
+
if led_sequence.HasField("animation_sequence"):
|
|
481
|
+
for frame, idx in enumerate(led_sequence.animation_sequence.frames):
|
|
482
|
+
if frame.HasField("color"):
|
|
483
|
+
color = clamp_and_normalize_color(frame.color)
|
|
484
|
+
led_sequence.animation_sequence.frames[idx] = color
|
|
485
|
+
elif led_sequence.HasField("blink_sequence"):
|
|
486
|
+
if led_sequence.blink_sequence.HasField("color"):
|
|
487
|
+
led_sequence.blink_sequence.color.CopyFrom(
|
|
488
|
+
clamp_and_normalize_color(led_sequence.blink_sequence.color))
|
|
489
|
+
elif led_sequence.HasField("pulse_sequence"):
|
|
490
|
+
if led_sequence.pulse_sequence.HasField("color"):
|
|
491
|
+
led_sequence.pulse_sequence.color.CopyFrom(
|
|
492
|
+
clamp_and_normalize_color(led_sequence.pulse_sequence.color))
|
|
493
|
+
elif led_sequence.HasField("synced_blink_sequence"):
|
|
494
|
+
for frame, idx in enumerate(led_sequence.synced_blink_sequence.frames):
|
|
495
|
+
if frame.HasField("color"):
|
|
496
|
+
color = clamp_and_normalize_color(frame.color)
|
|
497
|
+
led_sequence.synced_blink_sequence.frames[idx] = color
|
|
498
|
+
elif led_sequence.HasField("solid_color_sequence"):
|
|
499
|
+
if led_sequence.solid_color_sequence.HasField("color"):
|
|
500
|
+
led_sequence.solid_color_sequence.color.CopyFrom(
|
|
501
|
+
clamp_and_normalize_color(led_sequence.solid_color_sequence.color))
|
|
502
|
+
return led_sequence_group
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
# Scale color so that their Euclidean norm does not exceed max_color_magnitude.
|
|
506
|
+
# NOTE: max_color_magnitude of 255 (roughly 50% of sqrt(3*255^2)=441.67) is a heuristic chosen to prevent damage to the robot's LEDs.
|
|
507
|
+
# Exceeding this value may result in damage to the robot's LEDs that will NOT be covered under warranty.
|
|
508
|
+
def clamp_and_normalize_color(color, max_color_magnitude=255):
|
|
509
|
+
r, g, b = color.rgb.r, color.rgb.g, color.rgb.b
|
|
510
|
+
norm = math.sqrt(r**2 + g**2 + b**2)
|
|
511
|
+
if norm > max_color_magnitude and norm > 0:
|
|
512
|
+
scale = max_color_magnitude / norm
|
|
513
|
+
scaled_color = audio_visual_pb2.Color(
|
|
514
|
+
rgb=audio_visual_pb2.Color.RGB(r=int(r * scale), g=int(g * scale), b=int(b * scale)))
|
|
515
|
+
print(f"Input color {color} scaled by {scale:.2f}. Clamped color: {scaled_color}.")
|
|
516
|
+
color = scaled_color
|
|
517
|
+
return color
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
import logging
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from concurrent.futures import Future
|
|
11
|
+
|
|
12
|
+
import bosdyn.client
|
|
13
|
+
from bosdyn.api import audio_visual_pb2
|
|
14
|
+
from bosdyn.client.audio_visual import (AudioVisualClient, BehaviorExpiredError, DoesNotExistError,
|
|
15
|
+
InvalidClientError)
|
|
16
|
+
|
|
17
|
+
_LOGGER = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AudioVisualHelper:
|
|
21
|
+
"""Context manager that runs an AV behavior for the duration of the context.
|
|
22
|
+
|
|
23
|
+
Use as follows:
|
|
24
|
+
|
|
25
|
+
.. code-block:: python
|
|
26
|
+
|
|
27
|
+
with AudioVisualHelper(robot, behavior_name, refresh_rate):
|
|
28
|
+
# Lights and sounds will play here
|
|
29
|
+
# Lights and sounds will stop here.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
robot: Robot object for creating clients
|
|
33
|
+
behavior_name: Name of the desired behavior to run
|
|
34
|
+
refresh_rate: What rate to refresh the behavior (seconds)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, robot, behavior_name, refresh_rate, logger=None):
|
|
38
|
+
self.robot = robot
|
|
39
|
+
self.logger = logger
|
|
40
|
+
self.behavior_name = behavior_name
|
|
41
|
+
self.refresh_rate = refresh_rate
|
|
42
|
+
self.av_client = None
|
|
43
|
+
self._behavior_running_fut = None
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
self.av_client = robot.ensure_client(AudioVisualClient.default_service_name)
|
|
47
|
+
except:
|
|
48
|
+
_LOGGER.warning("Could not initialize AV client, skipping AudioVisualHelper.")
|
|
49
|
+
|
|
50
|
+
self.av_thread = None
|
|
51
|
+
self.stop_event = threading.Event()
|
|
52
|
+
|
|
53
|
+
def __enter__(self):
|
|
54
|
+
self._behavior_running_fut = Future()
|
|
55
|
+
self._behavior_running_fut.set_running_or_notify_cancel()
|
|
56
|
+
|
|
57
|
+
if self.av_client:
|
|
58
|
+
self.av_thread = threading.Thread(target=self._run_behavior_thread, args=())
|
|
59
|
+
self.av_thread.start()
|
|
60
|
+
else:
|
|
61
|
+
self._behavior_running_fut.set_result(False)
|
|
62
|
+
|
|
63
|
+
return self._behavior_running_fut
|
|
64
|
+
|
|
65
|
+
def __exit__(self, exc_type, exc_value, tb):
|
|
66
|
+
if self.av_thread:
|
|
67
|
+
self.stop_event.set()
|
|
68
|
+
self.av_thread.join()
|
|
69
|
+
|
|
70
|
+
def _run_behavior_thread(self):
|
|
71
|
+
# Check if the robot has AV hardware
|
|
72
|
+
if not self.robot.get_cached_hardware_hardware_configuration().has_audio_visual_system:
|
|
73
|
+
self._behavior_running_fut.set_result(False)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
def set_future_result(result):
|
|
77
|
+
if not self._behavior_running_fut.done():
|
|
78
|
+
self._behavior_running_fut.set_result(result)
|
|
79
|
+
|
|
80
|
+
def set_future_exception(exc):
|
|
81
|
+
if not self._behavior_running_fut.done():
|
|
82
|
+
self._behavior_running_fut.set_exception(exc)
|
|
83
|
+
|
|
84
|
+
# Run the AV behavior until the stop_event is triggered
|
|
85
|
+
while not self.stop_event.wait(self.refresh_rate):
|
|
86
|
+
try:
|
|
87
|
+
end_time_secs = time.time() + self.refresh_rate + 0.10 # add 100ms margin
|
|
88
|
+
result = self.av_client.run_behavior(self.behavior_name, end_time_secs)
|
|
89
|
+
set_future_result(
|
|
90
|
+
result.run_result == audio_visual_pb2.RunBehaviorResponse.RESULT_BEHAVIOR_RUN)
|
|
91
|
+
except DoesNotExistError as exc:
|
|
92
|
+
set_future_exception(exc)
|
|
93
|
+
_LOGGER.exception(f'Audio Visual Behavior {self.behavior_name} does not exist.')
|
|
94
|
+
return # Since the behavior doesn't exist, we can stop trying to run it
|
|
95
|
+
except BehaviorExpiredError as exc:
|
|
96
|
+
set_future_exception(exc)
|
|
97
|
+
_LOGGER.warning('Behavior was expired when received by client.')
|
|
98
|
+
except bosdyn.client.PersistentRpcError as exc:
|
|
99
|
+
set_future_exception(exc)
|
|
100
|
+
_LOGGER.exception('Failed to run behavior. Quitting AudioVisualHelper.')
|
|
101
|
+
return # A persistent error means we can't talk to the AV service, we can stop.
|
|
102
|
+
except bosdyn.client.RpcError:
|
|
103
|
+
_LOGGER.exception('Failed to run behavior. Retrying.')
|
|
104
|
+
except bosdyn.client.Error as exc:
|
|
105
|
+
set_future_exception(exc)
|
|
106
|
+
_LOGGER.exception('Unknown exception caught, quitting AudioVisualHelper.')
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
self.av_client.stop_behavior(self.behavior_name)
|
|
111
|
+
except InvalidClientError:
|
|
112
|
+
_LOGGER.warning('Failed to stop behavior, run by a different client.')
|
|
@@ -19,7 +19,8 @@ from bosdyn.api import (directory_pb2, directory_registration_pb2,
|
|
|
19
19
|
from bosdyn.client.common import (BaseClient, error_factory, error_pair,
|
|
20
20
|
handle_common_header_errors, handle_unset_status_error)
|
|
21
21
|
|
|
22
|
-
from .
|
|
22
|
+
from .error_callback_result import ErrorCallbackResult
|
|
23
|
+
from .exceptions import ResponseError, RetryableUnavailableError, RpcError, TimedOutError
|
|
23
24
|
|
|
24
25
|
_LOGGER = logging.getLogger(__name__)
|
|
25
26
|
|
|
@@ -299,10 +300,12 @@ class DirectoryRegistrationKeepAlive(object):
|
|
|
299
300
|
rpc_timeout_seconds: Number of seconds to wait for a dir_reg_client RPC. Defaults to None,
|
|
300
301
|
for no timeout.
|
|
301
302
|
rpc_interval_seconds: Interval at which to request service registrations.
|
|
303
|
+
initial_retry_seconds: Initial number of seconds to wait before retrying a failed
|
|
304
|
+
registration request. Defaults to 1 second.
|
|
302
305
|
"""
|
|
303
306
|
|
|
304
307
|
def __init__(self, dir_reg_client, logger=None, rpc_timeout_seconds=None,
|
|
305
|
-
rpc_interval_seconds=30):
|
|
308
|
+
rpc_interval_seconds=30, initial_retry_seconds=1):
|
|
306
309
|
self.authority = None
|
|
307
310
|
self.directory_name = None
|
|
308
311
|
self.host = None
|
|
@@ -310,11 +313,15 @@ class DirectoryRegistrationKeepAlive(object):
|
|
|
310
313
|
self.port = None
|
|
311
314
|
self.service_type = None
|
|
312
315
|
self.dir_reg_client = dir_reg_client
|
|
316
|
+
#: Callable[[Exception], ErrorCallbackResult] | None: Optional callback to be called when
|
|
317
|
+
#: an error occurs in the reregistration thread.
|
|
318
|
+
self.reregistration_error_callback = None
|
|
313
319
|
|
|
314
320
|
self._end_reregister_signal = threading.Event()
|
|
315
321
|
self._lock = threading.Lock()
|
|
316
322
|
self._rpc_timeout = rpc_timeout_seconds
|
|
317
323
|
self._reregister_period = rpc_interval_seconds
|
|
324
|
+
self._initial_retry_seconds = initial_retry_seconds
|
|
318
325
|
|
|
319
326
|
# Configure the thread to do re-registration.
|
|
320
327
|
self._thread = threading.Thread(target=self._periodic_reregister)
|
|
@@ -430,9 +437,13 @@ class DirectoryRegistrationKeepAlive(object):
|
|
|
430
437
|
Raises:
|
|
431
438
|
RpcError: Problem communicating with the robot.
|
|
432
439
|
"""
|
|
440
|
+
retry_interval = self._initial_retry_seconds
|
|
441
|
+
wait_time = self._reregister_period
|
|
442
|
+
|
|
433
443
|
self.logger.info('Starting directory registration loop for {}'.format(self.directory_name))
|
|
434
|
-
while
|
|
444
|
+
while not self._end_reregister_signal.wait(wait_time):
|
|
435
445
|
exec_start = time.time()
|
|
446
|
+
action = ErrorCallbackResult.RESUME_NORMAL_OPERATION
|
|
436
447
|
try:
|
|
437
448
|
self.dir_reg_client.register(
|
|
438
449
|
self.directory_name,
|
|
@@ -454,9 +465,27 @@ class DirectoryRegistrationKeepAlive(object):
|
|
|
454
465
|
pass
|
|
455
466
|
except TimedOutError:
|
|
456
467
|
self.logger.warning('Timed out, timeout set to "{}"'.format(self._rpc_timeout))
|
|
468
|
+
except RpcError as exc:
|
|
469
|
+
self.logger.exception('Reregistration failed with RpcError')
|
|
470
|
+
if self.reregistration_error_callback is not None:
|
|
471
|
+
try:
|
|
472
|
+
action = self.reregistration_error_callback(exc)
|
|
473
|
+
except Exception: #pylint: disable=broad-except
|
|
474
|
+
self.logger.exception('Exception thrown in the provided error callback')
|
|
457
475
|
except Exception:
|
|
458
476
|
# Log all other exceptions, but continue looping in hopes that it resolves itself
|
|
459
477
|
self.logger.exception('Caught general exception')
|
|
460
|
-
|
|
461
|
-
|
|
478
|
+
|
|
479
|
+
elapsed = time.time() - exec_start
|
|
480
|
+
if action == ErrorCallbackResult.RETRY_IMMEDIATELY:
|
|
481
|
+
wait_time = 0.0
|
|
482
|
+
elif action == ErrorCallbackResult.ABORT:
|
|
462
483
|
break
|
|
484
|
+
elif action == ErrorCallbackResult.RETRY_WITH_EXPONENTIAL_BACK_OFF:
|
|
485
|
+
wait_time = retry_interval - elapsed
|
|
486
|
+
retry_interval = min(2.0 * retry_interval, self._reregister_period)
|
|
487
|
+
else:
|
|
488
|
+
# action doesn't match one of the enum values or is one of
|
|
489
|
+
# RESUME_NORMAL_OPERATION or DEFAULT_ACTION
|
|
490
|
+
retry_interval = self._initial_retry_seconds
|
|
491
|
+
wait_time = self._reregister_period - elapsed
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
import enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ErrorCallbackResult(enum.Enum):
|
|
11
|
+
"""Enum indicating error resolution for errors encountered on SDK background threads.
|
|
12
|
+
|
|
13
|
+
There are a few places in the SDK where errors can occur in background threads and it would
|
|
14
|
+
be useful to provide these errors to client code to resolve. Once the application's provided
|
|
15
|
+
callback performs its action, it returns one of these enum values to indicate what the
|
|
16
|
+
background thread should do next.
|
|
17
|
+
"""
|
|
18
|
+
#: Take the default action as if no error handler had been provided.
|
|
19
|
+
DEFAULT_ACTION = 1
|
|
20
|
+
#: Retry the operation immediately, presumably because the error has been resolved and the
|
|
21
|
+
#: operation can be retried.
|
|
22
|
+
RETRY_IMMEDIATELY = 2
|
|
23
|
+
#: Retry, with the period between successive retries increasing exponentially.
|
|
24
|
+
RETRY_WITH_EXPONENTIAL_BACK_OFF = 3
|
|
25
|
+
#: Continue normal operation, presuming the error has been resolved and no further action
|
|
26
|
+
#: is needed.
|
|
27
|
+
RESUME_NORMAL_OPERATION = 4
|
|
28
|
+
#: Abort the loop in the background thread.
|
|
29
|
+
ABORT = 5
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# is subject to the terms and conditions of the Boston Dynamics Software
|
|
5
5
|
# Development Kit License (20191101-BDSDK-SL).
|
|
6
6
|
|
|
7
|
+
from deprecated.sphinx import deprecated
|
|
8
|
+
|
|
7
9
|
from bosdyn.api import gripper_camera_param_service_pb2_grpc
|
|
8
10
|
from bosdyn.client.common import BaseClient, common_header_errors
|
|
9
11
|
|
|
@@ -77,12 +79,16 @@ class GripperCameraParamClient(BaseClient):
|
|
|
77
79
|
get_gripper_camera_calib_request (gripper_camera_params_pb2.GripperCameraGetCalibrationRequest) : The command reqeust to get gripper camera calibration
|
|
78
80
|
|
|
79
81
|
Returns:
|
|
80
|
-
The GripperCameraGetCalibrationResponse message, which contains the GripperCameraCalibrationProto
|
|
82
|
+
The GripperCameraGetCalibrationResponse message, which contains the GripperCameraCalibrationProto
|
|
81
83
|
"""
|
|
82
84
|
return self.call(self._stub.GetCamCalib, get_gripper_camera_calib_request,
|
|
83
85
|
error_from_response=common_header_errors, **kwargs)
|
|
84
86
|
|
|
85
|
-
def
|
|
86
|
-
"""
|
|
87
|
+
def get_camera_calib_async(self, get_gripper_camera_calib_request, **kwargs):
|
|
88
|
+
"""Async version of get_camera_calib()."""
|
|
87
89
|
return self.call_async(self._stub.GetCamCalib, get_gripper_camera_calib_request,
|
|
88
90
|
error_from_response=common_header_errors, **kwargs)
|
|
91
|
+
|
|
92
|
+
@deprecated(version='5.0', reason='Use get_camera_calib_async() instead.')
|
|
93
|
+
def get_camera_calib_asnyc(self, get_gripper_camera_calib_request, **kwargs):
|
|
94
|
+
return self.get_camera_calib_async(get_gripper_camera_calib_request, **kwargs)
|
bosdyn/client/keepalive.py
CHANGED
|
@@ -19,6 +19,7 @@ import bosdyn.util
|
|
|
19
19
|
from bosdyn.api.keepalive import keepalive_pb2, keepalive_service_pb2_grpc
|
|
20
20
|
from bosdyn.client.common import (BaseClient, common_header_errors, error_factory, error_pair,
|
|
21
21
|
handle_common_header_errors, handle_unset_status_error)
|
|
22
|
+
from bosdyn.client.error_callback_result import ErrorCallbackResult
|
|
22
23
|
from bosdyn.client.exceptions import ResponseError, RetryableRpcError
|
|
23
24
|
|
|
24
25
|
|
|
@@ -225,7 +226,7 @@ class PolicyKeepalive():
|
|
|
225
226
|
#pylint: disable=too-many-arguments
|
|
226
227
|
def __init__(self, client: KeepaliveClient, policy: Policy, rpc_timeout_seconds: float = None,
|
|
227
228
|
rpc_interval_seconds: float = None, logger: 'logging.Logger' = None,
|
|
228
|
-
remove_policy_on_exit: bool = False):
|
|
229
|
+
remove_policy_on_exit: bool = False, initial_retry_seconds: float = 1.0):
|
|
229
230
|
|
|
230
231
|
self.logger = logger or logging.getLogger()
|
|
231
232
|
self.remove_policy_on_exit = remove_policy_on_exit
|
|
@@ -238,6 +239,11 @@ class PolicyKeepalive():
|
|
|
238
239
|
# This will raise an exception if there's no action at all.
|
|
239
240
|
self._rpc_interval_seconds = rpc_interval_seconds or policy.shortest_action_delay() / 3
|
|
240
241
|
self._rpc_timeout_seconds = rpc_timeout_seconds
|
|
242
|
+
self._initial_retry_seconds = initial_retry_seconds
|
|
243
|
+
|
|
244
|
+
#: Callable[[Exception], ErrorCallbackResult] | None: Optional callback to be called when
|
|
245
|
+
#: an error occurs in the keepalive thread.
|
|
246
|
+
self.keepalive_error_callback = None
|
|
241
247
|
|
|
242
248
|
self._end_check_in_signal = threading.Event()
|
|
243
249
|
self._thread = threading.Thread(target=self._periodic_check_in)
|
|
@@ -277,24 +283,47 @@ class PolicyKeepalive():
|
|
|
277
283
|
self._end_check_in_signal.set()
|
|
278
284
|
|
|
279
285
|
def _periodic_check_in(self):
|
|
280
|
-
|
|
286
|
+
retry_interval = self._initial_retry_seconds
|
|
287
|
+
wait_time = self._rpc_interval_seconds
|
|
288
|
+
|
|
289
|
+
# Block and wait for the stop signal. If we receive it within the check-in period,
|
|
290
|
+
# leave the loop. Under normal conditions, wait up to self._check_in_period seconds, minus
|
|
291
|
+
# the RPC processing time. (values < 0 are OK and unblock immediately)
|
|
292
|
+
while not self._end_check_in_signal.wait(wait_time):
|
|
281
293
|
exec_start = time.time()
|
|
294
|
+
action = ErrorCallbackResult.RESUME_NORMAL_OPERATION
|
|
282
295
|
|
|
283
296
|
try:
|
|
284
297
|
self._check_in()
|
|
285
298
|
except RetryableRpcError as exc:
|
|
286
299
|
self.logger.warning('exception during check-in:\n%s\n', exc)
|
|
287
300
|
self.logger.info('continuing check-in')
|
|
288
|
-
|
|
301
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
302
|
+
if self.keepalive_error_callback is not None:
|
|
303
|
+
action = ErrorCallbackResult.DEFAULT_ACTION
|
|
304
|
+
try:
|
|
305
|
+
action = self.keepalive_error_callback(exc)
|
|
306
|
+
except Exception: # pylint: disable=broad-except
|
|
307
|
+
self.logger.exception(
|
|
308
|
+
'Exception thrown in the provided keepalive error callback')
|
|
309
|
+
else:
|
|
310
|
+
raise
|
|
289
311
|
# How long did the RPC and processing of said RPC take?
|
|
290
312
|
exec_seconds = time.time() - exec_start
|
|
291
313
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
# Wait up to self._check_in_period seconds, minus the RPC processing time.
|
|
295
|
-
# (values < 0 are OK and will return immediately)
|
|
296
|
-
if self._end_check_in_signal.wait(self._rpc_interval_seconds - exec_seconds):
|
|
314
|
+
if action == ErrorCallbackResult.ABORT:
|
|
315
|
+
self.logger.warning('Callback directed the keepalive thread to exit.')
|
|
297
316
|
break
|
|
317
|
+
elif action == ErrorCallbackResult.RETRY_IMMEDIATELY:
|
|
318
|
+
wait_time = 0
|
|
319
|
+
continue
|
|
320
|
+
elif action == ErrorCallbackResult.RETRY_WITH_EXPONENTIAL_BACK_OFF:
|
|
321
|
+
wait_time = retry_interval - exec_seconds
|
|
322
|
+
retry_interval = min(2 * retry_interval, self._rpc_interval_seconds)
|
|
323
|
+
else:
|
|
324
|
+
# Success path, or default action (resume normal operation)
|
|
325
|
+
wait_time = self._rpc_interval_seconds - exec_seconds
|
|
326
|
+
retry_interval = self._initial_retry_seconds
|
|
298
327
|
self.logger.info('Policy check-in stopped')
|
|
299
328
|
|
|
300
329
|
|
bosdyn/client/math_helpers.py
CHANGED
|
@@ -660,6 +660,10 @@ class SE3Pose(object):
|
|
|
660
660
|
ret[0:3, 3] = [self.x, self.y, self.z]
|
|
661
661
|
return ret
|
|
662
662
|
|
|
663
|
+
def translation_norm(self):
|
|
664
|
+
"""Calculates the Euclidean norm (magnitude) of the translation component pose."""
|
|
665
|
+
return math.sqrt(self.x**2 + self.y**2 + self.z**2)
|
|
666
|
+
|
|
663
667
|
def mult(self, se3pose):
|
|
664
668
|
"""
|
|
665
669
|
Computes the multiplication between the current math_helpers.SE3Pose and the input se3pose.
|
|
@@ -1176,4 +1180,4 @@ def quat_to_eulerZYX(q):
|
|
|
1176
1180
|
yaw = math.atan2(2 * (q.x * q.y + q.w * q.z), q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z)
|
|
1177
1181
|
roll = math.atan2(2 * (q.y * q.z + q.w * q.x),
|
|
1178
1182
|
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z)
|
|
1179
|
-
return yaw, pitch, roll
|
|
1183
|
+
return yaw, pitch, roll
|
|
@@ -20,6 +20,7 @@ from bosdyn.client import (ResponseError, RetryableUnavailableError, TimedOutErr
|
|
|
20
20
|
TooManyRequestsError)
|
|
21
21
|
from bosdyn.client.common import (BaseClient, error_factory, handle_common_header_errors,
|
|
22
22
|
handle_lease_use_result_errors, handle_unset_status_error)
|
|
23
|
+
from bosdyn.client.error_callback_result import ErrorCallbackResult
|
|
23
24
|
|
|
24
25
|
LOGGER = logging.getLogger('payload_registration_client')
|
|
25
26
|
|
|
@@ -381,16 +382,23 @@ class PayloadRegistrationKeepAlive(object):
|
|
|
381
382
|
class name is acquired.
|
|
382
383
|
rpc_timeout_secs: Number of seconds to wait for a pay_reg_client RPC. Defaults to None,
|
|
383
384
|
for no timeout.
|
|
385
|
+
initial_retry_seconds: Number of seconds to wait before retrying registration that failed
|
|
386
|
+
due to unhandled errors including RPC transport issues.
|
|
384
387
|
"""
|
|
385
388
|
|
|
386
389
|
def __init__(self, pay_reg_client, payload, secret, registration_interval_secs=30, logger=None,
|
|
387
|
-
rpc_timeout_secs=None):
|
|
390
|
+
rpc_timeout_secs=None, initial_retry_seconds=1.0):
|
|
388
391
|
self.pay_reg_client = pay_reg_client
|
|
389
392
|
self.payload = payload
|
|
390
393
|
self.secret = secret
|
|
391
394
|
self._registration_interval_secs = registration_interval_secs
|
|
392
395
|
self.logger = logger or logging.getLogger(self.__class__.__name__)
|
|
393
396
|
self._rpc_timeout_secs = rpc_timeout_secs
|
|
397
|
+
self._initial_retry_seconds = initial_retry_seconds
|
|
398
|
+
|
|
399
|
+
#: Callable[[Exception], ErrorCallbackResult] | None: Optional callback to be called when
|
|
400
|
+
#: an error occurs in the re-registration thread.
|
|
401
|
+
self.reregistration_error_callback = None
|
|
394
402
|
|
|
395
403
|
# Configure the thread to do re-registration.
|
|
396
404
|
self._end_reregister_signal = threading.Event()
|
|
@@ -423,6 +431,7 @@ class PayloadRegistrationKeepAlive(object):
|
|
|
423
431
|
|
|
424
432
|
# This will raise an exception if the thread has already started.
|
|
425
433
|
self._thread.start()
|
|
434
|
+
return self
|
|
426
435
|
|
|
427
436
|
def is_alive(self):
|
|
428
437
|
"""Are we still periodically re-registering?
|
|
@@ -445,8 +454,12 @@ class PayloadRegistrationKeepAlive(object):
|
|
|
445
454
|
RpcError: Problem communicating with the robot.
|
|
446
455
|
"""
|
|
447
456
|
self.logger.info('Starting registration loop')
|
|
448
|
-
|
|
457
|
+
retry_interval = self._initial_retry_seconds
|
|
458
|
+
wait_time = self._registration_interval_secs
|
|
459
|
+
|
|
460
|
+
while not self._end_reregister_signal.wait(wait_time):
|
|
449
461
|
exec_start = time.time()
|
|
462
|
+
action = ErrorCallbackResult.RESUME_NORMAL_OPERATION
|
|
450
463
|
try:
|
|
451
464
|
self.pay_reg_client.register_payload(self.payload, self.secret)
|
|
452
465
|
except PayloadAlreadyExistsError:
|
|
@@ -459,10 +472,31 @@ class PayloadRegistrationKeepAlive(object):
|
|
|
459
472
|
self.logger.warning('Timed out, timeout set to "{}"'.format(self._rpc_timeout_secs))
|
|
460
473
|
except TooManyRequestsError:
|
|
461
474
|
self.logger.warning("Too many requests error")
|
|
462
|
-
except Exception as exc:
|
|
463
|
-
#
|
|
464
|
-
|
|
475
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
476
|
+
# If the application provided an error handler, give it an opportunity to resolve
|
|
477
|
+
# the issue.
|
|
478
|
+
if self.reregistration_error_callback is not None:
|
|
479
|
+
action = ErrorCallbackResult.DEFAULT_ACTION
|
|
480
|
+
try:
|
|
481
|
+
action = self.reregistration_error_callback(exc)
|
|
482
|
+
except Exception: # pylint: disable=broad-except
|
|
483
|
+
self.logger.exception(
|
|
484
|
+
'Exception thrown in the provided re-registration error callback ')
|
|
485
|
+
else:
|
|
486
|
+
# Log all other exceptions, but continue looping in hopes that it resolves itself
|
|
487
|
+
self.logger.exception('Caught general exception.')
|
|
488
|
+
|
|
465
489
|
exec_sec = time.time() - exec_start
|
|
466
|
-
if
|
|
490
|
+
if action == ErrorCallbackResult.ABORT:
|
|
491
|
+
self.logger.warning('Callback directed the re-registration loop to exit.')
|
|
467
492
|
break
|
|
493
|
+
elif action == ErrorCallbackResult.RETRY_IMMEDIATELY:
|
|
494
|
+
wait_time = 0.0
|
|
495
|
+
elif action == ErrorCallbackResult.RETRY_WITH_EXPONENTIAL_BACK_OFF:
|
|
496
|
+
wait_time = retry_interval - exec_sec
|
|
497
|
+
retry_interval = min(retry_interval * 2, self._registration_interval_secs)
|
|
498
|
+
else:
|
|
499
|
+
# Success path, or default action (resume normal operation)
|
|
500
|
+
wait_time = self._registration_interval_secs - exec_sec
|
|
501
|
+
retry_interval = self._initial_retry_seconds
|
|
468
502
|
self.logger.info('Re-registration stopped')
|
bosdyn/client/robot.py
CHANGED
|
@@ -131,6 +131,10 @@ class Robot(object):
|
|
|
131
131
|
self._time_sync_thread = None
|
|
132
132
|
self.executor = None
|
|
133
133
|
|
|
134
|
+
#: Callable[[Exception], ErrorCallbackResult] | None: Optional callback to be invoked when
|
|
135
|
+
#: an error occurs in the token refresh thread.
|
|
136
|
+
self.token_refresh_error_callback = None
|
|
137
|
+
|
|
134
138
|
# Set default max message length for sending and receiving. These values are used when
|
|
135
139
|
# creating channels.
|
|
136
140
|
self.max_send_message_length = DEFAULT_MAX_MESSAGE_LENGTH
|
bosdyn/client/sdk.py
CHANGED
|
@@ -64,6 +64,7 @@ from .spot_check import SpotCheckClient
|
|
|
64
64
|
from .time_sync import TimeSyncClient
|
|
65
65
|
from .world_object import WorldObjectClient
|
|
66
66
|
|
|
67
|
+
from .audio_visual import AudioVisualClient # isort:skip
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
class SdkError(Error):
|
|
@@ -111,6 +112,7 @@ _DEFAULT_SERVICE_CLIENTS = [
|
|
|
111
112
|
AuthClient,
|
|
112
113
|
AutoReturnClient,
|
|
113
114
|
AutowalkClient,
|
|
115
|
+
AudioVisualClient,
|
|
114
116
|
DataAcquisitionClient,
|
|
115
117
|
DataAcquisitionStoreClient,
|
|
116
118
|
DataBufferClient,
|
|
@@ -356,7 +356,7 @@ class _StringParamValidator(_ParamValidatorInterface):
|
|
|
356
356
|
if err:
|
|
357
357
|
return err
|
|
358
358
|
|
|
359
|
-
if len(self.param_spec.options) > 0:
|
|
359
|
+
if not self.param_spec.editable and len(self.param_spec.options) > 0:
|
|
360
360
|
if param_value.value not in self.param_spec.options:
|
|
361
361
|
return CustomParamError(
|
|
362
362
|
status=CustomParamError.STATUS_INVALID_VALUE, error_messages=[
|
bosdyn/client/token_manager.py
CHANGED
|
@@ -14,11 +14,15 @@ import logging
|
|
|
14
14
|
import threading
|
|
15
15
|
|
|
16
16
|
from .auth import InvalidTokenError
|
|
17
|
-
from .
|
|
17
|
+
from .error_callback_result import ErrorCallbackResult
|
|
18
|
+
from .exceptions import ResponseError, RpcError, TimedOutError
|
|
18
19
|
from .token_cache import WriteFailedError
|
|
19
20
|
|
|
20
21
|
_LOGGER = logging.getLogger(__name__)
|
|
21
22
|
|
|
23
|
+
USER_TOKEN_REFRESH_TIME_DELTA = datetime.timedelta(hours=1)
|
|
24
|
+
USER_TOKEN_RETRY_INTERVAL_START = datetime.timedelta(seconds=1)
|
|
25
|
+
|
|
22
26
|
|
|
23
27
|
class TokenManager:
|
|
24
28
|
"""Refreshes the user token in the robot object.
|
|
@@ -26,10 +30,13 @@ class TokenManager:
|
|
|
26
30
|
The refresh policy assumes the token is minted and then the manager is
|
|
27
31
|
launched."""
|
|
28
32
|
|
|
29
|
-
def __init__(self, robot, timestamp=None
|
|
33
|
+
def __init__(self, robot, timestamp=None, refresh_interval=USER_TOKEN_REFRESH_TIME_DELTA,
|
|
34
|
+
initial_retry_interval=USER_TOKEN_RETRY_INTERVAL_START):
|
|
30
35
|
self.robot = robot
|
|
31
36
|
|
|
32
37
|
self._last_timestamp = timestamp or datetime.datetime.now()
|
|
38
|
+
self._refresh_interval = refresh_interval
|
|
39
|
+
self._initial_retry_seconds = initial_retry_interval
|
|
33
40
|
|
|
34
41
|
# Daemon threads can still run during shutdown after python has
|
|
35
42
|
# started to clear out things in globals().
|
|
@@ -51,33 +58,55 @@ class TokenManager:
|
|
|
51
58
|
|
|
52
59
|
def update(self):
|
|
53
60
|
"""Refresh the user token as needed."""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
while not self._exit_thread.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
except WriteFailedError:
|
|
64
|
-
_LOGGER.exception(
|
|
65
|
-
"Failed to save the token to the cache. Continuing without caching.")
|
|
66
|
-
except (InvalidTokenError, ResponseError, RpcError):
|
|
67
|
-
_LOGGER.exception("Error refreshing the token. Retry in %s", retry_interval)
|
|
68
|
-
|
|
69
|
-
# Exponential back-off on retrying
|
|
70
|
-
self._exit_thread.wait(retry_interval.seconds)
|
|
71
|
-
retry_interval = min(2 * retry_interval, USER_TOKEN_REFRESH_TIME_DELTA)
|
|
72
|
-
continue
|
|
73
|
-
|
|
74
|
-
retry_interval = USER_TOKEN_RETRY_INTERVAL_START
|
|
75
|
-
|
|
76
|
-
# Wait until the specified time or get interrupted by user.
|
|
61
|
+
retry_interval = self._initial_retry_seconds
|
|
62
|
+
wait_time = min(self._refresh_interval - (datetime.datetime.now() - self._last_timestamp),
|
|
63
|
+
self._refresh_interval)
|
|
64
|
+
|
|
65
|
+
while not self._exit_thread.wait(wait_time.total_seconds()):
|
|
66
|
+
start_time = datetime.datetime.now()
|
|
67
|
+
action = ErrorCallbackResult.RESUME_NORMAL_OPERATION
|
|
68
|
+
try:
|
|
69
|
+
self.robot.authenticate_with_token(self.robot.user_token)
|
|
77
70
|
self._last_timestamp = datetime.datetime.now()
|
|
78
|
-
|
|
71
|
+
except WriteFailedError:
|
|
72
|
+
_LOGGER.exception(
|
|
73
|
+
"Failed to save the token to the cache. Continuing without caching.")
|
|
74
|
+
except (InvalidTokenError, ResponseError, RpcError) as exc:
|
|
75
|
+
_LOGGER.exception("Error refreshing the token.")
|
|
76
|
+
# Default course of action is to retry with a back-off, unless the application
|
|
77
|
+
# supplied callback directs us to do otherwise.
|
|
78
|
+
action = ErrorCallbackResult.RETRY_WITH_EXPONENTIAL_BACK_OFF
|
|
79
|
+
# If the application provided a callback and the error was encountered while
|
|
80
|
+
# refreshing the token, invoke the callback so that the application can take
|
|
81
|
+
# appropriate action.
|
|
82
|
+
if self.robot.token_refresh_error_callback is not None and not isinstance(
|
|
83
|
+
exc, TimedOutError):
|
|
84
|
+
try:
|
|
85
|
+
action = self.robot.token_refresh_error_callback(exc)
|
|
86
|
+
except Exception: #pylint: disable=broad-except
|
|
87
|
+
_LOGGER.exception(
|
|
88
|
+
"Exception thrown in the provided token refresh error callback")
|
|
89
|
+
if action == ErrorCallbackResult.RESUME_NORMAL_OPERATION:
|
|
90
|
+
_LOGGER.warning("Refreshing token in %s", self._refresh_interval)
|
|
91
|
+
|
|
92
|
+
elapsed = datetime.datetime.now() - start_time
|
|
93
|
+
if action == ErrorCallbackResult.ABORT:
|
|
94
|
+
_LOGGER.warning(
|
|
95
|
+
"Application-supplied callback directed the token refresh loop to exit.")
|
|
96
|
+
break
|
|
97
|
+
elif action == ErrorCallbackResult.RETRY_IMMEDIATELY:
|
|
98
|
+
_LOGGER.warning("Retrying to refresh token immediately.")
|
|
99
|
+
wait_time = datetime.timedelta(seconds=0)
|
|
100
|
+
elif action == ErrorCallbackResult.RESUME_NORMAL_OPERATION:
|
|
101
|
+
wait_time = self._refresh_interval - elapsed
|
|
102
|
+
retry_interval = self._initial_retry_seconds
|
|
103
|
+
else:
|
|
104
|
+
# action doesn't match one of the enum values or is one of
|
|
105
|
+
# RETRY_WITH_EXPONENTIAL_BACK_OFF or DEFAULT_ACTION
|
|
106
|
+
_LOGGER.warning("Retrying token refresh in %s", retry_interval)
|
|
107
|
+
wait_time = retry_interval - elapsed
|
|
108
|
+
retry_interval = min(2 * retry_interval, self._refresh_interval)
|
|
79
109
|
|
|
80
|
-
self._exit_thread.wait(elapsed_time.seconds)
|
|
81
110
|
message = 'Shutting down monitoring of token belonging to robot {}'.format(
|
|
82
111
|
self.robot.address)
|
|
83
112
|
_LOGGER.debug(message)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bosdyn-client
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.1.1
|
|
4
4
|
Summary: Boston Dynamics API client code and interfaces
|
|
5
5
|
Home-page: https://dev.bostondynamics.com/
|
|
6
6
|
Author: Boston Dynamics
|
|
@@ -15,8 +15,8 @@ Classifier: License :: Other/Proprietary License
|
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Requires-Python: >=3.7
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: bosdyn-api (==5.0.
|
|
19
|
-
Requires-Dist: bosdyn-core (==5.0.
|
|
18
|
+
Requires-Dist: bosdyn-api (==5.0.1.1)
|
|
19
|
+
Requires-Dist: bosdyn-core (==5.0.1.1)
|
|
20
20
|
Requires-Dist: grpcio
|
|
21
21
|
Requires-Dist: pyjwt
|
|
22
22
|
Requires-Dist: numpy
|
|
@@ -8,6 +8,8 @@ bosdyn/client/area_callback_service_servicer.py,sha256=o1kYKV83Q-ud-_rmT17XTSqBd
|
|
|
8
8
|
bosdyn/client/area_callback_service_utils.py,sha256=R8ljJe8fPszMI6RyuGRyv_QGu63kw1yZAveZydlpERI,5858
|
|
9
9
|
bosdyn/client/arm_surface_contact.py,sha256=DRfPfsFEzfk6ufe080ViqasUefl2ZUtcvcNENgcf55k,3710
|
|
10
10
|
bosdyn/client/async_tasks.py,sha256=gEPev6_jaUCe-G5PqktMiMGb7ohDy0daunxzQD5jafg,5594
|
|
11
|
+
bosdyn/client/audio_visual.py,sha256=_o2kvOuTgYfXfOgCPotBGdg3xYRCz55wv2tZt4VhvgE,23194
|
|
12
|
+
bosdyn/client/audio_visual_helpers.py,sha256=9qksn7epH5jBdbwa6VVMcm2mn8HCVtJuSW1C847Jo6w,4499
|
|
11
13
|
bosdyn/client/auth.py,sha256=YLo6jP0Ssl_Z6rHtTiPiKUNIweDRYub9w3iHdUe6n40,5302
|
|
12
14
|
bosdyn/client/auto_return.py,sha256=kqT1gaaneLYIPFVAFzpvTHRwa8NYYQ2OBf7wViBQudE,5598
|
|
13
15
|
bosdyn/client/autowalk.py,sha256=e57FcAC4fu2BPD57Yy4j1F8DPIPPic0Qt1E0UM-oZWo,5734
|
|
@@ -25,20 +27,21 @@ bosdyn/client/data_buffer.py,sha256=Ta-7CCoy_-StzXDm8cLIjvOnMDgKEzKFc9mcDmVkubc,
|
|
|
25
27
|
bosdyn/client/data_chunk.py,sha256=6xq9zqLD-JH72ZxzEQEKYqhXvb34TlcZoeqAYaXRxCw,1959
|
|
26
28
|
bosdyn/client/data_service.py,sha256=aKfXJCciJ2M2WZRKfdWiLS2n-UEKWSRkOgcTnArbc5o,5201
|
|
27
29
|
bosdyn/client/directory.py,sha256=jWBxnPzBJSrfr4fuLf2VuZGsxOo8Q5iX5tEw6Zx2VY0,4906
|
|
28
|
-
bosdyn/client/directory_registration.py,sha256=
|
|
30
|
+
bosdyn/client/directory_registration.py,sha256=SV2xWly0Zu1XaWhyJ9eqXYeWSjFbwfyFmup_RjR3Cdw,20806
|
|
29
31
|
bosdyn/client/docking.py,sha256=hYwZNIir3_pt-WvMFiXzMqDCwW8nA5H1BYOUD6_3-00,17596
|
|
30
32
|
bosdyn/client/door.py,sha256=dbBuGcuoZL3gRhxTQoQHKDlnzcML8ELBtQxCQqomYpo,3110
|
|
33
|
+
bosdyn/client/error_callback_result.py,sha256=B3O9D8ceV1ZsS19UvguISlbvJSjtQ1aZfktVuekZXLU,1317
|
|
31
34
|
bosdyn/client/estop.py,sha256=-dy3SP6lcOryyTB_VXe0C_cFk-qK1TNJdSm9DKSz-MM,32073
|
|
32
35
|
bosdyn/client/exceptions.py,sha256=VQg5cSrzB9_pmFcUCbBaRF2QtBUKfdzpR94N3q2rNx0,5323
|
|
33
36
|
bosdyn/client/fault.py,sha256=ymo4M1Vy-bUasD8uOm1pxE1HnjheCds6qA9hZiJbAzY,6050
|
|
34
37
|
bosdyn/client/frame_helpers.py,sha256=Rrm0Zx10LoaVsJv-FPCX02LVQwNkyKVO28lZz7Bmlvg,12572
|
|
35
38
|
bosdyn/client/graph_nav.py,sha256=8sULbBAFaFxJPF3ZdI_kqLYpzA0XbhGO-zECRyVH1J4,70681
|
|
36
|
-
bosdyn/client/gripper_camera_param.py,sha256=
|
|
39
|
+
bosdyn/client/gripper_camera_param.py,sha256=kxhBffjbq5R-t1OxWEAFguo_tKW5xGIuasdflSmfAwY,4683
|
|
37
40
|
bosdyn/client/image.py,sha256=fn41QOvaAOH7UT7m2ENXayI4fLXg5NQBZrmpLFZVBBE,21980
|
|
38
41
|
bosdyn/client/image_service_helpers.py,sha256=l2hZKqcG7PI05-_bhh8BLkpN4METmG53JCR0YF3q-8Y,40421
|
|
39
42
|
bosdyn/client/inverse_kinematics.py,sha256=KBgvGdnt-JZJvDzbdJ72Kqw8RHoOsqKq_p9rQtvwuKo,1708
|
|
40
43
|
bosdyn/client/ir_enable_disable.py,sha256=6c5MAuO1Fj0RlVcjkP1vhs-qTLiOpM3RnD8F7_ik1Gw,2153
|
|
41
|
-
bosdyn/client/keepalive.py,sha256=
|
|
44
|
+
bosdyn/client/keepalive.py,sha256=Sz1sobzkyDfamKfaam7-dP3jIVlVOBs4l6JE5qqTjEA,14892
|
|
42
45
|
bosdyn/client/lease.py,sha256=-B698QATgRuddH_pc5YN2LhMkZDacZP1kkf1MeYjqec,45412
|
|
43
46
|
bosdyn/client/lease_resource_hierarchy.py,sha256=b_YpVCeiJwVQzAy1Xh5h-1HYjpbzZvmDZAcREGCOgBc,3070
|
|
44
47
|
bosdyn/client/lease_validator.py,sha256=Vo5-2mtfCh50i7MDXLf4NlOBOgqd28ynPp2FF7NNGx0,14505
|
|
@@ -47,11 +50,11 @@ bosdyn/client/local_grid.py,sha256=YszM_pTmeGuGIwExgRwB76PXJciO6rZqfaL2RHLxAf0,3
|
|
|
47
50
|
bosdyn/client/log_status.py,sha256=wcKZEFw8GHyWeYKysZ75i3PrLb1r2xVUEAyO3SDmxOI,12656
|
|
48
51
|
bosdyn/client/manipulation_api_client.py,sha256=bdTTqZk73m7l9bty9CNC9Bs-hTTRFEA_wDweMb4mLu4,4204
|
|
49
52
|
bosdyn/client/map_processing.py,sha256=xmwTkAfIIvRbaRnOCj-JTkrfS13n4z_DAqg-sc8hL2Y,10239
|
|
50
|
-
bosdyn/client/math_helpers.py,sha256=
|
|
53
|
+
bosdyn/client/math_helpers.py,sha256=HLJ2pmul--Ualfa2cWHIgdNAZScK8MC6kjwqNKSCX44,48793
|
|
51
54
|
bosdyn/client/metrics_logging.py,sha256=9gjVK5Xu4JpOqMg4_tKMHd-XHOh--xaiU_OP9zcGOMs,6646
|
|
52
55
|
bosdyn/client/network_compute_bridge_client.py,sha256=L7RmgCRKiPBn3SVwIxCqP0hkEkhRhF5k5xz2BpC-Omk,7966
|
|
53
56
|
bosdyn/client/payload.py,sha256=12vZirEI4-gu9KPHsDg6MH0QCmnxbolWR1j81lvgfVw,2271
|
|
54
|
-
bosdyn/client/payload_registration.py,sha256=
|
|
57
|
+
bosdyn/client/payload_registration.py,sha256=rDGMK3ZtI6nmvCFsjm1MjueYglDNuHHu-7Q5V901VM8,24306
|
|
55
58
|
bosdyn/client/payload_software_update.py,sha256=nYrFOxuikVd-cChkx8aZ9gpVQSxjadgPcxyT6eklwT4,8716
|
|
56
59
|
bosdyn/client/payload_software_update_initiation.py,sha256=LDAe_gDUCLDKOC54MbhC-hsywqNMmT_dtnYjmMaef1g,3216
|
|
57
60
|
bosdyn/client/point_cloud.py,sha256=F_AJBYql8b6Ok_-IEmxpV4ajdXZ_GOahPe_QwWLf7xk,8627
|
|
@@ -59,18 +62,18 @@ bosdyn/client/power.py,sha256=teVPBPcwsUHvn996upF06Y5MJsfQrE-93ezMXAAYSmA,26955
|
|
|
59
62
|
bosdyn/client/processors.py,sha256=Z-Djf_I_lhfokB-f_L0PewAY8J95LThdWVju1zJ2BaE,1275
|
|
60
63
|
bosdyn/client/ray_cast.py,sha256=Ca1yJo0aY6OmVAazb19fy44L-9LzcKVxr_fHt_EoQtg,4465
|
|
61
64
|
bosdyn/client/recording.py,sha256=sQ34G_ckrE-M42ER2GUbYI7UibvdrjHycaia58IpJ2s,25913
|
|
62
|
-
bosdyn/client/robot.py,sha256=
|
|
65
|
+
bosdyn/client/robot.py,sha256=XQCp9NjcwS4Bhhohjup_AcLlYm67DV0tlPxuqZTwU1M,31114
|
|
63
66
|
bosdyn/client/robot_command.py,sha256=LtoVKlJwwhTmADRMvJIWJ4B5rY_MpdhHnL72zKm1ECU,108248
|
|
64
67
|
bosdyn/client/robot_id.py,sha256=0VZHG9hltwTLAm1_Bt26Xq1O6EROswqNwHvjY7kaplk,2482
|
|
65
68
|
bosdyn/client/robot_state.py,sha256=h551ke5eHdAC7NgVuLphY8FZR899Ii8_lYwuoX1w1nk,7073
|
|
66
|
-
bosdyn/client/sdk.py,sha256=
|
|
69
|
+
bosdyn/client/sdk.py,sha256=XGW0DqlBfZv31LNYjjtU8p-86C5U-x2zJd7bAF3eDzg,12945
|
|
67
70
|
bosdyn/client/server_util.py,sha256=uLT12vs5nAhdJ0ryaKuE82dxXnBOupebyDuzI8tbLRo,10560
|
|
68
|
-
bosdyn/client/service_customization_helpers.py,sha256=
|
|
71
|
+
bosdyn/client/service_customization_helpers.py,sha256=GD23vhBfwCi1S4eBYIBoTbvHe9kwap1cbq0CHXlciGw,49155
|
|
69
72
|
bosdyn/client/signals_helpers.py,sha256=Sp91IrMxVU-PeH6TK2njzFCKmFMyshRJqNa4DYRMqDU,3682
|
|
70
73
|
bosdyn/client/spot_check.py,sha256=PKqN3kwL6oISkqwXEm_R4vz0uixIsfowWY9mC0mM8Cc,14619
|
|
71
74
|
bosdyn/client/time_sync.py,sha256=mDkcR5RlAKfAOwEUoBjwxtJFDKuGFGmiDcrOeCO2P_g,23089
|
|
72
75
|
bosdyn/client/token_cache.py,sha256=Vwf9YfsR7pTyu1fLRzXrvDo9hG5GBJcen8Azlo_5_iA,3507
|
|
73
|
-
bosdyn/client/token_manager.py,sha256=
|
|
76
|
+
bosdyn/client/token_manager.py,sha256=FkF2-xWXrKG1ttTH2omhjx7FtbIGnYkB1vAaKEhg3xM,5112
|
|
74
77
|
bosdyn/client/units_helpers.py,sha256=5SAmL8vsnl06oGNjzb57fUkuUbGvtbeNdg4NgW0wYAY,1084
|
|
75
78
|
bosdyn/client/util.py,sha256=Cr_IB1taOh6Hmu1EdTHa_NU08eXnTEG7kzGX_2xDfoE,19968
|
|
76
79
|
bosdyn/client/world_object.py,sha256=CNfZJxwdTjd-Oh35liNdkZ27sAzdnBVFTkpVIICfRTo,17066
|
|
@@ -94,7 +97,7 @@ bosdyn/client/spot_cam/power.py,sha256=HS3nJF8hXq9m1JziOIwLHGLtlNMyLgewWBgs-mRZm
|
|
|
94
97
|
bosdyn/client/spot_cam/ptz.py,sha256=O1m7zDZ92zRmvy9qhjojiphMQwAweTO0HVizQFdWFFE,10630
|
|
95
98
|
bosdyn/client/spot_cam/streamquality.py,sha256=e-RjizZPwZSOS4Jlqb5Ds-mC6uKam252dpEHkb58Oc8,6364
|
|
96
99
|
bosdyn/client/spot_cam/version.py,sha256=R82eyCAY9PfZqbN8D6hNzSeZatpgpsFr995dRt1Mbe0,2856
|
|
97
|
-
bosdyn_client-5.0.
|
|
98
|
-
bosdyn_client-5.0.
|
|
99
|
-
bosdyn_client-5.0.
|
|
100
|
-
bosdyn_client-5.0.
|
|
100
|
+
bosdyn_client-5.0.1.1.dist-info/METADATA,sha256=4xSEgKQR1XVaeQsiKHhel8oRa_4wNT1QJDSMXRUV1-U,3993
|
|
101
|
+
bosdyn_client-5.0.1.1.dist-info/WHEEL,sha256=AtBG6SXL3KF_v0NxLf0ehyVOh0cold-JbJYXNGorC6Q,92
|
|
102
|
+
bosdyn_client-5.0.1.1.dist-info/top_level.txt,sha256=an2OWgx1ej2jFjmBjPWNQ68ZglvUfKhmXWW-WhTtDmA,7
|
|
103
|
+
bosdyn_client-5.0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|