Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
471
tools/EVerest-main/modules/EV/EvManager/main/car_simulation.cpp
Normal file
471
tools/EVerest-main/modules/EV/EvManager/main/car_simulation.cpp
Normal file
@@ -0,0 +1,471 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "car_simulation.hpp"
|
||||
|
||||
#include "constants.hpp"
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
constexpr double MS_FACTOR = (1.0 / 60.0 / 60.0 / 1000.0);
|
||||
|
||||
void CarSimulation::state_machine() {
|
||||
using types::ev_board_support::EvCpState;
|
||||
|
||||
const auto state_has_changed = sim_data.state != sim_data.last_state;
|
||||
sim_data.last_state = sim_data.state;
|
||||
|
||||
switch (sim_data.state) {
|
||||
case SimState::UNPLUGGED:
|
||||
if (state_has_changed) {
|
||||
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::A);
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
// Wait for physical plugin (ev BSP sees state A on CP and not Disconnected)
|
||||
|
||||
sim_data.slac_state = types::slac::State::UNMATCHED;
|
||||
if (!r_ev.empty()) {
|
||||
r_ev[0]->call_stop_charging();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SimState::PLUGGED_IN:
|
||||
if (state_has_changed) {
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::B);
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
simulate_soc();
|
||||
}
|
||||
break;
|
||||
case SimState::CHARGING_REGULATED:
|
||||
if (state_has_changed || sim_data.pwm_duty_cycle != sim_data.last_pwm_duty_cycle) {
|
||||
sim_data.last_pwm_duty_cycle = sim_data.pwm_duty_cycle;
|
||||
// do not draw power if EVSE paused by stopping PWM
|
||||
if (sim_data.pwm_duty_cycle > 7.0 && sim_data.pwm_duty_cycle < 97.0) {
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::C);
|
||||
r_ev_board_support->call_allow_power_on(true);
|
||||
} else {
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::B);
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SimState::CHARGING_FIXED:
|
||||
// Todo(sl): What to do here
|
||||
if (state_has_changed) {
|
||||
// Also draw power if EVSE stopped PWM - this is a break the rules simulator->mode to test the charging
|
||||
// implementation!
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::C);
|
||||
r_ev_board_support->call_allow_power_on(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case SimState::ERROR_E:
|
||||
if (state_has_changed) {
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::E);
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
}
|
||||
break;
|
||||
case SimState::DIODE_FAIL:
|
||||
if (state_has_changed) {
|
||||
r_ev_board_support->call_diode_fail(true);
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
}
|
||||
break;
|
||||
case SimState::ISO_POWER_READY:
|
||||
if (state_has_changed) {
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::C);
|
||||
}
|
||||
break;
|
||||
case SimState::ISO_CHARGING_REGULATED:
|
||||
if (state_has_changed) {
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::C);
|
||||
r_ev_board_support->call_allow_power_on(true);
|
||||
}
|
||||
break;
|
||||
case SimState::BCB_TOGGLE:
|
||||
if (sim_data.bcb_toggle_C) {
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::C);
|
||||
sim_data.bcb_toggle_C = false;
|
||||
} else {
|
||||
r_ev_board_support->call_set_cp_state(EvCpState::B);
|
||||
sim_data.bcb_toggle_C = true;
|
||||
++sim_data.bcb_toggles;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
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::milliseconds>(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;
|
||||
}
|
||||
|
||||
auto soc = (sim_data.battery_charge_wh / sim_data.battery_capacity_wh) * 100.0;
|
||||
|
||||
if (soc > 100.0) {
|
||||
soc = 100.0;
|
||||
} else if (soc <= 0.0) {
|
||||
soc = 0.0;
|
||||
}
|
||||
|
||||
if (latest_soc != soc) {
|
||||
latest_soc = soc;
|
||||
|
||||
if (!r_ev.empty()) {
|
||||
r_ev[0]->call_update_soc(soc);
|
||||
}
|
||||
}
|
||||
|
||||
ev_info.soc = soc;
|
||||
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]);
|
||||
const auto sleep_time_ms = sleep_time * 1000;
|
||||
sim_data.sleep_ticks_left = static_cast<long long>(sleep_time_ms / loop_interval_ms) + 1;
|
||||
}
|
||||
auto& sleep_ticks_left = sim_data.sleep_ticks_left.value();
|
||||
sleep_ticks_left -= 1;
|
||||
if (not(sleep_ticks_left > 0)) {
|
||||
sim_data.sleep_ticks_left.reset();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CarSimulation::iec_wait_pwr_ready(const CmdArguments& arguments) {
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
return (sim_data.pwm_duty_cycle > 7.0f && sim_data.pwm_duty_cycle < 97.0f);
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_wait_pwm_is_running(const CmdArguments& arguments) {
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
return (sim_data.pwm_duty_cycle > 4.0f && sim_data.pwm_duty_cycle < 97.0f);
|
||||
}
|
||||
|
||||
bool CarSimulation::draw_power_regulated(const CmdArguments& arguments) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
bool CarSimulation::pause(const CmdArguments& arguments) {
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CarSimulation::unplug(const CmdArguments& arguments) {
|
||||
sim_data.state = SimState::UNPLUGGED;
|
||||
charge_mode = ChargeMode::None;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CarSimulation::error_e(const CmdArguments& arguments) {
|
||||
sim_data.state = SimState::ERROR_E;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CarSimulation::diode_fail(const CmdArguments& arguments) {
|
||||
sim_data.state = SimState::DIODE_FAIL;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CarSimulation::rcd_current(const CmdArguments& arguments) {
|
||||
sim_data.rcd_current_ma = std::stof(arguments[0]);
|
||||
r_ev_board_support->call_set_rcd_error(sim_data.rcd_current_ma);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_wait_slac_matched(const CmdArguments& arguments) {
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
|
||||
if (sim_data.slac_state == types::slac::State::UNMATCHED) {
|
||||
EVLOG_debug << "Slac UNMATCHED";
|
||||
if (!r_slac.empty()) {
|
||||
EVLOG_debug << "Slac trigger matching";
|
||||
r_slac[0]->call_reset();
|
||||
r_slac[0]->call_trigger_matching();
|
||||
sim_data.slac_state = types::slac::State::MATCHING;
|
||||
}
|
||||
}
|
||||
if (sim_data.slac_state == types::slac::State::MATCHED) {
|
||||
EVLOG_debug << "Slac Matched";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_wait_pwr_ready(const CmdArguments& arguments) {
|
||||
if (sim_data.iso_pwr_ready) {
|
||||
sim_data.state = SimState::ISO_POWER_READY;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_dc_power_on(const CmdArguments& arguments) {
|
||||
sim_data.state = SimState::ISO_POWER_READY;
|
||||
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;
|
||||
}
|
||||
|
||||
if (sim_data.iso_charger_paused) {
|
||||
|
||||
const auto cmds =
|
||||
std::array<std::string, 2>{"pause;iso_wait_v2g_session_stopped;sleep 2;iso_wait_pwm_is_running;",
|
||||
"iso_wait_pwr_ready;iso_wait_for_stop 36000"};
|
||||
|
||||
EVLOG_info << "Charger wants to pause the session";
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
|
||||
// NOTE(sl): Change when the Energymode has more then 2 values
|
||||
const std::string energy_mode = (sim_data.energy_mode == EnergyMode::AC) ? "AC" : "DC";
|
||||
const std::string iso_start_v2g_session = "iso_start_v2g_session " + energy_mode + ";";
|
||||
|
||||
auto& modify_session_cmds = sim_data.modify_charging_session_cmds.emplace();
|
||||
|
||||
modify_session_cmds = cmds[0];
|
||||
modify_session_cmds += iso_start_v2g_session;
|
||||
modify_session_cmds += cmds[1];
|
||||
|
||||
sim_data.iso_pwr_ready = false;
|
||||
sim_data.sleep_ticks_left.reset();
|
||||
sim_data.iso_charger_paused = false;
|
||||
|
||||
// NOTE(sl): return false, otherwise the simulation will end too early before the session cmds can be adjusted
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_start_v2g_session(const CmdArguments& arguments, bool three_phases) {
|
||||
const auto& energy_mode = arguments[0];
|
||||
const auto& payment_option = arguments[1];
|
||||
const auto& departure_time = std::stoi(arguments[2]);
|
||||
const auto& e_amount = std::stoi(arguments[3]);
|
||||
|
||||
const auto selected_payment_option = [payment_option,
|
||||
this](bool auto_payment_option) -> types::iso15118::SelectedPaymentOption {
|
||||
if (auto_payment_option) {
|
||||
return types::iso15118::SelectedPaymentOption{};
|
||||
}
|
||||
auto selected_payment_option =
|
||||
types::iso15118::SelectedPaymentOption{std::nullopt, config.force_payment_option};
|
||||
|
||||
if (payment_option == "externalpayment") {
|
||||
selected_payment_option.payment_option = types::iso15118::PaymentOption::ExternalPayment;
|
||||
} else if (payment_option == "contract") {
|
||||
selected_payment_option.payment_option = types::iso15118::PaymentOption::Contract;
|
||||
} else {
|
||||
EVLOG_warning << "Go back to auto payment because payment_option was not recognized";
|
||||
selected_payment_option.enforce_payment_option.reset();
|
||||
}
|
||||
|
||||
return selected_payment_option;
|
||||
}(payment_option == "auto");
|
||||
|
||||
if (energy_mode == constants::AC) {
|
||||
sim_data.energy_mode = EnergyMode::AC;
|
||||
if (not three_phases) {
|
||||
r_ev[0]->call_start_charging(types::iso15118::EnergyTransferMode::AC_single_phase_core,
|
||||
selected_payment_option, departure_time, e_amount);
|
||||
charge_mode = ChargeMode::AC;
|
||||
} else {
|
||||
r_ev[0]->call_start_charging(types::iso15118::EnergyTransferMode::AC_three_phase_core,
|
||||
selected_payment_option, departure_time, e_amount);
|
||||
charge_mode = ChargeMode::ACThreePhase;
|
||||
}
|
||||
} else if (energy_mode == constants::DC) {
|
||||
r_ev[0]->call_start_charging(types::iso15118::EnergyTransferMode::DC_extended, selected_payment_option,
|
||||
departure_time, e_amount);
|
||||
sim_data.energy_mode = EnergyMode::DC;
|
||||
charge_mode = ChargeMode::DC;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_draw_power_regulated(const CmdArguments& arguments) {
|
||||
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;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_stop_charging(const CmdArguments& arguments) {
|
||||
r_ev[0]->call_stop_charging();
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
if (sim_data.state != SimState::UNPLUGGED) {
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
}
|
||||
charge_mode = ChargeMode::None;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_wait_for_stop(const CmdArguments& arguments, size_t loop_interval_ms) {
|
||||
if (not sim_data.sleep_ticks_left.has_value()) {
|
||||
const auto sleep_time_ms = std::stold(arguments[0]) * 1000;
|
||||
sim_data.sleep_ticks_left = static_cast<long long>(sleep_time_ms / loop_interval_ms) + 1;
|
||||
}
|
||||
auto& sleep_ticks_left = sim_data.sleep_ticks_left.value();
|
||||
sleep_ticks_left -= 1;
|
||||
if (not(sleep_ticks_left > 0)) {
|
||||
r_ev[0]->call_stop_charging();
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
sim_data.sleep_ticks_left.reset();
|
||||
return true;
|
||||
}
|
||||
if (sim_data.iso_stopped) {
|
||||
EVLOG_info << "POWER OFF iso stopped";
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
sim_data.sleep_ticks_left.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sim_data.iso_charger_paused) {
|
||||
|
||||
const auto cmds =
|
||||
std::array<std::string, 2>{"pause;iso_wait_v2g_session_stopped;sleep 2;iso_wait_pwm_is_running;",
|
||||
"iso_wait_pwr_ready;iso_wait_for_stop 36000"};
|
||||
|
||||
EVLOG_info << "Charger wants to pause the session";
|
||||
r_ev_board_support->call_allow_power_on(false);
|
||||
|
||||
// NOTE(sl): Change when the Energymode has more then 2 values
|
||||
const std::string energy_mode = (sim_data.energy_mode == EnergyMode::AC) ? "AC" : "DC";
|
||||
const std::string iso_start_v2g_session = "iso_start_v2g_session " + energy_mode + ";";
|
||||
|
||||
auto& modify_session_cmds = sim_data.modify_charging_session_cmds.emplace();
|
||||
|
||||
modify_session_cmds = cmds[0];
|
||||
modify_session_cmds += iso_start_v2g_session;
|
||||
modify_session_cmds += cmds[1];
|
||||
|
||||
sim_data.iso_pwr_ready = false;
|
||||
sim_data.sleep_ticks_left.reset();
|
||||
sim_data.iso_charger_paused = false;
|
||||
|
||||
// NOTE(sl): return false, otherwise the simulation will end too early before the session cmds can be adjusted
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_wait_v2g_session_stopped(const CmdArguments& arguments) {
|
||||
if (sim_data.v2g_finished) {
|
||||
sim_data.v2g_finished = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_pause_charging(const CmdArguments& arguments) {
|
||||
r_ev[0]->call_pause_charging();
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
sim_data.iso_pwr_ready = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_wait_for_resume(const CmdArguments& arguments) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CarSimulation::iso_start_bcb_toggle(const CmdArguments& arguments) {
|
||||
sim_data.v2g_finished = false;
|
||||
sim_data.state = SimState::BCB_TOGGLE;
|
||||
if (sim_data.bcb_toggles >= std::stoul(arguments[0]) || sim_data.bcb_toggles == 3) {
|
||||
sim_data.bcb_toggles = 0;
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CarSimulation::wait_for_real_plugin(const CmdArguments& arguments) {
|
||||
using types::board_support_common::Event;
|
||||
if (sim_data.actual_bsp_event == Event::A) {
|
||||
EVLOG_info << "Real plugin detected";
|
||||
sim_data.state = SimState::PLUGGED_IN;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
150
tools/EVerest-main/modules/EV/EvManager/main/car_simulation.hpp
Normal file
150
tools/EVerest-main/modules/EV/EvManager/main/car_simulation.hpp
Normal file
@@ -0,0 +1,150 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "simulation_data.hpp"
|
||||
|
||||
#include "../EvManager.hpp"
|
||||
#include <generated/interfaces/ISO15118_ev/Interface.hpp>
|
||||
#include <generated/interfaces/ev_board_support/Interface.hpp>
|
||||
#include <generated/interfaces/ev_manager/Implementation.hpp>
|
||||
#include <generated/interfaces/ev_slac/Interface.hpp>
|
||||
#include <generated/types/ev_board_support.hpp>
|
||||
|
||||
using CmdArguments = std::vector<std::string>;
|
||||
|
||||
class CarSimulation {
|
||||
public:
|
||||
CarSimulation(const std::unique_ptr<ev_board_supportIntf>& r_ev_board_support_,
|
||||
const std::vector<std::unique_ptr<ISO15118_evIntf>>& r_ev_,
|
||||
const std::vector<std::unique_ptr<ev_slacIntf>>& r_slac_,
|
||||
const std::unique_ptr<ev_managerImplBase>& 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);
|
||||
}
|
||||
|
||||
void set_soc(double soc) {
|
||||
if (soc < 0 || soc > 100) {
|
||||
throw std::out_of_range("SoC value " + std::to_string(soc) + " is out of range (0-100)");
|
||||
}
|
||||
sim_data.battery_charge_wh = config.dc_energy_capacity * (soc / 100.0);
|
||||
}
|
||||
|
||||
const SimState& get_state() const {
|
||||
return sim_data.state;
|
||||
}
|
||||
|
||||
std::optional<std::string>& get_modify_charging_session_cmds() {
|
||||
return sim_data.modify_charging_session_cmds;
|
||||
}
|
||||
|
||||
void update_modify_charging_session_cmds(const std::string& cmds) {
|
||||
sim_data.modify_charging_session_cmds.emplace(cmds);
|
||||
}
|
||||
|
||||
void set_state(SimState state) {
|
||||
sim_data.state = state;
|
||||
}
|
||||
|
||||
void set_bsp_event(types::board_support_common::Event event) {
|
||||
sim_data.actual_bsp_event = event;
|
||||
}
|
||||
|
||||
void set_pp(types::board_support_common::Ampacity pp) {
|
||||
sim_data.pp = pp;
|
||||
}
|
||||
|
||||
void set_rcd_current(float rcd_current) {
|
||||
sim_data.rcd_current_ma = rcd_current;
|
||||
}
|
||||
|
||||
void set_pwm_duty_cycle(float pwm_duty_cycle) {
|
||||
sim_data.pwm_duty_cycle = pwm_duty_cycle;
|
||||
}
|
||||
|
||||
void set_slac_state(types::slac::State slac_state) {
|
||||
sim_data.slac_state = slac_state;
|
||||
}
|
||||
|
||||
void set_iso_pwr_ready(bool iso_pwr_ready) {
|
||||
sim_data.iso_pwr_ready = iso_pwr_ready;
|
||||
}
|
||||
|
||||
void set_evse_max_current(size_t evse_max_current) {
|
||||
sim_data.evse_maxcurrent = evse_max_current;
|
||||
}
|
||||
|
||||
void set_iso_stopped(bool iso_stopped) {
|
||||
sim_data.iso_stopped = iso_stopped;
|
||||
}
|
||||
|
||||
void set_iso_charger_paused(bool iso_charger_paused) {
|
||||
sim_data.iso_charger_paused = iso_charger_paused;
|
||||
}
|
||||
|
||||
void set_v2g_finished(bool v2g_finished) {
|
||||
sim_data.v2g_finished = v2g_finished;
|
||||
}
|
||||
|
||||
void set_dc_power_on(bool dc_power_on) {
|
||||
sim_data.dc_power_on = dc_power_on;
|
||||
}
|
||||
|
||||
void state_machine();
|
||||
bool sleep(const CmdArguments&, size_t);
|
||||
bool iec_wait_pwr_ready(const CmdArguments&);
|
||||
bool iso_wait_pwm_is_running(const CmdArguments&);
|
||||
bool draw_power_regulated(const CmdArguments&);
|
||||
bool draw_power_fixed(const CmdArguments&);
|
||||
bool pause(const CmdArguments&);
|
||||
bool unplug(const CmdArguments&);
|
||||
bool error_e(const CmdArguments&);
|
||||
bool diode_fail(const CmdArguments&);
|
||||
bool rcd_current(const CmdArguments&);
|
||||
bool iso_wait_slac_matched(const CmdArguments&);
|
||||
bool iso_wait_pwr_ready(const CmdArguments&);
|
||||
bool iso_dc_power_on(const CmdArguments&);
|
||||
bool iso_start_v2g_session(const CmdArguments&, bool);
|
||||
bool iso_draw_power_regulated(const CmdArguments&);
|
||||
bool iso_stop_charging(const CmdArguments&);
|
||||
bool iso_wait_for_stop(const CmdArguments&, size_t);
|
||||
bool iso_wait_v2g_session_stopped(const CmdArguments&);
|
||||
bool iso_pause_charging(const CmdArguments&);
|
||||
bool iso_wait_for_resume(const CmdArguments&);
|
||||
bool iso_start_bcb_toggle(const CmdArguments&);
|
||||
bool wait_for_real_plugin(const CmdArguments&);
|
||||
|
||||
private:
|
||||
SimulationData sim_data;
|
||||
const module::Conf& config;
|
||||
std::chrono::time_point<std::chrono::steady_clock> timepoint_last_update;
|
||||
double charge_current_a{0};
|
||||
|
||||
double latest_soc{0};
|
||||
|
||||
enum class ChargeMode {
|
||||
None,
|
||||
AC,
|
||||
ACThreePhase,
|
||||
DC,
|
||||
} charge_mode{ChargeMode::None};
|
||||
|
||||
const std::unique_ptr<ev_board_supportIntf>& r_ev_board_support;
|
||||
const std::vector<std::unique_ptr<ISO15118_evIntf>>& r_ev;
|
||||
const std::vector<std::unique_ptr<ev_slacIntf>>& r_slac;
|
||||
const std::unique_ptr<ev_managerImplBase>& p_ev_manager;
|
||||
|
||||
void simulate_soc();
|
||||
};
|
||||
@@ -0,0 +1,439 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "car_simulatorImpl.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "simulation_command.hpp"
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace module::main {
|
||||
|
||||
void car_simulatorImpl::init() {
|
||||
simulated_already_plugged_in_key = fmt::format("{}_{}_simulated_already_plugged_in", mod->info.name, mod->info.id);
|
||||
simulated_plugged_in_command_key = fmt::format("{}_{}_simulated_plugged_in_command", mod->info.name, mod->info.id);
|
||||
last_commands = {};
|
||||
loop_interval_ms = constants::DEFAULT_LOOP_INTERVAL_MS;
|
||||
register_all_commands();
|
||||
subscribe_to_variables_on_init();
|
||||
|
||||
car_simulation = std::make_unique<CarSimulation>(mod->r_ev_board_support, mod->r_ev, mod->r_slac, mod->p_ev_manager,
|
||||
mod->config);
|
||||
|
||||
std::thread(&car_simulatorImpl::run, this).detach();
|
||||
}
|
||||
|
||||
void car_simulatorImpl::ready() {
|
||||
subscribe_to_external_mqtt();
|
||||
|
||||
setup_ev_parameters();
|
||||
|
||||
if (mod->config.keep_cross_boot_plugin_state) {
|
||||
if (!mod->r_kvs.empty()) {
|
||||
auto plugin_command_variant = mod->r_kvs.at(0)->call_load(simulated_plugged_in_command_key);
|
||||
auto* old_plugin_command = std::get_if<std::string>(&plugin_command_variant);
|
||||
// If the old plugin command was not stored use this one as a default backup which will require an unplug
|
||||
std::string plugin_command{"iec_wait_pwr_ready;sleep 60"};
|
||||
if (old_plugin_command != nullptr) {
|
||||
plugin_command = *old_plugin_command;
|
||||
}
|
||||
|
||||
auto already_plugged_in_variant = mod->r_kvs.at(0)->call_load(simulated_already_plugged_in_key);
|
||||
bool* was_plugged_in = std::get_if<bool>(&already_plugged_in_variant);
|
||||
if (was_plugged_in != nullptr && *was_plugged_in) {
|
||||
enabled = true;
|
||||
handle_modify_charging_session(plugin_command);
|
||||
plugged_in = true;
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mod->config.auto_enable) {
|
||||
auto enable_copy = mod->config.auto_enable;
|
||||
handle_enable(enable_copy);
|
||||
}
|
||||
if (mod->config.auto_exec) {
|
||||
auto value_copy = mod->config.auto_exec_commands;
|
||||
handle_execute_charging_session(value_copy);
|
||||
}
|
||||
}
|
||||
|
||||
void car_simulatorImpl::handle_enable(bool& value) {
|
||||
if (enabled == value) {
|
||||
// ignore if value is the same
|
||||
EVLOG_warning << "Enabled value didn't change, ignoring enable!";
|
||||
return;
|
||||
}
|
||||
|
||||
reset_car_simulation_defaults();
|
||||
|
||||
call_ev_board_support_functions();
|
||||
|
||||
enabled = value;
|
||||
|
||||
mod->r_ev_board_support->call_enable(value);
|
||||
publish_enabled(value);
|
||||
}
|
||||
|
||||
void car_simulatorImpl::handle_execute_charging_session(std::string& value) {
|
||||
if (!check_can_execute()) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_execution_active(false);
|
||||
reset_car_simulation_defaults();
|
||||
|
||||
update_command_queue(value);
|
||||
|
||||
const std::lock_guard<std::mutex> lock{car_simulation_mutex};
|
||||
if (!command_queue.empty()) {
|
||||
set_execution_active(true);
|
||||
}
|
||||
}
|
||||
|
||||
void car_simulatorImpl::handle_modify_charging_session(std::string& value) {
|
||||
if (!enabled) {
|
||||
EVLOG_warning << "Simulation disabled, cannot execute charging simulation.";
|
||||
return;
|
||||
}
|
||||
|
||||
set_execution_active(false);
|
||||
|
||||
update_command_queue(value);
|
||||
|
||||
const std::lock_guard<std::mutex> lock{car_simulation_mutex};
|
||||
if (!command_queue.empty()) {
|
||||
set_execution_active(true);
|
||||
}
|
||||
}
|
||||
|
||||
void car_simulatorImpl::run() {
|
||||
while (true) {
|
||||
if (enabled && execution_active) {
|
||||
|
||||
bool finished = run_simulation_loop();
|
||||
|
||||
if (cancel_charging_session_flag) {
|
||||
cancel_charging_session_flag = false;
|
||||
finished = true;
|
||||
}
|
||||
|
||||
auto& modify_session_cmds = car_simulation->get_modify_charging_session_cmds();
|
||||
if (modify_session_cmds.has_value()) {
|
||||
auto cmds = modify_session_cmds.value();
|
||||
handle_modify_charging_session(cmds);
|
||||
modify_session_cmds.reset();
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
EVLOG_info << "Finished simulation.";
|
||||
set_execution_active(false);
|
||||
|
||||
reset_car_simulation_defaults();
|
||||
|
||||
// If we have auto_exec_infinite configured, restart simulation when it is done
|
||||
if (mod->config.auto_exec && mod->config.auto_exec_infinite) {
|
||||
auto value_copy = mod->config.auto_exec_commands;
|
||||
handle_execute_charging_session(value_copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(loop_interval_ms));
|
||||
}
|
||||
}
|
||||
|
||||
void car_simulatorImpl::register_all_commands() {
|
||||
command_registry = std::make_unique<CommandRegistry>();
|
||||
|
||||
command_registry->register_command("sleep", 1, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->sleep(arguments, loop_interval_ms);
|
||||
});
|
||||
command_registry->register_command("iec_wait_pwr_ready", 0, [this](const CmdArguments& arguments) {
|
||||
if (!mod->r_kvs.empty() and not plugged_in) {
|
||||
plugged_in = true;
|
||||
mod->r_kvs.at(0)->call_store(simulated_already_plugged_in_key, true);
|
||||
mod->r_kvs.at(0)->call_store(simulated_plugged_in_command_key, last_commands);
|
||||
}
|
||||
return this->car_simulation->iec_wait_pwr_ready(arguments);
|
||||
});
|
||||
command_registry->register_command("iso_wait_pwm_is_running", 0, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_wait_pwm_is_running(arguments);
|
||||
});
|
||||
command_registry->register_command("draw_power_regulated", 2, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->draw_power_regulated(arguments);
|
||||
});
|
||||
command_registry->register_command("draw_power_fixed", 2, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->draw_power_fixed(arguments);
|
||||
});
|
||||
command_registry->register_command(
|
||||
"pause", 0, [this](const CmdArguments& arguments) { return this->car_simulation->pause(arguments); });
|
||||
command_registry->register_command("unplug", 0, [this](const CmdArguments& arguments) {
|
||||
if (!mod->r_kvs.empty() and plugged_in) {
|
||||
plugged_in = false;
|
||||
mod->r_kvs.at(0)->call_store(simulated_already_plugged_in_key, false);
|
||||
}
|
||||
return this->car_simulation->unplug(arguments);
|
||||
});
|
||||
command_registry->register_command(
|
||||
"error_e", 0, [this](const CmdArguments& arguments) { return this->car_simulation->error_e(arguments); });
|
||||
command_registry->register_command(
|
||||
"diode_fail", 0, [this](const CmdArguments& arguments) { return this->car_simulation->diode_fail(arguments); });
|
||||
command_registry->register_command("rcd_current", 1, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->rcd_current(arguments);
|
||||
});
|
||||
command_registry->register_command("iso_draw_power_regulated", 2, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_draw_power_regulated(arguments);
|
||||
});
|
||||
command_registry->register_command("wait_for_real_plugin", 0, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->wait_for_real_plugin(arguments);
|
||||
});
|
||||
command_registry->register_command("plugin", 0, [this](const CmdArguments& /*arguments*/) {
|
||||
if (not mod->config.plugin_commands.empty()) {
|
||||
car_simulation->update_modify_charging_session_cmds(mod->config.plugin_commands);
|
||||
return false;
|
||||
}
|
||||
EVLOG_error << "plugin command called but \"plugin_commands\" config key not set";
|
||||
return true;
|
||||
});
|
||||
command_registry->register_command("set_soc", 1, [this](const CmdArguments& arguments) {
|
||||
try {
|
||||
double soc = std::stod(arguments.at(0));
|
||||
this->car_simulation->set_soc(soc);
|
||||
} catch (const std::invalid_argument& e) {
|
||||
EVLOG_error << "set_soc command called with invalid argument: " << arguments.at(0);
|
||||
return true;
|
||||
} catch (const std::out_of_range& e) {
|
||||
EVLOG_error << "set_soc command called with out of range argument: " << arguments.at(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!mod->r_slac.empty()) {
|
||||
command_registry->register_command("iso_wait_slac_matched", 0, [this](const CmdArguments& arguments) {
|
||||
if (!mod->r_kvs.empty() and not plugged_in) {
|
||||
plugged_in = true;
|
||||
mod->r_kvs.at(0)->call_store(simulated_already_plugged_in_key, true);
|
||||
mod->r_kvs.at(0)->call_store(simulated_plugged_in_command_key, last_commands);
|
||||
}
|
||||
return this->car_simulation->iso_wait_slac_matched(arguments);
|
||||
});
|
||||
}
|
||||
|
||||
if (!mod->r_ev.empty()) {
|
||||
command_registry->register_command("iso_wait_pwr_ready", 0, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_wait_pwr_ready(arguments);
|
||||
});
|
||||
command_registry->register_command("iso_dc_power_on", 0, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_dc_power_on(arguments);
|
||||
});
|
||||
command_registry->register_command("iso_start_v2g_session", 1, [this](const CmdArguments& arguments) {
|
||||
constexpr auto payment_option = "auto";
|
||||
EVLOG_debug << "Using default payment_options, departure time and eamount";
|
||||
CmdArguments args{arguments[0], payment_option, std::to_string(mod->config.departure_time),
|
||||
std::to_string(mod->config.e_amount)};
|
||||
return this->car_simulation->iso_start_v2g_session(args, mod->config.three_phases);
|
||||
});
|
||||
command_registry->register_command("iso_start_v2g_session", 2, [this](const CmdArguments& arguments) {
|
||||
EVLOG_debug << "Using default departure time and eamount";
|
||||
CmdArguments args{arguments[0], arguments[1], std::to_string(mod->config.departure_time),
|
||||
std::to_string(mod->config.e_amount)};
|
||||
return this->car_simulation->iso_start_v2g_session(args, mod->config.three_phases);
|
||||
});
|
||||
command_registry->register_command("iso_start_v2g_session", 3, [this](const CmdArguments& arguments) {
|
||||
constexpr auto payment_option = "auto";
|
||||
EVLOG_debug << "Using default payment_options";
|
||||
CmdArguments args{arguments[0], payment_option, arguments[1], arguments[2]};
|
||||
return this->car_simulation->iso_start_v2g_session(args, mod->config.three_phases);
|
||||
});
|
||||
command_registry->register_command("iso_start_v2g_session", 4, [this](const CmdArguments& arguments) {
|
||||
// 1. Argument: EnergyMode
|
||||
// 2. Argument: PaymentOption
|
||||
// 3. Argument: DepartureTime
|
||||
// 4. Argument: EAmount
|
||||
return this->car_simulation->iso_start_v2g_session(arguments, mod->config.three_phases);
|
||||
});
|
||||
command_registry->register_command("iso_stop_charging", 0, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_stop_charging(arguments);
|
||||
});
|
||||
command_registry->register_command("iso_wait_for_stop", 1, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_wait_for_stop(arguments, loop_interval_ms);
|
||||
});
|
||||
command_registry->register_command("iso_wait_v2g_session_stopped", 0, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_wait_v2g_session_stopped(arguments);
|
||||
});
|
||||
command_registry->register_command("iso_pause_charging", 0, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_pause_charging(arguments);
|
||||
});
|
||||
command_registry->register_command("iso_wait_for_resume", 0, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_wait_for_resume(arguments);
|
||||
});
|
||||
command_registry->register_command("iso_start_bcb_toggle", 1, [this](const CmdArguments& arguments) {
|
||||
return this->car_simulation->iso_start_bcb_toggle(arguments);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool car_simulatorImpl::run_simulation_loop() {
|
||||
// Execute sim commands until a command blocks, or we are finished
|
||||
const std::lock_guard<std::mutex> lock{car_simulation_mutex};
|
||||
while (execution_active && !command_queue.empty()) {
|
||||
auto& current_command = command_queue.front();
|
||||
|
||||
auto command_blocked = false;
|
||||
|
||||
try {
|
||||
command_blocked = !current_command.execute();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << e.what();
|
||||
}
|
||||
|
||||
if (!command_blocked) {
|
||||
command_queue.pop();
|
||||
} else {
|
||||
break; // command blocked, wait for timer to run this function again
|
||||
}
|
||||
}
|
||||
|
||||
car_simulation->state_machine();
|
||||
|
||||
return command_queue.empty();
|
||||
}
|
||||
|
||||
bool car_simulatorImpl::check_can_execute() {
|
||||
if (!enabled) {
|
||||
EVLOG_warning << "Simulation disabled, cannot execute charging simulation.";
|
||||
return false;
|
||||
}
|
||||
if (execution_active) {
|
||||
EVLOG_warning << "Execution of charging session simulation already running, cannot start new one.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void car_simulatorImpl::subscribe_to_variables_on_init() {
|
||||
// subscribe bsp_event
|
||||
const std::lock_guard<std::mutex> lock{car_simulation_mutex};
|
||||
using types::board_support_common::BspEvent;
|
||||
mod->r_ev_board_support->subscribe_bsp_event([this](const auto& bsp_event) {
|
||||
car_simulation->set_bsp_event(bsp_event.event);
|
||||
if (bsp_event.event == types::board_support_common::Event::Disconnected &&
|
||||
car_simulation->get_state() != SimState::UNPLUGGED) {
|
||||
cancel_charging_session();
|
||||
}
|
||||
mod->p_ev_manager->publish_bsp_event(bsp_event);
|
||||
});
|
||||
|
||||
// subscribe bsp_measurement
|
||||
using types::board_support_common::BspMeasurement;
|
||||
mod->r_ev_board_support->subscribe_bsp_measurement([this](const auto& measurement) {
|
||||
car_simulation->set_pp(measurement.proximity_pilot.ampacity);
|
||||
car_simulation->set_pwm_duty_cycle(measurement.cp_pwm_duty_cycle);
|
||||
if (measurement.rcd_current_mA.has_value()) {
|
||||
car_simulation->set_rcd_current(measurement.rcd_current_mA.value());
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
slac->subscribe_state([this](const auto& state) { car_simulation->set_slac_state(state); });
|
||||
}
|
||||
|
||||
// subscribe ev events
|
||||
if (!mod->r_ev.empty()) {
|
||||
const auto& _ev = mod->r_ev.at(0);
|
||||
_ev->subscribe_ev_power_ready([this](auto value) { car_simulation->set_iso_pwr_ready(value); });
|
||||
_ev->subscribe_ac_evse_max_current(
|
||||
[this](auto value) { mod->r_ev_board_support->call_set_ac_max_current(value); });
|
||||
_ev->subscribe_ac_evse_target_power([this](const types::iso15118::AcTargetPower& value) {
|
||||
if (value.target_active_power.has_value()) {
|
||||
auto phase_count = mod->config.three_phases ? 3 : 1;
|
||||
if (value.target_active_power_L2.has_value() and mod->config.three_phases) {
|
||||
phase_count--;
|
||||
}
|
||||
if (value.target_active_power_L3.has_value() and mod->config.three_phases) {
|
||||
phase_count--;
|
||||
}
|
||||
|
||||
const auto current = value.target_active_power.value() / (mod->config.ac_nominal_voltage * phase_count);
|
||||
mod->r_ev_board_support->call_set_ac_max_current(current);
|
||||
}
|
||||
// TODO(SL): Adding missing reactive power
|
||||
});
|
||||
_ev->subscribe_stop_from_charger([this]() { car_simulation->set_iso_stopped(true); });
|
||||
_ev->subscribe_v2g_session_finished([this]() { car_simulation->set_v2g_finished(true); });
|
||||
_ev->subscribe_dc_power_on([this]() { car_simulation->set_dc_power_on(true); });
|
||||
_ev->subscribe_pause_from_charger([this]() { car_simulation->set_iso_charger_paused(true); });
|
||||
}
|
||||
}
|
||||
|
||||
void car_simulatorImpl::setup_ev_parameters() {
|
||||
if (!mod->r_ev.empty()) {
|
||||
mod->r_ev[0]->call_set_dc_params({mod->config.dc_max_current_limit, mod->config.dc_max_power_limit,
|
||||
mod->config.dc_max_voltage_limit, mod->config.dc_energy_capacity,
|
||||
mod->config.dc_target_current, 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(
|
||||
{mod->config.dc_discharge_max_current_limit, mod->config.dc_discharge_max_power_limit,
|
||||
mod->config.dc_discharge_target_current, mod->config.dc_discharge_v2g_minimal_soc});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void car_simulatorImpl::call_ev_board_support_functions() {
|
||||
mod->r_ev_board_support->call_allow_power_on(false);
|
||||
|
||||
mod->r_ev_board_support->call_set_ac_max_current(mod->config.max_current);
|
||||
mod->r_ev_board_support->call_set_three_phases(mod->config.three_phases);
|
||||
}
|
||||
|
||||
void car_simulatorImpl::subscribe_to_external_mqtt() {
|
||||
const auto& mqtt = mod->mqtt;
|
||||
mqtt.subscribe("everest_external/nodered/" + std::to_string(mod->config.connector_id) + "/carsim/cmd/enable",
|
||||
[this](const std::string& message) {
|
||||
auto enable = (message == "true") ? true : false;
|
||||
handle_enable(enable);
|
||||
});
|
||||
mqtt.subscribe("everest_external/nodered/" + std::to_string(mod->config.connector_id) +
|
||||
"/carsim/cmd/execute_charging_session",
|
||||
[this](const auto data) {
|
||||
auto data_copy{data};
|
||||
handle_execute_charging_session(data_copy);
|
||||
});
|
||||
mqtt.subscribe("everest_external/nodered/" + std::to_string(mod->config.connector_id) +
|
||||
"/carsim/cmd/modify_charging_session",
|
||||
[this](auto data) {
|
||||
auto data_copy = data;
|
||||
handle_modify_charging_session(data_copy);
|
||||
});
|
||||
}
|
||||
|
||||
void car_simulatorImpl::reset_car_simulation_defaults() {
|
||||
const std::lock_guard<std::mutex> lock{car_simulation_mutex};
|
||||
car_simulation->reset();
|
||||
}
|
||||
|
||||
void car_simulatorImpl::update_command_queue(std::string& value) {
|
||||
const std::lock_guard<std::mutex> lock{car_simulation_mutex};
|
||||
last_commands = value;
|
||||
command_queue = SimulationCommand::parse_sim_commands(value, *command_registry);
|
||||
}
|
||||
|
||||
void car_simulatorImpl::set_execution_active(bool value) {
|
||||
execution_active = value;
|
||||
}
|
||||
|
||||
void car_simulatorImpl::cancel_charging_session() {
|
||||
cancel_charging_session_flag = true;
|
||||
}
|
||||
|
||||
} // namespace module::main
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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 <generated/interfaces/car_simulator/Implementation.hpp>
|
||||
|
||||
#include "../EvManager.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
#include "car_simulation.hpp"
|
||||
#include "command_registry.hpp"
|
||||
#include <queue>
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class car_simulatorImpl : public car_simulatorImplBase {
|
||||
public:
|
||||
car_simulatorImpl() = delete;
|
||||
car_simulatorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<EvManager>& 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:
|
||||
// command handler functions (virtual)
|
||||
virtual void handle_enable(bool& value) override;
|
||||
virtual void handle_execute_charging_session(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<EvManager>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
|
||||
void run();
|
||||
void handle_modify_charging_session(std::string& value);
|
||||
bool check_can_execute();
|
||||
bool run_simulation_loop();
|
||||
void register_all_commands();
|
||||
void subscribe_to_variables_on_init();
|
||||
void setup_ev_parameters();
|
||||
void call_ev_board_support_functions();
|
||||
void subscribe_to_external_mqtt();
|
||||
void reset_car_simulation_defaults();
|
||||
void update_command_queue(std::string& value);
|
||||
void set_execution_active(bool value);
|
||||
void cancel_charging_session();
|
||||
|
||||
std::unique_ptr<CommandRegistry> command_registry;
|
||||
|
||||
std::mutex car_simulation_mutex;
|
||||
std::unique_ptr<CarSimulation> car_simulation;
|
||||
|
||||
bool enabled{false};
|
||||
std::atomic<bool> execution_active{false};
|
||||
std::atomic<bool> cancel_charging_session_flag{false};
|
||||
std::atomic<bool> plugged_in{false};
|
||||
size_t loop_interval_ms{};
|
||||
|
||||
std::queue<SimulationCommand> command_queue;
|
||||
|
||||
std::string simulated_already_plugged_in_key;
|
||||
std::string simulated_plugged_in_command_key;
|
||||
std::string last_commands;
|
||||
// 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_CAR_SIMULATOR_IMPL_HPP
|
||||
@@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class RegisteredCommandBase {
|
||||
public:
|
||||
virtual ~RegisteredCommandBase() = default;
|
||||
virtual bool operator()(const std::vector<std::string>& /*arguments*/) const = 0;
|
||||
[[nodiscard]] virtual std::size_t get_argument_count() const = 0;
|
||||
};
|
||||
|
||||
class RegisteredCommand : public RegisteredCommandBase {
|
||||
public:
|
||||
RegisteredCommand(std::string command_, std::size_t argument_count_,
|
||||
std::function<bool(std::vector<std::string>)> function_) :
|
||||
command_name{std::move(command_)}, argument_count(argument_count_), function{std::move(function_)} {
|
||||
}
|
||||
|
||||
~RegisteredCommand() override = default;
|
||||
|
||||
bool operator()(const std::vector<std::string>& arguments) const override {
|
||||
if (arguments.size() != argument_count) {
|
||||
throw std::invalid_argument{"Invalid number of arguments for: " + command_name + " expected " +
|
||||
std::to_string(argument_count) + " got " + std::to_string(arguments.size())};
|
||||
}
|
||||
return function(arguments);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t get_argument_count() const override {
|
||||
return argument_count;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string command_name;
|
||||
std::size_t argument_count;
|
||||
std::function<bool(std::vector<std::string>)> function;
|
||||
};
|
||||
|
||||
class CommandRegistry {
|
||||
public:
|
||||
CommandRegistry() = default;
|
||||
|
||||
void register_command(std::string command_name, size_t argument_count,
|
||||
const std::function<bool(std::vector<std::string>)>& function) {
|
||||
registered_commands.emplace(command_name,
|
||||
std::make_unique<RegisteredCommand>(command_name, argument_count, function));
|
||||
}
|
||||
|
||||
const RegisteredCommandBase& get_registered_command(const std::string& command_name,
|
||||
std::size_t arguments_count) const {
|
||||
|
||||
const auto cmd_count = registered_commands.count(command_name);
|
||||
|
||||
if (cmd_count == 1) {
|
||||
if (auto iter = registered_commands.find(command_name); iter != registered_commands.end()) {
|
||||
return *iter->second;
|
||||
}
|
||||
} else if (cmd_count >= 2) {
|
||||
auto range = registered_commands.equal_range(command_name);
|
||||
for (auto iter = range.first; iter != range.second; ++iter) {
|
||||
if (iter->second->get_argument_count() == arguments_count) {
|
||||
return *iter->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw std::invalid_argument{"Command not found: " + command_name};
|
||||
}
|
||||
|
||||
private:
|
||||
using RegisteredCommands = std::unordered_multimap<std::string, std::unique_ptr<RegisteredCommandBase>>;
|
||||
|
||||
RegisteredCommands registered_commands;
|
||||
};
|
||||
10
tools/EVerest-main/modules/EV/EvManager/main/constants.hpp
Normal file
10
tools/EVerest-main/modules/EV/EvManager/main/constants.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
namespace constants {
|
||||
static constexpr auto THREE_PHASES{"3"};
|
||||
static constexpr auto DEFAULT_LOOP_INTERVAL_MS{250};
|
||||
static constexpr auto AC{"ac"};
|
||||
static constexpr auto DC{"dc"};
|
||||
} // namespace constants
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "simulation_command.hpp"
|
||||
#include "command_registry.hpp"
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
SimulationCommand::SimulationCommand(const RegisteredCommandBase* registered_command_in,
|
||||
const CmdArguments& arguments_in) :
|
||||
arguments{arguments_in}, registered_command{registered_command_in} {
|
||||
}
|
||||
|
||||
bool SimulationCommand::execute() const {
|
||||
return (*registered_command)(arguments);
|
||||
}
|
||||
|
||||
std::queue<SimulationCommand> SimulationCommand::parse_sim_commands(const std::string& value,
|
||||
const CommandRegistry& command_registry) {
|
||||
auto commands_vector{convert_commands_string_to_vector(value)};
|
||||
|
||||
auto commands_with_arguments{split_into_commands_with_arguments(commands_vector)};
|
||||
|
||||
return compile_commands(commands_with_arguments, command_registry);
|
||||
}
|
||||
|
||||
SimulationCommand::RawCommands SimulationCommand::convert_commands_string_to_vector(const std::string& commands_view) {
|
||||
|
||||
auto commands = std::string{commands_view};
|
||||
|
||||
// convert to lower case inplace
|
||||
std::transform(commands.begin(), commands.end(), commands.begin(),
|
||||
[](const auto& character) { return std::tolower(character); });
|
||||
|
||||
// replace newlines with semicolons
|
||||
std::replace(commands.begin(), commands.end(), '\n', ';');
|
||||
|
||||
// split by semicolons
|
||||
std::stringstream commands_stream{commands};
|
||||
auto command = std::string{};
|
||||
auto commands_vector = std::vector<std::string>{};
|
||||
|
||||
while (std::getline(commands_stream, command, ';')) {
|
||||
commands_vector.push_back(command);
|
||||
}
|
||||
return commands_vector;
|
||||
}
|
||||
SimulationCommand::CommandsWithArguments
|
||||
SimulationCommand::split_into_commands_with_arguments(std::vector<std::string>& commands_vector) {
|
||||
auto commands_with_arguments = std::vector<std::pair<std::string, std::vector<std::string>>>{};
|
||||
|
||||
for (auto& command : commands_vector) {
|
||||
commands_with_arguments.push_back(split_into_command_with_arguments(command));
|
||||
}
|
||||
return commands_with_arguments;
|
||||
}
|
||||
|
||||
SimulationCommand::CommandWithArguments SimulationCommand::split_into_command_with_arguments(std::string& command) {
|
||||
// replace commas with spaces
|
||||
std::replace(command.begin(), command.end(), ',', ' ');
|
||||
|
||||
// get command name and arguments
|
||||
auto command_stream = std::stringstream{command};
|
||||
auto command_name = std::string{};
|
||||
auto argument = std::string{};
|
||||
auto arguments = std::vector<std::string>{};
|
||||
|
||||
// get command name
|
||||
std::getline(command_stream, command_name, ' ');
|
||||
|
||||
// get arguments
|
||||
while (std::getline(command_stream, argument, ' ')) {
|
||||
arguments.push_back(argument);
|
||||
}
|
||||
|
||||
return {command_name, arguments};
|
||||
}
|
||||
std::queue<SimulationCommand> SimulationCommand::compile_commands(CommandsWithArguments& commands_with_arguments,
|
||||
const CommandRegistry& command_registry) {
|
||||
auto compiled_commands = std::queue<SimulationCommand>{};
|
||||
|
||||
for (auto& [command, arguments] : commands_with_arguments) {
|
||||
compiled_commands.emplace(&command_registry.get_registered_command(command, arguments.size()), arguments);
|
||||
}
|
||||
|
||||
return compiled_commands;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "command_registry.hpp"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using CmdArguments = std::vector<std::string>;
|
||||
|
||||
class SimulationCommand {
|
||||
public:
|
||||
SimulationCommand(const RegisteredCommandBase* registered_command_in, const CmdArguments& arguments_in);
|
||||
|
||||
bool execute() const;
|
||||
|
||||
static std::queue<SimulationCommand> parse_sim_commands(const std::string& value,
|
||||
const CommandRegistry& command_registry);
|
||||
|
||||
private:
|
||||
using RawCommands = std::vector<std::string>;
|
||||
using CommandWithArguments = std::pair<std::string, CmdArguments>;
|
||||
using CommandsWithArguments = std::vector<CommandWithArguments>;
|
||||
|
||||
static RawCommands convert_commands_string_to_vector(const std::string& commands_view);
|
||||
static CommandsWithArguments split_into_commands_with_arguments(std::vector<std::string>& commands_vector);
|
||||
static CommandWithArguments split_into_command_with_arguments(std::string& command);
|
||||
static std::queue<SimulationCommand> compile_commands(CommandsWithArguments& commands_with_arguments,
|
||||
const CommandRegistry& command_registry);
|
||||
std::vector<std::string> arguments;
|
||||
|
||||
const RegisteredCommandBase* registered_command;
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "command_registry.hpp"
|
||||
#include "simulation_command.hpp"
|
||||
#include <generated/types/board_support_common.hpp>
|
||||
#include <generated/types/slac.hpp>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
enum class SimState {
|
||||
UNPLUGGED,
|
||||
PLUGGED_IN,
|
||||
CHARGING_REGULATED,
|
||||
CHARGING_FIXED,
|
||||
ERROR_E,
|
||||
DIODE_FAIL,
|
||||
ISO_POWER_READY,
|
||||
ISO_CHARGING_REGULATED,
|
||||
BCB_TOGGLE,
|
||||
UNDEFINED,
|
||||
};
|
||||
|
||||
enum class EnergyMode {
|
||||
AC,
|
||||
DC,
|
||||
};
|
||||
|
||||
struct SimulationData {
|
||||
|
||||
SimulationData() = default;
|
||||
|
||||
SimState state{SimState::UNPLUGGED};
|
||||
SimState last_state{SimState::UNDEFINED};
|
||||
types::slac::State slac_state{types::slac::State::UNMATCHED};
|
||||
std::optional<size_t> sleep_ticks_left{};
|
||||
|
||||
bool v2g_finished{false};
|
||||
bool iso_stopped{false};
|
||||
bool iso_charger_paused{false};
|
||||
size_t evse_maxcurrent{0};
|
||||
size_t max_current{0};
|
||||
std::string payment{"ExternalPayment"};
|
||||
|
||||
EnergyMode energy_mode{EnergyMode::AC};
|
||||
std::optional<std::string> modify_charging_session_cmds{std::nullopt};
|
||||
|
||||
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};
|
||||
float last_pwm_duty_cycle{0.0f};
|
||||
|
||||
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};
|
||||
};
|
||||
Reference in New Issue
Block a user