From 5cf18911e6edc1443ead32cd4cd0367b91f9f7f7 Mon Sep 17 00:00:00 2001 From: MarzellT Date: Thu, 18 Apr 2024 16:37:21 +0200 Subject: [PATCH] temp commit Signed-off-by: MarzellT --- README.md | 4 +- config/config-sil-dc.yaml | 2 +- interfaces/car_simulator.yaml | 1 + modules/CMakeLists.txt | 1 + modules/EvManager/CMakeLists.txt | 27 + modules/EvManager/EvManager.cpp | 16 + modules/EvManager/EvManager.hpp | 97 +++ modules/EvManager/doc.rst | 22 + modules/EvManager/docs/index.rst | 23 + modules/EvManager/main/RegisteredCommand.cpp | 4 + modules/EvManager/main/RegisteredCommand.hpp | 67 +++ modules/EvManager/main/SimCommand.cpp | 18 + modules/EvManager/main/SimCommand.hpp | 34 ++ modules/EvManager/main/SimData.cpp | 77 +++ modules/EvManager/main/SimData.hpp | 77 +++ modules/EvManager/main/car_simulatorImpl.cpp | 556 ++++++++++++++++++ modules/EvManager/main/car_simulatorImpl.hpp | 80 +++ modules/EvManager/manifest.yaml | 98 +++ modules/EvManager/tests/CMakeLists.txt | 21 + .../tests/RegisteredCommand.test.cpp | 2 + 20 files changed, 1224 insertions(+), 3 deletions(-) create mode 100644 modules/EvManager/CMakeLists.txt create mode 100644 modules/EvManager/EvManager.cpp create mode 100644 modules/EvManager/EvManager.hpp create mode 100644 modules/EvManager/doc.rst create mode 100644 modules/EvManager/docs/index.rst create mode 100644 modules/EvManager/main/RegisteredCommand.cpp create mode 100644 modules/EvManager/main/RegisteredCommand.hpp create mode 100644 modules/EvManager/main/SimCommand.cpp create mode 100644 modules/EvManager/main/SimCommand.hpp create mode 100644 modules/EvManager/main/SimData.cpp create mode 100644 modules/EvManager/main/SimData.hpp create mode 100644 modules/EvManager/main/car_simulatorImpl.cpp create mode 100644 modules/EvManager/main/car_simulatorImpl.hpp create mode 100644 modules/EvManager/manifest.yaml create mode 100644 modules/EvManager/tests/CMakeLists.txt create mode 100644 modules/EvManager/tests/RegisteredCommand.test.cpp diff --git a/README.md b/README.md index 3ef6319352..ffabdc78e2 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ cd everest-dev-environment/dependency_manager edm init --workspace ~/checkout/everest-workspace ``` -This sets up a workspace based on the most recent EVerest release. If you want to check out the most recent main you can use the following command: +This sets up a workspace based on the most recent EVerest release. If you want to check out the most recent main you can use the following commandName: ```bash cd everest-dev-environment/dependency_manager edm init main --workspace ~/checkout/everest-workspace @@ -106,7 +106,7 @@ cmake .. make install ``` -(Optional) In case you have more than one CPU core and more RAM availble you can use the following command to significantly speed up the build process: +(Optional) In case you have more than one CPU core and more RAM availble you can use the following commandName to significantly speed up the build process: ```bash make -j$(nproc) install ``` diff --git a/config/config-sil-dc.yaml b/config/config-sil-dc.yaml index ef2f029410..b0c305d5b3 100644 --- a/config/config-sil-dc.yaml +++ b/config/config-sil-dc.yaml @@ -56,7 +56,7 @@ active_modules: imd: module: IMDSimulator ev_manager: - module: JsEvManager + module: EvManager config_module: connector_id: 1 auto_enable: true diff --git a/interfaces/car_simulator.yaml b/interfaces/car_simulator.yaml index 4138318f05..e767353bcc 100644 --- a/interfaces/car_simulator.yaml +++ b/interfaces/car_simulator.yaml @@ -5,6 +5,7 @@ description: >- cmds: enable: description: >- + # TODO: change description Sets the ID that uniquely identifies the EVSE. The EVSEID shall match the following structure: = diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 1f8384e121..907772e477 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(EvManager) ev_add_module(ErrorHistory) ev_add_module(EvseManager) ev_add_module(EvseSecurity) diff --git a/modules/EvManager/CMakeLists.txt b/modules/EvManager/CMakeLists.txt new file mode 100644 index 0000000000..eec6022f84 --- /dev/null +++ b/modules/EvManager/CMakeLists.txt @@ -0,0 +1,27 @@ +# +# 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/car_simulatorImpl.cpp" + "main/SimData.cpp" + "main/SimCommand.cpp" + "main/RegisteredCommand.cpp" +) + +# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 +# insert other things like install cmds etc here +if(EVEREST_CORE_BUILD_TESTING) + add_subdirectory(tests) +endif() +# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/EvManager/EvManager.cpp b/modules/EvManager/EvManager.cpp new file mode 100644 index 0000000000..69769d3a96 --- /dev/null +++ b/modules/EvManager/EvManager.cpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#include "EvManager.hpp" +#include "main/car_simulatorImpl.hpp" + +namespace module { + +void EvManager::init() { + invoke_init(*p_main); +} + +void EvManager::ready() { + invoke_ready(*p_main); +} + +} // namespace module diff --git a/modules/EvManager/EvManager.hpp b/modules/EvManager/EvManager.hpp new file mode 100644 index 0000000000..46962e39bd --- /dev/null +++ b/modules/EvManager/EvManager.hpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef EV_MANAGER_HPP +#define EV_MANAGER_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 +#include +#include + +// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 +// insert your custom include headers here +// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 + +namespace module { + +struct Conf { + int connector_id; + bool auto_enable; + bool auto_exec; + std::string auto_exec_commands; + int dc_max_current_limit; + int dc_max_power_limit; + int dc_max_voltage_limit; + int dc_energy_capacity; + int dc_target_current; + int dc_target_voltage; + bool support_sae_j2847; + int dc_discharge_max_current_limit; + int dc_discharge_max_power_limit; + int dc_discharge_target_current; + int dc_discharge_v2g_minimal_soc; + double max_curent; + bool three_phases; +}; + +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::vector> r_ev, std::vector> r_slac, + std::vector> r_powermeter, Conf& config) : + ModuleBase(info), + mqtt(mqtt_provider), + p_main(std::move(p_main)), + r_ev_board_support(std::move(r_ev_board_support)), + r_ev(std::move(r_ev)), + r_slac(std::move(r_slac)), + r_powermeter(std::move(r_powermeter)), + config(config){}; + + Everest::MqttProvider& mqtt; + const std::unique_ptr p_main; + const std::unique_ptr r_ev_board_support; + const std::vector> r_ev; + const std::vector> r_slac; + const std::vector> r_powermeter; + 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 + // 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 // EV_MANAGER_HPP diff --git a/modules/EvManager/doc.rst b/modules/EvManager/doc.rst new file mode 100644 index 0000000000..51760ccef3 --- /dev/null +++ b/modules/EvManager/doc.rst @@ -0,0 +1,22 @@ +.. _everest_modules_handwritten_EvManager: + +.. This file is a placeholder for an optional single file + handwritten documentation for the EvManager module. + Please decide whether you want to use this single file, + or a set of files in the doc/ directory. + In the latter case, you can delete this file. + In the former case, you can delete the doc/ directory. + +.. This handwritten documentation is optional. In case + you do not want to write it, you can delete this file + and the doc/ directory. + +.. The documentation can be written in reStructuredText, + and will be converted to HTML and PDF by Sphinx. + +******************************************* +EvManager +******************************************* + +:ref:`Link ` to the module's reference. +This module implements a Car simulator that can execute charging sessions using the yeti-simulation-control interface diff --git a/modules/EvManager/docs/index.rst b/modules/EvManager/docs/index.rst new file mode 100644 index 0000000000..738843b915 --- /dev/null +++ b/modules/EvManager/docs/index.rst @@ -0,0 +1,23 @@ +.. _everest_modules_handwritten_EvManager: + +.. This file is a placeholder for optional multiple files + handwritten documentation for the EvManager module. + Please decide whether you want to use the doc.rst file + or a set of files in the doc/ directory. + In the latter case, you can delete the doc.rst file. + In the former case, you can delete the doc/ directory. + +.. This handwritten documentation is optional. In case + you do not want to write it, you can delete this file + and the doc/ directory. + +.. The documentation can be written in reStructuredText, + and will be converted to HTML and PDF by Sphinx. + This index.rst file is the entry point for the module documentation. + +******************************************* +EvManager +******************************************* + +:ref:`Link ` to the module's reference. +This module implements a Car simulator that can execute charging sessions using the yeti-simulation-control interface diff --git a/modules/EvManager/main/RegisteredCommand.cpp b/modules/EvManager/main/RegisteredCommand.cpp new file mode 100644 index 0000000000..907523e43e --- /dev/null +++ b/modules/EvManager/main/RegisteredCommand.cpp @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include "RegisteredCommand.hpp" diff --git a/modules/EvManager/main/RegisteredCommand.hpp b/modules/EvManager/main/RegisteredCommand.hpp new file mode 100644 index 0000000000..5c46730e4a --- /dev/null +++ b/modules/EvManager/main/RegisteredCommand.hpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#ifndef TMP_CLION_CLANG_TIDY_SIMCOMMAND_HPP +#define TMP_CLION_CLANG_TIDY_SIMCOMMAND_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +namespace module::main { + +template class RegisteredCommand; + +class RegisteredCommandBase { +public: + RegisteredCommandBase() = default; + virtual ~RegisteredCommandBase(){}; + RegisteredCommandBase(const RegisteredCommandBase&) = default; + RegisteredCommandBase& operator=(const RegisteredCommandBase&) = default; + RegisteredCommandBase(RegisteredCommandBase&&) = default; + RegisteredCommandBase& operator=(RegisteredCommandBase&&) = default; + + virtual bool operator()(const std::vector& arguments) const = 0; + + // TODO: maybe we could deduce the argument count from the function signature somehow + template + static void registerCommand(std::string commandName, FunctionT&& function, size_t argumentCount) { + registeredCommands.try_emplace(commandName, std::make_unique>( + commandName, argumentCount, std::forward(function))); + } + + static const RegisteredCommandBase& getRegisteredCommand(const std::string& commandName) { + return *registeredCommands.at(commandName).get(); + } + +private: + inline static std::unordered_map> registeredCommands; +}; + +template class RegisteredCommand : public RegisteredCommandBase { +public: + RegisteredCommand(std::string commandName, std::size_t argumentCount, FunctionT function) : + commandName{std::move(commandName)}, argumentCount{argumentCount}, function{std::move(function)} { + } + + ~RegisteredCommand() override = default; + + bool operator()(const std::vector& arguments) const override { + if (!arguments.empty() && arguments.size() != argumentCount) { + throw std::invalid_argument{"Invalid number of arguments"}; + } + return function(arguments); + } + +private: + std::string commandName; + std::size_t argumentCount; + FunctionT function; +}; +} // namespace module::main + +#endif \ No newline at end of file diff --git a/modules/EvManager/main/SimCommand.cpp b/modules/EvManager/main/SimCommand.cpp new file mode 100644 index 0000000000..fd54507343 --- /dev/null +++ b/modules/EvManager/main/SimCommand.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include "SimCommand.hpp" +#include +#include +#include +namespace module::main { + +SimCommand::SimCommand(std::string commandName, std::vector arguments) : + arguments{std::move(arguments)}, registeredCommand{RegisteredCommandBase::getRegisteredCommand(commandName)} { +} + +bool SimCommand::execute() { + return registeredCommand(arguments); +} + +} // namespace module::main diff --git a/modules/EvManager/main/SimCommand.hpp b/modules/EvManager/main/SimCommand.hpp new file mode 100644 index 0000000000..17afaf1579 --- /dev/null +++ b/modules/EvManager/main/SimCommand.hpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#ifndef TMP_CLION_CLANG_TIDY_SIMCOMMAND_HPP +#define TMP_CLION_CLANG_TIDY_SIMCOMMAND_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace module::main { + +class RegisteredCommandBase; + +class SimCommand { +public: + SimCommand(std::string commandName, std::vector arguments); + + bool execute(); + +private: + std::vector arguments; + + const RegisteredCommandBase& registeredCommand; +}; + +} // namespace module::main + +#endif // TMP_CLION_CLANG_TIDY_SIMCOMMAND_HPP diff --git a/modules/EvManager/main/SimData.cpp b/modules/EvManager/main/SimData.cpp new file mode 100644 index 0000000000..8fc1903766 --- /dev/null +++ b/modules/EvManager/main/SimData.cpp @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include "SimData.hpp" +#include +#include + +namespace module::main { + +std::queue parseSimCommands(const std::string& commands) { + auto commandsVector{convertCommandsStringToVector(commands)}; + + auto commandsWithArguments{splitIntoCommandsWithArguments(commandsVector)}; + + return compileCommands(commandsWithArguments); +} + +std::vector convertCommandsStringToVector(const std::string_view& commandsView) { + + auto commands = std::string{commandsView}; + + // convert to lower case inplace + std::transform(commands.begin(), commands.end(), commands.begin(), + [](unsigned char character) { return std::tolower(character); }); + + // replace newlines with semicolons + std::replace(commands.begin(), commands.end(), '\n', ';'); + + // split by semicolons + std::stringstream commandsStream{commands}; + auto command = std::string{}; + auto commandsVector = std::vector{}; + + while (std::getline(commandsStream, command, ';')) { + commandsVector.push_back(command); + } + return commandsVector; +} +std::vector>> +splitIntoCommandsWithArguments(std::vector& commandsVector) { + auto commandsWithArguments = std::vector>>{}; + + for (auto& command : commandsVector) { + commandsWithArguments.push_back(splitIntoCommandWithArguments(command)); + } + return commandsWithArguments; +} + +std::pair> splitIntoCommandWithArguments(std::string& command) { + // replace commas with spaces + std::replace(command.begin(), command.end(), ',', ' '); + + // get commandName name and arguments + auto commandStream = std::stringstream{command}; + auto commandName = std::string{}; + auto argument = std::string{}; + auto arguments = std::vector{}; + + // get commandName name + std::getline(commandStream, commandName, ' '); + + // get arguments + while (std::getline(commandStream, argument, ' ')) { + arguments.push_back(argument); + } + + return {commandName, arguments}; +} +std::queue +compileCommands(std::vector>>& commandsWithArguments) { + auto compiledCommands = std::queue{}; + for (auto& [command, arguments] : commandsWithArguments) { + compiledCommands.emplace(command, arguments); + } + return compiledCommands; +} +} // namespace module::main diff --git a/modules/EvManager/main/SimData.hpp b/modules/EvManager/main/SimData.hpp new file mode 100644 index 0000000000..4471316061 --- /dev/null +++ b/modules/EvManager/main/SimData.hpp @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#ifndef EVEREST_CORE_SIMDATA_HPP +#define EVEREST_CORE_SIMDATA_HPP + +#include "SimCommand.hpp" +#include "generated/types/board_support_common.hpp" +#include +#include +#include +#include +#include +#include + +namespace module::main { + +enum class SimState { + UNPLUGGED, + PLUGGED_IN, + CHARGING_REGULATED, + CHARGING_FIXED, + ERROR_E, + DIODE_FAIL, + ISO_POWER_READY, + ISO_CHARGING_REGULATED, + BCB_TOGGLE, +}; + +struct SimData { + bool executionActive{false}; + size_t loopCurrentCommandIndex{0}; + + SimState state{SimState::UNPLUGGED}; + SimState lastState{SimState::UNPLUGGED}; + std::string slacState; + + bool v2g_finished{false}; + bool iso_stopped{false}; + size_t evse_maxcurrent{0}; + size_t maxCurrent{0}; + std::string payment{"ExternalPayment"}; + std::string energymode{"AC_single_phase_core"}; + bool iso_pwr_ready{false}; + + size_t bcb_toggles{0}; + bool bcb_toggle_C{true}; + + types::board_support_common::Ampacity pp; + float rcd_current_mA{0.0f}; + float pwm_duty_cycle{0.0f}; + + bool dc_power_on{false}; + size_t last_pwm_duty_cycle{0}; + + types::board_support_common::Event actualBspEvent{}; + + + + std::queue commandQueue; +}; + +std::queue parseSimCommands(const std::string& value); + +std::vector convertCommandsStringToVector(const std::string_view& commands); + +std::vector>> +splitIntoCommandsWithArguments(std::vector& commandsVector); + +std::pair> splitIntoCommandWithArguments(std::string& command); + +std::queue +compileCommands(std::vector>>& commandsWithArguments); + +} // namespace module::main + +#endif // EVEREST_CORE_SIMDATA_HPP diff --git a/modules/EvManager/main/car_simulatorImpl.cpp b/modules/EvManager/main/car_simulatorImpl.cpp new file mode 100644 index 0000000000..20884f8fb1 --- /dev/null +++ b/modules/EvManager/main/car_simulatorImpl.cpp @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include "car_simulatorImpl.hpp" +#include "main/SimCommand.hpp" +#include +#include + +namespace module::main { + +void car_simulatorImpl::init() { + // TODO: simulation loop time from manifest.yaml? + simData = std::make_unique(); + subscribeToExternalMQTT(); + subscribeToVariablesOnInit(); +} + +void car_simulatorImpl::ready() { + registerAllCommands(); + enabled = false; + + setupEVParameters(); + + if (mod->config.auto_enable) { + auto enableCopy = mod->config.auto_enable; + handle_enable(enableCopy); + } + if (mod->config.auto_exec) { + auto valueCopy = mod->config.auto_exec_commands; + handle_executeChargingSession(valueCopy); + } +} + +void car_simulatorImpl::handle_enable(bool& value) { + if (!enabled.has_value()) { + EVLOG_warning << "Already received data, but framework is not ready yet."; + return; + } + + if (enabled == value) { + // ignore if value is the same + return; + } + + simData = std::make_unique(); + + callEVBoardSupportFunctions(); + + // set loop interval + if (value) { + enabled = true; + loopIntervalMs = defaultLoopIntervalMs; + simulationThread = std::thread{[this] { handleSimulationLoop(); }}; + } else { + enabled = false; + loopIntervalMs.reset(); + simulationThread.join(); + } + + mod->r_ev_board_support->call_enable(value); + publish_enabled(value); +} + +void car_simulatorImpl::handle_executeChargingSession(std::string& value) { + // Check enabled + if (!checkCanExecute()) { + return; + } + + simData = std::make_unique(); + + auto valueCopy = value; + auto& commandQueue = simData->commandQueue; + commandQueue = parseSimCommands(valueCopy); + + // Start execution + if (!commandQueue.empty()) { + simData->executionActive = true; + } +} + +void car_simulatorImpl::handle_modifyChargingSession(std::string& value) { + if (!enabled) { + EVLOG_warning << "Simulation disabled, cannot execute charging simulation."; + return; + } + + auto valueCopy = value; + simData->commandQueue = parseSimCommands(valueCopy); + + if (!simData->commandQueue.empty()) { + simData->executionActive = true; + } +} + +void car_simulatorImpl::handleSimulationLoop() { + while (enabled) { + if (simData && simData->executionActive) { + if (loopIntervalMs.has_value()) { + runSimulationLoop(); + std::this_thread::sleep_for(std::chrono::milliseconds(loopIntervalMs.value())); + } else { + break; + } + } + } +} + +void car_simulatorImpl::registerAllCommands() { + const auto sleepFuntion = [this](const std::vector& arguments) { + static std::optional sleepTimeLeftMs; + if (!sleepTimeLeftMs.has_value()) { + sleepTimeLeftMs = + std::stoll(arguments[0]) * static_cast((1 / static_cast(loopIntervalMs.value()))) + 1; + } + --sleepTimeLeftMs.value(); + return (!(sleepTimeLeftMs > 0)); + }; + RegisteredCommandBase::registerCommand("sleep", sleepFuntion, 1); + + const auto iec_wait_pwr_readyFunction = [this](const std::vector& arguments) { + return (simData->pwm_duty_cycle > 7.0f && simData->pwm_duty_cycle < 97.0f); + }; + RegisteredCommandBase::registerCommand("iec_wait_pwr_ready", iec_wait_pwr_readyFunction, 0); + + const auto iso_wait_pwm_is_runningFunction = [this](const std::vector& arguments) { + simData->state = SimState::PLUGGED_IN; + return (simData->pwm_duty_cycle > 4.0f && simData->pwm_duty_cycle < 97.0f); + }; + RegisteredCommandBase::registerCommand("iso_wait_pwm_is_running", iso_wait_pwm_is_runningFunction, 0); + + const auto draw_power_regulatedFunction = [this](const std::vector& arguments) { + mod->r_ev_board_support->call_set_ac_max_current(std::stod(arguments[0])); + if (arguments[1] == "3") { + mod->r_ev_board_support->call_set_three_phases(true); + } else { + mod->r_ev_board_support->call_set_three_phases(false); + } + simData->state = SimState::CHARGING_REGULATED; + return true; + }; + RegisteredCommandBase::registerCommand("draw_power_regulated", draw_power_regulatedFunction, 2); + + const auto draw_power_fixedFunction = [this](const std::vector& arguments) { + mod->r_ev_board_support->call_set_ac_max_current(std::stod(arguments[0])); + if (arguments[1] == "3") { + mod->r_ev_board_support->call_set_three_phases(true); + } else { + mod->r_ev_board_support->call_set_three_phases(false); + } + simData->state = SimState::CHARGING_FIXED; + return true; + }; + RegisteredCommandBase::registerCommand("draw_power_fixed", draw_power_fixedFunction, 2); + + const auto pauseFunction = [this](const std::vector& arguments) { + simData->state = SimState::PLUGGED_IN; + return true; + }; + RegisteredCommandBase::registerCommand("pause", pauseFunction, 0); + + const auto unplugFunction = [this](const std::vector& arguments) { + simData->state = SimState::UNPLUGGED; + return true; + }; + RegisteredCommandBase::registerCommand("unplug", unplugFunction, 0); + + const auto error_eFunction = [this](const std::vector& arguments) { + simData->state = SimState::ERROR_E; + return true; + }; + RegisteredCommandBase::registerCommand("error_e", error_eFunction, 0); + + const auto diode_failFunction = [this](const std::vector& arguments) { + simData->state = SimState::DIODE_FAIL; + return true; + }; + RegisteredCommandBase::registerCommand("diode_fail", diode_failFunction, 0); + + const auto rcd_currentFunction = [this](const std::vector& arguments) { + simData->rcd_current_mA = std::stof(arguments[0]); + return true; + }; + RegisteredCommandBase::registerCommand("rcd_current", rcd_currentFunction, 1); + + const auto iso_wait_slac_matchedFunction = [this](const std::vector& arguments) { + simData->state = SimState::PLUGGED_IN; + + if (mod->r_slac.empty()) { + EVLOG_debug << "Slac undefined"; + } + if (simData->slacState == "UNMATCHED") { + EVLOG_debug << "Slac UNMATCHED"; + if (!mod->r_slac.empty()) { + EVLOG_debug << "Slac trigger matching"; + mod->r_slac[0]->call_reset(); + mod->r_slac[0]->call_trigger_matching(); + simData->slacState = "TRIGGERED"; + } + } + if (simData->slacState == "MATCHED") { + EVLOG_debug << "Slac Matched"; + return true; + } + return false; + }; + RegisteredCommandBase::registerCommand("iso_wait_slac_matched", iso_wait_slac_matchedFunction, 0); + + if (!mod->r_ev.empty()) { + const auto iso_start_v2g_sessionFunction = [this](const std::vector& arguments) { + const auto& argument = arguments[0]; + if (argument == "ac_single_phase_core") { + mod->r_ev[0]->call_start_charging("AC_single_phase_core"); + } + if (argument == "ac_three_phase_core") { + mod->r_ev[0]->call_start_charging("AC_three_phase_core"); + } + if (argument == "dc_core") { + mod->r_ev[0]->call_start_charging("DC_core"); + } + if (argument == "dc_extended") { + mod->r_ev[0]->call_start_charging("DC_extended"); + } + if (argument == "dc_combo_core") { + mod->r_ev[0]->call_start_charging("DC_combo_core"); + } + if (argument == "dc_unique") { + mod->r_ev[0]->call_start_charging("DC_unique"); + } else { + return false; + } + return true; + }; + RegisteredCommandBase::registerCommand("iso_start_v2g_sessionFunction", iso_start_v2g_sessionFunction, 1); + } + + const auto iso_wait_pwr_readyFunction = [this](const std::vector& arguments) { + if (simData->iso_pwr_ready) { + simData->state = SimState::ISO_POWER_READY; + return true; + } + return false; + }; + RegisteredCommandBase::registerCommand("iso_wait_pwr_ready", iso_wait_pwr_readyFunction, 0); + + const auto iso_dc_power_onFunction = [this](const std::vector& arguments) { + simData->state = SimState::ISO_POWER_READY; + if (simData->dc_power_on) { + simData->state = SimState::ISO_CHARGING_REGULATED; + mod->r_ev_board_support->call_allow_power_on(true); + return true; + } + return false; + }; + RegisteredCommandBase::registerCommand("iso_dc_power_on", iso_dc_power_onFunction, 0); + + const auto iso_draw_power_regulatedFuncdtion = [this](const std::vector& arguments) { + mod->r_ev_board_support->call_set_ac_max_current(std::stod(arguments[0])); + if (arguments[1] == "3") { + mod->r_ev_board_support->call_set_three_phases(true); + } else { + mod->r_ev_board_support->call_set_three_phases(false); + } + simData->state = SimState::ISO_CHARGING_REGULATED; + return true; + }; + RegisteredCommandBase::registerCommand("iso_draw_power_regulated", iso_draw_power_regulatedFuncdtion, 2); + + if (!mod->r_ev.empty()) { + const auto iso_stop_chargingFunction = [this](const std::vector& arguments) { + mod->r_ev[0]->call_stop_charging(); + mod->r_ev_board_support->call_allow_power_on(false); + simData->state = SimState::PLUGGED_IN; + return true; + }; + RegisteredCommandBase::registerCommand("iso_stop_charging", iso_stop_chargingFunction, 0); + + const auto iso_wait_for_stop = [this](const std::vector& arguments) { + static auto sleepTimeLeftMs = std::optional{}; + if (!sleepTimeLeftMs.has_value()) { + sleepTimeLeftMs = + std::stoll(arguments[0]) * static_cast(1 / static_cast(loopIntervalMs.value())) + 1; + } + --sleepTimeLeftMs.value(); + if (!sleepTimeLeftMs > 0) { + mod->r_ev[0]->call_stop_charging(); + mod->r_ev_board_support->call_allow_power_on(false); + simData->state = SimState::PLUGGED_IN; + return true; + } + if (simData->iso_stopped) { + EVLOG_info << "POWER OFF iso stopped"; + mod->r_ev_board_support->call_allow_power_on(false); + simData->state = SimState::PLUGGED_IN; + return true; + } + return false; + }; + RegisteredCommandBase::registerCommand("iso_wait_for_stop", iso_wait_for_stop, 1); + + const auto iso_wait_v2g_session_stoppedFunction = [this](const std::vector& arguments) { + if (simData->v2g_finished) { + return true; + } + return false; + }; + RegisteredCommandBase::registerCommand("iso_wait_v2g_session_stopped", iso_wait_v2g_session_stoppedFunction, 0); + + const auto iso_pause_chargingFunction = [this](const std::vector& arguments) { + mod->r_ev[0]->call_pause_charging(); + simData->state = SimState::PLUGGED_IN; + simData->iso_pwr_ready = false; + return true; + }; + RegisteredCommandBase::registerCommand("iso_pause_charging", iso_pause_chargingFunction, 0); + + const auto iso_wait_for_resumeFunction = [this](const std::vector& arguments) { return false; }; + RegisteredCommandBase::registerCommand("iso_wait_for_resume", iso_wait_for_resumeFunction, 0); + + const auto iso_start_bcb_toggleFunction = [this](const std::vector& arguments) { + simData->state = SimState::BCB_TOGGLE; + if (simData->bcb_toggles >= std::stoul(arguments[0]) || simData->bcb_toggles == 3) { + simData->bcb_toggles = 0; + simData->state = SimState::PLUGGED_IN; + return true; + } + return false; + }; + RegisteredCommandBase::registerCommand("iso_start_bcb_toggle", iso_start_bcb_toggleFunction, 1); + } + + const auto wait_for_real_pluginFunction = [this](const std::vector& arguments) { + using types::board_support_common::Event; + if (simData->actualBspEvent == Event::A) { + EVLOG_info << "Real plugin detected"; + simData->state = SimState::PLUGGED_IN; + return true; + } + return false; + }; + RegisteredCommandBase::registerCommand("wait_for_real_plugin", wait_for_real_pluginFunction, 0); +} + +void car_simulatorImpl::carStateMachine() { + using types::ev_board_support::EvCpState; + + const auto stateHasChanged = simData->state == simData->lastState; + simData->lastState = simData->state; + + switch (simData->state) { + case SimState::UNPLUGGED: + if (stateHasChanged) { + + mod->r_ev_board_support->call_set_cp_state(EvCpState::A); + mod->r_ev_board_support->call_allow_power_on(false); + // Wait for physical plugin (ev BSP sees state A on CP and not Disconnected) + + // If we have auto_exec configured, restart simulation when it was unplugged + EVLOG_info << "Unplug detected, restarting simulation."; + simData->slacState = "UNMATCHED"; + mod->r_ev[0]->call_stop_charging(); + if (mod->config.auto_exec) { + auto valueCopy = mod->config.auto_exec_commands; + handle_executeChargingSession(valueCopy); + } + } + break; + case SimState::PLUGGED_IN: + if (stateHasChanged) { + mod->r_ev_board_support->call_set_cp_state(EvCpState::B); + mod->r_ev_board_support->call_allow_power_on(false); + } + break; + case SimState::CHARGING_REGULATED: + if (stateHasChanged || simData->pwm_duty_cycle != simData->last_pwm_duty_cycle) { + simData->last_pwm_duty_cycle = simData->pwm_duty_cycle; + // do not draw power if EVSE paused by stopping PWM + if (simData->pwm_duty_cycle > 7.0 && simData->pwm_duty_cycle < 97.0) { + mod->r_ev_board_support->call_set_cp_state(EvCpState::C); + } else { + mod->r_ev_board_support->call_set_cp_state(EvCpState::B); + } + } + break; + case SimState::CHARGING_FIXED: + // Todo(sl): What to do here + if (stateHasChanged) { + // Also draw power if EVSE stopped PWM - this is a break the rules mode to test the charging implementation! + mod->r_ev_board_support->call_set_cp_state(EvCpState::C); + } + break; + + case SimState::ERROR_E: + if (stateHasChanged) { + mod->r_ev_board_support->call_set_cp_state(EvCpState::E); + mod->r_ev_board_support->call_allow_power_on(false); + } + break; + case SimState::DIODE_FAIL: + if (stateHasChanged) { + mod->r_ev_board_support->call_diode_fail(true); + mod->r_ev_board_support->call_allow_power_on(false); + } + break; + case SimState::ISO_POWER_READY: + if (stateHasChanged) { + mod->r_ev_board_support->call_set_cp_state(EvCpState::C); + } + break; + case SimState::ISO_CHARGING_REGULATED: + if (stateHasChanged) { + mod->r_ev_board_support->call_set_cp_state(EvCpState::C); + } + break; + case SimState::BCB_TOGGLE: + if (simData->bcb_toggle_C) { + mod->r_ev_board_support->call_set_cp_state(EvCpState::C); + simData->bcb_toggle_C = false; + } else { + mod->r_ev_board_support->call_set_cp_state(EvCpState::B); + simData->bcb_toggle_C = true; + ++simData->bcb_toggles; + } + break; + default: + simData->state = SimState::UNPLUGGED; + break; + } +} + +void car_simulatorImpl::runSimulationLoop() { + // Execute sim commands until a command blocks or we are finished + auto& commandQueue = simData->commandQueue; + while (simData->executionActive && !commandQueue.empty()) { + auto& currentCommand = commandQueue.front(); + + if (currentCommand.execute()) { + } else + break; // command blocked, wait for timer to run this function again + commandQueue.pop(); + } + + EVLOG_debug << "Finished simulation."; + + // reset simData + simData = std::make_unique(); + + // If we have auto_exec configured, restart simulation when it is done + if (mod->config.auto_exec) { + auto valueCopy = mod->config.auto_exec_commands; + handle_executeChargingSession(valueCopy); + } + carStateMachine(); +} + +bool car_simulatorImpl::checkCanExecute() { + if (!enabled.has_value()) { + EVLOG_warning << "Already received data, but framework is not ready yet."; + return false; + } + + const auto enabledValue = enabled.value(); + if (!enabledValue) { + EVLOG_warning << "Simulation disabled, cannot execute charging simulation."; + return false; + } + if (simData->executionActive) { + EVLOG_warning << "Execution of charging session simulation already running, cannot start new one."; + return false; + } + + return true; +} + +void car_simulatorImpl::subscribeToVariablesOnInit() { + // subscribe bsp_event + using types::board_support_common::BspEvent; + mod->r_ev_board_support->subscribe_bsp_event([this](auto bsp_event) { + simData->actualBspEvent = bsp_event.event; + if (bsp_event.event == types::board_support_common::Event::Disconnected && + simData->state == main::SimState::UNPLUGGED) { + simData->executionActive = false; + simData->state = main::SimState::UNPLUGGED; + } + }); + + // subscribe bsp_measurement + using types::board_support_common::BspMeasurement; + mod->r_ev_board_support->subscribe_bsp_measurement([this](auto measurement) { + simData->pp = measurement.proximity_pilot.ampacity; + simData->rcd_current_mA = measurement.rcd_current_mA.value(); + simData->pwm_duty_cycle = measurement.cp_pwm_duty_cycle; + }); + + // subscribe slac_state + if (!mod->r_slac.empty()) { + const auto& slac = mod->r_slac.at(0); + slac->subscribe_state([this](const auto& state) { simData->slacState = state; }); + } + + // subscribe ev events + if (!mod->r_ev.empty()) { + const auto& _ev = mod->r_ev.at(0); + _ev->subscribe_AC_EVPowerReady([this](auto value) { simData->iso_pwr_ready = value; }); + _ev->subscribe_AC_EVSEMaxCurrent([this](auto value) { simData->iso_pwr_ready = value; }); + _ev->subscribe_AC_StopFromCharger([this]() { simData->iso_stopped = true; }); + _ev->subscribe_V2G_Session_Finished([this]() { simData->v2g_finished = true; }); + _ev->subscribe_DC_PowerOn([this]() { simData->dc_power_on = true; }); + } +} +void car_simulatorImpl::setupEVParameters() { + if (!mod->r_ev.empty()) { + mod->r_ev[0]->call_set_dc_params({.MaxCurrentLimit = mod->config.dc_max_current_limit, + .MaxPowerLimit = mod->config.dc_max_power_limit, + .MaxVoltageLimit = mod->config.dc_max_voltage_limit, + .EnergyCapacity = mod->config.dc_energy_capacity, + .TargetCurrent = mod->config.dc_target_current, + .TargetVoltage = mod->config.dc_target_voltage}); + if (mod->config.support_sae_j2847) { + mod->r_ev[0]->call_enable_sae_j2847_v2g_v2h(); + mod->r_ev[0]->call_set_bpt_dc_params( + {.DischargeMaxCurrentLimit = mod->config.dc_discharge_max_current_limit, + .DischargeMaxPowerLimit = mod->config.dc_discharge_max_power_limit, + .DischargeTargetCurrent = mod->config.dc_discharge_target_current, + .DischargeMinimalSoC = mod->config.dc_discharge_v2g_minimal_soc}); + } + } +} +void car_simulatorImpl::callEVBoardSupportFunctions() { + mod->r_ev_board_support->call_allow_power_on(false); + + mod->r_ev_board_support->call_set_ac_max_current(mod->config.max_curent); + mod->r_ev_board_support->call_set_three_phases(mod->config.three_phases); +} +void car_simulatorImpl::subscribeToExternalMQTT() { + const auto& mqtt = mod->mqtt; + EVLOG_critical << "TEST MQTT SUBSCRIBE"; + EVLOG_critical << "everest_external/nodered/" + std::to_string(mod->config.connector_id) + "/carsim/cmd/enable"; + mqtt.subscribe("everest_external/nodered/" + std::to_string(mod->config.connector_id) + "/carsim/cmd/enable", + [this](const std::string& message) { EVLOG_critical << "Received message: " << message; }); + mqtt.subscribe("everest_external/nodered/" + std::to_string(mod->config.connector_id) + + "/carsim/cmd/execute_charging_session", + [this](const auto data) { + auto dataCopy{data}; + handle_executeChargingSession(dataCopy); + }); + mqtt.subscribe("everest_external/nodered/" + std::to_string(mod->config.connector_id) + + "/carsim/cmd/modify_charging_session", + [this](auto data) { + auto dataCopy = data; + handle_modifyChargingSession(dataCopy); + }); + EVLOG_critical << "TEST MQTT DONE"; +} + +} // namespace module::main diff --git a/modules/EvManager/main/car_simulatorImpl.hpp b/modules/EvManager/main/car_simulatorImpl.hpp new file mode 100644 index 0000000000..d5e3e2544a --- /dev/null +++ b/modules/EvManager/main/car_simulatorImpl.hpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef MAIN_CAR_SIMULATOR_IMPL_HPP +#define MAIN_CAR_SIMULATOR_IMPL_HPP + +// +// AUTO GENERATED - MARKED REGIONS WILL BE KEPT +// template version 3 +// + +#include +#include + +#include "../EvManager.hpp" +#include "SimData.hpp" + +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 +// insert your custom include headers here +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 + +namespace module::main { + +struct Conf {}; + +class car_simulatorImpl : public car_simulatorImplBase { +public: + car_simulatorImpl() = delete; + car_simulatorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : + car_simulatorImplBase(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: + // commandName handler functions (virtual) + virtual void handle_enable(bool& value) override; + virtual void handle_executeChargingSession(std::string& value) override; + + // 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 + + void handle_modifyChargingSession(std::string& value); + bool checkCanExecute(); + void handleSimulationLoop(); + void runSimulationLoop(); + void registerAllCommands(); + void carStateMachine(); + void subscribeToVariablesOnInit(); + void setupEVParameters(); + void callEVBoardSupportFunctions(); + void subscribeToExternalMQTT(); + + std::unique_ptr simData; + std::optional enabled; + std::optional loopIntervalMs; + const size_t defaultLoopIntervalMs{250}; + + std::thread simulationThread; + // 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 module::main + +#endif // MAIN_CAR_SIMULATOR_IMPL_HPP diff --git a/modules/EvManager/manifest.yaml b/modules/EvManager/manifest.yaml new file mode 100644 index 0000000000..f12e14cacc --- /dev/null +++ b/modules/EvManager/manifest.yaml @@ -0,0 +1,98 @@ +description: >- + This module implements a Car simulator that can execute charging sessions using the yeti-simulation-control interface +config: + connector_id: + description: Connector id of the evse manager to which this simulator is connected to + type: integer + auto_enable: + description: >- + Enable this simulation directly at start. Set to true for pure SIL configs, set to false for HIL. + type: boolean + default: false + auto_exec: + description: >- + Enable automatic execution of simulation commands at startup from auto_exec_commands config option. + type: boolean + default: false + auto_exec_commands: + description: >- + Simulation commands, e.g. sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 30;unplug + type: string + default: "" + dc_max_current_limit: + description: Maximum current allowed by the EV + type: integer + default: 300 + dc_max_power_limit: + description: Maximum power allowed by the EV + type: integer + default: 150000 + dc_max_voltage_limit: + description: Maximum voltage allowed by the EV + type: integer + default: 900 + dc_energy_capacity: + description: Energy capacity of the EV + type: integer + default: 60000 + dc_target_current: + description: Target current requested by the EV + type: integer + default: 5 + dc_target_voltage: + description: Target voltage requested by the EV + type: integer + default: 200 + support_sae_j2847: + description: Supporting SAE J2847 ISO 2 bidi version + type: boolean + default: false + dc_discharge_max_current_limit: + description: Maximum discharge current allowed by the EV + type: integer + default: 300 + dc_discharge_max_power_limit: + description: Maximum discharge power allowed by the EV + type: integer + default: 150000 + dc_discharge_target_current: + description: Discharge target current requested by the EV + type: integer + default: 5 + dc_discharge_v2g_minimal_soc: + description: Discharge minimal soc at which the evse should shutdown + type: integer + default: 20 + max_curent: + description: Ac max current in Ampere + type: number + default: 16 + three_phases: + description: Support three phase + type: boolean + default: true +provides: + main: + interface: car_simulator + description: This implements the car simulator +requires: + ev_board_support: + interface: ev_board_support + ev: + interface: ISO15118_ev + min_connections: 0 + max_connections: 1 + slac: + interface: ev_slac + min_connections: 0 + max_connections: 1 + powermeter: + interface: powermeter + min_connections: 0 + max_connections: 1 +enable_external_mqtt: true +metadata: + license: https://opensource.org/licenses/Apache-2.0 + authors: + - Cornelius Claussen + - Sebastian Lukas diff --git a/modules/EvManager/tests/CMakeLists.txt b/modules/EvManager/tests/CMakeLists.txt new file mode 100644 index 0000000000..c2a3249328 --- /dev/null +++ b/modules/EvManager/tests/CMakeLists.txt @@ -0,0 +1,21 @@ +set(TARGET_NAME ${PROJECT_NAME}_module_ev_manager_tests) +add_executable(${TARGET_NAME}) + +target_sources(${TARGET_NAME} + PRIVATE + RegisteredCommand.test.cpp +) + +target_link_libraries(${TARGET_NAME} + PRIVATE + everest::framework + everest::log + Catch2::Catch2WithMain +) +if (NOT DISABLE_EDM) + list(APPEND CMAKE_MODULE_PATH ${CPM_PACKAGE_catch2_SOURCE_DIR}/extras) + include(Catch) + catch_discover_tests(${TARGET_NAME}) +endif () + +catch_discover_tests(${TARGET_NAME}) diff --git a/modules/EvManager/tests/RegisteredCommand.test.cpp b/modules/EvManager/tests/RegisteredCommand.test.cpp new file mode 100644 index 0000000000..754962c51d --- /dev/null +++ b/modules/EvManager/tests/RegisteredCommand.test.cpp @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest