bosdyn-client 5.0.1__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.
@@ -145,6 +145,98 @@ class AudioVisualClient(BaseClient):
145
145
  error_from_response=_stop_behavior_error, copy_request=False,
146
146
  **kwargs)
147
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)
148
240
 
149
241
  def list_behaviors(self, **kwargs):
150
242
  """List all currently added AudioVisualBehaviors.
@@ -301,6 +393,9 @@ def _get_live_behavior(response):
301
393
  return response.live_behavior
302
394
 
303
395
 
396
+ def _get_deleted_behaviors(response):
397
+ return response.deleted_behaviors
398
+
304
399
 
305
400
  _AUDIO_VISUAL_RUN_BEHAVIOR_STATUS_TO_ERROR = collections.defaultdict(
306
401
  lambda: (AudioVisualResponseError, None))
@@ -317,6 +412,25 @@ _AUDIO_VISUAL_STOP_BEHAVIOR_STATUS_TO_ERROR.update({
317
412
  audio_visual_pb2.StopBehaviorResponse.STATUS_INVALID_CLIENT: error_pair(InvalidClientError)
318
413
  })
319
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
+ })
320
434
 
321
435
 
322
436
  @handle_common_header_errors
@@ -337,3 +451,67 @@ def _stop_behavior_error(response):
337
451
  status_to_error=_AUDIO_VISUAL_STOP_BEHAVIOR_STATUS_TO_ERROR)
338
452
 
339
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
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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bosdyn-client
3
- Version: 5.0.1
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.1)
19
- Requires-Dist: bosdyn-core (==5.0.1)
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,7 +8,7 @@ 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=yfGy7qL6WRvYPE_gAzBJLzQ8Aucc_u5Hkb_L8fJ-Jyc,14230
11
+ bosdyn/client/audio_visual.py,sha256=_o2kvOuTgYfXfOgCPotBGdg3xYRCz55wv2tZt4VhvgE,23194
12
12
  bosdyn/client/audio_visual_helpers.py,sha256=9qksn7epH5jBdbwa6VVMcm2mn8HCVtJuSW1C847Jo6w,4499
13
13
  bosdyn/client/auth.py,sha256=YLo6jP0Ssl_Z6rHtTiPiKUNIweDRYub9w3iHdUe6n40,5302
14
14
  bosdyn/client/auto_return.py,sha256=kqT1gaaneLYIPFVAFzpvTHRwa8NYYQ2OBf7wViBQudE,5598
@@ -66,7 +66,7 @@ bosdyn/client/robot.py,sha256=XQCp9NjcwS4Bhhohjup_AcLlYm67DV0tlPxuqZTwU1M,31114
66
66
  bosdyn/client/robot_command.py,sha256=LtoVKlJwwhTmADRMvJIWJ4B5rY_MpdhHnL72zKm1ECU,108248
67
67
  bosdyn/client/robot_id.py,sha256=0VZHG9hltwTLAm1_Bt26Xq1O6EROswqNwHvjY7kaplk,2482
68
68
  bosdyn/client/robot_state.py,sha256=h551ke5eHdAC7NgVuLphY8FZR899Ii8_lYwuoX1w1nk,7073
69
- bosdyn/client/sdk.py,sha256=UFG5sRbnzEiB7hlUBIPm9KVceBrEOLGDuLQJtoIdyzM,12864
69
+ bosdyn/client/sdk.py,sha256=XGW0DqlBfZv31LNYjjtU8p-86C5U-x2zJd7bAF3eDzg,12945
70
70
  bosdyn/client/server_util.py,sha256=uLT12vs5nAhdJ0ryaKuE82dxXnBOupebyDuzI8tbLRo,10560
71
71
  bosdyn/client/service_customization_helpers.py,sha256=GD23vhBfwCi1S4eBYIBoTbvHe9kwap1cbq0CHXlciGw,49155
72
72
  bosdyn/client/signals_helpers.py,sha256=Sp91IrMxVU-PeH6TK2njzFCKmFMyshRJqNa4DYRMqDU,3682
@@ -97,7 +97,7 @@ bosdyn/client/spot_cam/power.py,sha256=HS3nJF8hXq9m1JziOIwLHGLtlNMyLgewWBgs-mRZm
97
97
  bosdyn/client/spot_cam/ptz.py,sha256=O1m7zDZ92zRmvy9qhjojiphMQwAweTO0HVizQFdWFFE,10630
98
98
  bosdyn/client/spot_cam/streamquality.py,sha256=e-RjizZPwZSOS4Jlqb5Ds-mC6uKam252dpEHkb58Oc8,6364
99
99
  bosdyn/client/spot_cam/version.py,sha256=R82eyCAY9PfZqbN8D6hNzSeZatpgpsFr995dRt1Mbe0,2856
100
- bosdyn_client-5.0.1.dist-info/METADATA,sha256=RYhDRNp8XDqkZmc5omW_vvTfEGEEpRYEAy3o961WbZg,3987
101
- bosdyn_client-5.0.1.dist-info/WHEEL,sha256=AtBG6SXL3KF_v0NxLf0ehyVOh0cold-JbJYXNGorC6Q,92
102
- bosdyn_client-5.0.1.dist-info/top_level.txt,sha256=an2OWgx1ej2jFjmBjPWNQ68ZglvUfKhmXWW-WhTtDmA,7
103
- bosdyn_client-5.0.1.dist-info/RECORD,,
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,,