AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
node_manifest.cpp
Go to the documentation of this file.
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
16
17#include <fstream>
18#include <algorithm>
19
21#include "yaml-cpp/yaml.h"
22
24namespace YAML
25{
26template <>
27struct convert<auto_apms_behavior_tree::NodeManifest::ParamMap>
28{
30 static Node encode(const Manifest::ParamMap& rhs)
31 {
32 Node node;
33 for (const auto& [name, params] : rhs)
34 {
35 Node params_node;
36 params_node[Manifest::PARAM_NAME_CLASS] = params.class_name;
37 params_node[Manifest::PARAM_NAME_PACKAGE] = params.package;
38 params_node[Manifest::PARAM_NAME_LIBRARY] = params.library;
39 params_node[Manifest::PARAM_NAME_PORT] = params.port;
40 params_node[Manifest::PARAM_NAME_WAIT_TIMEOUT] = params.wait_timeout.count();
41 params_node[Manifest::PARAM_NAME_REQUEST_TIMEOUT] = params.request_timeout.count();
42 node[name] = params_node;
43 }
44 return node;
45 }
46 static bool decode(const Node& node, Manifest::ParamMap& lhs)
47 {
48 if (!node.IsMap())
49 throw std::runtime_error("Root YAML::Node must be map but is type " + std::to_string(node.Type()) +
50 " (0: Undefined - 1: Null - 2: Scalar - 3: Sequence - 4: Map).");
51
52 for (YAML::const_iterator it = node.begin(); it != node.end(); ++it)
53 {
54 const auto& name = it->first.as<std::string>();
55 const auto& params_node = it->second;
56 if (!params_node.IsMap())
57 throw std::runtime_error("Params YAML::Node must be map but is type " + std::to_string(params_node.Type()) +
58 " (0: Undefined - 1: Null - 2: Scalar - 3: Sequence - 4: Map).");
59
60 Manifest::Params params;
61 for (YAML::const_iterator p = params_node.begin(); p != params_node.end(); ++p)
62 {
63 const auto param_key = p->first.as<std::string>();
64 const auto& val = p->second;
65 if (!val.IsScalar())
66 throw std::runtime_error("Value for key '" + param_key + "' must be scalar but is type " +
67 std::to_string(val.Type()) +
68 " (0: Undefined - 1: Null - 2: Scalar - 3: Sequence - 4: Map).");
69
70 if (param_key == Manifest::PARAM_NAME_CLASS)
71 {
72 params.class_name = val.as<std::string>();
73 continue;
74 }
75 if (param_key == Manifest::PARAM_NAME_PORT)
76 {
77 params.port = val.as<std::string>();
78 continue;
79 }
80 if (param_key == Manifest::PARAM_NAME_PACKAGE)
81 {
82 params.package = val.as<std::string>();
83 continue;
84 }
85 if (param_key == Manifest::PARAM_NAME_LIBRARY)
86 {
87 params.library = val.as<std::string>();
88 continue;
89 }
90 if (param_key == Manifest::PARAM_NAME_WAIT_TIMEOUT)
91 {
92 params.wait_timeout = std::chrono::duration<double>(val.as<double>());
93 continue;
94 }
95 if (param_key == Manifest::PARAM_NAME_REQUEST_TIMEOUT)
96 {
97 params.request_timeout = std::chrono::duration<double>(val.as<double>());
98 continue;
99 }
100 // Unkown parameter
101 throw std::runtime_error("Unkown parameter '" + param_key + "'.");
102 }
103 lhs[name] = params;
104 }
105 return true;
106 }
107};
108} // namespace YAML
110
112{
113
114// clang-format off
115const std::string NodeManifest::PARAM_NAME_CLASS = _AUTO_APMS_BEHAVIOR_TREE__NODE_MANIFEST_PARAM_CLASS;
116const std::string NodeManifest::PARAM_NAME_PACKAGE = _AUTO_APMS_BEHAVIOR_TREE__NODE_MANIFEST_PARAM_PACKAGE;
117const std::string NodeManifest::PARAM_NAME_LIBRARY = _AUTO_APMS_BEHAVIOR_TREE__NODE_MANIFEST_PARAM_LIBRARY;
118const std::string NodeManifest::PARAM_NAME_PORT = _AUTO_APMS_BEHAVIOR_TREE__NODE_MANIFEST_PARAM_PORT;
119const std::string NodeManifest::PARAM_NAME_REQUEST_TIMEOUT = _AUTO_APMS_BEHAVIOR_TREE__NODE_MANIFEST_PARAM_REQUEST_TIMEOUT;
120const std::string NodeManifest::PARAM_NAME_WAIT_TIMEOUT = _AUTO_APMS_BEHAVIOR_TREE__NODE_MANIFEST_PARAM_WAIT_TIMEOUT;
121// clang-format on
122
123NodeManifest::NodeManifest(const ParamMap& param_map) : param_map_{ param_map }
124{
125}
126
127NodeManifest NodeManifest::fromFiles(const std::vector<std::string>& file_paths)
128{
129 NodeManifest manifest;
130 if (file_paths.empty())
131 return manifest;
132 manifest = fromFile(file_paths[0]);
133 for (size_t i = 1; i < file_paths.size(); ++i)
134 {
135 manifest.merge(fromFile(file_paths[i]));
136 }
137 return manifest;
138}
139
140NodeManifest NodeManifest::fromFile(const std::string& file_path)
141{
142 return YAML::LoadFile(file_path).as<ParamMap>();
143}
144
145NodeManifest NodeManifest::fromString(const std::string& manifest_str)
146{
147 const bool empty =
148 std::all_of(manifest_str.begin(), manifest_str.end(), [](unsigned char c) { return std::isspace(c); });
149 return empty ? ParamMap() : YAML::Load(manifest_str).as<ParamMap>();
150}
151
152bool NodeManifest::contains(const std::string& node_name) const
153{
154 return param_map_.find(node_name) != param_map_.end();
155}
156
157NodeManifest::Params& NodeManifest::operator[](const std::string& node_name)
158{
159 if (contains(node_name))
160 return param_map_[node_name];
161 throw std::out_of_range{ "Node '" + node_name + "' doesn't exist in manifest (Size: " +
162 std::to_string(param_map_.size()) + "). Use the Add() method to add an entry." };
163}
164
165const NodeManifest::Params& NodeManifest::operator[](const std::string& node_name) const
166{
167 if (contains(node_name))
168 return param_map_.at(node_name);
169 throw std::out_of_range{ "Node '" + node_name +
170 "' doesn't exist in manifest (Size: " + std::to_string(param_map_.size()) + ")." };
171}
172
173NodeManifest& NodeManifest::add(const std::string& node_name, const Params& p)
174{
175 if (contains(node_name))
176 {
177 throw std::logic_error{ "Node '" + node_name +
178 "' already exists in manifest (Size: " + std::to_string(param_map_.size()) + ")." };
179 }
180 param_map_[node_name] = p;
181 return *this;
182}
183
184NodeManifest& NodeManifest::remove(const std::string& node_name)
185{
186 if (!contains(node_name))
187 {
188 throw std::logic_error{ "Node '" + node_name +
189 "' doesn't exist in manifest, so the corresponding entry cannot be removed." };
190 }
191 param_map_.erase(node_name);
192 return *this;
193}
194
196{
197 for (const auto& [node_name, params] : m.getInternalMap())
198 add(node_name, params);
199 return *this;
200}
201
203{
204 for (const auto& [node_name, params] : param_map_)
205 {
206 const std::string node_package_name = class_loader.getClassPackage(params.class_name);
207 if (node_package_name.empty())
208 {
209 throw auto_apms_core::exceptions::ResourceNotFoundError("Cannot find class '" + params.class_name +
210 "' required by node '" + node_name +
211 "', because no such resource is registered with this "
212 "plugin loader instance.");
213 }
214 const std::string node_lib_path = class_loader.getClassLibraryPath(params.class_name);
215 if (!params.package.empty())
216 {
217 // Verify the plugin can be found in the package
218 if (params.package == node_package_name)
219 {
221 "Cannot find class '" + params.class_name + "' required by node '" + node_name + "' in package '" +
222 params.package + "'. Internally, the resource is registered by package '" + node_package_name +
223 "' instead. This can occur if multiple packages register a node plugin with the same class name. "
224 "To resolve this issue, introduce a unique package namespace to the respective class names.");
225 }
226 }
227 param_map_[node_name].package = node_package_name;
228 param_map_[node_name].library = node_lib_path; // Any entry in library will be overwritten
229 }
230 return *this;
231}
232
233void NodeManifest::toFile(const std::string& file_path) const
234{
235 YAML::Node root;
236 root = param_map_;
237 std::ofstream out_stream{ file_path };
238 if (out_stream.is_open())
239 {
240 out_stream << root;
241 out_stream.close();
242 }
243 else
244 {
245 throw std::runtime_error("Error opening node plugin manifest output file '" + file_path + "'.");
246 }
247}
248
249std::string NodeManifest::toString() const
250{
251 YAML::Node root;
252 root = param_map_;
253 YAML::Emitter out;
254 out << root;
255 return out.c_str();
256}
257
259{
260 return param_map_;
261}
262
263} // namespace auto_apms_behavior_tree
Data structure for resource lookup data and configuration parameters required for loading and registe...
static const std::string PARAM_NAME_REQUEST_TIMEOUT
static NodeManifest fromFile(const std::string &file_path)
Create a node plugin manifest from a file.
const ParamMap & getInternalMap() const
NodeManifest & merge(const NodeManifest &m)
static const std::string PARAM_NAME_CLASS
static const std::string PARAM_NAME_PACKAGE
bool contains(const std::string &node_name) const
static const std::string PARAM_NAME_LIBRARY
std::map< std::string, NodeRegistrationParams > ParamMap
Mapping of a node's name and its registration parameters.
static NodeManifest fromString(const std::string &manifest_str)
static const std::string PARAM_NAME_WAIT_TIMEOUT
static NodeManifest fromFiles(const std::vector< std::string > &file_paths)
Create a node plugin manifest from multiple files. They are loaded in the given order.
void toFile(const std::string &file_path) const
static const std::string PARAM_NAME_PORT
NodeManifest & add(const std::string &node_name, const NodeRegistrationParams &p)
NodeManifest(const ParamMap &param_map={})
NodeManifest & autoComplete(NodePluginClassLoader &class_loader)
Automatically fill node plugin resource information in manifest.
NodeManifest & remove(const std::string &node_name)
NodeRegistrationParams & operator[](const std::string &node_name)
Version of pluginlib::ClassLoader specifically for loading installed behavior tree node plugins.
Behavior tree node registration parameters.