Skip to content

Commit 2031186

Browse files
authored
Make GPIO controller publishers realtime safe (backport #1807) (#1828)
1 parent b68162c commit 2031186

2 files changed

Lines changed: 145 additions & 64 deletions

File tree

ur_controllers/include/ur_controllers/gpio_controller.hpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include "rclcpp/time.hpp"
5858
#include "rclcpp/duration.hpp"
5959
#include "std_msgs/msg/bool.hpp"
60+
#include "realtime_tools/realtime_publisher.hpp"
6061
#include "ur_controllers/gpio_controller_parameters.hpp"
6162

6263
namespace ur_controllers
@@ -123,9 +124,16 @@ class GPIOController : public controller_interface::ControllerInterface
123124

124125
CallbackReturn on_deactivate(const rclcpp_lifecycle::State& previous_state) override;
125126

127+
CallbackReturn on_cleanup(const rclcpp_lifecycle::State& previous_state) override;
128+
126129
CallbackReturn on_init() override;
127130

128131
private:
132+
// Rejects a service request when the controller is not active.
133+
// Returns true if the controller is active and the request may proceed.
134+
template <typename ResponseT>
135+
bool ensureActive(const ResponseT& resp);
136+
129137
bool setIO(ur_msgs::srv::SetIO::Request::SharedPtr req, ur_msgs::srv::SetIO::Response::SharedPtr resp);
130138

131139
bool setAnalogOutput(ur_msgs::srv::SetAnalogOutput::Request::SharedPtr req,
@@ -174,11 +182,12 @@ class GPIOController : public controller_interface::ControllerInterface
174182
rclcpp::Service<ur_msgs::srv::SetPayload>::SharedPtr set_payload_srv_;
175183
rclcpp::Service<std_srvs::srv::Trigger>::SharedPtr tare_sensor_srv_;
176184

177-
std::shared_ptr<rclcpp::Publisher<ur_msgs::msg::IOStates>> io_pub_;
178-
std::shared_ptr<rclcpp::Publisher<ur_msgs::msg::ToolDataMsg>> tool_data_pub_;
179-
std::shared_ptr<rclcpp::Publisher<ur_dashboard_msgs::msg::RobotMode>> robot_mode_pub_;
180-
std::shared_ptr<rclcpp::Publisher<ur_dashboard_msgs::msg::SafetyMode>> safety_mode_pub_;
181-
std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Bool>> program_state_pub_;
185+
// Publishing in realtime ros2_control loop, so these are wrapped in non-blocking tries to prevent controller overrun
186+
std::shared_ptr<realtime_tools::RealtimePublisher<ur_msgs::msg::IOStates>> io_pub_;
187+
std::shared_ptr<realtime_tools::RealtimePublisher<ur_msgs::msg::ToolDataMsg>> tool_data_pub_;
188+
std::shared_ptr<realtime_tools::RealtimePublisher<ur_dashboard_msgs::msg::RobotMode>> robot_mode_pub_;
189+
std::shared_ptr<realtime_tools::RealtimePublisher<ur_dashboard_msgs::msg::SafetyMode>> safety_mode_pub_;
190+
std::shared_ptr<realtime_tools::RealtimePublisher<std_msgs::msg::Bool>> program_state_pub_;
182191

183192
ur_msgs::msg::IOStates io_msg_;
184193
ur_msgs::msg::ToolDataMsg tool_data_msg_;

ur_controllers/src/gpio_controller.cpp

Lines changed: 131 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,45 @@
3838
#include "ur_controllers/gpio_controller.hpp"
3939

4040
#include <cmath>
41+
#include <lifecycle_msgs/msg/state.hpp>
4142
#include <string>
4243

4344
namespace ur_controllers
4445
{
46+
namespace
47+
{
48+
// Publishes only when `field` changes, and commits the new value into `msg` only on a successful
49+
// try_publish.
50+
// If the publish is dropped (e.g. lock contention), `msg` is left unchanged and the change is retried on the next
51+
// cycle.
52+
template <typename MsgT, typename FieldT>
53+
bool try_publish_on_change(realtime_tools::RealtimePublisher<MsgT>& pub, MsgT& msg, FieldT MsgT::* field,
54+
const FieldT& new_value)
55+
{
56+
if (msg.*field == new_value) {
57+
return true;
58+
}
59+
MsgT candidate = msg;
60+
candidate.*field = new_value;
61+
if (!pub.try_publish(candidate)) {
62+
return false;
63+
}
64+
msg.*field = new_value;
65+
return true;
66+
}
67+
} // namespace
68+
69+
template <typename ResponseT>
70+
bool GPIOController::ensureActive(const ResponseT& resp)
71+
{
72+
if (get_lifecycle_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE) {
73+
RCLCPP_ERROR(get_node()->get_logger(), "Can't accept new requests. Controller is not running.");
74+
resp->success = false;
75+
return false;
76+
}
77+
return true;
78+
}
79+
4580
controller_interface::CallbackReturn GPIOController::on_init()
4681
{
4782
try {
@@ -196,6 +231,55 @@ ur_controllers::GPIOController::on_configure(const rclcpp_lifecycle::State& /*pr
196231
// get parameters from the listener in case they were updated
197232
params_ = param_listener_->get_params();
198233

234+
try {
235+
auto qos_latched = rclcpp::SystemDefaultsQoS();
236+
qos_latched.transient_local();
237+
qos_latched.reliable();
238+
// register publishers outside the realtime update loop
239+
io_pub_ = std::make_shared<realtime_tools::RealtimePublisher<ur_msgs::msg::IOStates>>(
240+
get_node()->create_publisher<ur_msgs::msg::IOStates>("~/io_states", rclcpp::SystemDefaultsQoS()));
241+
242+
tool_data_pub_ = std::make_shared<realtime_tools::RealtimePublisher<ur_msgs::msg::ToolDataMsg>>(
243+
get_node()->create_publisher<ur_msgs::msg::ToolDataMsg>("~/tool_data", rclcpp::SystemDefaultsQoS()));
244+
245+
robot_mode_pub_ = std::make_shared<realtime_tools::RealtimePublisher<ur_dashboard_msgs::msg::RobotMode>>(
246+
get_node()->create_publisher<ur_dashboard_msgs::msg::RobotMode>("~/robot_mode", qos_latched));
247+
248+
safety_mode_pub_ = std::make_shared<realtime_tools::RealtimePublisher<ur_dashboard_msgs::msg::SafetyMode>>(
249+
get_node()->create_publisher<ur_dashboard_msgs::msg::SafetyMode>("~/safety_mode", qos_latched));
250+
251+
program_state_pub_ = std::make_shared<realtime_tools::RealtimePublisher<std_msgs::msg::Bool>>(
252+
get_node()->create_publisher<std_msgs::msg::Bool>("~/robot_program_running", qos_latched));
253+
254+
// register services outside the realtime update loop; calls are rejected while the controller is not active
255+
set_io_srv_ = get_node()->create_service<ur_msgs::srv::SetIO>(
256+
"~/set_io", std::bind(&GPIOController::setIO, this, std::placeholders::_1, std::placeholders::_2));
257+
set_analog_output_srv_ = get_node()->create_service<ur_msgs::srv::SetAnalogOutput>(
258+
"~/set_analog_output",
259+
std::bind(&GPIOController::setAnalogOutput, this, std::placeholders::_1, std::placeholders::_2));
260+
261+
set_speed_slider_srv_ = get_node()->create_service<ur_msgs::srv::SetSpeedSliderFraction>(
262+
"~/set_speed_slider",
263+
std::bind(&GPIOController::setSpeedSlider, this, std::placeholders::_1, std::placeholders::_2));
264+
265+
resend_robot_program_srv_ = get_node()->create_service<std_srvs::srv::Trigger>(
266+
"~/resend_robot_program",
267+
std::bind(&GPIOController::resendRobotProgram, this, std::placeholders::_1, std::placeholders::_2));
268+
269+
hand_back_control_srv_ = get_node()->create_service<std_srvs::srv::Trigger>(
270+
"~/hand_back_control",
271+
std::bind(&GPIOController::handBackControl, this, std::placeholders::_1, std::placeholders::_2));
272+
273+
set_payload_srv_ = get_node()->create_service<ur_msgs::srv::SetPayload>(
274+
"~/set_payload", std::bind(&GPIOController::setPayload, this, std::placeholders::_1, std::placeholders::_2));
275+
276+
tare_sensor_srv_ = get_node()->create_service<std_srvs::srv::Trigger>(
277+
"~/zero_ftsensor",
278+
std::bind(&GPIOController::zeroFTSensor, this, std::placeholders::_1, std::placeholders::_2));
279+
} catch (...) {
280+
return LifecycleNodeInterface::CallbackReturn::ERROR;
281+
}
282+
199283
return LifecycleNodeInterface::CallbackReturn::SUCCESS;
200284
}
201285

@@ -226,7 +310,7 @@ void GPIOController::publishIO()
226310
static_cast<uint8_t>(state_interfaces_[i + StateInterfaces::ANALOG_IO_TYPES + 2].get_optional().value_or(0.0));
227311
}
228312

229-
io_pub_->publish(io_msg_);
313+
io_pub_->try_publish(io_msg_);
230314
}
231315

232316
void GPIOController::publishToolData()
@@ -247,38 +331,27 @@ void GPIOController::publishToolData()
247331
static_cast<float>(state_interfaces_[StateInterfaces::TOOL_OUTPUT_CURRENT].get_optional().value_or(0.0));
248332
tool_data_msg_.tool_temperature =
249333
static_cast<float>(state_interfaces_[StateInterfaces::TOOL_TEMPERATURE].get_optional().value_or(0.0));
250-
tool_data_pub_->publish(tool_data_msg_);
334+
tool_data_pub_->try_publish(tool_data_msg_);
251335
}
252336

253337
void GPIOController::publishRobotMode()
254338
{
255339
auto robot_mode = static_cast<int8_t>(state_interfaces_[StateInterfaces::ROBOT_MODE].get_optional().value_or(0.0));
256-
257-
if (robot_mode_msg_.mode != robot_mode) {
258-
robot_mode_msg_.mode = robot_mode;
259-
robot_mode_pub_->publish(robot_mode_msg_);
260-
}
340+
try_publish_on_change(*robot_mode_pub_, robot_mode_msg_, &ur_dashboard_msgs::msg::RobotMode::mode, robot_mode);
261341
}
262342

263343
void GPIOController::publishSafetyMode()
264344
{
265345
auto safety_mode = static_cast<uint8_t>(state_interfaces_[StateInterfaces::SAFETY_MODE].get_optional().value_or(0.0));
266-
267-
if (safety_mode_msg_.mode != safety_mode) {
268-
safety_mode_msg_.mode = safety_mode;
269-
safety_mode_pub_->publish(safety_mode_msg_);
270-
}
346+
try_publish_on_change(*safety_mode_pub_, safety_mode_msg_, &ur_dashboard_msgs::msg::SafetyMode::mode, safety_mode);
271347
}
272348

273349
void GPIOController::publishProgramRunning()
274350
{
275351
auto program_running_value =
276352
static_cast<uint8_t>(state_interfaces_[StateInterfaces::PROGRAM_RUNNING].get_optional().value_or(0.0));
277353
bool program_running = program_running_value == 1.0 ? true : false;
278-
if (program_running_msg_.data != program_running) {
279-
program_running_msg_.data = program_running;
280-
program_state_pub_->publish(program_running_msg_);
281-
}
354+
try_publish_on_change(*program_state_pub_, program_running_msg_, &std_msgs::msg::Bool::data, program_running);
282355
}
283356

284357
controller_interface::CallbackReturn
@@ -289,63 +362,34 @@ ur_controllers::GPIOController::on_activate(const rclcpp_lifecycle::State& /*pre
289362
std::this_thread::sleep_for(std::chrono::milliseconds(50));
290363
}
291364

292-
try {
293-
auto qos_latched = rclcpp::SystemDefaultsQoS();
294-
qos_latched.transient_local();
295-
qos_latched.reliable();
296-
// register publisher
297-
io_pub_ = get_node()->create_publisher<ur_msgs::msg::IOStates>("~/io_states", rclcpp::SystemDefaultsQoS());
298-
299-
tool_data_pub_ =
300-
get_node()->create_publisher<ur_msgs::msg::ToolDataMsg>("~/tool_data", rclcpp::SystemDefaultsQoS());
301-
302-
robot_mode_pub_ = get_node()->create_publisher<ur_dashboard_msgs::msg::RobotMode>("~/robot_mode", qos_latched);
303-
304-
safety_mode_pub_ = get_node()->create_publisher<ur_dashboard_msgs::msg::SafetyMode>("~/safety_mode", qos_latched);
305-
306-
program_state_pub_ = get_node()->create_publisher<std_msgs::msg::Bool>("~/robot_program_running", qos_latched);
307-
set_io_srv_ = get_node()->create_service<ur_msgs::srv::SetIO>(
308-
"~/set_io", std::bind(&GPIOController::setIO, this, std::placeholders::_1, std::placeholders::_2));
309-
set_analog_output_srv_ = get_node()->create_service<ur_msgs::srv::SetAnalogOutput>(
310-
"~/set_analog_output",
311-
std::bind(&GPIOController::setAnalogOutput, this, std::placeholders::_1, std::placeholders::_2));
312-
313-
set_speed_slider_srv_ = get_node()->create_service<ur_msgs::srv::SetSpeedSliderFraction>(
314-
"~/set_speed_slider",
315-
std::bind(&GPIOController::setSpeedSlider, this, std::placeholders::_1, std::placeholders::_2));
316-
317-
resend_robot_program_srv_ = get_node()->create_service<std_srvs::srv::Trigger>(
318-
"~/resend_robot_program",
319-
std::bind(&GPIOController::resendRobotProgram, this, std::placeholders::_1, std::placeholders::_2));
320-
321-
hand_back_control_srv_ = get_node()->create_service<std_srvs::srv::Trigger>(
322-
"~/hand_back_control",
323-
std::bind(&GPIOController::handBackControl, this, std::placeholders::_1, std::placeholders::_2));
324-
325-
set_payload_srv_ = get_node()->create_service<ur_msgs::srv::SetPayload>(
326-
"~/set_payload", std::bind(&GPIOController::setPayload, this, std::placeholders::_1, std::placeholders::_2));
327-
328-
tare_sensor_srv_ = get_node()->create_service<std_srvs::srv::Trigger>(
329-
"~/zero_ftsensor",
330-
std::bind(&GPIOController::zeroFTSensor, this, std::placeholders::_1, std::placeholders::_2));
331-
} catch (...) {
332-
return LifecycleNodeInterface::CallbackReturn::ERROR;
333-
}
334365
return LifecycleNodeInterface::CallbackReturn::SUCCESS;
335366
}
336367

337368
controller_interface::CallbackReturn
338369
ur_controllers::GPIOController::on_deactivate(const rclcpp_lifecycle::State& /*previous_state*/)
370+
{
371+
// No teardown. Publishers and services are created in on_configure and released in on_cleanup.
372+
// on_activate/on_deactivate run inside the realtime update loop, so we keep DDS management separate to avoid
373+
// stalling the controller_manager update cycle.
374+
return LifecycleNodeInterface::CallbackReturn::SUCCESS;
375+
}
376+
377+
controller_interface::CallbackReturn
378+
ur_controllers::GPIOController::on_cleanup(const rclcpp_lifecycle::State& /*previous_state*/)
339379
{
340380
try {
341-
// reset publisher
342381
io_pub_.reset();
343382
tool_data_pub_.reset();
344383
robot_mode_pub_.reset();
345384
safety_mode_pub_.reset();
346385
program_state_pub_.reset();
347386
set_io_srv_.reset();
387+
set_analog_output_srv_.reset();
348388
set_speed_slider_srv_.reset();
389+
resend_robot_program_srv_.reset();
390+
hand_back_control_srv_.reset();
391+
set_payload_srv_.reset();
392+
tare_sensor_srv_.reset();
349393
} catch (...) {
350394
return LifecycleNodeInterface::CallbackReturn::ERROR;
351395
}
@@ -354,6 +398,10 @@ ur_controllers::GPIOController::on_deactivate(const rclcpp_lifecycle::State& /*p
354398

355399
bool GPIOController::setIO(ur_msgs::srv::SetIO::Request::SharedPtr req, ur_msgs::srv::SetIO::Response::SharedPtr resp)
356400
{
401+
if (!ensureActive(resp)) {
402+
return false;
403+
}
404+
357405
if (req->fun == req->FUN_SET_DIGITAL_OUT && req->pin >= 0 && req->pin <= 17) {
358406
// io async success
359407
std::ignore = command_interfaces_[CommandInterfaces::IO_ASYNC_SUCCESS].set_value(ASYNC_WAITING);
@@ -413,6 +461,10 @@ bool GPIOController::setIO(ur_msgs::srv::SetIO::Request::SharedPtr req, ur_msgs:
413461
bool GPIOController::setAnalogOutput(ur_msgs::srv::SetAnalogOutput::Request::SharedPtr req,
414462
ur_msgs::srv::SetAnalogOutput::Response::SharedPtr resp)
415463
{
464+
if (!ensureActive(resp)) {
465+
return false;
466+
}
467+
416468
std::string domain_string = "UNKNOWN";
417469
switch (req->data.domain) {
418470
case ur_msgs::msg::Analog::CURRENT:
@@ -456,6 +508,10 @@ bool GPIOController::setAnalogOutput(ur_msgs::srv::SetAnalogOutput::Request::Sha
456508
bool GPIOController::setSpeedSlider(ur_msgs::srv::SetSpeedSliderFraction::Request::SharedPtr req,
457509
ur_msgs::srv::SetSpeedSliderFraction::Response::SharedPtr resp)
458510
{
511+
if (!ensureActive(resp)) {
512+
return false;
513+
}
514+
459515
if (req->speed_slider_fraction >= 0.01 && req->speed_slider_fraction <= 1.0) {
460516
RCLCPP_INFO(get_node()->get_logger(), "Setting speed slider to %.2f%%.", req->speed_slider_fraction * 100.0);
461517
// reset success flag
@@ -486,6 +542,10 @@ bool GPIOController::setSpeedSlider(ur_msgs::srv::SetSpeedSliderFraction::Reques
486542
bool GPIOController::resendRobotProgram(std_srvs::srv::Trigger::Request::SharedPtr /*req*/,
487543
std_srvs::srv::Trigger::Response::SharedPtr resp)
488544
{
545+
if (!ensureActive(resp)) {
546+
return false;
547+
}
548+
489549
// reset success flag
490550
std::ignore = command_interfaces_[CommandInterfaces::RESEND_ROBOT_PROGRAM_ASYNC_SUCCESS].set_value(ASYNC_WAITING);
491551
// call the service in the hardware
@@ -515,6 +575,10 @@ bool GPIOController::resendRobotProgram(std_srvs::srv::Trigger::Request::SharedP
515575
bool GPIOController::handBackControl(std_srvs::srv::Trigger::Request::SharedPtr /*req*/,
516576
std_srvs::srv::Trigger::Response::SharedPtr resp)
517577
{
578+
if (!ensureActive(resp)) {
579+
return false;
580+
}
581+
518582
// reset success flag
519583
std::ignore = command_interfaces_[CommandInterfaces::HAND_BACK_CONTROL_ASYNC_SUCCESS].set_value(ASYNC_WAITING);
520584
// call the service in the hardware
@@ -543,6 +607,10 @@ bool GPIOController::handBackControl(std_srvs::srv::Trigger::Request::SharedPtr
543607
bool GPIOController::setPayload(const ur_msgs::srv::SetPayload::Request::SharedPtr req,
544608
ur_msgs::srv::SetPayload::Response::SharedPtr resp)
545609
{
610+
if (!ensureActive(resp)) {
611+
return false;
612+
}
613+
546614
// reset success flag
547615
std::ignore = command_interfaces_[CommandInterfaces::PAYLOAD_ASYNC_SUCCESS].set_value(ASYNC_WAITING);
548616

@@ -586,6 +654,10 @@ bool GPIOController::setPayload(const ur_msgs::srv::SetPayload::Request::SharedP
586654
bool GPIOController::zeroFTSensor(std_srvs::srv::Trigger::Request::SharedPtr /*req*/,
587655
std_srvs::srv::Trigger::Response::SharedPtr resp)
588656
{
657+
if (!ensureActive(resp)) {
658+
return false;
659+
}
660+
589661
// reset success flag
590662
std::ignore = command_interfaces_[CommandInterfaces::ZERO_FTSENSOR_ASYNC_SUCCESS].set_value(ASYNC_WAITING);
591663
// call the service in the hardware

0 commit comments

Comments
 (0)