AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
mission_builder_base.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 "auto_apms_mission/mission_builder_base.hpp"
16
17#include "auto_apms_util/exceptions.hpp"
18
19namespace auto_apms_mission
20{
21
22const std::string MissionBuildHandlerBase::ORCHESTRATOR_EXECUTOR_NAME = _AUTO_APMS_MISSION__ORCHESTRATOR_EXECUTOR_NAME;
23const std::string MissionBuildHandlerBase::MISSION_EXECUTOR_NAME = _AUTO_APMS_MISSION__MISSION_EXECUTOR_NAME;
24const std::string MissionBuildHandlerBase::EVENT_MONITOR_EXECUTOR_NAME =
25 _AUTO_APMS_MISSION__EVENT_MONITOR_EXECUTOR_NAME;
26const std::string MissionBuildHandlerBase::EVENT_HANDLER_EXECUTOR_NAME =
27 _AUTO_APMS_MISSION__EVENT_HANDLER_EXECUTOR_NAME;
28
29MissionBuildHandlerBase::MissionBuildHandlerBase(
30 rclcpp::Node::SharedPtr ros_node_ptr, NodeLoader::SharedPtr tree_node_loader_ptr)
31: TreeBuildHandler("mission_builder", ros_node_ptr, tree_node_loader_ptr)
32{
33}
34
36 const std::string & build_request, const NodeManifest & /*node_manifest*/, const std::string & root_tree_name)
37{
38 try {
39 mission_config_ = MissionConfiguration::fromResourceIdentity(build_request);
40 } catch (const auto_apms_util::exceptions::ResourceIdentityFormatError & e) {
41 RCLCPP_WARN(logger_, "%s", e.what());
42 return false;
43 } catch (const auto_apms_util::exceptions::ResourceError & e) {
44 RCLCPP_WARN(logger_, "%s", e.what());
45 return false;
46 } catch (const auto_apms_util::exceptions::YAMLFormatError & e) {
47 RCLCPP_WARN(logger_, "Invalid YAML format of mission configuration '%s': %s", build_request.c_str(), e.what());
48 return false;
49 }
50 if (!root_tree_name.empty()) {
51 RCLCPP_WARN(logger_, "Argument root_tree_name is not empty. Custom root tree names are not allowed.");
52 return false;
53 }
54
55 // Parse all tree resource identities and replace all empty tree names with the corresponding root tree name
56 auto add_tree_name_to_identity = [this](TreeResource::Identity & identity) {
57 if (identity.tree_name.empty()) {
58 TreeResource r(identity);
59 identity.tree_name = r.getRootTreeName();
60 }
61 };
62 try {
63 for (TreeResource::Identity & identity : mission_config_.bringup) add_tree_name_to_identity(identity);
64 for (TreeResource::Identity & identity : mission_config_.mission) add_tree_name_to_identity(identity);
65 for (auto & [monitor_id, handler_id] : mission_config_.contingency) {
66 add_tree_name_to_identity(monitor_id);
67 add_tree_name_to_identity(handler_id);
68 }
69 for (auto & [monitor_id, handler_id] : mission_config_.emergency) {
70 add_tree_name_to_identity(monitor_id);
71 add_tree_name_to_identity(handler_id);
72 }
73 for (TreeResource::Identity & identity : mission_config_.shutdown) add_tree_name_to_identity(identity);
74 } catch (const auto_apms_util::exceptions::ResourceError & e) {
75 RCLCPP_WARN(logger_, "%s", e.what());
76 return false;
77 }
78
79 return true;
80}
81
82MissionBuildHandlerBase::TreeDocument::TreeElement MissionBuildHandlerBase::buildTree(
83 TreeDocument & doc, TreeBlackboard & bb)
84{
85 using namespace auto_apms_behavior_tree::model;
86
87 // Load orchestrator tree
88 RCLCPP_DEBUG(logger_, "Loading orchestrator tree.");
89 TreeDocument::TreeElement root_tree =
90 doc.newTreeFromResource("auto_apms_mission::orchestrator_base::MissionOrchestrator").makeRoot();
91
92 RCLCPP_DEBUG(logger_, "Configuring orchestrator root blackboard.");
93 configureOrchestratorRootBlackboard(bb);
94
95 TreeDocument::TreeElement is_contingency_tree = doc.getTree("__IsContingency__").removeChildren();
96 TreeDocument::TreeElement is_emergency_tree = doc.getTree("__IsEmergency__").removeChildren();
97
98 if (mission_config_.contingency.empty()) {
99 is_contingency_tree.insertNode<model::AlwaysFailure>();
100 } else {
101 model::Fallback fallback = is_contingency_tree.insertNode<model::Fallback>();
102 for (const auto & [monitor_id, _] : mission_config_.contingency) {
103 fallback.insertNode<model::ScriptCondition>().set_code("event_id == '" + monitor_id.str() + "'");
104 }
105 }
106
107 if (mission_config_.emergency.empty()) {
108 is_emergency_tree.insertNode<model::AlwaysFailure>();
109 } else {
110 model::Fallback fallback = is_emergency_tree.insertNode<model::Fallback>();
111 for (const auto & [monitor_id, _] : mission_config_.emergency) {
112 fallback.insertNode<model::ScriptCondition>().set_code("event_id == '" + monitor_id.str() + "'");
113 }
114 }
115
116 // Bring up
117 if (!mission_config_.bringup.empty()) {
118 RCLCPP_DEBUG(logger_, "Creating bringup subtree.");
119 TreeDocument::TreeElement bringup_tree = doc.getTree("__BringUp__");
120 buildBringUp(bringup_tree, mission_config_.bringup);
121 if (const BT::Result res = bringup_tree.verify(); !res) {
122 throw auto_apms_behavior_tree::exceptions::TreeBuildHandlerError("Bringup tree is not valid: " + res.error());
123 }
124 }
125
126 // Mission
127 RCLCPP_DEBUG(logger_, "Creating mission subtree.");
128 TreeDocument::TreeElement mission_tree = doc.getTree("__RunMission__");
129 buildMission(mission_tree, mission_config_.mission);
130 if (const BT::Result res = mission_tree.verify(); !res) {
131 throw auto_apms_behavior_tree::exceptions::TreeBuildHandlerError("Mission tree is not valid: " + res.error());
132 }
133
134 // Event monitor
135 if (!mission_config_.contingency.empty() || !mission_config_.emergency.empty()) {
136 RCLCPP_DEBUG(logger_, "Creating event monitor subtree.");
137 TreeDocument::TreeElement event_monitor_tree = doc.getTree("__MonitorEvents__");
138 buildEventMonitor(event_monitor_tree, mission_config_.contingency, mission_config_.emergency);
139 if (const BT::Result res = event_monitor_tree.verify(); !res) {
140 throw auto_apms_behavior_tree::exceptions::TreeBuildHandlerError(
141 "Event monitor tree is not valid: " + res.error());
142 }
143 }
144
145 // Contingency Handling
146 if (!mission_config_.contingency.empty()) {
147 RCLCPP_DEBUG(logger_, "Creating contingency handler subtree.");
148 TreeDocument::TreeElement contingency_tree = doc.getTree("__HandleContingency__");
149 buildContingencyHandling(contingency_tree, mission_config_.contingency);
150 if (const BT::Result res = contingency_tree.verify(); !res) {
151 throw auto_apms_behavior_tree::exceptions::TreeBuildHandlerError(
152 "Contingency handling tree is not valid: " + res.error());
153 }
154 }
155
156 // Emergency Handling
157 if (!mission_config_.emergency.empty()) {
158 RCLCPP_DEBUG(logger_, "Creating emergency handler subtree.");
159 TreeDocument::TreeElement emergency_tree = doc.getTree("__HandleEmergency__");
160 buildEmergencyHandling(emergency_tree, mission_config_.emergency);
161 if (const BT::Result res = emergency_tree.verify(); !res) {
162 throw auto_apms_behavior_tree::exceptions::TreeBuildHandlerError(
163 "Emergency handling tree is not valid: " + res.error());
164 }
165 }
166
167 // Shut down
168 if (!mission_config_.shutdown.empty()) {
169 RCLCPP_DEBUG(logger_, "Creating shutdown subtree.");
170 TreeDocument::TreeElement shutdown_tree = doc.getTree("__ShutDown__");
171 buildShutDown(shutdown_tree, mission_config_.shutdown);
172 if (const BT::Result res = shutdown_tree.verify(); !res) {
173 throw auto_apms_behavior_tree::exceptions::TreeBuildHandlerError("Shutdown tree is not valid: " + res.error());
174 }
175 }
176
177 // Write tree for debugging purposes
178 // doc.writeToFile("/home/robin/Desktop/px4-ros2-env/src/dep/auto-apms/test.xml");
179
180 return root_tree;
181}
182
183void MissionBuildHandlerBase::buildBringUp(
184 TreeDocument::TreeElement & sub_tree, const std::vector<TreeResource::Identity> & trees)
185{
186 sub_tree.removeChildren();
187 for (const TreeResource::Identity & r : trees) {
188 sub_tree.insertTreeFromResource(r);
189 }
190}
191
192void MissionBuildHandlerBase::buildEventMonitor(
193 TreeDocument::TreeElement & sub_tree,
194 const std::vector<std::pair<TreeResource::Identity, TreeResource::Identity>> & contingencies,
195 const std::vector<std::pair<TreeResource::Identity, TreeResource::Identity>> & emergencies)
196{
197 std::vector<TreeResource::Identity> sorted_monitor_ids;
198
199 // Emergencies have higher priority than contingencies (they are inserted to the vector first)
200 for (const auto & [monitor_id, _] : emergencies) {
201 sorted_monitor_ids.push_back(monitor_id);
202 }
203 for (const auto & [monitor_id, _] : contingencies) {
204 if (!auto_apms_util::contains(sorted_monitor_ids, monitor_id)) sorted_monitor_ids.push_back(monitor_id);
205 }
206
207 // Assemble an extra document that holds all monitor subtrees
208 TreeDocument monitor_doc;
209 for (const TreeResource::Identity & monitor_id : sorted_monitor_ids) {
210 if (!monitor_doc.hasTreeName(monitor_id.str())) {
211 monitor_doc.newTreeFromResource(monitor_id).setName(monitor_id.str());
212 }
213 }
214
215 model::Fallback fallback = sub_tree.removeChildren().insertNode<model::Fallback>();
216 for (const TreeResource::Identity & monitor_id : sorted_monitor_ids) {
217 // We want the monitor to have its own blackboard, so we create a subtree node
218 const std::string monitor_tree_name = monitor_id.str();
219 model::SubTree subtree_node = fallback.getParentDocument().hasTreeName(monitor_tree_name)
220 ? fallback.insertNode<model::SubTree>(monitor_tree_name)
221 : fallback.insertNode<model::SubTree>(monitor_doc.getTree(monitor_tree_name));
222 subtree_node.setConditionalScript(BT::PostCond::ON_SUCCESS, "event_id := '" + monitor_tree_name + "'");
223 }
224}
225
226void MissionBuildHandlerBase::buildContingencyHandling(
227 TreeDocument::TreeElement & sub_tree,
228 const std::vector<std::pair<TreeResource::Identity, TreeResource::Identity>> & contingencies)
229{
230 // Assemble an extra document that holds all handler subtrees
231 TreeDocument handler_doc;
232 for (const auto & [_, handler_id] : contingencies) {
233 if (!handler_doc.hasTreeName(handler_id.str())) {
234 handler_doc.newTreeFromResource(handler_id).setName(handler_id.str());
235 }
236 }
237
238 model::ReactiveFallback fallback = sub_tree.removeChildren().insertNode<model::ReactiveFallback>();
239
240 // With the highest priority (first child) we acknowledge when the event is reset (e.g. we abort the current action
241 // and resume)
242 fallback.insertNode<model::AlwaysFailure>()
243 .setName("ResetEventHandler")
244 .setConditionalScript(BT::PreCond::SUCCESS_IF, "event_id == ''");
245
246 for (const auto & [monitor_id, handler_id] : contingencies) {
247 model::AsyncSequence seq =
248 fallback.insertNode<model::AsyncSequence>().setName("EventHandler (" + handler_id.str() + ")");
249 seq.insertNode<model::ScriptCondition>().set_code("event_id == '" + monitor_id.str() + "'");
250
251 // We want the handler to have its own blackboard, so we create a subtree node
252 const std::string handler_tree_name = handler_id.str();
253 if (seq.getParentDocument().hasTreeName(handler_tree_name)) {
254 seq.insertNode<model::SubTree>(handler_tree_name);
255 } else {
256 // Automatically add the handler tree if it doesn't exist yet
257 seq.insertNode<model::SubTree>(handler_doc.getTree(handler_tree_name));
258 }
259
260 // Once the contingency handler subtree is done, the tree idles as long as the respective event is still active
261 seq.insertNode<model::KeepRunningUntilFailure>()
262 .insertNode<model::ScriptCondition>()
263 .setName("IsEventStillActive")
264 .set_code("event_id == '" + monitor_id.str() + "'");
265 }
266}
267
268void MissionBuildHandlerBase::buildEmergencyHandling(
269 TreeDocument::TreeElement & sub_tree,
270 const std::vector<std::pair<TreeResource::Identity, TreeResource::Identity>> & emergencies)
271{
272 // Assemble an extra document that holds all handler subtrees
273 TreeDocument handler_doc;
274 for (const auto & [_, handler_id] : emergencies) {
275 if (!handler_doc.hasTreeName(handler_id.str())) {
276 handler_doc.newTreeFromResource(handler_id).setName(handler_id.str());
277 }
278 }
279
280 model::ReactiveFallback fallback = sub_tree.removeChildren().insertNode<model::ReactiveFallback>();
281 for (const auto & [monitor_id, handler_id] : emergencies) {
282 model::AsyncSequence seq =
283 fallback.insertNode<model::AsyncSequence>().setName("EventHandler (" + handler_id.str() + ")");
284 seq.insertNode<model::ScriptCondition>().set_code("event_id == '" + monitor_id.str() + "'");
285
286 // We want the handler to have its own blackboard, so we create a subtree node
287 const std::string handler_tree_name = handler_id.str();
288 if (seq.getParentDocument().hasTreeName(handler_tree_name)) {
289 seq.insertNode<model::SubTree>(handler_tree_name);
290 } else {
291 // Automatically add the handler tree if it doesn't exist yet
292 seq.insertNode<model::SubTree>(handler_doc.getTree(handler_tree_name));
293 }
294 }
295}
296
297void MissionBuildHandlerBase::buildShutDown(
298 TreeDocument::TreeElement & sub_tree, const std::vector<TreeResource::Identity> & trees)
299{
300 sub_tree.removeChildren();
301 for (const TreeResource::Identity & r : trees) {
302 sub_tree.insertTreeFromResource(r);
303 }
304}
305
306void MissionBuildHandlerBase::configureOrchestratorRootBlackboard(TreeBlackboard & /*bb*/) {}
307
308} // namespace auto_apms_mission
const rclcpp::Logger logger_
ROS 2 logger initialized with the name of the build handler.
TreeElement & makeRoot()
Set this behavior tree as the root tree of the parent document.
TreeElement newTreeFromResource(const TreeResource &resource, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one the trees found inside a part...
TreeElement getTree(const std::string &tree_name)
Get the corresponding behavior tree element for a tree inside this document.
std::string getRootTreeName() const
Get the name of the root tree of this behavior tree resource.
Subtree behavior tree node model.
bool setBuildRequest(const std::string &build_request, const NodeManifest &node_manifest, const std::string &root_tree_name) override final
Specify the behavior tree build request encoded in a string.
TreeDocument::TreeElement buildTree(TreeDocument &doc, TreeBlackboard &bb) override final
Build the behavior tree specified before.
bool contains(const ContainerT< ValueT, AllocatorT > &c, const ValueT &val)
Check whether a particular container structure contains a value.
Definition container.hpp:36
Models for all available behavior tree nodes.
Mission design utilities incorporating behavior trees to model the complexity of arbitrary operations...