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.
- bosdyn/client/access_controlled_door_util.py +206 -0
- bosdyn/client/arm_surface_contact.py +2 -2
- bosdyn/client/async_tasks.py +3 -2
- bosdyn/client/audio_visual_helpers.py +3 -2
- bosdyn/client/autowalk.py +0 -2
- bosdyn/client/command_line.py +72 -15
- bosdyn/client/common.py +1 -1
- bosdyn/client/data_acquisition.py +3 -5
- bosdyn/client/data_acquisition_helpers.py +0 -3
- bosdyn/client/data_acquisition_plugin.py +1 -2
- bosdyn/client/data_acquisition_plugin_service.py +3 -2
- bosdyn/client/data_acquisition_store.py +1 -7
- bosdyn/client/data_buffer.py +5 -4
- bosdyn/client/directory_registration.py +3 -2
- bosdyn/client/estop.py +3 -2
- bosdyn/client/fault.py +1 -1
- bosdyn/client/gps/aggregator_client.py +2 -4
- bosdyn/client/gps/gps_listener.py +5 -7
- bosdyn/client/gps/ntrip_client.py +12 -3
- bosdyn/client/graph_nav.py +67 -13
- bosdyn/client/hazard_avoidance.py +119 -0
- bosdyn/client/image.py +5 -4
- bosdyn/client/image_service_helpers.py +6 -7
- bosdyn/client/ir_enable_disable.py +1 -1
- bosdyn/client/keepalive.py +4 -2
- bosdyn/client/lease.py +3 -2
- bosdyn/client/lease_validator.py +0 -1
- bosdyn/client/log_status.py +57 -3
- bosdyn/client/map_processing.py +2 -4
- bosdyn/client/network_compute_bridge_client.py +4 -6
- bosdyn/client/payload.py +2 -3
- bosdyn/client/payload_registration.py +11 -10
- bosdyn/client/power.py +84 -27
- bosdyn/client/processors.py +27 -2
- bosdyn/client/recording.py +3 -3
- bosdyn/client/robot_command.py +22 -22
- bosdyn/client/robot_state.py +1 -1
- bosdyn/client/sdk.py +2 -3
- bosdyn/client/service_customization_helpers.py +1 -1
- bosdyn/client/spot_cam/audio.py +1 -2
- bosdyn/client/spot_cam/health.py +1 -1
- bosdyn/client/spot_cam/lighting.py +1 -1
- bosdyn/client/spot_cam/media_log.py +1 -1
- bosdyn/client/spot_cam/network.py +3 -2
- bosdyn/client/spot_cam/power.py +1 -1
- bosdyn/client/spot_cam/ptz.py +1 -1
- bosdyn/client/spot_cam/streamquality.py +1 -1
- bosdyn/client/spot_cam/version.py +1 -1
- bosdyn/client/spot_check.py +5 -6
- bosdyn/client/url_validation_util.py +220 -0
- bosdyn/client/util.py +2 -4
- bosdyn/client/world_object.py +1 -1
- {bosdyn_client-5.0.1.2.dist-info → bosdyn_client-5.1.1.dist-info}/METADATA +3 -3
- bosdyn_client-5.1.1.dist-info/RECORD +106 -0
- bosdyn_client-5.0.1.2.dist-info/RECORD +0 -103
- {bosdyn_client-5.0.1.2.dist-info → bosdyn_client-5.1.1.dist-info}/WHEEL +0 -0
- {bosdyn_client-5.0.1.2.dist-info → bosdyn_client-5.1.1.dist-info}/top_level.txt +0 -0
bosdyn/client/log_status.py
CHANGED
|
@@ -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,
|
|
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):
|
bosdyn/client/map_processing.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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,
|
|
17
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
|
318
|
-
time_until_timeout = end_time -
|
|
319
|
-
start_call_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 =
|
|
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.
|
|
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.
|
|
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 =
|
|
403
|
-
safe_power_off_motors(command_client, state_client, timeout_sec=end_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 -
|
|
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 =
|
|
446
|
-
safe_power_off_motors(command_client, state_client, timeout_sec=end_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 -
|
|
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 =
|
|
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
|
|
570
|
-
time_until_timeout = end_time -
|
|
571
|
-
start_call_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 =
|
|
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
|
bosdyn/client/processors.py
CHANGED
|
@@ -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)
|
bosdyn/client/recording.py
CHANGED
|
@@ -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,
|
|
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,
|
|
16
|
-
|
|
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
|
|