Skip to content

Commit f17da97

Browse files
committed
refactor(franka): use thread safe value class
1 parent e71a9c5 commit f17da97

3 files changed

Lines changed: 123 additions & 63 deletions

File tree

‎extensions/rcs_fr3/src/hw/Franka.cpp‎

Lines changed: 39 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ FrankaState* Franka::get_state() {
8686
if (this->running_controller.load() == Controller::none) {
8787
current_robot_state = this->robot.readOnce();
8888
} else {
89-
std::lock_guard<std::mutex> lock(this->interpolator_mutex);
90-
current_robot_state = this->curr_state;
89+
current_robot_state = this->curr_state.load();
9190
}
9291
auto* state = new FrankaState();
9392
state->robot_state = current_robot_state;
@@ -111,12 +110,10 @@ common::Pose Franka::get_cartesian_position() {
111110
this->check_for_background_errors();
112111
franka::RobotState robot_state;
113112
if (this->running_controller.load() == Controller::none) {
114-
this->curr_state = this->robot.readOnce();
115-
robot_state = this->curr_state;
113+
robot_state = this->robot.readOnce();
114+
this->curr_state.store(robot_state);
116115
} else {
117-
this->interpolator_mutex.lock();
118-
robot_state = this->curr_state;
119-
this->interpolator_mutex.unlock();
116+
robot_state = this->curr_state.load();
120117
}
121118
return GetTCPInBaseFrame(robot_state, this->m_cfg.tcp_offset);
122119
}
@@ -125,11 +122,10 @@ common::Pose Franka::get_cartesian_flange_position() {
125122
this->check_for_background_errors();
126123
franka::RobotState robot_state;
127124
if (this->running_controller.load() == Controller::none) {
128-
this->curr_state = this->robot.readOnce();
129-
robot_state = this->curr_state;
125+
robot_state = this->robot.readOnce();
126+
this->curr_state.store(robot_state);
130127
} else {
131-
std::lock_guard<std::mutex> lock(this->interpolator_mutex);
132-
robot_state = this->curr_state;
128+
robot_state = this->curr_state.load();
133129
}
134130
return GetFlangeInBaseFrame(robot_state);
135131
}
@@ -146,16 +142,14 @@ void Franka::set_joint_position(const common::VectorXd& q) {
146142

147143
common::VectorXd Franka::get_joint_position() {
148144
this->check_for_background_errors();
149-
common::Vector7d joints;
145+
franka::RobotState robot_state;
150146
if (this->running_controller.load() == Controller::none) {
151-
this->curr_state = this->robot.readOnce();
152-
joints = common::Vector7d(this->curr_state.q.data());
147+
robot_state = this->robot.readOnce();
148+
this->curr_state.store(robot_state);
153149
} else {
154-
this->interpolator_mutex.lock();
155-
joints = common::Vector7d(this->curr_state.q.data());
156-
this->interpolator_mutex.unlock();
150+
robot_state = this->curr_state.load();
157151
}
158-
return joints;
152+
return common::Vector7d(robot_state.q.data());
159153
}
160154

161155
void Franka::set_guiding_mode(bool x, bool y, bool z, bool roll, bool pitch,
@@ -219,25 +213,25 @@ void Franka::controller_set_joint_position(const common::Vector7d& desired_q) {
219213
this->interpolator_mutex.lock();
220214
}
221215

216+
franka::RobotState state_now = this->curr_state.load();
217+
const common::Vector7d q_now =
218+
Eigen::Map<common::Vector7d>(state_now.q.data());
219+
222220
const bool approach_on_start =
223221
starting_fresh && this->m_cfg.blocking_move_on_start;
224222
double approach_time = -1.0;
225223
if (approach_on_start) {
226224
const double kMinApproachTime = 0.3; // s
227225
const double kMaxApproachTime = 5.0; // s
228-
const common::Vector7d q_now =
229-
Eigen::Map<common::Vector7d>(this->curr_state.q.data());
230226
const double max_gap = (desired_q - q_now).cwiseAbs().maxCoeff();
231227
const double speed = std::max(this->m_cfg.approach_joint_speed, 1e-6);
232228
approach_time =
233229
std::clamp(max_gap / speed, kMinApproachTime, kMaxApproachTime);
234230
}
235231

236232
this->joint_interpolator.reset(
237-
this->controller_time,
238-
Eigen::Map<common::Vector7d>(this->curr_state.q.data()), desired_q,
239-
this->m_active_policy_rate, traj_rate, traj_interpolation_time_fraction,
240-
approach_time);
233+
this->controller_time, q_now, desired_q, this->m_active_policy_rate,
234+
traj_rate, traj_interpolation_time_fraction, approach_time);
241235

242236
// if not thread is running, then start
243237
if (starting_fresh) {
@@ -256,16 +250,12 @@ void Franka::controller_set_joint_position(const common::Vector7d& desired_q) {
256250
const auto start = std::chrono::steady_clock::now();
257251
while (true) {
258252
this->check_for_background_errors();
259-
common::Vector7d q;
260-
common::Vector7d dq;
261-
{
262-
std::lock_guard<std::mutex> lock(this->interpolator_mutex);
263-
q = Eigen::Map<common::Vector7d>(this->curr_state.q.data());
264-
dq = Eigen::Map<common::Vector7d>(this->curr_state.dq.data());
265-
}
253+
franka::RobotState state = this->curr_state.load();
254+
const common::Vector7d q = Eigen::Map<common::Vector7d>(state.q.data());
255+
const common::Vector7d dq = Eigen::Map<common::Vector7d>(state.dq.data());
266256
const double elapsed = std::chrono::duration<double>(
267257
std::chrono::steady_clock::now() - start)
268-
.count();
258+
.count();
269259
const double pos_err = (q - desired_q).cwiseAbs().maxCoeff();
270260
const double vel = dq.cwiseAbs().maxCoeff();
271261
if (elapsed >= approach_time && pos_err < pos_tol && vel < vel_tol) {
@@ -280,18 +270,15 @@ void Franka::controller_set_joint_position(const common::Vector7d& desired_q) {
280270
}
281271

282272
void Franka::check_for_background_errors() {
283-
std::lock_guard<std::mutex> lock(this->exception_mutex);
284-
if (this->background_exception) {
273+
std::exception_ptr ex = this->background_exception.load_and_clear();
274+
if (ex) {
285275
this->stop_control_thread();
286-
std::exception_ptr ex = this->background_exception;
287-
this->background_exception = nullptr;
288276
std::rethrow_exception(ex);
289277
}
290278
}
291279

292280
void Franka::clear_background_error() {
293-
std::lock_guard<std::mutex> lock(this->exception_mutex);
294-
this->background_exception = nullptr;
281+
this->background_exception.store(nullptr);
295282
}
296283

297284
void Franka::osc_set_cartesian_position(
@@ -308,10 +295,7 @@ void Franka::osc_set_cartesian_position(
308295
if (starting_fresh) {
309296
this->controller_time = 0.0;
310297
this->m_active_policy_rate = this->m_cfg.policy_rate;
311-
{
312-
std::lock_guard<std::mutex> lock(this->interpolator_mutex);
313-
this->curr_state = this->robot.readOnce();
314-
}
298+
this->curr_state.store(this->robot.readOnce());
315299
this->traj_interpolator = common::LinearPoseTrajInterpolator();
316300
} else if (this->running_controller.load() != Controller::osc) {
317301
throw std::runtime_error(
@@ -323,7 +307,7 @@ void Franka::osc_set_cartesian_position(
323307
}
324308

325309
common::Pose curr_pose =
326-
GetTCPInBaseFrame(this->curr_state, this->m_cfg.tcp_offset);
310+
GetTCPInBaseFrame(this->curr_state.load(), this->m_cfg.tcp_offset);
327311

328312
const bool approach_on_start =
329313
starting_fresh && this->m_cfg.blocking_move_on_start;
@@ -375,13 +359,8 @@ void Franka::osc_set_cartesian_position(
375359
desired_pose_EE_in_base_frame.quaternion().normalized();
376360
while (true) {
377361
this->check_for_background_errors();
378-
franka::RobotState state;
379-
common::Vector7d dq;
380-
{
381-
std::lock_guard<std::mutex> lock(this->interpolator_mutex);
382-
state = this->curr_state;
383-
dq = Eigen::Map<common::Vector7d>(this->curr_state.dq.data());
384-
}
362+
franka::RobotState state = this->curr_state.load();
363+
const common::Vector7d dq = Eigen::Map<common::Vector7d>(state.dq.data());
385364
const common::Pose meas_pose =
386365
GetTCPInBaseFrame(state, this->m_cfg.tcp_offset);
387366
const double pos_err = (target_p - meas_pose.translation()).norm();
@@ -391,7 +370,7 @@ void Franka::osc_set_cartesian_position(
391370
const double vel = dq.cwiseAbs().maxCoeff();
392371
const double elapsed = std::chrono::duration<double>(
393372
std::chrono::steady_clock::now() - start)
394-
.count();
373+
.count();
395374
if (elapsed >= approach_time && pos_err < pos_tol && ori_err < ori_tol &&
396375
vel < vel_tol) {
397376
break;
@@ -494,8 +473,9 @@ void Franka::osc() {
494473
int policy_rate = 20;
495474
int traj_rate = 500;
496475

476+
this->curr_state.store(robot_state);
477+
497478
this->interpolator_mutex.lock();
498-
this->curr_state = robot_state;
499479
this->controller_time += period.toSec();
500480
this->traj_interpolator.next_step(this->controller_time,
501481
desired_pos_EE_in_base_frame,
@@ -638,8 +618,7 @@ void Franka::osc() {
638618
return tau_d_rate_limited;
639619
});
640620
} catch (...) {
641-
std::lock_guard<std::mutex> lock(this->exception_mutex);
642-
this->background_exception = std::current_exception();
621+
this->background_exception.store(std::current_exception());
643622
}
644623

645624
// Ensure we mark the controller as stopped so we can restart later
@@ -685,8 +664,9 @@ void Franka::joint_controller() {
685664

686665
common::Vector7d desired_q;
687666

667+
this->curr_state.store(robot_state);
668+
688669
this->interpolator_mutex.lock();
689-
this->curr_state = robot_state;
690670
this->controller_time += period.toSec();
691671
this->joint_interpolator.next_step(this->controller_time, desired_q);
692672
this->interpolator_mutex.unlock();
@@ -734,8 +714,7 @@ void Franka::joint_controller() {
734714
return tau_d_rate_limited;
735715
});
736716
} catch (...) {
737-
std::lock_guard<std::mutex> lock(this->exception_mutex);
738-
this->background_exception = std::current_exception();
717+
this->background_exception.store(std::current_exception());
739718
}
740719

741720
this->running_controller.store(Controller::none);
@@ -767,8 +746,9 @@ void Franka::zero_torque_controller() {
767746
try {
768747
this->robot.control([&](const franka::RobotState& robot_state,
769748
franka::Duration period) -> franka::Torques {
749+
this->curr_state.store(robot_state);
750+
770751
this->interpolator_mutex.lock();
771-
this->curr_state = robot_state;
772752
this->controller_time += period.toSec();
773753
this->interpolator_mutex.unlock();
774754
if (this->running_controller.load() == Controller::none) {
@@ -778,8 +758,7 @@ void Franka::zero_torque_controller() {
778758
return franka::Torques({0, 0, 0, 0, 0, 0, 0});
779759
});
780760
} catch (...) {
781-
std::lock_guard<std::mutex> lock(this->exception_mutex);
782-
this->background_exception = std::current_exception();
761+
this->background_exception.store(std::current_exception());
783762
}
784763

785764
this->running_controller.store(Controller::none);

‎extensions/rcs_fr3/src/hw/Franka.h‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,10 @@ class Franka : public common::Robot {
119119
// the interpolation window stays consistent for the controller's lifetime.
120120
int m_active_policy_rate = 20;
121121
common::LinearJointPositionTrajInterpolator joint_interpolator;
122-
franka::RobotState curr_state;
122+
common::ThreadSafeValue<franka::RobotState> curr_state;
123123
std::mutex interpolator_mutex;
124124
std::atomic<Controller> running_controller{Controller::none};
125-
std::exception_ptr background_exception = nullptr;
126-
std::mutex exception_mutex;
125+
common::ThreadSafeValue<std::exception_ptr> background_exception;
127126
void osc();
128127
void joint_controller();
129128
void zero_torque_controller();

‎include/rcs/utils.h‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
#define RCS_UTIL_H
33

44
#include <Eigen/Eigen>
5+
#include <array>
6+
#include <deque>
57
#include <memory>
8+
#include <mutex>
9+
#include <utility>
10+
#include <vector>
611

712
namespace rcs {
813
namespace common {
@@ -39,6 +44,83 @@ void bootstrap_egl(std::uintptr_t fn_addr, std::uintptr_t display,
3944
std::uintptr_t context);
4045
void ensure_current();
4146

47+
/***
48+
* @brief thread safe holder for a single value, e.g. to hand data from a
49+
* non-realtime thread to a control loop
50+
*/
51+
template <typename T>
52+
class ThreadSafeValue {
53+
private:
54+
T value_;
55+
mutable std::mutex mutex_;
56+
57+
public:
58+
ThreadSafeValue() = default;
59+
explicit ThreadSafeValue(const T& value) : value_(value) {}
60+
61+
void store(const T& value) {
62+
std::lock_guard<std::mutex> lock(mutex_);
63+
value_ = value;
64+
}
65+
66+
T load() const {
67+
std::lock_guard<std::mutex> lock(mutex_);
68+
return value_;
69+
}
70+
71+
/***
72+
* @brief atomically read the value and reset it to a default constructed one
73+
*/
74+
T load_and_clear() {
75+
std::lock_guard<std::mutex> lock(mutex_);
76+
return std::exchange(value_, T{});
77+
}
78+
};
79+
80+
/***
81+
* @brief thread safe ring buffer of fixed maximum size, drops the oldest
82+
* element once the buffer is full
83+
*/
84+
template <typename T>
85+
class ThreadSafeFixedBuffer {
86+
private:
87+
std::deque<T> deque_;
88+
size_t max_size_;
89+
mutable std::mutex mutex_;
90+
91+
public:
92+
explicit ThreadSafeFixedBuffer(size_t max_size) : max_size_(max_size) {}
93+
94+
void push_back(const T& value) {
95+
std::lock_guard<std::mutex> lock(mutex_);
96+
97+
deque_.push_back(value);
98+
if (deque_.size() > max_size_) {
99+
deque_.pop_front();
100+
}
101+
}
102+
103+
T get(size_t index) const {
104+
std::lock_guard<std::mutex> lock(mutex_);
105+
return deque_[index];
106+
}
107+
108+
size_t size() const {
109+
std::lock_guard<std::mutex> lock(mutex_);
110+
return deque_.size();
111+
}
112+
113+
void clear() {
114+
std::lock_guard<std::mutex> lock(mutex_);
115+
deque_.clear();
116+
}
117+
118+
std::vector<T> to_vector() const {
119+
std::lock_guard<std::mutex> lock(mutex_);
120+
return std::vector<T>(deque_.begin(), deque_.end());
121+
}
122+
};
123+
42124
} // namespace common
43125
} // namespace rcs
44126

0 commit comments

Comments
 (0)