bosdyn-client 3.3.2__py3-none-any.whl → 4.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bosdyn/client/__init__.py +5 -6
- bosdyn/client/area_callback_region_handler_base.py +19 -4
- bosdyn/client/area_callback_service_servicer.py +29 -1
- bosdyn/client/area_callback_service_utils.py +45 -51
- bosdyn/client/auth.py +13 -28
- bosdyn/client/autowalk.py +3 -0
- bosdyn/client/channel.py +23 -26
- bosdyn/client/command_line.py +64 -13
- bosdyn/client/common.py +4 -4
- bosdyn/client/data_acquisition.py +47 -6
- bosdyn/client/data_acquisition_plugin.py +12 -2
- bosdyn/client/data_acquisition_plugin_service.py +33 -2
- bosdyn/client/data_acquisition_store.py +38 -0
- bosdyn/client/data_buffer.py +22 -8
- bosdyn/client/data_chunk.py +1 -0
- bosdyn/client/directory_registration.py +1 -14
- bosdyn/client/exceptions.py +0 -4
- bosdyn/client/frame_helpers.py +3 -1
- bosdyn/client/gps/NMEAParser.py +189 -0
- bosdyn/client/gps/__init__.py +6 -0
- bosdyn/client/gps/aggregator_client.py +56 -0
- bosdyn/client/gps/gps_listener.py +153 -0
- bosdyn/client/gps/registration_client.py +48 -0
- bosdyn/client/graph_nav.py +50 -20
- bosdyn/client/image.py +20 -7
- bosdyn/client/image_service_helpers.py +14 -14
- bosdyn/client/lease.py +27 -22
- bosdyn/client/lease_validator.py +5 -5
- bosdyn/client/manipulation_api_client.py +1 -1
- bosdyn/client/map_processing.py +10 -5
- bosdyn/client/math_helpers.py +21 -11
- bosdyn/client/metrics_logging.py +147 -0
- bosdyn/client/network_compute_bridge_client.py +6 -0
- bosdyn/client/power.py +40 -0
- bosdyn/client/recording.py +3 -3
- bosdyn/client/robot.py +15 -16
- bosdyn/client/robot_command.py +341 -203
- bosdyn/client/robot_id.py +6 -5
- bosdyn/client/robot_state.py +6 -0
- bosdyn/client/sdk.py +5 -11
- bosdyn/client/server_util.py +11 -11
- bosdyn/client/service_customization_helpers.py +776 -64
- bosdyn/client/signals_helpers.py +105 -0
- bosdyn/client/spot_cam/compositor.py +6 -2
- bosdyn/client/spot_cam/ptz.py +24 -14
- bosdyn/client/spot_check.py +160 -0
- bosdyn/client/time_sync.py +5 -5
- bosdyn/client/units_helpers.py +39 -0
- bosdyn/client/util.py +100 -64
- bosdyn/client/world_object.py +5 -5
- {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/METADATA +4 -3
- bosdyn_client-4.0.1.dist-info/RECORD +97 -0
- {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/WHEEL +1 -1
- bosdyn/client/log_annotation.py +0 -359
- bosdyn_client-3.3.2.dist-info/RECORD +0 -90
- {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/top_level.txt +0 -0
bosdyn/client/command_line.py
CHANGED
|
@@ -29,12 +29,13 @@ from bosdyn.util import duration_str, timestamp_to_datetime
|
|
|
29
29
|
from .auth import InvalidLoginError, InvalidTokenError
|
|
30
30
|
from .data_acquisition import DataAcquisitionClient
|
|
31
31
|
from .data_acquisition_helpers import acquire_and_process_request
|
|
32
|
+
from .data_acquisition_plugin import DataAcquisitionPluginClient
|
|
32
33
|
from .data_buffer import DataBufferClient
|
|
33
34
|
from .data_service import DataServiceClient
|
|
34
35
|
from .directory import DirectoryClient, NonexistentServiceError
|
|
35
36
|
from .directory_registration import DirectoryRegistrationClient, DirectoryRegistrationResponseError
|
|
36
37
|
from .estop import EstopClient, EstopEndpoint, EstopKeepAlive
|
|
37
|
-
from .exceptions import Error,
|
|
38
|
+
from .exceptions import Error, InvalidRequestError, ProxyConnectionError
|
|
38
39
|
from .image import (ImageClient, ImageResponseError, UnknownImageSourceError, build_image_request,
|
|
39
40
|
save_images_as_files)
|
|
40
41
|
from .lease import LeaseClient
|
|
@@ -92,9 +93,6 @@ class Command(object, metaclass=abc.ABCMeta):
|
|
|
92
93
|
except ProxyConnectionError:
|
|
93
94
|
print('Could not contact robot with hostname "{}".'.format(options.hostname),
|
|
94
95
|
file=sys.stderr)
|
|
95
|
-
except InvalidAppTokenError:
|
|
96
|
-
print('The provided app token "{}" is invalid.'.format(options.app_token),
|
|
97
|
-
file=sys.stderr)
|
|
98
96
|
except InvalidTokenError:
|
|
99
97
|
print('The provided user token is invalid.', file=sys.stderr)
|
|
100
98
|
except InvalidLoginError:
|
|
@@ -719,6 +717,7 @@ class GetLogCommand(Command):
|
|
|
719
717
|
|
|
720
718
|
def __init__(self, subparsers, command_dict):
|
|
721
719
|
"""Get log status from robot
|
|
720
|
+
|
|
722
721
|
Args:
|
|
723
722
|
subparsers: List of argument parsers.
|
|
724
723
|
command_dict: Dictionary of command names which take parsed options.
|
|
@@ -728,9 +727,11 @@ class GetLogCommand(Command):
|
|
|
728
727
|
|
|
729
728
|
def _run(self, robot, options):
|
|
730
729
|
"""Implementation of the command.
|
|
730
|
+
|
|
731
731
|
Args:
|
|
732
732
|
robot: Robot object on which to run the command.
|
|
733
733
|
options: Parsed command-line arguments.
|
|
734
|
+
|
|
734
735
|
Returns:
|
|
735
736
|
True
|
|
736
737
|
"""
|
|
@@ -748,6 +749,7 @@ class GetActiveLogStatusesCommand(Command):
|
|
|
748
749
|
|
|
749
750
|
def __init__(self, subparsers, command_dict):
|
|
750
751
|
"""Retrieve active log statuses for robot.
|
|
752
|
+
|
|
751
753
|
Args:
|
|
752
754
|
subparsers: List of argument parsers.
|
|
753
755
|
command_dict: Dictionary of command names which take parsed options.
|
|
@@ -756,9 +758,11 @@ class GetActiveLogStatusesCommand(Command):
|
|
|
756
758
|
|
|
757
759
|
def _run(self, robot, options):
|
|
758
760
|
"""Implementation of the command.
|
|
761
|
+
|
|
759
762
|
Args:
|
|
760
763
|
robot: Robot object on which to run the command.
|
|
761
764
|
options: Parsed command-line arguments.
|
|
765
|
+
|
|
762
766
|
Returns:
|
|
763
767
|
True
|
|
764
768
|
"""
|
|
@@ -882,6 +886,7 @@ class StartRetroLogCommand(Command):
|
|
|
882
886
|
|
|
883
887
|
def __init__(self, subparsers, command_dict):
|
|
884
888
|
"""Start a retro log
|
|
889
|
+
|
|
885
890
|
Args:
|
|
886
891
|
subparsers: List of argument parsers.
|
|
887
892
|
command_dict: Dictionary of command names which take parsed options.
|
|
@@ -891,9 +896,11 @@ class StartRetroLogCommand(Command):
|
|
|
891
896
|
|
|
892
897
|
def _run(self, robot, options):
|
|
893
898
|
"""Implementation of the command.
|
|
899
|
+
|
|
894
900
|
Args:
|
|
895
901
|
robot: Robot object on which to run the command.
|
|
896
902
|
options: Parsed command-line arguments.
|
|
903
|
+
|
|
897
904
|
Returns:
|
|
898
905
|
True
|
|
899
906
|
"""
|
|
@@ -1903,7 +1910,6 @@ class GetImageCommand(Command):
|
|
|
1903
1910
|
print('Robot cannot generate the "{}" at this time. Retry the command.'.format(
|
|
1904
1911
|
options.source_name))
|
|
1905
1912
|
return False
|
|
1906
|
-
|
|
1907
1913
|
# Save the image files in the correct format (jpeg, pgm for raw/rle).
|
|
1908
1914
|
save_images_as_files(response)
|
|
1909
1915
|
|
|
@@ -2027,7 +2033,7 @@ class DataAcquisitionCommand(Subcommands):
|
|
|
2027
2033
|
"""
|
|
2028
2034
|
super(DataAcquisitionCommand, self).__init__(subparsers, command_dict, [
|
|
2029
2035
|
DataAcquisitionServiceCommand, DataAcquisitionRequestCommand,
|
|
2030
|
-
DataAcquisitionStatusCommand
|
|
2036
|
+
DataAcquisitionStatusCommand, DataAcquisitionGetLiveDataCommand
|
|
2031
2037
|
])
|
|
2032
2038
|
|
|
2033
2039
|
|
|
@@ -2110,9 +2116,10 @@ class DataAcquisitionServiceCommand(Command):
|
|
|
2110
2116
|
# Constants to describe width of columns for printing the data names and types
|
|
2111
2117
|
self._data_type_width = 15
|
|
2112
2118
|
self._data_name_width = 35
|
|
2113
|
-
self._service_name_width =
|
|
2119
|
+
self._service_name_width = 35
|
|
2120
|
+
self._has_live_data_width = 30
|
|
2114
2121
|
|
|
2115
|
-
def _format_and_print_capability(self, data_type, data_name, service_name=""):
|
|
2122
|
+
def _format_and_print_capability(self, data_type, data_name, service_name="", has_live_data=""):
|
|
2116
2123
|
"""Print the data acquisition capability.
|
|
2117
2124
|
|
|
2118
2125
|
Args:
|
|
@@ -2120,8 +2127,10 @@ class DataAcquisitionServiceCommand(Command):
|
|
|
2120
2127
|
data_name (string): The name of the data acquisition capability
|
|
2121
2128
|
service_name(string): For image capabilities, a service name is required.
|
|
2122
2129
|
"""
|
|
2123
|
-
print(
|
|
2124
|
-
|
|
2130
|
+
print(
|
|
2131
|
+
('{:' + str(self._data_type_width) + '} {:' + str(self._data_name_width) + '} {:' +
|
|
2132
|
+
str(self._service_name_width) + '} {:' + str(self._has_live_data_width) + '}').format(
|
|
2133
|
+
data_type, data_name, service_name, has_live_data))
|
|
2125
2134
|
|
|
2126
2135
|
def _run(self, robot, options):
|
|
2127
2136
|
"""Implementation of the 'info' command.
|
|
@@ -2133,10 +2142,13 @@ class DataAcquisitionServiceCommand(Command):
|
|
|
2133
2142
|
capabilities = robot.ensure_client(
|
|
2134
2143
|
DataAcquisitionClient.default_service_name).get_service_info()
|
|
2135
2144
|
print("Data Acquisition Service's Available Capabilities\n")
|
|
2136
|
-
self._format_and_print_capability("Data Type", "Data Name", "(optional) Service Name"
|
|
2137
|
-
|
|
2145
|
+
self._format_and_print_capability("Data Type", "Data Name", "(optional) Service Name",
|
|
2146
|
+
"(optional) has_live_data")
|
|
2147
|
+
print("-" * (self._data_type_width + self._data_name_width + self._service_name_width +
|
|
2148
|
+
self._has_live_data_width))
|
|
2138
2149
|
for data_name in capabilities.data_sources:
|
|
2139
|
-
self._format_and_print_capability("data", data_name.name, data_name.service_name
|
|
2150
|
+
self._format_and_print_capability("data", data_name.name, data_name.service_name,
|
|
2151
|
+
str(data_name.has_live_data))
|
|
2140
2152
|
for img_service in capabilities.image_sources:
|
|
2141
2153
|
for img in img_service.image_source_names:
|
|
2142
2154
|
self._format_and_print_capability("image", img, img_service.service_name)
|
|
@@ -2178,6 +2190,41 @@ class DataAcquisitionStatusCommand(Command):
|
|
|
2178
2190
|
return True
|
|
2179
2191
|
|
|
2180
2192
|
|
|
2193
|
+
class DataAcquisitionGetLiveDataCommand(Command):
|
|
2194
|
+
"""Call GetLiveData based on service name."""
|
|
2195
|
+
|
|
2196
|
+
NAME = 'live'
|
|
2197
|
+
|
|
2198
|
+
def __init__(self, subparsers, command_dict):
|
|
2199
|
+
"""Call GetLiveData based on service name.
|
|
2200
|
+
|
|
2201
|
+
Args:
|
|
2202
|
+
subparsers: List of argument parsers.
|
|
2203
|
+
command_dict: Dictionary of command names which take parsed options.
|
|
2204
|
+
"""
|
|
2205
|
+
super(DataAcquisitionGetLiveDataCommand, self).__init__(subparsers, command_dict)
|
|
2206
|
+
self._parser.add_argument('--data-source', metavar='DATA_SRC', default=[],
|
|
2207
|
+
help='Data source name', action='append', required=True)
|
|
2208
|
+
|
|
2209
|
+
def _run(self, robot, options):
|
|
2210
|
+
"""Implementation of the 'live' command.
|
|
2211
|
+
|
|
2212
|
+
Args:
|
|
2213
|
+
robot: Robot object on which to run the command.
|
|
2214
|
+
options: Parsed command-line arguments.
|
|
2215
|
+
|
|
2216
|
+
Returns:
|
|
2217
|
+
True once complete.
|
|
2218
|
+
"""
|
|
2219
|
+
daq_client = robot.ensure_client(DataAcquisitionClient.default_service_name)
|
|
2220
|
+
request = data_acquisition_pb2.LiveDataRequest()
|
|
2221
|
+
request.data_captures.extend(
|
|
2222
|
+
[data_acquisition_pb2.DataCapture(name=data_name) for data_name in options.data_source])
|
|
2223
|
+
response = daq_client.get_live_data(request)
|
|
2224
|
+
print(response)
|
|
2225
|
+
return True
|
|
2226
|
+
|
|
2227
|
+
|
|
2181
2228
|
class HostComputerIPCommand(Command):
|
|
2182
2229
|
"""Determine a computer's IP address."""
|
|
2183
2230
|
|
|
@@ -2248,6 +2295,7 @@ class PowerRobotCommand(Command):
|
|
|
2248
2295
|
|
|
2249
2296
|
def _run(self, robot, options):
|
|
2250
2297
|
"""
|
|
2298
|
+
|
|
2251
2299
|
Args:
|
|
2252
2300
|
robot: Robot object on which to run the command.
|
|
2253
2301
|
options: Parsed command-line arguments.
|
|
@@ -2282,6 +2330,7 @@ class PowerPayloadsCommand(Command):
|
|
|
2282
2330
|
|
|
2283
2331
|
def _run(self, robot, options):
|
|
2284
2332
|
"""
|
|
2333
|
+
|
|
2285
2334
|
Args:
|
|
2286
2335
|
robot: Robot object on which to run the command.
|
|
2287
2336
|
options: Parsed command-line arguments.
|
|
@@ -2317,6 +2366,7 @@ class PowerWifiRadioCommand(Command):
|
|
|
2317
2366
|
|
|
2318
2367
|
def _run(self, robot, options):
|
|
2319
2368
|
"""
|
|
2369
|
+
|
|
2320
2370
|
Args:
|
|
2321
2371
|
robot: Robot object on which to run the command.
|
|
2322
2372
|
options: Parsed command-line arguments.
|
|
@@ -2368,6 +2418,7 @@ def main(args=None):
|
|
|
2368
2418
|
|
|
2369
2419
|
# Create robot object and authenticate.
|
|
2370
2420
|
sdk = bosdyn.client.create_standard_sdk('BosdynClient')
|
|
2421
|
+
sdk.register_service_client(DataAcquisitionPluginClient)
|
|
2371
2422
|
|
|
2372
2423
|
robot = sdk.create_robot(options.hostname)
|
|
2373
2424
|
|
bosdyn/client/common.py
CHANGED
|
@@ -16,6 +16,7 @@ import grpc
|
|
|
16
16
|
from deprecated.sphinx import deprecated
|
|
17
17
|
|
|
18
18
|
from bosdyn.api.header_pb2 import CommonError
|
|
19
|
+
from bosdyn.deprecated import moved_to
|
|
19
20
|
|
|
20
21
|
from .channel import TransportError, translate_exception
|
|
21
22
|
from .data_chunk import chunk_message, parse_from_chunks
|
|
@@ -98,7 +99,7 @@ def custom_params_error(response, status_value=None, status_field_name='status',
|
|
|
98
99
|
|
|
99
100
|
def error_pair(error_message):
|
|
100
101
|
"""Creates a pair of an error class and the associated docstring as the error message
|
|
101
|
-
|
|
102
|
+
which can be used by the error_factory.
|
|
102
103
|
|
|
103
104
|
Args:
|
|
104
105
|
error_message: A class that inherits from the python Error class.
|
|
@@ -486,13 +487,12 @@ class BaseClient(object):
|
|
|
486
487
|
def _get_logger(self, rpc_method):
|
|
487
488
|
method_name = getattr(rpc_method, '_method', None)
|
|
488
489
|
if method_name:
|
|
489
|
-
method_name_short = str(method_name).
|
|
490
|
+
method_name_short = str(method_name.decode()).rsplit(BaseClient._SPLIT_METHOD, 1)[-1]
|
|
490
491
|
# This returns the same instance if it's been created before.
|
|
491
492
|
return self.logger.getChild(method_name_short)
|
|
492
493
|
return self.logger
|
|
493
494
|
|
|
494
|
-
chunk_message =
|
|
495
|
-
version='3.3.0')(chunk_message)
|
|
495
|
+
chunk_message = moved_to(chunk_message, version='3.3.0')
|
|
496
496
|
|
|
497
497
|
|
|
498
498
|
class FutureWrapper():
|
|
@@ -15,10 +15,11 @@ from google.protobuf import json_format
|
|
|
15
15
|
|
|
16
16
|
from bosdyn.api import data_acquisition_pb2 as data_acquisition
|
|
17
17
|
from bosdyn.api import data_acquisition_service_pb2_grpc as data_acquisition_service
|
|
18
|
-
from bosdyn.client.common import (BaseClient, common_header_errors,
|
|
19
|
-
handle_common_header_errors,
|
|
20
|
-
|
|
21
|
-
from bosdyn.
|
|
18
|
+
from bosdyn.client.common import (BaseClient, common_header_errors, custom_params_error,
|
|
19
|
+
error_factory, error_pair, handle_common_header_errors,
|
|
20
|
+
handle_custom_params_errors, handle_unset_status_error)
|
|
21
|
+
from bosdyn.client.exceptions import Error, InternalServerError, ResponseError
|
|
22
|
+
from bosdyn.util import now_nsec, now_sec, now_timestamp, seconds_to_duration
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class DataAcquisitionResponseError(ResponseError):
|
|
@@ -64,8 +65,7 @@ class DataAcquisitionClient(BaseClient):
|
|
|
64
65
|
if not self._timesync_endpoint:
|
|
65
66
|
data_timestamp = now_timestamp()
|
|
66
67
|
else:
|
|
67
|
-
data_timestamp = self._timesync_endpoint.robot_timestamp_from_local_secs(
|
|
68
|
-
time.time())
|
|
68
|
+
data_timestamp = self._timesync_endpoint.robot_timestamp_from_local_secs(now_sec())
|
|
69
69
|
action_id = data_acquisition.CaptureActionId(action_name=action_name, group_name=group_name,
|
|
70
70
|
timestamp=data_timestamp)
|
|
71
71
|
req = data_acquisition.AcquireDataRequest(acquisition_requests=acquisition_requests,
|
|
@@ -174,6 +174,7 @@ class DataAcquisitionClient(BaseClient):
|
|
|
174
174
|
|
|
175
175
|
def cancel_acquisition(self, request_id, **kwargs):
|
|
176
176
|
"""Cancel a data acquisition based on the request id.
|
|
177
|
+
|
|
177
178
|
Args:
|
|
178
179
|
request_id (int): The request id associated with an AcquireData request.
|
|
179
180
|
Raises:
|
|
@@ -197,6 +198,16 @@ class DataAcquisitionClient(BaseClient):
|
|
|
197
198
|
error_from_response=_cancel_acquisition_error, copy_request=False,
|
|
198
199
|
**kwargs)
|
|
199
200
|
|
|
201
|
+
def get_live_data(self, request):
|
|
202
|
+
"""Call the GetLiveData RPC of the plugin service."""
|
|
203
|
+
return self.call(self._stub.GetLiveData, request, error_from_response=_get_live_data_error,
|
|
204
|
+
copy_request=True)
|
|
205
|
+
|
|
206
|
+
def get_live_data_async(self, request):
|
|
207
|
+
"""Async version of the get_live_data() RPC."""
|
|
208
|
+
return self.call_async(self._stub.GetLiveData, request,
|
|
209
|
+
error_from_response=_get_live_data_error, copy_request=True)
|
|
210
|
+
|
|
200
211
|
|
|
201
212
|
_ACQUIRE_DATA_STATUS_TO_ERROR = collections.defaultdict(lambda:
|
|
202
213
|
(DataAcquisitionResponseError, None))
|
|
@@ -221,6 +232,17 @@ _CANCEL_ACQUISITION_STATUS_TO_ERROR.update({
|
|
|
221
232
|
error_pair(CancellationFailedError)
|
|
222
233
|
})
|
|
223
234
|
|
|
235
|
+
_CAPABILITY_LIVE_DATA_STATUS_TO_ERROR = collections.defaultdict(lambda: (None, None))
|
|
236
|
+
_CAPABILITY_LIVE_DATA_STATUS_TO_ERROR.update({
|
|
237
|
+
# STATUS_UNKNOWN is not handled directly.
|
|
238
|
+
data_acquisition.LiveDataResponse.CapabilityLiveData.STATUS_OK: (None, None),
|
|
239
|
+
data_acquisition.LiveDataResponse.CapabilityLiveData.STATUS_UNKNOWN_CAPTURE_TYPE:
|
|
240
|
+
error_pair(UnknownCaptureTypeError),
|
|
241
|
+
# STATUS_CUSTOM_PARAMS_ERROR is handled separately.
|
|
242
|
+
data_acquisition.LiveDataResponse.CapabilityLiveData.STATUS_INTERNAL_ERROR:
|
|
243
|
+
error_pair(InternalServerError),
|
|
244
|
+
})
|
|
245
|
+
|
|
224
246
|
|
|
225
247
|
def metadata_to_proto(metadata):
|
|
226
248
|
"""Checks the type to determine if a conversion is required to create a
|
|
@@ -278,6 +300,25 @@ def _cancel_acquisition_error(response):
|
|
|
278
300
|
status_to_error=_CANCEL_ACQUISITION_STATUS_TO_ERROR)
|
|
279
301
|
|
|
280
302
|
|
|
303
|
+
@handle_common_header_errors
|
|
304
|
+
def _get_live_data_error(response):
|
|
305
|
+
"""Return a custom exception based on the first invalid CapabilityLiveData, None if no error."""
|
|
306
|
+
for capability_live_data in response.live_data:
|
|
307
|
+
result = custom_params_error(capability_live_data, total_response=response)
|
|
308
|
+
if result is not None:
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
result = error_factory(
|
|
312
|
+
response, capability_live_data.status,
|
|
313
|
+
status_to_string=data_acquisition.LiveDataResponse.CapabilityLiveData.Status.Name,
|
|
314
|
+
status_to_error=_CAPABILITY_LIVE_DATA_STATUS_TO_ERROR)
|
|
315
|
+
if result is not None:
|
|
316
|
+
# The exception is using the capability_live_data. Replace it with the full response.
|
|
317
|
+
result.response = response
|
|
318
|
+
return result
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
|
|
281
322
|
def _get_service_info_capabilities(response):
|
|
282
323
|
return response.capabilities
|
|
283
324
|
|
|
@@ -14,8 +14,8 @@ from bosdyn.client.common import (BaseClient, error_factory, error_pair,
|
|
|
14
14
|
handle_common_header_errors, handle_custom_params_errors,
|
|
15
15
|
handle_unset_status_error)
|
|
16
16
|
from bosdyn.client.data_acquisition import (DataAcquisitionClient, DataAcquisitionResponseError,
|
|
17
|
-
UnknownCaptureTypeError,
|
|
18
|
-
get_request_id, metadata_to_proto)
|
|
17
|
+
UnknownCaptureTypeError, _get_live_data_error,
|
|
18
|
+
acquire_data_error, get_request_id, metadata_to_proto)
|
|
19
19
|
from bosdyn.util import now_timestamp
|
|
20
20
|
|
|
21
21
|
|
|
@@ -78,6 +78,16 @@ class DataAcquisitionPluginClient(BaseClient):
|
|
|
78
78
|
return self.call_async(self._stub.AcquirePluginData, request,
|
|
79
79
|
error_from_response=acquire_data_error, copy_request=False, **kwargs)
|
|
80
80
|
|
|
81
|
+
def get_live_data(self, request):
|
|
82
|
+
"""Call the GetLiveData RPC of the plugin service."""
|
|
83
|
+
return self.call(self._stub.GetLiveData, request, error_from_response=_get_live_data_error,
|
|
84
|
+
copy_request=True)
|
|
85
|
+
|
|
86
|
+
def get_live_data_async(self, request):
|
|
87
|
+
"""Async version of the get_live_data() RPC."""
|
|
88
|
+
return self.call_async(self._stub.GetLiveData, request,
|
|
89
|
+
error_from_response=_get_live_data_error, copy_request=True)
|
|
90
|
+
|
|
81
91
|
# The get_status, get_service_info, and cancel_acquisition methods are identical to the ones
|
|
82
92
|
# implemented in the DataAcquisitionClient.
|
|
83
93
|
if sys.version_info.major == 2:
|
|
@@ -286,6 +286,8 @@ class DataAcquisitionPluginService(
|
|
|
286
286
|
the data collection function or saving any data. Ordered input arguments (to acquire_response_fn):
|
|
287
287
|
data_acquisition_pb2.AcquirePluginDataRequest, data_acquisition_pb2.AcquirePluginDataResponse. Output (to
|
|
288
288
|
data_collect_fn): Boolean
|
|
289
|
+
live_response_fn: Optional function that sends signals data to the robot for purposes of displaying it on the tablet and Orbit during teleoperation. Input argument (to live_response_fn):
|
|
290
|
+
data_acquisition_pb2.LiveDataRequest.
|
|
289
291
|
executor: Optional thread pool.
|
|
290
292
|
|
|
291
293
|
Attributes:
|
|
@@ -294,6 +296,7 @@ class DataAcquisitionPluginService(
|
|
|
294
296
|
this plugin can do.
|
|
295
297
|
data_collect_fn: Function that performs the data collection and storage.
|
|
296
298
|
acquire_response_fn: Function that can validate a request and provide a timeout deadline.
|
|
299
|
+
live_response_fn: Function that sends signals data to the robot for purposes of displaying it on the tablet and Orbit during teleoperation.
|
|
297
300
|
request_manager (RequestManager): Helper class which manages the RequestStates created with
|
|
298
301
|
each acquisition RPC.
|
|
299
302
|
executor (ThreadPoolExecutor): Thread pool to run the plugin service on.
|
|
@@ -303,7 +306,7 @@ class DataAcquisitionPluginService(
|
|
|
303
306
|
service_type = 'bosdyn.api.DataAcquisitionPluginService'
|
|
304
307
|
|
|
305
308
|
def __init__(self, robot, capabilities, data_collect_fn, acquire_response_fn=None,
|
|
306
|
-
executor=None, logger=None):
|
|
309
|
+
executor=None, logger=None, live_response_fn=None):
|
|
307
310
|
super(DataAcquisitionPluginService, self).__init__()
|
|
308
311
|
self.logger = logger or _LOGGER
|
|
309
312
|
self.capabilities = capabilities
|
|
@@ -313,6 +316,7 @@ class DataAcquisitionPluginService(
|
|
|
313
316
|
}
|
|
314
317
|
self.data_collect_fn = data_collect_fn
|
|
315
318
|
self.acquire_response_fn = acquire_response_fn
|
|
319
|
+
self.live_response_fn = live_response_fn
|
|
316
320
|
self.request_manager = RequestManager()
|
|
317
321
|
self.executor = executor or ThreadPoolExecutor(max_workers=2)
|
|
318
322
|
self.robot = robot
|
|
@@ -320,7 +324,7 @@ class DataAcquisitionPluginService(
|
|
|
320
324
|
self.data_buffer_client = robot.ensure_client(DataBufferClient.default_service_name)
|
|
321
325
|
|
|
322
326
|
def validate_params(self, request, response):
|
|
323
|
-
"""Validate that any parameters set in the request are valid according the
|
|
327
|
+
"""Validate that any parameters set in the request are valid according the spec."""
|
|
324
328
|
for capture in request.acquisition_requests.data_captures:
|
|
325
329
|
try:
|
|
326
330
|
error = self.value_validators[capture.name](capture.custom_params)
|
|
@@ -468,6 +472,33 @@ class DataAcquisitionPluginService(
|
|
|
468
472
|
populate_response_header(response, request)
|
|
469
473
|
return response
|
|
470
474
|
|
|
475
|
+
def GetLiveData(self, request, context):
|
|
476
|
+
"""Get the live data available from this plugin.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
request (data_acquisition_pb2.LiveDataRequest): The live data request with params.
|
|
480
|
+
context (GRPC ClientContext): tracks internal grpc statuses and information (unused).
|
|
481
|
+
Returns:
|
|
482
|
+
response (data_acquisition_pb2.LiveDataResponse): Result of live_response_fn.
|
|
483
|
+
"""
|
|
484
|
+
response = data_acquisition_pb2.LiveDataResponse()
|
|
485
|
+
with ResponseContext(response, request, self.data_buffer_client):
|
|
486
|
+
try:
|
|
487
|
+
if self.live_response_fn is not None:
|
|
488
|
+
response = self.live_response_fn(request)
|
|
489
|
+
populate_response_header(response, request)
|
|
490
|
+
else:
|
|
491
|
+
populate_response_header(
|
|
492
|
+
response, request,
|
|
493
|
+
error_code=header_pb2.CommonError.CODE_INTERNAL_SERVER_ERROR,
|
|
494
|
+
error_msg="live_response_fn is None")
|
|
495
|
+
except Exception as general_error: # pylint: disable=broad-exception-caught
|
|
496
|
+
self.logger.exception("Failed during call to user live response function")
|
|
497
|
+
populate_response_header(
|
|
498
|
+
response, request, error_code=header_pb2.CommonError.CODE_INTERNAL_SERVER_ERROR,
|
|
499
|
+
error_msg=str(general_error))
|
|
500
|
+
return response
|
|
501
|
+
|
|
471
502
|
|
|
472
503
|
# pylint: disable=protected-access
|
|
473
504
|
class RequestManager:
|
|
@@ -255,6 +255,40 @@ class DataAcquisitionStoreClient(BaseClient):
|
|
|
255
255
|
error_from_response=common_header_errors, copy_request=False,
|
|
256
256
|
**kwargs)
|
|
257
257
|
|
|
258
|
+
def query_stored_captures(self, query=None, **kwargs):
|
|
259
|
+
"""Query stored captures from the robot.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
query (bosdyn.api.DataQueryParams) : Query parameters.
|
|
263
|
+
Raises:
|
|
264
|
+
RpcError: Problem communicating with the robot.
|
|
265
|
+
"""
|
|
266
|
+
request = data_acquisition_store.QueryStoredCapturesRequest(query=query)
|
|
267
|
+
self._apply_request_processors(request, copy_request=False)
|
|
268
|
+
return self.call(self._stub.QueryStoredCaptures, request,
|
|
269
|
+
error_from_response=common_header_errors,
|
|
270
|
+
assemble_type=data_acquisition_store.QueryStoredCapturesResponse,
|
|
271
|
+
copy_request=False, **kwargs)
|
|
272
|
+
|
|
273
|
+
def query_max_capture_id(self, **kwargs):
|
|
274
|
+
"""Query max capture id from the robot.
|
|
275
|
+
Returns:
|
|
276
|
+
QueryMaxCaptureIdResult, which has a max_capture_id uint64, corresponding to the
|
|
277
|
+
greatest capture id on the robot. Used for skiping DAQ synchronization
|
|
278
|
+
on connect.
|
|
279
|
+
"""
|
|
280
|
+
request = data_acquisition_store.QueryMaxCaptureIdRequest()
|
|
281
|
+
return self.call(self._stub.QueryMaxCaptureId, request,
|
|
282
|
+
value_from_response=_get_max_capture_id,
|
|
283
|
+
error_from_response=common_header_errors, copy_request=False, **kwargs)
|
|
284
|
+
|
|
285
|
+
def query_max_capture_id_async(self, **kwargs):
|
|
286
|
+
"""Async version of the query_max_capture_id() RPC."""
|
|
287
|
+
request = data_acquisition_store.QueryMaxCaptureIdRequest(query=query)
|
|
288
|
+
return self.call_async(self._stub.QueryMaxCaptureId, request,
|
|
289
|
+
value_from_response=_get_max_capture_id,
|
|
290
|
+
error_from_response=common_header_errors, copy_request=False,
|
|
291
|
+
**kwargs)
|
|
258
292
|
|
|
259
293
|
|
|
260
294
|
def _get_action_ids(response):
|
|
@@ -271,3 +305,7 @@ def _get_image(response):
|
|
|
271
305
|
|
|
272
306
|
def _get_metadata(response):
|
|
273
307
|
return response.metadata
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _get_max_capture_id(response):
|
|
311
|
+
return response.max_capture_id
|
bosdyn/client/data_buffer.py
CHANGED
|
@@ -143,10 +143,7 @@ class DataBufferClient(BaseClient):
|
|
|
143
143
|
def _do_add_text_messages(self, func, text_messages, **kwargs):
|
|
144
144
|
"""Internal text message RPC stub call."""
|
|
145
145
|
request = data_buffer_protos.RecordTextMessagesRequest()
|
|
146
|
-
|
|
147
|
-
# pylint: disable=no-member
|
|
148
|
-
request.text_messages.add().CopyFrom(in_text_msg)
|
|
149
|
-
|
|
146
|
+
request.text_messages.extend(text_messages)
|
|
150
147
|
return func(self._stub.RecordTextMessages, request, value_from_response=None,
|
|
151
148
|
error_from_response=common_header_errors, **kwargs)
|
|
152
149
|
|
|
@@ -364,7 +361,7 @@ class DataBufferClient(BaseClient):
|
|
|
364
361
|
|
|
365
362
|
|
|
366
363
|
class LoggingHandler(logging.Handler): # pylint: disable=too-many-instance-attributes
|
|
367
|
-
"""A logging system Handler that will publish text to
|
|
364
|
+
"""A logging system Handler that will publish text to the data-buffer service.
|
|
368
365
|
|
|
369
366
|
Args:
|
|
370
367
|
service: Name of the service. See LogAnnotationTextMessage.
|
|
@@ -375,6 +372,7 @@ class LoggingHandler(logging.Handler): # pylint: disable=too-many-instance-attr
|
|
|
375
372
|
msg_num_limit: If number of messages reaches this number, send data with data_buffer_client.
|
|
376
373
|
msg_age_limit: If messages have been sitting locally for this many seconds, send data with
|
|
377
374
|
data_buffer_client.
|
|
375
|
+
skip_rpcs: Do not log any messages for RPC sending.
|
|
378
376
|
|
|
379
377
|
Raises:
|
|
380
378
|
log_annotation.InvalidArgument: The TimeSyncEndpoint is not valid.
|
|
@@ -382,8 +380,11 @@ class LoggingHandler(logging.Handler): # pylint: disable=too-many-instance-attr
|
|
|
382
380
|
|
|
383
381
|
def __init__( # pylint: disable=too-many-arguments
|
|
384
382
|
self, service, data_buffer_client, level=logging.NOTSET, time_sync_endpoint=None,
|
|
385
|
-
rpc_timeout=1, msg_num_limit=10, msg_age_limit=1):
|
|
383
|
+
rpc_timeout=1, msg_num_limit=10, msg_age_limit=1, skip_rpcs=False):
|
|
386
384
|
logging.Handler.__init__(self, level=level)
|
|
385
|
+
self.addFilter(is_not_text_log)
|
|
386
|
+
if skip_rpcs:
|
|
387
|
+
self.addFilter(is_not_rpc)
|
|
387
388
|
self.msg_age_limit = msg_age_limit
|
|
388
389
|
self.msg_num_limit = msg_num_limit
|
|
389
390
|
self.rpc_timeout = rpc_timeout
|
|
@@ -544,11 +545,12 @@ class LoggingHandler(logging.Handler): # pylint: disable=too-many-instance-attr
|
|
|
544
545
|
if len(self._msg_queue) >= self._dump_msg_count:
|
|
545
546
|
self._dump_msg_queue()
|
|
546
547
|
|
|
547
|
-
def record_to_msg(self, record):
|
|
548
|
+
def record_to_msg(self, record: logging.LogRecord):
|
|
548
549
|
"""Convert logging record to TextMessage proto."""
|
|
549
550
|
level = self.record_level_to_proto_level(record.levelno)
|
|
550
551
|
msg = data_buffer_protos.TextMessage(source=self.service, level=level,
|
|
551
|
-
message=self.format(record)
|
|
552
|
+
message=self.format(record), filename=record.filename,
|
|
553
|
+
line_number=record.lineno)
|
|
552
554
|
# pylint: disable=no-member
|
|
553
555
|
if self.time_sync_endpoint is not None:
|
|
554
556
|
try:
|
|
@@ -572,3 +574,15 @@ class LoggingHandler(logging.Handler): # pylint: disable=too-many-instance-attr
|
|
|
572
574
|
if record_level >= logging.INFO:
|
|
573
575
|
return data_buffer_protos.TextMessage.LEVEL_INFO
|
|
574
576
|
return data_buffer_protos.TextMessage.LEVEL_DEBUG
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def is_not_text_log(record: logging.LogRecord) -> bool:
|
|
580
|
+
"""Filter out the RecordMessages calls that the handler sends so that we do not go into an infinite loop."""
|
|
581
|
+
return not record.name.endswith('.DataBufferService.RecordTextMessages')
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def is_not_rpc(record: logging.LogRecord) -> bool:
|
|
585
|
+
"""Because our loggers use the form sdk_name.robot_name.service_name.rpc_method, we can't easily just turn
|
|
586
|
+
off all logging for rpcs. This function identifies the rpc logging calls to be able to strip them out."""
|
|
587
|
+
return not (record.module == 'common' and
|
|
588
|
+
(record.funcName == 'call' or record.funcName == 'call_async'))
|
bosdyn/client/data_chunk.py
CHANGED
|
@@ -55,7 +55,6 @@ class DirectoryRegistrationClient(BaseClient):
|
|
|
55
55
|
host_ip,
|
|
56
56
|
port,
|
|
57
57
|
user_token_required=True,
|
|
58
|
-
application_token_required=False,
|
|
59
58
|
liveness_timeout_secs=0,
|
|
60
59
|
**kwargs):
|
|
61
60
|
"""Register a service routing with the robot.
|
|
@@ -72,7 +71,6 @@ class DirectoryRegistrationClient(BaseClient):
|
|
|
72
71
|
host_ip: The ip address of the system that the service is being hosted on.
|
|
73
72
|
port: The port number the service can be accessed through on the host system.
|
|
74
73
|
user_token_required: If a user token should be verified to access the service.
|
|
75
|
-
application_token_required: Deprecated - Do not use.
|
|
76
74
|
liveness_timeout_secs: Number of seconds without directory heartbeat before timeout fault.
|
|
77
75
|
|
|
78
76
|
Raises:
|
|
@@ -80,11 +78,6 @@ class DirectoryRegistrationClient(BaseClient):
|
|
|
80
78
|
ServiceAlreadyExistsError: The service already exists.
|
|
81
79
|
DirectoryRegistrationResponseError: Something went wrong during the directory registration.
|
|
82
80
|
"""
|
|
83
|
-
if (application_token_required):
|
|
84
|
-
_LOGGER.warning(
|
|
85
|
-
'The application_token_required parameter has been deprecated and will have no effect.'
|
|
86
|
-
)
|
|
87
|
-
|
|
88
81
|
service_entry = directory_pb2.ServiceEntry(
|
|
89
82
|
name=name,
|
|
90
83
|
type=service_type,
|
|
@@ -125,7 +118,6 @@ class DirectoryRegistrationClient(BaseClient):
|
|
|
125
118
|
host_ip,
|
|
126
119
|
port,
|
|
127
120
|
user_token_required=True,
|
|
128
|
-
application_token_required=False,
|
|
129
121
|
liveness_timeout_secs=0,
|
|
130
122
|
**kwargs):
|
|
131
123
|
"""Update a service definition of an existing service that matches the service name.
|
|
@@ -142,7 +134,6 @@ class DirectoryRegistrationClient(BaseClient):
|
|
|
142
134
|
host_ip: The ip address of the system that the service is being hosted on.
|
|
143
135
|
port: The port number the service can be accessed through on the host system.
|
|
144
136
|
user_token_required: If a user token should be verified to access the service.
|
|
145
|
-
application_token_required Deprecated - Do not use.
|
|
146
137
|
liveness_timeout_secs: Number of seconds without directory heartbeat before timeout fault.
|
|
147
138
|
|
|
148
139
|
Raises:
|
|
@@ -150,10 +141,6 @@ class DirectoryRegistrationClient(BaseClient):
|
|
|
150
141
|
ServiceDoesNotExistError: The service does not exist.
|
|
151
142
|
DirectoryRegistrationResponseError: Something went wrong during the directory registration.
|
|
152
143
|
"""
|
|
153
|
-
if (application_token_required):
|
|
154
|
-
_LOGGER.warning(
|
|
155
|
-
'The application_token_required parameter has been deprecated and will have no effect.'
|
|
156
|
-
)
|
|
157
144
|
|
|
158
145
|
service_entry = directory_pb2.ServiceEntry(
|
|
159
146
|
name=name,
|
|
@@ -274,7 +261,7 @@ def reset_service_registration(
|
|
|
274
261
|
|
|
275
262
|
This is useful when a program wants to register a new service but there may be an old entry
|
|
276
263
|
in the robot directory from a previous instance of the program. If the service
|
|
277
|
-
does not already
|
|
264
|
+
does not already exist, the exception will be suppressed and a new registration will
|
|
278
265
|
still be performed. Unregistering the service has the advantage of clearing all service
|
|
279
266
|
faults, if any existed.
|
|
280
267
|
"""
|
bosdyn/client/exceptions.py
CHANGED
|
@@ -81,10 +81,6 @@ class ClientCancelledOperationError(PersistentRpcError):
|
|
|
81
81
|
"""The user cancelled the rpc request."""
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
class InvalidAppTokenError(PersistentRpcError):
|
|
85
|
-
"""The provided app token is invalid."""
|
|
86
|
-
|
|
87
|
-
|
|
88
84
|
class InvalidClientCertificateError(PersistentRpcError):
|
|
89
85
|
"""The provided client certificate is invalid."""
|
|
90
86
|
|