From 15893a5bdb61257ac76e2c563e4b04c839ad63fd Mon Sep 17 00:00:00 2001 From: Mark Oude Elberink Date: Fri, 5 Dec 2025 11:51:59 +0000 Subject: [PATCH 1/9] feat: initial telemetry Signed-off-by: Mark Oude Elberink --- .../Huawei_V100R023C10/CHANGELOG.md | 6 + .../Huawei_V100R023C10/CMakeLists.txt | 5 +- .../Huawei_V100R023C10/Huawei_V100R023C10.cpp | 10 ++ .../Huawei_V100R023C10/Huawei_V100R023C10.hpp | 9 +- .../connector_base/base.cpp | 33 +++++ .../connector_base/base.hpp | 1 + .../PowerSupplies/Huawei_V100R023C10/doc.rst | 57 +++++++ .../include/configuration.hpp | 4 + .../include/dispenser.hpp | 1 + .../include/telemetry.hpp | 140 ++++++++++++++++++ .../lib/connector.cpp | 20 +++ .../lib/dispenser.cpp | 25 ++++ .../fusion_charger/modbus/registers/raw.hpp | 1 + .../fusion_charger_modbus_driver/src/raw.cpp | 12 ++ .../Huawei_V100R023C10/manifest.yaml | 7 + .../telemetry_manager_everest.cpp | 101 +++++++++++++ .../telemetry_manager_everest.hpp | 60 ++++++++ 17 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/telemetry.hpp create mode 100644 modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/telemetry_manager_everest.cpp create mode 100644 modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/telemetry_manager_everest.hpp diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md index 13b8548f5a..6e6b190171 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md @@ -1,5 +1,11 @@ # 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. + ## June 2025 - Module diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CMakeLists.txt b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CMakeLists.txt index 39b20a266e..abe3c690e9 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CMakeLists.txt +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CMakeLists.txt @@ -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) diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp index c54d1d07ec..e21f107a5c 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp @@ -76,6 +76,14 @@ void Huawei_V100R023C10::init() { 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(); + } else { + this->telemetry_manager = std::make_shared( + [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]); @@ -98,6 +106,8 @@ void Huawei_V100R023C10::init() { 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; diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp index 890dd22f26..16c98d5201 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp @@ -21,6 +21,7 @@ // ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 // insert your custom include headers here +#include "telemetry_manager_everest.hpp" #include #include // ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 @@ -43,12 +44,14 @@ struct Conf { bool allow_insecure_goose; bool verify_secure_goose; std::string upstream_voltage_source; + std::string telemetry_topic_prefix; }; class Huawei_V100R023C10 : public Everest::ModuleBase { public: Huawei_V100R023C10() = delete; - Huawei_V100R023C10(const ModuleInfo& info, std::unique_ptr p_connector_1, + Huawei_V100R023C10(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, + std::unique_ptr p_connector_1, std::unique_ptr p_connector_2, std::unique_ptr p_connector_3, std::unique_ptr p_connector_4, @@ -57,6 +60,7 @@ class Huawei_V100R023C10 : public Everest::ModuleBase { std::vector> r_carside_powermeter, std::vector> 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)), @@ -67,6 +71,7 @@ class Huawei_V100R023C10 : public Everest::ModuleBase { r_over_voltage_monitor(std::move(r_over_voltage_monitor)), config(config){}; + Everest::MqttProvider& mqtt; const std::unique_ptr p_connector_1; const std::unique_ptr p_connector_2; const std::unique_ptr p_connector_3; @@ -99,6 +104,8 @@ class Huawei_V100R023C10 : public Everest::ModuleBase { }; // PSU upstream voltage source UpstreamVoltageSource upstream_voltage_source; + + std::shared_ptr telemetry_manager; // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 protected: diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp index d623468b10..9b90d31408 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp @@ -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; @@ -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); @@ -85,6 +93,9 @@ void ConnectorBase::ev_init() { } }); + 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) { @@ -111,9 +122,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( @@ -124,6 +140,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); }); } @@ -137,6 +155,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 @@ -155,6 +175,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() { @@ -214,6 +239,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) { @@ -233,6 +263,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) { diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.hpp index 48ac165d2f..bcb26520ac 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.hpp @@ -89,6 +89,7 @@ class ConnectorBase { void init_capabilities(); std::string log_prefix; + std::string telemetry_subtopic; std::atomic module_placeholder_allocation_failure_raised; diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst index fcf88e4c9b..c0c104400a 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst @@ -22,6 +22,63 @@ For the everest measurements two options are available: - Using a carside powermeter (ideally the powermeter that is connected to the EvseManager's ``powermeter_car_side``) - Using a carside powermeter but during cable check using an ``OVM`` (see ``HACK_use_ovm_while_cable_check`` config option) +Telemetry +========= + +The module can 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 data published looks like this (example for base topic ``base_topic``): + +``base_topic/connector/1`` + +.. code-block:: json + + { + "max_rated_psu_current": 100.0, + "max_rated_psu_voltage": 1000.0, + "min_rated_psu_current": 1.0, + "min_rated_psu_voltage": 100.0, + "psu_port_available": "AVAILABLE", + "rated_output_power_psu": 60000.0 + } + +``base_topic/connector/1/dispenser_to_psu`` + +.. code-block:: json + + { + "bsp_event": "PowerOn", + "everest_mode": "Export", + "everest_phase": "Charging", + "export_current": 20.0, + "export_voltage": 400.0, + "output_current": 0.0, + "output_voltage": 0.0, + "upstream_voltage": 0.0 + } + +``base_topic/psu`` + +.. code-block:: json + + { + "ac_input_current_a": 10.0, + "ac_input_current_b": 10.5, + "ac_input_current_c": 9.5, + "ac_input_voltage_a": 230.0, + "ac_input_voltage_b": 231.0, + "ac_input_voltage_c": 229.0, + "psu_running_mode": "RUNNING", + "total_historic_input_energy": 100000.0 + } + +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. + Power Supply Mock ================== diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/configuration.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/configuration.hpp index d5bef4b03e..9fb26c50f4 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/configuration.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/configuration.hpp @@ -8,6 +8,7 @@ #include "callbacks.hpp" #include "fusion_charger/modbus/registers/raw.hpp" +#include "telemetry.hpp" #include "tls_util.hpp" typedef fusion_charger::modbus_driver::raw_registers::ConnectorType ConnectorType; @@ -40,6 +41,9 @@ struct DispenserConfig { std::optional tls_config; std::chrono::milliseconds module_placeholder_allocation_timeout; + + std::shared_ptr telemetry_manager = + std::make_shared(); }; struct ConnectorConfig { diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp index 137580676d..bd4eab7707 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp @@ -22,6 +22,7 @@ #include "connector.hpp" #include "connector_goose_sender.hpp" #include "state.hpp" +#include "telemetry.hpp" using namespace fusion_charger::modbus_driver::raw_registers; using namespace fusion_charger::modbus_driver; diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/telemetry.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/telemetry.hpp new file mode 100644 index 0000000000..51a79013aa --- /dev/null +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/telemetry.hpp @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest +#pragma once + +#include +#include + +namespace fusion_charger::telemetry { + +/** + * @brief Base interface class for fusion charger telemetry managers + */ +class TelemetryManagerBase { +public: + virtual ~TelemetryManagerBase() = default; + /** + * @brief Add a new subtopic for telemetry datapoints + * @param subtopic The subtopic name + */ + virtual void add_subtopic(const std::string& subtopic) = 0; + /** + * @brief Notify that a datapoint for a subtopic has changed (string value) + * @param subtopic The subtopic name + * @param datapoint The datapoint name + * @param value The new value + */ + virtual void datapoint_changed(const std::string& subtopic, const std::string& datapoint, + const std::string& value) = 0; + /** + * @brief Notify that a datapoint for a subtopic has changed (double value) + * @param subtopic The subtopic name + * @param datapoint The datapoint name + * @param value The new value + */ + virtual void datapoint_changed(const std::string& subtopic, const std::string& datapoint, double value) = 0; + + /** + * @brief Notify that a datapoint for a subtopic has changed (bool value) + * @param subtopic The subtopic name + * @param datapoint The datapoint name + * @param value The new value + */ + virtual void datapoint_changed(const std::string& subtopic, const std::string& datapoint, bool value) = 0; + + /** + * @brief Check if a datapoint exists for a subtopic + * @param subtopic The subtopic name + * @param datapoint The datapoint name + * @return true if the datapoint exists, false otherwise + */ + virtual bool datapoint_exists(const std::string& subtopic, const std::string& datapoint) = 0; + + /** + * @brief Initialize a datapoint for a subtopic with null + * @param subtopic The subtopic name + * @param datapoint The datapoint name + */ + virtual void initialize_datapoint(const std::string& subtopic, const std::string& datapoint) = 0; + + /** + * @brief Initialize a datapoint for a subtopic with a value + * @note Does nothing if the datapoint already exists + * @param subtopic The subtopic name + * @param datapoint The datapoint name + * @param value The initial value + */ + template + void initialize_datapoint(const std::string& subtopic, const std::string& datapoint, T value) { + if (!datapoint_exists(subtopic, datapoint)) { + datapoint_changed(subtopic, datapoint, value); + } + } + + /** + * @brief Utility to add a callback to a holding complex register data provider that notifies the telemetry manager + * on value changes + * @tparam T The type of the data provider value + * @param subtopic The subtopic name + * @param datapoint The datapoint name + * @param data_provider The data provider to add the callback to + * @param conversion_func An optional conversion function to convert (e.g. to SI units) the value before sending it + * to the telemetry manager + */ + template + void + register_complex_register_data_provider(const std::string& subtopic, const std::string& datapoint, + modbus::registers::data_providers::DataProviderHolding* data_provider, + std::function conversion_func = nullptr) { + initialize_datapoint(subtopic, datapoint); + data_provider->add_write_callback([this, subtopic, datapoint, conversion_func](T value) { + if (conversion_func) { + value = conversion_func(value); + } + this->datapoint_changed(subtopic, datapoint, value); + }); + } + + /** + * @brief Utility to add a callback to a holding complex register data provider that notifies the telemetry manager + * on value changes, converting the value to a string using the provided function + * @tparam T The type of the data provider value + * @param subtopic The subtopic name + * @param datapoint The datapoint name + * @param data_provider The data provider to add the callback to + * @param to_string_func The function to convert the value to a string + */ + template + void register_complex_register_data_provider_enum( + const std::string& subtopic, const std::string& datapoint, + modbus::registers::data_providers::DataProviderHolding* data_provider, + std::function to_string_func) { + initialize_datapoint(subtopic, datapoint); + data_provider->add_write_callback([this, subtopic, datapoint, to_string_func](T value) { + this->datapoint_changed(subtopic, datapoint, to_string_func(value)); + }); + } +}; + +/** + * @brief Null implementation of the TelemetryManagerBase that does nothing + */ +class TelemetryManagerNull : public TelemetryManagerBase { +public: + void add_subtopic(const std::string& subtopic) override { + } + void datapoint_changed(const std::string& subtopic, const std::string& datapoint, + const std::string& value) override { + } + void datapoint_changed(const std::string& subtopic, const std::string& datapoint, double value) override { + } + void datapoint_changed(const std::string& subtopic, const std::string& datapoint, bool value) override { + } + void initialize_datapoint(const std::string& subtopic, const std::string& datapoint) override { + } + bool datapoint_exists(const std::string& subtopic, const std::string& datapoint) override { + return true; + } +}; + +}; // namespace fusion_charger::telemetry diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp index 25d185f35b..c69135d2fc 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp @@ -314,6 +314,26 @@ void Connector::start() { // todo: reset fsm? goose_sender.start(); + + std::string connector_telemetry_subtopic = "connector/" + std::to_string(connector_config.global_connector_number); + dispenser_config.telemetry_manager->add_subtopic(connector_telemetry_subtopic); + + dispenser_config.telemetry_manager->register_complex_register_data_provider( + connector_telemetry_subtopic, "max_rated_psu_current", &connector_registers.max_rated_psu_current); + dispenser_config.telemetry_manager->register_complex_register_data_provider( + connector_telemetry_subtopic, "min_rated_psu_current", &connector_registers.min_rated_psu_current); + dispenser_config.telemetry_manager->register_complex_register_data_provider( + connector_telemetry_subtopic, "max_rated_psu_voltage", &connector_registers.max_rated_psu_voltage); + dispenser_config.telemetry_manager->register_complex_register_data_provider( + connector_telemetry_subtopic, "min_rated_psu_voltage", &connector_registers.min_rated_psu_voltage); + + dispenser_config.telemetry_manager->register_complex_register_data_provider( + connector_telemetry_subtopic, "rated_output_power_psu", &connector_registers.rated_output_power_psu, + [](const float& kw) { return kw * 1000.0; }); + + dispenser_config.telemetry_manager->register_complex_register_data_provider_enum( + connector_telemetry_subtopic, "psu_port_available", &connector_registers.psu_port_available, + psu_output_port_availability_to_string); } void Connector::stop() { diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp index ec668d5fa7..56e1d433cb 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp @@ -436,6 +436,31 @@ void Dispenser::init() { modbus_unsolicitated_event_thread = std::thread([this]() { modbus_unsolicitated_event_thread_run(); }); goose_receiver_thread = std::thread([this]() { goose_receiver_thread_run(); }); + + // add telemetry callbacks + dispenser_config.telemetry_manager->add_subtopic("psu"); + + dispenser_config.telemetry_manager->register_complex_register_data_provider("psu", "ac_input_voltage_a", + &psu_registers->ac_input_voltage_a); + dispenser_config.telemetry_manager->register_complex_register_data_provider("psu", "ac_input_voltage_b", + &psu_registers->ac_input_voltage_b); + dispenser_config.telemetry_manager->register_complex_register_data_provider("psu", "ac_input_voltage_c", + &psu_registers->ac_input_voltage_c); + + dispenser_config.telemetry_manager->register_complex_register_data_provider("psu", "ac_input_current_a", + &psu_registers->ac_input_current_a); + dispenser_config.telemetry_manager->register_complex_register_data_provider("psu", "ac_input_current_b", + &psu_registers->ac_input_current_b); + dispenser_config.telemetry_manager->register_complex_register_data_provider("psu", "ac_input_current_c", + &psu_registers->ac_input_current_c); + + dispenser_config.telemetry_manager->register_complex_register_data_provider_enum( + "psu", "psu_running_mode", &psu_registers->psu_running_mode, + [](const PSURunningMode& mode) { return SettingPowerUnitRegisters::psu_running_mode_to_string(mode); }); + + dispenser_config.telemetry_manager->register_complex_register_data_provider( + "psu", "total_historic_input_energy", &psu_registers->total_historic_input_energy, + [](const double& kwh) { return kwh * 1000.0; }); } void Dispenser::update_psu_communication_state() { diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/raw.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/raw.hpp index f4a9491f50..7f1fd0d07d 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/raw.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/raw.hpp @@ -129,6 +129,7 @@ enum class PsuOutputPortAvailability : std::uint16_t { }; std::string working_status_to_string(const WorkingStatus& status); +std::string psu_output_port_availability_to_string(const PsuOutputPortAvailability& availability); class CollectedConnectorRegisters : public ComplexRegisterSubregistry { public: diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/src/raw.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/src/raw.cpp index 84fe145acf..d07281582f 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/src/raw.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/src/raw.cpp @@ -23,6 +23,18 @@ std::string fusion_charger::modbus_driver::raw_registers::working_status_to_stri } } +std::string fusion_charger::modbus_driver::raw_registers::psu_output_port_availability_to_string( + const PsuOutputPortAvailability& availability) { + switch (availability) { + case PsuOutputPortAvailability::AVAILABLE: + return "AVAILABLE"; + case PsuOutputPortAvailability::NOT_AVAILABLE: + return "NOT_AVAILABLE"; + default: + return "UNKNOWN"; + } +} + std::string fusion_charger::modbus_driver::raw_registers::SettingPowerUnitRegisters::psu_running_mode_to_string( const PSURunningMode& mode) { switch (mode) { diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/manifest.yaml b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/manifest.yaml index a41d7915da..953811b24b 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/manifest.yaml +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/manifest.yaml @@ -91,6 +91,12 @@ config: - IMD - OVM default: IMD + telemetry_topic_prefix: + description: | + MQTT Topic prefix for telemetry data published by this module. + Set to an empty string (default) to disable telemetry publishing. + type: string + default: "" provides: connector_1: description: Power supply interface for the first connector. @@ -188,6 +194,7 @@ requires: interface: over_voltage_monitor min_connections: 0 max_connections: 4 +enable_external_mqtt: true metadata: license: https://opensource.org/licenses/Apache-2.0 authors: diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/telemetry_manager_everest.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/telemetry_manager_everest.cpp new file mode 100644 index 0000000000..6ba7d13dfe --- /dev/null +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/telemetry_manager_everest.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest + +#include "telemetry_manager_everest.hpp" + +TelementryManagerEverest::TelementryManagerEverest( + std::function publish_func, const std::string& base_topic) : + publish_func(publish_func), base_topic(base_topic) { + publisher_thread = std::thread(&TelementryManagerEverest::publisher_thread_func, this); +} + +TelementryManagerEverest::~TelementryManagerEverest() { + stop_publisher_thread = true; + if (publisher_thread.joinable()) { + subtopics_changed_cv.notify_all(); + publisher_thread.join(); + } +} + +void TelementryManagerEverest::add_subtopic(const std::string& subtopic) { + if (subtopics_data.find(subtopic) == subtopics_data.end()) { + SubtopicDataEntry entry; + subtopics_data[subtopic] = entry; + } +} + +void TelementryManagerEverest::datapoint_changed(const std::string& subtopic, const std::string& datapoint, + const std::string& value) { + datapoint_changed_internal(subtopic, datapoint, value); +} + +void TelementryManagerEverest::datapoint_changed(const std::string& subtopic, const std::string& datapoint, + double value) { + datapoint_changed_internal(subtopic, datapoint, value); +} + +void TelementryManagerEverest::datapoint_changed(const std::string& subtopic, const std::string& datapoint, + bool value) { + datapoint_changed_internal(subtopic, datapoint, value); +} + +void TelementryManagerEverest::publisher_thread_func() { + for (;;) { + // await changes + { + std::unique_lock lock(subtopics_mutex); + subtopics_changed_cv.wait(lock, [this]() { + if (stop_publisher_thread) { + return true; + } + for (const auto& [subtopic, data_entry] : subtopics_data) { + if (data_entry.has_changes) { + return true; + } + } + return false; + }); + + if (stop_publisher_thread) { + return; + } + } + + // wait for a few ms to batch changes + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // publish changed subtopics + { + std::lock_guard lock(subtopics_mutex); + for (auto& [subtopic, subtopic_data_entry] : subtopics_data) { + if (subtopic_data_entry.has_changes) { + // publish data_entry.data_entries to telemetry system + publish_func(base_topic + "/" + subtopic, subtopic_data_entry.data_entries); + + // reset change flag + subtopic_data_entry.has_changes = false; + } + } + } + } +} + +bool TelementryManagerEverest::datapoint_exists(const std::string& subtopic, const std::string& datapoint) { + if (subtopics_data.find(subtopic) != subtopics_data.end()) { + std::lock_guard lock(subtopics_mutex); + return subtopics_data[subtopic].data_entries.find(datapoint) != subtopics_data[subtopic].data_entries.end(); + } + + return false; +} + +void TelementryManagerEverest::initialize_datapoint(const std::string& subtopic, const std::string& datapoint) { + if (subtopics_data.find(subtopic) != subtopics_data.end()) { + std::lock_guard lock(subtopics_mutex); + if (subtopics_data[subtopic].data_entries.find(datapoint) == subtopics_data[subtopic].data_entries.end()) { + subtopics_data[subtopic].data_entries[datapoint] = nullptr; + subtopics_data[subtopic].has_changes = true; + subtopics_changed_cv.notify_one(); + } + } +} diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/telemetry_manager_everest.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/telemetry_manager_everest.hpp new file mode 100644 index 0000000000..09929ef722 --- /dev/null +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/telemetry_manager_everest.hpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +class TelementryManagerEverest : public fusion_charger::telemetry::TelemetryManagerBase { + std::function publish_func; + std::string base_topic; + + struct SubtopicDataEntry { + nlohmann::json data_entries; + bool has_changes{false}; + }; + + std::mutex subtopics_mutex; + std::condition_variable subtopics_changed_cv; + + // key: subtopic, value: data entry + std::unordered_map subtopics_data; + + std::thread publisher_thread; + std::atomic stop_publisher_thread{false}; + + void publisher_thread_func(); + + template + void datapoint_changed_internal(const std::string& subtopic, const std::string& datapoint, T value) { + if (subtopics_data.find(subtopic) != subtopics_data.end()) { + std::lock_guard lock(subtopics_mutex); + + subtopics_data[subtopic].data_entries[datapoint] = value; + subtopics_data[subtopic].has_changes = true; + + subtopics_changed_cv.notify_one(); + } + } + +public: + TelementryManagerEverest(std::function publish_func, + const std::string& base_topic); + ~TelementryManagerEverest() override; + + void add_subtopic(const std::string& subtopic) override; + void datapoint_changed(const std::string& subtopic, const std::string& datapoint, + const std::string& value) override; + void datapoint_changed(const std::string& subtopic, const std::string& datapoint, double value) override; + void datapoint_changed(const std::string& subtopic, const std::string& datapoint, bool value) override; + bool datapoint_exists(const std::string& subtopic, const std::string& datapoint) override; + void initialize_datapoint(const std::string& subtopic, const std::string& datapoint) override; +}; From e7de7633f54f88f090955141710f93877b3c2c09 Mon Sep 17 00:00:00 2001 From: Mark Oude Elberink Date: Fri, 12 Dec 2025 14:07:20 +0000 Subject: [PATCH 2/9] feat: mock simulate missing telemetry data Signed-off-by: Mark Oude Elberink --- .../power_stack_mock/power_stack_mock.hpp | 9 +++++ .../include/power_stack_mock/util.hpp | 1 + .../power_stack_mock/lib/power_stack_mock.cpp | 24 +++++++++++++ .../power_stack_mock/lib/util.cpp | 12 +++++++ .../power_stack_mock/src/main.cpp | 36 +++++++++++++++++++ 5 files changed, 82 insertions(+) diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/include/power_stack_mock/power_stack_mock.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/include/power_stack_mock/power_stack_mock.hpp index 8f421d4c96..34294877c0 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/include/power_stack_mock/power_stack_mock.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/include/power_stack_mock/power_stack_mock.hpp @@ -91,6 +91,10 @@ class PowerStackMock { void send_max_rated_voltage_of_output_port(float voltage, std::uint16_t local_connector_number); void send_min_rated_voltage_of_output_port(float voltage, std::uint16_t local_connector_number); void send_rated_power_of_output_port(float power, std::uint16_t local_connector_number); + void send_total_historical_ac_input_energy(double energy); + void send_ac_input_voltages_currents(float voltage_a, float voltage_b, float voltage_c, float current_a, + float current_b, float current_c); + void send_port_available(bool available, std::uint16_t local_connector_number); std::optional get_last_power_requirement_request(std::uint16_t global_connector_number); @@ -109,6 +113,11 @@ class PowerStackMock { void set_enable_answer_module_placeholder_allocation(bool enable); + /** + * @brief get the global connector number from the local connector number (range 1-4) + */ + int get_global_connector_number_from_local(std::uint16_t local_connector_number); + private: PowerStackMockConfig config; goose_ethernet::EthernetInterface eth; diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/include/power_stack_mock/util.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/include/power_stack_mock/util.hpp index 642b13e31a..4ef373f38a 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/include/power_stack_mock/util.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/include/power_stack_mock/util.hpp @@ -23,6 +23,7 @@ float uint16_vec_to_float(std::vector vec); std::vector float_to_uint16_vec(float value); double uint16_vec_to_double(std::vector vec); +std::vector double_to_uint16_vec(double value); std::uint32_t uint16_vec_to_uint32(std::vector vec); diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/lib/power_stack_mock.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/lib/power_stack_mock.cpp index e677394deb..61d26557cc 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/lib/power_stack_mock.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/lib/power_stack_mock.cpp @@ -385,6 +385,25 @@ void PowerStackMock::send_rated_power_of_output_port(float power, std::uint16_t float_to_uint16_vec(power)); } +void PowerStackMock::send_total_historical_ac_input_energy(double energy) { + client.write_multiple_registers(0x2013, double_to_uint16_vec(energy)); +} + +void PowerStackMock::send_ac_input_voltages_currents(float voltage_a, float voltage_b, float voltage_c, float current_a, + float current_b, float current_c) { + client.write_multiple_registers(0x2007, float_to_uint16_vec(voltage_a)); + client.write_multiple_registers(0x2009, float_to_uint16_vec(voltage_b)); + client.write_multiple_registers(0x200B, float_to_uint16_vec(voltage_c)); + client.write_multiple_registers(0x200D, float_to_uint16_vec(current_a)); + client.write_multiple_registers(0x200F, float_to_uint16_vec(current_b)); + client.write_multiple_registers(0x2011, float_to_uint16_vec(current_c)); +} + +void PowerStackMock::send_port_available(bool available, std::uint16_t local_connector_number) { + client.write_single_register( + 0x212F + static_cast(offset_from_connector_number(local_connector_number)), available ? 1 : 0); +} + int PowerStackMock::open_socket(std::uint16_t port) { psu_printf("Waiting for modbus connection\n"); @@ -553,3 +572,8 @@ int PowerStackMock::client_socket() { void PowerStackMock::set_enable_answer_module_placeholder_allocation(bool enable) { answer_module_placeholder_allocation = enable; } + +int PowerStackMock::get_global_connector_number_from_local(std::uint16_t local_connector_number) { + return client.read_holding_registers( + 0x110E + static_cast(offset_from_connector_number(local_connector_number)), 1)[0]; +} diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/lib/util.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/lib/util.cpp index 4887e4a16e..08ab4208a7 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/lib/util.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/lib/util.cpp @@ -96,6 +96,18 @@ double uint16_vec_to_double(std::vector vec) { return *((double*)v0); } +std::vector double_to_uint16_vec(double value) { + std::uint8_t* v = reinterpret_cast(&value); + + std::vector out; + out.push_back(static_cast(v[7] << 8 | v[6])); + out.push_back(static_cast(v[5] << 8 | v[4])); + out.push_back(static_cast(v[3] << 8 | v[2])); + out.push_back(static_cast(v[1] << 8 | v[0])); + + return out; +} + std::uint32_t uint16_vec_to_uint32(std::vector vec) { std::uint16_t v0[2] = { static_cast(vec[1]), diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/src/main.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/src/main.cpp index e0f9dfed1c..a74e019577 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/src/main.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/power_stack_mock/src/main.cpp @@ -87,6 +87,38 @@ class Mock { int not_sending_capabilities_counter = 0; // used to test "capabilities not received" error + double mock_total_historical_ac_input_energy = 0.0; + + /** + * @brief generate voltage, current and total historic power values and send them to the dispenser + */ + void generate_and_send_voltage_current_power(int seconds_since_last_call) { + double used_power = 0.0; // power in W that is being drawn by the EV(s) + + for (int i = 1; i <= used_connectors; i++) { + int global_connector_number = mock->get_global_connector_number_from_local(i); + auto req_opt = mock->get_last_power_requirement_request(i); + if (req_opt.has_value()) { + auto req = req_opt.value(); + used_power += req.voltage * req.current; + } + } + + // add to total historical power + mock_total_historical_ac_input_energy += (used_power * seconds_since_last_call) / 3600.0 / 1000.0; // kWh + mock_total_historical_ac_input_energy += 0.00001; // add a bit more to simulate standby consumption + + double ac_base_voltage = 230.0; + ac_base_voltage += + static_cast((std::rand() % 256) / 10.0 - 12.8); // add noise between -12.8V and +12.7V + + double ac_base_current = used_power / ac_base_voltage / 3.0; // 3 phases + + mock->send_total_historical_ac_input_energy(mock_total_historical_ac_input_energy); + mock->send_ac_input_voltages_currents(ac_base_voltage, ac_base_voltage, ac_base_voltage, ac_base_current, + ac_base_current, ac_base_current); + } + void periodic_update() { auto now = std::chrono::steady_clock::now(); if (now < periodic_update_deadline) { @@ -112,10 +144,14 @@ class Mock { mock->send_max_rated_voltage_of_output_port(1000.0, i); mock->send_min_rated_voltage_of_output_port(100.0, i); mock->send_rated_power_of_output_port(60.0, i); + + mock->send_port_available(true, i); } } else { not_sending_capabilities_counter++; } + + generate_and_send_voltage_current_power(5); // called every 5 seconds, the approximation is good enough } std::array car_plugged_in = {false, false, false, false}; From 11037a93a01c00c37031d3c6296ec65fdc8e54a0 Mon Sep 17 00:00:00 2001 From: Mark Oude Elberink Date: Fri, 5 Dec 2025 14:34:37 +0000 Subject: [PATCH 3/9] feat: add BSP errors Signed-off-by: Mark Oude Elberink --- errors/evse_board_support.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/errors/evse_board_support.yaml b/errors/evse_board_support.yaml index 46c3a126b3..fb62c477ce 100644 --- a/errors/evse_board_support.yaml +++ b/errors/evse_board_support.yaml @@ -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: Tilted + description: The EVSE has been tilted beyond acceptable limits. + - name: Water + description: A substantial amount of water has been detected inside the EVSE. + - name: DoorOpen + description: The door or enclosure of the EVSE is open. - name: VendorError description: >- Vendor specific error code. Will stop charging session. @@ -57,4 +63,3 @@ errors: - name: CommunicationFault description: >- The communication to the hardware or underlying driver is lost or has errors. - From 4b0ac25387c62499376e18f6acce9e6ae497f702 Mon Sep 17 00:00:00 2001 From: Mark Oude Elberink Date: Fri, 5 Dec 2025 14:34:54 +0000 Subject: [PATCH 4/9] feat: add errors to YetiSimulator module Signed-off-by: Mark Oude Elberink --- modules/Simulation/YetiSimulator/util/errors.cpp | 9 +++++++++ modules/Simulation/YetiSimulator/util/errors.hpp | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/modules/Simulation/YetiSimulator/util/errors.cpp b/modules/Simulation/YetiSimulator/util/errors.cpp index 92a15d16b6..72abbb8dbf 100644 --- a/modules/Simulation/YetiSimulator/util/errors.cpp +++ b/modules/Simulation/YetiSimulator/util/errors.cpp @@ -80,6 +80,15 @@ std::tuple> parse_error_type(const std::str if (error_type == "MREC26CutCable") { return {raise, error_definitions::evse_board_support_MREC26CutCable}; } + if (error_type == "Tilted") { + return {raise, error_definitions::evse_board_support_Tilted}; + } + if (error_type == "Water") { + return {raise, error_definitions::evse_board_support_Water}; + } + if (error_type == "DoorOpen") { + return {raise, error_definitions::evse_board_support_DoorOpen}; + } if (error_type == "ac_rcd_MREC2GroundFailure") { return {raise, error_definitions::ac_rcd_MREC2GroundFailure}; } diff --git a/modules/Simulation/YetiSimulator/util/errors.hpp b/modules/Simulation/YetiSimulator/util/errors.hpp index 9c902341d6..3d02d57504 100644 --- a/modules/Simulation/YetiSimulator/util/errors.hpp +++ b/modules/Simulation/YetiSimulator/util/errors.hpp @@ -103,6 +103,13 @@ inline const auto evse_board_support_MREC25BrokenLatch = inline const auto evse_board_support_MREC26CutCable = ErrorDefinition{"evse_board_support/MREC26CutCable", "", "Simulated fault event"}; +inline const auto evse_board_support_Tilted = ErrorDefinition{"evse_board_support/Tilted", "", "Simulated fault event"}; + +inline const auto evse_board_support_Water = ErrorDefinition{"evse_board_support/Water", "", "Simulated fault event"}; + +inline const auto evse_board_support_DoorOpen = + ErrorDefinition{"evse_board_support/DoorOpen", "", "Simulated fault event"}; + inline const auto ac_rcd_VendorError = ErrorDefinition{"ac_rcd/VendorError", "", "Simulated fault event"}; inline const auto ac_rcd_Selftest = ErrorDefinition{"ac_rcd/Selftest", "", "Simulated fault event"}; From 35dcf91d4e32a56a602df9cac02d1d15d72edf7f Mon Sep 17 00:00:00 2001 From: Mark Oude Elberink Date: Fri, 5 Dec 2025 11:43:23 +0000 Subject: [PATCH 5/9] feat: add error handling for Dispenser alarms Signed-off-by: Mark Oude Elberink --- .../Huawei_V100R023C10/Huawei_V100R023C10.cpp | 56 +++++++++++++++++++ .../Huawei_V100R023C10/Huawei_V100R023C10.hpp | 4 ++ .../connector_base/base.cpp | 11 ++++ .../include/connector.hpp | 21 +++++-- .../include/dispenser.hpp | 22 ++++++++ .../lib/connector.cpp | 25 ++++++++- .../lib/dispenser.cpp | 45 ++++++++++++++- 7 files changed, 175 insertions(+), 9 deletions(-) diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp index e21f107a5c..b833ce7ac2 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp @@ -38,6 +38,21 @@ static std::vector get_connector_bases(Huawei_V100R023C10* mod, 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/DoorOpen"; + case DispenserAlarms::WATER_ALARM: + return "evse_board_support/Water"; + case DispenserAlarms::EPO_ALARM: + return "evse_board_support/MREC8EmergencyStop"; + case DispenserAlarms::TILT_ALARM: + return "evse_board_support/Tilted"; + default: + throw std::runtime_error("Unknown DispenserAlarm enum value"); + } +} + void Huawei_V100R023C10::init() { this->communication_fault_raised = false; this->psu_not_running_raised = false; @@ -128,6 +143,47 @@ void Huawei_V100R023C10::init() { } dispenser = std::make_unique(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{}); + } + 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()) { + 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() { diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp index 16c98d5201..f75f8bf724 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.hpp @@ -23,6 +23,7 @@ // insert your custom include headers here #include "telemetry_manager_everest.hpp" #include +#include #include // ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 @@ -98,6 +99,9 @@ class Huawei_V100R023C10 : public Everest::ModuleBase { std::vector 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> dispenser_alarms_per_bsp; + enum class UpstreamVoltageSource { IMD, OVM, diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp index 9b90d31408..1f8c6b7e2f 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp @@ -93,6 +93,17 @@ void ConnectorBase::ev_init() { } }); + 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); + EVLOG_info << "Received contactor fault error from BSP"; + }, + [this](const Everest::error::Error& error) { + get_connector()->set_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"); diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/connector.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/connector.hpp index 7b33d7e2ff..09d808e2ed 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/connector.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/connector.hpp @@ -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 do_unsolicitated_report_callback = []() {}); Connector(const Connector&) = delete; ~Connector(); @@ -182,21 +183,33 @@ 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 - std::uint16_t local_connector_number; // 1-4 + std::string log_prefix; // Prefix for log messages + uint16_t local_connector_number; // 1-4 ConnectorConfig connector_config; DispenserConfig dispenser_config; std::shared_ptr eth_interface; ConnectorGooseSender goose_sender; ConnectorRegistersConfig connector_registers_config; ConnectorRegisters connector_registers; + std::function do_unsolicitated_report_callback; // callback to dispenser to trigger an + // unsolicitated report std::optional rated_output_power_psu; // in kW, set by register callback std::optional max_rated_psu_voltage; // in V, set by register callback std::optional max_rated_psu_current; // in A, set by register callback + // persistant storage for the dc output contactor fault alarm state; used in + // start() to populate the register + std::optional dc_output_contactor_fault_alarm_active; + ConnectorFSM fsm; bool last_module_placeholder_allocation_failed; diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp index bd4eab7707..17e7d51674 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp @@ -46,6 +46,16 @@ typedef fusion_charger::modbus_driver::raw_registers::SettingPowerUnitRegisters: // category and subcategory typedef std::set ErrorEventSet; +enum class DispenserAlarms { + DOOR_STATUS_ALARM, + WATER_ALARM, + EPO_ALARM, + TILT_ALARM, +}; + +/// @brief Get a list of all possible DispenserAlarms +std::vector get_all_dispenser_alarms(); + class Dispenser { private: std::vector> connectors; @@ -82,6 +92,9 @@ class Dispenser { std::optional psu_running_mode = std::nullopt; + // mutex to call functions on the registry from multiple threads + std::mutex registry_mutex; + const int MAX_NUMBER_OF_CONNECTORS = 4; // true if the psu wrote its mac address via modbus @@ -136,4 +149,13 @@ class Dispenser { // Connector numbers start at 1 return connectors[local_connector_number - 1]; } + + /// @brief Trigger an unsolicitated report to be sent now. + void do_unsolicitated_report_now(); + + /// @brief Set state for a dispenser alarm. Also triggers an immediate + /// unsolicitated 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); }; diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp index c69135d2fc..8778f697bd 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp @@ -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 do_unsolicitated_report_callback) : connector_config(connector_config), local_connector_number(local_connector_number), dispenser_config(dispenser_config), @@ -199,7 +200,8 @@ Connector::Connector(ConnectorConfig connector_config, std::uint16_t local_conne std::placeholders::_1, std::placeholders::_2); return callbacks; }(), - log, log_prefix) { + log, log_prefix), + do_unsolicitated_report_callback(do_unsolicitated_report_callback) { } Connector::~Connector() { @@ -311,6 +313,11 @@ void Connector::start() { log.info << log_prefix + "PSU Max rated current changed to " + std::to_string(value) + " A"; }); + if (dc_output_contactor_fault_alarm_active.has_value()) { + connector_registers.dc_output_contact_fault.update_value(dc_output_contactor_fault_alarm_active.value() ? 1 + : 0); + } + // todo: reset fsm? goose_sender.start(); @@ -545,3 +552,15 @@ std::vector Connector::get_hmac_key() { const std::uint8_t* hmac_key = connector_registers.hmac_key.get_value(); // pointer to private memory return std::vector(hmac_key, hmac_key + connector_registers.hmac_key.get_size()); } + +void Connector::set_dc_output_contactor_fault_alarm(bool active) { + connector_registers.dc_output_contact_fault.update_value(active ? 1 : 0); + + // persisted to be set on start() + dc_output_contactor_fault_alarm_active = active; + + // immediately do an unsolicitated report + if (do_unsolicitated_report_callback) { + do_unsolicitated_report_callback(); + } +} diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp index 56e1d433cb..88579e6954 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp @@ -12,10 +12,16 @@ using namespace fusion_charger::modbus_driver::raw_registers; using namespace fusion_charger::modbus_driver; using namespace fusion_charger::modbus_extensions; +std::vector get_all_dispenser_alarms() { + return {DispenserAlarms::DOOR_STATUS_ALARM, DispenserAlarms::WATER_ALARM, + DispenserAlarms::EPO_ALARM, DispenserAlarms::TILT_ALARM}; +} + void Dispenser::modbus_unsolicitated_event_thread_run() { std::this_thread::sleep_for(std::chrono::seconds(1)); while (psu_communication_is_ok()) { try { + std::lock_guard lock(registry_mutex); auto req = registry->unsolicitated_report(); if (req.has_value()) { server->send_unsolicitated_report(req.value(), std::chrono::seconds(3)); @@ -178,8 +184,9 @@ Dispenser::Dispenser(DispenserConfig dispenser_config, std::vector connector = std::make_shared( - connector_configs[local_connector_number - 1], local_connector_number, dispenser_config, log); + std::shared_ptr connector = + std::make_shared(connector_configs[local_connector_number - 1], local_connector_number, + dispenser_config, log, [this]() { do_unsolicitated_report_now(); }); connectors.push_back(connector); } } @@ -305,6 +312,8 @@ PSURunningMode Dispenser::get_psu_running_mode() { } void Dispenser::init() { + std::lock_guard lock(registry_mutex); + log.info << "Using host, port and interface: " + dispenser_config.psu_host + ":" + std::to_string(dispenser_config.psu_port) + " % " + dispenser_config.eth_interface; @@ -494,3 +503,35 @@ bool Dispenser::is_stop_requested() { Dispenser::~Dispenser() { stop(); } + +void Dispenser::do_unsolicitated_report_now() { + std::lock_guard lock(registry_mutex); + if (registry.has_value()) { + auto req = registry->unsolicitated_report(); + if (req.has_value() && server.has_value()) { + server->send_unsolicitated_report(req.value(), std::chrono::seconds(3)); + } + } +} + +void Dispenser::set_dispenser_alarm(DispenserAlarms alarm, bool active) { + switch (alarm) { + case DispenserAlarms::DOOR_STATUS_ALARM: + dispenser_registers->door_status_alarm.update_value(active ? 1 : 0); + break; + case DispenserAlarms::WATER_ALARM: + dispenser_registers->water_alarm.update_value(active ? 1 : 0); + break; + case DispenserAlarms::EPO_ALARM: + dispenser_registers->epo_alarm.update_value(active ? 1 : 0); + break; + case DispenserAlarms::TILT_ALARM: + dispenser_registers->tilt_alarm.update_value(active ? 1 : 0); + break; + default: + log.error << "Unknown alarm type"; + break; + } + + do_unsolicitated_report_now(); +} From c6f939bc0d791d82ae4a264c96cccc747e3efa04 Mon Sep 17 00:00:00 2001 From: Mark Oude Elberink Date: Fri, 5 Dec 2025 14:54:15 +0000 Subject: [PATCH 6/9] doc: add doc to changelog + doc.rst Signed-off-by: Mark Oude Elberink --- .../Huawei_V100R023C10/CHANGELOG.md | 8 +++++++ .../PowerSupplies/Huawei_V100R023C10/doc.rst | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md index 6e6b190171..4ef30d8682 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md @@ -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 publishing some specific BSP errors to the PSU as dispenser and connector alarms: + - Per dispenser: + - `evse_board_support/DoorOpen` published as Door status alarm to the PSU + - `evse_board_support/Water` published as Water alarm + - `evse_board_support/MREC8EmergencyStop` published as EPO alarm + - `evse_board_support/Tilted` published as Tilt alarm + - Per connector: + - `evse_board_support/MREC17EVSEContactorFault` published as DC output contactor fault ## June 2025 diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst index c0c104400a..e5ece91031 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst @@ -79,6 +79,29 @@ The units are SI units (Amps, Volts, Watts, Watt-hours). All telemetry values can be null, indicating that no value has been received or sent yet. +BSP Errors +========== + +This driver supports publishing a few BSP errors to the Power supply unit as Dispenser and Conenctor Alarms: + ++-------------------------------------------------+---------------------------+---------------+ +| Everest BSP Error | PSU Modbus Register name | Scope | ++=================================================+===========================+===============+ +| ``evse_board_support/DoorOpen`` | Door status alarm | Dispenser | ++-------------------------------------------------+---------------------------+---------------+ +| ``evse_board_support/Water`` | Water alarm | Dispenser | ++-------------------------------------------------+---------------------------+---------------+ +| ``evse_board_support/MREC8EmergencyStop`` | EPO alarm | Dispenser | ++-------------------------------------------------+---------------------------+---------------+ +| ``evse_board_support/Tilted`` | 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 ================== From c8a59aa5cb9646c28462d2a599786905272817b5 Mon Sep 17 00:00:00 2001 From: Mark Oude Elberink Date: Mon, 8 Dec 2025 09:46:05 +0000 Subject: [PATCH 7/9] refactor: use callbacks for alarms Signed-off-by: Mark Oude Elberink --- .../include/connector.hpp | 6 +-- .../include/dispenser.hpp | 5 +++ .../lib/connector.cpp | 9 +--- .../lib/dispenser.cpp | 41 ++++++++++--------- .../modbus/registers/connector.hpp | 6 ++- .../modbus/registers/dispenser.hpp | 27 +++++++----- .../fusion_charger/modbus/registers/utils.hpp | 11 ++++- .../src/utils.cpp | 11 ++++- 8 files changed, 72 insertions(+), 44 deletions(-) diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/connector.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/connector.hpp index 09d808e2ed..5817738c50 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/connector.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/connector.hpp @@ -206,9 +206,9 @@ class Connector { std::optional max_rated_psu_voltage; // in V, set by register callback std::optional max_rated_psu_current; // in A, set by register callback - // persistant storage for the dc output contactor fault alarm state; used in - // start() to populate the register - std::optional dc_output_contactor_fault_alarm_active; + // dc output contactor fault alarm state; used directly in register read + // callback + std::atomic dc_output_contactor_fault_alarm_active; ConnectorFSM fsm; bool last_module_placeholder_allocation_failed; diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp index 17e7d51674..190ccf9dbe 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp @@ -87,9 +87,12 @@ class Dispenser { std::optional psu_registers; std::optional error_registers; + // Raised errors by the PSU ErrorEventSet raised_errors = {}; std::mutex raised_error_mutex; + std::unordered_map> dispenser_alarms; + std::optional psu_running_mode = std::nullopt; // mutex to call functions on the registry from multiple threads @@ -114,6 +117,8 @@ class Dispenser { bool psu_communication_is_ok(); bool is_stop_requested(); + bool get_dispenser_alarm_state(DispenserAlarms alarm); + public: Dispenser(DispenserConfig dispenser_config, std::vector connector_configs, logs::LogIntf log = logs::log_printf); diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp index 8778f697bd..55c3022a9e 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/connector.cpp @@ -188,6 +188,7 @@ Connector::Connector(ConnectorConfig connector_config, uint16_t local_connector_ 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), @@ -313,11 +314,6 @@ void Connector::start() { log.info << log_prefix + "PSU Max rated current changed to " + std::to_string(value) + " A"; }); - if (dc_output_contactor_fault_alarm_active.has_value()) { - connector_registers.dc_output_contact_fault.update_value(dc_output_contactor_fault_alarm_active.value() ? 1 - : 0); - } - // todo: reset fsm? goose_sender.start(); @@ -554,9 +550,6 @@ std::vector Connector::get_hmac_key() { } void Connector::set_dc_output_contactor_fault_alarm(bool active) { - connector_registers.dc_output_contact_fault.update_value(active ? 1 : 0); - - // persisted to be set on start() dc_output_contactor_fault_alarm_active = active; // immediately do an unsolicitated report diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp index 88579e6954..a5a724e0c9 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp @@ -354,6 +354,18 @@ void Dispenser::init() { dispenser_registers_config.software_version = dispenser_config.software_version; dispenser_registers_config.esn = dispenser_config.esn; dispenser_registers_config.connector_count = dispenser_config.charging_connector_count; + dispenser_registers_config.get_door_status_alarm = [this]() { + return get_dispenser_alarm_state(DispenserAlarms::DOOR_STATUS_ALARM); + }; + dispenser_registers_config.get_water_alarm = [this]() { + return get_dispenser_alarm_state(DispenserAlarms::WATER_ALARM); + }; + dispenser_registers_config.get_epo_alarm = [this]() { + return get_dispenser_alarm_state(DispenserAlarms::EPO_ALARM); + }; + dispenser_registers_config.get_tilt_alarm = [this]() { + return get_dispenser_alarm_state(DispenserAlarms::TILT_ALARM); + }; dispenser_registers.emplace(dispenser_registers_config); @@ -515,23 +527,14 @@ void Dispenser::do_unsolicitated_report_now() { } void Dispenser::set_dispenser_alarm(DispenserAlarms alarm, bool active) { - switch (alarm) { - case DispenserAlarms::DOOR_STATUS_ALARM: - dispenser_registers->door_status_alarm.update_value(active ? 1 : 0); - break; - case DispenserAlarms::WATER_ALARM: - dispenser_registers->water_alarm.update_value(active ? 1 : 0); - break; - case DispenserAlarms::EPO_ALARM: - dispenser_registers->epo_alarm.update_value(active ? 1 : 0); - break; - case DispenserAlarms::TILT_ALARM: - dispenser_registers->tilt_alarm.update_value(active ? 1 : 0); - break; - default: - log.error << "Unknown alarm type"; - break; - } - - do_unsolicitated_report_now(); + dispenser_alarms[alarm] = active; + + do_unsolicitated_report_now(); +} + +bool Dispenser::get_dispenser_alarm_state(DispenserAlarms alarm) { + if (this->dispenser_alarms.find(alarm) == this->dispenser_alarms.end()) { + this->dispenser_alarms[alarm] = false; + } + return this->dispenser_alarms[alarm].load(); } diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/connector.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/connector.hpp index 519dd77f12..e9df44c6b4 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/connector.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/connector.hpp @@ -28,6 +28,7 @@ struct ConnectorRegistersConfig { std::function get_output_current; std::function get_contactor_status; std::function get_electronic_lock_status; + std::function get_dc_output_contact_fault; }; struct ConnectorRegisters { @@ -72,7 +73,7 @@ struct ConnectorRegisters { DataProviderHolding psu_port_available; // alarms - DataProviderHoldingUnsolicitatedReportCallback dc_output_contact_fault; + DataProviderCallbacksUnsolicitated dc_output_contact_fault; DataProviderHoldingUnsolicitatedReportCallback inverse_connection_dispenser_inlet_cable; /** @@ -113,7 +114,8 @@ struct ConnectorRegisters { hmac_key(), rated_output_power_psu(0), psu_port_available(PsuOutputPortAvailability::NOT_AVAILABLE), - dc_output_contact_fault(0, utils::always_report), + dc_output_contact_fault(utils::wrap_alarm_register_func(config.get_dc_output_contact_fault), + utils::ignore_write, utils::always_report), inverse_connection_dispenser_inlet_cable(0, utils::always_report) { } diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/dispenser.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/dispenser.hpp index efc8a6de2b..33a38a7fa6 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/dispenser.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/dispenser.hpp @@ -18,6 +18,10 @@ struct DispenserRegistersConfig { std::string software_version; std::string esn; std::uint32_t connector_count; + std::function get_door_status_alarm; + std::function get_water_alarm; + std::function get_epo_alarm; + std::function get_tilt_alarm; }; struct DispenserRegisters { @@ -30,10 +34,10 @@ struct DispenserRegisters { DataProviderHolding charging_connectors_count; DataProviderStringHolding<22> esn_dispenser; DataProviderCallbacksUnsolicitated time_sync; - DataProviderHoldingUnsolicitatedReportCallback door_status_alarm; - DataProviderHoldingUnsolicitatedReportCallback water_alarm; - DataProviderHoldingUnsolicitatedReportCallback epo_alarm; - DataProviderHoldingUnsolicitatedReportCallback tilt_alarm; + DataProviderCallbacksUnsolicitated door_status_alarm; + DataProviderCallbacksUnsolicitated water_alarm; + DataProviderCallbacksUnsolicitated epo_alarm; + DataProviderCallbacksUnsolicitated tilt_alarm; DispenserRegisters(DispenserRegistersConfig config) : manufacturer(config.manufacturer), @@ -44,13 +48,16 @@ struct DispenserRegisters { charging_connectors_count(config.connector_count), esn_dispenser(config.esn.c_str()), - time_sync([]() { return std::time(NULL); }, [](std::uint32_t) {}, utils::always_report), - door_status_alarm(0, utils::always_report), - water_alarm(0, utils::always_report), - epo_alarm(0, utils::always_report), - tilt_alarm(0, utils::always_report) { + time_sync([]() { return std::time(NULL); }, utils::ignore_write, utils::always_report), + door_status_alarm(utils::wrap_alarm_register_func(config.get_door_status_alarm), + utils::ignore_write, utils::always_report), + water_alarm(utils::wrap_alarm_register_func(config.get_water_alarm), utils::ignore_write, + utils::always_report), + epo_alarm(utils::wrap_alarm_register_func(config.get_epo_alarm), utils::ignore_write, + utils::always_report), + tilt_alarm(utils::wrap_alarm_register_func(config.get_tilt_alarm), utils::ignore_write, + utils::always_report) { } - void add_to_registry(modbus::registers::registry::ComplexRegisterRegistry& registry) { raw_registers::CommonDispenserRegisters::DataProviders common_data_providers{ manufacturer, model, protocol_version, hardware_version, software_version}; diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/utils.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/utils.hpp index 060651ab91..0662b33570 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/utils.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/include/fusion_charger/modbus/registers/utils.hpp @@ -2,7 +2,8 @@ // Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest #pragma once -#include +#include +#include namespace fusion_charger::modbus_driver { namespace utils { @@ -12,5 +13,13 @@ bool always_report(); template void ignore_write(T) { } +/** + * @brief Wraps a function that returns a boolean into a function that returns a + * uint16_t value, where true maps to 1 and false maps to 0. + * @param func The function to wrap. + * @return A function that calls \c func and returns uint16_t. + */ +std::function wrap_alarm_register_func(const std::function& func); + } // namespace utils } // namespace fusion_charger::modbus_driver diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/src/utils.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/src/utils.cpp index dd00df3add..d69b508735 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/src/utils.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/huawei-fusioncharge-driver/libs/fusion_charger_modbus_driver/src/utils.cpp @@ -2,6 +2,15 @@ // Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest #include -bool fusion_charger::modbus_driver::utils::always_report() { +namespace fusion_charger::modbus_driver { +namespace utils { + +bool always_report() { return true; } +std::function wrap_alarm_register_func(const std::function& func) { + return [func]() { return static_cast(func() ? 1 : 0); }; +} + +}; // namespace utils +}; // namespace fusion_charger::modbus_driver From bd7e067feaffac3afd85ad36aa40b48afd9afd48 Mon Sep 17 00:00:00 2001 From: Mark Oude Elberink Date: Thu, 11 Dec 2025 09:56:23 +0000 Subject: [PATCH 8/9] feat: add error telemetry Signed-off-by: Mark Oude Elberink --- .../connector_base/base.cpp | 4 +++ .../PowerSupplies/Huawei_V100R023C10/doc.rst | 12 +++++++ .../include/dispenser.hpp | 5 +++ .../lib/dispenser.cpp | 32 +++++++++++++++++-- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp index 1f8c6b7e2f..c4b24e0d4d 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/connector_base/base.cpp @@ -93,14 +93,18 @@ 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"; }); diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst index e5ece91031..2587216c9a 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst @@ -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, @@ -73,6 +74,17 @@ 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:: diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp index 190ccf9dbe..ac353e2c27 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/include/dispenser.hpp @@ -100,6 +100,8 @@ class Dispenser { const int MAX_NUMBER_OF_CONNECTORS = 4; + static const std::string DISPENSER_TELEMETRY_ALARMS_SUBTOPIC; + // 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 @@ -119,6 +121,9 @@ class Dispenser { 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 connector_configs, logs::LogIntf log = logs::log_printf); diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp index a5a724e0c9..70e31de4cc 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/fusion_charger_lib/fusion-charger-dispenser-library/lib/dispenser.cpp @@ -12,9 +12,11 @@ using namespace fusion_charger::modbus_driver::raw_registers; using namespace fusion_charger::modbus_driver; using namespace fusion_charger::modbus_extensions; +const std::string Dispenser::DISPENSER_TELEMETRY_ALARMS_SUBTOPIC = "dispenser/published_alarms"; + std::vector get_all_dispenser_alarms() { - return {DispenserAlarms::DOOR_STATUS_ALARM, DispenserAlarms::WATER_ALARM, - DispenserAlarms::EPO_ALARM, DispenserAlarms::TILT_ALARM}; + return {DispenserAlarms::DOOR_STATUS_ALARM, DispenserAlarms::WATER_ALARM, DispenserAlarms::EPO_ALARM, + DispenserAlarms::TILT_ALARM}; } void Dispenser::modbus_unsolicitated_event_thread_run() { @@ -482,6 +484,14 @@ void Dispenser::init() { dispenser_config.telemetry_manager->register_complex_register_data_provider( "psu", "total_historic_input_energy", &psu_registers->total_historic_input_energy, [](const double& kwh) { return kwh * 1000.0; }); + + // publish alarms + dispenser_config.telemetry_manager->add_subtopic(DISPENSER_TELEMETRY_ALARMS_SUBTOPIC); + + for (auto alarm : get_all_dispenser_alarms()) { + dispenser_config.telemetry_manager->initialize_datapoint(DISPENSER_TELEMETRY_ALARMS_SUBTOPIC, + dispenser_alarm_to_telemetry_datapoint(alarm), false); + } } void Dispenser::update_psu_communication_state() { @@ -529,6 +539,9 @@ void Dispenser::do_unsolicitated_report_now() { void Dispenser::set_dispenser_alarm(DispenserAlarms alarm, bool active) { dispenser_alarms[alarm] = active; + dispenser_config.telemetry_manager->datapoint_changed(DISPENSER_TELEMETRY_ALARMS_SUBTOPIC, + dispenser_alarm_to_telemetry_datapoint(alarm), active); + do_unsolicitated_report_now(); } @@ -538,3 +551,18 @@ bool Dispenser::get_dispenser_alarm_state(DispenserAlarms alarm) { } return this->dispenser_alarms[alarm].load(); } + +std::string Dispenser::dispenser_alarm_to_telemetry_datapoint(DispenserAlarms alarm) { + switch (alarm) { + case DispenserAlarms::DOOR_STATUS_ALARM: + return "door_status_alarm"; + case DispenserAlarms::WATER_ALARM: + return "water_alarm"; + case DispenserAlarms::EPO_ALARM: + return "epo_alarm"; + case DispenserAlarms::TILT_ALARM: + return "tilt_alarm"; + default: + throw std::runtime_error("Unknown dispenser alarm"); + } +} From 6eb55798568982e7ebce68965ac74dc273d9fc3d Mon Sep 17 00:00:00 2001 From: Mark Oude Elberink Date: Fri, 12 Dec 2025 14:56:58 +0000 Subject: [PATCH 9/9] chore: rename bsp errors Signed-off-by: Mark Oude Elberink --- errors/evse_board_support.yaml | 8 ++++---- .../PowerSupplies/Huawei_V100R023C10/CHANGELOG.md | 6 +++--- .../Huawei_V100R023C10/Huawei_V100R023C10.cpp | 6 +++--- .../PowerSupplies/Huawei_V100R023C10/doc.rst | 6 +++--- modules/Simulation/YetiSimulator/util/errors.cpp | 12 ++++++------ modules/Simulation/YetiSimulator/util/errors.hpp | 10 ++++++---- 6 files changed, 25 insertions(+), 23 deletions(-) diff --git a/errors/evse_board_support.yaml b/errors/evse_board_support.yaml index fb62c477ce..d7e18ad0da 100644 --- a/errors/evse_board_support.yaml +++ b/errors/evse_board_support.yaml @@ -48,12 +48,12 @@ errors: description: The latch on the connector is broken. - name: MREC26CutCable description: The output cable has been severed from the EVSE. - - name: Tilted + - name: TiltDetected description: The EVSE has been tilted beyond acceptable limits. - - name: Water + - name: WaterIngressDetected description: A substantial amount of water has been detected inside the EVSE. - - name: DoorOpen - description: The door or enclosure of the EVSE is open. + - 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. diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md index 4ef30d8682..98d98387ba 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/CHANGELOG.md @@ -7,10 +7,10 @@ - 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 publishing some specific BSP errors to the PSU as dispenser and connector alarms: - Per dispenser: - - `evse_board_support/DoorOpen` published as Door status alarm to the PSU - - `evse_board_support/Water` published as Water alarm + - `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/Tilted` published as Tilt alarm + - `evse_board_support/TiltDetected` published as Tilt alarm - Per connector: - `evse_board_support/MREC17EVSEContactorFault` published as DC output contactor fault diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp index b833ce7ac2..f66f28433d 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/Huawei_V100R023C10.cpp @@ -41,13 +41,13 @@ static std::vector get_connector_bases(Huawei_V100R023C10* mod, static std::string get_everest_error_for_dispenser_alarm(DispenserAlarms alarm) { switch (alarm) { case DispenserAlarms::DOOR_STATUS_ALARM: - return "evse_board_support/DoorOpen"; + return "evse_board_support/EnclosureOpen"; case DispenserAlarms::WATER_ALARM: - return "evse_board_support/Water"; + return "evse_board_support/WaterIngressDetected"; case DispenserAlarms::EPO_ALARM: return "evse_board_support/MREC8EmergencyStop"; case DispenserAlarms::TILT_ALARM: - return "evse_board_support/Tilted"; + return "evse_board_support/TiltDetected"; default: throw std::runtime_error("Unknown DispenserAlarm enum value"); } diff --git a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst index 2587216c9a..755186b722 100644 --- a/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst +++ b/modules/HardwareDrivers/PowerSupplies/Huawei_V100R023C10/doc.rst @@ -99,13 +99,13 @@ This driver supports publishing a few BSP errors to the Power supply unit as Dis +-------------------------------------------------+---------------------------+---------------+ | Everest BSP Error | PSU Modbus Register name | Scope | +=================================================+===========================+===============+ -| ``evse_board_support/DoorOpen`` | Door status alarm | Dispenser | +| ``evse_board_support/EnclosureOpen`` | Door status alarm | Dispenser | +-------------------------------------------------+---------------------------+---------------+ -| ``evse_board_support/Water`` | Water alarm | Dispenser | +| ``evse_board_support/WaterIngressDetected`` | Water alarm | Dispenser | +-------------------------------------------------+---------------------------+---------------+ | ``evse_board_support/MREC8EmergencyStop`` | EPO alarm | Dispenser | +-------------------------------------------------+---------------------------+---------------+ -| ``evse_board_support/Tilted`` | Tilt alarm | Dispenser | +| ``evse_board_support/TiltDetected`` | Tilt alarm | Dispenser | +-------------------------------------------------+---------------------------+---------------+ | ``evse_board_support/MREC17EVSEContactorFault`` | DC output contactor fault | Per Connector | +-------------------------------------------------+---------------------------+---------------+ diff --git a/modules/Simulation/YetiSimulator/util/errors.cpp b/modules/Simulation/YetiSimulator/util/errors.cpp index 72abbb8dbf..9249458de5 100644 --- a/modules/Simulation/YetiSimulator/util/errors.cpp +++ b/modules/Simulation/YetiSimulator/util/errors.cpp @@ -80,14 +80,14 @@ std::tuple> parse_error_type(const std::str if (error_type == "MREC26CutCable") { return {raise, error_definitions::evse_board_support_MREC26CutCable}; } - if (error_type == "Tilted") { - return {raise, error_definitions::evse_board_support_Tilted}; + if (error_type == "TiltDetected") { + return {raise, error_definitions::evse_board_support_TiltDetected}; } - if (error_type == "Water") { - return {raise, error_definitions::evse_board_support_Water}; + if (error_type == "WaterIngressDetected") { + return {raise, error_definitions::evse_board_support_WaterIngressDetected}; } - if (error_type == "DoorOpen") { - return {raise, error_definitions::evse_board_support_DoorOpen}; + if (error_type == "EnclosureOpen") { + return {raise, error_definitions::evse_board_support_EnclosureOpen}; } if (error_type == "ac_rcd_MREC2GroundFailure") { return {raise, error_definitions::ac_rcd_MREC2GroundFailure}; diff --git a/modules/Simulation/YetiSimulator/util/errors.hpp b/modules/Simulation/YetiSimulator/util/errors.hpp index 3d02d57504..6906fbac11 100644 --- a/modules/Simulation/YetiSimulator/util/errors.hpp +++ b/modules/Simulation/YetiSimulator/util/errors.hpp @@ -103,12 +103,14 @@ inline const auto evse_board_support_MREC25BrokenLatch = inline const auto evse_board_support_MREC26CutCable = ErrorDefinition{"evse_board_support/MREC26CutCable", "", "Simulated fault event"}; -inline const auto evse_board_support_Tilted = ErrorDefinition{"evse_board_support/Tilted", "", "Simulated fault event"}; +inline const auto evse_board_support_TiltDetected = + ErrorDefinition{"evse_board_support/TiltDetected", "", "Simulated fault event"}; -inline const auto evse_board_support_Water = ErrorDefinition{"evse_board_support/Water", "", "Simulated fault event"}; +inline const auto evse_board_support_WaterIngressDetected = + ErrorDefinition{"evse_board_support/WaterIngressDetected", "", "Simulated fault event"}; -inline const auto evse_board_support_DoorOpen = - ErrorDefinition{"evse_board_support/DoorOpen", "", "Simulated fault event"}; +inline const auto evse_board_support_EnclosureOpen = + ErrorDefinition{"evse_board_support/EnclosureOpen", "", "Simulated fault event"}; inline const auto ac_rcd_VendorError = ErrorDefinition{"ac_rcd/VendorError", "", "Simulated fault event"};