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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions errors/evse_manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ errors:
description: Internal error of the state machine
- name: MREC4OverCurrentFailure
description: Over current event
- name: MREC5OverVoltage
description: Over voltage event
- name: MREC9AuthorizationTimeout
description: No authorization was provided within timeout after plugin
- name: PowermeterTransactionStartFailed
Expand Down
1 change: 1 addition & 0 deletions modules/EVSE/EvseManager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ target_sources(${MODULE_NAME}
ErrorHandling.cpp
backtrace.cpp
PersistentStore.cpp
over_voltage/OverVoltageMonitor.cpp
)

target_link_libraries(${MODULE_NAME}
Expand Down
7 changes: 7 additions & 0 deletions modules/EVSE/EvseManager/ErrorHandling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ void ErrorHandling::clear_overcurrent_error() {
process_error();
}

void ErrorHandling::raise_over_voltage_error(Everest::error::Severity severity, const std::string& description) {
Everest::error::Error error_object =
p_evse->error_factory->create_error("evse_manager/MREC5OverVoltage", "", description, severity);
p_evse->raise_error(error_object);
process_error();
}

// Find out if the current error set is fatal to charging or not
void ErrorHandling::process_error() {
const auto fatal = errors_prevent_charging();
Expand Down
2 changes: 2 additions & 0 deletions modules/EVSE/EvseManager/ErrorHandling.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ class ErrorHandling {
void raise_overcurrent_error(const std::string& description);
void clear_overcurrent_error();

void raise_over_voltage_error(Everest::error::Severity severity, const std::string& description);

void raise_internal_error(const std::string& description);
void clear_internal_error();

Expand Down
36 changes: 36 additions & 0 deletions modules/EVSE/EvseManager/EvseManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,17 @@ void EvseManager::ready() {
config.fail_on_powermeter_errors ? r_powermeter_billing() : EMPTY_POWERMETER_VECTOR,
r_over_voltage_monitor, config.inoperative_error_use_vendor_id));

internal_over_voltage_monitor = std::make_unique<OverVoltageMonitor>(
[this](OverVoltageMonitor::FaultType type, const std::string& description) {
if (this->error_handling) {
const auto severity = type == OverVoltageMonitor::FaultType::Emergency
? Everest::error::Severity::High
: Everest::error::Severity::Medium;
this->error_handling->raise_over_voltage_error(severity, description);
}
},
std::chrono::milliseconds(config.internal_over_voltage_duration_ms));

if (not config.lock_connector_in_state_b) {
EVLOG_warning << "Unlock connector in CP state B. This violates IEC61851-1:2019 D.6.5 Table D.9 line 4 and "
"should not be used in public environments!";
Expand Down Expand Up @@ -456,6 +467,10 @@ void EvseManager::ready() {
if (not r_over_voltage_monitor.empty()) {
r_over_voltage_monitor[0]->call_start();
}
if (internal_over_voltage_monitor) {
internal_over_voltage_monitor->reset();
internal_over_voltage_monitor->start_monitor();
}
});

r_hlc[0]->subscribe_current_demand_finished([this] {
Expand All @@ -464,8 +479,21 @@ void EvseManager::ready() {
if (not r_over_voltage_monitor.empty()) {
r_over_voltage_monitor[0]->call_stop();
}
if (internal_over_voltage_monitor) {
internal_over_voltage_monitor->stop_monitor();
}
});

// Subscribe to voltage measurements from over_voltage_monitor interface
// The internal monitor acts as a software watchdog following the hardware OVM values
if (not r_over_voltage_monitor.empty()) {
r_over_voltage_monitor[0]->subscribe_voltage_measurement_V([this](float voltage_V) {
if (internal_over_voltage_monitor) {
internal_over_voltage_monitor->update_voltage(voltage_V);
}
});
}

// Isolation monitoring for DC charging handler
if (not r_imd.empty()) {

Expand Down Expand Up @@ -767,6 +795,10 @@ void EvseManager::ready() {
r_over_voltage_monitor[0]->call_set_limits(get_emergency_over_voltage_threshold(),
get_error_over_voltage_threshold());
}
if (internal_over_voltage_monitor) {
internal_over_voltage_monitor->set_limits(get_emergency_over_voltage_threshold(),
get_error_over_voltage_threshold());
}
});

r_hlc[0]->subscribe_departure_time([this](const std::string& t) {
Expand Down Expand Up @@ -1000,6 +1032,10 @@ void EvseManager::ready() {
if (not r_over_voltage_monitor.empty() and event == CPEvent::CarUnplugged) {
r_over_voltage_monitor[0]->call_reset_over_voltage_error();
}
if (internal_over_voltage_monitor and event == CPEvent::CarUnplugged) {
internal_over_voltage_monitor->stop_monitor();
internal_over_voltage_monitor->reset();
}

charger->bsp_event_queue.push(event);

Expand Down
3 changes: 3 additions & 0 deletions modules/EVSE/EvseManager/EvseManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "PersistentStore.hpp"
#include "SessionLog.hpp"
#include "VarContainer.hpp"
#include "over_voltage/OverVoltageMonitor.hpp"
#include "scoped_lock_timeout.hpp"
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1

Expand Down Expand Up @@ -74,6 +75,7 @@ struct Conf {
bool ac_enforce_hlc;
bool ac_with_soc;
int dc_isolation_voltage_V;
int internal_over_voltage_duration_ms;
bool dbg_hlc_auth_after_tstep;
int hack_sleep_in_cable_check;
int hack_sleep_in_cable_check_volkswagen;
Expand Down Expand Up @@ -324,6 +326,7 @@ class EvseManager : public Everest::ModuleBase {
VarContainer<types::isolation_monitor::IsolationMeasurement> isolation_measurement;
VarContainer<types::power_supply_DC::VoltageCurrent> powersupply_measurement;
VarContainer<bool> selftest_result;
std::unique_ptr<OverVoltageMonitor> internal_over_voltage_monitor;

// Track voltage to earth failures for debouncing
int voltage_to_earth_failure_count{0};
Expand Down
22 changes: 21 additions & 1 deletion modules/EVSE/EvseManager/doc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,26 @@ In addition, on the DC side the following hardware modules can be connected:
CableCheck, PreCharge and CurrentDemand steps.
* DC power supply: This is the AC/DC converter that actually charges the car.

<<<<<<< HEAD
<<<<<<< HEAD
Software over-voltage supervision is always active during DC charging. The configuration option
``internal_over_voltage_duration_ms`` defines for how long the measured DC voltage
must exceed the negotiated limit before EvseManager raises ``MREC5OverVoltage``.
Set it to ``0`` to trigger immediately once the threshold is crossed.

||||||| parent of afc3ead44 (feat(over_voltage): Internal over-voltage monitor implementation in EVSEManager)
=======
Software over-voltage supervision is always active. The configuration option
||||||| parent of 7d60e60da (Update modules/EVSE/EvseManager/doc.rst)
Software over-voltage supervision is always active. The configuration option
=======
Software over-voltage supervision is always active during DC charging. The configuration option
>>>>>>> 7d60e60da (Update modules/EVSE/EvseManager/doc.rst)
``internal_over_voltage_duration_ms`` defines for how long the measured DC voltage
must exceed the negotiated limit before EvseManager raises ``MREC5OverVoltage``.
Set it to ``0`` to trigger immediately once the threshold is crossed.

>>>>>>> afc3ead44 (feat(over_voltage): Internal over-voltage monitor implementation in EVSEManager)
Published variables
===================

Expand Down Expand Up @@ -281,7 +301,7 @@ Powermeter errors cause the EvseManager to become Inoperative, if fail_on_powerm

* powermeter/CommunicationFault

When a charging session is stopped because of an error, the EvseManager differentiates between **Emergency Shutdowns** and **Error Shutdowns**. The severity of the
When a charging session is stopped because of an error, the EvseManager differentiates between **Emergency Shutdowns** and **Error Shutdowns**. The severity of the
error influences the type of the shudown. Emergency shutdowns are caused by errors with `Severity::High` and error shutdowns are caused by errors with `Severity::Medium` or `Severity::Low`.

In case of an **Emergency Shutdown** the EvseManager will immediately:
Expand Down
7 changes: 7 additions & 0 deletions modules/EVSE/EvseManager/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ config:
Default is 0, which means the voltage will be determined according to IEC 61851-23 (2023) CC.4.1.2
type: integer
default: 0
internal_over_voltage_duration_ms:
description: >-
Time in milliseconds the internal software over voltage monitor waits before raising MREC5 once the measured
voltage exceeds the negotiated limit.
type: integer
minimum: 0
default: 400
dbg_hlc_auth_after_tstep:
description: >-
Special mode: send HLC auth ok only after t_step_XX is finished (true) or directly when available (false)
Expand Down
154 changes: 154 additions & 0 deletions modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest

#include "over_voltage/OverVoltageMonitor.hpp"

#include <algorithm>

Check warning on line 6 in modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp#L6

Include file: <algorithm> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <chrono>

Check warning on line 7 in modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp#L7

Include file: <chrono> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <condition_variable>

Check warning on line 8 in modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp#L8

Include file: <condition_variable> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <fmt/core.h>

Check warning on line 9 in modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp#L9

Include file: <fmt/core.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <thread>

Check warning on line 10 in modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/EVSE/EvseManager/over_voltage/OverVoltageMonitor.cpp#L10

Include file: <thread> not found. Please note: Cppcheck does not need standard library headers to get proper results.

namespace module {

OverVoltageMonitor::OverVoltageMonitor(ErrorCallback callback, std::chrono::milliseconds duration) :
error_callback_(std::move(callback)), duration_(duration) {
timer_thread_ = std::thread(&OverVoltageMonitor::timer_thread_func, this);
}

OverVoltageMonitor::~OverVoltageMonitor() {
{
std::lock_guard<std::mutex> lock(timer_mutex_);
timer_thread_exit_ = true;
timer_armed_ = false;
}
timer_cv_.notify_one();
if (timer_thread_.joinable()) {
timer_thread_.join();
}
}

void OverVoltageMonitor::set_limits(double emergency_limit, double error_limit) {
emergency_limit_ = emergency_limit;
error_limit_ = error_limit;
limits_valid_ = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is not verified if limits are plausible limits_valid is not needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically update_voltage is not called before set_limits but to avoid potential false errors I would argue to leave it to be on the safe side. Not sure how in the future we need to change the flow and shielding checking against invalid limits is not necessary a bad thing.

}

void OverVoltageMonitor::start_monitor() {
fault_latched_ = false;
cancel_error_timer();
running_ = true;
}

void OverVoltageMonitor::stop_monitor() {
running_ = false;
cancel_error_timer();
}

void OverVoltageMonitor::reset() {
fault_latched_ = false;
cancel_error_timer();
}

void OverVoltageMonitor::update_voltage(double voltage_v) {
if (!running_ || fault_latched_ || !limits_valid_) {
return;
}

if (voltage_v >= emergency_limit_) {
cancel_error_timer();
trigger_fault(FaultType::Emergency,
fmt::format("Voltage {:.2f} V exceeded emergency limit {:.2f} V.", voltage_v, emergency_limit_));
return;
}

if (voltage_v >= error_limit_) {
arm_error_timer(voltage_v);
} else {
cancel_error_timer();
}
}

void OverVoltageMonitor::trigger_fault(FaultType type, const std::string& reason) {
fault_latched_ = true;
running_ = false;
cancel_error_timer();
if (error_callback_) {
error_callback_(type, reason);
}
}

void OverVoltageMonitor::arm_error_timer(double voltage_v) {
if (duration_.count() == 0) {
trigger_fault(FaultType::Error, fmt::format("Voltage {:.2f} V exceeded limit {:.2f} V for at least {} ms.",
voltage_v, error_limit_, duration_.count()));
return;
}

{
std::lock_guard<std::mutex> lock(timer_mutex_);
if (timer_armed_) {
timer_voltage_snapshot_ = std::max(timer_voltage_snapshot_, voltage_v);
return;
}
timer_armed_ = true;
timer_voltage_snapshot_ = voltage_v;
timer_deadline_ = std::chrono::steady_clock::now() + duration_;
}
timer_cv_.notify_one();
}

void OverVoltageMonitor::cancel_error_timer() {
{
std::lock_guard<std::mutex> lock(timer_mutex_);
if (!timer_armed_) {
return;
}
timer_armed_ = false;
}
timer_cv_.notify_one();
}

void OverVoltageMonitor::timer_thread_func() {
std::unique_lock<std::mutex> lock(timer_mutex_);

while (!timer_thread_exit_) {
// Wait until a timer is armed or exit is requested
timer_cv_.wait(lock, [this] { return timer_thread_exit_ || timer_armed_; });
if (timer_thread_exit_) {
break;
}

// Capture the current deadline and wait until it expires or is cancelled/updated
auto deadline = timer_deadline_;
while (!timer_thread_exit_ && timer_armed_) {
if (timer_cv_.wait_until(lock, deadline) == std::cv_status::timeout) {
break;
}
// Woken up: check for exit, cancellation or re-arming with a new deadline
if (timer_thread_exit_ || !timer_armed_ || timer_deadline_ != deadline) {
break;
}
}

if (timer_thread_exit_) {
break;
}
if (!timer_armed_ || timer_deadline_ != deadline) {
// Timer was cancelled or re-armed; go back to waiting
continue;
}

// Timer expired with this deadline and is still armed
const double voltage = timer_voltage_snapshot_;
timer_armed_ = false;

// Release the lock while invoking the callback path
lock.unlock();
trigger_fault(FaultType::Error, fmt::format("Voltage {:.2f} V exceeded limit {:.2f} V for at least {} ms.",
voltage, error_limit_, duration_.count()));
lock.lock();
}
}

} // namespace module
Loading
Loading