bosdyn-client 5.0.1.2__py3-none-any.whl → 5.1.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 (57) hide show
  1. bosdyn/client/access_controlled_door_util.py +206 -0
  2. bosdyn/client/arm_surface_contact.py +2 -2
  3. bosdyn/client/async_tasks.py +3 -2
  4. bosdyn/client/audio_visual_helpers.py +3 -2
  5. bosdyn/client/autowalk.py +0 -2
  6. bosdyn/client/command_line.py +72 -15
  7. bosdyn/client/common.py +1 -1
  8. bosdyn/client/data_acquisition.py +3 -5
  9. bosdyn/client/data_acquisition_helpers.py +0 -3
  10. bosdyn/client/data_acquisition_plugin.py +1 -2
  11. bosdyn/client/data_acquisition_plugin_service.py +3 -2
  12. bosdyn/client/data_acquisition_store.py +1 -7
  13. bosdyn/client/data_buffer.py +5 -4
  14. bosdyn/client/directory_registration.py +3 -2
  15. bosdyn/client/estop.py +3 -2
  16. bosdyn/client/fault.py +1 -1
  17. bosdyn/client/gps/aggregator_client.py +2 -4
  18. bosdyn/client/gps/gps_listener.py +5 -7
  19. bosdyn/client/gps/ntrip_client.py +12 -3
  20. bosdyn/client/graph_nav.py +67 -13
  21. bosdyn/client/hazard_avoidance.py +119 -0
  22. bosdyn/client/image.py +5 -4
  23. bosdyn/client/image_service_helpers.py +6 -7
  24. bosdyn/client/ir_enable_disable.py +1 -1
  25. bosdyn/client/keepalive.py +4 -2
  26. bosdyn/client/lease.py +3 -2
  27. bosdyn/client/lease_validator.py +0 -1
  28. bosdyn/client/log_status.py +57 -3
  29. bosdyn/client/map_processing.py +2 -4
  30. bosdyn/client/network_compute_bridge_client.py +4 -6
  31. bosdyn/client/payload.py +2 -3
  32. bosdyn/client/payload_registration.py +11 -10
  33. bosdyn/client/power.py +84 -27
  34. bosdyn/client/processors.py +27 -2
  35. bosdyn/client/recording.py +3 -3
  36. bosdyn/client/robot_command.py +22 -22
  37. bosdyn/client/robot_state.py +1 -1
  38. bosdyn/client/sdk.py +2 -3
  39. bosdyn/client/service_customization_helpers.py +1 -1
  40. bosdyn/client/spot_cam/audio.py +1 -2
  41. bosdyn/client/spot_cam/health.py +1 -1
  42. bosdyn/client/spot_cam/lighting.py +1 -1
  43. bosdyn/client/spot_cam/media_log.py +1 -1
  44. bosdyn/client/spot_cam/network.py +3 -2
  45. bosdyn/client/spot_cam/power.py +1 -1
  46. bosdyn/client/spot_cam/ptz.py +1 -1
  47. bosdyn/client/spot_cam/streamquality.py +1 -1
  48. bosdyn/client/spot_cam/version.py +1 -1
  49. bosdyn/client/spot_check.py +5 -6
  50. bosdyn/client/url_validation_util.py +220 -0
  51. bosdyn/client/util.py +2 -4
  52. bosdyn/client/world_object.py +1 -1
  53. {bosdyn_client-5.0.1.2.dist-info → bosdyn_client-5.1.1.dist-info}/METADATA +3 -3
  54. bosdyn_client-5.1.1.dist-info/RECORD +106 -0
  55. bosdyn_client-5.0.1.2.dist-info/RECORD +0 -103
  56. {bosdyn_client-5.0.1.2.dist-info → bosdyn_client-5.1.1.dist-info}/WHEEL +0 -0
  57. {bosdyn_client-5.0.1.2.dist-info → bosdyn_client-5.1.1.dist-info}/top_level.txt +0 -0
@@ -12,11 +12,13 @@ This allows client code to start, extend or terminate experiment logs and start
12
12
  """
13
13
 
14
14
  import collections
15
+ import re
16
+ import time
15
17
 
16
18
  import bosdyn.util
17
19
  from bosdyn.api.log_status import log_status_pb2 as log_status
18
20
  from bosdyn.api.log_status import log_status_service_pb2_grpc as log_status_service
19
- from bosdyn.client.common import (BaseClient, common_header_errors, error_factory, error_pair,
21
+ from bosdyn.client.common import (BaseClient, error_factory, error_pair,
20
22
  handle_common_header_errors, handle_unset_status_error)
21
23
  from bosdyn.client.exceptions import ResponseError
22
24
 
@@ -41,6 +43,10 @@ class ConcurrencyLimitReachedError(LogStatusResponseError):
41
43
  """The limit of concurrent retro logs has be reached, a new log cannot be started."""
42
44
 
43
45
 
46
+ class NoDataForEventError(LogStatusResponseError):
47
+ """No data is available for the provided event, so a log cannot be started."""
48
+
49
+
44
50
  class LogStatusClient(BaseClient):
45
51
  """A client for interacting with robot logs."""
46
52
  # Typical name of the service in the robot's directory listing.
@@ -88,7 +94,7 @@ class LogStatusClient(BaseClient):
88
94
  error_from_response=get_active_log_statuses_error,
89
95
  copy_request=False, **kwargs)
90
96
 
91
- def start_experiment_log(self, seconds, **kwargs):
97
+ def start_experiment_log(self, seconds, past_textlog_duration=0, **kwargs):
92
98
  """Start an experiment log, to run for a specified duration.
93
99
 
94
100
  Args:
@@ -99,14 +105,16 @@ class LogStatusClient(BaseClient):
99
105
  """
100
106
  req = log_status.StartExperimentLogRequest()
101
107
  req.keep_alive.CopyFrom(bosdyn.util.seconds_to_duration(seconds))
108
+ req.past_textlog_duration.CopyFrom(bosdyn.util.seconds_to_duration(past_textlog_duration))
102
109
  return self.call(self._stub.StartExperimentLog, req,
103
110
  error_from_response=start_experiment_log_error, copy_request=False,
104
111
  **kwargs)
105
112
 
106
- def start_experiment_log_async(self, seconds, **kwargs):
113
+ def start_experiment_log_async(self, seconds, past_textlog_duration=0, **kwargs):
107
114
  """Start an experiment log, to run for a specified duration."""
108
115
  req = log_status.StartExperimentLogRequest()
109
116
  req.keep_alive.CopyFrom(bosdyn.util.seconds_to_duration(seconds))
117
+ req.past_textlog_duration.CopyFrom(bosdyn.util.seconds_to_duration(past_textlog_duration))
110
118
  return self.call_async(self._stub.StartExperimentLog, req,
111
119
  error_from_response=start_experiment_log_error, copy_request=False,
112
120
  **kwargs)
@@ -135,6 +143,31 @@ class LogStatusClient(BaseClient):
135
143
  error_from_response=start_retro_log_error, copy_request=False,
136
144
  **kwargs)
137
145
 
146
+ def start_concurrent_log(self, duration_seconds, event=None, **kwargs):
147
+ """Start an experiment log that allows concurrency, to run based on a particular data_set, as derived from the recipe corresponding to the provided event. An event must be provided!"""
148
+ req = log_status.StartConcurrentLogRequest()
149
+ req.keep_alive.CopyFrom(bosdyn.util.seconds_to_duration(duration_seconds))
150
+
151
+ if event:
152
+ req.event.CopyFrom(event)
153
+
154
+ return self.call(self._stub.StartConcurrentLog, req,
155
+ error_from_response=start_concurrent_log_error, copy_request=False,
156
+ **kwargs)
157
+
158
+ def start_concurrent_log_async(self, duration_seconds, data_set_names=None, properties=None,
159
+ event=None, **kwargs):
160
+ """Start an experiment log that allows concurrency, to run based on a particular data_set, as derived from the recipe corresponding to the provided event. An event must be provided!"""
161
+ req = log_status.StartConcurrentLogRequest()
162
+ req.keep_alive.CopyFrom(bosdyn.util.seconds_to_duration(duration_seconds))
163
+
164
+ if event:
165
+ req.event.CopyFrom(event)
166
+
167
+ return self.call_async(self._stub.StartConcurrentLog, req,
168
+ error_from_response=start_concurrent_log_error, copy_request=False,
169
+ **kwargs)
170
+
138
171
  def update_experiment(self, id, seconds, **kwargs):
139
172
  """Update an experiment log to run for a specified duration.
140
173
 
@@ -217,6 +250,18 @@ _START_RETRO_LOG_STATUS_TO_ERROR.update({
217
250
  error_pair(ConcurrencyLimitReachedError),
218
251
  })
219
252
 
253
+ _START_CONCURRENT_LOG_STATUS_TO_ERROR = \
254
+ collections.defaultdict(lambda: (LogStatusResponseError, None))
255
+ _START_CONCURRENT_LOG_STATUS_TO_ERROR.update({
256
+ log_status.StartConcurrentLogResponse.STATUS_OK: (None, None),
257
+ log_status.StartConcurrentLogResponse.STATUS_EXPERIMENT_LOG_RUNNING:
258
+ error_pair(ExperimentAlreadyRunningError),
259
+ log_status.StartConcurrentLogResponse.STATUS_CONCURRENCY_LIMIT_REACHED:
260
+ error_pair(ConcurrencyLimitReachedError),
261
+ log_status.StartConcurrentLogResponse.STATUS_NO_DATA_FOR_EVENT:
262
+ error_pair(NoDataForEventError),
263
+ })
264
+
220
265
  _UPDATE_EXPERIMENT_LOG_STATUS_TO_ERROR = \
221
266
  collections.defaultdict(lambda: (LogStatusResponseError, None))
222
267
  _UPDATE_EXPERIMENT_LOG_STATUS_TO_ERROR.update({
@@ -272,6 +317,15 @@ def start_retro_log_error(response):
272
317
  status_to_error=_START_RETRO_LOG_STATUS_TO_ERROR)
273
318
 
274
319
 
320
+ @handle_common_header_errors
321
+ @handle_unset_status_error(unset='STATUS_UNKNOWN')
322
+ def start_concurrent_log_error(response):
323
+ """Return a custom exception based on the StartConcurrentLog response, None if no error."""
324
+ return error_factory(response, response.status,
325
+ status_to_string=log_status.StartConcurrentLogResponse.Status.Name,
326
+ status_to_error=_START_CONCURRENT_LOG_STATUS_TO_ERROR)
327
+
328
+
275
329
  @handle_common_header_errors
276
330
  @handle_unset_status_error(unset='STATUS_UNKNOWN')
277
331
  def update_experiment_log_error(response):
@@ -6,11 +6,9 @@
6
6
 
7
7
  """For clients of the graph_nav map processing service."""
8
8
 
9
- from bosdyn.api.graph_nav import map_pb2, map_processing_pb2, map_processing_service_pb2
9
+ from bosdyn.api.graph_nav import map_processing_pb2
10
10
  from bosdyn.api.graph_nav import map_processing_service_pb2_grpc as map_processing
11
- from bosdyn.client.common import (BaseClient, common_header_errors, error_factory,
12
- handle_common_header_errors, handle_lease_use_result_errors,
13
- handle_unset_status_error)
11
+ from bosdyn.client.common import BaseClient, handle_common_header_errors, handle_unset_status_error
14
12
  from bosdyn.client.exceptions import ResponseError
15
13
 
16
14
 
@@ -8,12 +8,10 @@
8
8
 
9
9
  import collections
10
10
 
11
- from bosdyn.api import (network_compute_bridge_pb2, network_compute_bridge_service_pb2,
12
- network_compute_bridge_service_pb2_grpc)
13
- from bosdyn.client.common import (BaseClient, error_factory, error_pair,
14
- handle_common_header_errors, handle_custom_params_errors,
15
- handle_lease_use_result_errors, handle_unset_status_error)
16
- from bosdyn.client.exceptions import Error, InternalServerError, ResponseError, UnsetStatusError
11
+ from bosdyn.api import network_compute_bridge_pb2, network_compute_bridge_service_pb2_grpc
12
+ from bosdyn.client.common import (BaseClient, error_pair, handle_common_header_errors,
13
+ handle_custom_params_errors)
14
+ from bosdyn.client.exceptions import ResponseError, UnsetStatusError
17
15
 
18
16
 
19
17
  class ExternalServiceNotFoundError(ResponseError):
bosdyn/client/payload.py CHANGED
@@ -11,7 +11,6 @@ This allows client code to read from the robot payload registry.
11
11
 
12
12
  import logging
13
13
 
14
- import bosdyn.api.payload_pb2 as payload_protos
15
14
  import bosdyn.api.payload_pb2 as payload_service_protos
16
15
  import bosdyn.api.payload_service_pb2_grpc as payload_service
17
16
 
@@ -38,7 +37,7 @@ class PayloadClient(BaseClient):
38
37
 
39
38
  Args:
40
39
  kw_args: Extra arguments to pass to grpc call invocation.
41
-
40
+
42
41
  Returns:
43
42
  A list of the proto message definitions of all registered payloads
44
43
 
@@ -54,7 +53,7 @@ class PayloadClient(BaseClient):
54
53
 
55
54
  Args:
56
55
  kw_args: Extra arguments to pass to grpc call invocation.
57
-
56
+
58
57
  Returns:
59
58
  A list of the proto message definitions of all registered payloads
60
59
 
@@ -19,8 +19,9 @@ import bosdyn.api.payload_registration_service_pb2_grpc as payload_registration_
19
19
  from bosdyn.client import (ResponseError, RetryableUnavailableError, TimedOutError,
20
20
  TooManyRequestsError)
21
21
  from bosdyn.client.common import (BaseClient, error_factory, handle_common_header_errors,
22
- handle_lease_use_result_errors, handle_unset_status_error)
22
+ handle_unset_status_error)
23
23
  from bosdyn.client.error_callback_result import ErrorCallbackResult
24
+ from bosdyn.util import now_sec
24
25
 
25
26
  LOGGER = logging.getLogger('payload_registration_client')
26
27
 
@@ -156,7 +157,7 @@ class PayloadRegistrationClient(BaseClient):
156
157
 
157
158
  def get_payload_auth_token(self, guid, secret, **kw_args):
158
159
  """Request a limited-access auth token for a payload.
159
-
160
+
160
161
  Getting the auth token requires payload to be authorized via the web console.
161
162
 
162
163
  Args:
@@ -166,7 +167,7 @@ class PayloadRegistrationClient(BaseClient):
166
167
 
167
168
  Returns:
168
169
  A limited-access user token for the robot
169
-
170
+
170
171
  Raises:
171
172
  RpcError: Problem communicating with the robot.
172
173
  PayloadNotAuthorizedError: The payload with the provided GUID is
@@ -370,7 +371,7 @@ class PayloadRegistrationKeepAlive(object):
370
371
  the robot if it is ever forgotten. However, payload registrations on Spot are persistent
371
372
  across power cycles and updates, so in most cases there is no need to send a payload
372
373
  registration request after the first successful payload registration. The use of a payload
373
- registration keep alive should only be used when a payload is expected to be regularly
374
+ registration keep alive should only be used when a payload is expected to be regularly
374
375
  reconfigured by forgetting & re-authorizing the payload in the web page.
375
376
 
376
377
  Args:
@@ -413,9 +414,9 @@ class PayloadRegistrationKeepAlive(object):
413
414
 
414
415
  def start(self):
415
416
  """Register and then kick off thread.
416
-
417
+
417
418
  Can not be restarted with this method after a shutdown.
418
-
419
+
419
420
  Raises:
420
421
  RpcError: Problem communicating with the robot.
421
422
  RuntimeError: The thread was attempted to start more than once.
@@ -435,7 +436,7 @@ class PayloadRegistrationKeepAlive(object):
435
436
 
436
437
  def is_alive(self):
437
438
  """Are we still periodically re-registering?
438
-
439
+
439
440
  Returns:
440
441
  A bool stating if still alive
441
442
  """
@@ -449,7 +450,7 @@ class PayloadRegistrationKeepAlive(object):
449
450
 
450
451
  def _periodic_reregister(self):
451
452
  """Handles a removal of the payload from the robot payload page while still connected.
452
-
453
+
453
454
  Raises:
454
455
  RpcError: Problem communicating with the robot.
455
456
  """
@@ -458,7 +459,7 @@ class PayloadRegistrationKeepAlive(object):
458
459
  wait_time = self._registration_interval_secs
459
460
 
460
461
  while not self._end_reregister_signal.wait(wait_time):
461
- exec_start = time.time()
462
+ exec_start = now_sec()
462
463
  action = ErrorCallbackResult.RESUME_NORMAL_OPERATION
463
464
  try:
464
465
  self.pay_reg_client.register_payload(self.payload, self.secret)
@@ -486,7 +487,7 @@ class PayloadRegistrationKeepAlive(object):
486
487
  # Log all other exceptions, but continue looping in hopes that it resolves itself
487
488
  self.logger.exception('Caught general exception.')
488
489
 
489
- exec_sec = time.time() - exec_start
490
+ exec_sec = now_sec() - exec_start
490
491
  if action == ErrorCallbackResult.ABORT:
491
492
  self.logger.warning('Callback directed the re-registration loop to exit.')
492
493
  break
bosdyn/client/power.py CHANGED
@@ -13,13 +13,14 @@ from concurrent.futures import TimeoutError
13
13
  from deprecated.sphinx import deprecated
14
14
  from google.protobuf.duration_pb2 import Duration
15
15
 
16
- from bosdyn.api import (basic_command_pb2, full_body_command_pb2, license_pb2, power_pb2,
17
- power_service_pb2_grpc, robot_command_pb2, robot_state_pb2)
16
+ from bosdyn.api import (basic_command_pb2, full_body_command_pb2, power_pb2, power_service_pb2_grpc,
17
+ robot_command_pb2, robot_state_pb2)
18
18
  from bosdyn.client.common import (BaseClient, common_license_errors, error_factory,
19
19
  handle_common_header_errors, handle_lease_use_result_errors,
20
20
  handle_unset_status_error)
21
21
  from bosdyn.client.exceptions import (Error, InternalServerError, LicenseError, ResponseError,
22
22
  TimedOutError)
23
+ from bosdyn.util import now_sec
23
24
 
24
25
  from .lease import add_lease_wallet_processors
25
26
 
@@ -82,9 +83,10 @@ class SafetyStopUnknownStopTypeError(PowerResponseError):
82
83
 
83
84
  class PowerClient(BaseClient):
84
85
  """A client for enabling / disabling robot motor power.
86
+
85
87
  Commands are non-blocking. Clients are expected to issue a power command and then periodically
86
- check the status of this command.
87
- This service requires ownership over the robot, in the form of a lease.
88
+ check the status of this command. This service requires ownership over the robot, in the form of
89
+ a lease.
88
90
  """
89
91
  default_service_name = 'power'
90
92
  service_type = 'bosdyn.api.PowerService'
@@ -134,7 +136,7 @@ class PowerClient(BaseClient):
134
136
  _fan_power_command_error_from_response, **kwargs)
135
137
 
136
138
  def fan_power_command_feedback(self, command_id, **kwargs):
137
- """Check the status of a previously issued fan command"""
139
+ """Check the status of a previously issued fan command."""
138
140
  req = self._fan_power_command_feedback_request(command_id)
139
141
  return self.call(self._stub.FanPowerCommandFeedback, req, None,
140
142
  _fan_power_feedback_error_from_response, **kwargs)
@@ -190,7 +192,10 @@ def _handle_license_errors(func):
190
192
 
191
193
 
192
194
  def _common_license_errors(response):
193
- """Return an exception based on license status. None if no error."""
195
+ """Return an exception based on license status.
196
+
197
+ None if no error.
198
+ """
194
199
  if response.status != power_pb2.STATUS_LICENSE_ERROR:
195
200
  return None
196
201
 
@@ -284,14 +289,17 @@ _RESET_SAFETY_STOP_STATUS_TO_ERROR.update({
284
289
  @deprecated(reason='Replaced by the less ambiguous safe_power_off_motors function.',
285
290
  version='3.0.0', action="ignore")
286
291
  def safe_power_off(command_client, state_client, timeout_sec=30, update_frequency=1.0, **kwargs):
287
- """Safely power off motors. See safe_power_off_motors()."""
292
+ """Safely power off motors.
293
+
294
+ See safe_power_off_motors().
295
+ """
288
296
  safe_power_off_motors(command_client, state_client, timeout_sec, update_frequency, **kwargs)
289
297
 
290
298
 
291
299
  def safe_power_off_motors(command_client, state_client, timeout_sec=30, update_frequency=1.0,
292
300
  **kwargs):
293
- """Power off robot motors safely. This function blocks until robot safely powers off. This
294
- means the robot will attempt to sit before powering motors off.
301
+ """Power off robot motors safely. This function blocks until robot safely powers off. This means
302
+ the robot will attempt to sit before powering motors off.
295
303
 
296
304
  Args:
297
305
  command_client (RobotCommandClient): client for calling RobotCommandService safe power off.
@@ -305,7 +313,7 @@ def safe_power_off_motors(command_client, state_client, timeout_sec=30, update_f
305
313
  power.CommandTimedOutError: Did not power off within timeout_sec
306
314
  RobotCommandResponseError: Something went wrong with the safe power off.
307
315
  """
308
- start_time = time.time()
316
+ start_time = now_sec()
309
317
  end_time = start_time + timeout_sec
310
318
  update_time = 1.0 / update_frequency
311
319
 
@@ -314,9 +322,9 @@ def safe_power_off_motors(command_client, state_client, timeout_sec=30, update_f
314
322
  command = robot_command_pb2.RobotCommand(full_body_command=full_body_command)
315
323
  command_client.robot_command(command=command, **kwargs)
316
324
 
317
- while time.time() < end_time:
318
- time_until_timeout = end_time - time.time()
319
- start_call_time = time.time()
325
+ while now_sec() < end_time:
326
+ time_until_timeout = end_time - now_sec()
327
+ start_call_time = now_sec()
320
328
  future = state_client.get_robot_state_async(**kwargs)
321
329
  try:
322
330
  response = future.result(timeout=time_until_timeout)
@@ -324,7 +332,7 @@ def safe_power_off_motors(command_client, state_client, timeout_sec=30, update_f
324
332
  return
325
333
  except TimeoutError:
326
334
  raise CommandTimedOutError
327
- call_time = time.time() - start_call_time
335
+ call_time = now_sec() - start_call_time
328
336
  sleep_time = max(0.0, update_time - call_time)
329
337
  time.sleep(sleep_time)
330
338
  raise CommandTimedOutError
@@ -333,14 +341,20 @@ def safe_power_off_motors(command_client, state_client, timeout_sec=30, update_f
333
341
  @deprecated(reason='Replaced by the less ambiguous power_on_motors function.', version='2.3.4',
334
342
  action="ignore")
335
343
  def power_on(power_client, timeout_sec=30, update_frequency=1.0, **kwargs):
336
- """Power on robot motors. See power_on_motors()."""
344
+ """Power on robot motors.
345
+
346
+ See power_on_motors().
347
+ """
337
348
  power_on_motors(power_client, timeout_sec, update_frequency, **kwargs)
338
349
 
339
350
 
340
351
  @deprecated(reason='Replaced by the less ambiguous power_off_motors function.', version='2.3.4',
341
352
  action="ignore")
342
353
  def power_off(power_client, timeout_sec=30, update_frequency=1.0, **kwargs):
343
- """Power off the robot motors. See power_off_motors()."""
354
+ """Power off the robot motors.
355
+
356
+ See power_off_motors().
357
+ """
344
358
  power_off_motors(power_client, timeout_sec, update_frequency, **kwargs)
345
359
 
346
360
 
@@ -399,10 +413,10 @@ def safe_power_off_robot(command_client, state_client, power_client, timeout_sec
399
413
  power.CommandTimedOutError: Did not power off within timeout_sec
400
414
  RobotCommandResponseError: Something went wrong with the safe power off.
401
415
  """
402
- end_time = time.time() + timeout_sec
403
- safe_power_off_motors(command_client, state_client, timeout_sec=end_time - time.time(),
416
+ end_time = now_sec() + timeout_sec
417
+ safe_power_off_motors(command_client, state_client, timeout_sec=end_time - now_sec(),
404
418
  update_frequency=update_frequency, **kwargs)
405
- power_off_robot(power_client, timeout_sec=end_time - time.time(),
419
+ power_off_robot(power_client, timeout_sec=end_time - now_sec(),
406
420
  update_frequency=update_frequency, **kwargs)
407
421
 
408
422
 
@@ -442,10 +456,10 @@ def safe_power_cycle_robot(command_client, state_client, power_client, timeout_s
442
456
  power.CommandTimedOutError: Did not power off within timeout_sec
443
457
  RobotCommandResponseError: Something went wrong with the safe power off.
444
458
  """
445
- end_time = time.time() + timeout_sec
446
- safe_power_off_motors(command_client, state_client, timeout_sec=end_time - time.time(),
459
+ end_time = now_sec() + timeout_sec
460
+ safe_power_off_motors(command_client, state_client, timeout_sec=end_time - now_sec(),
447
461
  update_frequency=update_frequency, **kwargs)
448
- power_cycle_robot(power_client, timeout_sec=end_time - time.time(),
462
+ power_cycle_robot(power_client, timeout_sec=end_time - now_sec(),
449
463
  update_frequency=update_frequency, **kwargs)
450
464
 
451
465
 
@@ -467,6 +481,49 @@ def power_cycle_robot(power_client, timeout_sec=30, update_frequency=1.0, **kwar
467
481
  **kwargs)
468
482
 
469
483
 
484
+ def safe_soft_reboot_robot(command_client, state_client, power_client, timeout_sec=30,
485
+ update_frequency=1.0, **kwargs):
486
+ """Soft reboot the robot safely. This function blocks until robot safely powers off. The robot
487
+ will attempt to sit before soft rebooting.
488
+
489
+ Args:
490
+ command_client (RobotCommandClient): client for calling RobotCommandService safe power off.
491
+ state_client (RobotStateClient): client for monitoring power state.
492
+ power_client (bosdyn.api.PowerClient): client for calling power service.
493
+ timeout_sec (float): Max time this function will block for.
494
+ update_frequency (float): The frequency with which the robot should check if the command
495
+ has succeeded.
496
+
497
+ Raises:
498
+ RpcError: Problem communicating with the robot.
499
+ power.CommandTimedOutError: Did not power off within timeout_sec
500
+ RobotCommandResponseError: Something went wrong with the safe power off.
501
+ """
502
+ end_time = now_sec() + timeout_sec
503
+ safe_power_off_motors(command_client, state_client, timeout_sec=end_time - now_sec(),
504
+ update_frequency=update_frequency, **kwargs)
505
+ soft_reboot_robot(power_client, timeout_sec=end_time - now_sec(),
506
+ update_frequency=update_frequency, **kwargs)
507
+
508
+
509
+ def soft_reboot_robot(power_client, timeout_sec=30, update_frequency=1.0, **kwargs):
510
+ """Soft reboot the robot. Rebooting the robot will stop API comms.
511
+
512
+ Args:
513
+ power_client (bosdyn.api.PowerClient): client for calling power service.
514
+ timeout_sec (float): Max time this function will block for.
515
+ update_frequency (float): The frequency with which the robot should check if the command
516
+ has succeeded.
517
+ Raises:
518
+ RpcError: Problem communicating with the robot.
519
+ power.CommandTimedOutError: Did not power off within timeout_sec
520
+ PowerResponseError: Something went wrong during the power off sequence.
521
+ """
522
+ request = power_pb2.PowerCommandRequest.REQUEST_SOFT_REBOOT_ROBOT
523
+ _power_command(power_client, request, timeout_sec, update_frequency, expect_grpc_timeout=True,
524
+ **kwargs)
525
+
526
+
470
527
  def power_off_payload_ports(power_client, timeout_sec=30, update_frequency=1.0, **kwargs):
471
528
  """Power off the robot payload ports.
472
529
 
@@ -551,7 +608,7 @@ def _power_command(power_client, request, timeout_sec=30, update_frequency=1.0,
551
608
  has succeeded.
552
609
  expect_timeout (bool): Expect API comms to drop on a success.
553
610
  """
554
- start_time = time.time()
611
+ start_time = now_sec()
555
612
  end_time = start_time + timeout_sec
556
613
  update_time = 1.0 / update_frequency
557
614
 
@@ -566,9 +623,9 @@ def _power_command(power_client, request, timeout_sec=30, update_frequency=1.0,
566
623
  return # Command succeeded immediately.
567
624
 
568
625
  power_command_id = response.power_command_id
569
- while time.time() < end_time:
570
- time_until_timeout = end_time - time.time()
571
- start_call_time = time.time()
626
+ while now_sec() < end_time:
627
+ time_until_timeout = end_time - now_sec()
628
+ start_call_time = now_sec()
572
629
  future = power_client.power_command_feedback_async(power_command_id, **kwargs)
573
630
  try:
574
631
  response = future.result(timeout=time_until_timeout)
@@ -585,7 +642,7 @@ def _power_command(power_client, request, timeout_sec=30, update_frequency=1.0,
585
642
  raise
586
643
  except TimeoutError:
587
644
  raise CommandTimedOutError
588
- call_time = time.time() - start_call_time
645
+ call_time = now_sec() - start_call_time
589
646
  sleep_time = max(0.0, update_time - call_time)
590
647
  time.sleep(sleep_time)
591
648
  raise CommandTimedOutError
@@ -25,11 +25,36 @@ class AddRequestHeader(object):
25
25
 
26
26
  def mutate(self, request):
27
27
  """Mutate request such that its header contains a client name and a timestamp.
28
-
28
+
29
29
  Headers are not required for third party proto requests/responses.
30
30
  """
31
31
  header = self._create_header()
32
32
  try:
33
33
  request.header.CopyFrom(header)
34
34
  except AttributeError:
35
- pass
35
+ pass
36
+
37
+
38
+ class DataBufferLoggingProcessor:
39
+ """Processor that logs every protobuf message to the robot's data buffer."""
40
+
41
+ def __init__(self, data_buffer_client):
42
+ """
43
+ Args:
44
+ data_buffer_client: Instance of DataBufferClient.
45
+ """
46
+ self.data_buffer_client = data_buffer_client
47
+
48
+ def mutate(self, proto, **kwargs):
49
+ """Logs the protobuf message to the data buffer.
50
+ Args:
51
+ proto: The protobuf request or response to log.
52
+ """
53
+ self.data_buffer_client.add_protobuf_async(proto)
54
+
55
+
56
+ def log_all_rpcs(client, data_buffer_client):
57
+ """Attach a DataBufferLoggingProcessor to log all RPC requests and responses for the given client."""
58
+ processor = DataBufferLoggingProcessor(data_buffer_client)
59
+ client.request_processors.append(processor)
60
+ client.response_processors.append(processor)
@@ -9,11 +9,11 @@
9
9
  import collections
10
10
  from enum import Enum
11
11
 
12
- from bosdyn.api.graph_nav import map_pb2, nav_pb2, recording_pb2, recording_service_pb2
12
+ from bosdyn.api.graph_nav import map_pb2, recording_pb2
13
13
  from bosdyn.api.graph_nav import recording_service_pb2_grpc as recording_service
14
14
  from bosdyn.client.common import (BaseClient, common_header_errors, error_factory,
15
- handle_common_header_errors, handle_lease_use_result_errors,
16
- handle_license_errors_if_present, handle_unset_status_error)
15
+ handle_common_header_errors, handle_license_errors_if_present,
16
+ handle_unset_status_error)
17
17
  from bosdyn.client.exceptions import ResponseError
18
18
 
19
19