Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion errors/evse_board_support.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ errors:
description: The latch on the connector is broken.
- name: MREC26CutCable
description: The output cable has been severed from the EVSE.
- name: TiltDetected
description: The EVSE has been tilted beyond acceptable limits.
- name: WaterIngressDetected
description: A substantial amount of water has been detected inside the EVSE.
- name: EnclosureOpen
description: The EVSE enclosure is open, e.g. a door or panel is not properly closed.
- name: VendorError
description: >-
Vendor specific error code. Will stop charging session.
Expand All @@ -57,4 +63,3 @@ errors:
- name: CommunicationFault
description: >-
The communication to the hardware or underlying driver is lost or has errors.

Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## December 2025

### Module

- The module can now publish telemetry data on a specified mqtt base topic, set via the config option `telemetry_topic_prefix`. The concrete telemetry data is published only when the data changes to reduce mqtt traffic. The telemetry data is published as json objects per dispenser and per connector. See the module documentation for details.

Check notice on line 7 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md#L7

Expected: 80; Actual: 342
- The module now supports publishing some specific BSP errors to the PSU as dispenser and connector alarms:

Check notice on line 8 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md#L8

Expected: 80; Actual: 107
- Per dispenser:
- `evse_board_support/EnclosureOpen` published as Door status alarm to the PSU
- `evse_board_support/WaterIngressDetected` published as Water alarm
- `evse_board_support/MREC8EmergencyStop` published as EPO alarm
- `evse_board_support/TiltDetected` published as Tilt alarm
- Per connector:
- `evse_board_support/MREC17EVSEContactorFault` published as DC output contactor fault

Check notice on line 15 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md#L15

Expected: 80; Actual: 90

## June 2025

- Module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ target_sources(${MODULE_NAME}
)

# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
target_sources(${MODULE_NAME} PRIVATE "connector_base/base.cpp")
target_sources(${MODULE_NAME} PRIVATE
"connector_base/base.cpp"
"telemetry_manager_everest.cpp"
)
add_subdirectory(fusion_charger_lib)

option(INSTALL_FUSION_CHARGER_MOCK "Install fusion charger mock" OFF)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@
return connector_bases;
}

static std::string get_everest_error_for_dispenser_alarm(DispenserAlarms alarm) {
switch (alarm) {
case DispenserAlarms::DOOR_STATUS_ALARM:
return "evse_board_support/EnclosureOpen";
case DispenserAlarms::WATER_ALARM:
return "evse_board_support/WaterIngressDetected";
case DispenserAlarms::EPO_ALARM:
return "evse_board_support/MREC8EmergencyStop";
case DispenserAlarms::TILT_ALARM:
return "evse_board_support/TiltDetected";
default:
throw std::runtime_error("Unknown DispenserAlarm enum value");
}
}

void Huawei_V100R023C10::init() {
this->communication_fault_raised = false;
this->psu_not_running_raised = false;
Expand Down Expand Up @@ -76,6 +91,14 @@
std::runtime_error("OVMs are necessary but number of OVMs does not match number of connectors in use"));
}

if (config.telemetry_topic_prefix.empty()) {
this->telemetry_manager = std::make_shared<fusion_charger::telemetry::TelemetryManagerNull>();
} else {
this->telemetry_manager = std::make_shared<TelementryManagerEverest>(
[this](const std::string& topic, const nlohmann::json& data) { mqtt.publish(topic, data.dump()); },
config.telemetry_topic_prefix);
}

// Initialize all connectors. After that the config was loaded and we can initialize the dispenser
for (int i = 0; i < number_of_connectors_used; i++) {
invoke_init(*implementations[i]);
Expand All @@ -98,6 +121,8 @@
dispenser_config.module_placeholder_allocation_timeout =
std::chrono::seconds(config.module_placeholder_allocation_timeout_s);

dispenser_config.telemetry_manager = this->telemetry_manager;

if (config.tls_enabled) {
tls_util::MutualTlsClientConfig mutual_tls_config;
mutual_tls_config.ca_cert = config.psu_ca_cert;
Expand All @@ -118,6 +143,47 @@
}

dispenser = std::make_unique<Dispenser>(dispenser_config, connector_configs, log);

// Subscribe to BSP Dispenser Alarms
for (int i = 0; i < number_of_connectors_used; i++) {
dispenser_alarms_per_bsp.push_back(std::set<DispenserAlarms>{});
}
for (int bsp_idx = 0; bsp_idx < number_of_connectors_used; bsp_idx++) {
for (auto& alarm : get_all_dispenser_alarms()) {
std::string everest_error = get_everest_error_for_dispenser_alarm(alarm);

r_board_support[bsp_idx]->subscribe_error(
everest_error,
[this, bsp_idx, alarm, everest_error](const ::Everest::error::Error& e) {
// Error raised
auto& alarms = dispenser_alarms_per_bsp[bsp_idx];
if (alarms.find(alarm) == alarms.end()) {
alarms.insert(alarm);
EVLOG_info << "Raising dispenser alarm due to BSP error " << everest_error;
dispenser->set_dispenser_alarm(alarm, true);
}
},
[this, bsp_idx, alarm, everest_error](const ::Everest::error::Error& e) {
// Error cleared
auto& alarms = dispenser_alarms_per_bsp[bsp_idx];
if (alarms.find(alarm) != alarms.end()) {

Check warning on line 169 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp#L169

Redundant checking of STL container element existence before removing it.
alarms.erase(alarm);
// check if any other BSP raised this alarm
bool alarm_still_raised = false;
for (const auto& other_alarms : dispenser_alarms_per_bsp) {
if (other_alarms.find(alarm) != other_alarms.end()) {
alarm_still_raised = true;
break;
}
}
if (not alarm_still_raised) {
EVLOG_info << "Clearing dispenser alarm as all BSPs cleared error " << everest_error;
dispenser->set_dispenser_alarm(alarm, false);
}
}
});
}
}
}

void Huawei_V100R023C10::ready() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include "telemetry_manager_everest.hpp"
#include <dispenser.hpp>
#include <set>

Check warning on line 26 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp#L26

Include file: <set> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <vector>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1

Expand All @@ -43,12 +45,14 @@
bool allow_insecure_goose;
bool verify_secure_goose;
std::string upstream_voltage_source;
std::string telemetry_topic_prefix;

Check warning on line 48 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp#L48

struct member 'Conf::telemetry_topic_prefix' is never used.
};

class Huawei_V100R023C10 : public Everest::ModuleBase {
public:
Huawei_V100R023C10() = delete;
Huawei_V100R023C10(const ModuleInfo& info, std::unique_ptr<power_supply_DCImplBase> p_connector_1,
Huawei_V100R023C10(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,

Check warning on line 54 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp#L54

Member variable 'Huawei_V100R023C10::number_of_connectors_used' is not initialized in the constructor.
std::unique_ptr<power_supply_DCImplBase> p_connector_1,
std::unique_ptr<power_supply_DCImplBase> p_connector_2,
std::unique_ptr<power_supply_DCImplBase> p_connector_3,
std::unique_ptr<power_supply_DCImplBase> p_connector_4,
Expand All @@ -57,6 +61,7 @@
std::vector<std::unique_ptr<powermeterIntf>> r_carside_powermeter,
std::vector<std::unique_ptr<over_voltage_monitorIntf>> r_over_voltage_monitor, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
p_connector_1(std::move(p_connector_1)),
p_connector_2(std::move(p_connector_2)),
p_connector_3(std::move(p_connector_3)),
Expand All @@ -67,6 +72,7 @@
r_over_voltage_monitor(std::move(r_over_voltage_monitor)),
config(config){};

Everest::MqttProvider& mqtt;
const std::unique_ptr<power_supply_DCImplBase> p_connector_1;
const std::unique_ptr<power_supply_DCImplBase> p_connector_2;
const std::unique_ptr<power_supply_DCImplBase> p_connector_3;
Expand All @@ -93,12 +99,17 @@
std::vector<power_supply_DCImplBase*> implementations = {p_connector_1.get(), p_connector_2.get(),
p_connector_3.get(), p_connector_4.get()};

// List of sets of active DispenserAlarms for each BSP module
std::vector<std::set<DispenserAlarms>> dispenser_alarms_per_bsp;

Check warning on line 103 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp#L103

class member 'Huawei_V100R023C10::dispenser_alarms_per_bsp' is never used.

enum class UpstreamVoltageSource {
IMD,
OVM,
};
// PSU upstream voltage source
UpstreamVoltageSource upstream_voltage_source;

std::shared_ptr<fusion_charger::telemetry::TelemetryManagerBase> telemetry_manager;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1

protected:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ void ConnectorBase::ev_init() {

init_capabilities();

telemetry_subtopic = "connector/" + std::to_string(config.global_connector_number) + "/dispenser_to_psu";
mod->telemetry_manager->add_subtopic(telemetry_subtopic);

mod->telemetry_manager->initialize_datapoint(telemetry_subtopic, "bsp_event");

mod->r_board_support[this->connector_no]->subscribe_event(
[this](const types::board_support_common::BspEvent& event) {
EVLOG_verbose << log_prefix << "Received event: " << event;
Expand All @@ -75,6 +80,9 @@ void ConnectorBase::ev_init() {
this->external_provided_data.contactor_status = ContactorStatus::OFF;
}

mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "bsp_event",
types::board_support_common::event_to_string(event.event));

auto connector = this->get_connector();
std::lock_guard lock(connector_mutex);

Expand All @@ -85,6 +93,24 @@ void ConnectorBase::ev_init() {
}
});

mod->telemetry_manager->initialize_datapoint(telemetry_subtopic, "dc_output_contactor_fault_alarm", false);

mod->r_board_support[this->connector_no]->subscribe_error(
"evse_board_support/MREC17EVSEContactorFault",
[this](const Everest::error::Error& error) {
get_connector()->set_dc_output_contactor_fault_alarm(true);
mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "dc_output_contactor_fault_alarm", true);
EVLOG_info << "Received contactor fault error from BSP";
},
[this](const Everest::error::Error& error) {
get_connector()->set_dc_output_contactor_fault_alarm(false);
mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "dc_output_contactor_fault_alarm", false);
EVLOG_info << "Contactor fault error from BSP cleared";
});

mod->telemetry_manager->initialize_datapoint(telemetry_subtopic, "output_voltage");
mod->telemetry_manager->initialize_datapoint(telemetry_subtopic, "output_current");

if (not mod->r_carside_powermeter.empty()) {
mod->r_carside_powermeter[this->connector_no]->subscribe_powermeter(
[this](const types::powermeter::Powermeter& power) {
Expand All @@ -111,9 +137,14 @@ void ConnectorBase::ev_init() {

// Everest voltage measurement publishing
this->impl->publish_voltage_current(export_vc);

mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "output_voltage", output_voltage);
mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "output_current", output_current);
});
}

mod->telemetry_manager->initialize_datapoint(telemetry_subtopic, "upstream_voltage");

// note that Huawei_V100R023C10 already checks, if the required interfaces are available
if (mod->upstream_voltage_source == Huawei_V100R023C10::UpstreamVoltageSource::IMD) {
mod->r_isolation_monitor[this->connector_no]->subscribe_isolation_measurement(
Expand All @@ -124,6 +155,8 @@ void ConnectorBase::ev_init() {
EVLOG_debug << log_prefix << "Publishing upstream voltage from IMD: " << iso.voltage_V.value_or(0)
<< "V";
this->external_provided_data.upstream_voltage = iso.voltage_V.value_or(0);
mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "upstream_voltage",
this->external_provided_data.upstream_voltage);
});
}

Expand All @@ -137,6 +170,8 @@ void ConnectorBase::ev_init() {
if (mod->upstream_voltage_source == Huawei_V100R023C10::UpstreamVoltageSource::OVM) {
EVLOG_debug << log_prefix << "Publishing upstream voltage from OVM: " << voltage << "V";
this->external_provided_data.upstream_voltage = voltage;
mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "upstream_voltage",
this->external_provided_data.upstream_voltage);
}

// Everest voltage measurement publishing
Expand All @@ -155,6 +190,11 @@ void ConnectorBase::ev_init() {
}
});
}

mod->telemetry_manager->initialize_datapoint(telemetry_subtopic, "everest_mode");
mod->telemetry_manager->initialize_datapoint(telemetry_subtopic, "everest_phase");
mod->telemetry_manager->initialize_datapoint(telemetry_subtopic, "export_voltage");
mod->telemetry_manager->initialize_datapoint(telemetry_subtopic, "export_current");
}

void ConnectorBase::ev_ready() {
Expand Down Expand Up @@ -214,6 +254,11 @@ void ConnectorBase::ev_handle_setMode(types::power_supply_DC::Mode mode, types::
break;
}

mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "everest_mode",
types::power_supply_DC::mode_to_string(mode));
mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "everest_phase",
types::power_supply_DC::charging_phase_to_string(phase));

this->impl->publish_mode(mode);
}
void ConnectorBase::ev_handle_setExportVoltageCurrent(double voltage, double current) {
Expand All @@ -233,6 +278,9 @@ void ConnectorBase::ev_handle_setExportVoltageCurrent(double voltage, double cur
export_voltage = voltage;
export_current_limit = current;

mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "export_voltage", voltage);
mod->telemetry_manager->datapoint_changed(telemetry_subtopic, "export_current", current);

this->get_connector()->new_export_voltage_current(voltage, current);
}
void ConnectorBase::ev_handle_setImportVoltageCurrent(double voltage, double current) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
void init_capabilities();

std::string log_prefix;
std::string telemetry_subtopic;

Check warning on line 92 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.hpp#L92

class member 'ConnectorBase::telemetry_subtopic' is never used.

std::atomic<bool> module_placeholder_allocation_failure_raised;

Expand Down
Loading