Files
Eric F d398a6ced2 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
2026-06-08 00:38:27 -04:00

761 lines
33 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "board_support/evse_board_supportImpl.hpp"
#include "util/state.hpp"
#include "util/util.hpp"
#include "util/errors.hpp"
namespace module {
namespace {
types::powermeter::Powermeter power_meter_external(const state::PowermeterData& powermeter_data) {
const auto current_iso_time_string = util::get_current_iso_time_string();
const auto& [time_stamp, import_totalWattHr, export_totalWattHr, wattL1, vrmsL1, irmsL1, import_wattHrL1,
export_wattHrL1, tempL1, freqL1, wattL2, vrmsL2, irmsL2, import_wattHrL2, export_wattHrL2, tempL2,
freqL2, wattL3, vrmsL3, irmsL3, import_wattHrL3, export_wattHrL3, tempL3, freqL3, irmsN] =
powermeter_data;
const std::vector<types::temperature::Temperature> temperatures = {
{static_cast<float>(powermeter_data.tempL1), std::nullopt, "Body"}};
return {current_iso_time_string, // timestamp
{
static_cast<float>(import_totalWattHr),
static_cast<float>(import_wattHrL1),
static_cast<float>(import_wattHrL2),
static_cast<float>(import_wattHrL3),
}, // energy_Wh_import
"YETI_POWERMETER", // meter_id
false, // phase_seq_error
std::make_optional<types::units::Energy>({
static_cast<float>(export_totalWattHr),
static_cast<float>(export_wattHrL1),
static_cast<float>(export_wattHrL2),
static_cast<float>(export_wattHrL3),
}), // energy_Wh_export
types::units::Power{
static_cast<float>(wattL1 + wattL2 + wattL3),
static_cast<float>(wattL1),
static_cast<float>(wattL2),
static_cast<float>(wattL3),
}, // power_W
types::units::Voltage{std::nullopt, vrmsL1, vrmsL2, vrmsL3}, // voltage_V
std::nullopt, // VAR
types::units::Current{std::nullopt, irmsL1, irmsL2, irmsL3, irmsN}, // current_A
types::units::Frequency{static_cast<float>(freqL1), static_cast<float>(freqL2),
static_cast<float>(freqL3)}, // frequency_Hz
std::nullopt, // energy_Wh_import_signed
std::nullopt, // energy_Wh_export_signed
std::nullopt, // power_W_signed
std::nullopt, // voltage_V_signed
std::nullopt, // VAR_signed
std::nullopt, // current_A_signed
std::nullopt, // frequency_Hz_signed
std::nullopt, // signed_meter_value
temperatures}; // temperatures
}
double duty_cycle_to_amps(const double dc) {
if (dc < 8.0 / 100.0)
return 0;
if (dc < 85.0 / 100.0)
return dc * 100.0 * 0.6;
if (dc < 96.0 / 100.0)
return (dc * 100.0 - 64) * 2.5;
if (dc < 97.0 / 100.0)
return 80;
return 0;
}
bool is_voltage_in_range(const double voltage, const double center) {
constexpr auto interval = 1.1;
return voltage > center - interval and voltage < center + interval;
}
std::string event_to_string(state::State state) {
using state::State;
switch (state) {
case State::STATE_A:
return "A";
case State::STATE_B:
return "B";
case State::STATE_C:
return "C";
case State::STATE_D:
return "D";
case State::STATE_E:
return "E";
case State::STATE_F:
return "F";
case State::STATE_DISABLED:
return "F";
case State::Event_PowerOn:
return "PowerOn";
case State::Event_PowerOff:
return "PowerOff";
default:
return "invalid";
}
}
types::board_support_common::BspEvent event_to_enum(state::State event) {
using state::State;
using types::board_support_common::Event;
switch (event) {
case State::STATE_A:
return {Event::A};
case State::STATE_B:
return {Event::B};
case State::STATE_C:
return {Event::C};
case State::STATE_D:
return {Event::D};
case State::STATE_E:
return {Event::E};
case State::STATE_F:
return {Event::F};
case State::STATE_DISABLED:
return {Event::F};
case State::Event_PowerOn:
return {Event::PowerOn};
case State::Event_PowerOff:
return {Event::PowerOff};
default:
EVLOG_error << "Invalid event : " << event_to_string(event);
return {Event::F};
}
}
} // namespace
void YetiSimulator::init() {
invoke_init(*p_powermeter);
invoke_init(*p_board_support);
invoke_init(*p_ev_board_support);
invoke_init(*p_rcd);
invoke_init(*p_connector_lock);
reset_module_state();
mqtt.subscribe(
"everest_external/nodered/" + std::to_string(config.connector_id) + "/carsim/error",
[this](const std::string& payload) {
const auto [raise, error_definition] = parse_error_type(payload);
if (not error_definition.has_value()) {
return;
}
if (error_definition->error_target == ErrorTarget::BoardSupport) {
const auto error =
p_board_support->error_factory->create_error(error_definition->type, error_definition->sub_type,
error_definition->message, error_definition->severity);
forward_error(p_board_support, error, raise);
} else if (error_definition->error_target == ErrorTarget::ConnectorLock) {
const auto error = p_connector_lock->error_factory->create_error(
error_definition->type, error_definition->sub_type, error_definition->message,
error_definition->severity);
} else if (error_definition->error_target == ErrorTarget::Rcd) {
const auto error =
p_rcd->error_factory->create_error(error_definition->type, error_definition->sub_type,
error_definition->message, error_definition->severity);
} else if (error_definition->error_target == ErrorTarget::Powermeter) {
const auto error =
p_powermeter->error_factory->create_error(error_definition->type, error_definition->sub_type,
error_definition->message, error_definition->severity);
forward_error(p_powermeter, error, raise);
} else {
EVLOG_error << "No known ErrorTarget";
}
});
}
void YetiSimulator::ready() {
invoke_ready(*p_powermeter);
invoke_ready(*p_board_support);
invoke_ready(*p_ev_board_support);
invoke_ready(*p_rcd);
invoke_ready(*p_connector_lock);
module_state->pubCnt = 0;
std::thread(&YetiSimulator::run_simulation, this, 250).detach();
if (info.telemetry_enabled) {
std::thread(&YetiSimulator::run_telemetry_slow, this).detach();
std::thread(&YetiSimulator::run_telemetry_fast, this).detach();
}
}
void YetiSimulator::run_telemetry_slow() const {
while (true) {
const auto current_iso_time_string = util::get_current_iso_time_string();
auto& p_p_c_v = module_state->telemetry_data.power_path_controller_version;
p_p_c_v.timestamp = current_iso_time_string;
telemetry.publish("livedata", "power_path_controller_version",
{{"timestamp", p_p_c_v.timestamp},
{"type", p_p_c_v.type},
{"hardware_version", p_p_c_v.hardware_version},
{"software_version", p_p_c_v.software_version},
{"date_manufactured", p_p_c_v.date_manufactured},
{"operating_time_h", p_p_c_v.operating_time_h},
{"operating_time_warning", p_p_c_v.operating_time_h_warning},
{"operating_time_error", p_p_c_v.operating_time_h_error},
{"error", p_p_c_v.error}});
std::this_thread::sleep_for(std::chrono::milliseconds{15000});
}
}
void YetiSimulator::run_telemetry_fast() const {
while (true) {
const auto current_iso_time_string = util::get_current_iso_time_string();
auto& p_p_c = module_state->telemetry_data.power_path_controller;
p_p_c.timestamp = current_iso_time_string;
p_p_c.cp_voltage_high = module_state->pwm_voltage_hi;
p_p_c.cp_voltage_low = module_state->pwm_voltage_lo;
p_p_c.cp_pwm_duty_cycle = module_state->pwm_duty_cycle * 100.0;
p_p_c.cp_state = state_to_string(*module_state);
p_p_c.temperature_controller = module_state->powermeter_data.tempL1;
p_p_c.temperature_car_connector = module_state->powermeter_data.tempL1 * 2.0;
p_p_c.watchdog_reset_count = 0;
p_p_c.error = false;
auto& p_s = module_state->telemetry_data.power_switch;
p_s.timestamp = current_iso_time_string;
p_s.is_on = module_state->relais_on;
p_s.time_to_switch_on_ms = 110;
p_s.time_to_switch_off_ms = 100;
p_s.temperature_C = 20;
p_s.error = false;
p_s.error_over_current = false;
auto& rcd = module_state->telemetry_data.rcd;
rcd.timestamp = current_iso_time_string;
rcd.current_mA = module_state->simulation_data.rcd_current;
telemetry.publish("livedata", "power_path_controller",
{{"timestamp", p_p_c.timestamp},
{"type", p_p_c.type},
{"cp_voltage_high", p_p_c.cp_voltage_high},
{"cp_voltage_low", p_p_c.cp_voltage_low},
{"cp_pwm_duty_cycle", p_p_c.cp_pwm_duty_cycle},
{"cp_state", p_p_c.cp_state},
{"pp_ohm", p_p_c.pp_ohm},
{"supply_voltage_12V", p_p_c.supply_voltage_12V},
{"supply_voltage_minus_12V", p_p_c.supply_voltage_minus_12V},
{"temperature_controller", p_p_c.temperature_controller},
{"temperature_car_connector", p_p_c.temperature_car_connector},
{"watchdog_reset_count", p_p_c.watchdog_reset_count},
{"error", p_p_c.error}});
telemetry.publish("livedata", "power_switch",
{{"timestamp", p_s.timestamp},
{"type", p_s.type},
{"switching_count", p_s.switching_count},
{"switching_count_warning", p_s.switching_count_warning},
{"switching_count_error", p_s.switching_count_error},
{"is_on", p_s.is_on},
{"time_to_switch_on_ms", p_s.time_to_switch_on_ms},
{"time_to_switch_off_ms", p_s.time_to_switch_off_ms},
{"temperature_C", p_s.temperature_C},
{"error", p_s.error},
{"error_over_current", p_s.error_over_current}});
telemetry.publish("livedata", "rcd",
{{"timestamp", rcd.timestamp},
{"type", rcd.type},
{"enabled", rcd.enabled},
{"current_mA", rcd.current_mA},
{"triggered", rcd.triggered},
{"error", rcd.error}});
std::this_thread::sleep_for(std::chrono::milliseconds{1000});
}
}
[[noreturn]] void YetiSimulator::run_simulation(const int sleep_time_ms) {
while (true) {
if (module_state->simulation_enabled) {
simulation_step();
}
module_state->pubCnt++;
switch (module_state->pubCnt) {
case 1:
publish_powermeter();
publish_telemetry();
break;
case 3:
publish_keepalive();
break;
default:
module_state->pubCnt = 0;
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds{sleep_time_ms});
}
}
void YetiSimulator::simulation_step() {
check_error_rcd();
read_from_car();
simulation_statemachine();
add_noise();
simulate_powermeter();
publish_ev_board_support();
}
void YetiSimulator::check_error_rcd() {
using Everest::error::Severity;
const auto rcd_error = error_definitions::ac_rcd_DC;
if (module_state->simulation_data.rcd_current > 5.0) {
if (not p_rcd->error_state_monitor->is_error_active(rcd_error.type, rcd_error.sub_type)) {
const auto error = p_rcd->error_factory->create_error(rcd_error.type, rcd_error.sub_type, rcd_error.message,
Severity::High);
p_rcd->raise_error(error);
}
} else {
if (p_rcd->error_state_monitor->is_error_active(rcd_error.type, rcd_error.sub_type)) {
p_rcd->clear_error(rcd_error.type, rcd_error.sub_type);
}
}
p_rcd->publish_rcd_current_mA(module_state->simulation_data.rcd_current);
}
void YetiSimulator::read_from_car() {
const auto diode_fault = error_definitions::evse_board_support_DiodeFault;
double amps{0.0};
// 5% dutycycle: Getting current from HLC
if (module_state->pwm_duty_cycle >= 0.03 and module_state->pwm_duty_cycle <= 0.07) {
amps = module_state->ev_max_current;
} else if (module_state->ev_max_current == 0.0) { // Basic charging: only pwm dutycycle
amps = duty_cycle_to_amps(module_state->pwm_duty_cycle);
} else { // Nominal dutycycle: Smallest amp between hlc and pwm dutycycle, ev_max_current can be negative
amps = std::min(duty_cycle_to_amps(module_state->pwm_duty_cycle), std::fabs(module_state->ev_max_current));
if (module_state->ev_max_current < 0) {
amps = -amps;
}
}
const auto amps1 = (module_state->relais_on and module_state->ev_phases > 0) ? amps : 0.0;
const auto amps2 =
(module_state->relais_on and module_state->ev_phases > 1 and module_state->use_three_phases_confirmed) ? amps
: 0.0;
const auto amps3 =
(module_state->relais_on and module_state->ev_phases > 2 and module_state->use_three_phases_confirmed) ? amps
: 0.0;
if (module_state->pwm_running) {
module_state->pwm_voltage_hi = module_state->simulation_data.cp_voltage;
module_state->pwm_voltage_lo = -12.0;
} else {
module_state->pwm_voltage_hi = module_state->simulation_data.cp_voltage;
module_state->pwm_voltage_lo = module_state->pwm_voltage_hi;
}
if (module_state->pwm_error_f) {
module_state->pwm_voltage_hi = -12.0;
module_state->pwm_voltage_lo = -12.0;
}
if (module_state->simulation_data.error_e) {
module_state->pwm_voltage_hi = 0.0;
module_state->pwm_voltage_lo = 0.0;
}
if (module_state->simulation_data.diode_fail) {
module_state->pwm_voltage_lo = -module_state->pwm_voltage_hi;
}
const auto cpLo = module_state->pwm_voltage_lo;
const auto cpHi = module_state->pwm_voltage_hi;
// sth is wrong with negative signal
if (module_state->pwm_running and not is_voltage_in_range(cpLo, -12.0)) {
// CP-PE short or signal somehow gone
if (is_voltage_in_range(cpLo, 0.0) and is_voltage_in_range(cpHi, 0.0)) {
module_state->current_state = state::State::STATE_E;
drawPower(0, 0, 0, 0);
} else if (is_voltage_in_range(cpHi + cpLo, 0.0)) { // Diode fault
const auto error = p_board_support->error_factory->create_error(diode_fault.type, diode_fault.sub_type,
diode_fault.message, diode_fault.severity);
forward_error(p_board_support, error, true);
drawPower(0, 0, 0, 0);
}
} else if (is_voltage_in_range(cpHi, 12.0)) {
// +12V State A IDLE (open circuit)
// clear all errors that clear on disconnection
if (p_board_support->error_state_monitor->is_error_active(diode_fault.type, diode_fault.sub_type)) {
p_board_support->clear_error(diode_fault.type);
}
module_state->current_state = state::State::STATE_A;
drawPower(0, 0, 0, 0);
} else if (is_voltage_in_range(cpHi, 9.0)) {
module_state->current_state = state::State::STATE_B;
drawPower(0, 0, 0, 0);
} else if (is_voltage_in_range(cpHi, 6.0)) {
module_state->current_state = state::State::STATE_C;
drawPower(amps1, amps2, amps3, 0.2);
} else if (is_voltage_in_range(cpHi, 3.0)) {
module_state->current_state = state::State::STATE_D;
drawPower(amps1, amps2, amps3, 0.2);
} else if (is_voltage_in_range(cpHi, -12.0)) {
module_state->current_state = state::State::STATE_F;
drawPower(0, 0, 0, 0);
}
}
void YetiSimulator::simulation_statemachine() {
if (module_state->republish_state or module_state->last_state not_eq module_state->current_state) {
publish_event(module_state->current_state);
module_state->republish_state = false;
}
switch (module_state->current_state) {
case state::State::STATE_DISABLED:
powerOff();
module_state->power_on_allowed = false;
break;
case state::State::STATE_A: {
module_state->use_three_phases_confirmed = module_state->use_three_phases;
cp_state_x1();
module_state->simplified_mode = false;
if (module_state->last_state not_eq state::State::STATE_A and
module_state->last_state not_eq state::State::STATE_DISABLED and
module_state->last_state not_eq state::State::STATE_F) {
powerOff();
// If car was unplugged, reset RCD flag.
module_state->simdata_setting.rcd_current = 0.1;
module_state->rcd_error = false;
}
break;
}
case state::State::STATE_B:
// Table A.6: Sequence 7 EV stops charging
// Table A.6: Sequence 8.2 EV supply equipment
// responds to EV opens S2 (w/o PWM)
if (module_state->last_state not_eq state::State::STATE_A and
module_state->last_state not_eq state::State::STATE_B) {
// Need to switch off according to Table A.6 Sequence 8.1 within
powerOff();
}
// Table A.6: Sequence 1.1 Plug-in
if (module_state->last_state == state::State::STATE_A) {
module_state->simplified_mode = false;
// Fix a race-condition between resetting powermeter parameters and reporting powermeter to the EvseManager
// back.
// The EvseManager reports in the transaction_finished event the total charged kWh.
// With resetting the powermeter too quickly, sometimes the EvseManager reports 0.00 kWh back.
reset_powermeter();
}
break;
case state::State::STATE_C:
// Table A.6: Sequence 1.2 Plug-in
if (module_state->last_state == state::State::STATE_A) {
module_state->simplified_mode = true;
}
if (not module_state->pwm_running) { // C1
// Table A.6 Sequence 10.2: EV does not stop drawing power even
// if PWM stops. Stop within 6 seconds (E.g. Kona1!)
// This is implemented in EvseManager
if (not module_state->power_on_allowed)
powerOff();
} else if (module_state->power_on_allowed) { // C2
// Table A.6: Sequence 4 EV ready to charge.
// Must enable power within 3 seconds.
powerOn();
}
break;
case state::State::STATE_D:
// Table A.6: Sequence 1.2 Plug-in (w/ventilation)
if (module_state->last_state == state::State::STATE_A) {
module_state->simplified_mode = true;
}
if (not module_state->pwm_running) {
// Table A.6 Sequence 10.2: EV does not stop drawing power
// even if PWM stops. Stop within 6 seconds (E.g. Kona1!)
/* if (mod.last_pwm_running) // FIMXE implement 6 second timer
startTimer(6000);
if (timerElapsed()) { */
// force power off under load
powerOff();
// }
} else if (module_state->power_on_allowed and not module_state->relais_on and module_state->has_ventilation) {
// Table A.6: Sequence 4 EV ready to charge.
// Must enable power within 3 seconds.
powerOn();
}
break;
case state::State::STATE_E: {
powerOff();
cp_state_x1();
break;
}
case state::State::STATE_F:
powerOff();
break;
default:
break;
}
module_state->last_state = module_state->current_state;
module_state->last_pwm_running = module_state->pwm_running;
}
void YetiSimulator::add_noise() {
const auto random_number_between_0_and_1 = [] {
return static_cast<double>(rand()) / static_cast<double>(RAND_MAX);
};
const auto noise = 1 + (random_number_between_0_and_1() - 0.5) * 0.02;
const auto lonoise = 1 + (random_number_between_0_and_1() - 0.5) * 0.005;
const auto impedance = module_state->simdata_setting.impedance / 1000.0;
module_state->simulation_data.currents.L1 = module_state->simdata_setting.currents.L1 * noise;
module_state->simulation_data.currents.L2 = module_state->simdata_setting.currents.L2 * noise;
module_state->simulation_data.currents.L3 = module_state->simdata_setting.currents.L3 * noise;
module_state->simulation_data.currents.N = module_state->simdata_setting.currents.N * noise;
module_state->simulation_data.voltages.L1 =
module_state->simdata_setting.voltages.L1 * noise - impedance * module_state->simulation_data.currents.L1;
module_state->simulation_data.voltages.L2 =
module_state->simdata_setting.voltages.L2 * noise - impedance * module_state->simulation_data.currents.L2;
module_state->simulation_data.voltages.L3 =
module_state->simdata_setting.voltages.L3 * noise - impedance * module_state->simulation_data.currents.L3;
module_state->simulation_data.frequencies.L1 = module_state->simdata_setting.frequencies.L1 * lonoise;
module_state->simulation_data.frequencies.L2 = module_state->simdata_setting.frequencies.L2 * lonoise;
module_state->simulation_data.frequencies.L3 = module_state->simdata_setting.frequencies.L3 * lonoise;
module_state->simulation_data.cp_voltage = module_state->simdata_setting.cp_voltage * noise;
module_state->simulation_data.rcd_current = module_state->simdata_setting.rcd_current * noise;
module_state->simulation_data.pp_resistor = module_state->simdata_setting.pp_resistor * noise;
module_state->simulation_data.diode_fail = module_state->simdata_setting.diode_fail;
module_state->simulation_data.error_e = module_state->simdata_setting.error_e;
}
void YetiSimulator::simulate_powermeter() {
using namespace std::chrono;
const auto time_stamp = time_point_cast<milliseconds>(system_clock::now()).time_since_epoch().count();
if (module_state->powermeter_sim_last_time_stamp == 0)
module_state->powermeter_sim_last_time_stamp = time_stamp;
const auto deltat = time_stamp - module_state->powermeter_sim_last_time_stamp;
module_state->powermeter_sim_last_time_stamp = time_stamp;
const auto wattL1 = module_state->simulation_data.voltages.L1 * module_state->simulation_data.currents.L1 *
(module_state->relais_on ? 1 : 0);
const auto wattL2 = module_state->simulation_data.voltages.L2 * module_state->simulation_data.currents.L2 *
(module_state->relais_on and module_state->use_three_phases_confirmed ? 1 : 0);
const auto wattL3 = module_state->simulation_data.voltages.L3 * module_state->simulation_data.currents.L3 *
(module_state->relais_on and module_state->use_three_phases_confirmed ? 1 : 0);
if (module_state->simulation_data.currents.L1 >= 0) {
module_state->import_watt_hr.L1 += wattL1 * deltat / 1000.0 / 3600.0;
module_state->import_watt_hr.L2 += wattL2 * deltat / 1000.0 / 3600.0;
module_state->import_watt_hr.L3 += wattL3 * deltat / 1000.0 / 3600.0;
} else {
module_state->export_watt_hr.L1 += wattL1 * deltat / 1000.0 / 3600.0;
module_state->export_watt_hr.L2 += wattL2 * deltat / 1000.0 / 3600.0;
module_state->export_watt_hr.L3 += wattL3 * deltat / 1000.0 / 3600.0;
}
module_state->powermeter_data.time_stamp = round(time_stamp / 1000);
module_state->powermeter_data.export_totalWattHr =
round(module_state->export_watt_hr.L1 + module_state->export_watt_hr.L2 + module_state->export_watt_hr.L3);
module_state->powermeter_data.import_totalWattHr =
round(module_state->import_watt_hr.L1 + module_state->import_watt_hr.L2 + module_state->import_watt_hr.L3);
module_state->powermeter_data.wattL1 = round(wattL1);
module_state->powermeter_data.vrmsL1 = module_state->simulation_data.voltages.L1;
module_state->powermeter_data.irmsL1 = module_state->simulation_data.currents.L1;
module_state->powermeter_data.import_wattHrL1 = round(module_state->import_watt_hr.L1);
module_state->powermeter_data.export_wattHrL1 = round(module_state->export_watt_hr.L1);
module_state->powermeter_data.tempL1 = 25.0 + fabs(wattL1 + wattL2 + wattL3) * 0.003;
module_state->powermeter_data.freqL1 = module_state->simulation_data.frequencies.L1;
module_state->powermeter_data.wattL2 = round(wattL2);
module_state->powermeter_data.vrmsL2 = module_state->simulation_data.voltages.L2;
module_state->powermeter_data.irmsL2 = module_state->simulation_data.currents.L2;
module_state->powermeter_data.import_wattHrL2 = round(module_state->import_watt_hr.L2);
module_state->powermeter_data.export_wattHrL2 = round(module_state->export_watt_hr.L2);
module_state->powermeter_data.tempL2 = 25.0 + fabs(wattL1 + wattL2 + wattL3) * 0.003;
module_state->powermeter_data.freqL2 = module_state->simulation_data.frequencies.L2;
module_state->powermeter_data.wattL3 = round(wattL3);
module_state->powermeter_data.vrmsL3 = module_state->simulation_data.voltages.L3;
module_state->powermeter_data.irmsL3 = module_state->simulation_data.currents.L3;
module_state->powermeter_data.import_wattHrL3 = round(module_state->import_watt_hr.L3);
module_state->powermeter_data.export_wattHrL3 = round(module_state->export_watt_hr.L3);
module_state->powermeter_data.tempL3 = 25.0 + fabs(wattL1 + wattL2 + wattL3) * 0.003;
module_state->powermeter_data.freqL3 = module_state->simulation_data.frequencies.L3;
module_state->powermeter_data.irmsN = module_state->simulation_data.currents.N;
}
void YetiSimulator::publish_ev_board_support() const {
const auto pp = read_pp_ampacity();
p_ev_board_support->publish_bsp_measurement(
{pp, static_cast<float>(module_state->pwm_duty_cycle * 100), module_state->simulation_data.rcd_current});
}
void YetiSimulator::publish_powermeter() {
p_powermeter->publish_powermeter(power_meter_external(module_state->powermeter_data));
// Deprecated external stuff
const auto totalKWattHr =
module_state->powermeter_data.irmsL1 >= 0
? (module_state->powermeter_data.import_wattHrL1 + module_state->powermeter_data.import_wattHrL2 +
module_state->powermeter_data.import_wattHrL3) /
1000.0
: (module_state->powermeter_data.export_wattHrL1 + module_state->powermeter_data.export_wattHrL2 +
module_state->powermeter_data.export_wattHrL3) /
1000.0;
mqtt.publish("/external/powermeter/vrmsL1", module_state->powermeter_data.vrmsL1);
mqtt.publish("/external/powermeter/phaseSeqError", false);
mqtt.publish("/external/powermeter/time_stamp", std::to_string(module_state->powermeter_data.time_stamp));
mqtt.publish("/external/powermeter/tempL1", module_state->powermeter_data.tempL1);
mqtt.publish("/external/powermeter/totalKw",
(module_state->powermeter_data.wattL1 + module_state->powermeter_data.wattL2 +
module_state->powermeter_data.wattL3) /
1000.0);
mqtt.publish("/external/powermeter/totalKWattHr", totalKWattHr);
mqtt.publish("/external/powermeter_json", json{module_state->powermeter_data}.dump());
mqtt.publish("/external/" + info.id + "/powermeter/tempL1", module_state->powermeter_data.tempL1);
mqtt.publish("/external/" + info.id + "/powermeter/totalKw",
(module_state->powermeter_data.wattL1 + module_state->powermeter_data.wattL2 +
module_state->powermeter_data.wattL3) /
1000.0);
mqtt.publish("/external/" + info.id + "/powermeter/totalKWattHr", totalKWattHr);
}
void YetiSimulator::publish_telemetry() {
p_board_support->publish_telemetry({static_cast<float>(module_state->powermeter_data.tempL1), // evse_temperature_C
1500., // fan_rpm
12.01, // supply_voltage_12V
-11.8, // supply_voltage_minus_12V
module_state->relais_on}); // relais_on
}
void YetiSimulator::publish_keepalive() {
using namespace std::chrono;
mqtt.publish(
"/external/keepalive_json",
json{
{"hw_revision", 0},
{"hw_type", 0},
{"protocol_version_major", 0},
{"protocol_version_minor", 1},
{"sw_version_string", "simulation"},
{"time_stamp", {time_point_cast<milliseconds>(system_clock::now()).time_since_epoch().count() / 1000}},
}
.dump());
}
void YetiSimulator::drawPower(const double l1, const double l2, const double l3, const double n) const {
module_state->simdata_setting.currents.L1 = l1;
module_state->simdata_setting.currents.L2 = l2;
module_state->simdata_setting.currents.L3 = l3;
module_state->simdata_setting.currents.N = n;
}
void YetiSimulator::powerOn() {
if (not module_state->relais_on) {
publish_event(state::State::Event_PowerOn);
module_state->relais_on = true;
module_state->telemetry_data.power_switch.switching_count += 1;
}
}
void YetiSimulator::powerOff() {
if (module_state->relais_on) {
publish_event(state::State::Event_PowerOff);
module_state->telemetry_data.power_switch.switching_count++;
module_state->relais_on = false;
}
}
void YetiSimulator::publish_event(state::State event) {
p_board_support->publish_event(event_to_enum(event));
}
void YetiSimulator::pwm_on(const double dutycycle) {
if (dutycycle > 0.0) {
module_state->pwm_duty_cycle = dutycycle;
module_state->pwm_running = true;
module_state->pwm_error_f = false;
} else {
cp_state_x1();
}
}
void YetiSimulator::cp_state_x1() {
module_state->pwm_duty_cycle = 1.0;
module_state->pwm_running = false;
module_state->pwm_error_f = false;
}
void YetiSimulator::cp_state_f() {
module_state->pwm_duty_cycle = 1.0;
module_state->pwm_running = false;
module_state->pwm_error_f = true;
}
void YetiSimulator::reset_powermeter() const {
if (config.reset_powermeter_on_session_start) {
module_state->import_watt_hr.L1 = 0;
module_state->import_watt_hr.L2 = 0;
module_state->import_watt_hr.L3 = 0;
module_state->export_watt_hr.L1 = 0;
module_state->export_watt_hr.L2 = 0;
module_state->export_watt_hr.L3 = 0;
}
module_state->powermeter_sim_last_time_stamp = 0;
}
types::board_support_common::ProximityPilot YetiSimulator::read_pp_ampacity() const {
const auto pp_resistor = module_state->simdata_setting.pp_resistor;
if (pp_resistor < 80.0 or pp_resistor > 2460) {
EVLOG_error << "PP resistor value " << pp_resistor << " Ohm seems to be outside the allowed range.";
return {types::board_support_common::Ampacity::None};
}
// PP resistor value in spec, use a conservative interpretation of the resistance ranges
if (pp_resistor > 936.0 and pp_resistor <= 2460.0) {
return {types::board_support_common::Ampacity::A_13};
}
if (pp_resistor > 308.0 && pp_resistor <= 936.0) {
return {types::board_support_common::Ampacity::A_20};
}
if (pp_resistor > 140.0 && pp_resistor <= 308.0) {
return {types::board_support_common::Ampacity::A_32};
}
if (pp_resistor > 80.0 && pp_resistor <= 140.0) {
return {types::board_support_common::Ampacity::A_63_3ph_70_1ph};
}
return {types::board_support_common::Ampacity::None};
}
} // namespace module