AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
set_parameter.cpp
1// Copyright 2024 Robin Müller
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include <type_traits>
16
17#include "auto_apms_behavior_tree/util/parameter.hpp"
18#include "auto_apms_behavior_tree_core/node.hpp"
19#include "rcl_interfaces/msg/parameter.hpp"
20#include "rcl_interfaces/msg/parameter_type.hpp"
21#include "rcl_interfaces/msg/parameter_value.hpp"
22#include "rcl_interfaces/srv/set_parameters.hpp"
23
24#define INPUT_KEY_PARAM_NAME "parameter"
25#define INPUT_KEY_PARAM_VALUE "value"
26#define INPUT_KEY_NODE_NAME "node"
27
29{
30
31template <typename T>
32class SetParameterTemplate : public core::RosServiceNode<rcl_interfaces::srv::SetParameters>
33{
34public:
35 SetParameterTemplate(const std::string & instance_name, const Config & config, const Context & context)
36 : RosServiceNode(instance_name, config, context)
37 {
38 // If input port is empty or not provided, refer to this ROS 2 node as the target
39 std::string node_name = context_.getFullyQualifiedRosNodeName();
40 if (
41 config.input_ports.find(INPUT_KEY_NODE_NAME) != config.input_ports.end() &&
42 !config.input_ports.at(INPUT_KEY_NODE_NAME).empty()) {
43 node_name = config.input_ports.at(INPUT_KEY_NODE_NAME);
44 }
45 createClient(node_name + "/set_parameters");
46 }
47
48 static BT::PortsList providedPorts()
49 {
50 // We do not use the default port for the service name
51
52 // There is no string conversion function for variables that are type initialized using the value port if the
53 // BT::Any version is used. To prevent errors when using these variables in e.g. the scripting language we have to
54 // set the type to BT::AnyTypeAllowed to truly indicate that the type is not set by this port
55 using AnyType = typename std::conditional_t<std::is_same_v<BT::Any, T>, BT::AnyTypeAllowed, T>;
56 return {
57 BT::InputPort<std::string>(
58 INPUT_KEY_NODE_NAME, "Name of the targeted ROS 2 node. Leave empty to target this executor's node."),
59 BT::InputPort<AnyType>(INPUT_KEY_PARAM_VALUE, "Value of the parameter to be set."),
60 BT::InputPort<std::string>(INPUT_KEY_PARAM_NAME, "Name of the parameter to be set."),
61 };
62 }
63
64 bool setRequest(Request::SharedPtr & request) override final
65 {
66 rcl_interfaces::msg::Parameter parameter;
67 const BT::Expected<std::string> expected_name = getInput<std::string>(INPUT_KEY_PARAM_NAME);
68 if (!expected_name || expected_name.value().empty()) {
69 RCLCPP_ERROR(
70 logger_, "%s - Parameter name must not be empty.", context_.getFullyQualifiedTreeNodeName(this).c_str());
71 RCLCPP_DEBUG_EXPRESSION(
72 logger_, !expected_name, "%s - Error message: %s", context_.getFullyQualifiedTreeNodeName(this).c_str(),
73 expected_name.error().c_str());
74 return false;
75 }
76 parameter.name = expected_name.value();
77
78 rclcpp::ParameterType inferred_param_type = rclcpp::PARAMETER_NOT_SET;
79 if constexpr (!std::is_same_v<BT::Any, T>) {
80 inferred_param_type = rclcpp::ParameterValue(T()).get_type();
81 }
82
83 rcl_interfaces::msg::ParameterValue param_val;
84 const BT::PortsRemapping::iterator it = config().input_ports.find(INPUT_KEY_PARAM_VALUE);
85 if (it == config().input_ports.end() || it->second.empty()) {
86 if constexpr (std::is_same_v<BT::Any, T>) {
87 throw exceptions::RosNodeError(
88 context_.getFullyQualifiedTreeNodeName(this) + " - Parameter value must not be empty.");
89 } else {
90 // Use default value of rcl_interfaces::msg::Parameter if input is not specified (Don't set param_val.value)
91 param_val.type = inferred_param_type;
92 }
93 } else {
94 // If input port is a literal, the user must use one of the statically typed versions of SetParameter, since
95 // it's impossible to know what type the parameter to be set should have
96 if (!isBlackboardPointer(it->second) && std::is_same_v<BT::Any, T>) {
97 throw exceptions::RosNodeError(
98 context_.getFullyQualifiedTreeNodeName(this) +
99 " - Cannot infer type from literal string. Use one of the type specialized versions of SetParameter "
100 "instead.");
101 }
102
103 const BT::Expected<T> expected_entry = getInput<T>(INPUT_KEY_PARAM_VALUE);
104 if (!expected_entry) {
105 RCLCPP_ERROR(
106 logger_, "%s - %s", context_.getFullyQualifiedTreeNodeName(this).c_str(), expected_entry.error().c_str());
107 return false;
108 }
109 const BT::Expected<rclcpp::ParameterValue> expected_param_val =
110 createParameterValueFromAny(BT::Any(expected_entry.value()), inferred_param_type);
111 if (!expected_param_val) {
112 // Conversion might not be possible. In this case, log error message and reject to set parameter.
113 RCLCPP_ERROR(
114 logger_, "%s - %s", context_.getFullyQualifiedTreeNodeName(this).c_str(), expected_param_val.error().c_str());
115 return false;
116 }
117 param_val = expected_param_val.value().to_value_msg();
118 }
119
120 parameter.value = param_val;
121 requested_parameter_ = rclcpp::Parameter(parameter.name, parameter.value);
122 request->parameters.push_back(parameter);
123 return true;
124 }
125
126 BT::NodeStatus onResponseReceived(const Response::SharedPtr & response) override final
127 {
128 const rcl_interfaces::msg::SetParametersResult & result = response->results[0];
129 if (!result.successful) {
130 RCLCPP_ERROR(
131 logger_, "Failed to set parameter %s = %s (Type: %s) via service '%s': %s",
132 requested_parameter_.get_name().c_str(), requested_parameter_.value_to_string().c_str(),
133 requested_parameter_.get_type_name().c_str(), getServiceName().c_str(), result.reason.c_str());
134 return BT::NodeStatus::FAILURE;
135 }
136 return BT::NodeStatus::SUCCESS;
137 }
138
139private:
140 rclcpp::Parameter requested_parameter_;
141};
142
143// Automatically infer the parameter type from BT::Any
144class SetParameter : public SetParameterTemplate<BT::Any>
145{
146public:
147 using SetParameterTemplate::SetParameterTemplate;
148};
149
150class SetParameterBool : public SetParameterTemplate<bool>
151{
152public:
153 using SetParameterTemplate::SetParameterTemplate;
154};
155
156class SetParameterInt : public SetParameterTemplate<int64_t>
157{
158public:
159 using SetParameterTemplate::SetParameterTemplate;
160};
161
162class SetParameterDouble : public SetParameterTemplate<double>
163{
164public:
165 using SetParameterTemplate::SetParameterTemplate;
166};
167
168class SetParameterString : public SetParameterTemplate<std::string>
169{
170public:
171 using SetParameterTemplate::SetParameterTemplate;
172};
173
174class SetParameterByteVec : public SetParameterTemplate<std::vector<uint8_t>>
175{
176public:
177 using SetParameterTemplate::SetParameterTemplate;
178};
179
180class SetParameterBoolVec : public SetParameterTemplate<std::vector<bool>>
181{
182public:
183 using SetParameterTemplate::SetParameterTemplate;
184};
185
186class SetParameterIntVec : public SetParameterTemplate<std::vector<int64_t>>
187{
188public:
189 using SetParameterTemplate::SetParameterTemplate;
190};
191
192class SetParameterDoubleVec : public SetParameterTemplate<std::vector<double>>
193{
194public:
195 using SetParameterTemplate::SetParameterTemplate;
196};
197
198class SetParameterStringVec : public SetParameterTemplate<std::vector<std::string>>
199{
200public:
201 using SetParameterTemplate::SetParameterTemplate;
202};
203
204} // namespace auto_apms_behavior_tree
205
206AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameter)
207AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameterBool)
208AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameterInt)
209AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameterDouble)
210AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameterString)
211AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameterByteVec)
212AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameterBoolVec)
213AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameterIntVec)
214AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameterDoubleVec)
215AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::SetParameterStringVec)
Generic behavior tree node wrapper for a ROS 2 service client.
RosServiceNode(const std::string &instance_name, const Config &config, Context context)
#define AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(type)
Macro for registering a behavior tree node plugin.
Definition node.hpp:44
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32
BT::Expected< rclcpp::ParameterValue > createParameterValueFromAny(const BT::Any &any, rclcpp::ParameterType type)
Convert a BT::Any object to a ROS 2 parameter value.
Definition parameter.cpp:51