diff --git a/interfaces/ev_board_support.yaml b/interfaces/ev_board_support.yaml index b1dfb4e81..968d70b02 100644 --- a/interfaces/ev_board_support.yaml +++ b/interfaces/ev_board_support.yaml @@ -55,5 +55,9 @@ vars: BSP Measurements type: object $ref: /board_support_common#/BspMeasurement + ev_info: + description: More details about the EV if available + type: object + $ref: /evse_manager#/EVInfo errors: - - reference: /errors/generic \ No newline at end of file + - reference: /errors/generic diff --git a/interfaces/ev_manager.yaml b/interfaces/ev_manager.yaml new file mode 100644 index 000000000..3f887ecab --- /dev/null +++ b/interfaces/ev_manager.yaml @@ -0,0 +1,16 @@ +description: >- + This interface defines the ev manager. An ev manager represents the + charging logic of the ev side +cmds: {} +vars: + bsp_event: + description: >- + Events from CP/Relais + type: object + $ref: /board_support_common#/BspEvent + ev_info: + description: More details about the EV if available + type: object + $ref: /evse_manager#/EVInfo +errors: + - reference: /errors/evse_manager diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 859a06da0..83b1b3715 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..992dc6ce6 --- /dev/null +++ b/modules/EvAPI/EvAPI.cpp @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#include "EvAPI.hpp" +#include + +namespace module { + +static const auto NOTIFICATION_PERIOD = std::chrono::seconds(1); + +void EvSessionInfo::reset() { + std::lock_guard lock(this->session_info_mutex); + this->event = std::nullopt; +} + +void EvSessionInfo::update_event(const types::board_support_common::Event& event) { + std::lock_guard lock(this->session_info_mutex); + this->event = event; +} + +EvSessionInfo::operator std::string() { + std::lock_guard lock(this->session_info_mutex); + + const auto now = date::utc_clock::now(); + + std::string state = "Unknown"; + if (this->event.has_value()) { + state = types::board_support_common::event_to_string(this->event.value()); + } + + json session_info = json::object({ + {"state", state}, + {"datetime", Everest::Date::to_rfc3339(now)}, + }); + + return session_info.dump(); +} + +void EvAPI::init() { + invoke_init(*p_main); + + std::vector ev_connectors; + std::string var_ev_connectors = this->api_base + "ev_connectors"; + + for (auto& ev : this->r_ev_manager) { + auto& session_info = this->info.emplace_back(std::make_unique()); + const std::string ev_base = this->api_base + ev->module_id; + ev_connectors.push_back(ev->module_id); + + // API variables + const std::string var_base = ev_base + "/var/"; + + const 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()); + }); + + const 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_event(bsp_event.event); + this->mqtt.publish(var_session_info, *session_info); + }); + + const std::string var_datetime = var_base + "datetime"; + 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 + const std::string cmd_base = ev_base + "/cmd/"; + } + + this->api_threads.push_back(std::thread([this, var_ev_connectors, ev_connectors]() { + auto next_tick = std::chrono::steady_clock::now(); + while (this->running) { + const json ev_connectors_array = ev_connectors; + this->mqtt.publish(var_ev_connectors, ev_connectors_array.dump()); + + next_tick += NOTIFICATION_PERIOD; + std::this_thread::sleep_until(next_tick); + } + })); +} + +void EvAPI::ready() { + invoke_ready(*p_main); + + const 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) { + if (not r_error_history.empty()) { + // request active errors + types::error_history::FilterArguments filter; + filter.state_filter = types::error_history::State::Active; + const 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..7d535ea03 --- /dev/null +++ b/modules/EvAPI/EvAPI.hpp @@ -0,0 +1,108 @@ +// 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 + +#include + +namespace module { + +class LimitDecimalPlaces; + +class EvSessionInfo { +public: + EvSessionInfo(){}; + + void reset(); + void update_event(const types::board_support_common::Event& event); + + /// \brief Converts this struct into a serialized json object + operator std::string(); + +private: + std::mutex session_info_mutex; + std::optional event; +}; +} // 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; + + 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..e69326b6c --- /dev/null +++ b/modules/EvAPI/main/emptyImpl.cpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 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..cda675cf9 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 @@ -31,6 +32,7 @@ struct Conf { bool auto_exec; bool auto_exec_infinite; std::string auto_exec_commands; + double ac_nominal_voltage; int dc_max_current_limit; int dc_max_power_limit; int dc_max_voltage_limit; @@ -44,18 +46,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 +69,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..a11065e36 --- /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..9313508a8 100644 --- a/modules/EvManager/main/car_simulation.cpp +++ b/modules/EvManager/main/car_simulation.cpp @@ -6,6 +6,8 @@ #include +constexpr double MS_FACTOR = (1.0 / 60.0 / 60.0 / 1000.0); + void CarSimulation::state_machine() { using types::ev_board_support::EvCpState; @@ -92,8 +94,59 @@ 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() { + const double ms = + std::chrono::duration_cast(std::chrono::steady_clock::now() - timepoint_last_update) + .count(); + const double factor = MS_FACTOR * ms; + double power = 0.0; + types::evse_manager::EVInfo ev_info; + switch (charge_mode) { + case ChargeMode::None: + // nothing to do + break; + case ChargeMode::AC: + power = charge_current_a * config.ac_nominal_voltage; + ev_info.target_current = charge_current_a; + ev_info.target_voltage = 0; + break; + case ChargeMode::ACThreePhase: + power = charge_current_a * config.ac_nominal_voltage * 3.0; + ev_info.target_current = charge_current_a; + ev_info.target_voltage = 0; + break; + case ChargeMode::DC: + 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; + break; + } + + 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]); @@ -121,22 +174,28 @@ 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_current_a = std::stod(arguments[0]); + r_ev_board_support->call_set_ac_max_current(charge_current_a); if (arguments[1] == constants::THREE_PHASES) { r_ev_board_support->call_set_three_phases(true); + charge_mode = ChargeMode::ACThreePhase; } else { r_ev_board_support->call_set_three_phases(false); + charge_mode = ChargeMode::AC; } sim_data.state = SimState::CHARGING_REGULATED; return true; } bool CarSimulation::draw_power_fixed(const CmdArguments& arguments) { - r_ev_board_support->call_set_ac_max_current(std::stod(arguments[0])); + charge_current_a = std::stod(arguments[0]); + r_ev_board_support->call_set_ac_max_current(charge_current_a); if (arguments[1] == constants::THREE_PHASES) { r_ev_board_support->call_set_three_phases(true); + charge_mode = ChargeMode::ACThreePhase; } else { r_ev_board_support->call_set_three_phases(false); + charge_mode = ChargeMode::AC; } sim_data.state = SimState::CHARGING_FIXED; return true; @@ -149,6 +208,7 @@ bool CarSimulation::pause(const CmdArguments& arguments) { bool CarSimulation::unplug(const CmdArguments& arguments) { sim_data.state = SimState::UNPLUGGED; + charge_mode = ChargeMode::None; return true; } @@ -200,6 +260,7 @@ 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_mode = ChargeMode::DC; return true; } return false; @@ -211,11 +272,14 @@ 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_mode = ChargeMode::AC; } else { r_ev[0]->call_start_charging(types::iso15118_ev::EnergyTransferMode::AC_three_phase_core); + charge_mode = ChargeMode::ACThreePhase; } } else if (energy_mode == constants::DC) { r_ev[0]->call_start_charging(types::iso15118_ev::EnergyTransferMode::DC_extended); + charge_mode = ChargeMode::DC; } else { return false; } @@ -223,11 +287,14 @@ 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_current_a = std::stod(arguments[0]); + r_ev_board_support->call_set_ac_max_current(charge_current_a); if (arguments[1] == constants::THREE_PHASES) { r_ev_board_support->call_set_three_phases(true); + charge_mode = ChargeMode::ACThreePhase; } else { r_ev_board_support->call_set_three_phases(false); + charge_mode = ChargeMode::AC; } sim_data.state = SimState::ISO_CHARGING_REGULATED; return true; @@ -237,6 +304,7 @@ bool CarSimulation::iso_stop_charging(const CmdArguments& arguments) { r_ev[0]->call_stop_charging(); r_ev_board_support->call_allow_power_on(false); sim_data.state = SimState::PLUGGED_IN; + charge_mode = ChargeMode::None; return true; } diff --git a/modules/EvManager/main/car_simulation.hpp b/modules/EvManager/main/car_simulation.hpp index 120ff3a10..0613bb8d4 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,21 @@ class CarSimulation { private: SimulationData sim_data; + const module::Conf& config; + std::chrono::time_point timepoint_last_update; + double charge_current_a{0}; + + enum class ChargeMode { + None, + AC, + ACThreePhase, + DC, + } charge_mode{ChargeMode::None}; 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..5f0f5da55 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(); } @@ -231,6 +232,7 @@ void car_simulatorImpl::subscribe_to_variables_on_init() { set_execution_active(false); car_simulation->set_state(SimState::UNPLUGGED); } + mod->p_ev_manager->publish_bsp_event(bsp_event); }); // subscribe bsp_measurement @@ -243,6 +245,11 @@ void car_simulatorImpl::subscribe_to_variables_on_init() { } }); + // subscribe EVInfo + using types::evse_manager::EVInfo; + mod->r_ev_board_support->subscribe_ev_info( + [this](const auto& ev_info) { mod->p_ev_manager->publish_ev_info(ev_info); }); + // 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..5280169fc 100644 --- a/modules/EvManager/manifest.yaml +++ b/modules/EvManager/manifest.yaml @@ -24,6 +24,10 @@ config: Simulation commands, e.g. sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 30;unplug type: string default: "" + ac_nominal_voltage: + description: Nominal AC voltage between phase and neutral in Volt + type: number + default: 230 dc_max_current_limit: description: Maximum current allowed by the EV type: integer @@ -76,10 +80,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: Implementation of the ev manager to provide additional information such as detailed EV info 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) {