Modulo 5.0.0
Loading...
Searching...
No Matches
BaseControllerInterface.hpp
1#pragma once
2
3#include <any>
4#include <chrono>
5#include <mutex>
6
7#include <controller_interface/controller_interface.hpp>
8#include <controller_interface/helpers.hpp>
9#include <realtime_tools/realtime_buffer.h>
10#include <realtime_tools/realtime_publisher.h>
11#include <tf2_ros/buffer.h>
12#include <tf2_ros/static_transform_broadcaster.h>
13#include <tf2_ros/transform_broadcaster.h>
14#include <tf2_ros/transform_listener.h>
15
16#include <state_representation/parameters/ParameterMap.hpp>
17
18#include <modulo_core/EncodedState.hpp>
19#include <modulo_core/Predicate.hpp>
20#include <modulo_core/communication/MessagePair.hpp>
21#include <modulo_core/exceptions.hpp>
22#include <modulo_core/translators/message_writers.hpp>
23#include <modulo_core/translators/parameter_translators.hpp>
24
25#include <modulo_interfaces/msg/assignment.hpp>
26#include <modulo_interfaces/msg/predicate_collection.hpp>
27#include <modulo_interfaces/srv/empty_trigger.hpp>
28#include <modulo_interfaces/srv/string_trigger.hpp>
29
30#include <modulo_utils/parsing.hpp>
31
32#include <modulo_core/concepts.hpp>
33
34namespace modulo_controllers {
35
36typedef std::variant<
37 std::shared_ptr<rclcpp::Subscription<modulo_core::EncodedState>>,
38 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::Bool>>,
39 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::Float64>>,
40 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::Float64MultiArray>>,
41 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::Int32>>,
42 std::shared_ptr<rclcpp::Subscription<std_msgs::msg::String>>, std::any>
43 SubscriptionVariant;
44
45typedef std::variant<
46 realtime_tools::RealtimeBuffer<std::shared_ptr<modulo_core::EncodedState>>,
47 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Bool>>,
48 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64>>,
49 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64MultiArray>>,
50 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Int32>>,
51 realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::String>>, std::any>
52 BufferVariant;
53
54typedef std::tuple<
55 std::shared_ptr<state_representation::State>, std::shared_ptr<rclcpp::Publisher<modulo_core::EncodedState>>,
56 realtime_tools::RealtimePublisherSharedPtr<modulo_core::EncodedState>>
57 EncodedStatePublishers;
58typedef std::pair<
59 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Bool>>,
60 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::Bool>>
61 BoolPublishers;
62typedef std::pair<
63 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Float64>>,
64 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::Float64>>
65 DoublePublishers;
66typedef std::pair<
67 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Float64MultiArray>>,
68 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::Float64MultiArray>>
69 DoubleVecPublishers;
70typedef std::pair<
71 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::Int32>>,
72 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::Int32>>
73 IntPublishers;
74typedef std::pair<
75 std::shared_ptr<rclcpp::Publisher<std_msgs::msg::String>>,
76 realtime_tools::RealtimePublisherSharedPtr<std_msgs::msg::String>>
77 StringPublishers;
78typedef std::pair<std::any, std::any> CustomPublishers;
79
80typedef std::variant<
81 EncodedStatePublishers, BoolPublishers, DoublePublishers, DoubleVecPublishers, IntPublishers, StringPublishers,
82 CustomPublishers>
83 PublisherVariant;
84
90 ControllerInput() = default;
91 ControllerInput(BufferVariant buffer_variant) : buffer(std::move(buffer_variant)) {}
92 BufferVariant buffer;
93 std::chrono::time_point<std::chrono::steady_clock> timestamp;
94};
95
103 bool success;
104 std::string message;
105};
106
111class BaseControllerInterface : public controller_interface::ControllerInterface {
112public:
117
122 CallbackReturn on_init() override;
123
129 CallbackReturn on_configure(const rclcpp_lifecycle::State& previous_state) override;
130
131protected:
141 const std::shared_ptr<state_representation::ParameterInterface>& parameter, const std::string& description,
142 bool read_only = false);
143
154 template<typename T>
155 void add_parameter(const std::string& name, const T& value, const std::string& description, bool read_only = false);
156
167 virtual bool
168 on_validate_parameter_callback(const std::shared_ptr<state_representation::ParameterInterface>& parameter);
169
175 [[nodiscard]] std::shared_ptr<state_representation::ParameterInterface> get_parameter(const std::string& name) const;
176
183 template<typename T>
184 T get_parameter_value(const std::string& name) const;
185
194 template<typename T>
195 void set_parameter_value(const std::string& name, const T& value);
196
202 template<typename T>
203 void add_assignment(const std::string& assignment_name);
204
211 template<typename T>
212 void set_assignment(const std::string& assignment_name, const T& assignment_value);
213
222 template<typename T>
223 T get_assignment(const std::string& assignment_name) const;
224
230 void add_predicate(const std::string& predicate_name, bool predicate_value);
231
237 void add_predicate(const std::string& predicate_name, const std::function<bool(void)>& predicate_function);
238
245 [[nodiscard]] bool get_predicate(const std::string& predicate_name) const;
246
254 void set_predicate(const std::string& predicate_name, bool predicate_value);
255
263 void set_predicate(const std::string& predicate_name, const std::function<bool(void)>& predicate_function);
264
271 void add_trigger(const std::string& trigger_name);
272
277 void trigger(const std::string& trigger_name);
278
287 template<typename T>
288 void add_input(const std::string& name, const std::string& topic_name = "");
289
298 template<typename T>
299 void add_output(const std::string& name, const std::string& topic_name = "");
300
308 template<typename T>
309 std::optional<T> read_input(const std::string& name);
310
319 template<typename T>
320 void write_output(const std::string& name, const T& data);
321
327 void add_service(const std::string& service_name, const std::function<ControllerServiceResponse(void)>& callback);
328
337 void add_service(
338 const std::string& service_name,
339 const std::function<ControllerServiceResponse(const std::string& string)>& callback);
340
345 void
346 add_service_lockfree(const std::string& service_name, const std::function<ControllerServiceResponse(void)>& callback);
347
353 const std::string& service_name,
354 const std::function<ControllerServiceResponse(const std::string& string)>& callback);
355
359 void add_tf_listener();
360
371 [[nodiscard]] state_representation::CartesianPose lookup_transform(
372 const std::string& frame, const std::string& reference_frame, const tf2::TimePoint& time_point,
373 const tf2::Duration& duration);
374
385 [[nodiscard]] state_representation::CartesianPose lookup_transform(
386 const std::string& frame, const std::string& reference_frame = "world", double validity_period = -1.0,
387 const tf2::Duration& duration = tf2::Duration(std::chrono::microseconds(10)));
388
392 void add_tf_broadcaster();
393
398
403 void send_transform(const state_representation::CartesianPose& transform);
404
409 void send_transforms(const std::vector<state_representation::CartesianPose>& transforms);
410
415 void send_static_transform(const state_representation::CartesianPose& transform);
416
421 void send_static_transforms(const std::vector<state_representation::CartesianPose>& transforms);
422
427 [[nodiscard]] rclcpp::QoS get_qos() const;
428
433 void set_qos(const rclcpp::QoS& qos);
434
439 bool is_active() const;
440
445 std::timed_mutex& get_command_mutex();
446
447private:
455 bool validate_parameter(const std::shared_ptr<state_representation::ParameterInterface>& parameter);
456
461 void pre_set_parameters_callback(std::vector<rclcpp::Parameter>& parameters);
462
469 rcl_interfaces::msg::SetParametersResult on_set_parameters_callback(const std::vector<rclcpp::Parameter>& parameters);
470
476 modulo_interfaces::msg::Predicate get_predicate_message(const std::string& name, bool value) const;
477
483 void publish_predicate(const std::string& predicate_name, bool value) const;
484
488 void publish_predicates();
489
497 std::string validate_and_declare_signal(
498 const std::string& signal_name, const std::string& type, const std::string& default_topic,
499 bool fixed_topic = false);
500
509 void create_input(const ControllerInput& input, const std::string& name, const std::string& topic_name);
510
519 void create_output(const PublisherVariant& publishers, const std::string& name, const std::string& topic_name);
520
527 template<typename T>
528 std::shared_ptr<rclcpp::Subscription<T>> create_subscription(const std::string& name, const std::string& topic_name);
529
534 bool check_input_valid(const std::string& name) const;
535
539 template<typename PublisherT, typename MsgT, typename T>
540 void write_std_output(const std::string& name, const T& data);
541
545 void add_inputs();
546
550 void add_outputs();
551
559 template<bool acquire_lock, typename CallbackT>
560 void create_service(const std::string& service_name, const std::function<CallbackT>& callback)
561 requires(
562 std::is_same_v<CallbackT, ControllerServiceResponse(const std::string&)>
563 || std::is_same_v<CallbackT, ControllerServiceResponse(void)>);
564
571 std::string validate_service_name(const std::string& service_name, const std::string& type) const;
572
583 [[nodiscard]] geometry_msgs::msg::TransformStamped lookup_ros_transform(
584 const std::string& frame, const std::string& reference_frame, const tf2::TimePoint& time_point,
585 const tf2::Duration& duration);
586
594 template<typename T>
595 void publish_transforms(
596 const std::vector<state_representation::CartesianPose>& transforms, const std::shared_ptr<T>& tf_broadcaster,
597 bool is_static = false);
598
599 state_representation::ParameterMap parameter_map_;
600 std::unordered_map<std::string, bool> read_only_parameters_;
601 std::shared_ptr<rclcpp::node_interfaces::PreSetParametersCallbackHandle>
602 pre_set_parameter_cb_handle_;
603 std::shared_ptr<rclcpp::node_interfaces::OnSetParametersCallbackHandle>
604 on_set_parameter_cb_handle_;
605 rcl_interfaces::msg::SetParametersResult set_parameters_result_;
606 bool pre_set_parameter_callback_called_ = false;
607
608 std::vector<SubscriptionVariant> subscriptions_;
609 std::map<std::string, ControllerInput> inputs_;
610 std::map<std::string, std::shared_ptr<modulo_core::communication::MessagePairInterface>>
611 input_message_pairs_;
612 std::map<std::string, PublisherVariant> outputs_;
613 double input_validity_period_;
614 rclcpp::QoS qos_ = rclcpp::QoS(10);
615
616 std::map<std::string, std::shared_ptr<rclcpp::Service<modulo_interfaces::srv::EmptyTrigger>>>
617 empty_services_;
618 std::map<std::string, std::shared_ptr<rclcpp::Service<modulo_interfaces::srv::StringTrigger>>>
619 string_services_;
620
621 state_representation::ParameterMap assignments_map_;
622 std::shared_ptr<rclcpp::Publisher<modulo_interfaces::msg::Assignment>> assignment_publisher_;
623
624 std::map<std::string, modulo_core::Predicate> predicates_;
625 std::shared_ptr<rclcpp::Publisher<modulo_interfaces::msg::PredicateCollection>>
626 predicate_publisher_;
627 std::vector<std::string> triggers_;
628 modulo_interfaces::msg::PredicateCollection predicate_message_;
629 std::shared_ptr<rclcpp::TimerBase> predicate_timer_;
630
631 std::timed_mutex command_mutex_;
632
633 std::map<std::string, std::function<void(CustomPublishers&, const std::string&)>>
634 custom_output_configuration_callables_;
635 std::map<std::string, std::function<void(const std::string&, const std::string&)>>
636 custom_input_configuration_callables_;
637
638 std::shared_ptr<tf2_ros::Buffer> tf_buffer_;
639 std::shared_ptr<tf2_ros::TransformListener> tf_listener_;
640 std::shared_ptr<tf2_ros::TransformBroadcaster> tf_broadcaster_;
641 std::shared_ptr<tf2_ros::StaticTransformBroadcaster> static_tf_broadcaster_;
642};
643
644template<typename T>
646 const std::string& name, const T& value, const std::string& description, bool read_only) {
647 if (name.empty()) {
648 RCLCPP_ERROR(get_node()->get_logger(), "Failed to add parameter: Provide a non empty string as a name.");
649 return;
650 }
651 add_parameter(state_representation::make_shared_parameter(name, value), description, read_only);
652}
653
654template<typename T>
655inline T BaseControllerInterface::get_parameter_value(const std::string& name) const {
656 try {
657 return this->parameter_map_.template get_parameter_value<T>(name);
658 } catch (const state_representation::exceptions::InvalidParameterException& ex) {
660 "Failed to get parameter value of parameter '" + name + "': " + ex.what());
661 }
662}
663
664template<typename T>
665inline void BaseControllerInterface::set_parameter_value(const std::string& name, const T& value) {
666 try {
667 std::vector<rclcpp::Parameter> parameters{
668 modulo_core::translators::write_parameter(state_representation::make_shared_parameter(name, value))};
669 pre_set_parameters_callback(parameters);
670 pre_set_parameter_callback_called_ = true;
671 auto result = get_node()->set_parameters(parameters).at(0);
672 if (!result.successful) {
673 RCLCPP_ERROR_THROTTLE(
674 get_node()->get_logger(), *get_node()->get_clock(), 1000,
675 "Failed to set parameter value of parameter '%s': %s", name.c_str(), result.reason.c_str());
676 }
677 } catch (const std::exception& ex) {
678 RCLCPP_ERROR_THROTTLE(
679 get_node()->get_logger(), *get_node()->get_clock(), 1000, "Failed to set parameter value of parameter '%s': %s",
680 name.c_str(), ex.what());
681 }
682}
683
684template<typename T>
685inline void BaseControllerInterface::add_input(const std::string& name, const std::string& topic_name) {
686 if constexpr (modulo_core::concepts::CustomT<T>) {
687 auto buffer = std::make_shared<realtime_tools::RealtimeBuffer<std::shared_ptr<T>>>();
688 auto input = ControllerInput(buffer);
689 auto parsed_name = validate_and_declare_signal(name, "input", topic_name);
690 if (!parsed_name.empty()) {
691 inputs_.insert_or_assign(parsed_name, input);
692 custom_input_configuration_callables_.insert_or_assign(
693 name, [this](const std::string& name, const std::string& topic) {
694 auto subscription =
695 get_node()->create_subscription<T>(topic, qos_, [this, name](const std::shared_ptr<T> message) {
696 auto buffer_variant = std::get<std::any>(inputs_.at(name).buffer);
697 auto buffer = std::any_cast<std::shared_ptr<realtime_tools::RealtimeBuffer<std::shared_ptr<T>>>>(
698 buffer_variant);
699 buffer->writeFromNonRT(message);
700 inputs_.at(name).timestamp = std::chrono::steady_clock::now();
701 });
702 subscriptions_.push_back(subscription);
703 });
704 }
705 } else {
706 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<modulo_core::EncodedState>>();
707 auto input = ControllerInput(buffer);
708 auto parsed_name = validate_and_declare_signal(name, "input", topic_name);
709 if (!parsed_name.empty()) {
710 inputs_.insert_or_assign(parsed_name, input);
711 input_message_pairs_.insert_or_assign(
712 parsed_name,
713 modulo_core::communication::make_shared_message_pair(std::make_shared<T>(), get_node()->get_clock()));
714 }
715 }
716}
717
718template<>
719inline void BaseControllerInterface::add_input<bool>(const std::string& name, const std::string& topic_name) {
720 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Bool>>();
721 auto input = ControllerInput(buffer);
722 create_input(input, name, topic_name);
723}
724
725template<>
726inline void BaseControllerInterface::add_input<double>(const std::string& name, const std::string& topic_name) {
727 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64>>();
728 auto input = ControllerInput(buffer);
729 create_input(input, name, topic_name);
730}
731
732template<>
733inline void
734BaseControllerInterface::add_input<std::vector<double>>(const std::string& name, const std::string& topic_name) {
735 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64MultiArray>>();
736 auto input = ControllerInput(buffer);
737 create_input(input, name, topic_name);
738}
739
740template<>
741inline void BaseControllerInterface::add_input<int>(const std::string& name, const std::string& topic_name) {
742 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Int32>>();
743 auto input = ControllerInput(buffer);
744 create_input(input, name, topic_name);
745}
746
747template<>
748inline void BaseControllerInterface::add_input<std::string>(const std::string& name, const std::string& topic_name) {
749 auto buffer = realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::String>>();
750 auto input = ControllerInput(buffer);
751 create_input(input, name, topic_name);
752}
753
754template<typename T>
755inline std::shared_ptr<rclcpp::Subscription<T>>
756BaseControllerInterface::create_subscription(const std::string& name, const std::string& topic_name) {
757 return get_node()->create_subscription<T>(topic_name, qos_, [this, name](const std::shared_ptr<T> message) {
758 std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<T>>>(inputs_.at(name).buffer).writeFromNonRT(message);
759 inputs_.at(name).timestamp = std::chrono::steady_clock::now();
760 });
761}
762
763template<typename T>
764inline void BaseControllerInterface::add_output(const std::string& name, const std::string& topic_name) {
765 if constexpr (modulo_core::concepts::CustomT<T>) {
766 typedef std::pair<std::shared_ptr<rclcpp::Publisher<T>>, realtime_tools::RealtimePublisherSharedPtr<T>> PublisherT;
767 auto parsed_name = validate_and_declare_signal(name, "output", topic_name);
768 if (!parsed_name.empty()) {
769 outputs_.insert_or_assign(parsed_name, PublisherT());
770 custom_output_configuration_callables_.insert_or_assign(
771 name, [this](CustomPublishers& pub, const std::string& topic) {
772 auto publisher = get_node()->create_publisher<T>(topic, qos_);
773 pub.first = publisher;
774 pub.second = std::make_shared<realtime_tools::RealtimePublisher<T>>(publisher);
775 });
776 }
777 } else {
778 std::shared_ptr<state_representation::State> state_ptr = std::make_shared<T>();
779 create_output(EncodedStatePublishers(state_ptr, {}, {}), name, topic_name);
780 }
781}
782
783template<>
784inline void BaseControllerInterface::add_output<bool>(const std::string& name, const std::string& topic_name) {
785 create_output(BoolPublishers(), name, topic_name);
786}
787
788template<>
789inline void BaseControllerInterface::add_output<double>(const std::string& name, const std::string& topic_name) {
790 create_output(DoublePublishers(), name, topic_name);
791}
792
793template<>
794inline void
795BaseControllerInterface::add_output<std::vector<double>>(const std::string& name, const std::string& topic_name) {
796 create_output(DoubleVecPublishers(), name, topic_name);
797}
798
799template<>
800inline void BaseControllerInterface::add_output<int>(const std::string& name, const std::string& topic_name) {
801 create_output(IntPublishers(), name, topic_name);
802}
803
804template<>
805inline void BaseControllerInterface::add_output<std::string>(const std::string& name, const std::string& topic_name) {
806 create_output(StringPublishers(), name, topic_name);
807}
808
809template<typename T>
810inline std::optional<T> BaseControllerInterface::read_input(const std::string& name) {
811 if (!check_input_valid(name)) {
812 return {};
813 }
814
815 if constexpr (modulo_core::concepts::CustomT<T>) {
816 try {
817 auto buffer_variant = std::get<std::any>(inputs_.at(name).buffer);
818 auto buffer = std::any_cast<std::shared_ptr<realtime_tools::RealtimeBuffer<std::shared_ptr<T>>>>(buffer_variant);
819 return **(buffer->readFromNonRT());
820 } catch (const std::bad_any_cast& ex) {
821 RCLCPP_ERROR(get_node()->get_logger(), "Failed to read custom input: %s", ex.what());
822 }
823 return {};
824 } else {
825 auto message =
826 **std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<modulo_core::EncodedState>>>(inputs_.at(name).buffer)
827 .readFromNonRT();
828 std::shared_ptr<state_representation::State> state;
829 try {
830 auto message_pair = input_message_pairs_.at(name);
831 message_pair->read<modulo_core::EncodedState, state_representation::State>(message);
832 state = message_pair->get_message_pair<modulo_core::EncodedState, state_representation::State>()->get_data();
833 } catch (const std::exception& ex) {
834 RCLCPP_WARN_THROTTLE(
835 get_node()->get_logger(), *get_node()->get_clock(), 1000,
836 "Could not read EncodedState message on input '%s': %s", name.c_str(), ex.what());
837 return {};
838 }
839 if (state->is_empty()) {
840 return {};
841 }
842 auto cast_ptr = std::dynamic_pointer_cast<T>(state);
843 if (cast_ptr != nullptr) {
844 return *cast_ptr;
845 } else {
846 RCLCPP_WARN_THROTTLE(
847 get_node()->get_logger(), *get_node()->get_clock(), 1000,
848 "Dynamic cast of message on input '%s' from type '%s' to type '%s' failed.", name.c_str(),
849 get_state_type_name(state->get_type()).c_str(), get_state_type_name(T().get_type()).c_str());
850 }
851 return {};
852 }
853}
854
855template<>
856inline std::optional<bool> BaseControllerInterface::read_input<bool>(const std::string& name) {
857 if (!check_input_valid(name)) {
858 return {};
859 }
860 // no need to check for emptiness of the pointer: timestamps are default constructed to 0, so an input being valid
861 // means that a message was received
862 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Bool>>>(inputs_.at(name).buffer)
863 .readFromNonRT())
864 ->data;
865}
866
867template<>
868inline std::optional<double> BaseControllerInterface::read_input<double>(const std::string& name) {
869 if (!check_input_valid(name)) {
870 return {};
871 }
872 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64>>>(inputs_.at(name).buffer)
873 .readFromNonRT())
874 ->data;
875}
876
877template<>
878inline std::optional<std::vector<double>>
880 if (!check_input_valid(name)) {
881 return {};
882 }
883 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Float64MultiArray>>>(
884 inputs_.at(name).buffer)
885 .readFromNonRT())
886 ->data;
887}
888
889template<>
890inline std::optional<int> BaseControllerInterface::read_input<int>(const std::string& name) {
891 if (!check_input_valid(name)) {
892 return {};
893 }
894 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::Int32>>>(inputs_.at(name).buffer)
895 .readFromNonRT())
896 ->data;
897}
898
899template<>
900inline std::optional<std::string> BaseControllerInterface::read_input<std::string>(const std::string& name) {
901 if (!check_input_valid(name)) {
902 return {};
903 }
904 return (*std::get<realtime_tools::RealtimeBuffer<std::shared_ptr<std_msgs::msg::String>>>(inputs_.at(name).buffer)
905 .readFromNonRT())
906 ->data;
907}
908
909template<typename T>
910inline void BaseControllerInterface::write_output(const std::string& name, const T& data) {
911 if (outputs_.find(name) == outputs_.end()) {
912 RCLCPP_WARN_THROTTLE(
913 get_node()->get_logger(), *get_node()->get_clock(), 1000, "Could not find output '%s'", name.c_str());
914 return;
915 }
916
917 if constexpr (modulo_core::concepts::CustomT<T>) {
918 CustomPublishers publishers;
919 try {
920 publishers = std::get<CustomPublishers>(outputs_.at(name));
921 } catch (const std::bad_variant_access&) {
922 RCLCPP_WARN_THROTTLE(
923 get_node()->get_logger(), *get_node()->get_clock(), 1000,
924 "Could not retrieve publisher for output '%s': Invalid output type", name.c_str());
925 return;
926 }
927
928 std::shared_ptr<realtime_tools::RealtimePublisher<T>> rt_pub;
929 try {
930 rt_pub = std::any_cast<std::shared_ptr<realtime_tools::RealtimePublisher<T>>>(publishers.second);
931 } catch (const std::bad_any_cast& ex) {
932 RCLCPP_ERROR_THROTTLE(
933 get_node()->get_logger(), *get_node()->get_clock(), 1000,
934 "Skipping publication of output '%s' due to wrong data type: %s", name.c_str(), ex.what());
935 return;
936 }
937 if (rt_pub && rt_pub->trylock()) {
938 rt_pub->msg_ = data;
939 rt_pub->unlockAndPublish();
940 }
941 } else {
942 if (data.is_empty()) {
943 RCLCPP_DEBUG_THROTTLE(
944 get_node()->get_logger(), *get_node()->get_clock(), 1000,
945 "Skipping publication of output '%s' due to emptiness of state", name.c_str());
946 return;
947 }
948 EncodedStatePublishers publishers;
949 try {
950 publishers = std::get<EncodedStatePublishers>(outputs_.at(name));
951 } catch (const std::bad_variant_access&) {
952 RCLCPP_WARN_THROTTLE(
953 get_node()->get_logger(), *get_node()->get_clock(), 1000,
954 "Could not retrieve publisher for output '%s': Invalid output type", name.c_str());
955 return;
956 }
957 if (const auto output_type = std::get<0>(publishers)->get_type(); output_type != data.get_type()) {
958 RCLCPP_WARN_THROTTLE(
959 get_node()->get_logger(), *get_node()->get_clock(), 1000,
960 "Skipping publication of output '%s' due to wrong data type (expected '%s', got '%s')",
961 state_representation::get_state_type_name(output_type).c_str(),
962 state_representation::get_state_type_name(data.get_type()).c_str(), name.c_str());
963 return;
964 }
965 auto rt_pub = std::get<2>(publishers);
966 if (rt_pub && rt_pub->trylock()) {
967 try {
968 modulo_core::translators::write_message<T>(rt_pub->msg_, data, get_node()->get_clock()->now());
970 RCLCPP_ERROR_THROTTLE(
971 get_node()->get_logger(), *get_node()->get_clock(), 1000, "Failed to publish output '%s': %s", name.c_str(),
972 ex.what());
973 }
974 rt_pub->unlockAndPublish();
975 }
976 }
977}
978
979template<typename PublisherT, typename MsgT, typename T>
980void BaseControllerInterface::write_std_output(const std::string& name, const T& data) {
981 if (outputs_.find(name) == outputs_.end()) {
982 RCLCPP_WARN_THROTTLE(
983 get_node()->get_logger(), *get_node()->get_clock(), 1000, "Could not find output '%s'", name.c_str());
984 return;
985 }
986 PublisherT publishers;
987 try {
988 publishers = std::get<PublisherT>(outputs_.at(name));
989 } catch (const std::bad_variant_access&) {
990 RCLCPP_WARN_THROTTLE(
991 get_node()->get_logger(), *get_node()->get_clock(), 1000,
992 "Could not retrieve publisher for output '%s': Invalid output type", name.c_str());
993 return;
994 }
995 auto rt_pub = publishers.second;
996 if (rt_pub && rt_pub->trylock()) {
997 rt_pub->msg_.data = data;
998 rt_pub->unlockAndPublish();
999 }
1000}
1001
1002template<>
1003inline void BaseControllerInterface::write_output(const std::string& name, const bool& data) {
1004 write_std_output<BoolPublishers, std_msgs::msg::Bool, bool>(name, data);
1005}
1006
1007template<>
1008inline void BaseControllerInterface::write_output(const std::string& name, const double& data) {
1009 write_std_output<DoublePublishers, std_msgs::msg::Float64, double>(name, data);
1010}
1011
1012template<>
1013inline void BaseControllerInterface::write_output(const std::string& name, const std::vector<double>& data) {
1014 write_std_output<DoubleVecPublishers, std_msgs::msg::Float64MultiArray, std::vector<double>>(name, data);
1015}
1016
1017template<>
1018inline void BaseControllerInterface::write_output(const std::string& name, const int& data) {
1019 write_std_output<IntPublishers, std_msgs::msg::Int32, int>(name, data);
1020}
1021
1022template<>
1023inline void BaseControllerInterface::write_output(const std::string& name, const std::string& data) {
1024 write_std_output<StringPublishers, std_msgs::msg::String, std::string>(name, data);
1025}
1026
1027template<bool acquire_lock, typename CallbackT>
1028inline void
1029BaseControllerInterface::create_service(const std::string& service_name, const std::function<CallbackT>& callback)
1030 requires(
1031 std::is_same_v<CallbackT, ControllerServiceResponse(const std::string&)>
1032 || std::is_same_v<CallbackT, ControllerServiceResponse(void)>)
1033{
1034 constexpr bool is_empty = std::is_same_v<CallbackT, ControllerServiceResponse(void)>;
1035 using ServiceT =
1036 std::conditional_t<is_empty, modulo_interfaces::srv::EmptyTrigger, modulo_interfaces::srv::StringTrigger>;
1037
1038 auto parsed_service_name = validate_service_name(service_name, is_empty ? "empty" : "string");
1039 if (parsed_service_name.empty()) {
1040 return;
1041 }
1042
1043 try {
1044 auto service = get_node()->create_service<ServiceT>(
1045 "~/" + parsed_service_name,
1046 [this, callback](
1047 const std::shared_ptr<typename ServiceT::Request> request,
1048 std::shared_ptr<typename ServiceT::Response> response) {
1049 try {
1050 auto run_callback = [&]() {
1051 ControllerServiceResponse callback_response;
1052 if constexpr (is_empty) {
1053 callback_response = callback();
1054 } else {
1055 callback_response = callback(request->payload);
1056 }
1057 response->success = callback_response.success;
1058 response->message = callback_response.message;
1059 };
1060
1061 if constexpr (!acquire_lock) {
1062 run_callback();
1063 } else {
1064 std::unique_lock<std::timed_mutex> lock(this->command_mutex_, std::defer_lock);
1065 if (lock.try_lock_for(std::chrono::milliseconds{100})) {
1066 run_callback();
1067 } else {
1068 response->success = false;
1069 response->message = "Unable to acquire lock for command interface within 100ms";
1070 }
1071 }
1072 } catch (const std::exception& ex) {
1073 response->success = false;
1074 response->message = ex.what();
1075 }
1076 },
1077 qos_);
1078 if constexpr (is_empty) {
1079 empty_services_.insert_or_assign(parsed_service_name, service);
1080 } else {
1081 string_services_.insert_or_assign(parsed_service_name, service);
1082 }
1083 } catch (const std::exception& ex) {
1084 RCLCPP_ERROR(get_node()->get_logger(), "Failed to add service '%s': %s", parsed_service_name.c_str(), ex.what());
1085 }
1086}
1087
1088template<typename T>
1089inline void BaseControllerInterface::add_assignment(const std::string& assignment_name) {
1090 std::string parsed_name = modulo_utils::parsing::parse_topic_name(assignment_name);
1091 if (parsed_name.empty()) {
1092 RCLCPP_ERROR_STREAM(
1093 get_node()->get_logger(),
1094 "The parsed name for assignment '" + assignment_name
1095 + "' is empty. Provide a string with valid characters for the assignment name ([a-z0-9_]).");
1096 return;
1097 }
1098 if (assignment_name != parsed_name) {
1099 RCLCPP_WARN_STREAM(
1100 get_node()->get_logger(),
1101 "The parsed name for assignment '" + assignment_name + "' is '" + parsed_name
1102 + "'. Use the parsed name to refer to this assignment.");
1103 }
1104 try {
1105 this->assignments_map_.get_parameter(parsed_name);
1106 RCLCPP_WARN_STREAM(
1107 get_node()->get_logger(), "Assignment with name '" + parsed_name + "' already exists, overwriting.");
1108 } catch (const state_representation::exceptions::InvalidParameterException& ex) {
1109 RCLCPP_DEBUG_STREAM(get_node()->get_logger(), "Adding assignment '" << parsed_name << "'.");
1110 }
1111 try {
1112 assignments_map_.set_parameter(state_representation::make_shared_parameter<T>(parsed_name));
1113 } catch (const std::exception& ex) {
1114 RCLCPP_ERROR_STREAM_THROTTLE(
1115 get_node()->get_logger(), *get_node()->get_clock(), 1000,
1116 "Failed to add assignment '" << parsed_name << "': " << ex.what());
1117 }
1118}
1119
1120template<typename T>
1121inline void BaseControllerInterface::set_assignment(const std::string& assignment_name, const T& assignment_value) {
1122 modulo_interfaces::msg::Assignment message;
1123 std::shared_ptr<state_representation::ParameterInterface> assignment;
1124 try {
1125 assignment = assignments_map_.get_parameter(assignment_name);
1126 } catch (const state_representation::exceptions::InvalidParameterException&) {
1127 RCLCPP_ERROR_STREAM_THROTTLE(
1128 get_node()->get_logger(), *get_node()->get_clock(), 1000,
1129 "Failed to set assignment '" << assignment_name << "': Assignment does not exist.");
1130 return;
1131 }
1132 try {
1133 assignment->set_parameter_value<T>(assignment_value);
1134 } catch (const state_representation::exceptions::InvalidParameterCastException&) {
1135 RCLCPP_ERROR_STREAM_THROTTLE(
1136 get_node()->get_logger(), *get_node()->get_clock(), 1000,
1137 "Failed to set assignment '" << assignment_name << "': Incompatible value type.");
1138 return;
1139 }
1140 if (assignment_publisher_ == nullptr) {
1141 RCLCPP_ERROR_STREAM_THROTTLE(
1142 get_node()->get_logger(), *get_node()->get_clock(), 1000,
1143 "No assignment publisher configured. Make sure to add assignments `on_init` of the controller.");
1144 return;
1145 }
1146 message.node = get_node()->get_fully_qualified_name();
1147 message.assignment = modulo_core::translators::write_parameter(assignment).to_parameter_msg();
1148 assignment_publisher_->publish(message);
1149}
1150
1151template<typename T>
1152inline T BaseControllerInterface::get_assignment(const std::string& assignment_name) const {
1153 std::shared_ptr<state_representation::ParameterInterface> assignment;
1154 try {
1155 assignment = assignments_map_.get_parameter(assignment_name);
1156 } catch (const state_representation::exceptions::InvalidParameterException&) {
1158 "Failed to get value of assignment '" + assignment_name + "': Assignment does not exist.");
1159 }
1160 try {
1161 return assignment->get_parameter_value<T>();
1162 } catch (const state_representation::exceptions::InvalidParameterCastException&) {
1163 auto expected_type = state_representation::get_parameter_type_name(assignment->get_parameter_type());
1165 "Incompatible type for assignment '" + assignment_name + "' defined with type '" + expected_type + "'.");
1166 }
1167}
1168
1169template<typename T>
1170inline void BaseControllerInterface::publish_transforms(
1171 const std::vector<state_representation::CartesianPose>& transforms, const std::shared_ptr<T>& tf_broadcaster,
1172 bool is_static) {
1173 std::string modifier = is_static ? "static " : "";
1174 if (tf_broadcaster == nullptr) {
1175 RCLCPP_ERROR_STREAM_THROTTLE(
1176 this->get_node()->get_logger(), *this->get_node()->get_clock(), 1000,
1177 "Failed to send " << modifier << "transform: No " << modifier << "TF broadcaster configured.");
1178 return;
1179 }
1180 try {
1181 std::vector<geometry_msgs::msg::TransformStamped> transform_messages;
1182 transform_messages.reserve(transforms.size());
1183 for (const auto& tf : transforms) {
1184 geometry_msgs::msg::TransformStamped transform_message;
1185 modulo_core::translators::write_message(transform_message, tf, this->get_node()->get_clock()->now());
1186 transform_messages.emplace_back(transform_message);
1187 }
1188 tf_broadcaster->sendTransform(transform_messages);
1189 } catch (const std::exception& ex) {
1190 RCLCPP_ERROR_STREAM_THROTTLE(
1191 this->get_node()->get_logger(), *this->get_node()->get_clock(), 1000,
1192 "Failed to send " << modifier << "transform: " << ex.what());
1193 }
1194}
1195
1196}// namespace modulo_controllers
Base controller class to combine ros2_control, control libraries and modulo.
void send_static_transform(const state_representation::CartesianPose &transform)
Send a static transform to TF.
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.
void add_static_tf_broadcaster()
Configure a static transform broadcaster.
rclcpp::QoS get_qos() const
Getter of the Quality of Service attribute.
void add_service_lockfree(const std::string &service_name, const std::function< ControllerServiceResponse(void)> &callback)
void send_static_transforms(const std::vector< state_representation::CartesianPose > &transforms)
Send a vector of static transforms to TF.
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.
void add_tf_broadcaster()
Configure a transform broadcaster.
bool is_active() const
Check if the controller is currently in state active or not.
void send_transform(const state_representation::CartesianPose &transform)
Send a transform to TF.
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 add_assignment(const std::string &assignment_name)
Add an assignment to the map of assignments.
void set_assignment(const std::string &assignment_name, const T &assignment_value)
Set an assignment.
CallbackReturn on_init() override
Declare parameters and register the on_set_parameters callback.
T get_assignment(const std::string &assignment_name) const
Get the value of an assignment.
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 > &parameter, 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 add_tf_listener()
Configure a transform buffer and listener.
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 > &parameter)
Parameter validation function to be redefined by derived controller classes.
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.
void send_transforms(const std::vector< state_representation::CartesianPose > &transforms)
Send a vector of transforms to TF.
An exception class to notify errors when getting the value of an assignment.
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 > &parameter)
Write a ROS Parameter from a ParameterInterface pointer.
Input structure to save topic data in a realtime buffer and timestamps in one object.
Response structure to be returned by controller services.