Skip to content

Commit efa63e1

Browse files
Release v5.0.1 of Boston Dynamics Spot SDK (#15)
Co-authored-by: Boston Dynamics SDK Publisher <[email protected]>
1 parent 6d8255c commit efa63e1

21 files changed

+921
-67
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The official Spot SDK documentation also contains information relevant to the C+
2121
- [Payload developer documentation](https://dev.bostondynamics.com/docs/payload/readme). Payloads add additional sensing, communication, and control capabilities beyond what the base platform provides. The Payload ICD covers the mechanical, electrical, and software interfaces that Spot supports.
2222
- [Spot API protocol definition](https://dev.bostondynamics.com/docs/protos/readme). This reference guide covers the details of the protocol applications used to communicate to Spot. Application developers who wish to use a language other than Python can implement clients that speak the protocol.
2323

24-
This is version 5.0.0 of the C++ SDK. Please review the [Release Notes](docs/cpp_release_notes.md) to see what has changed.
24+
This is version 5.0.1 of the C++ SDK. Please review the [Release Notes](docs/cpp_release_notes.md) to see what has changed.
2525

2626
## Contents
2727

cpp/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# This file is autogenerated.
88

99
cmake_minimum_required (VERSION 3.10.2)
10-
project (bosdyn VERSION 5.0.0)
10+
project (bosdyn VERSION 5.0.1)
1111

1212
# Dependencies:
1313
find_package(Protobuf REQUIRED)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Copyright (c) 2023 Boston Dynamics, Inc. All rights reserved.
3+
*
4+
* Downloading, reproducing, distributing or otherwise using the SDK Software
5+
* is subject to the terms and conditions of the Boston Dynamics Software
6+
* Development Kit License (20191101-BDSDK-SL).
7+
*/
8+
9+
10+
#include "service_wait.h"
11+
#include "bosdyn/common/success_condition.h"
12+
13+
namespace bosdyn::directory::util {
14+
15+
// Error category for WaitError.
16+
class WaitCategory : public std::error_category {
17+
public:
18+
const char* name() const noexcept override { return "wait_error"; }
19+
std::string message(int ev) const override {
20+
switch (static_cast<WaitError>(ev)) {
21+
case WaitError::kSuccess:
22+
return "Success";
23+
case WaitError::kTimeout:
24+
return "Timeout";
25+
}
26+
return "Unknown";
27+
}
28+
std::error_condition default_error_condition(int ev) const noexcept override {
29+
return std::error_condition(ev, *this);
30+
}
31+
bool equivalent(int code, const std::error_condition& condition) const noexcept override {
32+
if (condition == SuccessCondition::Success) {
33+
return code == static_cast<int>(WaitError::kSuccess);
34+
}
35+
return false;
36+
}
37+
};
38+
39+
const WaitCategory s_wait_category{};
40+
41+
std::error_code make_error_code(WaitError value) {
42+
return {static_cast<int>(value), s_wait_category};
43+
}
44+
45+
WaitResult WaitForAllServices(const std::set<std::string>& service_names,
46+
bosdyn::client::Robot& robot, const bosdyn::common::Duration& timeout,
47+
const bosdyn::common::Duration& interval) {
48+
auto start_time = bosdyn::common::NowTimePoint();
49+
auto end_time = start_time + timeout;
50+
WaitResult result;
51+
52+
do {
53+
bosdyn::client::Result<std::vector<::bosdyn::api::ServiceEntry>> list_result =
54+
robot.ListServices();
55+
if (!list_result) {
56+
if (list_result.status.code() != RetryableRPCCondition::Retryable) {
57+
// Return immediately if the error is not retryable.
58+
result.status = list_result.status;
59+
return result;
60+
}
61+
// Retryable error, keep trying.
62+
} else {
63+
result.missing_services = service_names;
64+
for (const api::ServiceEntry& entry : list_result.response) {
65+
result.missing_services.erase(entry.name());
66+
}
67+
if (result.missing_services.empty()) {
68+
result.status = {WaitError::kSuccess};
69+
return result;
70+
}
71+
}
72+
std::this_thread::sleep_for(interval);
73+
} while (bosdyn::common::NowTimePoint() < end_time);
74+
75+
// Failed to find all services. Return the ones that are missing according to the latest set of
76+
// found services.
77+
result.status = {WaitError::kTimeout};
78+
return result;
79+
}
80+
81+
} // namespace bosdyn::directory::util
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright (c) 2023 Boston Dynamics, Inc. All rights reserved.
3+
*
4+
* Downloading, reproducing, distributing or otherwise using the SDK Software
5+
* is subject to the terms and conditions of the Boston Dynamics Software
6+
* Development Kit License (20191101-BDSDK-SL).
7+
*/
8+
9+
10+
#pragma once
11+
12+
#include <set>
13+
14+
#include "bosdyn/client/robot/robot.h"
15+
#include "bosdyn/common/time.h"
16+
17+
namespace bosdyn::directory::util {
18+
19+
enum class WaitError {
20+
kSuccess = 0,
21+
kTimeout = 1,
22+
};
23+
24+
struct WaitResult {
25+
// Normally a WaitError, but may be an RPC failure.
26+
bosdyn::common::Status status;
27+
// List of any services that were missing when a timeout was hit.
28+
std::set<std::string> missing_services;
29+
};
30+
31+
/**
32+
* Block until all requested services are registered with the directory. Useful during startup to
33+
* wait until the services you depend on are all running before you start creating clients.
34+
*
35+
* The status code will normally be a WaitError for success or timeout. If it fails with a timeout,
36+
* the services that were not yet registered will be returned in missing_services. If the calls to
37+
* list the services fail in an un-retryable manner, that error code will be returned in the status
38+
* instead.
39+
*/
40+
WaitResult WaitForAllServices(
41+
const std::set<std::string>& service_names, bosdyn::client::Robot& robot,
42+
const bosdyn::common::Duration& timeout,
43+
const bosdyn::common::Duration& interval = std::chrono::milliseconds(100));
44+
45+
std::error_code make_error_code(WaitError value);
46+
47+
} // namespace bosdyn::directory::util
48+
49+
namespace std {
50+
template <>
51+
struct is_error_code_enum<bosdyn::directory::util::WaitError> : true_type {};
52+
} // namespace std

cpp/bosdyn/client/directory_registration/directory_registration_helpers.cpp

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,27 @@ namespace client {
2020
DirectoryRegistrationKeepAlive::DirectoryRegistrationKeepAlive(
2121
DirectoryRegistrationClient* directory_registration_client,
2222
const ::bosdyn::api::ServiceEntry& service_entry, const ::bosdyn::api::Endpoint& endpoint,
23-
::bosdyn::common::Duration rpc_interval, FaultClient* fault_client)
23+
::bosdyn::common::Duration rpc_interval, FaultClient* fault_client,
24+
std::function<ErrorCallbackResult(const ::bosdyn::common::Status&)> error_callback,
25+
::bosdyn::common::Duration registration_initial_retry_interval)
2426
: m_directory_registration_client(directory_registration_client),
2527
m_service_entry(service_entry),
2628
m_endpoint(endpoint),
2729
m_registration_interval(rpc_interval),
30+
m_registration_initial_retry_interval(registration_initial_retry_interval),
2831
m_thread(),
32+
m_periodic_thread_helper(std::make_unique<PeriodicThreadHelper>()),
2933
m_thread_stopped(true),
30-
m_fault_client(fault_client) {}
34+
m_fault_client(fault_client),
35+
m_error_callback(error_callback) {}
36+
3137

3238
DirectoryRegistrationKeepAlive::~DirectoryRegistrationKeepAlive() {
3339
Shutdown();
3440
Unregister().IgnoreError();
3541
}
3642

3743
void DirectoryRegistrationKeepAlive::Start(const std::vector<std::string>& fault_attributes) {
38-
m_should_exit = false;
3944
m_thread_stopped = false;
4045

4146
// Populate the registration fault with default values.
@@ -61,16 +66,20 @@ void DirectoryRegistrationKeepAlive::Start(const std::vector<std::string>& fault
6166
bool DirectoryRegistrationKeepAlive::IsAlive() const { return !m_thread_stopped; }
6267

6368
void DirectoryRegistrationKeepAlive::Shutdown() {
64-
m_should_exit = true;
65-
m_thread.join();
69+
m_periodic_thread_helper->Stop();
70+
if (m_thread.joinable()) {
71+
m_thread.join();
72+
}
6673
}
6774

6875
UnregisterServiceResultType DirectoryRegistrationKeepAlive::Unregister() {
6976
return m_directory_registration_client->UnregisterService(m_service_entry.name());
7077
}
7178

7279
void DirectoryRegistrationKeepAlive::PeriodicReregisterThreadMethod() {
73-
bool first_time = true;
80+
81+
::bosdyn::common::Duration retry_interval = m_registration_initial_retry_interval;
82+
::bosdyn::common::Duration wait_interval = m_registration_interval;
7483

7584
// Set up register and update requests
7685
::bosdyn::api::RegisterServiceRequest register_service_request;
@@ -81,19 +90,15 @@ void DirectoryRegistrationKeepAlive::PeriodicReregisterThreadMethod() {
8190
update_service_request.mutable_service_entry()->CopyFrom(m_service_entry);
8291

8392
// Continually attempt to register and update the service.
84-
while (!m_should_exit) {
85-
if (!first_time) {
86-
std::this_thread::sleep_for(m_registration_interval);
87-
}
88-
first_time = false;
89-
93+
do {
9094
// Register service.
9195
auto res_register =
9296
m_directory_registration_client->RegisterService(register_service_request);
9397
// Successful registration. Service faults are automatically cleared for the new service
9498
// by the directory registration service.
9599
if (res_register) {
96100
m_registration_fault_active = false;
101+
wait_interval = m_registration_interval;
97102
continue;
98103
}
99104
// If registration failed in a bad way.
@@ -102,6 +107,27 @@ void DirectoryRegistrationKeepAlive::PeriodicReregisterThreadMethod() {
102107
if (m_fault_client && m_fault_client->TriggerServiceFault(m_service_fault)) {
103108
m_registration_fault_active = true;
104109
}
110+
auto action = ErrorCallbackResult::kResumeNormalOperation;
111+
if (m_error_callback) {
112+
try {
113+
action = m_error_callback(res_register.status);
114+
} catch (const std::exception& e) {
115+
std::cerr << "Exception thrown in error callback: " << e.what() << std::endl;
116+
}
117+
}
118+
if (action == ErrorCallbackResult::kAbort) {
119+
break;
120+
}
121+
if (action == ErrorCallbackResult::kRetryImmediately) {
122+
wait_interval = std::chrono::seconds(0);
123+
} else if (action == ErrorCallbackResult::kRetryWithExponentialBackOff) {
124+
// Exponentially increase the retry interval.
125+
wait_interval = retry_interval;
126+
retry_interval = std::min(retry_interval * 2, m_registration_interval);
127+
} else {
128+
// Default action is to continue with the next iteration.
129+
wait_interval = m_registration_interval;
130+
}
105131
continue;
106132
}
107133

@@ -112,6 +138,27 @@ void DirectoryRegistrationKeepAlive::PeriodicReregisterThreadMethod() {
112138
if (m_fault_client && m_fault_client->TriggerServiceFault(m_service_fault)) {
113139
m_registration_fault_active = true;
114140
}
141+
auto action = ErrorCallbackResult::kResumeNormalOperation;
142+
if (m_error_callback) {
143+
try {
144+
action = m_error_callback(res_register.status);
145+
} catch (const std::exception& e) {
146+
std::cerr << "Exception thrown in error callback: " << e.what() << std::endl;
147+
}
148+
}
149+
if (action == ErrorCallbackResult::kAbort) {
150+
break;
151+
}
152+
if (action == ErrorCallbackResult::kRetryImmediately) {
153+
wait_interval = std::chrono::seconds(0);
154+
} else if (action == ErrorCallbackResult::kRetryWithExponentialBackOff) {
155+
// Exponentially increase the retry interval.
156+
wait_interval = retry_interval;
157+
retry_interval = std::min(retry_interval * 2, m_registration_interval);
158+
} else {
159+
// Default action is to continue with the next iteration.
160+
wait_interval = m_registration_interval;
161+
}
115162
continue;
116163
}
117164

@@ -121,9 +168,12 @@ void DirectoryRegistrationKeepAlive::PeriodicReregisterThreadMethod() {
121168
m_registration_fault_active = false;
122169
}
123170
}
124-
}
171+
wait_interval = m_registration_interval;
172+
retry_interval = m_registration_initial_retry_interval;
173+
} while (m_periodic_thread_helper->WaitForInterval(wait_interval));
125174

126175
m_thread_stopped = true;
176+
m_periodic_thread_helper->Stop();
127177
}
128178

129179
} // namespace client

cpp/bosdyn/client/directory_registration/directory_registration_helpers.h

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@
99

1010
#pragma once
1111

12-
#include <mutex>
12+
#include <functional>
13+
#include <memory>
14+
#include <string>
1315
#include <thread>
16+
#include <vector>
1417

1518
#include "directory_registration_client.h"
19+
#include "bosdyn/client/error_callback/error_callback_result.h"
1620
#include "bosdyn/client/fault/fault_client.h"
1721
#include "bosdyn/client/service_client/common_result_types.h"
22+
#include "bosdyn/client/util/periodic_thread_helper.h"
1823

1924
#include <bosdyn/api/directory.pb.h>
2025

@@ -32,7 +37,9 @@ class DirectoryRegistrationKeepAlive {
3237
DirectoryRegistrationClient* directory_registration_client,
3338
const ::bosdyn::api::ServiceEntry& service_entry, const ::bosdyn::api::Endpoint& endpoint,
3439
::bosdyn::common::Duration rpc_interval = std::chrono::seconds(30),
35-
FaultClient* fault_client = nullptr);
40+
FaultClient* fault_client = nullptr,
41+
std::function<ErrorCallbackResult(const ::bosdyn::common::Status&)> error_callback = {},
42+
::bosdyn::common::Duration registration_initial_retry_interval = std::chrono::seconds(1));
3643

3744
~DirectoryRegistrationKeepAlive();
3845

@@ -52,18 +59,22 @@ class DirectoryRegistrationKeepAlive {
5259
// Background thread that continually registers/updates the service.
5360
void PeriodicReregisterThreadMethod();
5461

62+
// Wait for the refresh interval and coordinate with the destructor on the condition variable.
63+
bool WaitForInterval(::bosdyn::common::Duration interval);
64+
5565
DirectoryRegistrationClient* m_directory_registration_client = nullptr;
5666

5767
::bosdyn::api::ServiceEntry m_service_entry;
5868
::bosdyn::api::Endpoint m_endpoint;
5969

6070
// Time between registration/update calls. Should be shorter than the liveness timeout.
6171
::bosdyn::common::Duration m_registration_interval;
72+
::bosdyn::common::Duration m_registration_initial_retry_interval;
6273

6374
std::thread m_thread;
6475

65-
// Thread should exit.
66-
bool m_should_exit;
76+
// Object used to coordinate the lifecycle and timing of the periodic thread.
77+
std::unique_ptr<PeriodicThreadHelper> m_periodic_thread_helper;
6778

6879
// Is thread stopped.
6980
bool m_thread_stopped;
@@ -76,6 +87,9 @@ class DirectoryRegistrationKeepAlive {
7687

7788
// Bool to track if there is a failure.
7889
bool m_registration_fault_active;
90+
91+
// Callback to invoke in the event of a periodic reregistration failure.
92+
std::function<ErrorCallbackResult(const ::bosdyn::common::Status&)> m_error_callback;
7993
};
8094

8195
} // namespace client
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright (c) 2023 Boston Dynamics, Inc. All rights reserved.
3+
*
4+
* Downloading, reproducing, distributing or otherwise using the SDK Software
5+
* is subject to the terms and conditions of the Boston Dynamics Software
6+
* Development Kit License (20191101-BDSDK-SL).
7+
*/
8+
9+
10+
#pragma once
11+
12+
namespace bosdyn {
13+
namespace client {
14+
15+
enum class ErrorCallbackResult {
16+
kDefaultAction,
17+
kRetryImmediately,
18+
kRetryWithExponentialBackOff,
19+
kResumeNormalOperation,
20+
kAbort,
21+
};
22+
23+
} // namespace client
24+
} // namespace bosdyn

0 commit comments

Comments
 (0)