bosdyn-client 5.0.1.2__py3-none-any.whl → 5.1.0__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 +61 -4
  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.0.dist-info}/METADATA +3 -3
  54. bosdyn_client-5.1.0.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.0.dist-info}/WHEEL +0 -0
  57. {bosdyn_client-5.0.1.2.dist-info → bosdyn_client-5.1.0.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
@@ -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
 
@@ -920,6 +924,53 @@ class StartRetroLogCommand(Command):
920
924
  return True
921
925
 
922
926
 
927
+ class StartConcurrentLogCommand(Command):
928
+ """Start a concurrent experiment log, with event-derived data."""
929
+
930
+ NAME = 'concurrent'
931
+
932
+ def __init__(self, subparsers, command_dict):
933
+ """Start data log
934
+ Args:
935
+ subparsers: List of argument parsers.
936
+ command_dict: Dictionary of command names which take parsed options.
937
+ """
938
+ super(StartConcurrentLogCommand, self).__init__(subparsers, command_dict)
939
+ self._parser.add_argument('seconds', type=float, help='how long should the experiment run?')
940
+ self._parser.add_argument('event_type',
941
+ help='name of the event type we want to match a recipe against')
942
+
943
+ def _run(self, robot, options):
944
+ """Implementation of the command.
945
+ Args:
946
+ robot: Robot object on which to run the command.
947
+ options: Parsed command-line arguments.
948
+ Returns:
949
+ True
950
+ """
951
+
952
+ client = robot.ensure_client(LogStatusClient.default_service_name)
953
+ time_sync_endpoint = TimeSyncEndpoint(
954
+ robot.ensure_client(TimeSyncClient.default_service_name))
955
+ if not time_sync_endpoint.establish_timesync():
956
+ print("Failed to establish time sync with the robot.")
957
+ return False
958
+
959
+ robot_now = time_sync_endpoint.robot_timestamp_from_local_secs(now_sec())
960
+
961
+ event = Event()
962
+ event.type = options.event_type
963
+ event.description = 'Triggering a recipe data log'
964
+ event.source = 'LogStatus CLI'
965
+ event.id = uuid.uuid4().hex
966
+ event.start_time.CopyFrom(robot_now)
967
+ event.end_time.CopyFrom(robot_now)
968
+
969
+ response = client.start_concurrent_log(options.seconds, event)
970
+ print(response.log_status)
971
+ return True
972
+
973
+
923
974
  class TerminateLogCommand(Command):
924
975
  """Terminate log gathering process."""
925
976
 
@@ -1054,7 +1105,7 @@ class TextMsgCommand(Command):
1054
1105
  if options.timestamp:
1055
1106
  try:
1056
1107
  robot_timestamp = robot.time_sync.robot_timestamp_from_local_secs(
1057
- time.time(), timesync_timeout_sec=1.0)
1108
+ now_sec(), timesync_timeout_sec=1.0)
1058
1109
  except TimeSyncError as err:
1059
1110
  print("Failed to send message with timestamp: {}.".format(err))
1060
1111
  return False
@@ -1107,7 +1158,7 @@ class OperatorCommentCommand(Command):
1107
1158
  """
1108
1159
  client_timestamp = None
1109
1160
  if options.timestamp:
1110
- client_timestamp = time.time()
1161
+ client_timestamp = now_sec()
1111
1162
  try:
1112
1163
  robot.time_sync.wait_for_sync(timeout_sec=1.0)
1113
1164
  except TimeSyncError as err:
@@ -2648,6 +2699,10 @@ class PowerWifiRadioCommand(Command):
2648
2699
 
2649
2700
 
2650
2701
 
2702
+
2703
+
2704
+
2705
+
2651
2706
  def main(args=None):
2652
2707
  """Command-line interface for interacting with robot services."""
2653
2708
  parser = argparse.ArgumentParser(prog='bosdyn.client', description=main.__doc__)
@@ -2675,9 +2730,11 @@ def main(args=None):
2675
2730
  LocalGridCommands(subparsers, command_dict)
2676
2731
  DataAcquisitionCommand(subparsers, command_dict)
2677
2732
  HostComputerIPCommand(subparsers, command_dict)
2733
+ VideoRecordingCommands(subparsers, command_dict)
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:
bosdyn/client/estop.py CHANGED
@@ -18,6 +18,7 @@ import time
18
18
  from google.protobuf.duration_pb2 import Duration
19
19
 
20
20
  from bosdyn.api import estop_pb2, estop_service_pb2_grpc
21
+ from bosdyn.util import now_sec
21
22
 
22
23
  from .common import (BaseClient, common_header_errors, error_factory, handle_common_header_errors,
23
24
  handle_unset_status_error)
@@ -578,7 +579,7 @@ class EstopKeepAlive(object):
578
579
  while True:
579
580
  # Include the time it takes to execute keep_running, in case it takes a significant
580
581
  # portion of our check in period.
581
- exec_start = time.time()
582
+ exec_start = now_sec()
582
583
  if not self._keep_running():
583
584
  break
584
585
  try:
@@ -604,7 +605,7 @@ class EstopKeepAlive(object):
604
605
  self._ok()
605
606
 
606
607
  # How long did the RPC and processing of said RPC take?
607
- exec_sec = time.time() - exec_start
608
+ exec_sec = now_sec() - exec_start
608
609
 
609
610
  # Block and wait for the stop signal. If we receive it within the check-in period,
610
611
  # leave the loop. This check must be at the end of the loop!
bosdyn/client/fault.py CHANGED
@@ -8,7 +8,7 @@
8
8
  import collections
9
9
 
10
10
  from bosdyn.api import fault_service_pb2_grpc, service_fault_pb2
11
- from bosdyn.client.common import (BaseClient, common_header_errors, error_factory, error_pair,
11
+ from bosdyn.client.common import (BaseClient, error_factory, error_pair,
12
12
  handle_common_header_errors, handle_unset_status_error)
13
13
 
14
14
  from .exceptions import ResponseError
@@ -6,10 +6,8 @@
6
6
 
7
7
  """For clients to use the Gps Aggregator service."""
8
8
 
9
- from bosdyn.api.gps import aggregator_pb2, aggregator_service_pb2_grpc, gps_pb2
10
- from bosdyn.client.common import (BaseClient, common_header_errors, error_factory, error_pair,
11
- handle_common_header_errors, handle_unset_status_error)
12
- from bosdyn.client.exceptions import ResponseError
9
+ from bosdyn.api.gps import aggregator_pb2, aggregator_service_pb2_grpc
10
+ from bosdyn.client.common import BaseClient, handle_common_header_errors
13
11
 
14
12
 
15
13
  class AggregatorClient(BaseClient):
@@ -11,15 +11,13 @@ import socket
11
11
  import time
12
12
  from typing import List
13
13
 
14
- import bosdyn.api
15
- import bosdyn.client.util
16
14
  from bosdyn.api.gps.gps_pb2 import GpsDataPoint, GpsDevice
17
15
  from bosdyn.client.exceptions import ProxyConnectionError
18
16
  from bosdyn.client.gps.aggregator_client import AggregatorClient
19
17
  from bosdyn.client.gps.NMEAParser import NMEAParser
20
18
  from bosdyn.client.gps.ntrip_client import NtripClient, NtripClientParams
21
19
  from bosdyn.client.robot import UnregisteredServiceNameError
22
- from bosdyn.util import RobotTimeConverter, duration_to_seconds
20
+ from bosdyn.util import RobotTimeConverter, now_sec
23
21
 
24
22
 
25
23
  class NMEAStreamReader(object):
@@ -44,7 +42,7 @@ class NMEAStreamReader(object):
44
42
  raw_data = str(raw_data, "utf-8")
45
43
  except UnicodeDecodeError:
46
44
  # Throttle the logs.
47
- now = time.time()
45
+ now = now_sec()
48
46
  if self.last_failed_read_log_time is None or (
49
47
  now - self.last_failed_read_log_time) > self.LOG_THROTTLE_TIME:
50
48
  self.logger.exception("Failed to decode NMEA message. Is it not Unicode?")
@@ -160,17 +158,17 @@ class GpsListener:
160
158
  agg_future = self.aggregator_client.new_gps_data_async(
161
159
  accumulated_data, self.gps_device)
162
160
  accumulated_data.clear()
163
- timestamp_of_last_rpc = time.time()
161
+ timestamp_of_last_rpc = now_sec()
164
162
  time_passed_since_last_rpc = 0
165
163
  else:
166
164
  if time_passed_since_last_rpc > every_x_seconds:
167
165
  if agg_future is None or agg_future.done():
168
166
  agg_future = self.aggregator_client.new_gps_data_async([],
169
167
  self.gps_device)
170
- timestamp_of_last_rpc = time.time()
168
+ timestamp_of_last_rpc = now_sec()
171
169
  time_passed_since_last_rpc = 0
172
170
  else:
173
- time_passed_since_last_rpc = time.time() - timestamp_of_last_rpc
171
+ time_passed_since_last_rpc = now_sec() - timestamp_of_last_rpc
174
172
 
175
173
  # If we are running an NTRIP client, pass it the latest GGA message.
176
174
  if self.ntrip_client is not None: