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
@@ -0,0 +1,206 @@
1
+ # Copyright (c) 2023 Boston Dynamics, Inc. All rights reserved.
2
+ #
3
+ # Downloading, reproducing, distributing or otherwise using the SDK Software
4
+ # is subject to the terms and conditions of the Boston Dynamics Software
5
+ # Development Kit License (20191101-BDSDK-SL).
6
+
7
+ import json
8
+ from pathlib import Path
9
+ from string import Template
10
+
11
+ import requests
12
+
13
+ from bosdyn.client.url_validation_util import safe_api_call
14
+
15
+ API_TIMEOUT_DEFAULT = 30
16
+
17
+
18
+ def file_to_json(file_path):
19
+ """
20
+ Helper to read and parse JSON files.
21
+ Args:
22
+ file_path (Path): Path to JSON file.
23
+ Returns:
24
+ dict: Parsed JSON data.
25
+ """
26
+ return json.loads(file_path.read_text())
27
+
28
+
29
+
30
+
31
+ # Functions to handle api calls to access control systems
32
+ def door_action(api_calls, door_id, action, path_to_cert=None, is_robot=True):
33
+ """Executes a sequence of API calls required to perform an action (e.g., open or close) on a specified door,
34
+ handling data substitutions and certificate verification as needed.
35
+
36
+ api_calls (list of dict): List of API call specifications, where each dict contains information
37
+ such as 'method', 'url', 'action', 'sni_hostname', 'route', 'request_data', and 'responses'.
38
+ door_id (str): Identifier of the door to perform the action on.
39
+ action (list of str): The action(s) to perform (e.g., "open", "close"). Only calls matching
40
+ the specified action(s) will be executed.
41
+ path_to_cert (str, optional): Path to a certificate file for SSL verification. If None, no certificate
42
+ is used.
43
+ is_robot (bool, optional): Indicates if the API calls are being made on behalf of a robot. Defaults to True.
44
+
45
+ Returns:
46
+ dict: An error message dictionary containing details about any error encountered during the API calls.
47
+ If all calls succeed, returns an empty dictionary.
48
+ """
49
+ error_msg = {}
50
+ cross_call_substitutions = {"door_id": door_id}
51
+
52
+ for call_data in api_calls:
53
+
54
+ if call_data.get("action") not in action:
55
+ continue
56
+
57
+ method = call_data.get("method")
58
+ url = call_data.get("url")
59
+ sni_hostname = call_data.get("sni_hostname")
60
+ route = call_data.get("route")
61
+ request_data = call_data.get("request_data", {})
62
+ responses = call_data.get("responses", {})
63
+ call_action = call_data.get("action", "")
64
+
65
+ if not method or not url:
66
+ error_msg["extra_message"] = "API call error: missing method or url"
67
+ break
68
+
69
+ # Format URL and data - note: substitutions are case-INsensitive
70
+ try:
71
+ # This will only replace instances of values formatted like $variable or ${variable}
72
+ url = Template(url).safe_substitute(cross_call_substitutions)
73
+
74
+ templated_request_data = {}
75
+ for k, v in request_data.items():
76
+ if isinstance(v, dict): # Handle nested dicts, like headers
77
+ templated_request_data[k] = {
78
+ k2:
79
+ Template(v2).safe_substitute(cross_call_substitutions) if isinstance(
80
+ v2, str) else v2 for k2, v2 in v.items()
81
+ }
82
+ elif isinstance(v, str): # Handle top level string values
83
+ templated_request_data[k] = Template(v).safe_substitute(
84
+ cross_call_substitutions)
85
+ else:
86
+ templated_request_data[k] = v
87
+ # Check if cert file was included at the configuration, if yes use it
88
+ if path_to_cert:
89
+ templated_request_data["verify"] = path_to_cert
90
+ except Exception as e:
91
+ print(f"{e} has no value, couldn't make substitution")
92
+
93
+ result_of_call, api_error_msg = make_access_control_system_api_call(
94
+ method, url, templated_request_data, responses, sni_hostname=sni_hostname,
95
+ is_robot=is_robot, route=route)
96
+ if api_error_msg:
97
+ error_msg["action"] = call_action
98
+ error_msg["api_error"] = api_error_msg
99
+ break
100
+
101
+ # If the call returned data, store it for subsequent calls
102
+ if result_of_call:
103
+ try:
104
+ for result in result_of_call:
105
+ tag = result[0]
106
+ content = result[1]
107
+ cross_call_substitutions[tag] = content
108
+ except Exception as e:
109
+ print(f"API call error: couldn't read data, got exception {e}")
110
+ error_msg["extra_message"] = f"API call error: couldn't read data, got exception"
111
+ break
112
+ return error_msg
113
+
114
+
115
+
116
+
117
+ def make_access_control_system_api_call(method, url, request_data, store_responses=None,
118
+ sni_hostname=None, is_robot=True, route=None):
119
+ """Makes an HTTP request and optionally extracts specific fields from the JSON response.
120
+
121
+ Args:
122
+ method (str): HTTP method to use (e.g., 'GET', 'POST')
123
+ url (str): The endpoint URL for the API call
124
+ request_data (dict): Request configuration including headers, body, etc.
125
+ store_responses (Optional[Dict[str, str]]): Dictionary mapping response field names to
126
+ JSON paths. For example:
127
+ {
128
+ "token": "auth.token", # Store response's auth.token as "token"
129
+ "session_id": "data.session" # Store response's data.session as "session_id"
130
+ }
131
+ If None, no data will be extracted from the response.
132
+ sni_hostname (str|None): If specified, this parameter provides the hostname declared by
133
+ and expected by the access control server during TLS negotiation. This should only be
134
+ required if the server's hostname is not resolvable via DNS.
135
+ route (str|None): Route type to use ("WIFI", "LTE").
136
+ If None, default interface (WIFI) will be used.
137
+
138
+ Returns:
139
+ Tuple[Optional[List[Tuple[str, Any]]], Optional[Dict]]:
140
+ - First element: If store_responses was provided and matching data was found,
141
+ returns list of tuples [(field_name, value), ...]. None otherwise.
142
+ - Second element: If error occurred, returns dict with error details:
143
+ {
144
+ 'status_code': int,
145
+ 'reason': str,
146
+ 'elapsed': float
147
+ }
148
+ None if successful.
149
+
150
+ Example:
151
+ >>> store_responses = {"auth_token": "data.token"}
152
+ >>> data, error = make_access_control_system_api_call("POST", "https://api.door/auth",
153
+ ... {"json": {"key": "value"}},
154
+ ... store_responses)
155
+ >>> if data:
156
+ ... # data might be [("auth_token", "abc123")]
157
+ ... token = dict(data)["auth_token"]
158
+ """
159
+ try:
160
+ response, status_message = safe_api_call(method, url, sni_hostname,
161
+ timeout=API_TIMEOUT_DEFAULT, is_robot=is_robot,
162
+ interface=route, **request_data)
163
+
164
+ if response is None:
165
+ # There was an error during the API call
166
+ return None, status_message
167
+
168
+ if response.status_code == 200:
169
+ if store_responses:
170
+ # Only try to parse JSON and extract data if we need to store responses
171
+ data_json = response.json()
172
+ return [(tag, get_value_by_path(data_json, path))
173
+ for tag, path in store_responses.items()
174
+ if get_value_by_path(data_json, path) is not None], None
175
+ # If we don't need to store responses, just return success
176
+ return None, None
177
+
178
+ # If the status code isn't 200, something went wrong
179
+ return None, {
180
+ 'status_code': response.status_code,
181
+ 'reason': response.reason,
182
+ 'elapsed': response.elapsed.total_seconds(),
183
+ }
184
+
185
+ except requests.exceptions.RequestException as e:
186
+ print(f"API call failed: {str(e)}")
187
+ return None, {
188
+ 'status_code': getattr(e.response, 'status_code', None),
189
+ 'reason': getattr(e.response, 'reason', str(e)),
190
+ 'elapsed': getattr(e.response, 'elapsed', None)
191
+ }
192
+
193
+
194
+ def get_value_by_path(data_json, path_to_info):
195
+ """Takes in a json representing the data of a call response, and a string representing the
196
+ path through the json to the desired data. Traverses the json and returns the data.
197
+ """
198
+ keys = path_to_info.split('.')
199
+ result = data_json
200
+
201
+ for key in keys:
202
+ result = result.get(key, None)
203
+ if result is not None:
204
+ return result
205
+ else:
206
+ return False
@@ -4,8 +4,8 @@
4
4
  # is subject to the terms and conditions of the Boston Dynamics Software
5
5
  # Development Kit License (20191101-BDSDK-SL).
6
6
 
7
- from bosdyn.api import arm_surface_contact_pb2, arm_surface_contact_service_pb2_grpc
8
- from bosdyn.client.common import BaseClient, common_header_errors
7
+ from bosdyn.api import arm_surface_contact_service_pb2_grpc
8
+ from bosdyn.client.common import BaseClient
9
9
  from bosdyn.client.robot_command import NoTimeSyncError, _edit_proto, _TimeConverter
10
10
 
11
11
  from .lease import add_lease_wallet_processors
@@ -6,7 +6,8 @@
6
6
 
7
7
  """Utilities for managing periodic tasks consisting of asynchronous GRPC calls."""
8
8
  import abc
9
- import time
9
+
10
+ from bosdyn.util import now_sec
10
11
 
11
12
  from .exceptions import ResponseError, RpcError
12
13
 
@@ -79,7 +80,7 @@ class AsyncGRPCTask(object, metaclass=abc.ABCMeta):
79
80
 
80
81
  def update(self):
81
82
  """Call this periodically to manage execution of task represented by this object."""
82
- now_sec = time.time()
83
+ now_sec = now_sec()
83
84
  if self._future is not None:
84
85
  if self._future.original_future.done():
85
86
  try:
@@ -13,6 +13,7 @@ import bosdyn.client
13
13
  from bosdyn.api import audio_visual_pb2
14
14
  from bosdyn.client.audio_visual import (AudioVisualClient, BehaviorExpiredError, DoesNotExistError,
15
15
  InvalidClientError)
16
+ from bosdyn.util import now_sec
16
17
 
17
18
  _LOGGER = logging.getLogger(__name__)
18
19
 
@@ -84,7 +85,7 @@ class AudioVisualHelper:
84
85
  # Run the AV behavior until the stop_event is triggered
85
86
  while not self.stop_event.wait(self.refresh_rate):
86
87
  try:
87
- end_time_secs = time.time() + self.refresh_rate + 0.10 # add 100ms margin
88
+ end_time_secs = now_sec() + self.refresh_rate + 0.10 # add 100ms margin
88
89
  result = self.av_client.run_behavior(self.behavior_name, end_time_secs)
89
90
  set_future_result(
90
91
  result.run_result == audio_visual_pb2.RunBehaviorResponse.RESULT_BEHAVIOR_RUN)
@@ -109,4 +110,4 @@ class AudioVisualHelper:
109
110
  try:
110
111
  self.av_client.stop_behavior(self.behavior_name)
111
112
  except InvalidClientError:
112
- _LOGGER.warning('Failed to stop behavior, run by a different client.')
113
+ _LOGGER.warning('Failed to stop behavior, run by a different client.')
bosdyn/client/autowalk.py CHANGED
@@ -14,8 +14,6 @@ from bosdyn.client.common import BaseClient, error_factory, error_pair
14
14
  from bosdyn.client.exceptions import ResponseError
15
15
  from bosdyn.client.lease import add_lease_wallet_processors
16
16
 
17
- from .data_chunk import chunk_message
18
-
19
17
 
20
18
  class AutowalkResponseError(ResponseError):
21
19
  """General class of errors for autowalk service."""
@@ -12,6 +12,7 @@ from __future__ import division
12
12
 
13
13
  import abc
14
14
  import argparse
15
+ import copy
15
16
  import datetime
16
17
  import os
17
18
  import signal
@@ -19,17 +20,19 @@ import socket
19
20
  import sys
20
21
  import threading
21
22
  import time
23
+ import uuid
22
24
 
23
- from deprecated.sphinx import deprecated
25
+ import google.protobuf.timestamp_pb2
24
26
  from google.protobuf import json_format
25
27
 
26
28
  import bosdyn.client
27
29
  from bosdyn.api import data_acquisition_pb2, image_pb2
28
30
  from bosdyn.api.data_buffer_pb2 import Event, TextMessage
29
31
  from bosdyn.api.data_index_pb2 import EventsCommentsSpec
32
+ from bosdyn.api.header_pb2 import CommonError
30
33
  from bosdyn.api.keepalive import keepalive_pb2
31
34
  from bosdyn.api.robot_state_pb2 import BehaviorFault
32
- from bosdyn.util import duration_str, timestamp_to_datetime
35
+ from bosdyn.util import duration_str, now_sec, timestamp_to_datetime
33
36
 
34
37
  from .auth import InvalidLoginError, InvalidTokenError
35
38
  from .data_acquisition import DataAcquisitionClient
@@ -694,14 +697,14 @@ class FaultWatchCommand(Command):
694
697
 
695
698
 
696
699
  class LogStatusCommands(Subcommands):
697
- """Start, update and terminate experiment logs, start and terminate retro logs and check status of
698
- active logs for robot."""
700
+ """Start, update and terminate experiment logs, start and terminate retro logs and check status
701
+ of active logs for robot."""
699
702
 
700
703
  NAME = 'log-status'
701
704
  NEED_AUTHENTICATION = True
702
705
 
703
706
  def __init__(self, subparsers, command_dict):
704
- """Interact with logs for robot
707
+ """Interact with logs for robot.
705
708
 
706
709
  Args:
707
710
  subparsers: List of argument parsers.
@@ -715,6 +718,7 @@ class LogStatusCommands(Subcommands):
715
718
  GetActiveLogStatusesCommand,
716
719
  ExperimentLogCommand,
717
720
  StartRetroLogCommand,
721
+ StartConcurrentLogCommand,
718
722
  TerminateLogCommand,
719
723
  ])
720
724
 
@@ -725,7 +729,7 @@ class GetLogCommand(Command):
725
729
  NAME = 'get'
726
730
 
727
731
  def __init__(self, subparsers, command_dict):
728
- """Get log status from robot
732
+ """Get log status from robot.
729
733
 
730
734
  Args:
731
735
  subparsers: List of argument parsers.
@@ -805,7 +809,7 @@ class StartTimedExperimentLogCommand(Command):
805
809
  NAME = 'timed'
806
810
 
807
811
  def __init__(self, subparsers, command_dict):
808
- """Start timed experiment log
812
+ """Start timed experiment log.
809
813
 
810
814
  Args:
811
815
  subparsers: List of argument parsers.
@@ -837,7 +841,8 @@ class StartContinuousExperimentLogCommand(Command):
837
841
  NAME = 'continuous'
838
842
 
839
843
  def __init__(self, subparsers, command_dict):
840
- """Start continuous experiment log, defaulted to update keep alive time by 10 seconds every 5 seconds.
844
+ """Start continuous experiment log, defaulted to update keep alive time by 10 seconds every
845
+ 5 seconds.
841
846
 
842
847
  Args:
843
848
  subparsers: List of argument parsers.
@@ -894,7 +899,7 @@ class StartRetroLogCommand(Command):
894
899
  NAME = 'retro'
895
900
 
896
901
  def __init__(self, subparsers, command_dict):
897
- """Start a retro log
902
+ """Start a retro log.
898
903
 
899
904
  Args:
900
905
  subparsers: List of argument parsers.
@@ -920,13 +925,61 @@ class StartRetroLogCommand(Command):
920
925
  return True
921
926
 
922
927
 
928
+ class StartConcurrentLogCommand(Command):
929
+ """Start a concurrent experiment log, with event-derived data."""
930
+
931
+ NAME = 'concurrent'
932
+
933
+ def __init__(self, subparsers, command_dict):
934
+ """Start data log
935
+ Args:
936
+ subparsers: List of argument parsers.
937
+ command_dict: Dictionary of command names which take parsed options.
938
+ """
939
+ super(StartConcurrentLogCommand, self).__init__(subparsers, command_dict)
940
+ self._parser.add_argument('seconds', type=float, help='how long should the experiment run?')
941
+ self._parser.add_argument('event_type',
942
+ help='name of the event type we want to match a recipe against')
943
+
944
+ def _run(self, robot, options):
945
+ """Implementation of the command.
946
+
947
+ Args:
948
+ robot: Robot object on which to run the command.
949
+ options: Parsed command-line arguments.
950
+ Returns:
951
+ True
952
+ """
953
+
954
+ client = robot.ensure_client(LogStatusClient.default_service_name)
955
+ time_sync_endpoint = TimeSyncEndpoint(
956
+ robot.ensure_client(TimeSyncClient.default_service_name))
957
+ if not time_sync_endpoint.establish_timesync():
958
+ print("Failed to establish time sync with the robot.")
959
+ return False
960
+
961
+ robot_now = time_sync_endpoint.robot_timestamp_from_local_secs(now_sec())
962
+
963
+ event = Event()
964
+ event.type = options.event_type
965
+ event.description = 'Triggering a recipe data log'
966
+ event.source = 'LogStatus CLI'
967
+ event.id = uuid.uuid4().hex
968
+ event.start_time.CopyFrom(robot_now)
969
+ event.end_time.CopyFrom(robot_now)
970
+
971
+ response = client.start_concurrent_log(options.seconds, event)
972
+ print(response.log_status)
973
+ return True
974
+
975
+
923
976
  class TerminateLogCommand(Command):
924
977
  """Terminate log gathering process."""
925
978
 
926
979
  NAME = 'terminate'
927
980
 
928
981
  def __init__(self, subparsers, command_dict):
929
- """Terminate log on robot
982
+ """Terminate log on robot.
930
983
 
931
984
  Args:
932
985
  subparsers: List of argument parsers.
@@ -1054,7 +1107,7 @@ class TextMsgCommand(Command):
1054
1107
  if options.timestamp:
1055
1108
  try:
1056
1109
  robot_timestamp = robot.time_sync.robot_timestamp_from_local_secs(
1057
- time.time(), timesync_timeout_sec=1.0)
1110
+ now_sec(), timesync_timeout_sec=1.0)
1058
1111
  except TimeSyncError as err:
1059
1112
  print("Failed to send message with timestamp: {}.".format(err))
1060
1113
  return False
@@ -1107,7 +1160,7 @@ class OperatorCommentCommand(Command):
1107
1160
  """
1108
1161
  client_timestamp = None
1109
1162
  if options.timestamp:
1110
- client_timestamp = time.time()
1163
+ client_timestamp = now_sec()
1111
1164
  try:
1112
1165
  robot.time_sync.wait_for_sync(timeout_sec=1.0)
1113
1166
  except TimeSyncError as err:
@@ -1123,7 +1176,7 @@ class DataServiceCommands(Subcommands):
1123
1176
  NAME = 'data'
1124
1177
 
1125
1178
  def __init__(self, subparsers, command_dict):
1126
- """Commands for querying the data-service
1179
+ """Commands for querying the data-service.
1127
1180
 
1128
1181
  Args:
1129
1182
  subparsers: List of argument parsers.
@@ -1295,7 +1348,7 @@ class GetDataBufferStatusCommand(Command):
1295
1348
  NAME = 'status'
1296
1349
 
1297
1350
  def __init__(self, subparsers, command_dict):
1298
- """Get status of data-buffer on robot
1351
+ """Get status of data-buffer on robot.
1299
1352
 
1300
1353
  Args:
1301
1354
  subparsers: List of argument parsers.
@@ -1538,7 +1591,6 @@ class MetricsCommand(Command):
1538
1591
 
1539
1592
  Returns:
1540
1593
  Timestamp string in ISO 8601 format
1541
-
1542
1594
  """
1543
1595
  # The json format of a timestamp is a string that looks like '"2022-01-12T21:56:05Z"',
1544
1596
  # so we strip off the outer quotes and return that.
@@ -2648,6 +2700,10 @@ class PowerWifiRadioCommand(Command):
2648
2700
 
2649
2701
 
2650
2702
 
2703
+
2704
+
2705
+
2706
+
2651
2707
  def main(args=None):
2652
2708
  """Command-line interface for interacting with robot services."""
2653
2709
  parser = argparse.ArgumentParser(prog='bosdyn.client', description=main.__doc__)
@@ -2678,6 +2734,7 @@ def main(args=None):
2678
2734
  PowerCommand(subparsers, command_dict)
2679
2735
  KeepaliveCommand(subparsers, command_dict)
2680
2736
 
2737
+
2681
2738
  options = parser.parse_args(args=args)
2682
2739
 
2683
2740
  setup_logging(verbose=options.verbose)
bosdyn/client/common.py CHANGED
@@ -25,7 +25,7 @@ from .exceptions import (CustomParamError, Error, InternalServerError, InvalidRe
25
25
 
26
26
  _LOGGER = logging.getLogger(__name__)
27
27
 
28
- from bosdyn.api import data_chunk_pb2, license_pb2
28
+ from bosdyn.api import license_pb2
29
29
 
30
30
  DEFAULT_RPC_TIMEOUT = 30 # seconds
31
31
 
@@ -8,15 +8,13 @@
8
8
 
9
9
  import collections
10
10
 
11
- from google.protobuf import json_format
12
-
13
11
  from bosdyn.api import data_acquisition_pb2 as data_acquisition
14
12
  from bosdyn.api import data_acquisition_service_pb2_grpc as data_acquisition_service
15
13
  from bosdyn.client.common import (BaseClient, common_header_errors, custom_params_error,
16
14
  error_factory, error_pair, handle_common_header_errors,
17
- handle_custom_params_errors, handle_unset_status_error)
18
- from bosdyn.client.exceptions import Error, InternalServerError, ResponseError
19
- from bosdyn.util import now_nsec, now_sec, now_timestamp, seconds_to_duration
15
+ handle_unset_status_error)
16
+ from bosdyn.client.exceptions import InternalServerError, ResponseError
17
+ from bosdyn.util import now_sec, now_timestamp, seconds_to_duration
20
18
 
21
19
 
22
20
  class DataAcquisitionResponseError(ResponseError):
@@ -12,11 +12,8 @@ from urllib.error import URLError
12
12
  from urllib.parse import urlencode
13
13
  from urllib.request import Request, urlopen
14
14
 
15
- from google.protobuf import json_format
16
15
  from google.protobuf.struct_pb2 import Struct
17
16
 
18
- import bosdyn.client
19
- import bosdyn.client.util
20
17
  from bosdyn.api import data_acquisition_pb2, data_acquisition_store_pb2
21
18
  from bosdyn.client.exceptions import ResponseError
22
19
 
@@ -15,8 +15,7 @@ from bosdyn.client.common import (BaseClient, error_factory, error_pair,
15
15
  handle_unset_status_error)
16
16
  from bosdyn.client.data_acquisition import (DataAcquisitionClient, DataAcquisitionResponseError,
17
17
  UnknownCaptureTypeError, _get_live_data_error,
18
- acquire_data_error, get_request_id, metadata_to_proto)
19
- from bosdyn.util import now_timestamp
18
+ acquire_data_error, metadata_to_proto)
20
19
 
21
20
 
22
21
  class DataAcquisitionPluginClient(BaseClient):
@@ -54,6 +54,7 @@ from bosdyn.client.data_acquisition_store import DataAcquisitionStoreClient
54
54
  from bosdyn.client.data_buffer import DataBufferClient
55
55
  from bosdyn.client.server_util import ResponseContext, populate_response_header
56
56
  from bosdyn.client.service_customization_helpers import create_value_validator
57
+ from bosdyn.util import now_sec
57
58
 
58
59
  _LOGGER = logging.getLogger(__name__)
59
60
 
@@ -590,7 +591,7 @@ class RequestManager:
590
591
  """
591
592
  state = self.get_request_state(request_id)
592
593
  with state._lock:
593
- state._completion_time = time.time()
594
+ state._completion_time = now_sec()
594
595
 
595
596
  def cleanup_requests(self, older_than_time=None):
596
597
  """Remove all requests that were completed farther in the past than older_than_time.
@@ -600,7 +601,7 @@ class RequestManager:
600
601
  Args:
601
602
  older_than_time (float): Optional time (in seconds) that requests will be removed after.
602
603
  """
603
- older_than_time = older_than_time or time.time() - kDefaultRequestExpiration
604
+ older_than_time = older_than_time or now_sec() - kDefaultRequestExpiration
604
605
  with self._lock:
605
606
  # Grab the contents to iterate through outside of the lock
606
607
  requests = list(self._requests.items())
@@ -10,18 +10,12 @@
10
10
  from os import fstat
11
11
  from pathlib import Path
12
12
 
13
- from google.protobuf import json_format
14
-
15
13
  from bosdyn.api import data_acquisition_store_pb2 as data_acquisition_store
16
14
  from bosdyn.api import data_acquisition_store_service_pb2_grpc as data_acquisition_store_service
17
15
  from bosdyn.api import data_chunk_pb2 as data_chunk
18
- from bosdyn.api import header_pb2, image_pb2
19
16
  from bosdyn.client.channel import DEFAULT_HEADER_BUFFER_LENGTH, DEFAULT_MAX_MESSAGE_LENGTH
20
- from bosdyn.client.common import (BaseClient, common_header_errors, error_factory, error_pair,
21
- handle_common_header_errors, handle_unset_status_error)
17
+ from bosdyn.client.common import BaseClient, common_header_errors
22
18
  from bosdyn.client.data_chunk import split_serialized
23
- from bosdyn.client.exceptions import Error, ResponseError
24
- from bosdyn.util import now_timestamp
25
19
 
26
20
  DEFAULT_CHUNK_SIZE_BYTES = int(DEFAULT_MAX_MESSAGE_LENGTH - DEFAULT_HEADER_BUFFER_LENGTH)
27
21
 
@@ -28,6 +28,7 @@ from bosdyn.api import parameter_pb2
28
28
  from bosdyn.client import time_sync
29
29
  from bosdyn.client.common import BaseClient, common_header_errors
30
30
  from bosdyn.client.exceptions import Error, ResponseError, RpcError
31
+ from bosdyn.util import now_sec
31
32
 
32
33
 
33
34
  class InvalidArgument(Error):
@@ -356,7 +357,7 @@ class DataBufferClient(BaseClient):
356
357
  (msg_type if msg_type is not None else
357
358
  (proto.DESCRIPTOR.full_name if proto is not None else 'Unknown')))
358
359
  else:
359
- return converter.robot_timestamp_from_local_secs(time.time())
360
+ return converter.robot_timestamp_from_local_secs(now_sec())
360
361
  return None
361
362
 
362
363
 
@@ -430,7 +431,7 @@ class LoggingHandler(logging.Handler): # pylint: disable=too-many-instance-attr
430
431
  msg = self.record_to_msg(record)
431
432
  with self._lock:
432
433
  self._msg_queue.append(msg)
433
- self._last_emit_time = time.time()
434
+ self._last_emit_time = now_sec()
434
435
 
435
436
  def flush(self):
436
437
  self._flush_event.set()
@@ -487,7 +488,7 @@ class LoggingHandler(logging.Handler): # pylint: disable=too-many-instance-attr
487
488
  while (self._num_failed_sends_sequential < self._limit_failed_sends_sequential and
488
489
  not self._shutdown_event.is_set()):
489
490
  flush = self._flush_event.wait(self._flush_event_wait_time)
490
- msg_age = time.time() - self._last_emit_time
491
+ msg_age = now_sec() - self._last_emit_time
491
492
  with self._lock:
492
493
  num_msgs = len(self._msg_queue)
493
494
  to_send = self._msg_queue[:num_msgs]
@@ -555,7 +556,7 @@ class LoggingHandler(logging.Handler): # pylint: disable=too-many-instance-attr
555
556
  if self.time_sync_endpoint is not None:
556
557
  try:
557
558
  msg.timestamp.CopyFrom(
558
- self.time_sync_endpoint.robot_timestamp_from_local_secs(time.time()))
559
+ self.time_sync_endpoint.robot_timestamp_from_local_secs(now_sec()))
559
560
  except time_sync.NotEstablishedError:
560
561
  # If timestamp is not set in the proto, data-buffer will timestamp it on receipt.
561
562
  msg.message = '(No time sync!): ' + msg.message
@@ -18,6 +18,7 @@ from bosdyn.api import (directory_pb2, directory_registration_pb2,
18
18
  directory_registration_service_pb2_grpc)
19
19
  from bosdyn.client.common import (BaseClient, error_factory, error_pair,
20
20
  handle_common_header_errors, handle_unset_status_error)
21
+ from bosdyn.util import now_sec
21
22
 
22
23
  from .error_callback_result import ErrorCallbackResult
23
24
  from .exceptions import ResponseError, RetryableUnavailableError, RpcError, TimedOutError
@@ -442,7 +443,7 @@ class DirectoryRegistrationKeepAlive(object):
442
443
 
443
444
  self.logger.info('Starting directory registration loop for {}'.format(self.directory_name))
444
445
  while not self._end_reregister_signal.wait(wait_time):
445
- exec_start = time.time()
446
+ exec_start = now_sec()
446
447
  action = ErrorCallbackResult.RESUME_NORMAL_OPERATION
447
448
  try:
448
449
  self.dir_reg_client.register(
@@ -476,7 +477,7 @@ class DirectoryRegistrationKeepAlive(object):
476
477
  # Log all other exceptions, but continue looping in hopes that it resolves itself
477
478
  self.logger.exception('Caught general exception')
478
479
 
479
- elapsed = time.time() - exec_start
480
+ elapsed = now_sec() - exec_start
480
481
  if action == ErrorCallbackResult.RETRY_IMMEDIATELY:
481
482
  wait_time = 0.0
482
483
  elif action == ErrorCallbackResult.ABORT: