1#include "modulo_components/ComponentInterface.hpp"
3#include <console_bridge/console.h>
4#include <tf2_msgs/msg/tf_message.hpp>
6#include <modulo_core/translators/message_readers.hpp>
7#include <modulo_core/translators/message_writers.hpp>
14 const std::shared_ptr<rclcpp::node_interfaces::NodeInterfaces<ALL_RCLCPP_NODE_INTERFACES>>& interfaces)
15 : node_base_(interfaces->get_node_base_interface()),
16 node_clock_(interfaces->get_node_clock_interface()),
17 node_logging_(interfaces->get_node_logging_interface()),
18 node_parameters_(interfaces->get_node_parameters_interface()),
19 node_services_(interfaces->get_node_services_interface()),
20 node_timers_(interfaces->get_node_timers_interface()),
21 node_topics_(interfaces->get_node_topics_interface()) {
23 this->pre_set_parameter_cb_handle_ = this->node_parameters_->add_pre_set_parameters_callback(
24 [
this](std::vector<rclcpp::Parameter>&
parameters) {
return this->pre_set_parameters_callback(
parameters); });
25 this->on_set_parameter_cb_handle_ = this->node_parameters_->add_on_set_parameters_callback(
26 [
this](
const std::vector<rclcpp::Parameter>&
parameters) -> rcl_interfaces::msg::SetParametersResult {
27 return this->on_set_parameters_callback(
parameters);
29 this->
add_parameter(
"rate", 10.0,
"The rate in Hertz for all periodic callbacks",
true);
31 this->predicate_publisher_ = rclcpp::create_publisher<modulo_interfaces::msg::PredicateCollection>(
32 this->node_parameters_, this->node_topics_,
"/predicates", this->qos_);
33 this->predicate_message_.node = this->node_base_->get_fully_qualified_name();
34 this->predicate_message_.type = modulo_interfaces::msg::PredicateCollection::COMPONENT;
37 this->period_ = 1.0 / this->rate_;
38 this->step_timer_ = rclcpp::create_wall_timer(
39 std::chrono::nanoseconds(
static_cast<int64_t>(1
e9 * this->period_)),
41 if (this->step_mutex_.try_lock()) {
43 this->step_mutex_.unlock();
46 nullptr, this->node_base_.get(), this->node_timers_.get());
50 this->step_mutex_.lock();
64 return std::chrono::nanoseconds(
static_cast<int64_t>(1
e9 * this->period_));
69 return rclcpp::Duration::from_seconds(this->period_);
77 const std::shared_ptr<state_representation::ParameterInterface>& parameter,
const std::string& description,
85 if (!this->node_parameters_->has_parameter(
parameter->get_name())) {
87 this->parameter_map_.set_parameter(
parameter);
88 this->read_only_parameters_.insert_or_assign(
parameter->get_name(),
false);
90 rcl_interfaces::msg::ParameterDescriptor
descriptor;
94 this->set_parameters_result_.successful =
true;
95 this->set_parameters_result_.reason =
"";
99 this->node_parameters_->declare_parameter(
parameter->get_name(), rclcpp::ParameterValue{},
descriptor);
103 std::vector<rclcpp::Parameter>
ros_parameters{this->node_parameters_->get_parameters({
parameter->get_name()})};
107 this->node_parameters_->undeclare_parameter(
parameter->get_name());
111 }
catch (
const std::exception&
ex) {
112 this->parameter_map_.remove_parameter(
parameter->get_name());
113 this->read_only_parameters_.erase(
parameter->get_name());
118 this->node_logging_->get_logger(),
"Parameter '" <<
parameter->get_name() <<
"' already exists.");
122std::shared_ptr<state_representation::ParameterInterface>
125 return this->parameter_map_.get_parameter(
name);
126 }
catch (
const state_representation::exceptions::InvalidParameterException&
ex) {
131void ComponentInterface::pre_set_parameters_callback(std::vector<rclcpp::Parameter>& parameters) {
132 if (this->pre_set_parameter_callback_called_) {
133 this->pre_set_parameter_callback_called_ =
false;
136 rcl_interfaces::msg::SetParametersResult
result;
141 if (this->read_only_parameters_.at(
ros_parameter.get_name())) {
143 this->node_logging_->get_logger(),
"Parameter '" <<
ros_parameter.get_name() <<
"' is read only.");
150 result.successful =
false;
151 result.reason +=
"Validation of parameter '" +
ros_parameter.get_name() +
"' returned false!";
157 }
catch (
const std::exception&
ex) {
158 result.successful =
false;
162 this->set_parameters_result_ =
result;
165rcl_interfaces::msg::SetParametersResult
166ComponentInterface::on_set_parameters_callback(
const std::vector<rclcpp::Parameter>&) {
167 auto result = this->set_parameters_result_;
168 this->set_parameters_result_.successful =
true;
169 this->set_parameters_result_.reason =
"";
173bool ComponentInterface::validate_parameter(
174 const std::shared_ptr<state_representation::ParameterInterface>& parameter) {
178 RCLCPP_ERROR(this->node_logging_->get_logger(),
"Value for parameter 'rate' has to be a positive finite number.");
186 const std::shared_ptr<state_representation::ParameterInterface>&) {
195 const std::string& predicate_name,
const std::function<
bool(
void)>& predicate_function) {
197 RCLCPP_ERROR(this->node_logging_->get_logger(),
"Failed to add predicate: Provide a non empty string as a name.");
202 this->node_logging_->get_logger(),
203 "Predicate with name '" <<
predicate_name <<
"' already exists, overwriting.");
209 }
catch (
const std::exception&
ex) {
211 this->node_logging_->get_logger(), *
this->node_clock_->get_clock(), 1000,
220 this->node_logging_->get_logger(), *
this->node_clock_->get_clock(), 1000,
221 "Failed to get predicate '" <<
predicate_name <<
"': Predicate does not exist, returning false.");
226 }
catch (
const std::exception&
ex) {
228 this->node_logging_->get_logger(),
229 "Failed to evaluate callback of predicate '" <<
predicate_it->first <<
"', returning false: " <<
ex.what());
239 const std::string& predicate_name,
const std::function<
bool(
void)>& predicate_function) {
243 this->node_logging_->get_logger(), *
this->node_clock_->get_clock(), 1000,
244 "Failed to set predicate '" <<
predicate_name <<
"': Predicate does not exist.");
255 RCLCPP_ERROR(this->node_logging_->get_logger(),
"Failed to add trigger: Provide a non empty string as a name.");
258 if (std::find(this->triggers_.cbegin(),
this->triggers_.cend(),
trigger_name) !=
this->triggers_.cend()) {
260 this->node_logging_->get_logger(),
261 "Failed to add trigger: there is already a trigger with name '" <<
trigger_name <<
"'.");
266 this->node_logging_->get_logger(),
267 "Failed to add trigger: there is already a predicate with name '" <<
trigger_name <<
"'.");
275 if (std::find(this->triggers_.cbegin(),
this->triggers_.cend(),
trigger_name) ==
this->triggers_.cend()) {
277 this->node_logging_->get_logger(),
278 "Failed to trigger: could not find trigger with name '" <<
trigger_name <<
"'.");
283 this->predicates_.at(
trigger_name).set_predicate([]() {
return false; });
287 const std::string& signal_name,
const std::string& default_topic,
bool fixed_topic) {
292 const std::string& signal_name,
const std::string& default_topic,
bool fixed_topic) {
296void ComponentInterface::declare_signal(
297 const std::string& signal_name,
const std::string& type,
const std::string& default_topic,
bool fixed_topic) {
304 this->node_logging_->get_logger(),
306 +
"'. Use the parsed name to refer to this " +
type);
308 if (this->
inputs_.find(parsed_signal_name) !=
this->inputs_.cend()) {
311 if (this->
outputs_.find(parsed_signal_name) !=
this->outputs_.cend()) {
323 this->node_logging_->get_logger(),
330 if (this->
outputs_.find(parsed_signal_name) ==
this->outputs_.cend()) {
337 this->
outputs_.at(parsed_signal_name)->publish();
340 this->node_logging_->get_logger(), *
this->node_clock_->get_clock(), 1000,
347 auto parsed_signal_name = modulo_utils::parsing::parse_topic_name(
signal_name);
350 this->node_logging_->get_logger(),
358 auto parsed_signal_name = modulo_utils::parsing::parse_topic_name(
signal_name);
361 this->node_logging_->get_logger(),
372 auto service = rclcpp::create_service<modulo_interfaces::srv::EmptyTrigger>(
375 const std::shared_ptr<modulo_interfaces::srv::EmptyTrigger::Request>,
376 std::shared_ptr<modulo_interfaces::srv::EmptyTrigger::Response>
response) {
381 }
catch (
const std::exception&
ex) {
386 this->qos_,
nullptr);
388 }
catch (
const std::exception&
ex) {
390 this->node_logging_->get_logger(),
"Failed to add service '" <<
service_name <<
"': " <<
ex.what());
395 const std::string& service_name,
400 auto service = rclcpp::create_service<modulo_interfaces::srv::StringTrigger>(
403 const std::shared_ptr<modulo_interfaces::srv::StringTrigger::Request>
request,
404 std::shared_ptr<modulo_interfaces::srv::StringTrigger::Response>
response) {
409 }
catch (
const std::exception&
ex) {
414 this->qos_,
nullptr);
416 }
catch (
const std::exception&
ex) {
418 this->node_logging_->get_logger(),
"Failed to add service '" <<
service_name <<
"': " <<
ex.what());
423std::string ComponentInterface::validate_service_name(
const std::string& service_name,
const std::string& type)
const {
427 modulo_utils::parsing::topic_validation_warning(
service_name,
type +
" service"));
431 this->node_logging_->get_logger(),
433 +
"'. Use the parsed name to refer to this service");
450 this->node_logging_->get_logger(),
"Failed to add periodic function: Provide a non empty string as a name.");
453 if (this->periodic_callbacks_.find(
name) !=
this->periodic_callbacks_.end()) {
455 this->node_logging_->get_logger(),
"Periodic function '" <<
name <<
"' already exists, overwriting.");
459 this->periodic_callbacks_.insert_or_assign(
name,
callback);
463 if (this->tf_broadcaster_ ==
nullptr) {
464 RCLCPP_DEBUG(this->node_logging_->get_logger(),
"Adding TF broadcaster.");
465 console_bridge::setLogLevel(console_bridge::CONSOLE_BRIDGE_LOG_NONE);
466 this->tf_broadcaster_ = std::make_shared<tf2_ros::TransformBroadcaster>(
467 this->node_parameters_, this->node_topics_, tf2_ros::DynamicBroadcasterQoS());
469 RCLCPP_DEBUG(this->node_logging_->get_logger(),
"TF broadcaster already exists.");
474 if (this->static_tf_broadcaster_ ==
nullptr) {
475 RCLCPP_DEBUG(this->node_logging_->get_logger(),
"Adding static TF broadcaster.");
476 console_bridge::setLogLevel(console_bridge::CONSOLE_BRIDGE_LOG_NONE);
477 tf2_ros::StaticBroadcasterQoS
qos;
478 rclcpp::PublisherOptionsWithAllocator<std::allocator<void>>
options;
479 this->static_tf_broadcaster_ =
480 std::make_shared<tf2_ros::StaticTransformBroadcaster>(this->node_parameters_, this->node_topics_,
qos,
options);
482 RCLCPP_DEBUG(this->node_logging_->get_logger(),
"Static TF broadcaster already exists.");
487 if (this->tf_buffer_ ==
nullptr || this->tf_listener_ ==
nullptr) {
488 RCLCPP_DEBUG(this->node_logging_->get_logger(),
"Adding TF buffer and listener.");
489 console_bridge::setLogLevel(console_bridge::CONSOLE_BRIDGE_LOG_NONE);
490 this->tf_buffer_ = std::make_shared<tf2_ros::Buffer>(this->node_clock_->get_clock());
491 this->tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*this->tf_buffer_);
493 RCLCPP_DEBUG(this->node_logging_->get_logger(),
"TF buffer and listener already exist.");
502 this->publish_transforms(
transforms, this->tf_broadcaster_);
510 this->publish_transforms(
transforms, this->static_tf_broadcaster_,
true);
514 const std::string& frame,
const std::string& reference_frame,
const tf2::TimePoint& time_point,
515 const tf2::Duration& duration) {
523 const std::string& frame,
const std::string& reference_frame,
double validity_period,
524 const tf2::Duration& duration) {
536geometry_msgs::msg::TransformStamped ComponentInterface::lookup_ros_transform(
537 const std::string& frame,
const std::string& reference_frame,
const tf2::TimePoint& time_point,
538 const tf2::Duration& duration) {
539 if (this->tf_buffer_ ==
nullptr || this->tf_listener_ ==
nullptr) {
544 }
catch (
const tf2::TransformException&
ex) {
558 RCLCPP_ERROR(this->node_logging_->get_logger(),
"An error was raised in the component.");
561modulo_interfaces::msg::Predicate ComponentInterface::get_predicate_message(
const std::string& name,
bool value)
const {
562 modulo_interfaces::msg::Predicate message;
563 message.predicate =
name;
564 message.value =
value;
568void ComponentInterface::publish_predicate(
const std::string& name,
bool value)
const {
569 auto message(this->predicate_message_);
570 message.predicates.push_back(this->get_predicate_message(
name,
value));
571 this->predicate_publisher_->publish(message);
575 auto message(this->predicate_message_);
581 if (message.predicates.size()) {
582 this->predicate_publisher_->publish(message);
594 this->node_logging_->get_logger(), *
this->node_clock_->get_clock(), 1000,
595 "Failed to publish output '" <<
signal <<
"': " <<
ex.what());
601 for (
const auto& [
name,
callback] : this->periodic_callbacks_) {
604 }
catch (
const std::exception&
ex) {
606 this->node_logging_->get_logger(), *
this->node_clock_->get_clock(), 1000,
607 "Failed to evaluate periodic function callback '" <<
name <<
"': " <<
ex.what());
613 RCLCPP_DEBUG(this->node_logging_->get_logger(),
"Finalizing all interfaces.");
616 this->predicate_publisher_.reset();
617 this->empty_services_.clear();
618 this->string_services_.clear();
619 if (this->step_timer_ !=
nullptr) {
620 this->step_timer_->cancel();
622 this->step_timer_.reset();
623 this->tf_buffer_.reset();
624 this->tf_listener_.reset();
625 this->tf_broadcaster_.reset();
626 this->static_tf_broadcaster_.reset();
double get_rate() const
Get the component rate in Hertz.
void send_transforms(const std::vector< state_representation::CartesianPose > &transforms)
Send a vector of transforms to TF.
virtual bool on_validate_parameter_callback(const std::shared_ptr< state_representation::ParameterInterface > ¶meter)
Parameter validation function to be redefined by derived Component classes.
void publish_outputs()
Helper function to publish all output signals.
void remove_input(const std::string &signal_name)
Remove an input from the map of inputs.
void declare_input(const std::string &signal_name, const std::string &default_topic="", bool fixed_topic=false)
Declare an input to create the topic parameter without adding it to the map of inputs yet.
void declare_output(const std::string &signal_name, const std::string &default_topic="", bool fixed_topic=false)
Declare an output to create the topic parameter without adding it to the map of outputs yet.
T get_period() const
Get the component period.
void send_static_transforms(const std::vector< state_representation::CartesianPose > &transforms)
Send a vector of static transforms to TF.
std::map< std::string, std::shared_ptr< modulo_core::communication::PublisherInterface > > outputs_
Map of outputs.
std::shared_ptr< state_representation::ParameterInterface > get_parameter(const std::string &name) const
Get a parameter by name.
void finalize_interfaces()
Finalize all interfaces.
T get_parameter_value(const std::string &name) const
Get a parameter value by name.
virtual ~ComponentInterface()
Virtual destructor.
void add_static_tf_broadcaster()
Configure a static transform broadcaster.
void add_parameter(const std::shared_ptr< state_representation::ParameterInterface > ¶meter, const std::string &description, bool read_only=false)
Add a parameter.
void trigger(const std::string &trigger_name)
Latch the trigger with the provided name.
state_representation::CartesianPose lookup_transform(const std::string &frame, const std::string &reference_frame, const tf2::TimePoint &time_point, const tf2::Duration &duration)
Look up a transform from TF.
virtual void on_step_callback()
Steps to execute periodically. To be redefined by derived Component classes.
void set_predicate(const std::string &predicate_name, bool predicate_value)
Set the value of the predicate given as parameter, if the predicate is not found does not do anything...
virtual void raise_error()
Notify an error in the component.
ComponentInterface(const std::shared_ptr< rclcpp::node_interfaces::NodeInterfaces< ALL_RCLCPP_NODE_INTERFACES > > &interfaces)
Constructor with all node interfaces.
void remove_output(const std::string &signal_name)
Remove an output from the map of outputs.
void publish_output(const std::string &signal_name)
Trigger the publishing of an output.
virtual void step()
Step function that is called periodically.
void publish_predicates()
Helper function to publish all predicates.
void set_qos(const rclcpp::QoS &qos)
Set the Quality of Service for ROS publishers and subscribers.
rclcpp::QoS get_qos() const
Getter of the Quality of Service attribute.
void add_predicate(const std::string &predicate_name, bool predicate_value)
Add a predicate to the map of predicates.
void send_transform(const state_representation::CartesianPose &transform)
Send a transform to TF.
void add_trigger(const std::string &trigger_name)
Add a trigger to the component. Triggers are predicates that are always false except when it's trigge...
void add_periodic_callback(const std::string &name, const std::function< void(void)> &callback)
Add a periodic callback function.
std::map< std::string, std::shared_ptr< modulo_core::communication::SubscriptionInterface > > inputs_
Map of inputs.
void add_tf_listener()
Configure a transform buffer and listener.
void evaluate_periodic_callbacks()
Helper function to evaluate all periodic function callbacks.
void add_tf_broadcaster()
Configure a transform broadcaster.
void send_static_transform(const state_representation::CartesianPose &transform)
Send a static transform to TF.
bool get_predicate(const std::string &predicate_name) const
Get the logical value of a predicate.
std::map< std::string, bool > periodic_outputs_
Map of outputs with periodic publishing flag.
void add_service(const std::string &service_name, const std::function< ComponentServiceResponse(void)> &callback)
Add a service to trigger a callback function with no input arguments.
An exception class to notify errors when adding a service.
An exception class to notify errors when adding a signal.
A base class for all core exceptions.
An exception class to notify errors with parameters in modulo classes.
An exception class to notify incompatibility when translating parameters from different sources.
rclcpp::Parameter write_parameter(const std::shared_ptr< state_representation::ParameterInterface > ¶meter)
Write a ROS Parameter from a ParameterInterface pointer.
rclcpp::ParameterType get_ros_parameter_type(const state_representation::ParameterType ¶meter_type)
Given a state representation parameter type, get the corresponding ROS parameter type.
void copy_parameter_value(const std::shared_ptr< const state_representation::ParameterInterface > &source_parameter, const std::shared_ptr< state_representation::ParameterInterface > ¶meter)
Copy the value of one parameter interface into another.
void read_message(state_representation::CartesianState &state, const geometry_msgs::msg::Accel &message)
Convert a ROS geometry_msgs::msg::Accel to a CartesianState.
std::shared_ptr< state_representation::ParameterInterface > read_parameter_const(const rclcpp::Parameter &ros_parameter, const std::shared_ptr< const state_representation::ParameterInterface > ¶meter)
Update the parameter value of a ParameterInterface from a ROS Parameter object only if the two parame...
Response structure to be returned by component services.