bosdyn-client 4.1.0__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.
- bosdyn/client/__init__.py +1 -0
- bosdyn/client/command_line.py +15 -7
- bosdyn/client/data_acquisition_helpers.py +1 -1
- bosdyn/client/data_acquisition_store.py +2 -2
- bosdyn/client/gps/NMEAParser.py +16 -6
- bosdyn/client/gps/gps_listener.py +31 -1
- bosdyn/client/gps/ntrip_client.py +240 -0
- bosdyn/client/graph_nav.py +26 -1
- bosdyn/client/gripper_camera_param.py +34 -0
- bosdyn/client/image.py +16 -0
- bosdyn/client/image_service_helpers.py +88 -68
- bosdyn/client/log_status.py +6 -0
- bosdyn/client/math_helpers.py +15 -1
- bosdyn/client/payload_software_update.py +185 -0
- bosdyn/client/payload_software_update_initiation.py +79 -0
- bosdyn/client/point_cloud.py +9 -0
- bosdyn/client/robot.py +5 -4
- bosdyn/client/sdk.py +4 -2
- bosdyn/client/service_customization_helpers.py +18 -5
- bosdyn/client/spot_cam/__init__.py +2 -0
- bosdyn/client/spot_cam/ptz.py +20 -24
- bosdyn/client/util.py +1 -1
- {bosdyn_client-4.1.0.dist-info → bosdyn_client-5.0.0.dist-info}/METADATA +4 -4
- {bosdyn_client-4.1.0.dist-info → bosdyn_client-5.0.0.dist-info}/RECORD +26 -23
- {bosdyn_client-4.1.0.dist-info → bosdyn_client-5.0.0.dist-info}/WHEEL +0 -0
- {bosdyn_client-4.1.0.dist-info → bosdyn_client-5.0.0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
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
|
|
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
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
img_resp
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
img_req.
|
|
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
|
-
|
|
708
|
-
|
|
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
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
img_resp.status
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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):
|
bosdyn/client/log_status.py
CHANGED
|
@@ -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
|
+
|
bosdyn/client/math_helpers.py
CHANGED
|
@@ -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)
|
bosdyn/client/point_cloud.py
CHANGED
|
@@ -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(
|
|
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 =
|
|
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:
|