bosdyn-client 4.1.1__py3-none-any.whl → 5.0.0__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.
@@ -4,6 +4,7 @@
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
+ import contextlib
7
8
  import inspect
8
9
  import logging
9
10
  import sys
@@ -15,11 +16,12 @@ import numpy as np
15
16
 
16
17
  from bosdyn.api import (header_pb2, image_pb2, image_service_pb2, image_service_pb2_grpc,
17
18
  service_customization_pb2, service_fault_pb2)
19
+ from bosdyn.client.data_buffer import DataBufferClient
18
20
  from bosdyn.client.exceptions import RpcError
19
21
  from bosdyn.client.fault import (FaultClient, ServiceFaultAlreadyExistsError,
20
22
  ServiceFaultDoesNotExistError)
21
23
  from bosdyn.client.image import UnsupportedPixelFormatRequestedError
22
- from bosdyn.client.server_util import populate_response_header
24
+ from bosdyn.client.server_util import ResponseContext, populate_response_header
23
25
  from bosdyn.client.service_customization_helpers import create_value_validator, validate_dict_spec
24
26
  from bosdyn.client.util import setup_logging
25
27
  from bosdyn.util import sec_to_nsec, seconds_to_duration
@@ -430,7 +432,7 @@ class VisualImageSource():
430
432
  exposure (float | function): The exposure time for an image in seconds. This can be a fixed
431
433
  value or a function which returns the exposure time as a float.
432
434
  request_custom_params (service_customization_pb2.DictParam): Custom Params associated with the image
433
- request. Should not be 'None', but left as an option to accomodate old callers
435
+ request. Should not be 'None', but left as an option to accommodate old callers
434
436
  Returns:
435
437
  An instance of the protobuf CaptureParameters message.
436
438
  """
@@ -562,7 +564,7 @@ class ImageCaptureThread():
562
564
  """Update the capture function to use custom_params and capture_func_kwargs if it can"""
563
565
 
564
566
  # capture_func is likely provided through create_capture_thread, which already does handling for pre-3.3 blocking_capture
565
- # Still, check for the custom_params argument to ensure we accomodate pre-3.3 direct implementations of ImageCaptureThreads
567
+ # Still, check for the custom_params argument to ensure we accommodate pre-3.3 direct implementations of ImageCaptureThreads
566
568
  if "custom_params" in inspect.signature(capture_func).parameters.keys():
567
569
  #If using a blocking capture function that takes in custom params or kwargs, one should supply those
568
570
  def output_capture_function():
@@ -607,10 +609,14 @@ class CameraBaseImageServicer(image_service_pb2_grpc.ImageServiceServicer):
607
609
  the image service will call an image sources' blocking_capture_function during the GetImage request.
608
610
  background_capture_params (service_customization_pb2.DictParam): If use_background_capture_thread is true,
609
611
  custom image source parameters used for all of the background captures. Otherwise ignored
612
+ log_images (bool): if true, include image request/response messages in robot logs. This is turned off
613
+ by default.
614
+
610
615
  """
611
616
 
612
617
  def __init__(self, bosdyn_sdk_robot, service_name, image_sources, logger=None,
613
- use_background_capture_thread=True, background_capture_params=None):
618
+ use_background_capture_thread=True, background_capture_params=None,
619
+ log_images=False):
614
620
  super(CameraBaseImageServicer, self).__init__()
615
621
  if logger is None:
616
622
  # Set up the logger to remove duplicated messages and use a specific logging format.
@@ -627,6 +633,13 @@ class CameraBaseImageServicer(image_service_pb2_grpc.ImageServiceServicer):
627
633
  # Fault client to report service faults
628
634
  self.fault_client = self.bosdyn_sdk_robot.ensure_client(FaultClient.default_service_name)
629
635
 
636
+ if log_images:
637
+ # Data buffer client for logging messages
638
+ self.data_buffer_client = self.bosdyn_sdk_robot.ensure_client(
639
+ DataBufferClient.default_service_name)
640
+ else:
641
+ self.data_buffer_client = None
642
+
630
643
  # Get a timesync endpoint from the robot instance such that the image timestamps can be
631
644
  # reported in the robot's time.
632
645
  self.bosdyn_sdk_robot.time_sync.wait_for_sync()
@@ -681,74 +694,81 @@ class CameraBaseImageServicer(image_service_pb2_grpc.ImageServiceServicer):
681
694
  specified in the request.
682
695
  """
683
696
  response = image_pb2.GetImageResponse()
684
- for img_req in request.image_requests:
685
- img_resp = response.image_responses.add()
686
- src_name = img_req.image_source_name
687
- if src_name not in self.image_sources_mapped:
688
- # The requested camera source does not match the name of the Ricoh Theta camera, so it cannot
689
- # be completed and will have a failure status in the response message.
690
- img_resp.status = image_pb2.ImageResponse.STATUS_UNKNOWN_CAMERA
691
- self.logger.warning("Camera source '%s' is unknown.", src_name)
692
- continue
693
-
694
- if img_req.resize_ratio < 0 or img_req.resize_ratio > 1:
695
- img_resp.status = image_pb2.ImageResponse.STATUS_UNSUPPORTED_RESIZE_RATIO_REQUESTED
696
- self.logger.warning("Resize ratio %f is unsupported.", img_req.resize_ratio)
697
- continue
698
-
699
- if img_req.HasField("custom_params"):
700
- value_validation_error = self.image_sources_mapped[src_name].value_validator(
701
- img_req.custom_params)
702
- if value_validation_error:
703
- img_resp.status = image_pb2.ImageResponse.STATUS_CUSTOM_PARAMS_ERROR
704
- img_resp.custom_param_error.CopyFrom(value_validation_error)
697
+ if self.data_buffer_client is not None:
698
+ response_context = ResponseContext(response, request, self.data_buffer_client)
699
+ else:
700
+ response_context = contextlib.nullcontext()
701
+ with response_context:
702
+ for img_req in request.image_requests:
703
+ img_resp = response.image_responses.add()
704
+ src_name = img_req.image_source_name
705
+ if src_name not in self.image_sources_mapped:
706
+ # The requested camera source does not match the name of the Ricoh Theta camera, so it cannot
707
+ # be completed and will have a failure status in the response message.
708
+ img_resp.status = image_pb2.ImageResponse.STATUS_UNKNOWN_CAMERA
709
+ self.logger.warning("Camera source '%s' is unknown.", src_name)
710
+ continue
711
+
712
+ if img_req.resize_ratio < 0 or img_req.resize_ratio > 1:
713
+ img_resp.status = image_pb2.ImageResponse.STATUS_UNSUPPORTED_RESIZE_RATIO_REQUESTED
714
+ self.logger.warning("Resize ratio %f is unsupported.", img_req.resize_ratio)
705
715
  continue
706
716
 
707
- # Set the image source information in the response.
708
- img_resp.source.CopyFrom(self.image_sources_mapped[src_name].image_source_proto)
717
+ if img_req.HasField("custom_params"):
718
+ value_validation_error = self.image_sources_mapped[src_name].value_validator(
719
+ img_req.custom_params)
720
+ if value_validation_error:
721
+ img_resp.status = image_pb2.ImageResponse.STATUS_CUSTOM_PARAMS_ERROR
722
+ img_resp.custom_param_error.CopyFrom(value_validation_error)
723
+ continue
724
+
725
+ # Set the image source information in the response.
726
+ img_resp.source.CopyFrom(self.image_sources_mapped[src_name].image_source_proto)
727
+
728
+ # Set the image capture parameters in the response.
729
+ img_resp.shot.capture_params.CopyFrom(
730
+ self.image_sources_mapped[src_name].get_image_capture_params(
731
+ img_req.custom_params))
732
+
733
+ if img_req.HasField("custom_params"):
734
+ #If future keyword arguments are added here, they'll need to be added to this call
735
+ #get_image_and_timestamp already calls a 'sanitized' capture function that handles pre-3.3 blocking_captures
736
+ captured_image, img_time_seconds = self.image_sources_mapped[
737
+ src_name].get_image_and_timestamp(custom_params=img_req.custom_params)
738
+ else:
739
+ #get_image_and_timestamp() can accommodate pre-3.3 blocking_capture calls if no custom params are supplied
740
+ captured_image, img_time_seconds = self.image_sources_mapped[
741
+ src_name].get_image_and_timestamp()
742
+
743
+ if captured_image is None or img_time_seconds is None:
744
+ img_resp.status = image_pb2.ImageResponse.STATUS_IMAGE_DATA_ERROR
745
+ error_message = "Failed to capture an image from %s on the server." % src_name
746
+ response.header.error.message = error_message
747
+ self.logger.warning(error_message)
748
+ continue
709
749
 
710
- # Set the image capture parameters in the response.
711
- img_resp.shot.capture_params.CopyFrom(
712
- self.image_sources_mapped[src_name].get_image_capture_params(img_req.custom_params))
750
+ # Convert the image capture time from the local clock time into the robot's time. Then set it as
751
+ # the acquisition timestamp for the image data.
752
+ img_resp.shot.acquisition_time.CopyFrom(
753
+ self.bosdyn_sdk_robot.time_sync.robot_timestamp_from_local_secs(
754
+ img_time_seconds))
713
755
 
714
- if img_req.HasField("custom_params"):
715
- #If future keyword arguments are added here, they'll need to be added to this call
716
- #get_image_and_timestamp already calls a 'sanitized' capture function that handles pre-3.3 blocking_captures
717
- captured_image, img_time_seconds = self.image_sources_mapped[
718
- src_name].get_image_and_timestamp(custom_params=img_req.custom_params)
719
- else:
720
- #get_image_and_timestamp() can accomodate pre-3.3 blocking_capture calls if no custom params are supplied
721
- captured_image, img_time_seconds = self.image_sources_mapped[
722
- src_name].get_image_and_timestamp()
723
-
724
- if captured_image is None or img_time_seconds is None:
725
- img_resp.status = image_pb2.ImageResponse.STATUS_IMAGE_DATA_ERROR
726
- error_message = "Failed to capture an image from %s on the server." % src_name
727
- response.header.error.message = error_message
728
- self.logger.warning(error_message)
729
- continue
730
-
731
- # Convert the image capture time from the local clock time into the robot's time. Then set it as
732
- # the acquisition timestamp for the image data.
733
- img_resp.shot.acquisition_time.CopyFrom(
734
- self.bosdyn_sdk_robot.time_sync.robot_timestamp_from_local_secs(img_time_seconds))
735
-
736
- img_resp.shot.image.rows = img_resp.source.rows
737
- img_resp.shot.image.cols = img_resp.source.cols
738
-
739
- # Set the image data.
740
- img_resp.shot.image.format = img_req.image_format
741
- decode_status = self._set_format_and_decode(captured_image, img_resp.shot.image,
742
- img_req)
743
- if decode_status != image_pb2.ImageResponse.STATUS_OK:
744
- img_resp.status = decode_status
745
-
746
- # Set that we successfully got the image.
747
- if img_resp.status == image_pb2.ImageResponse.STATUS_UNKNOWN:
748
- img_resp.status = image_pb2.ImageResponse.STATUS_OK
749
-
750
- # No header error codes, so set the response header as CODE_OK.
751
- populate_response_header(response, request)
756
+ img_resp.shot.image.rows = img_resp.source.rows
757
+ img_resp.shot.image.cols = img_resp.source.cols
758
+
759
+ # Set the image data.
760
+ img_resp.shot.image.format = img_req.image_format
761
+ decode_status = self._set_format_and_decode(captured_image, img_resp.shot.image,
762
+ img_req)
763
+ if decode_status != image_pb2.ImageResponse.STATUS_OK:
764
+ img_resp.status = decode_status
765
+
766
+ # Set that we successfully got the image.
767
+ if img_resp.status == image_pb2.ImageResponse.STATUS_UNKNOWN:
768
+ img_resp.status = image_pb2.ImageResponse.STATUS_OK
769
+
770
+ # No header error codes, so set the response header as CODE_OK.
771
+ populate_response_header(response, request)
752
772
  return response
753
773
 
754
774
  def __del__(self):
@@ -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
+ # Boston Dynamics, Inc. Confidential Information.
8
+ # Copyright 2025. All Rights Reserved.
7
9
  """Client for the log-status service.
8
10
 
9
11
  This allows client code to start, extend or terminate experiment logs and start retro logs.
@@ -183,6 +185,7 @@ class LogStatusClient(BaseClient):
183
185
  **kwargs)
184
186
 
185
187
 
188
+
186
189
  _GET_LOG_STATUS_STATUS_TO_ERROR = \
187
190
  collections.defaultdict(lambda: (LogStatusResponseError, None))
188
191
  _GET_LOG_STATUS_STATUS_TO_ERROR.update({
@@ -232,6 +235,7 @@ _TERMINATE_LOG_STATUS_TO_ERROR.update({
232
235
  })
233
236
 
234
237
 
238
+
235
239
  @handle_common_header_errors
236
240
  @handle_unset_status_error(unset='STATUS_UNKNOWN')
237
241
  def get_log_status_error(response):
@@ -284,3 +288,5 @@ def terminate_log_error(response):
284
288
  return error_factory(response, response.status,
285
289
  status_to_string=log_status.TerminateLogResponse.Status.Name,
286
290
  status_to_error=_TERMINATE_LOG_STATUS_TO_ERROR)
291
+
292
+
@@ -408,6 +408,10 @@ class SE2Velocity(object):
408
408
  + str(se2_vel_vector.shape[0]))
409
409
  return None
410
410
  else:
411
+ if (isinstance(se2_vel_vector[0], numpy.ndarray)):
412
+ return SE2Velocity(x=se2_vel_vector[0][0], y=se2_vel_vector[1][0],
413
+ angular=se2_vel_vector[2][0])
414
+
411
415
  return SE2Velocity(x=se2_vel_vector[0], y=se2_vel_vector[1],
412
416
  angular=se2_vel_vector[2])
413
417
 
@@ -521,6 +525,11 @@ class SE3Velocity(object):
521
525
  + str(se3_vel_vector.shape[0]))
522
526
  return None
523
527
  else:
528
+ if (isinstance(se3_vel_vector[0], numpy.ndarray)):
529
+ return SE3Velocity(lin_x=se3_vel_vector[0][0], lin_y=se3_vel_vector[1][0],
530
+ lin_z=se3_vel_vector[2][0], ang_x=se3_vel_vector[3][0],
531
+ ang_y=se3_vel_vector[4][0], ang_z=se3_vel_vector[5][0])
532
+
524
533
  return SE3Velocity(lin_x=se3_vel_vector[0], lin_y=se3_vel_vector[1],
525
534
  lin_z=se3_vel_vector[2], ang_x=se3_vel_vector[3],
526
535
  ang_y=se3_vel_vector[4], ang_z=se3_vel_vector[5])
@@ -1100,6 +1109,11 @@ def skew_matrix_2d(vec2_proto):
1100
1109
  return numpy.array([[vec2_proto.y, -vec2_proto.x]])
1101
1110
 
1102
1111
 
1112
+ def matrix_from_proto(proto):
1113
+ """Converts a geometry_pb2.Matrix or geometry_pb2.Matrixf to a numpy array."""
1114
+ return numpy.array(proto.values).reshape(proto.rows, proto.cols)
1115
+
1116
+
1103
1117
  def transform_se2velocity(a_adjoint_b_matrix, se2_velocity_in_b):
1104
1118
  """
1105
1119
  Changes the frame that the SE(2) Velocity is expressed in. More specifically, it converts the
@@ -1162,4 +1176,4 @@ def quat_to_eulerZYX(q):
1162
1176
  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)
1163
1177
  roll = math.atan2(2 * (q.y * q.z + q.w * q.x),
1164
1178
  q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z)
1165
- return yaw, pitch, roll
1179
+ return yaw, pitch, roll
@@ -0,0 +1,185 @@
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
+ """Payload software update service gRPC client.
8
+
9
+ This is used by Spot payloads to coordinate updates of their own software with Spot.
10
+ """
11
+
12
+ import datetime
13
+
14
+ from google.protobuf import timestamp_pb2
15
+
16
+ from bosdyn.api.payload_software_update_pb2 import (GetAvailableSoftwareUpdatesRequest,
17
+ SendCurrentVersionInfoRequest,
18
+ SendSoftwareUpdateStatusRequest)
19
+ from bosdyn.api.payload_software_update_service_pb2_grpc import PayloadSoftwareUpdateServiceStub
20
+ from bosdyn.api.robot_id_pb2 import SoftwareVersion
21
+ from bosdyn.api.software_package_pb2 import SoftwarePackageVersion, SoftwareUpdateStatus
22
+ from bosdyn.client.common import BaseClient
23
+
24
+
25
+ class PayloadSoftwareUpdateClient(BaseClient):
26
+ """A client for payloads to coordinate software updates with a robot."""
27
+
28
+ default_service_name = 'payload-software-update'
29
+ service_type = 'bosdyn.api.PayloadSoftwareUpdateService'
30
+
31
+ def __init__(self):
32
+ super(PayloadSoftwareUpdateClient, self).__init__(PayloadSoftwareUpdateServiceStub)
33
+
34
+ def send_current_software_info(
35
+ self, package_name: str, version: SoftwareVersion | list[int],
36
+ release_date: float | timestamp_pb2.Timestamp | datetime.datetime, build_id: str,
37
+ **kwargs):
38
+ """Send version information about the currently installed payload software to Spot.
39
+
40
+ Args:
41
+ package_name: Name of the package, e.g., "coreio"
42
+ version: Current semantic version of the installed software.
43
+ release_date: Release date of the currently installed software.
44
+ build_id: Unique identifier of the build.
45
+
46
+ Returns:
47
+ SendCurrentVersionInfoResponse: The response object from Spot. Currently this message
48
+ contains no information other than a standard response header.
49
+
50
+ Raises:
51
+ RpcError: Problem communicating with the robot.
52
+ """
53
+ request = self.make_info_request(package_name, version, release_date, build_id)
54
+ return self.call(self._stub.SendCurrentVersionInfo, request, **kwargs)
55
+
56
+ def send_current_software_info_async(
57
+ self, package_name: str, version: SoftwareVersion | list[int],
58
+ release_date: float | timestamp_pb2.Timestamp | datetime.datetime, build_id: str,
59
+ **kwargs):
60
+ """Async version of send_current_software_info().
61
+
62
+ Args:
63
+ package_name: Name of the package, e.g., "coreio"
64
+ version: Current semantic version of the installed software.
65
+ release_date: Release date of the currently installed software.
66
+ build_id: Unique identifier of the build.
67
+
68
+ Returns:
69
+ SendCurrentVersionInfoResponse: The response object from Spot. Currently this message
70
+ contains no information other than a standard response header.
71
+ Raises:
72
+ RpcError: Problem communicating with the robot.
73
+ """
74
+ request = self.make_info_request(package_name, version, release_date, build_id)
75
+ return self.call_async(self._stub.SendCurrentVersionInfo, request, **kwargs)
76
+
77
+ def get_available_updates(self, package_names: str | list[str], **kw_args):
78
+ """Get a list of package information for the named package(s).
79
+
80
+ Args:
81
+ package_names: The package name or list of package names to query.
82
+
83
+ Returns:
84
+ GetAvailableSoftwareUpdatesResponse: The response object from Spot containing the
85
+ version info for packages cached by Spot.
86
+
87
+ Raises:
88
+ RpcError: Problem communicating with the robot.
89
+ """
90
+ if not isinstance(package_names, list):
91
+ package_names = [package_names]
92
+ request = GetAvailableSoftwareUpdatesRequest(package_names=package_names)
93
+ return self.call(self._stub.GetAvailableSoftwareUpdates, request, **kw_args)
94
+
95
+ def get_available_updates_async(self, package_names: str | list[str], **kw_args):
96
+ """Async version of get_available_updates().
97
+
98
+ Args:
99
+ package_names: The package name or list of package names to query.
100
+
101
+ Returns:
102
+ GetAvailableSoftwareUpdatesResponse: The response object from Spot containing the
103
+ version info for packages cached by Spot.
104
+
105
+ Raises:
106
+ RpcError: Problem communicating with the robot.
107
+ """
108
+ if not isinstance(package_names, list):
109
+ package_names = [package_names]
110
+ request = GetAvailableSoftwareUpdatesRequest(package_names=package_names)
111
+ return self.call_async(self._stub.GetAvailableSoftwareUpdates, request, **kw_args)
112
+
113
+ def send_installation_status(self, package_name: str, status: SoftwareUpdateStatus.Status,
114
+ error_code: SoftwareUpdateStatus.ErrorCode, **kw_args):
115
+ """Send a status update of a payload software installation operation to Spot.
116
+
117
+ Args:
118
+ package_name: Name of the package being updated
119
+ status: Status code of installation operation
120
+ error_code: Error code of the installation operation, or ERROR_NONE if no error has
121
+ been encountered.
122
+
123
+ Returns:
124
+ SendSoftwareUpdateStatusResponse: The response object from Spot. Currently this message
125
+ contains nothing beyond a standard response header.
126
+
127
+ Raises:
128
+ RpcError: Problem communicating with the robot.
129
+ """
130
+ status = SoftwareUpdateStatus(package_name=package_name, status=status,
131
+ error_code=error_code)
132
+ request = SendSoftwareUpdateStatusRequest(update_status=status)
133
+ return self.call(self._stub.SendSoftwareUpdateStatus, request, **kw_args)
134
+
135
+ def send_installation_status_async(self, package_name: str, status: SoftwareUpdateStatus.Status,
136
+ error_code: SoftwareUpdateStatus.ErrorCode, **kw_args):
137
+ """Async version of send_installation_status().
138
+
139
+ Args:
140
+ package_name: Name of the package being updated
141
+ status: Status code of installation operation
142
+ error_code: Error code of the installation operation, or ERROR_NONE if no error has
143
+ been encountered.
144
+
145
+ Returns:
146
+ SendSoftwareUpdateStatusResponse: The response object from Spot. Currently this message
147
+ contains nothing beyond a standard response header.
148
+
149
+ Raises:
150
+ RpcError: Problem communicating with the robot.
151
+ """
152
+ status = SoftwareUpdateStatus(package_name=package_name, status=status,
153
+ error_code=error_code)
154
+ request = SendSoftwareUpdateStatusRequest(update_status=status)
155
+ return self.call_async(self._stub.SendSoftwareUpdateStatus, request, **kw_args)
156
+
157
+ @staticmethod
158
+ def make_info_request(package_name: str, version: SoftwareVersion,
159
+ release_date: float | timestamp_pb2.Timestamp | datetime.datetime,
160
+ build_id: str):
161
+ """Make a SendCurrentVersionInfoRequest message using the supplied information.
162
+
163
+ Args:
164
+ package_name: Name of the package, e.g., "coreio"
165
+ version: Current semantic version of the installed software.
166
+ release_date: Release date of the currently installed software.
167
+ build_id: Unique identifier of the build.
168
+
169
+ Returns:
170
+ SendCurrentVersionInfoRequest: Message communicating to Spot the version information
171
+ of the currently installed payload software.
172
+ """
173
+ if not isinstance(version, SoftwareVersion):
174
+ version = SoftwareVersion(major_version=version[0], minor_version=version[1],
175
+ patch_level=version[2])
176
+ if isinstance(release_date, float):
177
+ release_date = datetime.datetime.fromtimestamp(release_date)
178
+ if isinstance(release_date, datetime.datetime):
179
+ pb_release_date = timestamp_pb2.Timestamp()
180
+ pb_release_date.FromDatetime(release_date)
181
+ release_date = pb_release_date
182
+
183
+ return SendCurrentVersionInfoRequest(
184
+ package_version=SoftwarePackageVersion(package_name=package_name, version=version,
185
+ release_date=release_date, build_id=build_id))
@@ -0,0 +1,79 @@
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
+ """Payload software update initiation gRPC client.
8
+
9
+ This client uses an insecure channel for signaling to a payload that it should
10
+ send its version information or initiate a software update.
11
+ """
12
+
13
+ from bosdyn.api.payload_software_update_initiation_pb2 import (
14
+ TriggerInitiateUpdateRequest, TriggerSendPayloadSoftwareInfoRequest)
15
+ from bosdyn.api.payload_software_update_initiation_service_pb2_grpc import \
16
+ PayloadSoftwareUpdateInitiationServiceStub
17
+ from bosdyn.client.common import BaseClient
18
+
19
+
20
+ class PayloadSoftwareUpdateInitiationClient(BaseClient):
21
+ """A client used to direct a payload to send its version information or start an update."""
22
+
23
+ def __init__(self):
24
+ super(PayloadSoftwareUpdateInitiationClient,
25
+ self).__init__(PayloadSoftwareUpdateInitiationServiceStub)
26
+
27
+ default_service_name = 'payload-software-update-initiation'
28
+ service_type = 'bosdyn.api.PayloadSoftwareUpdateInitiationService'
29
+
30
+ def trigger_send_payload_software_info(self, **kwargs):
31
+ """Tell a payload to send its current version information to Spot.
32
+
33
+ Returns:
34
+ TriggerSendPayloadSoftwareInfoResponse: The response object from the payload. Currently
35
+ this message is empty.
36
+
37
+ Raises:
38
+ RpcError: Problem communicating with the payload.
39
+ """
40
+ return self.call(self._stub.TriggerSendPayloadSoftwareInfo,
41
+ TriggerSendPayloadSoftwareInfoRequest(), **kwargs)
42
+
43
+ def trigger_send_payload_software_info_async(self, **kwargs):
44
+ """Async version of trigger_send_payload_software_info().
45
+
46
+ Returns:
47
+ TriggerSendPayloadSoftwareInfoResponse: The response object from the payload. Currently
48
+ this message is empty.
49
+
50
+ Raises:
51
+ RpcError: Problem communicating with the payload.
52
+ """
53
+ return self.call_async(self._stub.TriggerSendPayloadSoftwareInfo,
54
+ TriggerSendPayloadSoftwareInfoRequest(), **kwargs)
55
+
56
+ def trigger_initiate_update(self, **kwargs):
57
+ """Tell a payload to initiate its software update logic.
58
+
59
+ Returns:
60
+ TriggerInitiateUpdateResponse: The response object from the payload. Currently this
61
+ message is empty.
62
+
63
+ Raises:
64
+ RpcError: Problem communicating with the payload.
65
+ """
66
+ return self.call(self._stub.TriggerInitiateUpdate, TriggerInitiateUpdateRequest(), **kwargs)
67
+
68
+ def trigger_initiate_update_async(self, **kwargs):
69
+ """Async version of trigger_initiate_update().
70
+
71
+ Returns:
72
+ TriggerInitiateUpdateResponse: The response object from the payload. Currently this
73
+ message is empty.
74
+
75
+ Raises:
76
+ RpcError: Problem communicating with the payload.
77
+ """
78
+ return self.call_async(self._stub.TriggerInitiateUpdate, TriggerInitiateUpdateRequest(),
79
+ **kwargs)
@@ -39,6 +39,10 @@ class PointCloudDataError(PointCloudResponseError):
39
39
  """System cannot generate point cloud data at this time."""
40
40
 
41
41
 
42
+ class PointCloudTypeError(PointCloudResponseError):
43
+ """System cannot generate point cloud with the request cloud_type."""
44
+
45
+
42
46
  _STATUS_TO_ERROR = collections.defaultdict(lambda: (PointCloudResponseError, None))
43
47
  _STATUS_TO_ERROR.update({
44
48
  point_cloud_protos.PointCloudResponse.STATUS_OK: (None, None),
@@ -50,6 +54,8 @@ _STATUS_TO_ERROR.update({
50
54
  error_pair(UnsetStatusError),
51
55
  point_cloud_protos.PointCloudResponse.STATUS_POINT_CLOUD_DATA_ERROR:
52
56
  error_pair(PointCloudDataError),
57
+ point_cloud_protos.PointCloudResponse.STATUS_UNSUPPORTED_CLOUD_TYPE:
58
+ error_pair(PointCloudTypeError),
53
59
  })
54
60
 
55
61
 
@@ -66,6 +72,8 @@ def _error_from_response(response):
66
72
  return None
67
73
 
68
74
 
75
+
76
+
69
77
  class PointCloudClient(BaseClient):
70
78
  """A client handling point clouds."""
71
79
 
@@ -177,6 +185,7 @@ class PointCloudClient(BaseClient):
177
185
  return point_cloud_protos.ListPointCloudSourcesRequest()
178
186
 
179
187
 
188
+
180
189
  def build_pc_request(point_cloud_source_name):
181
190
  """Helper function which builds an PointCloudRequest from a point cloud source name.
182
191
 
bosdyn/client/robot.py CHANGED
@@ -385,7 +385,7 @@ class Robot(object):
385
385
  self.update_user_token(user_token, username)
386
386
 
387
387
  def authenticate_from_payload_credentials(self, guid, secret, payload_registration_client=None,
388
- timeout=None):
388
+ timeout=None, retry_interval=1.0):
389
389
  """Authenticate to this Robot with the guid/secret of the hosting payload.
390
390
 
391
391
  This call is used to authenticate to a robot using payload credentials. If a payload is
@@ -413,7 +413,7 @@ class Robot(object):
413
413
  self.logger.warning(
414
414
  'Payload is not authorized. Authentication will block until an'
415
415
  ' operator authorizes the payload in the Admin Console.')
416
- time.sleep(0.1)
416
+ time.sleep(retry_interval)
417
417
  self.update_user_token(user_token)
418
418
 
419
419
  def update_user_token(self, user_token, username=None):
@@ -467,7 +467,8 @@ class Robot(object):
467
467
  return self.service_type_by_name
468
468
 
469
469
 
470
- def register_payload_and_authenticate(self, payload, secret, timeout=None):
470
+ def register_payload_and_authenticate(self, payload, secret, timeout=None,
471
+ auth_retry_interval=1.0):
471
472
  """Register a payload with the robot and request a user_token.
472
473
 
473
474
  This method will block until the payload is authorized by an operator in the robot webpage.
@@ -486,7 +487,7 @@ class Robot(object):
486
487
  pass
487
488
  self.authenticate_from_payload_credentials(
488
489
  payload.GUID, secret, payload_registration_client=payload_registration_client,
489
- timeout=timeout)
490
+ timeout=timeout, retry_interval=auth_retry_interval)
490
491
 
491
492
  def start_time_sync(self, time_sync_interval_sec=None):
492
493
  """Start time sync thread if needed.
bosdyn/client/sdk.py CHANGED
@@ -8,13 +8,13 @@
8
8
 
9
9
  import datetime
10
10
  import glob
11
+ import importlib.resources
11
12
  import logging
12
13
  import os
13
14
  import platform
14
15
  from enum import Enum
15
16
 
16
17
  import jwt
17
- import pkg_resources
18
18
  from deprecated.sphinx import deprecated
19
19
 
20
20
  from .arm_surface_contact import ArmSurfaceContactClient
@@ -178,6 +178,8 @@ def create_standard_sdk(client_name_prefix, service_clients=None, cert_resource_
178
178
 
179
179
 
180
180
 
181
+
182
+
181
183
  class Sdk(object):
182
184
  """Repository for settings typically common to a single developer and/or robot fleet.
183
185
  See also Robot for robot-specific settings.
@@ -281,7 +283,7 @@ class Sdk(object):
281
283
  """
282
284
  self.cert = None
283
285
  if resource_path_glob is None:
284
- self.cert = pkg_resources.resource_stream('bosdyn.client.resources', 'robot.pem').read()
286
+ self.cert = importlib.resources.read_binary('bosdyn.client.resources', 'robot.pem')
285
287
  else:
286
288
  cert_paths = [c for c in glob.glob(resource_path_glob) if os.path.isfile(c)]
287
289
  if not cert_paths: