6#include <controller_interface/controller_interface.hpp>
7#include <controller_interface/helpers.hpp>
8#include <realtime_tools/realtime_buffer.h>
9#include <realtime_tools/realtime_publisher.h>
11#include <state_representation/parameters/ParameterMap.hpp>
13#include <modulo_core/EncodedState.hpp>
14#include <modulo_core/Predicate.hpp>
15#include <modulo_core/communication/MessagePair.hpp>
16#include <modulo_core/exceptions.hpp>
17#include <modulo_core/translators/message_writers.hpp>
18#include <modulo_core/translators/parameter_translators.hpp>
20#include <modulo_interfaces/msg/predicate_collection.hpp>
21#include <modulo_interfaces/srv/empty_trigger.hpp>
22#include <modulo_interfaces/srv/string_trigger.hpp>
24#include <modulo_utils/parsing.hpp>
26#include <modulo_core/concepts.hpp>
28namespace modulo_controllers {
31 std::shared_ptr<rclcpp::Subscription<modulo_core::EncodedState>>,
32 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::Bool>>,
33 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::Float64>>,
34 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::Float64MultiArray>>,
35 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::Int32>>,
36 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::String>>, std::any>
40 realtime_tools::RealtimeBuffer<std::shared_ptr<modulo_core::EncodedState>>,
41 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Bool>>,
42 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64>>,
43 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64MultiArray>>,
44 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Int32>>,
45 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::String>>, std::any>
49 std::shared_ptr<state_representation::State>, std::shared_ptr<rclcpp::Publisher<modulo_core::EncodedState>>,
50 realtime_tools::RealtimePublisherSharedPtr<modulo_core::EncodedState>>
51 EncodedStatePublishers;
53 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Bool>>,
54 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::Bool>>
57 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Float64>>,
58 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::Float64>>
61 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Float64MultiArray>>,
62 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::Float64MultiArray>>
65 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Int32>>,
66 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::Int32>>
69 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::String>>,
70 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::String>>
72typedef std::pair<std::any, std::any> CustomPublishers;
75 EncodedStatePublishers, BoolPublishers, DoublePublishers, DoubleVecPublishers, IntPublishers, StringPublishers,
85 ControllerInput(BufferVariant buffer_variant) : buffer(std::move(buffer_variant)) {}
87 std::chrono::time_point<std::chrono::steady_clock> timestamp;
116 CallbackReturn
on_init()
override;
123 CallbackReturn
on_configure(
const rclcpp_lifecycle::State& previous_state)
override;
135 const std::shared_ptr<state_representation::ParameterInterface>& parameter,
const std::string& description,
136 bool read_only =
false);
149 void add_parameter(
const std::string& name,
const T& value,
const std::string& description,
bool read_only =
false);
169 [[nodiscard]] std::shared_ptr<state_representation::ParameterInterface>
get_parameter(
const std::string& name)
const;
196 void add_predicate(
const std::string& predicate_name,
bool predicate_value);
203 void add_predicate(
const std::string& predicate_name,
const std::function<
bool(
void)>& predicate_function);
211 [[nodiscard]]
bool get_predicate(
const std::string& predicate_name)
const;
220 void set_predicate(
const std::string& predicate_name,
bool predicate_value);
229 void set_predicate(
const std::string& predicate_name,
const std::function<
bool(
void)>& predicate_function);
243 void trigger(
const std::string& trigger_name);
254 void add_input(
const std::string& name,
const std::string& topic_name =
"");
265 void add_output(
const std::string& name,
const std::string& topic_name =
"");
281 std::optional<T>
read_input(
const std::string& name);
292 void write_output(
const std::string& name,
const T& data);
310 const std::string& service_name,
317 [[nodiscard]] rclcpp::QoS
get_qos()
const;
323 void set_qos(
const rclcpp::QoS& qos);
345 bool validate_parameter(
const std::shared_ptr<state_representation::ParameterInterface>& parameter);
351 void pre_set_parameters_callback(std::vector<rclcpp::Parameter>& parameters);
359 rcl_interfaces::msg::SetParametersResult on_set_parameters_callback(
const std::vector<rclcpp::Parameter>& parameters);
366 modulo_interfaces::msg::Predicate get_predicate_message(
const std::string& name,
bool value)
const;
373 void publish_predicate(
const std::string& predicate_name,
bool value)
const;
378 void publish_predicates();
387 std::string validate_and_declare_signal(
388 const std::string& signal_name,
const std::string& type,
const std::string& default_topic,
389 bool fixed_topic =
false);
399 void create_input(
const ControllerInput& input,
const std::string& name,
const std::string& topic_name);
409 void create_output(
const PublisherVariant& publishers,
const std::string& name,
const std::string& topic_name);
418 std::shared_ptr<rclcpp::Subscription<T>> create_subscription(
const std::string& name,
const std::string& topic_name);
424 bool check_input_valid(
const std::string& name)
const;
429 template<
typename PublisherT,
typename MsgT,
typename T>
430 void write_std_output(
const std::string& name,
const T& data);
448 std::string validate_service_name(
const std::string& service_name,
const std::string& type)
const;
450 state_representation::ParameterMap parameter_map_;
451 std::unordered_map<std::string, bool> read_only_parameters_;
452 std::shared_ptr<rclcpp::node_interfaces::PreSetParametersCallbackHandle>
453 pre_set_parameter_cb_handle_;
454 std::shared_ptr<rclcpp::node_interfaces::OnSetParametersCallbackHandle>
455 on_set_parameter_cb_handle_;
456 rcl_interfaces::msg::SetParametersResult set_parameters_result_;
457 bool pre_set_parameter_callback_called_ =
false;
459 std::vector<SubscriptionVariant> subscriptions_;
460 std::map<std::string, ControllerInput> inputs_;
461 std::map<std::string, std::shared_ptr<modulo_core::communication::MessagePairInterface>>
462 input_message_pairs_;
463 std::map<std::string, PublisherVariant> outputs_;
464 double input_validity_period_;
465 rclcpp::QoS qos_ = rclcpp::QoS(10);
467 std::map<std::string, std::shared_ptr<rclcpp::Service<modulo_interfaces::srv::EmptyTrigger>>>
469 std::map<std::string, std::shared_ptr<rclcpp::Service<modulo_interfaces::srv::StringTrigger>>>
472 std::map<std::string, modulo_core::Predicate> predicates_;
473 std::shared_ptr<rclcpp::Publisher<modulo_interfaces::msg::PredicateCollection>>
474 predicate_publisher_;
475 std::vector<std::string> triggers_;
476 modulo_interfaces::msg::PredicateCollection predicate_message_;
477 std::shared_ptr<rclcpp::TimerBase> predicate_timer_;
479 std::timed_mutex command_mutex_;
481 std::map<std::string, std::function<void(CustomPublishers&,
const std::string&)>>
482 custom_output_configuration_callables_;
483 std::map<std::string, std::function<void(
const std::string&,
const std::string&)>>
484 custom_input_configuration_callables_;
489 const std::string& name,
const T& value,
const std::string& description,
bool read_only) {
491 RCLCPP_ERROR(get_node()->get_logger(),
"Failed to add parameter: Provide a non empty string as a name.");
494 add_parameter(state_representation::make_shared_parameter(name, value), description, read_only);
501 }
catch (
const state_representation::exceptions::InvalidParameterException& ex) {
503 "Failed to get parameter value of parameter '" + name +
"': " + ex.what());
510 std::vector<rclcpp::Parameter> parameters{
512 pre_set_parameters_callback(parameters);
513 pre_set_parameter_callback_called_ =
true;
514 auto result = get_node()->set_parameters(parameters).at(0);
515 if (!result.successful) {
516 RCLCPP_ERROR_THROTTLE(
517 get_node()->get_logger(), *get_node()->get_clock(), 1000,
518 "Failed to set parameter value of parameter '%s': %s", name.c_str(), result.reason.c_str());
520 }
catch (
const std::exception& ex) {
521 RCLCPP_ERROR_THROTTLE(
522 get_node()->get_logger(), *get_node()->get_clock(), 1000,
"Failed to set parameter value of parameter '%s': %s",
523 name.c_str(), ex.what());
530 auto buffer = std::make_shared<realtime_tools::RealtimeBuffer<std::shared_ptr<T>>>();
532 auto parsed_name = validate_and_declare_signal(name,
"input", topic_name);
533 if (!parsed_name.empty()) {
534 inputs_.insert_or_assign(parsed_name, input);
535 custom_input_configuration_callables_.insert_or_assign(
536 name, [
this](
const std::string& name,
const std::string& topic) {
538 get_node()->create_subscription<T>(topic, qos_, [
this, name](
const std::shared_ptr<T> message) {
539 auto buffer_variant = std::get<std::any>(inputs_.at(name).buffer);
540 auto buffer = std::any_cast<std::shared_ptr<realtime_tools::RealtimeBuffer<std::shared_ptr<T>>>>(
542 buffer->writeFromNonRT(message);
543 inputs_.at(name).timestamp = std::chrono::steady_clock::now();
545 subscriptions_.push_back(subscription);
549 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<modulo_core::EncodedState>>();
551 auto parsed_name = validate_and_declare_signal(name,
"input", topic_name);
552 if (!parsed_name.empty()) {
553 inputs_.insert_or_assign(parsed_name, input);
554 input_message_pairs_.insert_or_assign(
556 modulo_core::communication::make_shared_message_pair(std::make_shared<T>(), get_node()->get_clock()));
563 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Bool>>();
565 create_input(input, name, topic_name);
570 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64>>();
571 auto input = ControllerInput(buffer);
572 create_input(input, name, topic_name);
578 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64MultiArray>>();
579 auto input = ControllerInput(buffer);
580 create_input(input, name, topic_name);
585 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Int32>>();
586 auto input = ControllerInput(buffer);
587 create_input(input, name, topic_name);
591inline void BaseControllerInterface::add_input<std::string>(
const std::string& name,
const std::string& topic_name) {
592 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::String>>();
593 auto input = ControllerInput(buffer);
594 create_input(input, name, topic_name);
598inline std::shared_ptr<rclcpp::Subscription<T>>
599BaseControllerInterface::create_subscription(
const std::string& name,
const std::string& topic_name) {
600 return get_node()->create_subscription<T>(topic_name, qos_, [
this, name](
const std::shared_ptr<T> message) {
601 std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<T>>>(inputs_.at(name).buffer).writeFromNonRT(message);
602 inputs_.at(name).timestamp = std::chrono::steady_clock::now();
609 typedef std::pair<std::shared_ptr<rclcpp::Publisher<T>>, realtime_tools::RealtimePublisherSharedPtr<T>> PublisherT;
610 auto parsed_name = validate_and_declare_signal(name,
"output", topic_name);
611 if (!parsed_name.empty()) {
612 outputs_.insert_or_assign(parsed_name, PublisherT());
613 custom_output_configuration_callables_.insert_or_assign(
614 name, [
this](CustomPublishers& pub,
const std::string& topic) {
615 auto publisher = get_node()->create_publisher<T>(topic, qos_);
616 pub.first = publisher;
617 pub.second = std::make_shared<realtime_tools::RealtimePublisher<T>>(publisher);
621 std::shared_ptr<state_representation::State> state_ptr = std::make_shared<T>();
622 create_output(EncodedStatePublishers(state_ptr, {}, {}), name, topic_name);
628 create_output(BoolPublishers(), name, topic_name);
633 create_output(DoublePublishers(), name, topic_name);
639 create_output(DoubleVecPublishers(), name, topic_name);
644 create_output(IntPublishers(), name, topic_name);
648inline void BaseControllerInterface::add_output<std::string>(
const std::string& name,
const std::string& topic_name) {
649 create_output(StringPublishers(), name, topic_name);
654 if (!check_input_valid(name)) {
660 auto buffer_variant = std::get<std::any>(inputs_.at(name).buffer);
661 auto buffer = std::any_cast<std::shared_ptr<realtime_tools::RealtimeBuffer<std::shared_ptr<T>>>>(buffer_variant);
662 return **(buffer->readFromNonRT());
663 }
catch (
const std::bad_any_cast& ex) {
664 RCLCPP_ERROR(get_node()->get_logger(),
"Failed to read custom input: %s", ex.what());
669 **std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<modulo_core::EncodedState>>>(inputs_.at(name).buffer)
671 std::shared_ptr<state_representation::State> state;
673 auto message_pair = input_message_pairs_.at(name);
674 message_pair->read<modulo_core::EncodedState, state_representation::State>(message);
675 state = message_pair->get_message_pair<modulo_core::EncodedState, state_representation::State>()->get_data();
676 }
catch (
const std::exception& ex) {
677 RCLCPP_WARN_THROTTLE(
678 get_node()->get_logger(), *get_node()->get_clock(), 1000,
679 "Could not read EncodedState message on input '%s': %s", name.c_str(), ex.what());
682 if (state->is_empty()) {
685 auto cast_ptr = std::dynamic_pointer_cast<T>(state);
686 if (cast_ptr !=
nullptr) {
689 RCLCPP_WARN_THROTTLE(
690 get_node()->get_logger(), *get_node()->get_clock(), 1000,
691 "Dynamic cast of message on input '%s' from type '%s' to type '%s' failed.", name.c_str(),
692 get_state_type_name(state->get_type()).c_str(), get_state_type_name(T().get_type()).c_str());
700 if (!check_input_valid(name)) {
705 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Bool>>>(inputs_.at(name).buffer)
712 if (!check_input_valid(name)) {
715 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64>>>(inputs_.at(name).buffer)
721inline std::optional<std::vector<double>>
723 if (!check_input_valid(name)) {
726 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64MultiArray>>>(
727 inputs_.at(name).buffer)
734 if (!check_input_valid(name)) {
737 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Int32>>>(inputs_.at(name).buffer)
743inline std::optional<std::string> BaseControllerInterface::read_input<std::string>(
const std::string& name) {
744 if (!check_input_valid(name)) {
747 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::String>>>(inputs_.at(name).buffer)
754 if (outputs_.find(name) == outputs_.end()) {
755 RCLCPP_WARN_THROTTLE(
756 get_node()->get_logger(), *get_node()->get_clock(), 1000,
"Could not find output '%s'", name.c_str());
761 CustomPublishers publishers;
763 publishers = std::get<CustomPublishers>(outputs_.at(name));
764 }
catch (
const std::bad_variant_access&) {
765 RCLCPP_WARN_THROTTLE(
766 get_node()->get_logger(), *get_node()->get_clock(), 1000,
767 "Could not retrieve publisher for output '%s': Invalid output type", name.c_str());
771 std::shared_ptr<realtime_tools::RealtimePublisher<T>> rt_pub;
773 rt_pub = std::any_cast<std::shared_ptr<realtime_tools::RealtimePublisher<T>>>(publishers.second);
774 }
catch (
const std::bad_any_cast& ex) {
775 RCLCPP_ERROR_THROTTLE(
776 get_node()->get_logger(), *get_node()->get_clock(), 1000,
777 "Skipping publication of output '%s' due to wrong data type: %s", name.c_str(), ex.what());
780 if (rt_pub && rt_pub->trylock()) {
782 rt_pub->unlockAndPublish();
785 if (data.is_empty()) {
786 RCLCPP_DEBUG_THROTTLE(
787 get_node()->get_logger(), *get_node()->get_clock(), 1000,
788 "Skipping publication of output '%s' due to emptiness of state", name.c_str());
791 EncodedStatePublishers publishers;
793 publishers = std::get<EncodedStatePublishers>(outputs_.at(name));
794 }
catch (
const std::bad_variant_access&) {
795 RCLCPP_WARN_THROTTLE(
796 get_node()->get_logger(), *get_node()->get_clock(), 1000,
797 "Could not retrieve publisher for output '%s': Invalid output type", name.c_str());
800 if (
const auto output_type = std::get<0>(publishers)->get_type(); output_type != data.get_type()) {
801 RCLCPP_WARN_THROTTLE(
802 get_node()->get_logger(), *get_node()->get_clock(), 1000,
803 "Skipping publication of output '%s' due to wrong data type (expected '%s', got '%s')",
804 state_representation::get_state_type_name(output_type).c_str(),
805 state_representation::get_state_type_name(data.get_type()).c_str(), name.c_str());
808 auto rt_pub = std::get<2>(publishers);
809 if (rt_pub && rt_pub->trylock()) {
813 RCLCPP_ERROR_THROTTLE(
814 get_node()->get_logger(), *get_node()->get_clock(), 1000,
"Failed to publish output '%s': %s", name.c_str(),
817 rt_pub->unlockAndPublish();
822template<
typename PublisherT,
typename MsgT,
typename T>
823void BaseControllerInterface::write_std_output(
const std::string& name,
const T& data) {
824 if (outputs_.find(name) == outputs_.end()) {
825 RCLCPP_WARN_THROTTLE(
826 get_node()->get_logger(), *get_node()->get_clock(), 1000,
"Could not find output '%s'", name.c_str());
829 PublisherT publishers;
831 publishers = std::get<PublisherT>(outputs_.at(name));
832 }
catch (
const std::bad_variant_access&) {
833 RCLCPP_WARN_THROTTLE(
834 get_node()->get_logger(), *get_node()->get_clock(), 1000,
835 "Could not retrieve publisher for output '%s': Invalid output type", name.c_str());
838 auto rt_pub = publishers.second;
839 if (rt_pub && rt_pub->trylock()) {
840 rt_pub->msg_.data = data;
841 rt_pub->unlockAndPublish();
847 write_std_output<BoolPublishers, std_msgs::msg::Bool, bool>(name, data);
852 write_std_output<DoublePublishers, std_msgs::msg::Float64, double>(name, data);
857 write_std_output<DoubleVecPublishers, std_msgs::msg::Float64MultiArray, std::vector<double>>(name, data);
862 write_std_output<IntPublishers, std_msgs::msg::Int32, int>(name, data);
867 write_std_output<StringPublishers, std_msgs::msg::String, std::string>(name, data);
Base controller class to combine ros2_control, control libraries and modulo.
std::shared_ptr< state_representation::ParameterInterface > get_parameter(const std::string &name) const
Get a parameter by name.
CallbackReturn on_configure(const rclcpp_lifecycle::State &previous_state) override
Add signals.
rclcpp::QoS get_qos() const
Getter of the Quality of Service attribute.
bool is_active() const
Check if the controller is currently in state active or not.
void set_parameter_value(const std::string &name, const T &value)
Set the value of a parameter.
T get_parameter_value(const std::string &name) const
Get a parameter value by name.
void add_output(const std::string &name, const std::string &topic_name="")
Add an output to the controller.
void set_input_validity_period(double input_validity_period)
Set the input validity period of input signals.
CallbackReturn on_init() override
Declare parameters and register the on_set_parameters callback.
std::optional< T > read_input(const std::string &name)
Read the most recent message of an input.
void add_parameter(const std::shared_ptr< state_representation::ParameterInterface > ¶meter, const std::string &description, bool read_only=false)
Add a parameter.
void write_output(const std::string &name, const T &data)
Write an object to an output.
void trigger(const std::string &trigger_name)
Latch the trigger with the provided name.
void set_qos(const rclcpp::QoS &qos)
Set the Quality of Service for ROS publishers and subscribers.
void add_input(const std::string &name, const std::string &topic_name="")
Add an input to the controller.
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...
void add_predicate(const std::string &predicate_name, bool predicate_value)
Add a predicate to the map of predicates.
bool get_predicate(const std::string &predicate_name) const
Get the logical value of a predicate.
virtual bool on_validate_parameter_callback(const std::shared_ptr< state_representation::ParameterInterface > ¶meter)
Parameter validation function to be redefined by derived controller classes.
BaseControllerInterface()
Default constructor.
void add_service(const std::string &service_name, const std::function< ControllerServiceResponse(void)> &callback)
Add a service to trigger a callback function with no input arguments.
std::timed_mutex & get_command_mutex()
Get the reference to the command mutex.
void add_trigger(const std::string &trigger_name)
Add a trigger to the controller.
An exception class to notify that the translation of a ROS message failed.
An exception class to notify errors with parameters in modulo classes.
void write_message(geometry_msgs::msg::Accel &message, const state_representation::CartesianState &state, const rclcpp::Time &time)
Convert a CartesianState to a ROS geometry_msgs::msg::Accel.
rclcpp::Parameter write_parameter(const std::shared_ptr< state_representation::ParameterInterface > ¶meter)
Write a ROS Parameter from a ParameterInterface pointer.
Response structure to be returned by controller services.