diff --git a/config/config-sil.yaml b/config/config-sil.yaml index eb6b994b7..0ab5e13d8 100644 --- a/config/config-sil.yaml +++ b/config/config-sil.yaml @@ -10,6 +10,12 @@ active_modules: - module_id: error_history implementation_id: error_history module: API + ev_api: + connections: + ev_manager: + - implementation_id: ev_manager + module_id: ev_manager + module: EvAPI error_history: module: ErrorHistory config_implementation: diff --git a/interfaces/ev_manager.yaml b/interfaces/ev_manager.yaml new file mode 100644 index 000000000..7ed3b0ba0 --- /dev/null +++ b/interfaces/ev_manager.yaml @@ -0,0 +1,24 @@ +description: >- + This interface defines the ev manager. An ev manager represents the + charging logic of the ev side +cmds: {} +vars: + session_event: + description: Emits all events related to sessions + type: object + $ref: /evse_manager#/SessionEvent + ev_info: + description: More details about the EV if available + type: object + $ref: /evse_manager#/EVInfo + bsp_event: + description: >- + Events from CP/Relais + type: object + $ref: /board_support_common#/BspEvent + car_manufacturer: + description: Car manufacturer (if known) + type: string + $ref: /evse_manager#/CarManufacturer +errors: + - reference: /errors/evse_manager \ No newline at end of file diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index fea8c4923..14d0f6e5a 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -2,6 +2,7 @@ ev_add_module(API) ev_add_module(Auth) ev_add_module(EnergyManager) ev_add_module(EnergyNode) +ev_add_module(EvAPI) ev_add_module(EvManager) ev_add_module(ErrorHistory) ev_add_module(Evse15118D20) diff --git a/modules/EvAPI/CMakeLists.txt b/modules/EvAPI/CMakeLists.txt new file mode 100644 index 000000000..06aa45c75 --- /dev/null +++ b/modules/EvAPI/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# AUTO GENERATED - MARKED REGIONS WILL BE KEPT +# template version 3 +# + +# module setup: +# - ${MODULE_NAME}: module name +ev_setup_cpp_module() + +# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 +# insert your custom targets and additional config variables here +# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 + +target_sources(${MODULE_NAME} + PRIVATE + "main/emptyImpl.cpp" +) + +# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 +# insert other things like install cmds etc here +# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/EvAPI/EvAPI.cpp b/modules/EvAPI/EvAPI.cpp new file mode 100644 index 000000000..838a3bda3 --- /dev/null +++ b/modules/EvAPI/EvAPI.cpp @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest +#include "EvAPI.hpp" +#include +#include + +namespace module { + +static const auto NOTIFICATION_PERIOD = std::chrono::seconds(1); + +SessionInfo::SessionInfo() : + start_energy_import_wh(0), + end_energy_import_wh(0), + start_energy_export_wh(0), + end_energy_export_wh(0), + latest_total_w(0), + state("Unknown") { + this->start_time_point = date::utc_clock::now(); + this->end_time_point = this->start_time_point; +} + +void SessionInfo::reset() { + std::lock_guard lock(this->session_info_mutex); + this->state = "Unknown"; + this->start_energy_import_wh = 0; + this->end_energy_import_wh = 0; + this->start_energy_export_wh = 0; + this->end_energy_export_wh = 0; + this->start_time_point = date::utc_clock::now(); + this->latest_total_w = 0; + this->permanent_fault = false; +} + +static void remove_error_from_list(std::vector& list, const std::string& error_type) { + list.erase(std::remove_if(list.begin(), list.end(), + [error_type](const module::SessionInfo::Error& err) { return err.type == error_type; }), + list.end()); +} + +void SessionInfo::update_state(const std::string& event) { + std::lock_guard lock(this->session_info_mutex); + this->state = event; +} + +void SessionInfo::set_start_energy_import_wh(int32_t start_energy_import_wh) { + std::lock_guard lock(this->session_info_mutex); + this->start_energy_import_wh = start_energy_import_wh; + this->end_energy_import_wh = start_energy_import_wh; + this->start_time_point = date::utc_clock::now(); + this->end_time_point = this->start_time_point; +} + +void SessionInfo::set_end_energy_import_wh(int32_t end_energy_import_wh) { + std::lock_guard lock(this->session_info_mutex); + this->end_energy_import_wh = end_energy_import_wh; + this->end_time_point = date::utc_clock::now(); +} + +void SessionInfo::set_latest_energy_import_wh(int32_t latest_energy_wh) { +} + +void SessionInfo::set_start_energy_export_wh(int32_t start_energy_export_wh) { + std::lock_guard lock(this->session_info_mutex); + this->start_energy_export_wh = start_energy_export_wh; + this->end_energy_export_wh = start_energy_export_wh; + this->start_energy_export_wh_was_set = true; +} + +void SessionInfo::set_end_energy_export_wh(int32_t end_energy_export_wh) { + std::lock_guard lock(this->session_info_mutex); + this->end_energy_export_wh = end_energy_export_wh; + this->end_energy_export_wh_was_set = true; +} + +void SessionInfo::set_latest_energy_export_wh(int32_t latest_export_energy_wh) { + std::lock_guard lock(this->session_info_mutex); +} + +void SessionInfo::set_latest_total_w(double latest_total_w) { + std::lock_guard lock(this->session_info_mutex); + this->latest_total_w = latest_total_w; +} + +void SessionInfo::set_enable_disable_source(const std::string& active_source, const std::string& active_state, + const int active_priority) { + std::lock_guard lock(this->session_info_mutex); + this->active_enable_disable_source = active_source; + this->active_enable_disable_state = active_state; + this->active_enable_disable_priority = active_priority; +} + +static void to_json(json& j, const SessionInfo::Error& e) { + j = json{{"type", e.type}, {"description", e.description}, {"severity", e.severity}}; +} + +SessionInfo::operator std::string() { + std::lock_guard lock(this->session_info_mutex); + + auto charged_energy_wh = this->end_energy_import_wh - this->start_energy_import_wh; + int32_t discharged_energy_wh{0}; + if ((this->start_energy_export_wh_was_set == true) && (this->end_energy_export_wh_was_set == true)) { + discharged_energy_wh = this->end_energy_export_wh - this->start_energy_export_wh; + } + auto now = date::utc_clock::now(); + + auto charging_duration_s = + std::chrono::duration_cast(this->end_time_point - this->start_time_point); + + json session_info = json::object({ + {"state", this->state}, + {"permanent_fault", this->permanent_fault}, + {"charged_energy_wh", charged_energy_wh}, + {"discharged_energy_wh", discharged_energy_wh}, + {"latest_total_w", this->latest_total_w}, + {"charging_duration_s", charging_duration_s.count()}, + {"datetime", Everest::Date::to_rfc3339(now)}, + + }); + + json active_disable_enable = json::object({{"source", this->active_enable_disable_source}, + {"state", this->active_enable_disable_state}, + {"priority", this->active_enable_disable_priority}}); + session_info["active_enable_disable_source"] = active_disable_enable; + + return session_info.dump(); +} + +void EvAPI::init() { + invoke_init(*p_main); + + std::vector connectors; + std::string var_connectors = this->api_base + "connectors"; + + for (auto& ev : this->r_ev_manager) { + auto& session_info = this->info.emplace_back(std::make_unique()); + auto& hw_caps = this->hw_capabilities_str.emplace_back(""); + std::string ev_base = this->api_base + ev->module_id; + connectors.push_back(ev->module_id); + + // API variables + std::string var_base = ev_base + "/var/"; + + std::string var_ev_info = var_base + "ev_info"; + ev->subscribe_ev_info([this, &ev, var_ev_info](types::evse_manager::EVInfo ev_info) { + json ev_info_json = ev_info; + this->mqtt.publish(var_ev_info, ev_info_json.dump()); + }); + + std::string var_session_info = var_base + "session_info"; + ev->subscribe_bsp_event([this, var_session_info, &session_info](const auto& bsp_event) { + session_info->update_state(types::board_support_common::event_to_string(bsp_event.event)); + this->mqtt.publish(var_session_info, *session_info); + }); + + std::string var_datetime = var_base + "datetime"; + std::string var_logging_path = var_base + "logging_path"; + this->api_threads.push_back(std::thread([this, var_datetime, var_session_info, &session_info]() { + auto next_tick = std::chrono::steady_clock::now(); + while (this->running) { + std::string datetime_str = Everest::Date::to_rfc3339(date::utc_clock::now()); + this->mqtt.publish(var_datetime, datetime_str); + this->mqtt.publish(var_session_info, *session_info); + + next_tick += NOTIFICATION_PERIOD; + std::this_thread::sleep_until(next_tick); + } + })); + + // API commands + std::string cmd_base = ev_base + "/cmd/"; + } + + this->api_threads.push_back(std::thread([this, var_connectors, connectors]() { + auto next_tick = std::chrono::steady_clock::now(); + while (this->running) { + json connectors_array = connectors; + this->mqtt.publish(var_connectors, connectors_array.dump()); + + next_tick += NOTIFICATION_PERIOD; + std::this_thread::sleep_until(next_tick); + } + })); +} + +void EvAPI::ready() { + invoke_ready(*p_main); + + std::string var_active_errors = this->api_base + "errors/var/active_errors"; + this->api_threads.push_back(std::thread([this, var_active_errors]() { + auto next_tick = std::chrono::steady_clock::now(); + while (this->running) { + std::string datetime_str = Everest::Date::to_rfc3339(date::utc_clock::now()); + + if (not r_error_history.empty()) { + // request active errors + types::error_history::FilterArguments filter; + filter.state_filter = types::error_history::State::Active; + auto active_errors = r_error_history.at(0)->call_get_errors(filter); + json errors_json = json(active_errors); + + // publish + this->mqtt.publish(var_active_errors, errors_json.dump()); + } + next_tick += NOTIFICATION_PERIOD; + std::this_thread::sleep_until(next_tick); + } + })); +} + +} // namespace module diff --git a/modules/EvAPI/EvAPI.hpp b/modules/EvAPI/EvAPI.hpp new file mode 100644 index 000000000..d97064aae --- /dev/null +++ b/modules/EvAPI/EvAPI.hpp @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef EVAPI_HPP +#define EVAPI_HPP + +// +// AUTO GENERATED - MARKED REGIONS WILL BE KEPT +// template version 2 +// + +#include "ld-ev.hpp" + +// headers for provided interface implementations +#include + +// headers for required interface implementations +#include +#include + +// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 +// insert your custom include headers here +#include +#include +#include +#include +#include + +#include +#include + +namespace module { + +class LimitDecimalPlaces; + +class SessionInfo { +public: + SessionInfo(); + + struct Error { + std::string type; + std::string description; + std::string severity; + }; + + bool start_energy_export_wh_was_set{ + false}; ///< Indicate if start export energy value (optional) has been received or not + bool end_energy_export_wh_was_set{ + false}; ///< Indicate if end export energy value (optional) has been received or not + + void reset(); + void update_state(const std::string& event); + void set_start_energy_import_wh(int32_t start_energy_import_wh); + void set_end_energy_import_wh(int32_t end_energy_import_wh); + void set_latest_energy_import_wh(int32_t latest_energy_wh); + void set_start_energy_export_wh(int32_t start_energy_export_wh); + void set_end_energy_export_wh(int32_t end_energy_export_wh); + void set_latest_energy_export_wh(int32_t latest_export_energy_wh); + void set_latest_total_w(double latest_total_w); + void set_enable_disable_source(const std::string& active_source, const std::string& active_state, + const int active_priority); + void set_permanent_fault(bool f) { + permanent_fault = f; + } + + /// \brief Converts this struct into a serialized json object + operator std::string(); + +private: + std::mutex session_info_mutex; + int32_t start_energy_import_wh; ///< Energy reading (import) at the beginning of this charging session in Wh + int32_t end_energy_import_wh; ///< Energy reading (import) at the end of this charging session in Wh + int32_t start_energy_export_wh; ///< Energy reading (export) at the beginning of this charging session in Wh + int32_t end_energy_export_wh; ///< Energy reading (export) at the end of this charging session in Wh + std::chrono::time_point start_time_point; ///< Start of the charging session + std::chrono::time_point end_time_point; ///< End of the charging session + double latest_total_w; ///< Latest total power reading in W + + std::string state = "Unknown"; + + std::string active_enable_disable_source{"Unspecified"}; + std::string active_enable_disable_state{"Enabled"}; + int active_enable_disable_priority{0}; + bool permanent_fault{false}; +}; +} // namespace module +// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 + +namespace module { + +struct Conf {}; + +class EvAPI : public Everest::ModuleBase { +public: + EvAPI() = delete; + EvAPI(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, std::unique_ptr p_main, + std::vector> r_ev_manager, + std::vector> r_error_history, Conf& config) : + ModuleBase(info), + mqtt(mqtt_provider), + p_main(std::move(p_main)), + r_ev_manager(std::move(r_ev_manager)), + r_error_history(std::move(r_error_history)), + config(config){}; + + Everest::MqttProvider& mqtt; + const std::unique_ptr p_main; + const std::vector> r_ev_manager; + const std::vector> r_error_history; + const Conf& config; + + // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 + // insert your public definitions here + // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 + +protected: + // ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1 + // insert your protected definitions here + // ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1 + +private: + friend class LdEverest; + void init(); + void ready(); + + // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 + // insert your private definitions here + std::vector api_threads; + bool running = true; + + std::list> info; + std::list hw_capabilities_str; + std::string selected_protocol; + + const std::string api_base = "everest_api/"; + // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 +}; + +// ev@087e516b-124c-48df-94fb-109508c7cda9:v1 +// insert other definitions here +// ev@087e516b-124c-48df-94fb-109508c7cda9:v1 + +} // namespace module + +#endif // EVAPI_HPP diff --git a/modules/EvAPI/README.md b/modules/EvAPI/README.md new file mode 100644 index 000000000..f0058c20e --- /dev/null +++ b/modules/EvAPI/README.md @@ -0,0 +1,2 @@ +# EvAPI module documentation +This module is responsible for providing a simple MQTT based API to EVerest internals exposing EvManager related functionality \ No newline at end of file diff --git a/modules/EvAPI/main/emptyImpl.cpp b/modules/EvAPI/main/emptyImpl.cpp new file mode 100644 index 000000000..ffebed0ca --- /dev/null +++ b/modules/EvAPI/main/emptyImpl.cpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest + +#include "emptyImpl.hpp" + +namespace module { +namespace main { + +void emptyImpl::init() { +} + +void emptyImpl::ready() { +} + +} // namespace main +} // namespace module diff --git a/modules/EvAPI/main/emptyImpl.hpp b/modules/EvAPI/main/emptyImpl.hpp new file mode 100644 index 000000000..92bbe5944 --- /dev/null +++ b/modules/EvAPI/main/emptyImpl.hpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef MAIN_EMPTY_IMPL_HPP +#define MAIN_EMPTY_IMPL_HPP + +// +// AUTO GENERATED - MARKED REGIONS WILL BE KEPT +// template version 3 +// + +#include + +#include "../EvAPI.hpp" + +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 +// insert your custom include headers here +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 + +namespace module { +namespace main { + +struct Conf {}; + +class emptyImpl : public emptyImplBase { +public: + emptyImpl() = delete; + emptyImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : + emptyImplBase(ev, "main"), mod(mod), config(config){}; + + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + // insert your public definitions here + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + +protected: + // no commands defined for this interface + + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + // insert your protected definitions here + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + +private: + const Everest::PtrContainer& mod; + const Conf& config; + + virtual void init() override; + virtual void ready() override; + + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 + // insert your private definitions here + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 +}; + +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 +// insert other definitions here +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 + +} // namespace main +} // namespace module + +#endif // MAIN_EMPTY_IMPL_HPP diff --git a/modules/EvAPI/manifest.yaml b/modules/EvAPI/manifest.yaml new file mode 100644 index 000000000..2c22fe3ae --- /dev/null +++ b/modules/EvAPI/manifest.yaml @@ -0,0 +1,22 @@ +description: >- + The EVerest Ev API module, exposing some internal functionality on an external + MQTT connection. +config: {} +provides: + main: + description: EVerest API + interface: empty +requires: + ev_manager: + interface: ev_manager + min_connections: 1 + max_connections: 128 + error_history: + interface: error_history + min_connections: 0 + max_connections: 1 +enable_external_mqtt: true +metadata: + license: https://opensource.org/licenses/Apache-2.0 + authors: + - Kai-Uwe Hermann diff --git a/modules/EvManager/CMakeLists.txt b/modules/EvManager/CMakeLists.txt index d5aea153d..0c5dd492c 100644 --- a/modules/EvManager/CMakeLists.txt +++ b/modules/EvManager/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(${MODULE_NAME} target_sources(${MODULE_NAME} PRIVATE "main/car_simulatorImpl.cpp" + "ev_manager/ev_managerImpl.cpp" ) # ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/EvManager/EvManager.cpp b/modules/EvManager/EvManager.cpp index 69769d3a9..d0d4eac28 100644 --- a/modules/EvManager/EvManager.cpp +++ b/modules/EvManager/EvManager.cpp @@ -7,10 +7,12 @@ namespace module { void EvManager::init() { invoke_init(*p_main); + invoke_init(*p_ev_manager); } void EvManager::ready() { invoke_ready(*p_main); + invoke_ready(*p_ev_manager); } } // namespace module diff --git a/modules/EvManager/EvManager.hpp b/modules/EvManager/EvManager.hpp index 41c6dc877..05a2598d6 100644 --- a/modules/EvManager/EvManager.hpp +++ b/modules/EvManager/EvManager.hpp @@ -12,6 +12,7 @@ // headers for provided interface implementations #include +#include // headers for required interface implementations #include @@ -44,18 +45,21 @@ struct Conf { int dc_discharge_v2g_minimal_soc; double max_current; bool three_phases; + int soc; }; class EvManager : public Everest::ModuleBase { public: EvManager() = delete; EvManager(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, - std::unique_ptr p_main, std::unique_ptr r_ev_board_support, + std::unique_ptr p_main, std::unique_ptr p_ev_manager, + std::unique_ptr r_ev_board_support, std::vector> r_ev, std::vector> r_slac, std::vector> r_powermeter, Conf& config) : ModuleBase(info), mqtt(mqtt_provider), p_main(std::move(p_main)), + p_ev_manager(std::move(p_ev_manager)), r_ev_board_support(std::move(r_ev_board_support)), r_ev(std::move(r_ev)), r_slac(std::move(r_slac)), @@ -64,6 +68,7 @@ class EvManager : public Everest::ModuleBase { Everest::MqttProvider& mqtt; const std::unique_ptr p_main; + const std::unique_ptr p_ev_manager; const std::unique_ptr r_ev_board_support; const std::vector> r_ev; const std::vector> r_slac; diff --git a/modules/EvManager/ev_manager/ev_managerImpl.cpp b/modules/EvManager/ev_manager/ev_managerImpl.cpp new file mode 100644 index 000000000..6080eecc3 --- /dev/null +++ b/modules/EvManager/ev_manager/ev_managerImpl.cpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include "ev_managerImpl.hpp" + +namespace module { +namespace ev_manager { + +void ev_managerImpl::init() { +} + +void ev_managerImpl::ready() { +} + +} // namespace ev_manager +} // namespace module diff --git a/modules/EvManager/ev_manager/ev_managerImpl.hpp b/modules/EvManager/ev_manager/ev_managerImpl.hpp new file mode 100644 index 000000000..177671a31 --- /dev/null +++ b/modules/EvManager/ev_manager/ev_managerImpl.hpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef EV_MANAGER_EV_MANAGER_IMPL_HPP +#define EV_MANAGER_EV_MANAGER_IMPL_HPP + +// +// AUTO GENERATED - MARKED REGIONS WILL BE KEPT +// template version 3 +// + +#include + +#include "../EvManager.hpp" + +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 +// insert your custom include headers here +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 + +namespace module { +namespace ev_manager { + +struct Conf {}; + +class ev_managerImpl : public ev_managerImplBase { +public: + ev_managerImpl() = delete; + ev_managerImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : + ev_managerImplBase(ev, "ev_manager"), mod(mod), config(config) {}; + + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + // insert your public definitions here + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + +protected: + // no commands defined for this interface + + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + // insert your protected definitions here + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + +private: + const Everest::PtrContainer& mod; + const Conf& config; + + virtual void init() override; + virtual void ready() override; + + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 + // insert your private definitions here + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 +}; + +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 +// insert other definitions here +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 + +} // namespace ev_manager +} // namespace module + +#endif // EV_MANAGER_EV_MANAGER_IMPL_HPP diff --git a/modules/EvManager/main/car_simulation.cpp b/modules/EvManager/main/car_simulation.cpp index a0de3f33b..057c1ee9a 100644 --- a/modules/EvManager/main/car_simulation.cpp +++ b/modules/EvManager/main/car_simulation.cpp @@ -92,8 +92,53 @@ void CarSimulation::state_machine() { sim_data.state = SimState::UNPLUGGED; break; } + + if (not state_has_changed and + (sim_data.state == SimState::CHARGING_REGULATED or sim_data.state == SimState::CHARGING_FIXED or + sim_data.state == SimState::ISO_CHARGING_REGULATED)) { + simulate_soc(); + } + timepoint_last_update = std::chrono::steady_clock::now(); }; +void CarSimulation::simulate_soc() { + double ms = + std::chrono::duration_cast(std::chrono::steady_clock::now() - timepoint_last_update) + .count(); + double factor = (1.0 / 60.0 / 60.0 / 1000.0) * ms; + double power = 0.0; + types::evse_manager::EVInfo ev_info; + if (charge_ac) { + charge_v = 230.0; + if (charge_three_phase) { + power = charge_a * charge_v * 3.0; + } else { + power = charge_a * charge_v; + } + ev_info.target_current = charge_a; + ev_info.target_voltage = 0; + } else { + power = config.dc_target_current * config.dc_target_voltage; + ev_info.target_current = config.dc_target_current; + ev_info.target_voltage = config.dc_target_voltage; + } + + if (sim_data.battery_charge_wh > sim_data.battery_capacity_wh) { + sim_data.battery_charge_wh = sim_data.battery_capacity_wh; + } else { + sim_data.battery_charge_wh += power * factor; + } + + ev_info.soc = (sim_data.battery_charge_wh / sim_data.battery_capacity_wh) * 100.0; + if (ev_info.soc > 100.0) { + ev_info.soc = 100.0; + } + ev_info.battery_capacity = sim_data.battery_capacity_wh; + ev_info.battery_full_soc = 100; + + p_ev_manager->publish_ev_info(ev_info); +} + bool CarSimulation::sleep(const CmdArguments& arguments, size_t loop_interval_ms) { if (not sim_data.sleep_ticks_left.has_value()) { const auto sleep_time = std::stold(arguments[0]); @@ -122,10 +167,15 @@ bool CarSimulation::iso_wait_pwm_is_running(const CmdArguments& arguments) { bool CarSimulation::draw_power_regulated(const CmdArguments& arguments) { r_ev_board_support->call_set_ac_max_current(std::stod(arguments[0])); + charge_a = std::stod(arguments[0]); if (arguments[1] == constants::THREE_PHASES) { r_ev_board_support->call_set_three_phases(true); + charge_ac = true; + charge_three_phase = true; } else { r_ev_board_support->call_set_three_phases(false); + charge_ac = true; + charge_three_phase = false; } sim_data.state = SimState::CHARGING_REGULATED; return true; @@ -133,10 +183,15 @@ bool CarSimulation::draw_power_regulated(const CmdArguments& arguments) { bool CarSimulation::draw_power_fixed(const CmdArguments& arguments) { r_ev_board_support->call_set_ac_max_current(std::stod(arguments[0])); + charge_a = std::stod(arguments[0]); if (arguments[1] == constants::THREE_PHASES) { r_ev_board_support->call_set_three_phases(true); + charge_ac = true; + charge_three_phase = true; } else { r_ev_board_support->call_set_three_phases(false); + charge_ac = true; + charge_three_phase = false; } sim_data.state = SimState::CHARGING_FIXED; return true; @@ -200,6 +255,8 @@ bool CarSimulation::iso_dc_power_on(const CmdArguments& arguments) { if (sim_data.dc_power_on) { sim_data.state = SimState::ISO_CHARGING_REGULATED; r_ev_board_support->call_allow_power_on(true); + charge_ac = false; + charge_three_phase = false; return true; } return false; @@ -211,11 +268,17 @@ bool CarSimulation::iso_start_v2g_session(const CmdArguments& arguments, bool th if (energy_mode == constants::AC) { if (three_phases == false) { r_ev[0]->call_start_charging(types::iso15118_ev::EnergyTransferMode::AC_single_phase_core); + charge_ac = true; + charge_three_phase = false; } else { r_ev[0]->call_start_charging(types::iso15118_ev::EnergyTransferMode::AC_three_phase_core); + charge_ac = true; + charge_three_phase = true; } } else if (energy_mode == constants::DC) { r_ev[0]->call_start_charging(types::iso15118_ev::EnergyTransferMode::DC_extended); + charge_ac = false; + charge_three_phase = false; } else { return false; } @@ -224,10 +287,15 @@ bool CarSimulation::iso_start_v2g_session(const CmdArguments& arguments, bool th bool CarSimulation::iso_draw_power_regulated(const CmdArguments& arguments) { r_ev_board_support->call_set_ac_max_current(std::stod(arguments[0])); + charge_a = std::stod(arguments[0]); if (arguments[1] == constants::THREE_PHASES) { r_ev_board_support->call_set_three_phases(true); + charge_ac = true; + charge_three_phase = true; } else { r_ev_board_support->call_set_three_phases(false); + charge_ac = true; + charge_three_phase = false; } sim_data.state = SimState::ISO_CHARGING_REGULATED; return true; diff --git a/modules/EvManager/main/car_simulation.hpp b/modules/EvManager/main/car_simulation.hpp index 120ff3a10..8ee4e8261 100644 --- a/modules/EvManager/main/car_simulation.hpp +++ b/modules/EvManager/main/car_simulation.hpp @@ -5,8 +5,10 @@ #include "simulation_data.hpp" +#include "../EvManager.hpp" #include #include +#include #include #include @@ -16,12 +18,22 @@ class CarSimulation { public: CarSimulation(const std::unique_ptr& r_ev_board_support_, const std::vector>& r_ev_, - const std::vector>& r_slac_) : - r_ev_board_support(r_ev_board_support_), r_ev(r_ev_), r_slac(r_slac_){}; + const std::vector>& r_slac_, + const std::unique_ptr& p_ev_manager_, const module::Conf& config_) : + r_ev_board_support(r_ev_board_support_), + r_ev(r_ev_), + r_slac(r_slac_), + p_ev_manager(p_ev_manager_), + config(config_) { + timepoint_last_update = std::chrono::steady_clock::now(); + }; ~CarSimulation() = default; void reset() { sim_data = SimulationData(); + sim_data.battery_capacity_wh = config.dc_energy_capacity; + double soc = config.soc; + sim_data.battery_charge_wh = config.dc_energy_capacity * (soc / 100.0); } const SimState& get_state() const { @@ -98,8 +110,17 @@ class CarSimulation { private: SimulationData sim_data; + const module::Conf& config; + std::chrono::time_point timepoint_last_update; + double charge_a{0}; + double charge_v{0}; + bool charge_ac{false}; + bool charge_three_phase{false}; const std::unique_ptr& r_ev_board_support; const std::vector>& r_ev; const std::vector>& r_slac; + const std::unique_ptr& p_ev_manager; + + void simulate_soc(); }; diff --git a/modules/EvManager/main/car_simulatorImpl.cpp b/modules/EvManager/main/car_simulatorImpl.cpp index 3a13c2702..f85691feb 100644 --- a/modules/EvManager/main/car_simulatorImpl.cpp +++ b/modules/EvManager/main/car_simulatorImpl.cpp @@ -13,7 +13,8 @@ void car_simulatorImpl::init() { register_all_commands(); subscribe_to_variables_on_init(); - car_simulation = std::make_unique(mod->r_ev_board_support, mod->r_ev, mod->r_slac); + car_simulation = std::make_unique(mod->r_ev_board_support, mod->r_ev, mod->r_slac, mod->p_ev_manager, + mod->config); std::thread(&car_simulatorImpl::run, this).detach(); } @@ -243,6 +244,11 @@ void car_simulatorImpl::subscribe_to_variables_on_init() { } }); + using types::board_support_common::BspEvent; + mod->r_ev_board_support->subscribe_bsp_event([this](const auto& bsp_event) { + mod->p_ev_manager->publish_bsp_event(bsp_event); + }); + // subscribe slac_state if (!mod->r_slac.empty()) { const auto& slac = mod->r_slac.at(0); diff --git a/modules/EvManager/main/simulation_data.hpp b/modules/EvManager/main/simulation_data.hpp index 6af9d1614..7e2bacf94 100644 --- a/modules/EvManager/main/simulation_data.hpp +++ b/modules/EvManager/main/simulation_data.hpp @@ -52,5 +52,8 @@ struct SimulationData { bool dc_power_on{false}; + double battery_charge_wh{0}; + double battery_capacity_wh{0}; + types::board_support_common::Event actual_bsp_event{types::board_support_common::Event::Disconnected}; }; diff --git a/modules/EvManager/manifest.yaml b/modules/EvManager/manifest.yaml index d725f30b6..dd74a0f83 100644 --- a/modules/EvManager/manifest.yaml +++ b/modules/EvManager/manifest.yaml @@ -76,10 +76,17 @@ config: description: Support three phase type: boolean default: true + soc: + description: SoC at start of a simulated charging process + type: integer + default: 30 provides: main: interface: car_simulator description: This implements the car simulator + ev_manager: + interface: ev_manager + description: TODO requires: ev_board_support: interface: ev_board_support diff --git a/modules/simulation/JsYetiSimulator/index.js b/modules/simulation/JsYetiSimulator/index.js index 5bf3dcdc2..995c38854 100644 --- a/modules/simulation/JsYetiSimulator/index.js +++ b/modules/simulation/JsYetiSimulator/index.js @@ -975,6 +975,10 @@ function publish_ev_board_support(mod) { rcd_current_mA: mod.simulation_data.rcd_current, proximity_pilot: pp, }); + + mod.provides.ev_board_support.publish.bsp_event({ + event: event_to_enum(mod.current_state) + }); } function simulate_powermeter(mod) {