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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/HardwareDrivers/PowerSupplies/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ev_add_module(DPM1000)
ev_add_module(Huawei_R100040Gx)
ev_add_module(Huawei_V100R023C10)
ev_add_module(UUGreenPower_UR1000X0)
ev_add_module(InfyPower)
ev_add_module(InfyPower_BEG1K075G)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Changelog

## June 2025

- Module
- The module now verifies the HMAC of received goose messages by default (this was not the case before). This can be disabled with the module config `verify_secure_goose: false`
- The modules' goose security options are now finer grained. `secure_goose` has been split into `send_secure_goose`, which controls the security of outgoing messages, `allow_insecure_goose` and `verify_secure_goose`, which control the security of incoming messages. See manifest.yaml for more details
- Module allocation failure (including module allocation response timeout) is now treated as a warning instead of an error
- Some info messages have been changed to debug messages to reduce log noise
- Capabilities are now used from the Powersupply instead of hardcoded values. An error is raised as long as the capabilities are not set by the Powersupply. When the powersupply communication fails, the stored capabilities are cleared and the error is raised again.
- The Ethernet socket now filters the received packages on kernel level to improve performance
- Adds a hack to use voltage readings from a over voltage monitor during cable check. For this enough voltage monitors must be configured and the config option `HACK_use_ovm_while_cable_check` must be enabled.
- Adds `upstream_voltage_source` config option to select which upstream voltage source to use.
- Mock
- The mock can now accept multiple connections to simulate multiple dispensers
- The mock has new options to enable or disable the security of incoming and outgoing goose messages. These are accessible via the environment variables `FUSION_CHARGER_MOCK_DISABLE_SEND_HMAC` and `FUSION_CHARGER_MOCK_DISABLE_VERIFY_HMAC`
- The mock has a new option to change the ethernet interface used for goose messages. This is accessible via the environment variable `FUSION_CHARGER_MOCK_ETH`
- A bug that caused the mock to use the wrong hmac key has been fixed
- The mock now waits 5 seconds before sending the capabilities to reflect the real hardware behavior better
- The mock can now send received power requirements to a mqtt broker. For this, set hte environment variables `FUSION_CHARGER_MOCK_MQTT_HOST` and `FUSION_CHARGER_MOCK_MQTT_PORT` and optionally `FUSION_CHARGER_MOCK_MQTT_BASE_TOPIC` (defaults to `fusion_charger_mock/`). The mock then publishes under `{base_topic}/{global connector number}/power_request` a json object with `{"voltage": <voltage>, "current": <current>}`
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
# template version 3
#

# module setup:
# - ${MODULE_NAME}: module name
ev_setup_cpp_module()

# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
# insert your custom targets and additional config variables here

target_link_libraries(${MODULE_NAME}
PRIVATE
fusion_charger_dispenser
)

target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1

target_sources(${MODULE_NAME}
PRIVATE
"connector_1/power_supply_DCImpl.cpp"
"connector_2/power_supply_DCImpl.cpp"
"connector_3/power_supply_DCImpl.cpp"
"connector_4/power_supply_DCImpl.cpp"
)

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

option(INSTALL_FUSION_CHARGER_MOCK "Install fusion charger mock" OFF)
if(INSTALL_FUSION_CHARGER_MOCK)
install(TARGETS fusion_charger_mock)
endif()
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest

#include "Huawei_V100R023C10.hpp"
#include "connector_1/power_supply_DCImpl.hpp"
#include "connector_2/power_supply_DCImpl.hpp"
#include "connector_3/power_supply_DCImpl.hpp"
#include "connector_4/power_supply_DCImpl.hpp"

namespace module {

static ConnectorBase* get_connector_impl(Huawei_V100R023C10* mod, std::uint8_t connector) {
switch (connector) {
case 0:
return &(dynamic_cast<connector_1::power_supply_DCImpl*>(mod->p_connector_1.get()))->base;
break;
case 1:
return &(dynamic_cast<connector_2::power_supply_DCImpl*>(mod->p_connector_2.get()))->base;
break;
case 2:
return &(dynamic_cast<connector_3::power_supply_DCImpl*>(mod->p_connector_3.get()))->base;
break;
case 3:
return &(dynamic_cast<connector_4::power_supply_DCImpl*>(mod->p_connector_4.get()))->base;
break;
default:
throw std::runtime_error("Connector number out of bounds (expected 0-3): " + std::to_string(connector));

break;
}
}

static std::vector<ConnectorBase*> get_connector_bases(Huawei_V100R023C10* mod, std::uint8_t connectors_used) {
std::vector<ConnectorBase*> connector_bases;
for (std::uint8_t i = 0; i < connectors_used; i++) {
connector_bases.push_back(get_connector_impl(mod, i));
}
return connector_bases;
}

void Huawei_V100R023C10::init() {
this->communication_fault_raised = false;
this->psu_not_running_raised = false;
this->initial_hmac_acquired = false;

number_of_connectors_used = this->r_board_support.size();
if (number_of_connectors_used > 4) {
throw std::runtime_error("Got more board support modules than connectors supported");
}

EVLOG_info << "Assuming number of connectors used = " << number_of_connectors_used
<< " (based on number of connected board support modules)";

if (config.upstream_voltage_source == "IMD") {
upstream_voltage_source = Huawei_V100R023C10::UpstreamVoltageSource::IMD;
} else if (config.upstream_voltage_source == "OVM") {
upstream_voltage_source = Huawei_V100R023C10::UpstreamVoltageSource::OVM;
} else {
EVLOG_AND_THROW(std::runtime_error("Invalid upstream voltage source: " + config.upstream_voltage_source));
}

bool imds_necessary = upstream_voltage_source == UpstreamVoltageSource::IMD;
bool ovms_necessary = upstream_voltage_source == UpstreamVoltageSource::OVM ||
config.HACK_use_ovm_while_cable_check; // note that if the hack is enabled we also need OVMs

if (this->r_carside_powermeter.size() != 0 and this->r_carside_powermeter.size() != number_of_connectors_used) {
EVLOG_AND_THROW(std::runtime_error(
"Either use no carside powermeters or use the same number of powermeters as connectors in use"));
}
if (imds_necessary and this->r_isolation_monitor.size() != number_of_connectors_used) {
EVLOG_AND_THROW(
std::runtime_error("IMDs are necessary but number of IMDs does not match number of connectors in use"));
}
if (ovms_necessary and this->r_over_voltage_monitor.size() != number_of_connectors_used) {
EVLOG_AND_THROW(
std::runtime_error("OVMs are necessary but number of OVMs does not match number of connectors in use"));
}

// 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]);
}

DispenserConfig dispenser_config;
dispenser_config.psu_host = config.psu_ip;
dispenser_config.psu_port = (std::uint16_t)config.psu_port;
dispenser_config.eth_interface = config.ethernet_interface;
// fixed
dispenser_config.manufacturer = 0x02;
dispenser_config.model = 0x80;
dispenser_config.charging_connector_count = number_of_connectors_used;
// end fixed

dispenser_config.esn = config.esn;
dispenser_config.send_secure_goose = config.send_secure_goose;
dispenser_config.allow_unsecured_goose = config.allow_insecure_goose;
dispenser_config.verify_secure_goose_hmac = config.verify_secure_goose;
dispenser_config.module_placeholder_allocation_timeout =
std::chrono::seconds(config.module_placeholder_allocation_timeout_s);

if (config.tls_enabled) {
tls_util::MutualTlsClientConfig mutual_tls_config;
mutual_tls_config.ca_cert = config.psu_ca_cert;
mutual_tls_config.client_cert = config.client_cert;
mutual_tls_config.client_key = config.client_key;
dispenser_config.tls_config = mutual_tls_config;
}

logs::LogIntf log{logs::LogFun([](const std::string& message) { EVLOG_error << message; }),
logs::LogFun([](const std::string& message) { EVLOG_warning << message; }),
logs::LogFun([](const std::string& message) { EVLOG_info << message; }),
logs::LogFun([](const std::string& message) { EVLOG_debug << message; }),
logs::LogFun([](const std::string& message) { EVLOG_verbose << message; })};

std::vector<ConnectorConfig> connector_configs;
for (auto& connector : get_connector_bases(this, number_of_connectors_used)) {
connector_configs.push_back(connector->get_connector_config());
}

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

void Huawei_V100R023C10::ready() {
this->dispenser->start();

for (int i = 0; i < number_of_connectors_used; i++) {
invoke_ready(*implementations[i]);
}

for (;;) {
if (this->dispenser->get_psu_running_mode() == PSURunningMode::RUNNING && !initial_hmac_acquired) {
acquire_initial_hmac_keys_for_all_connectors();
initial_hmac_acquired = true;
}

update_psu_not_running_error();
update_communication_errors();
update_vendor_errors();
restart_dispenser_if_needed();

std::this_thread::sleep_for(std::chrono::seconds(1));
}
}

void Huawei_V100R023C10::acquire_initial_hmac_keys_for_all_connectors() {
std::vector<std::thread> threads;
for (int i = 0; i < number_of_connectors_used; i++) {
threads.push_back(std::thread([this, i] { get_connector_impl(this, i)->do_init_hmac_acquire(); }));
}

for (auto& thread : threads) {
thread.join();
}
}

void Huawei_V100R023C10::update_communication_errors() {
auto connector_bases = get_connector_bases(this, number_of_connectors_used);

if (this->dispenser->get_psu_communication_state() != DispenserPsuCommunicationState::READY) {
if (!psu_not_running_raised) {
for (auto& connector : connector_bases) {
connector->raise_communication_fault();
}
psu_not_running_raised = true;
}
} else {
if (psu_not_running_raised) {
for (auto& connector : connector_bases) {
connector->clear_communication_fault();
}
psu_not_running_raised = false;
}
}
}

void Huawei_V100R023C10::update_psu_not_running_error() {
auto connector_bases = get_connector_bases(this, number_of_connectors_used);

if (this->dispenser->get_psu_running_mode() != PSURunningMode::RUNNING) {
if (!communication_fault_raised) {
for (auto& connector : connector_bases) {
connector->raise_psu_not_running();
}
communication_fault_raised = true;
}
} else {
if (communication_fault_raised) {
for (auto& connector : connector_bases) {
connector->clear_psu_not_running();
}
communication_fault_raised = false;
}
}
}

void Huawei_V100R023C10::restart_dispenser_if_needed() {
if (this->dispenser->get_psu_communication_state() == DispenserPsuCommunicationState::FAILED) {
// Clear the stored capabilities in all connectors so that the missing cababilities error is raised
// until we get new capabilities
for (auto& connector : get_connector_bases(this, number_of_connectors_used)) {
connector->clear_stored_capabilities();
}
EVLOG_info << "Dispenser: restarting communication (stopping first)";
this->dispenser->stop();
EVLOG_info << "Dispenser: starting communications again";
this->dispenser->start();
}
}

void Huawei_V100R023C10::update_vendor_errors() {
auto connector_bases = get_connector_bases(this, number_of_connectors_used);
auto new_error_set = this->dispenser->get_raised_errors();

ErrorEventSet new_raised_errors;
std::set_difference(new_error_set.begin(), new_error_set.end(), raised_errors.begin(), raised_errors.end(),
std::inserter(new_raised_errors, new_raised_errors.begin()));

for (auto raised_error : new_raised_errors) {
for (auto& connector : connector_bases) {
connector->raise_psu_error(raised_error);
}
}

ErrorEventSet new_cleared_errors;
std::set_difference(raised_errors.begin(), raised_errors.end(), new_error_set.begin(), new_error_set.end(),
std::inserter(new_cleared_errors, new_cleared_errors.begin()));

for (auto cleared_error : new_cleared_errors) {
for (auto& connector : connector_bases) {
connector->clear_psu_error(cleared_error);
}
}

ErrorEventSet errors_intersection;
std::set_intersection(raised_errors.begin(), raised_errors.end(), new_error_set.begin(), new_error_set.end(),
std::inserter(errors_intersection, errors_intersection.begin()));

ErrorEventSet changed_errors;
for (auto error : errors_intersection) {
auto old_error = raised_errors.find(error);
auto new_error = new_error_set.find(error);

if (old_error->payload.raw != new_error->payload.raw) {
for (auto& connector : connector_bases) {
connector->clear_psu_error(*old_error);
connector->raise_psu_error(*new_error);
}
}
}

raised_errors = new_error_set;
}

} // namespace module
Loading
Loading