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
bosdyn/client/__init__.py CHANGED
@@ -8,18 +8,17 @@
8
8
  Sets up some convenience imports for commonly used classes.
9
9
  """
10
10
  # yapf: enable
11
- from .auth import (AuthClient, ExpiredApplicationTokenError, InvalidApplicationTokenError,
12
- InvalidLoginError, InvalidTokenError)
11
+ from .auth import AuthClient, InvalidLoginError, InvalidTokenError
13
12
  from .common import BaseClient
14
13
  # yapf: disable
15
14
  from .exceptions import (ClientCancelledOperationError, CustomParamError, Error,
16
- InternalServerError, InvalidAppTokenError, InvalidClientCertificateError,
17
- InvalidRequestError, LeaseUseError, LicenseError,
18
- NonexistentAuthorityError, NotFoundError, PersistentRpcError,
19
- ProxyConnectionError, ResponseError, RetryableRpcError,
15
+ InternalServerError, InvalidClientCertificateError, InvalidRequestError,
16
+ LeaseUseError, LicenseError, NonexistentAuthorityError, NotFoundError,
17
+ PersistentRpcError, ProxyConnectionError, ResponseError, RetryableRpcError,
20
18
  RetryableUnavailableError, RpcError, ServerError,
21
19
  ServiceFailedDuringExecutionError, ServiceUnavailableError, TimedOutError,
22
20
  TooManyRequestsError, UnableToConnectToRobotError, UnauthenticatedError,
23
21
  UnimplementedError, UnknownDnsNameError, UnsetStatusError)
24
22
  from .robot import Robot
25
23
  from .sdk import BOSDYN_RESOURCE_ROOT, Sdk, create_standard_sdk
24
+
@@ -50,6 +50,15 @@ class CallbackTimedOutError(HandlerError):
50
50
  re-raised to make sure the response is set correctly."""
51
51
 
52
52
 
53
+ class RouteChangedResult:
54
+ """Options for how the helper class should respond to a route change."""
55
+
56
+ def __init__(self):
57
+ # Specify that if the callback has stopped (returned or raised from run()) that run()
58
+ # should be called again.
59
+ self.rerun_if_stopped = False
60
+
61
+
53
62
  class AreaCallbackRegionHandlerBase:
54
63
  """Base class for implementing a AreaCallbackRegionHandler.
55
64
 
@@ -100,6 +109,12 @@ class AreaCallbackRegionHandlerBase:
100
109
  """This function is called after run thread has finished and client calls EndCallback."""
101
110
  raise NotImplementedError("Derived class must implement this function.")
102
111
 
112
+ def route_changed(self, request: area_callback_pb2.RouteChangeRequest) -> RouteChangedResult:
113
+ """This function is called when Graph Nav re-routes inside the callback region.
114
+ In most cases, the callback does not need to do anything for this case and can leave the
115
+ default implementation"""
116
+ return RouteChangedResult()
117
+
103
118
  @property
104
119
  def area_callback_information(self) -> area_callback_pb2.AreaCallbackInformation:
105
120
  """Get area_callback_pb2.AreaCallbackInformation."""
@@ -155,10 +170,10 @@ class AreaCallbackRegionHandlerBase:
155
170
  self._update_response.complete.SetInParent()
156
171
 
157
172
  def set_localization_at_end(self):
158
- """Set the localization hint to the end of the callback region, indicating that graph nav
173
+ """Set the localization hint to the end of the callback region, indicating that graph nav
159
174
  that navigation should continue from this point.
160
175
  Robot control is required to set this. It should be called after walking to the end of
161
- the region, but before ceding control.
176
+ the region, but before ceding control.
162
177
 
163
178
  Raises:
164
179
  IncorrectUsage: When called without robot control.
@@ -243,7 +258,7 @@ class AreaCallbackRegionHandlerBase:
243
258
  sleep_time_secs (float): Time to sleep, in seconds.
244
259
 
245
260
  Raises:
246
- HandlerError: When a shutdown is requested during the sleep time..
261
+ HandlerError: When a shutdown is requested during the sleep time.
247
262
  """
248
263
  if self.robot.time_sec() > self._end_time:
249
264
  raise CallbackTimedOutError()
@@ -318,7 +333,7 @@ class AreaCallbackRegionHandlerBase:
318
333
  """Wrapper around the run function which catches exceptions and set update response.
319
334
 
320
335
  Args:
321
- shutdown_event (Event): Event that signals the run thread to shutdown.
336
+ shutdown_event (Event): Event that signals the run thread to shut down.
322
337
  """
323
338
  self._shutdown_event = shutdown_event
324
339
  _LOGGER.info('Beginning callback')
@@ -159,6 +159,34 @@ class AreaCallbackServiceServicer(area_callback_service_pb2_grpc.AreaCallbackSer
159
159
  self.area_callback_region_handler = None
160
160
  return response
161
161
 
162
+ def RouteChange(self, request, context):
163
+ """Called when we re-route within the callback. Most callbacks do not need to know about
164
+ changes in the route, and can ignore this."""
165
+ _LOGGER.info('Received RouteChange for command %d', request.command_id)
166
+ response = area_callback_pb2.RouteChangeResponse()
167
+ with ResponseContext(response, request, self._rpc_logger):
168
+ if (not self.area_callback_region_handler or
169
+ not self._is_active_command_id(request.command_id)):
170
+ response.status = area_callback_pb2.EndCallbackResponse.STATUS_INVALID_COMMAND_ID
171
+ return response
172
+ try:
173
+ route_changed_result = self.area_callback_region_handler.route_changed(request)
174
+ if (route_changed_result.rerun_if_stopped and
175
+ not self.area_callback_active_thread.is_alive()):
176
+ self.area_callback_active_thread_event = Event()
177
+ self.area_callback_active_thread = Thread(
178
+ target=self.area_callback_region_handler.internal_run_wrapper,
179
+ args=[self.area_callback_active_thread_event])
180
+ self.area_callback_active_thread.start()
181
+ _LOGGER.info('Re-created thread for command id %d', self._active_command_id)
182
+ response.status = response.STATUS_OK
183
+ except Exception as exc:
184
+ _LOGGER.exception("Failed route_changed_call")
185
+ response.header.error.code = response.header.error.CODE_INTERNAL_SERVER_ERROR
186
+ response.header.error.message = str(exc)
187
+
188
+ return response
189
+
162
190
  def _is_expired(self, end_time):
163
191
  current_robot_time_secs = self.robot.time_sec()
164
192
  end_time_secs = timestamp_to_sec(end_time)
@@ -213,7 +241,7 @@ class AreaCallbackServiceServicer(area_callback_service_pb2_grpc.AreaCallbackSer
213
241
  """Call to force run thread to terminate.
214
242
 
215
243
  Args:
216
- timeout (float, optional): Time allowed to run thread to shutdown. Defaults to 5.
244
+ timeout (float, optional): Time allowed to run thread to shut down. Defaults to 5.
217
245
 
218
246
  Returns:
219
247
  bool: True if the thread correctly shut down within the allowed time.
@@ -8,13 +8,13 @@ import logging
8
8
  import time
9
9
  from typing import List
10
10
 
11
+ import bosdyn.client
11
12
  from bosdyn.api import service_fault_pb2
12
13
  from bosdyn.api.graph_nav import area_callback_pb2
13
14
  from bosdyn.api.service_customization_pb2 import DictParam
14
15
  from bosdyn.client.directory import NonexistentServiceError
15
- from bosdyn.client.exceptions import RpcError
16
- from bosdyn.client.fault import (FaultResponseError, ServiceFaultAlreadyExistsError,
17
- ServiceFaultDoesNotExistError)
16
+ from bosdyn.client.fault import ServiceFaultAlreadyExistsError, ServiceFaultDoesNotExistError
17
+ from bosdyn.client.service_customization_helpers import dict_params_to_dict
18
18
 
19
19
  _LOGGER = logging.getLogger(__name__)
20
20
 
@@ -43,49 +43,18 @@ class AreaCallbackServiceConfig:
43
43
 
44
44
  def parse_params(self, params: DictParam):
45
45
  """ Parse params and validate they agree with the spec stored in area_callback_information.
46
-
46
+
47
47
  Args:
48
48
  params (DictParam): The parameters being validated.
49
49
  """
50
-
51
- spec = self.area_callback_information.custom_params
52
- values = {}
53
- for (key, param) in params.values.items():
54
- if key not in spec.specs:
55
- raise ValueError(f"No spec provided for parameter {key}.")
56
-
57
- param_spec = spec.specs[key].spec
58
- spec_field = param_spec.WhichOneof("spec")
59
- value_field = spec_field.replace("spec", "value")
60
- if not param.HasField(value_field):
61
- raise ValueError(f"Param {key} has a {spec_field}, but has no {value_field}.")
62
-
63
- type_spec = getattr(param_spec, spec_field)
64
- param_value = getattr(param, value_field)
65
-
66
- # For numerical values, check bounds.
67
- if hasattr(type_spec, "min_value") and \
68
- type_spec.HasField("min_value") and \
69
- param_value.value < type_spec.min_value.value:
70
- raise ValueError(f"Value for {key} too low; minimum is {type_spec.min_value.value} " \
71
- f"but passed value of {param_value.value}")
72
-
73
- if hasattr(type_spec, "max_value") and \
74
- type_spec.HasField("max_value") and \
75
- param_value.value > type_spec.max_value.value:
76
- raise ValueError(f"Value for {key} too high; maximum is {type_spec.max_value.value} " \
77
- f"but passed value of {param_value.value}")
78
-
79
- values[key] = param_value.value
80
-
81
- return values
50
+ return dict_params_to_dict(params, self.area_callback_information.custom_params)
82
51
 
83
52
 
84
53
  # Helper to raise service faults when other services are unavailable.
85
54
  def handle_service_faults(fault_client, robot_state_client, directory_client, service_name,
86
55
  prereq_services):
87
56
  service_fault = service_fault_pb2.ServiceFault()
88
- service_fault.fault_id.fault_name = f'{service_name}'
57
+ service_fault.fault_id.fault_name = service_name
89
58
  service_fault.fault_id.service_name = service_name
90
59
  service_fault.severity = service_fault_pb2.ServiceFault.SEVERITY_CRITICAL
91
60
  check_period = 0.5 # seconds.
@@ -98,43 +67,68 @@ def handle_service_faults(fault_client, robot_state_client, directory_client, se
98
67
  registered_service = directory_client.get_entry(service_name)
99
68
  except NonexistentServiceError as exc:
100
69
  continue
70
+ except bosdyn.client.Error as exc:
71
+ _LOGGER.error("Failed to check if %s exists during fault handling: %s", service_name,
72
+ exc)
73
+ continue
101
74
 
102
75
  set_fault = False
76
+ fault_exists = False
103
77
  unavailable_services = []
104
- for service in prereq_services:
78
+
79
+ # Need robot state to check existing faults.
80
+ try:
81
+ state = robot_state_client.get_robot_state()
82
+ except bosdyn.client.Error as exc:
83
+ _LOGGER.error("Failed to get robot state during fault handling: %s", exc)
84
+ continue
85
+
86
+ for prereq_service in prereq_services:
105
87
  # Make sure the prereq service exists.
106
88
  try:
107
- registered_service = directory_client.get_entry(service)
89
+ registered_service = directory_client.get_entry(prereq_service)
108
90
  except NonexistentServiceError as exc:
109
91
  set_fault = True
110
- unavailable_services.append(service)
92
+ unavailable_services.append(prereq_service)
93
+ continue
94
+ except bosdyn.client.Error as exc:
95
+ _LOGGER.error("Failed to check if %s exists during fault handling: %s",
96
+ prereq_service, exc)
97
+ set_fault = True
98
+ unavailable_services.append(prereq_service)
111
99
  continue
112
100
 
113
101
  # Make sure the prereq service isn't faulted.
114
- state = robot_state_client.get_robot_state()
115
102
  for fault in state.service_fault_state.faults:
116
- if fault.fault_id.service_name == service:
103
+ if fault.fault_id.service_name == prereq_service:
117
104
  set_fault = True
118
- unavailable_services.append(service)
105
+ unavailable_services.append(prereq_service)
119
106
  break
120
107
 
121
- # Fault the service.
122
- if set_fault:
108
+ # Check if the service is already faulted.
109
+ for fault in state.service_fault_state.faults:
110
+ if fault.fault_id.service_name == service_name:
111
+ fault_exists = True
112
+ break
113
+
114
+ # Fault the service if there isn't an existing fault.
115
+ if set_fault and not fault_exists:
123
116
  service_fault.error_message = 'Faulted due to issues with ' + ','.join(
124
117
  unavailable_services)
125
118
  try:
126
119
  fault_client.trigger_service_fault(service_fault)
120
+ _LOGGER.info("Triggered fault on %s", service_name)
127
121
  except ServiceFaultAlreadyExistsError:
128
122
  pass
129
- except (RpcError, FaultResponseError) as exc:
130
- _LOGGER.error(f"Failed to set {service_name} fault. {exc}")
123
+ except bosdyn.client.Error as exc:
124
+ _LOGGER.error("Failed to set %s fault: %s", service_name, exc)
131
125
 
132
126
  # Otherwise, clear the fault if it exists.
133
- else:
127
+ elif not set_fault and fault_exists:
134
128
  try:
135
129
  fault_client.clear_service_fault(service_fault.fault_id)
136
- set_fault = False
130
+ _LOGGER.info("Cleared fault on %s", service_name)
137
131
  except ServiceFaultDoesNotExistError:
138
132
  pass
139
- except (RpcError, FaultResponseError) as exc:
140
- _LOGGER.error(f"Failed to clear {service_name} fault. {exc}")
133
+ except bosdyn.client.Error as exc:
134
+ _LOGGER.error("Failed to clear %s fault: %s", service_name, exc)
bosdyn/client/auth.py CHANGED
@@ -39,14 +39,6 @@ class TemporarilyLockedOutError(AuthResponseError):
39
39
  """User is temporarily locked out of authentication."""
40
40
 
41
41
 
42
- class ExpiredApplicationTokenError(AuthResponseError):
43
- """Application token has expired. Please contact support@bostondynamics.com to receive a new one."""
44
-
45
-
46
- class InvalidApplicationTokenError(AuthResponseError):
47
- """The Application Token is invalid."""
48
-
49
-
50
42
  _STATUS_TO_ERROR = collections.defaultdict(lambda: (ResponseError, None))
51
43
  _STATUS_TO_ERROR.update({
52
44
  auth_pb2.GetAuthTokenResponse.STATUS_OK: (None, None),
@@ -55,11 +47,7 @@ _STATUS_TO_ERROR.update({
55
47
  auth_pb2.GetAuthTokenResponse.STATUS_INVALID_TOKEN:
56
48
  (InvalidTokenError, InvalidTokenError.__doc__),
57
49
  auth_pb2.GetAuthTokenResponse.STATUS_TEMPORARILY_LOCKED_OUT:
58
- (TemporarilyLockedOutError, TemporarilyLockedOutError.__doc__),
59
- auth_pb2.GetAuthTokenResponse.STATUS_INVALID_APPLICATION_TOKEN:
60
- (InvalidApplicationTokenError, InvalidApplicationTokenError.__doc__),
61
- auth_pb2.GetAuthTokenResponse.STATUS_EXPIRED_APPLICATION_TOKEN:
62
- (ExpiredApplicationTokenError, ExpiredApplicationTokenError.__doc__)
50
+ (TemporarilyLockedOutError, TemporarilyLockedOutError.__doc__)
63
51
  })
64
52
 
65
53
 
@@ -76,13 +64,12 @@ def _token_from_response(response):
76
64
  return response.token
77
65
 
78
66
 
79
- def _build_auth_request(username, password, app_token=None):
80
- return auth_pb2.GetAuthTokenRequest(username=username, password=password,
81
- application_token=app_token)
67
+ def _build_auth_request(username, password):
68
+ return auth_pb2.GetAuthTokenRequest(username=username, password=password)
82
69
 
83
70
 
84
- def _build_auth_token_request(token, app_token=None):
85
- return auth_pb2.GetAuthTokenRequest(token=token, application_token=app_token)
71
+ def _build_auth_token_request(token):
72
+ return auth_pb2.GetAuthTokenRequest(token=token)
86
73
 
87
74
 
88
75
  class AuthClient(BaseClient):
@@ -96,13 +83,12 @@ class AuthClient(BaseClient):
96
83
  def __init__(self, name=None):
97
84
  super(AuthClient, self).__init__(auth_service_pb2_grpc.AuthServiceStub, name=name)
98
85
 
99
- def auth(self, username, password, app_token=None, **kwargs):
86
+ def auth(self, username, password, **kwargs):
100
87
  """Authenticate to the robot with a username/password combo.
101
88
 
102
89
  Args:
103
90
  username: username on the robot.
104
91
  password: password for the username on the robot.
105
- app_token: Deprecated. Only include for robots with old software.
106
92
  kwargs: extra arguments for controlling RPC details.
107
93
 
108
94
  Returns:
@@ -111,25 +97,24 @@ class AuthClient(BaseClient):
111
97
  Raises:
112
98
  InvalidLoginError: If username and/or password are not valid.
113
99
  """
114
- req = _build_auth_request(username, password, app_token)
100
+ req = _build_auth_request(username, password)
115
101
  return self.call(self._stub.GetAuthToken, req, _token_from_response, _error_from_response,
116
102
  copy_request=False, **kwargs)
117
103
 
118
- def auth_async(self, username, password, app_token=None, **kwargs):
104
+ def auth_async(self, username, password, **kwargs):
119
105
  """Asynchronously authenticate to the robot with a username/password combo.
120
106
 
121
107
  See auth documentation for more details.
122
108
  """
123
- req = _build_auth_request(username, password, app_token)
109
+ req = _build_auth_request(username, password)
124
110
  return self.call_async(self._stub.GetAuthToken, req, _token_from_response,
125
111
  _error_from_response, copy_request=False, **kwargs)
126
112
 
127
- def auth_with_token(self, token, app_token=None, **kwargs):
113
+ def auth_with_token(self, token, **kwargs):
128
114
  """Authenticate to the robot using a previously created user token.
129
115
 
130
116
  Args:
131
117
  token: a user token previously issued by the robot.
132
- app_token: Deprecated. Only include for robots with old software.
133
118
  kwargs: extra arguments for controlling RPC details.
134
119
 
135
120
  Returns:
@@ -140,15 +125,15 @@ class AuthClient(BaseClient):
140
125
  Raises:
141
126
  InvalidTokenError: If the token was incorrectly formed, for the wrong robot, or expired.
142
127
  """
143
- req = _build_auth_token_request(token, app_token)
128
+ req = _build_auth_token_request(token)
144
129
  return self.call(self._stub.GetAuthToken, req, _token_from_response, _error_from_response,
145
130
  copy_request=False, **kwargs)
146
131
 
147
- def auth_with_token_async(self, token, app_token=None, **kwargs):
132
+ def auth_with_token_async(self, token, **kwargs):
148
133
  """Authenticate to the robot using a previously created user token.
149
134
 
150
135
  See auth_with_token documentation for more details.
151
136
  """
152
- req = _build_auth_token_request(token, app_token)
137
+ req = _build_auth_token_request(token)
153
138
  return self.call_async(self._stub.GetAuthToken, req, _token_from_response,
154
139
  _error_from_response, copy_request=False, **kwargs)
bosdyn/client/autowalk.py CHANGED
@@ -41,6 +41,7 @@ class AutowalkClient(BaseClient):
41
41
 
42
42
  def update_from(self, other):
43
43
  """Update instance from another object.
44
+
44
45
  Args:
45
46
  other: The object where to copy from.
46
47
  """
@@ -50,6 +51,7 @@ class AutowalkClient(BaseClient):
50
51
 
51
52
  def compile_autowalk(self, walk, data_chunk_byte_size=1000 * 1000, **kwargs):
52
53
  """Send the input walk file to the autowalk service for compilation.
54
+
53
55
  Args:
54
56
  walk: a walks_pb2.Walk input to be compiled by the autowalk service
55
57
  data_chunk_byte_size: max size of each streamed message
@@ -69,6 +71,7 @@ class AutowalkClient(BaseClient):
69
71
  def load_autowalk(self, walk, leases=[], data_chunk_byte_size=1000 * 1000, **kwargs):
70
72
  """Send the input walk file to the autowalk service for compilation and
71
73
  load resulting mission to the Mission Service on the robot.
74
+
72
75
  Args:
73
76
  walk: a walks_pb2.Walk input to be loaded onto the robot by the autowalk service
74
77
  leases: Leases the autowalk service will need to use. Unlike other clients, these MUST
bosdyn/client/channel.py CHANGED
@@ -9,53 +9,47 @@ import warnings
9
9
 
10
10
  import grpc
11
11
 
12
- from .exceptions import (ClientCancelledOperationError, InvalidAppTokenError,
13
- InvalidClientCertificateError, NonexistentAuthorityError, NotFoundError,
14
- PermissionDeniedError, ProxyConnectionError, ResponseTooLargeError,
15
- RetryableUnavailableError, RpcError, ServiceFailedDuringExecutionError,
16
- ServiceUnavailableError, TimedOutError, TooManyRequestsError,
17
- TransientFailureError, UnableToConnectToRobotError, UnauthenticatedError,
18
- UnimplementedError, UnknownDnsNameError)
12
+ from .exceptions import (ClientCancelledOperationError, InvalidClientCertificateError,
13
+ NonexistentAuthorityError, NotFoundError, PermissionDeniedError,
14
+ ProxyConnectionError, ResponseTooLargeError, RetryableUnavailableError,
15
+ RpcError, ServiceFailedDuringExecutionError, ServiceUnavailableError,
16
+ TimedOutError, TooManyRequestsError, TransientFailureError,
17
+ UnableToConnectToRobotError, UnauthenticatedError, UnimplementedError,
18
+ UnknownDnsNameError)
19
19
 
20
20
  TransportError = grpc.RpcError
21
21
 
22
22
  _LOGGER = logging.getLogger(__name__)
23
23
 
24
- # Set default max message length for sending and receiving to 100MB. This value is used when
24
+ # Set default max message length for sending and receiving. This value is used when
25
25
  # creating channels in the bosdyn.client.Robot class.
26
26
  DEFAULT_MAX_MESSAGE_LENGTH = 100 * (1024**2)
27
27
 
28
+ # Period in milliseconds after which a keepalive ping is sent on the transport.
29
+ DEFAULT_KEEP_ALIVE_TIME_MS = 5000
30
+
28
31
 
29
32
  class RefreshingAccessTokenAuthMetadataPlugin(grpc.AuthMetadataPlugin):
30
33
  """Plugin to refresh access token.
31
34
 
32
35
  Args:
33
- token_cb: Callable that returns a tuple of (app_token, user_token)
34
- add_app_token (bool): Deprecated
36
+ token_cb: a callback to provide a valid user token.
35
37
  """
36
38
 
37
- def __init__(self, token_cb, add_app_token=None):
39
+ def __init__(self, token_cb):
38
40
  self._token_cb = token_cb
39
- if add_app_token is not None:
40
- warnings.warn(
41
- 'add_app_token is deprecated for RefreshingAccessTokenAuthMetadataPlugin. '
42
- 'Do not set it', DeprecationWarning)
43
41
 
44
42
  def __call__(self, context, callback):
45
- app_token, user_token = self._token_cb()
46
- user_token_metadata = ('authorization', 'Bearer {}'.format(user_token))
43
+ user_token_metadata = ('authorization', 'Bearer {}'.format(self._token_cb()))
47
44
  metadata = (user_token_metadata,)
48
45
  error = None
49
46
  callback(metadata, error)
50
47
 
51
48
 
52
- def create_secure_channel_creds(cert, token_cb, add_app_token=None):
49
+ def create_secure_channel_creds(cert, token_cb):
53
50
  """Returns credentials for establishing a secure channel.
54
51
  Uses previously set values on the linked Sdk and self.
55
52
  """
56
- if add_app_token is not None:
57
- warnings.warn('add_app_token is deprecated for create_secure_channel_creds. Do not set it.',
58
- DeprecationWarning)
59
53
 
60
54
  transport_creds = grpc.ssl_channel_credentials(root_certificates=cert)
61
55
  plugin = RefreshingAccessTokenAuthMetadataPlugin(token_cb)
@@ -126,8 +120,6 @@ def translate_exception(rpc_error):
126
120
  if code is grpc.StatusCode.CANCELLED:
127
121
  if str(401) in details:
128
122
  return UnauthenticatedError(rpc_error, UnauthenticatedError.__doc__)
129
- elif str(403) in details:
130
- return InvalidAppTokenError(rpc_error, InvalidAppTokenError.__doc__)
131
123
  elif str(404) in details:
132
124
  return NotFoundError(rpc_error, NotFoundError.__doc__)
133
125
  elif str(429) in details:
@@ -183,15 +175,19 @@ def translate_exception(rpc_error):
183
175
  return RpcError(rpc_error, RpcError.__doc__)
184
176
 
185
177
 
186
- def generate_channel_options(max_send_message_length=None, max_receive_message_length=None):
178
+ def generate_channel_options(max_send_message_length=None, max_receive_message_length=None,
179
+ keep_alive_ping_time_ms=None):
187
180
  """Generate the array of options to specify in the creation of a client channel or server.
188
181
 
189
182
  The list contains the values for max allowed message length for both sending and
190
- receiving. If no values are provided, the default values of 100 MB are used.
183
+ receiving. If no values are provided, the default values of 100 MB are used. It also contains a
184
+ value for time between keep_alive pings, which default to 5s.
191
185
 
192
186
  Args:
193
187
  max_send_message_length (int): Max message length allowed for message to send.
194
188
  max_receive_message_length (int): Max message length allowed for message to receive.
189
+ keep_alive_ping_time_ms (int): Period in milliseconds after which a keepalive ping is sent
190
+ on the transport.
195
191
 
196
192
  Returns:
197
193
  Array with values for channel options.
@@ -199,4 +195,5 @@ def generate_channel_options(max_send_message_length=None, max_receive_message_l
199
195
 
200
196
  return [('grpc.max_send_message_length', max_send_message_length or DEFAULT_MAX_MESSAGE_LENGTH),
201
197
  ('grpc.max_receive_message_length', max_receive_message_length or
202
- DEFAULT_MAX_MESSAGE_LENGTH)]
198
+ DEFAULT_MAX_MESSAGE_LENGTH),
199
+ ('grpc.keepalive_time_ms', keep_alive_ping_time_ms or DEFAULT_KEEP_ALIVE_TIME_MS)]