Skip to content

Commit 72c0e9f

Browse files
committed
Add basic entrypoint_API module
Signed-off-by: Christoph Burandt <christoph.burandt@pionix.de>
1 parent cee8b66 commit 72c0e9f

File tree

14 files changed

+636
-3
lines changed

14 files changed

+636
-3
lines changed

config/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
generate_config_run_script(CONFIG EVerestAPI-entrypoint)
12
generate_config_run_script(CONFIG sil)
23
generate_config_run_script(CONFIG sil-rpcapi)
34
generate_config_run_script(CONFIG sil-two-evse)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
settings:
2+
telemetry_enabled: true
3+
active_modules:
4+
api_entrypoint:
5+
module: EVerest_API
6+
mapping:
7+
module:
8+
evse: 1
9+
connector: 2
10+
access:
11+
config:
12+
allow_global_read: true
13+
allow_global_write: false
14+
allow_set_read_only: false
15+
modules:
16+
auth:
17+
allow_read: true
18+
allow_write: true
19+
allow_set_read_only: true
20+
dm_1:
21+
module: display_message_API
22+
config_module:
23+
cfg_communication_check_to_s: 5
24+
mapping:
25+
module:
26+
evse: 1
27+
connector: 1
28+
implementations:
29+
main:
30+
evse: 1
31+
connector: 1
32+
ps_dc_1:
33+
module: power_supply_DC_API
34+
config_module:
35+
cfg_communication_check_to_s: 0
36+
cfg_heartbeat_interval_ms: 10000
37+
mapping:
38+
module:
39+
evse: 1
40+
connector: 1
41+
ps_dc_2:
42+
module: power_supply_DC_API
43+
config_module:
44+
cfg_communication_check_to_s: 0
45+
cfg_heartbeat_interval_ms: 10000
46+
mapping:
47+
module:
48+
evse: 2
49+
connector: 1
50+
51+
# send "{"headers": { "replyTo": "my/reply/topic" } }" to "everest_api/1/entrypoint/api_entrypoint/m2e/discover"
52+
# and receive an array of (module_id, type, version[optional]) tuples on the reply topic

lib/everest/everest_api_types/include/everest_api_types/entrypoint/API.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,11 @@ enum class ApiTypeEnum {
3333
struct ApiParameter {
3434
ApiTypeEnum type;
3535
std::string module_id;
36-
std::optional<int> version;
36+
std::optional<int32_t> version;
3737
};
3838

3939
struct ApiDiscoverResponse {
4040
std::vector<ApiParameter> apis;
4141
};
4242

43-
4443
} // namespace everest::lib::API::V1_0::types::entrypoint

lib/everest/everest_api_types/src/everest_api_types/entrypoint/json_codec.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ void to_json(json& j, ApiParameter const& k) noexcept {
170170
}
171171

172172
void from_json(const json& j, ApiParameter& k) {
173-
k.type = j.at("message");
173+
k.type = j.at("type");
174174
k.module_id = j.at("module_id");
175175
if (j.contains("version")) {
176176
k.version.emplace(j.at("version"));

modules/API/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
ev_add_module(API)
22
ev_add_module(EvAPI)
3+
ev_add_module(EVerest_API)
34
ev_add_module(RpcApi)
45
ev_add_module(auth_consumer_API)
56
ev_add_module(auth_token_provider_API)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#
2+
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
3+
# template version 3
4+
#
5+
6+
# module setup:
7+
# - ${MODULE_NAME}: module name
8+
ev_setup_cpp_module()
9+
10+
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
11+
# insert your custom targets and additional config variables here
12+
13+
target_compile_options(${MODULE_NAME}
14+
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
15+
16+
target_link_libraries(${MODULE_NAME}
17+
PRIVATE
18+
atomic
19+
everest::everest_api_types
20+
)
21+
22+
install(
23+
FILES ${CMAKE_CURRENT_SOURCE_DIR}/apis.yaml
24+
DESTINATION "${CMAKE_INSTALL_DATADIR}/everest/modules/${MODULE_NAME}/"
25+
)
26+
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
27+
28+
target_sources(${MODULE_NAME}
29+
PRIVATE
30+
"main/generic_errorImpl.cpp"
31+
)
32+
33+
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
34+
# insert other things like install cmds etc here
35+
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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+
return input;
56+
}
57+
58+
API_types_ext::ApiDiscoverResponse
59+
module_config_to_ApiDiscoverResponse(const std::map<std::string, ApiModuleParameter>& api_modules) {
60+
API_types_ext::ApiDiscoverResponse response;
61+
62+
for (const auto& [name, parameter] : api_modules) {
63+
API_types_ext::ApiTypeEnum type_as_enum;
64+
json api_type_j = strip_suffix(parameter.api_type, "_API");
65+
if (not ev_API::deserialize(api_type_j.dump(), type_as_enum)) {
66+
EVLOG_error << "Could not deserialize API type for '" << parameter.api_type << "'";
67+
}
68+
API_types_ext::ApiParameter api_params;
69+
api_params.type = type_as_enum;
70+
api_params.module_id = name;
71+
api_params.version = parameter.api_version;
72+
response.apis.emplace_back(api_params);
73+
}
74+
75+
return response;
76+
}
77+
} // namespace
78+
79+
namespace module {
80+
81+
namespace API_generic = API_types::generic;
82+
using ev_API::deserialize;
83+
84+
void EVerest_API::init() {
85+
invoke_init(*p_main);
86+
87+
topics.setup(info.id, "entrypoint", 1);
88+
}
89+
90+
void EVerest_API::ready() {
91+
invoke_ready(*p_main);
92+
93+
auto config_service_client = get_config_service_client();
94+
95+
const auto apis_details_path = info.paths.share / "apis.yaml";
96+
97+
auto api_modules = get_all_ApiModuleParameters(config_service_client.get(), apis_details_path);
98+
99+
api_discovery_response = module_config_to_ApiDiscoverResponse(api_modules);
100+
101+
generate_api_cmd_discover();
102+
103+
// Not calling start on comm_check - we only use the heartbeat functionality here
104+
setup_heartbeat_generator();
105+
}
106+
107+
void EVerest_API::generate_api_cmd_discover() {
108+
subscribe_api_topic("discover", [this](std::string const& data) {
109+
API_generic::RequestReply msg;
110+
if (deserialize(data, msg)) {
111+
mqtt.publish(msg.replyTo, API_types_ext::serialize(api_discovery_response));
112+
return true;
113+
}
114+
return false;
115+
});
116+
}
117+
118+
void EVerest_API::subscribe_api_topic(std::string const& var, ParseAndPublishFtor const& parse_and_publish) {
119+
auto topic = topics.extern_to_everest(var);
120+
mqtt.subscribe(topic, [=](std::string const& data) {
121+
try {
122+
if (not parse_and_publish(data)) {
123+
EVLOG_warning << "Invalid data: Deserialization failed.\n" << topic << "\n" << data;
124+
}
125+
} catch (const std::exception& e) {
126+
EVLOG_warning << "Topic: '" << topic << "' failed with -> " << e.what() << "\n => " << data;
127+
} catch (...) {
128+
EVLOG_warning << "Invalid data: Failed to parse JSON or to get data from it.\n" << topic;
129+
}
130+
});
131+
}
132+
133+
void EVerest_API::setup_heartbeat_generator() {
134+
auto topic = topics.everest_to_extern("heartbeat");
135+
auto action = [this, topic]() {
136+
mqtt.publish(topic, API_generic::serialize(hb_id++));
137+
return true;
138+
};
139+
comm_check.heartbeat(config.cfg_heartbeat_interval_ms, action);
140+
}
141+
142+
} // namespace module
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright Pionix GmbH and Contributors to EVerest
3+
#ifndef EVEREST_API_HPP
4+
#define EVEREST_API_HPP
5+
6+
//
7+
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
8+
// template version 2
9+
//
10+
11+
#include "ld-ev.hpp"
12+
13+
// headers for provided interface implementations
14+
#include <generated/interfaces/generic_error/Implementation.hpp>
15+
16+
// headers for required interface implementations
17+
#pragma GCC diagnostic push
18+
#pragma GCC diagnostic ignored "-Wunused-parameter"
19+
#include <generated/interfaces/generic_error/Implementation.hpp>
20+
#pragma GCC diagnostic pop
21+
22+
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
23+
// insert your custom include headers here
24+
#include <everest_api_types/entrypoint/API.hpp>
25+
#include <everest_api_types/utilities/CommCheckHandler.hpp>
26+
#include <everest_api_types/utilities/Topics.hpp>
27+
28+
namespace ev_API = everest::lib::API;
29+
namespace API_types = ev_API::V1_0::types;
30+
namespace API_types_ext = API_types::entrypoint;
31+
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
32+
33+
namespace module {
34+
35+
struct Conf {
36+
int cfg_communication_check_to_s;
37+
int cfg_heartbeat_interval_ms;
38+
};
39+
40+
class EVerest_API : public Everest::ModuleBase {
41+
public:
42+
EVerest_API() = delete;
43+
EVerest_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
44+
std::unique_ptr<generic_errorImplBase> p_main, Conf& config) :
45+
ModuleBase(info),
46+
mqtt(mqtt_provider),
47+
p_main(std::move(p_main)),
48+
config(config),
49+
comm_check("generic/CommunicationFault", "Bridge to implementation connection lost", this->p_main){};
50+
51+
Everest::MqttProvider& mqtt;
52+
const std::shared_ptr<generic_errorImplBase> p_main;
53+
const Conf& config;
54+
55+
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
56+
// insert your public definitions here
57+
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
58+
59+
protected:
60+
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
61+
// insert your protected definitions here
62+
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
63+
64+
private:
65+
friend class LdEverest;
66+
void init();
67+
void ready();
68+
69+
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
70+
// insert your private definitions here
71+
using ParseAndPublishFtor = std::function<bool(std::string const&)>;
72+
void subscribe_api_topic(std::string const& var, ParseAndPublishFtor const& parse_and_publish);
73+
74+
void generate_api_cmd_discover();
75+
76+
void setup_heartbeat_generator();
77+
78+
ev_API::Topics topics;
79+
ev_API::CommCheckHandler<generic_errorImplBase> comm_check;
80+
size_t hb_id{0};
81+
82+
API_types_ext::ApiDiscoverResponse api_discovery_response;
83+
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
84+
};
85+
86+
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
87+
// insert other definitions here
88+
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
89+
90+
} // namespace module
91+
92+
#endif // EVEREST_API_HPP

0 commit comments

Comments
 (0)