AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
create_node_model.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 <algorithm>
16#include <filesystem>
17#include <fstream>
18#include <iostream>
19#include <map>
20
21#include "auto_apms_behavior_tree_core/node/node_manifest.hpp"
22#include "auto_apms_behavior_tree_core/node/node_registration_interface.hpp"
23#include "auto_apms_behavior_tree_core/tree/tree_document.hpp"
24#include "auto_apms_util/logging.hpp"
25#include "auto_apms_util/string.hpp"
26#include "behaviortree_cpp/xml_parsing.h"
27#include "class_loader/class_loader.hpp"
28
29using namespace auto_apms_behavior_tree;
30
31int main(int argc, char ** argv)
32{
33 if (argc < 5) {
34 std::cerr << "create_node_model: Missing inputs! The program requires: \n\t1.) The path to the node plugin "
35 "manifest.\n\t2. The exhaustive list of libraries to be loaded by ClassLoader (Separated by "
36 "';').\n\t3.) Registration type mappings (Separated by ';', format: "
37 "'class_name=registration_type').\n\t4.) The xml file to store the model.\n";
38 std::cerr << "Usage: create_node_model <manifest_file> <library_paths> <registration_types> <output_file>.\n";
39 return EXIT_FAILURE;
40 }
41
42 try {
43 const std::filesystem::path manifest_file = std::filesystem::absolute(auto_apms_util::trimWhitespaces(argv[1]));
44 const std::vector<std::string> library_paths = auto_apms_util::splitString(argv[2], ";", true);
45 const std::vector<std::string> registration_type_entries = auto_apms_util::splitString(argv[3], ";", true);
46 const std::filesystem::path output_file = std::filesystem::absolute(auto_apms_util::trimWhitespaces(argv[4]));
47
48 // Build class_name -> registration_type mapping
49 std::map<std::string, std::string> registration_type_map;
50 for (const auto & entry : registration_type_entries) {
51 const auto eq_pos = entry.find('=');
52 if (eq_pos == std::string::npos) {
53 throw std::runtime_error(
54 "Invalid registration type entry '" + entry + "'. Expected format: 'class_name=registration_type'.");
55 }
56 registration_type_map[entry.substr(0, eq_pos)] = entry.substr(eq_pos + 1);
57 }
58
59 if (!std::filesystem::exists(manifest_file)) {
60 throw std::runtime_error("File manifest_file must exist.");
61 }
62 if (library_paths.empty()) {
63 throw std::runtime_error("Argument library_paths must not be empty.");
64 }
65 if (!output_file.has_filename()) {
66 throw std::runtime_error("Output file path must include a filename.");
67 }
68
69 // Ensure correct extensions
70 if (output_file.extension() != ".xml") {
71 throw std::runtime_error("Output file '" + output_file.string() + "' has wrong extension. Must be '.xml'.");
72 }
73
74 const rclcpp::Logger logger = rclcpp::get_logger("create_node_model__" + output_file.stem().string());
75
76 BT::BehaviorTreeFactory factory;
77 const auto manifest = core::NodeManifest::fromFile(manifest_file.string());
78
83
84 // Instantiate loaders for all libraries in library_paths (We don't use class_loader::MultiLibraryClassLoader
85 // because we want to keep track of the libraries that the nodes come from for debugging purposes)
86 std::vector<std::unique_ptr<class_loader::ClassLoader>> class_loaders;
87 for (const auto & path : library_paths) {
88 try {
89 class_loaders.push_back(std::make_unique<class_loader::ClassLoader>(path));
90 } catch (const std::exception & e) {
91 throw std::runtime_error("Failed to load library '" + path + "': " + e.what());
92 }
93 }
94
95 // Walk manifest and register all plugins with BT::BehaviorTreeFactory
96 for (const auto & [node_name, params] : manifest.map()) {
97 // Look up the registration type from the mapping provided by create_node_manifest
98 const auto reg_type_it = registration_type_map.find(params.class_name);
99 if (reg_type_it == registration_type_map.end()) {
100 throw std::runtime_error(
101 "Node '" + node_name + "' (Class: " + params.class_name +
102 ") has no registration type mapping. Please rebuild the package.");
103 }
104 const std::string & registration_type = reg_type_it->second;
105
106 class_loader::ClassLoader * loader = nullptr;
107 for (const auto & l : class_loaders) {
108 if (l->isClassAvailable<core::NodeRegistrationInterface>(registration_type)) {
109 loader = l.get();
110 }
111 }
112
113 if (!loader) {
114 throw std::runtime_error(
115 "Node '" + node_name + "' (Class: " + params.class_name +
116 ") cannot be registered, because the registration class '" + registration_type +
117 "' could not be found in any loaded library. Check that the class name is spelled correctly and "
118 "the node is registered by calling auto_apms_behavior_tree_register_nodes() in the CMakeLists.txt of the "
119 "corresponding package. Also make sure that you called the "
120 "AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE macro or PLUGINLIB_EXPORT_CLASS in the source file.");
121 }
122
123 RCLCPP_DEBUG(
124 logger, "Registering behavior tree node '%s' (Class: %s) from library %s.", node_name.c_str(),
125 params.class_name.c_str(), loader->getLibraryPath().c_str());
126
127 try {
128 const auto plugin_instance = loader->createUniqueInstance<core::NodeRegistrationInterface>(registration_type);
129 rclcpp::Node::SharedPtr node = nullptr;
130 rclcpp::CallbackGroup::SharedPtr group = nullptr;
131 rclcpp::executors::SingleThreadedExecutor::SharedPtr executor = nullptr;
132 core::RosNodeContext ros_node_context(
133 node, group, executor, params); // Values don't matter when not instantiating it
134 plugin_instance->registerWithBehaviorTreeFactory(factory, node_name, &ros_node_context);
135 } catch (const std::exception & e) {
136 throw std::runtime_error(
137 "Failed to register node '" + node_name + " (Class: " + params.class_name + ")': " + e.what());
138 }
139 }
140
141 // Generate node model XML from the factory
142 const std::string model_xml = BT::writeTreeNodesModelXML(factory);
143
144 // Parse the XML into a document to extract the NodeModelMap later
145 tinyxml2::XMLDocument model_doc;
146 if (model_doc.Parse(model_xml.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
147 throw std::runtime_error("Error parsing the generated node model XML: " + std::string(model_doc.ErrorStr()));
148 }
149
150 // Convert to NodeModelMap with hidden ports applied
151 auto model_map = core::TreeDocument::getNodeModel(model_doc, manifest);
152
153 // Create a TreeDocument and add the filtered node model
155 doc.addNodeModel(model_map);
156
157 // Write to file
158 doc.writeToFile(output_file.string());
159 } catch (const std::exception & e) {
160 std::cerr << "ERROR (create_node_model): " << e.what() << "\n";
161 return EXIT_FAILURE;
162 }
163
164 return EXIT_SUCCESS;
165}
Interface used for registering behavior tree node plugins.
Additional parameters specific to ROS 2 determined at runtime by TreeBuilder.
Document Object Model (DOM) for the behavior tree XML schema. This class offers a programmatic approa...
void writeToFile(const std::string &path) const
Write the XML of this tree document to a file.
TreeDocument & addNodeModel(NodeModelMap model_map)
Add a behavior tree node model element to the document by parsing the contents of model_map.
static NodeModelMap getNodeModel(tinyxml2::XMLDocument &doc, const NodeManifest &manifest)
Convert a behavior tree node model document to the corresponding data structure.
std::string trimWhitespaces(const std::string &str)
Trim whitespaces from both ends of a string.
Definition string.cpp:84
std::vector< std::string > splitString(const std::string &str, const std::string &delimiter, bool remove_empty=true)
Split a string into multiple tokens using a specific delimiter string (Delimiter may consist of multi...
Definition string.cpp:24
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32