3838#include " ur_controllers/gpio_controller.hpp"
3939
4040#include < cmath>
41+ #include < lifecycle_msgs/msg/state.hpp>
4142#include < string>
4243
4344namespace 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+
4580controller_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
232316void 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
253337void 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
263343void 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
273349void 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
284357controller_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
337368controller_interface::CallbackReturn
338369ur_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
355399bool 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:
413461bool 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
456508bool 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
486542bool 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
515575bool 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
543607bool 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
586654bool 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