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.
Files changed (56) hide show
  1. bosdyn/client/__init__.py +5 -6
  2. bosdyn/client/area_callback_region_handler_base.py +19 -4
  3. bosdyn/client/area_callback_service_servicer.py +29 -1
  4. bosdyn/client/area_callback_service_utils.py +45 -51
  5. bosdyn/client/auth.py +13 -28
  6. bosdyn/client/autowalk.py +3 -0
  7. bosdyn/client/channel.py +23 -26
  8. bosdyn/client/command_line.py +64 -13
  9. bosdyn/client/common.py +4 -4
  10. bosdyn/client/data_acquisition.py +47 -6
  11. bosdyn/client/data_acquisition_plugin.py +12 -2
  12. bosdyn/client/data_acquisition_plugin_service.py +33 -2
  13. bosdyn/client/data_acquisition_store.py +38 -0
  14. bosdyn/client/data_buffer.py +22 -8
  15. bosdyn/client/data_chunk.py +1 -0
  16. bosdyn/client/directory_registration.py +1 -14
  17. bosdyn/client/exceptions.py +0 -4
  18. bosdyn/client/frame_helpers.py +3 -1
  19. bosdyn/client/gps/NMEAParser.py +189 -0
  20. bosdyn/client/gps/__init__.py +6 -0
  21. bosdyn/client/gps/aggregator_client.py +56 -0
  22. bosdyn/client/gps/gps_listener.py +153 -0
  23. bosdyn/client/gps/registration_client.py +48 -0
  24. bosdyn/client/graph_nav.py +50 -20
  25. bosdyn/client/image.py +20 -7
  26. bosdyn/client/image_service_helpers.py +14 -14
  27. bosdyn/client/lease.py +27 -22
  28. bosdyn/client/lease_validator.py +5 -5
  29. bosdyn/client/manipulation_api_client.py +1 -1
  30. bosdyn/client/map_processing.py +10 -5
  31. bosdyn/client/math_helpers.py +21 -11
  32. bosdyn/client/metrics_logging.py +147 -0
  33. bosdyn/client/network_compute_bridge_client.py +6 -0
  34. bosdyn/client/power.py +40 -0
  35. bosdyn/client/recording.py +3 -3
  36. bosdyn/client/robot.py +15 -16
  37. bosdyn/client/robot_command.py +341 -203
  38. bosdyn/client/robot_id.py +6 -5
  39. bosdyn/client/robot_state.py +6 -0
  40. bosdyn/client/sdk.py +5 -11
  41. bosdyn/client/server_util.py +11 -11
  42. bosdyn/client/service_customization_helpers.py +776 -64
  43. bosdyn/client/signals_helpers.py +105 -0
  44. bosdyn/client/spot_cam/compositor.py +6 -2
  45. bosdyn/client/spot_cam/ptz.py +24 -14
  46. bosdyn/client/spot_check.py +160 -0
  47. bosdyn/client/time_sync.py +5 -5
  48. bosdyn/client/units_helpers.py +39 -0
  49. bosdyn/client/util.py +100 -64
  50. bosdyn/client/world_object.py +5 -5
  51. {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/METADATA +4 -3
  52. bosdyn_client-4.0.1.dist-info/RECORD +97 -0
  53. {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/WHEEL +1 -1
  54. bosdyn/client/log_annotation.py +0 -359
  55. bosdyn_client-3.3.2.dist-info/RECORD +0 -90
  56. {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/top_level.txt +0 -0
@@ -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, InvalidAppTokenError, InvalidRequestError, ProxyConnectionError
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 = 30
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(('{:' + str(self._data_type_width) + '} {:' + str(self._data_name_width) + '} {:' +
2124
- str(self._service_name_width) + '}').format(data_type, data_name, service_name))
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
- print("-" * (self._data_type_width + self._data_name_width + self._service_name_width))
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
- which can be used by the error_factory.
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).split(BaseClient._SPLIT_METHOD)[-1]
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 = deprecated(reason='Use bosdyn.client.data_chunk.chunk_message() instead.',
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, error_factory, error_pair,
19
- handle_common_header_errors, handle_unset_status_error)
20
- from bosdyn.client.exceptions import Error, ResponseError
21
- from bosdyn.util import now_timestamp, seconds_to_duration
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, acquire_data_error,
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 the spec."""
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
@@ -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
- for in_text_msg in text_messages:
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 a the data-buffer service.
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'))
@@ -27,6 +27,7 @@ def chunk_serialized(serialized: bytes, data_chunk_byte_size: int):
27
27
 
28
28
  def chunk_message(message, data_chunk_byte_size: int):
29
29
  """Take a message, and split it into data chunks
30
+
30
31
  Args:
31
32
  data_chunk_byte_size: max size of each streamed message
32
33
  """
@@ -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 exists, the exception will be suppressed and a new registration will
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
  """
@@ -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