bosdyn-client 4.1.1__py3-none-any.whl → 5.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bosdyn/client/__init__.py +1 -0
- bosdyn/client/audio_visual.py +339 -0
- bosdyn/client/audio_visual_helpers.py +112 -0
- bosdyn/client/command_line.py +15 -7
- bosdyn/client/data_acquisition_helpers.py +1 -1
- bosdyn/client/data_acquisition_store.py +2 -2
- bosdyn/client/directory_registration.py +34 -5
- bosdyn/client/error_callback_result.py +29 -0
- bosdyn/client/gps/NMEAParser.py +16 -6
- bosdyn/client/gps/gps_listener.py +31 -1
- bosdyn/client/gps/ntrip_client.py +240 -0
- bosdyn/client/graph_nav.py +16 -1
- bosdyn/client/gripper_camera_param.py +40 -0
- bosdyn/client/image.py +16 -0
- bosdyn/client/image_service_helpers.py +88 -68
- bosdyn/client/keepalive.py +37 -8
- bosdyn/client/log_status.py +6 -0
- bosdyn/client/math_helpers.py +18 -0
- bosdyn/client/payload_registration.py +40 -6
- bosdyn/client/payload_software_update.py +185 -0
- bosdyn/client/payload_software_update_initiation.py +79 -0
- bosdyn/client/point_cloud.py +9 -0
- bosdyn/client/robot.py +9 -4
- bosdyn/client/sdk.py +4 -2
- bosdyn/client/service_customization_helpers.py +19 -6
- bosdyn/client/spot_cam/__init__.py +2 -0
- bosdyn/client/spot_cam/ptz.py +20 -24
- bosdyn/client/token_manager.py +56 -27
- bosdyn/client/util.py +1 -1
- {bosdyn_client-4.1.1.dist-info → bosdyn_client-5.0.1.dist-info}/METADATA +4 -4
- {bosdyn_client-4.1.1.dist-info → bosdyn_client-5.0.1.dist-info}/RECORD +33 -27
- {bosdyn_client-4.1.1.dist-info → bosdyn_client-5.0.1.dist-info}/WHEEL +0 -0
- {bosdyn_client-4.1.1.dist-info → bosdyn_client-5.0.1.dist-info}/top_level.txt +0 -0
bosdyn/client/gps/NMEAParser.py
CHANGED
|
@@ -14,7 +14,7 @@ from google.protobuf.any_pb2 import Any
|
|
|
14
14
|
from google.protobuf.timestamp_pb2 import Timestamp
|
|
15
15
|
|
|
16
16
|
from bosdyn.api.gps.gps_pb2 import GpsDataPoint
|
|
17
|
-
from bosdyn.util import RobotTimeConverter,
|
|
17
|
+
from bosdyn.util import RobotTimeConverter, now_sec, seconds_to_timestamp
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
has_warned_no_zda = False
|
|
@@ -36,6 +36,7 @@ class NMEAParser(object):
|
|
|
36
36
|
# If your GPS outputs data at 20 Hz, this constant must be less than 0.050 seconds.
|
|
37
37
|
self.grouping_timeout = 0.025
|
|
38
38
|
self.last_failed_read_log_time = None
|
|
39
|
+
self.last_gga = None
|
|
39
40
|
|
|
40
41
|
def nmea_message_group_to_gps_data_point(self, nmea_messages: List[Tuple[str, str, int]],
|
|
41
42
|
time_converter: RobotTimeConverter):
|
|
@@ -53,12 +54,13 @@ class NMEAParser(object):
|
|
|
53
54
|
data, raw_nmea_msg, client_timestamp = nmea_message_list
|
|
54
55
|
|
|
55
56
|
if data.sentence_type == 'GGA':
|
|
57
|
+
self.last_gga = raw_nmea_msg
|
|
56
58
|
if data.latitude is not None and data.longitude is not None and data.altitude is not None:
|
|
57
59
|
data_point.llh.latitude = data.latitude
|
|
58
60
|
data_point.llh.longitude = data.longitude
|
|
59
61
|
data_point.llh.height = data.altitude
|
|
60
62
|
|
|
61
|
-
if data.num_sats is not None and int(data.num_sats) > 0:
|
|
63
|
+
if data.num_sats is not None and data.num_sats != '' and int(data.num_sats) > 0:
|
|
62
64
|
for _ in range(int(data.num_sats)):
|
|
63
65
|
sat = data_point.satellites.add()
|
|
64
66
|
|
|
@@ -86,7 +88,12 @@ class NMEAParser(object):
|
|
|
86
88
|
data_point.accuracy.vertical = data.std_dev_altitude
|
|
87
89
|
|
|
88
90
|
elif data.sentence_type == 'ZDA':
|
|
89
|
-
|
|
91
|
+
try:
|
|
92
|
+
gps_timestamp = data.datetime
|
|
93
|
+
except:
|
|
94
|
+
self.logger.exception("Failed to extract datetime from ZDA message.")
|
|
95
|
+
continue
|
|
96
|
+
|
|
90
97
|
# Protobuf timestamp does not use timezone aware timestamps.
|
|
91
98
|
gps_timestamp_no_tz = gps_timestamp.replace(tzinfo=None)
|
|
92
99
|
data_point.timestamp_gps.FromDatetime(gps_timestamp_no_tz)
|
|
@@ -111,7 +118,7 @@ class NMEAParser(object):
|
|
|
111
118
|
|
|
112
119
|
def parse(self, new_data: str, time_converter: RobotTimeConverter,
|
|
113
120
|
check: bool = True) -> List[GpsDataPoint]:
|
|
114
|
-
timestamp =
|
|
121
|
+
timestamp = now_sec() # Client timestamp when received.
|
|
115
122
|
self.data = self.data + new_data
|
|
116
123
|
|
|
117
124
|
if len(self.data) == 0:
|
|
@@ -143,14 +150,14 @@ class NMEAParser(object):
|
|
|
143
150
|
except Exception as e:
|
|
144
151
|
# Parsing error, log and skip.
|
|
145
152
|
# Throttle the logs.
|
|
146
|
-
now =
|
|
153
|
+
now = now_sec()
|
|
147
154
|
if self.last_failed_read_log_time is None or (
|
|
148
155
|
now - self.last_failed_read_log_time) > self.LOG_THROTTLE_TIME:
|
|
149
156
|
self.logger.exception(f"Failed to parse {stripped}. Is it NMEA?")
|
|
150
157
|
self.last_failed_read_log_time = now
|
|
151
158
|
continue
|
|
152
159
|
|
|
153
|
-
#
|
|
160
|
+
# If the message does not contain a timestamp attribute, abandon the rest of the logic
|
|
154
161
|
# and go to the beginning of the loop
|
|
155
162
|
if not hasattr(nmea_msg, 'timestamp'):
|
|
156
163
|
continue
|
|
@@ -193,3 +200,6 @@ class NMEAParser(object):
|
|
|
193
200
|
break
|
|
194
201
|
|
|
195
202
|
return [self.nmea_message_group_to_gps_data_point(x, time_converter) for x in found_subsets]
|
|
203
|
+
|
|
204
|
+
def get_latest_gga(self):
|
|
205
|
+
return self.last_gga
|
|
@@ -17,6 +17,7 @@ from bosdyn.api.gps.gps_pb2 import GpsDataPoint, GpsDevice
|
|
|
17
17
|
from bosdyn.client.exceptions import ProxyConnectionError
|
|
18
18
|
from bosdyn.client.gps.aggregator_client import AggregatorClient
|
|
19
19
|
from bosdyn.client.gps.NMEAParser import NMEAParser
|
|
20
|
+
from bosdyn.client.gps.ntrip_client import NtripClient, NtripClientParams
|
|
20
21
|
from bosdyn.client.robot import UnregisteredServiceNameError
|
|
21
22
|
from bosdyn.util import RobotTimeConverter, duration_to_seconds
|
|
22
23
|
|
|
@@ -70,6 +71,9 @@ class NMEAStreamReader(object):
|
|
|
70
71
|
|
|
71
72
|
return new_points
|
|
72
73
|
|
|
74
|
+
def get_latest_gga(self):
|
|
75
|
+
return self.parser.get_latest_gga()
|
|
76
|
+
|
|
73
77
|
|
|
74
78
|
class GpsListener:
|
|
75
79
|
|
|
@@ -77,10 +81,22 @@ class GpsListener:
|
|
|
77
81
|
self.logger = logger
|
|
78
82
|
self.robot = robot
|
|
79
83
|
self.time_converter = time_converter
|
|
84
|
+
self.stream = stream
|
|
80
85
|
self.reader = NMEAStreamReader(logger, stream, body_tform_gps, verbose)
|
|
81
86
|
self.gps_device = GpsDevice()
|
|
82
87
|
self.gps_device.name = name
|
|
83
88
|
self.aggregator_client = None
|
|
89
|
+
self.ntrip_client = None
|
|
90
|
+
|
|
91
|
+
def run_ntrip_client(self, ntrip_params: NtripClientParams):
|
|
92
|
+
self.ntrip_client = NtripClient(self.stream, ntrip_params, self.logger)
|
|
93
|
+
self.ntrip_client.start_stream()
|
|
94
|
+
|
|
95
|
+
def stop_ntrip_client(self):
|
|
96
|
+
if self.ntrip_client is None:
|
|
97
|
+
return
|
|
98
|
+
self.ntrip_client.stop_stream()
|
|
99
|
+
self.ntrip_client = None
|
|
84
100
|
|
|
85
101
|
def run(self):
|
|
86
102
|
# It is possible for a payload to come up faster than the service. Loop a few times
|
|
@@ -118,7 +134,7 @@ class GpsListener:
|
|
|
118
134
|
agg_future = None
|
|
119
135
|
|
|
120
136
|
# Attach and run until a SIGINT is received.
|
|
121
|
-
self.logger.info('
|
|
137
|
+
self.logger.info('Listening for GPS data.')
|
|
122
138
|
try:
|
|
123
139
|
while True:
|
|
124
140
|
try:
|
|
@@ -156,5 +172,19 @@ class GpsListener:
|
|
|
156
172
|
else:
|
|
157
173
|
time_passed_since_last_rpc = time.time() - timestamp_of_last_rpc
|
|
158
174
|
|
|
175
|
+
# If we are running an NTRIP client, pass it the latest GGA message.
|
|
176
|
+
if self.ntrip_client is not None:
|
|
177
|
+
# If the NTRIP Client's stream has been closed, restart it.
|
|
178
|
+
if not self.ntrip_client.is_streaming():
|
|
179
|
+
self.logger.info("Restarting NTRIP Client!")
|
|
180
|
+
self.ntrip_client.start_stream()
|
|
181
|
+
|
|
182
|
+
latest_gga = self.reader.get_latest_gga()
|
|
183
|
+
if latest_gga is not None:
|
|
184
|
+
self.ntrip_client.handle_nmea_gga(latest_gga)
|
|
185
|
+
|
|
159
186
|
except KeyboardInterrupt:
|
|
160
187
|
print() # Get past the ^C in the console output
|
|
188
|
+
|
|
189
|
+
# Just in case there is an NTRIP client still running, stop it here.
|
|
190
|
+
self.stop_ntrip_client()
|
|
@@ -0,0 +1,240 @@
|
|
|
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 base64
|
|
8
|
+
import socket
|
|
9
|
+
import ssl
|
|
10
|
+
import time
|
|
11
|
+
from threading import Thread
|
|
12
|
+
|
|
13
|
+
SERVER_RECONNECT_DELAY = 60 # seconds of delay before retry after server error
|
|
14
|
+
SOCKET_TIMEOUT = 10 # socket timeout in seconds
|
|
15
|
+
SOCKET_MAX_RECV_TIMEOUTS = 12 # number of timeouts before reconnecting
|
|
16
|
+
|
|
17
|
+
DEFAULT_NTRIP_SERVER = ""
|
|
18
|
+
DEFAULT_NTRIP_PORT = 2101
|
|
19
|
+
DEFAULT_NTRIP_TLS_PORT = 2102
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NtripClientParams:
|
|
23
|
+
"""
|
|
24
|
+
Class for storing parameters for connecting an NTRIP client to an NTRIP server.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, server=DEFAULT_NTRIP_SERVER, port=DEFAULT_NTRIP_PORT, user="", password="",
|
|
28
|
+
mountpoint="", tls=False):
|
|
29
|
+
"""
|
|
30
|
+
Constructor.
|
|
31
|
+
"""
|
|
32
|
+
self.server = server
|
|
33
|
+
self.port = port
|
|
34
|
+
self.user = user
|
|
35
|
+
self.password = password
|
|
36
|
+
self.mountpoint = mountpoint
|
|
37
|
+
self.tls = tls
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class NtripClient:
|
|
41
|
+
"""
|
|
42
|
+
Client used to connect to an NTRIP server to download GPS corrections. These corrections are
|
|
43
|
+
then forwarded on to the GPS device using the given stream.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, device, params: NtripClientParams, logger):
|
|
47
|
+
"""
|
|
48
|
+
Constructor.
|
|
49
|
+
"""
|
|
50
|
+
self.device = device
|
|
51
|
+
self.host = params.server
|
|
52
|
+
self.port = params.port
|
|
53
|
+
self.user = params.user
|
|
54
|
+
self.password = params.password
|
|
55
|
+
self.mountpoint = params.mountpoint
|
|
56
|
+
self.tls = params.tls
|
|
57
|
+
|
|
58
|
+
self.thread = None
|
|
59
|
+
self.streaming = False
|
|
60
|
+
self.sock = None
|
|
61
|
+
|
|
62
|
+
self.logger = logger
|
|
63
|
+
|
|
64
|
+
def make_request(self):
|
|
65
|
+
"""
|
|
66
|
+
Make a connection request to an NTRIP server.
|
|
67
|
+
"""
|
|
68
|
+
auth_str = base64.b64encode(f"{self.user}:{self.password}".encode()).decode()
|
|
69
|
+
request = (f"GET /{self.mountpoint} HTTP/1.1\r\n"
|
|
70
|
+
f"Host: {self.host}:{self.port}\r\n"
|
|
71
|
+
"User-Agent: NTRIP Python Client\r\n"
|
|
72
|
+
"Accept: */*\r\n"
|
|
73
|
+
f"Authorization: Basic {auth_str}\r\n"
|
|
74
|
+
"Connection: close\r\n"
|
|
75
|
+
"\r\n")
|
|
76
|
+
return request.encode()
|
|
77
|
+
|
|
78
|
+
def start_stream(self):
|
|
79
|
+
"""
|
|
80
|
+
Start streaming data from an NTRIP server to a GPS receiver.
|
|
81
|
+
"""
|
|
82
|
+
if self.streaming:
|
|
83
|
+
self.stop_stream()
|
|
84
|
+
self.thread = Thread(target=self.stream_data_worker, daemon=True)
|
|
85
|
+
self.thread.start()
|
|
86
|
+
|
|
87
|
+
def stop_stream(self):
|
|
88
|
+
"""
|
|
89
|
+
Stop streaming NTRIP data.
|
|
90
|
+
"""
|
|
91
|
+
self.streaming = False
|
|
92
|
+
if self.thread:
|
|
93
|
+
self.thread.join()
|
|
94
|
+
self.thread = None
|
|
95
|
+
|
|
96
|
+
def is_streaming(self):
|
|
97
|
+
"""
|
|
98
|
+
Determine if we are streaming NTRIP data.
|
|
99
|
+
"""
|
|
100
|
+
return self.streaming
|
|
101
|
+
|
|
102
|
+
def send_gga(self, gga):
|
|
103
|
+
"""
|
|
104
|
+
Given a GPGGA message, send it to the NTRIP server. This helps the NTRIP server send
|
|
105
|
+
corrections that are applicable to the area in which the receiver is operating.
|
|
106
|
+
"""
|
|
107
|
+
if self.sock:
|
|
108
|
+
try:
|
|
109
|
+
self.sock.send(gga.encode())
|
|
110
|
+
except OSError:
|
|
111
|
+
self.logger.warning("Error sending GGA")
|
|
112
|
+
return False
|
|
113
|
+
return True
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def create_icy_session(self):
|
|
117
|
+
"""
|
|
118
|
+
NTRIP Rev1 uses Shoutcast (ICY). Create an ICY session to stream RTCM data.
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
# Create a socket connection to the host and port.
|
|
122
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
123
|
+
if self.tls:
|
|
124
|
+
self.logger.info("Using secure connection")
|
|
125
|
+
context = ssl.create_default_context()
|
|
126
|
+
sock = context.wrap_socket(sock, server_hostname=self.host)
|
|
127
|
+
sock.settimeout(SOCKET_TIMEOUT)
|
|
128
|
+
self.logger.info("Connecting to NTRIP Server.")
|
|
129
|
+
sock.connect((self.host, self.port))
|
|
130
|
+
self.logger.info("Connected to NTRIP Server.")
|
|
131
|
+
|
|
132
|
+
# Send the request.
|
|
133
|
+
self.logger.info("Sending NTRIP Request.")
|
|
134
|
+
sock.send(self.make_request())
|
|
135
|
+
# Read a chunk of data, expecting to get the status line and headers.
|
|
136
|
+
response = sock.recv(1024)
|
|
137
|
+
|
|
138
|
+
response_lines = response.split(b"\r\n")
|
|
139
|
+
if len(response_lines) < 2:
|
|
140
|
+
self.logger.error("Invalid response from server: %s", response)
|
|
141
|
+
return False
|
|
142
|
+
status = response_lines[0].decode().split(" ")
|
|
143
|
+
self.logger.info(response_lines[0].decode())
|
|
144
|
+
if status[1] != "200":
|
|
145
|
+
self.logger.error("HTTP Error: %s, retrying in %d seconds",
|
|
146
|
+
response_lines[0].decode(), SERVER_RECONNECT_DELAY)
|
|
147
|
+
sock.close()
|
|
148
|
+
time.sleep(SERVER_RECONNECT_DELAY)
|
|
149
|
+
return False
|
|
150
|
+
self.logger.info("NTRIP Request Response received.")
|
|
151
|
+
for line in range(1, len(response_lines)):
|
|
152
|
+
if response_lines[line] == b"":
|
|
153
|
+
# Empty line, end of headers.
|
|
154
|
+
# The rest of the response contains data.
|
|
155
|
+
response = b"\r\n".join(response_lines[line + 1:])
|
|
156
|
+
break
|
|
157
|
+
self.logger.info(response_lines[line])
|
|
158
|
+
|
|
159
|
+
if response:
|
|
160
|
+
self.handle_ntrip_data(response)
|
|
161
|
+
|
|
162
|
+
# Make the socket available for sending GGA.
|
|
163
|
+
self.sock = sock
|
|
164
|
+
return True
|
|
165
|
+
except (socket.herror, socket.gaierror):
|
|
166
|
+
self.logger.warning("Error connecting to server %s:%d", self.host, self.port)
|
|
167
|
+
return False
|
|
168
|
+
except (TimeoutError, socket.timeout):
|
|
169
|
+
self.logger.warning("Connection timeout")
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
def stream_data(self):
|
|
173
|
+
"""
|
|
174
|
+
Stream NTRIP data from a connected server and send it to a GPS receiver.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
# NTRIP Rev1 uses Shoutcast (ICY), which looks a lot like HTTP but isn't quite the same.
|
|
178
|
+
# Create an ICY session here to talk to an NTRIP Rev1 caster.
|
|
179
|
+
ok = self.create_icy_session()
|
|
180
|
+
if not ok:
|
|
181
|
+
self.logger.error("Failed to create NTRIP Rev1 ICY session.")
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
timeouts = 0
|
|
185
|
+
has_data = False
|
|
186
|
+
while self.streaming and timeouts < SOCKET_MAX_RECV_TIMEOUTS:
|
|
187
|
+
try:
|
|
188
|
+
response = self.sock.recv(2048)
|
|
189
|
+
if response:
|
|
190
|
+
timeouts = 0
|
|
191
|
+
self.handle_ntrip_data(response)
|
|
192
|
+
if not has_data:
|
|
193
|
+
self.logger.info("Received NTRIP data.")
|
|
194
|
+
has_data = True
|
|
195
|
+
else:
|
|
196
|
+
self.logger.warning("Connection closed by server")
|
|
197
|
+
break
|
|
198
|
+
except (socket.herror, socket.gaierror):
|
|
199
|
+
self.logger.warning("Error connecting to server %s:%d", self.host, self.port)
|
|
200
|
+
except (TimeoutError, socket.timeout):
|
|
201
|
+
self.logger.exception("Socket timeout.")
|
|
202
|
+
timeouts += 1
|
|
203
|
+
|
|
204
|
+
self.logger.info("NTRIP stream closed. Closing socket.")
|
|
205
|
+
# Close the socket.
|
|
206
|
+
try:
|
|
207
|
+
self.sock.close()
|
|
208
|
+
except OSError:
|
|
209
|
+
pass
|
|
210
|
+
self.sock = None
|
|
211
|
+
|
|
212
|
+
self.logger.info("NTRIP client finished.")
|
|
213
|
+
|
|
214
|
+
def stream_data_worker(self):
|
|
215
|
+
"""
|
|
216
|
+
NTRIP client worker thread function.
|
|
217
|
+
"""
|
|
218
|
+
self.streaming = True
|
|
219
|
+
while self.streaming:
|
|
220
|
+
self.stream_data()
|
|
221
|
+
|
|
222
|
+
def handle_ntrip_data(self, data):
|
|
223
|
+
"""
|
|
224
|
+
Callback for handling NTRIP data.
|
|
225
|
+
"""
|
|
226
|
+
# Send to receiver as is.
|
|
227
|
+
self.device.write(data)
|
|
228
|
+
|
|
229
|
+
def handle_nmea_gga(self, sentence):
|
|
230
|
+
"""
|
|
231
|
+
Process an NMEA-GGA sentence passed in as a string.
|
|
232
|
+
"""
|
|
233
|
+
fields = sentence.split(",")
|
|
234
|
+
if len(fields) < 7:
|
|
235
|
+
return
|
|
236
|
+
quality = int(fields[6] or 0)
|
|
237
|
+
if quality not in (0, 6): # No fix or estimated.
|
|
238
|
+
if not self.send_gga(sentence + "\r\n"):
|
|
239
|
+
# Something went wrong, restart the stream.
|
|
240
|
+
self.start_stream()
|
bosdyn/client/graph_nav.py
CHANGED
|
@@ -413,11 +413,26 @@ class GraphNavClient(BaseClient):
|
|
|
413
413
|
request = self._build_navigate_to_anchor_request(
|
|
414
414
|
seed_tform_goal, travel_params, route_params, cmd_duration, leases, used_endpoint,
|
|
415
415
|
command_id, goal_waypoint_rt_seed_ewrt_seed_tolerance, gps_navigation_params)
|
|
416
|
-
return self.call_async(self._stub.
|
|
416
|
+
return self.call_async(self._stub.NavigateToAnchor, request,
|
|
417
417
|
value_from_response=_command_id_from_navigate_route_response,
|
|
418
418
|
error_from_response=_navigate_to_anchor_error, copy_request=False,
|
|
419
419
|
**kwargs)
|
|
420
420
|
|
|
421
|
+
def navigate_to_anchor_full_async(self, seed_tform_goal, cmd_duration, route_params=None,
|
|
422
|
+
travel_params=None, leases=None, timesync_endpoint=None,
|
|
423
|
+
goal_waypoint_rt_seed_ewrt_seed_tolerance=None,
|
|
424
|
+
command_id=None, gps_navigation_params=None, **kwargs):
|
|
425
|
+
"""Async version of navigate_to_anchor() that returns the full response."""
|
|
426
|
+
used_endpoint = timesync_endpoint or self._timesync_endpoint
|
|
427
|
+
if not used_endpoint:
|
|
428
|
+
raise GraphNavServiceResponseError(response=None, error_message='No timesync endpoint!')
|
|
429
|
+
request = self._build_navigate_to_anchor_request(
|
|
430
|
+
seed_tform_goal, travel_params, route_params, cmd_duration, leases, used_endpoint,
|
|
431
|
+
command_id, goal_waypoint_rt_seed_ewrt_seed_tolerance, gps_navigation_params)
|
|
432
|
+
return self.call_async(self._stub.NavigateToAnchor, request,
|
|
433
|
+
error_from_response=_navigate_to_anchor_error, copy_request=False,
|
|
434
|
+
**kwargs)
|
|
435
|
+
|
|
421
436
|
def navigation_feedback(self, command_id=0, **kwargs):
|
|
422
437
|
"""Returns the feedback corresponding to the active route follow command.
|
|
423
438
|
|
|
@@ -4,6 +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 deprecated.sphinx import deprecated
|
|
8
|
+
|
|
7
9
|
from bosdyn.api import gripper_camera_param_service_pb2_grpc
|
|
8
10
|
from bosdyn.client.common import BaseClient, common_header_errors
|
|
9
11
|
|
|
@@ -52,3 +54,41 @@ class GripperCameraParamClient(BaseClient):
|
|
|
52
54
|
"""Async version of gripper_camera_get_param_service_command()."""
|
|
53
55
|
return self.call_async(self._stub.GetParams, gripper_camera_get_param_request,
|
|
54
56
|
error_from_response=common_header_errors, **kwargs)
|
|
57
|
+
|
|
58
|
+
def set_camera_calib(self, set_gripper_camera_calib_request, **kwargs):
|
|
59
|
+
"""Issue gripper camera calibration
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
set_gripper_camera_calib_request (gripper_camera_params_pb2.GripperCameraCalibrationRequest) : The command request to set gripper camera calibration
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
The GripperCameraCalibrationResponse message.
|
|
66
|
+
"""
|
|
67
|
+
return self.call(self._stub.SetCamCalib, set_gripper_camera_calib_request,
|
|
68
|
+
error_from_response=common_header_errors, **kwargs)
|
|
69
|
+
|
|
70
|
+
def set_camera_calib_async(self, set_gripper_camera_calib_request, **kwargs):
|
|
71
|
+
"""Async version of set_camera_calib()."""
|
|
72
|
+
return self.call_async(self._stub.SetCamCalib, set_gripper_camera_calib_request,
|
|
73
|
+
error_from_response=common_header_errors, **kwargs)
|
|
74
|
+
|
|
75
|
+
def get_camera_calib(self, get_gripper_camera_calib_request, **kwargs):
|
|
76
|
+
"""Issue gripper camera get calibration
|
|
77
|
+
|
|
78
|
+
Arge:
|
|
79
|
+
get_gripper_camera_calib_request (gripper_camera_params_pb2.GripperCameraGetCalibrationRequest) : The command reqeust to get gripper camera calibration
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The GripperCameraGetCalibrationResponse message, which contains the GripperCameraCalibrationProto
|
|
83
|
+
"""
|
|
84
|
+
return self.call(self._stub.GetCamCalib, get_gripper_camera_calib_request,
|
|
85
|
+
error_from_response=common_header_errors, **kwargs)
|
|
86
|
+
|
|
87
|
+
def get_camera_calib_async(self, get_gripper_camera_calib_request, **kwargs):
|
|
88
|
+
"""Async version of get_camera_calib()."""
|
|
89
|
+
return self.call_async(self._stub.GetCamCalib, get_gripper_camera_calib_request,
|
|
90
|
+
error_from_response=common_header_errors, **kwargs)
|
|
91
|
+
|
|
92
|
+
@deprecated(version='5.0', reason='Use get_camera_calib_async() instead.')
|
|
93
|
+
def get_camera_calib_asnyc(self, get_gripper_camera_calib_request, **kwargs):
|
|
94
|
+
return self.get_camera_calib_async(get_gripper_camera_calib_request, **kwargs)
|
bosdyn/client/image.py
CHANGED
|
@@ -204,6 +204,22 @@ def _get_image_value(response):
|
|
|
204
204
|
return response.image_responses
|
|
205
205
|
|
|
206
206
|
|
|
207
|
+
def pixel_format_to_numpy_type(pixel_format):
|
|
208
|
+
if pixel_format == image_pb2.Image.PIXEL_FORMAT_GREYSCALE_U8:
|
|
209
|
+
dtype = np.uint8
|
|
210
|
+
elif pixel_format == image_pb2.Image.PIXEL_FORMAT_RGB_U8:
|
|
211
|
+
dtype = np.uint8
|
|
212
|
+
elif pixel_format == image_pb2.Image.PIXEL_FORMAT_RGBA_U8:
|
|
213
|
+
dtype = np.uint8
|
|
214
|
+
elif pixel_format == image_pb2.Image.PIXEL_FORMAT_DEPTH_U16:
|
|
215
|
+
dtype = np.uint16
|
|
216
|
+
elif pixel_format == image_pb2.Image.PIXEL_FORMAT_GREYSCALE_U16:
|
|
217
|
+
dtype = ">u2" # Big endian
|
|
218
|
+
else:
|
|
219
|
+
raise Exception('Image PixelFormat type {} not supported'.format(pixel_format))
|
|
220
|
+
return dtype
|
|
221
|
+
|
|
222
|
+
|
|
207
223
|
def write_pgm_or_ppm(image_response, filename="", filepath=".", include_pixel_format=False):
|
|
208
224
|
"""Write raw data from image_response to a PGM file.
|
|
209
225
|
|