- 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
472 lines
17 KiB
C++
472 lines
17 KiB
C++
// 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;
|
|
}
|