|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// Copyright Pionix GmbH and Contributors to EVerest |
| 3 | +#include "EVerest_API.hpp" |
| 4 | + |
| 5 | +#include <everest_api_types/entrypoint/codec.hpp> |
| 6 | +#include <everest_api_types/entrypoint/json_codec.hpp> |
| 7 | +#include <everest_api_types/generic/codec.hpp> |
| 8 | +#include <everest_api_types/utilities/codec.hpp> |
| 9 | +#include <utils/yaml_loader.hpp> |
| 10 | + |
| 11 | +#include <map> |
| 12 | +#include <string> |
| 13 | +#include <utility> |
| 14 | + |
| 15 | +namespace { |
| 16 | + |
| 17 | +struct ApiModuleParameter { |
| 18 | + std::string api_type{}; |
| 19 | + std::optional<int> api_version{}; |
| 20 | +}; |
| 21 | + |
| 22 | +std::map<std::string, ApiModuleParameter> |
| 23 | +get_all_ApiModuleParameters(Everest::config::ConfigServiceClient* config_service_client, |
| 24 | + const std::filesystem::path& api_details_filepath) { |
| 25 | + std::map<std::string, ApiModuleParameter> api_modules; |
| 26 | + |
| 27 | + json schema_data; |
| 28 | + try { |
| 29 | + schema_data = Everest::load_yaml(api_details_filepath); |
| 30 | + } catch (const std::exception& err) { |
| 31 | + EVLOG_error << "Error parsing YAML file at " << api_details_filepath << ": " << err.what(); |
| 32 | + return {}; |
| 33 | + } |
| 34 | + |
| 35 | + const auto& module_configs = config_service_client->get_module_configs(); |
| 36 | + |
| 37 | + for (const auto& [config, _] : module_configs) { |
| 38 | + if (schema_data.contains(config.module_type)) { |
| 39 | + ApiModuleParameter module_parameters; |
| 40 | + module_parameters.api_type = config.module_type; |
| 41 | + |
| 42 | + // No mechanism to retreive the version yet |
| 43 | + |
| 44 | + api_modules.emplace(config.module_id, module_parameters); |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + return api_modules; |
| 49 | +} |
| 50 | + |
| 51 | +std::string strip_suffix(const std::string& input, const std::string& suffix) { |
| 52 | + if (input.size() >= suffix.size() && input.substr(input.size() - suffix.size()) == suffix) { |
| 53 | + return input.substr(0, input.size() - suffix.size()); |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +API_types_ext::ApiDiscoverResponse |
| 58 | +module_config_to_ApiDiscoverResponse(const std::map<std::string, ApiModuleParameter>& api_modules) { |
| 59 | + API_types_ext::ApiDiscoverResponse response; |
| 60 | + |
| 61 | + for (const auto& [name, parameter] : api_modules) { |
| 62 | + API_types_ext::ApiTypeEnum type_as_enum; |
| 63 | + json api_type_j = strip_suffix(parameter.api_type, "_API"); |
| 64 | + if (not ev_API::deserialize(api_type_j.dump(), type_as_enum)) { |
| 65 | + EVLOG_error << "Could not deserialize API type for '" << parameter.api_type << "'"; |
| 66 | + } |
| 67 | + API_types_ext::ApiParameter api_params; |
| 68 | + api_params.type = type_as_enum; |
| 69 | + api_params.module_id = name; |
| 70 | + api_params.version = parameter.api_version; |
| 71 | + response.apis.emplace_back(api_params); |
| 72 | + } |
| 73 | + |
| 74 | + return response; |
| 75 | +} |
| 76 | +} // namespace |
| 77 | + |
| 78 | +namespace module { |
| 79 | + |
| 80 | +namespace API_generic = API_types::generic; |
| 81 | +using ev_API::deserialize; |
| 82 | + |
| 83 | +void EVerest_API::init() { |
| 84 | + invoke_init(*p_main); |
| 85 | + |
| 86 | + topics.setup(info.id, "entrypoint", 1); |
| 87 | +} |
| 88 | + |
| 89 | +void EVerest_API::ready() { |
| 90 | + invoke_ready(*p_main); |
| 91 | + |
| 92 | + auto config_service_client = get_config_service_client(); |
| 93 | + |
| 94 | + const auto apis_details_path = info.paths.share / "apis.yaml"; |
| 95 | + |
| 96 | + auto api_modules = get_all_ApiModuleParameters(config_service_client.get(), apis_details_path); |
| 97 | + |
| 98 | + api_discovery_response = module_config_to_ApiDiscoverResponse(api_modules); |
| 99 | + |
| 100 | + generate_api_cmd_discover(); |
| 101 | + |
| 102 | + // Not calling start on comm_check - we only use the heartbeat functionality here |
| 103 | + setup_heartbeat_generator(); |
| 104 | +} |
| 105 | + |
| 106 | +void EVerest_API::generate_api_cmd_discover() { |
| 107 | + subscribe_api_topic("discover", [this](std::string const& data) { |
| 108 | + API_generic::RequestReply msg; |
| 109 | + if (deserialize(data, msg)) { |
| 110 | + mqtt.publish(msg.replyTo, API_types_ext::serialize(api_discovery_response)); |
| 111 | + return true; |
| 112 | + } |
| 113 | + return false; |
| 114 | + }); |
| 115 | +} |
| 116 | + |
| 117 | +void EVerest_API::subscribe_api_topic(std::string const& var, ParseAndPublishFtor const& parse_and_publish) { |
| 118 | + auto topic = topics.extern_to_everest(var); |
| 119 | + mqtt.subscribe(topic, [=](std::string const& data) { |
| 120 | + try { |
| 121 | + if (not parse_and_publish(data)) { |
| 122 | + EVLOG_warning << "Invalid data: Deserialization failed.\n" << topic << "\n" << data; |
| 123 | + } |
| 124 | + } catch (const std::exception& e) { |
| 125 | + EVLOG_warning << "Topic: '" << topic << "' failed with -> " << e.what() << "\n => " << data; |
| 126 | + } catch (...) { |
| 127 | + EVLOG_warning << "Invalid data: Failed to parse JSON or to get data from it.\n" << topic; |
| 128 | + } |
| 129 | + }); |
| 130 | +} |
| 131 | + |
| 132 | +void EVerest_API::setup_heartbeat_generator() { |
| 133 | + auto topic = topics.everest_to_extern("heartbeat"); |
| 134 | + auto action = [this, topic]() { |
| 135 | + mqtt.publish(topic, API_generic::serialize(hb_id++)); |
| 136 | + return true; |
| 137 | + }; |
| 138 | + comm_check.heartbeat(config.cfg_heartbeat_interval_ms, action); |
| 139 | +} |
| 140 | + |
| 141 | +} // namespace module |
0 commit comments