AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
lifecycle_nodes.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// http://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 "auto_apms_behavior_tree_core/node.hpp"
16#include "lifecycle_msgs/msg/state.hpp"
17#include "lifecycle_msgs/msg/transition.hpp"
18#include "lifecycle_msgs/srv/change_state.hpp"
19#include "lifecycle_msgs/srv/get_state.hpp"
20
21#define INPUT_KEY_NODE_NAME "node"
22#define INPUT_KEY_TRANSITION "transition"
23#define OUTPUT_KEY_STATE_ID "state_id"
24#define OUTPUT_KEY_STATE_LABEL "state_label"
25
27{
28
39class LifecycleGetState : public core::RosServiceNode<lifecycle_msgs::srv::GetState>
40{
41public:
42 using RosServiceNode::RosServiceNode;
43
44 static BT::PortsList providedPorts()
45 {
46 // We do not use the default 'topic' port — the service name is derived from the 'node' port
47 // via the NodeRegistrationOptions::topic pattern "(input:node)/get_state".
48 return {
49 BT::InputPort<std::string>(INPUT_KEY_NODE_NAME, "Name of the targeted lifecycle node."),
50 BT::OutputPort<uint8_t>(OUTPUT_KEY_STATE_ID, "Numeric ID of the current lifecycle state."),
51 BT::OutputPort<std::string>(OUTPUT_KEY_STATE_LABEL, "Human-readable label of the current lifecycle state."),
52 };
53 }
54
55 bool setRequest(Request::SharedPtr & /*request*/) override final
56 {
57 // Initialize output ports with defaults so downstream nodes always read valid values
58 setOutput(OUTPUT_KEY_STATE_ID, uint8_t{lifecycle_msgs::msg::State::PRIMARY_STATE_UNKNOWN});
59 setOutput(OUTPUT_KEY_STATE_LABEL, std::string{});
60 return true;
61 }
62
63 BT::NodeStatus onResponseReceived(const Response::SharedPtr & response) override final
64 {
65 if (const BT::Result res = setOutput(OUTPUT_KEY_STATE_ID, response->current_state.id); !res) {
66 RCLCPP_ERROR(logger_, "%s - %s", context_.getFullyQualifiedTreeNodeName(this).c_str(), res.error().c_str());
67 return BT::NodeStatus::FAILURE;
68 }
69 if (const BT::Result res = setOutput(OUTPUT_KEY_STATE_LABEL, response->current_state.label); !res) {
70 RCLCPP_ERROR(logger_, "%s - %s", context_.getFullyQualifiedTreeNodeName(this).c_str(), res.error().c_str());
71 return BT::NodeStatus::FAILURE;
72 }
73 return BT::NodeStatus::SUCCESS;
74 }
75};
76
92template <int TRANSITION_ID = -1>
93class ChangeStateTemplate : public core::RosServiceNode<lifecycle_msgs::srv::ChangeState>
94{
95public:
96 using RosServiceNode::RosServiceNode;
97
98 static BT::PortsList providedPorts()
99 {
100 // We do not use the default 'topic' port — the service name is derived from the 'node' port
101 // via the NodeRegistrationOptions::topic pattern "(input:node)/change_state".
102 BT::PortsList ports = {
103 BT::InputPort<std::string>(INPUT_KEY_NODE_NAME, "Name of the targeted lifecycle node."),
104 };
105 if constexpr (TRANSITION_ID < 0) {
106 ports.insert(
107 BT::InputPort<int>(
108 INPUT_KEY_TRANSITION,
109 "ID of the lifecycle state machine transition to execute. Refer to lifecycle_msgs/msg/Transition for "
110 "available values (configure=1, cleanup=2, activate=3, deactivate=4, "
111 "unconfigured_shutdown=5, inactive_shutdown=6, active_shutdown=7)."));
112 }
113 return ports;
114 }
115
116 bool setRequest(Request::SharedPtr & request) override final
117 {
118 if constexpr (TRANSITION_ID >= 0) {
119 requested_transition_id_ = static_cast<uint8_t>(TRANSITION_ID);
120 } else {
121 const BT::Expected<int> expected = getInput<int>(INPUT_KEY_TRANSITION);
122 if (!expected) {
123 RCLCPP_ERROR(
124 logger_, "%s - %s", context_.getFullyQualifiedTreeNodeName(this).c_str(), expected.error().c_str());
125 return false;
126 }
127 if (expected.value() < 0 || expected.value() > 255) {
128 RCLCPP_ERROR(
129 logger_, "%s - Transition ID %d is out of the valid range [0, 255].",
130 context_.getFullyQualifiedTreeNodeName(this).c_str(), expected.value());
131 return false;
132 }
133 requested_transition_id_ = static_cast<uint8_t>(expected.value());
134 }
135 request->transition.id = requested_transition_id_;
136 return true;
137 }
138
139 BT::NodeStatus onResponseReceived(const Response::SharedPtr & response) override final
140 {
141 if (!response->success) {
142 RCLCPP_ERROR(
143 logger_, "%s - Failed to execute lifecycle transition (id: %u) via service '%s'.",
144 context_.getFullyQualifiedTreeNodeName(this).c_str(), static_cast<unsigned>(requested_transition_id_),
145 getServiceName().c_str());
146 return BT::NodeStatus::FAILURE;
147 }
148 return BT::NodeStatus::SUCCESS;
149 }
150
151private:
152 uint8_t requested_transition_id_{0};
153};
154
155// Generic version — transition ID is read from the 'transition' input port at tick time
156class LifecycleChangeState : public ChangeStateTemplate<-1>
157{
158public:
159 using ChangeStateTemplate::ChangeStateTemplate;
160};
161
162// Convenience specializations for the primary lifecycle transitions
163
165class LifecycleConfigure : public ChangeStateTemplate<lifecycle_msgs::msg::Transition::TRANSITION_CONFIGURE>
166{
167public:
168 using ChangeStateTemplate::ChangeStateTemplate;
169};
170
172class LifecycleCleanup : public ChangeStateTemplate<lifecycle_msgs::msg::Transition::TRANSITION_CLEANUP>
173{
174public:
175 using ChangeStateTemplate::ChangeStateTemplate;
176};
177
179class LifecycleActivate : public ChangeStateTemplate<lifecycle_msgs::msg::Transition::TRANSITION_ACTIVATE>
180{
181public:
182 using ChangeStateTemplate::ChangeStateTemplate;
183};
184
186class LifecycleDeactivate : public ChangeStateTemplate<lifecycle_msgs::msg::Transition::TRANSITION_DEACTIVATE>
187{
188public:
189 using ChangeStateTemplate::ChangeStateTemplate;
190};
191
192} // namespace auto_apms_behavior_tree
193
195AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(auto_apms_behavior_tree::LifecycleChangeState)
Sends the 'activate' transition (id=3) to a lifecycle node.
Sends the 'cleanup' transition (id=2) to a lifecycle node.
Sends the 'configure' transition (id=1) to a lifecycle node.
Sends the 'deactivate' transition (id=4) to a lifecycle node.
Retrieves the current lifecycle state of a ROS 2 managed node.
Generic behavior tree node wrapper for a ROS 2 service client.
#define AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE(type)
Macro for registering a behavior tree node plugin.
Definition node.hpp:46
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32