58 const Conf& conf()
const {
return conf_; }
69 inline auto id_prototype(std::string desc =
"") {
70 return schema::make_prototype<std::string>(std::move(desc)).c_identifier();
73 inline auto id_path_prototype(std::string desc =
"") {
74 return schema::make_prototype<std::string>(std::move(desc))
75 .pattern(
"^([a-zA-Z_][a-zA-Z0-9_]*/?)+$");
85 using IncludeSchema = decltype(schema::make_schema(
static_cast<IncludeConf*
>(
nullptr),
""));
141 std::optional<std::string> pattern;
142 std::optional<LogLevel> level;
151 {
"name", make_schema(&name,
"name of the logger to configure").require()},
152 {
"pattern", make_schema(&pattern,
"pattern of the logger")},
153 {
"level", make_schema(&level,
"level of the logger")},
157 bool validate(
const Conf& c, std::optional<SchemaError>& err)
const override {
158 const auto& s = this->
schema();
159 if (!s.validate(c, err)) {
162 if (!c.
has(
"pattern") && !c.
has(
"level")) {
164 "require at least one of 'pattern' or 'level' properties"));
175 std::string listen_address{
"127.0.0.1"};
176 uint16_t listen_port{8080};
177 uint16_t listen_threads{10};
178 std::string api_prefix{
"/api"};
179 std::string static_prefix{
""};
185 {
"listen", make_schema(&listen,
"whether web server is enabled")},
186 {
"listen_address", make_schema(&listen_address,
"address web server should listen at")},
187 {
"listen_port", make_schema(&listen_port,
"port web server should listen at")},
188 {
"listen_threads", make_schema(&listen_threads,
"threads web server should use")},
189 {
"static_prefix", make_schema(&static_prefix,
"endpoint prefix for static resources")},
190 {
"api_prefix", make_schema(&api_prefix,
"endpoint prefix for API resources")},
203 std::filesystem::path plugin_path{};
206 std::optional<std::string> plugin_name{};
209 std::optional<std::string> plugin_prefix{};
212 std::optional<bool> ignore_missing{};
219 std::optional<bool> ignore_failure{};
227 std::optional<bool> allow_clobber{};
231 explicit PluginConf(
const std::string& p) : plugin_path(p) {}
241 std::string canonical()
const;
244 CONFABLE_SCHEMA(PluginConf) {
247 auto proto = String(
nullptr,
"").c_identifier();
249 {
"path", make_schema(&plugin_path,
"absolute or relative path to plugin").require().not_empty().normalize(
true)},
250 {
"name", make_schema(&plugin_name, proto,
"alternative name plugin is available by")},
251 {
"prefix", make_schema(&plugin_prefix, proto,
"prefix the plugin name with this")},
252 {
"ignore_missing", make_schema(&ignore_missing,
"ignore not-exist errors")},
253 {
"ignore_failure", make_schema(&ignore_failure,
"ignore plugin loading errors")},
254 {
"allow_clobber", make_schema(&allow_clobber,
"replace same-named plugins")},
260 using PluginsSchema = schema_type<std::vector<PluginConf>>::type;
279 {WatchdogMode::Off,
"off"},
280 {WatchdogMode::Log,
"log"},
281 {WatchdogMode::Abort,
"abort"},
282 {WatchdogMode::Kill,
"kill"},
286 struct EngineConf : public Confable {
288 std::vector<std::string> ignore_sections{};
291 bool security_enable_hooks{
true};
292 bool security_enable_commands{
false};
293 bool security_enable_includes{
true};
294 size_t security_max_include_depth{64};
297 std::vector<std::string> plugin_path{};
298 bool plugins_ignore_missing{
false};
299 bool plugins_ignore_failure{
false};
300 bool plugins_allow_clobber{
true};
303 std::vector<Command> hooks_pre_connect{};
304 std::vector<Command> hooks_post_disconnect{};
307 bool triggers_ignore_source{
false};
310 std::optional<std::filesystem::path> registry_path{CLOE_DATA_HOME
"/registry"};
311 std::optional<std::filesystem::path> output_path{
"${CLOE_SIMULATION_UUID}"};
312 std::optional<std::filesystem::path> output_file_config{
"config.json"};
313 std::optional<std::filesystem::path> output_file_result{
"result.json"};
314 std::optional<std::filesystem::path> output_file_triggers{
"triggers.json"};
315 std::optional<std::filesystem::path> output_file_signals{
"signals.json"};
316 std::optional<std::filesystem::path> output_file_signals_autocompletion;
317 std::optional<std::filesystem::path> output_file_data_stream;
318 bool output_clobber_files{
true};
325 std::chrono::milliseconds polling_interval{100};
343 std::chrono::milliseconds watchdog_default_timeout{90'000};
372 std::map<std::string, std::optional<std::chrono::milliseconds>> watchdog_state_timeouts{
373 {
"CONNECT", std::chrono::milliseconds{300'000}},
374 {
"ABORT", std::chrono::milliseconds{90'000}},
375 {
"STOP", std::chrono::milliseconds{300'000}},
376 {
"DISCONNECT", std::chrono::milliseconds{600'000}},
385 bool keep_alive{
false};
388 CONFABLE_SCHEMA(EngineConf) {
391 auto dir_proto = []() {
return make_prototype<std::filesystem::path>().not_file(); };
392 auto file_proto = []() {
return make_prototype<std::filesystem::path>().not_dir().resolve(
false); };
394 {
"ignore", make_schema(&ignore_sections,
"JSON pointers to sections that should be ignored").extend(
true)},
396 {
"enable_hooks_section", make_schema(&security_enable_hooks,
"whether to enable engine hooks")},
397 {
"enable_command_action", make_schema(&security_enable_commands,
"whether to enable the command action")},
398 {
"enable_include_section", make_schema(&security_enable_includes,
"whether to allow config files to include other files")},
399 {
"max_include_depth", make_schema(&security_max_include_depth,
"how many recursive includes are allowed")},
402 {
"pre_connect", make_schema(&hooks_pre_connect,
"pre-connect hooks to execute").extend(
true)},
403 {
"post_disconnect", make_schema(&hooks_post_disconnect,
"post-disconnect hooks to execute").extend(
true)},
405 {
"plugin_path", make_schema(&plugin_path,
"list of directories to scan for plugins").extend(
false)},
407 {
"ignore_missing", make_schema(&plugins_ignore_missing,
"ignore not-exist errors")},
408 {
"ignore_failure", make_schema(&plugins_ignore_failure,
"ignore plugin loading errors")},
409 {
"allow_clobber", make_schema(&plugins_allow_clobber,
"replace same-named plugins")},
411 {
"registry_path", make_schema(®istry_path, dir_proto(),
"cloe registry directory")},
413 {
"path", make_schema(&output_path, dir_proto().resolve(
false),
"directory to dump output files in, relative to registry path")},
414 {
"clobber", make_schema(&output_clobber_files,
"whether to clobber existing files or not")},
416 {
"config", make_schema(&output_file_config, file_proto(),
"file to store config in")},
417 {
"result", make_schema(&output_file_result, file_proto(),
"file to store simulation result in")},
418 {
"triggers", make_schema(&output_file_triggers, file_proto(),
"file to store triggers in")},
419 {
"signals", make_schema(&output_file_signals, file_proto(),
"file to store signals in")},
420 {
"signals_autocompletion", make_schema(&output_file_signals_autocompletion, file_proto(),
"file to store signal autocompletion in")},
421 {
"api_recording", make_schema(&output_file_data_stream, file_proto(),
"file to store api data stream")},
425 {
"ignore_source", make_schema(&triggers_ignore_source,
"ignore trigger source when reading in triggers")},
427 {
"polling_interval", make_schema(&polling_interval,
"milliseconds to sleep when polling for next state")},
429 {
"mode", make_schema(&watchdog_mode,
"modus operandi of watchdog [one of: off, log, abort, kill]")},
430 {
"default_timeout", make_schema(&watchdog_default_timeout,
"default timeout if not overridden, 0 for no timeout")},
431 {
"state_timeouts", make_schema(&watchdog_state_timeouts,
"timeout specific to a given state, 0 for no timeout").unique_properties(
false)},
433 {
"keep_alive", make_schema(&keep_alive,
"keep simulation alive after termination")},
439 using EngineSchema = schema_type<EngineConf>::type;
454 std::optional<std::string> name;
455 std::optional<std::string> binding;
462 {
"binding", make_schema(&binding,
"name of binding")},
463 {
"name", make_schema(&name, id_prototype(),
"globally unique identifier for component")},
464 {
"args", make_schema(&args,
"defaults to set for binding/name combination").require()},
471 template <
typename C,
typename F>
475 this->set_factory_key(
"binding");
476 this->set_args_subset(
false);
480 void add_plugin(
const std::string& name, std::shared_ptr<Plugin> p) {
481 this->set_factory(name, p->make<F>()->schema(), [p, name](
const Conf& c) {
482 C tmp{name, p->make<F>()};
495 const std::string binding;
496 std::optional<std::string> name;
497 std::shared_ptr<SimulatorFactory> factory;
501 SimulatorConf(
const std::string& b, std::shared_ptr<SimulatorFactory> f)
502 : binding(b), factory(std::move(f)) {}
508 {
"binding", make_const_schema(binding,
"name of simulator binding").require()},
509 {
"name", make_schema(&name, id_prototype(),
"identifier override for binding")},
510 {
"args", make_schema(&args, factory->schema(),
"factory-specific arguments")},
527 const std::string binding;
528 std::optional<std::string> name;
530 std::shared_ptr<ControllerFactory> factory;
534 ControllerConf(
const std::string& b, std::shared_ptr<ControllerFactory> f)
535 : binding(b), factory(std::move(f)) {}
542 {
"binding", make_const_schema(binding,
"name of controller binding").require()},
543 {
"name", make_schema(&name, id_prototype(),
"identifier override for binding")},
544 {
"vehicle", make_schema(&vehicle,
"vehicle controller is assigned to").c_identifier().require()},
545 {
"args", make_schema(&args, factory->schema(),
"factory-specific arguments")},
560 std::string simulator;
561 std::string index_str;
565 bool is_by_name()
const {
return !index_str.empty(); }
566 bool is_by_index()
const {
return index_str.empty(); }
579 {
"simulator", make_schema(&simulator,
"simulator").not_empty().require()},
580 {
"index", make_schema(&index_num,
"index of vehicle in simulator").require()},
583 {
"simulator", make_schema(&simulator,
"simulator").not_empty().require()},
584 {
"name", make_schema(&index_str,
"name of vehicle in simulator").not_empty().require()},
595 void to_json(Json& j)
const override {
598 {
"simulator", simulator},
599 {
"index", index_num},
603 {
"simulator", simulator},
611 const std::string binding;
612 std::optional<std::string> name;
613 std::vector<std::string> from;
614 std::shared_ptr<ComponentFactory> factory;
618 ComponentConf(
const std::string& b, std::shared_ptr<ComponentFactory> f)
619 : binding(b), factory(std::move(f)) {}
626 {
"binding", make_const_schema(binding,
"name of binding").require()},
627 {
"name", make_schema(&name, id_prototype(),
"globally unique identifier for component")},
629 make_schema(&from,
"component inputs for binding"),
631 make_prototype<std::string>(
"component input for binding"),
632 [
this](CustomDeserializer*,
const Conf& c) {
633 this->from.push_back(c.
get<std::string>());
637 {
"args", make_schema(&args, factory->schema(),
"factory-specific args")},
675 std::string from_veh;
676 std::map<std::string, ComponentConf> components;
685 bool is_from_simulator()
const {
return from_veh.empty(); }
686 bool is_from_vehicle()
const {
return !from_veh.empty(); }
698 {
"name", make_schema(&name,
"globally unique identifier for vehicle").c_identifier().require()},
700 make_schema(&from_sim,
"simulator source"),
701 make_schema(&from_veh,
"vehicle source").c_identifier(),
703 {
"components", make_schema(&components, component_schema,
"component configuration of vehicle")},
713 void to_json(Json& j)
const override {
714 Json from = is_from_simulator() ? Json(from_sim) : Json(from_veh);
718 {
"components", components},
726 using MakeFunc = ComponentSchema::MakeFunc;
727 using TypeFactory = ComponentSchema::TypeFactory;
729 explicit VehicleSchema(std::string desc =
"") : Base(std::move(desc)) {}
732 bool has_factory(
const std::string& name)
const {
return components_.has_factory(name); }
733 void add_plugin(
const std::string& name, std::shared_ptr<Plugin> p) {
734 components_.add_plugin(name, p);
743 bool validate(
const Conf& c, std::optional<SchemaError>& err)
const override {
748 Json serialize(
const Type& x)
const {
return x.to_json(); }
750 Type make(
const Conf& c)
const {
return deserialize(c); }
752 Type deserialize(
const Conf& c)
const {
753 VehicleConf v{components_};
759 throw std::logic_error(
"VehicleSchema does not implement from_conf");
762 void to_json(Json&)
const override {
763 throw std::logic_error(
"VehicleSchema does not implement to_json");
775 std::optional<std::string> label{};
776 Source source{Source::FILESYSTEM};
781 bool optional{
false};
787 auto EANDA_SCHEMA = Variant{
788 String{
nullptr,
"inline format"}.pattern(
"^[a-zA-Z0-9_/]+(=.*)?$"),
790 {
"name", id_path_prototype().require()}
791 }.additional_properties(
true),
794 {
"label", make_schema(&label,
"description of trigger")},
795 {
"source", make_schema(&source,
"source from which trigger originates")},
796 {
"event", make_schema(&event, EANDA_SCHEMA,
"event").require()},
797 {
"action", make_schema(&action, EANDA_SCHEMA,
"action").require()},
798 {
"sticky", make_schema(&sticky,
"whether trigger should be sticky")},
799 {
"conceal", make_schema(&conceal,
"whether trigger should be concealed in history")},
800 {
"optional", make_schema(&optional,
"whether errors creating event or action should be ignored")},
801 {
"at", Ignore(
"time at which trigger was executed", JsonType::string)},
802 {
"since", Ignore(
"time since which trigger was in queue", JsonType::string)},
818 std::optional<std::string> name{};
830 int64_t controller_retry_limit{1000};
835 std::chrono::milliseconds controller_retry_sleep{1};
843 bool abort_on_controller_failure{
true};
850 {
"namespace", make_schema(&name, id_prototype(),
"namespace for simulation events and actions")},
851 {
"model_step_width", make_schema(&model_step_width,
"default model time step in ns")},
852 {
"controller_retry_limit", make_schema(&controller_retry_limit,
"times to retry controller processing before aborting")},
853 {
"controller_retry_sleep", make_schema(&controller_retry_sleep,
"time to sleep before retrying controller process")},
854 {
"abort_on_controller_failure", make_schema(&abort_on_controller_failure,
"abort simulation on controller failure")},
866 [[nodiscard]] std::string all_sections_missing(
const std::string& sep =
", ")
const;
867 [[nodiscard]]
const std::vector<std::string>& sections_missing()
const {
return sections_missing_; }
870 std::vector<std::string> sections_missing_;
873 using ConfReader = std::function<
Conf(
const std::string&)>;
881 std::vector<std::string> reserved_ids_;
882 std::optional<std::string> schema_ref_;
888 std::vector<IncludeConf> include;
889 std::vector<LoggingConf> logging;
890 std::vector<PluginConf> plugins;
891 std::vector<DefaultConf> simulator_defaults;
892 std::vector<SimulatorConf> simulators;
893 std::vector<DefaultConf> controller_defaults;
894 std::vector<ControllerConf> controllers;
895 std::vector<DefaultConf> component_defaults;
896 std::vector<VehicleConf> vehicles;
897 std::vector<TriggerConf> triggers;
901 EngineSchema engine_schema;
903 PluginsSchema plugins_schema;
910 std::set<std::string> scanned_plugin_paths_;
911 std::map<std::string, std::shared_ptr<Plugin>> all_plugins_;
912 std::vector<Conf> applied_confs_;
913 ConfReader conf_reader_func_;
921 ~
Stack()
override =
default;
926 Logger logger()
const {
return logger::get(
"cloe"); }
937 assert(fn !=
nullptr);
938 conf_reader_func_ = std::move(fn);
944 void merge_stackfile(
const std::string& filepath);
965 void insert_plugin(std::shared_ptr<Plugin> p,
const PluginConf& c = {});
970 bool has_plugin_with_name(
const std::string& key)
const;
975 bool has_plugin_with_path(
const std::string& path)
const;
982 std::shared_ptr<Plugin> get_plugin_with_name(
const std::string& key)
const;
989 std::shared_ptr<Plugin> get_plugin_with_path(
const std::string& key)
const;
994 std::shared_ptr<Plugin> get_plugin_or_load(
const std::string& key_or_path)
const;
1000 return all_plugins_;
1003 std::vector<DefaultConf> get_simulator_defaults(std::string binding, std::string name)
const;
1004 std::vector<DefaultConf> get_controller_defaults(std::string binding, std::string name)
const;
1005 std::vector<DefaultConf> get_vehicle_defaults(std::string name)
const;
1006 std::vector<DefaultConf> get_component_defaults(std::string binding, std::string name)
const;
1015 void validate_self()
const;
1020 bool is_valid()
const;
1034 void check_consistency()
const;
1039 void check_defaults()
const;
1051 bool is_complete()
const;
1056 void check_completeness()
const;
1067 Json active_config()
const;
1072 Json input_config()
const;
1084 bool validate(
const Conf& c, std::optional<SchemaError>& err)
const override;
1087 void to_json(Json& j)
const override;
1091 CONFABLE_SCHEMA(
Stack) {
1096 {
"$schema", make_schema(&schema_ref_,
"valid URI to schema describing this cloe stack version")},
1097 {
"version", make_schema(&version,
"version of stackfile").require().enum_of(
1098 CLOE_STACK_SUPPORTED_VERSIONS
1100 {
"engine", engine_schema},
1101 {
"include", include_schema},
1102 {
"logging", make_schema(&logging,
"logging configuration").extend(
true)},
1103 {
"plugins", plugins_schema},
1104 {
"server", make_schema(&server,
"server configuration")},
1105 {
"defaults", Struct{
1106 {
"simulators", make_schema(&simulator_defaults,
"simulator default configurations").extend(
true)},
1107 {
"controllers", make_schema(&controller_defaults,
"controller default configurations").extend(
true)},
1108 {
"components", make_schema(&component_defaults,
"component default configurations").extend(
true)},
1110 {
"vehicles", make_schema(&vehicles, vehicle_prototype,
"vehicle configuration").extend(
true)},
1111 {
"simulators", make_schema(&simulators, simulator_prototype,
"simulator configuration").extend(
true)},
1112 {
"controllers", make_schema(&controllers, controller_prototype,
"controller configuration").extend(
true)},
1113 {
"triggers", make_schema(&triggers,
"triggers").extend(
true)},
1114 {
"simulation", make_schema(&simulation,
"simulation configuration")},
1124 void from_conf(
const Conf& c,
size_t depth);
Definition: stack.hpp:643
Definition: stack.hpp:551
Definition: stack.hpp:472
void from_conf(const Conf &c) override
Definition: stack.hpp:60
Definition: stack.hpp:515
Definition: stack.hpp:862
Definition: stack.hpp:879
void initialize()
Definition: stack.hpp:1062
void set_conf_reader(ConfReader fn)
Definition: stack.hpp:936
const std::map< std::string, std::shared_ptr< Plugin > > & get_all_plugins() const
Definition: stack.hpp:999
void from_conf(const Conf &c) override
Definition: stack.hpp:1088
Definition: stack.hpp:723
bool validate(const Conf &c, std::optional< SchemaError > &err) const override
Definition: stack.hpp:743
void from_conf(const Conf &) override
Definition: stack.hpp:758
void reset_ptr() override
Definition: stack.hpp:766
Json json_schema() const override
Definition: stack.hpp:738
bool has(const std::string &key) const
Definition: conf.hpp:132
T get() const
Definition: conf.hpp:166
Definition: confable.hpp:43
virtual void from_conf(const Conf &c)
Definition: confable.cpp:70
virtual void reset_schema()
Definition: confable.cpp:54
virtual void validate_or_throw(const Conf &c) const
Definition: confable.cpp:58
Json to_json() const
Definition: confable.cpp:78
Schema & schema()
Definition: confable.cpp:47
Definition: error.hpp:130
Definition: schema.hpp:175
Json json_schema() const override
Definition: schema.hpp:256
bool validate(const Conf &c, std::optional< SchemaError > &err) const override
Definition: schema.hpp:257
Definition: interface.hpp:398
Definition: factory.hpp:406
std::chrono::nanoseconds Duration
Definition: cloe_fwd.hpp:36
#define ENUM_SERIALIZATION(xType, xMap)
Definition: enum.hpp:51
nlohmann::json Json
Definition: fable_fwd.hpp:35
std::filesystem::path IncludeConf
Definition: stack.hpp:84
WatchdogMode
Definition: stack.hpp:270
@ Kill
Kill the program immediately.
@ Abort
Abort after the state returns.
@ Log
Log infractions but nothing else.
@ Off
Disable the watchdog entirely.
Definition: stack.hpp:610
Definition: stack.hpp:526
Definition: stack.hpp:453
Definition: stack.hpp:559
void from_conf(const Conf &c) override
Definition: stack.hpp:590
Definition: stack.hpp:139
bool validate(const Conf &c, std::optional< SchemaError > &err) const override
Definition: stack.hpp:157
Definition: stack.hpp:201
Definition: stack.hpp:173
Definition: stack.hpp:814
Definition: stack.hpp:494
Definition: stack.hpp:774
Definition: stack.hpp:672
void from_conf(const Conf &c) override
Definition: stack.hpp:708
Source
Definition: trigger.hpp:351