Skip to content
Open
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
Expand Up @@ -5,6 +5,14 @@
### 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.
- The module now supports setting 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: 104
- Per dispenser:
- `evse_board_support/EnclosureOpen` set as Door status alarm to the PSU
- `evse_board_support/WaterIngressDetected` set as Water alarm
- `evse_board_support/MREC8EmergencyStop` set as EPO alarm
- `evse_board_support/TiltDetected` set as Tilt alarm
- Per connector:
- `evse_board_support/MREC17EVSEContactorFault` set as DC output contactor fault

## June 2025

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";
}

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 @@ -134,6 +149,48 @@
}

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

// Subscribe to BSP Dispenser Alarms
for (int bsp_idx = 0; bsp_idx < number_of_connectors_used; bsp_idx++) {
dispenser_alarms_per_bsp.push_back(std::set<DispenserAlarms>{});

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 175 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#L175

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 @@ -23,6 +23,7 @@
// insert your custom include headers here
#include "telemetry_publisher_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 Down Expand Up @@ -98,6 +99,9 @@
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,21 @@ void ConnectorBase::ev_init() {
}
});

mod->telemetry_publisher->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_publisher->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_publisher->datapoint_changed(telemetry_subtopic, "dc_output_contactor_fault_alarm", false);
EVLOG_info << "Contactor fault error from BSP cleared";
});

mod->telemetry_publisher->initialize_datapoint(telemetry_subtopic, telemetry_datapoint_keys::OUTPUT_VOLTAGE);
mod->telemetry_publisher->initialize_datapoint(telemetry_subtopic, telemetry_datapoint_keys::OUTPUT_CURRENT);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ The data published looks like this (example for base topic ``base_topic``):

{
"bsp_event": "PowerOn",
"dc_output_contactor_fault_alarm": false,
"everest_mode": "Export",
"everest_phase": "Charging",
"export_current": 20.0,
Expand All @@ -73,12 +74,46 @@ The data published looks like this (example for base topic ``base_topic``):
"total_historic_input_energy": 100000.0
}

``base_topic/dispenser/published_alarms``

.. code-block:: json

{
"door_status_alarm": false,
"epo_alarm": false,
"tilt_alarm": false,
"water_alarm": false
}

The units are SI units (Amps, Volts, Watts, Watt-hours).

.. note::

All telemetry values can be null, indicating that no value has been received or sent yet.

BSP Errors
==========

This driver supports setting specific errors to the Power supply unit as Dispenser and Connector Alarms as a reaction to EVerest BSP errors:

+-------------------------------------------------+---------------------------+---------------+
| Everest BSP Error | PSU Modbus Register name | Scope |
+=================================================+===========================+===============+
| ``evse_board_support/EnclosureOpen`` | Door status alarm | Dispenser |
+-------------------------------------------------+---------------------------+---------------+
| ``evse_board_support/WaterIngressDetected`` | Water alarm | Dispenser |
+-------------------------------------------------+---------------------------+---------------+
| ``evse_board_support/MREC8EmergencyStop`` | EPO alarm | Dispenser |
+-------------------------------------------------+---------------------------+---------------+
| ``evse_board_support/TiltDetected`` | Tilt alarm | Dispenser |
+-------------------------------------------------+---------------------------+---------------+
| ``evse_board_support/MREC17EVSEContactorFault`` | DC output contactor fault | Per Connector |
+-------------------------------------------------+---------------------------+---------------+

The connector alarms are published 1:1 to the connectors (if the BSP for connector 1 has the error, connector 1 gets the alarm, etc).

For the dispenser alarms, if any of the BSPs has the error, the alarm is published to the dispenser. If all BSPs clear the error, the alarm is cleared.

Power Supply Mock
==================

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ class Connector {
friend class Dispenser;

public:
Connector(ConnectorConfig connector_config, std::uint16_t local_connector_number, DispenserConfig dispenser_config,
logs::LogIntf log);
Connector(
ConnectorConfig connector_config, uint16_t local_connector_number, DispenserConfig dispenser_config,
logs::LogIntf log, std::function<void()> trigger_unsolicited_report_cb = []() {});
Connector(const Connector&) = delete;
~Connector();

Expand Down Expand Up @@ -182,6 +183,12 @@ class Connector {
*/
void reset_psu_capabilities();

/**
* @brief Set or clear the DC output contactor fault alarm for this connector.
* This will immediately publish the alarm state via Modbus.
*/
void set_dc_output_contactor_fault_alarm(bool active);

private:
logs::LogIntf log;
std::string log_prefix; // Prefix for log messages
Expand All @@ -192,11 +199,17 @@ class Connector {
ConnectorGooseSender goose_sender;
ConnectorRegistersConfig connector_registers_config;
ConnectorRegisters connector_registers;
std::function<void()> trigger_unsolicited_report_cb; // callback to dispenser to trigger an
// unsolicited report

std::optional<float> rated_output_power_psu; // in kW, set by register callback
std::optional<float> max_rated_psu_voltage; // in V, set by register callback
std::optional<float> max_rated_psu_current; // in A, set by register callback

// dc output contactor fault alarm state; used directly in register read
// callback
std::atomic<bool> dc_output_contactor_fault_alarm_active;

ConnectorFSM fsm;
bool last_module_placeholder_allocation_failed;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#pragma once
#include <atomic>
#include <condition_variable>

Check warning on line 5 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp#L5

Include file: <condition_variable> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <fusion_charger/goose/power_request.hpp>
#include <fusion_charger/goose/stop_charge_request.hpp>
#include <fusion_charger/modbus/extensions/unsolicitated_registry.hpp>
Expand Down Expand Up @@ -46,6 +47,16 @@
// category and subcategory
typedef std::set<ErrorEvent, ErrorEventComparator> ErrorEventSet;

enum class DispenserAlarms {
DOOR_STATUS_ALARM,
WATER_ALARM,
EPO_ALARM,
TILT_ALARM,
};

/// @brief Get a list of all possible DispenserAlarms
const std::vector<DispenserAlarms>& get_all_dispenser_alarms();

class Dispenser {
private:
std::vector<std::shared_ptr<Connector>> connectors;
Expand Down Expand Up @@ -77,13 +88,23 @@
std::optional<PowerUnitRegisters> psu_registers;
std::optional<ErrorRegisters> error_registers;

// Raised errors by the PSU
ErrorEventSet raised_errors = {};
std::mutex raised_error_mutex;

// Raised errrors by the dispenser (this driver)
std::unordered_map<DispenserAlarms, std::atomic<bool>> dispenser_alarms;

Check warning on line 96 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp#L96

class member 'Dispenser::dispenser_alarms' is never used.

std::optional<SettingPowerUnitRegisters::PSURunningMode> psu_running_mode = std::nullopt;

// Mutex + CV combination to trigger unsolicited reports
std::mutex unsolicited_report_mutex;
std::condition_variable unsolicited_report_cv;

const int MAX_NUMBER_OF_CONNECTORS = 4;

static const std::string DISPENSER_TELEMETRY_ALARMS_SUBTOPIC;

Check warning on line 106 in modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp#L106

class member 'Dispenser::DISPENSER_TELEMETRY_ALARMS_SUBTOPIC' is never used.

// true if the psu wrote its mac address via modbus
bool psu_mac_received = false;
// true if the psu wrote the connectors hmac key via modbus
Expand All @@ -101,6 +122,12 @@
bool psu_communication_is_ok();
bool is_stop_requested();

/// @brief get the state of a dispenser alarm, true if active
bool get_dispenser_alarm_state(DispenserAlarms alarm);

/// @brief get the telemetry datapoint key for a dispenser alarm
std::string dispenser_alarm_to_telemetry_datapoint(DispenserAlarms alarm);

public:
Dispenser(DispenserConfig dispenser_config, std::vector<ConnectorConfig> connector_configs,
logs::LogIntf log = logs::log_printf);
Expand Down Expand Up @@ -136,4 +163,13 @@
// Connector numbers start at 1
return connectors[local_connector_number - 1];
}

/// @brief Trigger an unsolicited report to be sent now.
void trigger_unsolicited_report();

/// @brief Set state for a dispenser alarm. Also triggers an immediate
/// unsolicited report.
/// @param alarm the alarm to set
/// @param active true to set the alarm, false to clear it
void set_dispenser_alarm(DispenserAlarms alarm, bool active);
};
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,9 @@ std::string state_to_string(States state) {
return "UNKNOWN";
}

Connector::Connector(ConnectorConfig connector_config, std::uint16_t local_connector_number,
DispenserConfig dispenser_config, logs::LogIntf log) :
Connector::Connector(ConnectorConfig connector_config, uint16_t local_connector_number,
DispenserConfig dispenser_config, logs::LogIntf log,
std::function<void()> trigger_unsolicited_report_cb) :
connector_config(connector_config),
local_connector_number(local_connector_number),
dispenser_config(dispenser_config),
Expand All @@ -187,6 +188,7 @@ Connector::Connector(ConnectorConfig connector_config, std::uint16_t local_conne
config.get_output_current = connector_config.connector_callbacks.output_current;
config.get_contactor_status = connector_config.connector_callbacks.contactor_status;
config.get_electronic_lock_status = connector_config.connector_callbacks.electronic_lock_status;
config.get_dc_output_contact_fault = [this]() { return dc_output_contactor_fault_alarm_active.load(); };
return config;
}()),
connector_registers(connector_registers_config),
Expand All @@ -199,7 +201,9 @@ Connector::Connector(ConnectorConfig connector_config, std::uint16_t local_conne
std::placeholders::_1, std::placeholders::_2);
return callbacks;
}(),
log, log_prefix) {
log, log_prefix),
trigger_unsolicited_report_cb(trigger_unsolicited_report_cb),
dc_output_contactor_fault_alarm_active(false) {
}

Connector::~Connector() {
Expand Down Expand Up @@ -545,3 +549,12 @@ std::vector<std::uint8_t> Connector::get_hmac_key() {
const std::uint8_t* hmac_key = connector_registers.hmac_key.get_value(); // pointer to private memory
return std::vector<std::uint8_t>(hmac_key, hmac_key + connector_registers.hmac_key.get_size());
}

void Connector::set_dc_output_contactor_fault_alarm(bool active) {
dc_output_contactor_fault_alarm_active = active;

// immediately do an unsolicited report
if (trigger_unsolicited_report_cb) {
trigger_unsolicited_report_cb();
}
}
Loading
Loading