Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,23 @@
#
# 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
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/isolation_monitorImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
target_sources(${MODULE_NAME} PRIVATE
"main/registers.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Frickly Systems GmbH
// Copyright Pionix GmbH and Contributors to EVerest
#include "DoldRN5893.hpp"
namespace module {
void DoldRN5893::init() {
invoke_init(*p_main);
}
void DoldRN5893::ready() {
invoke_ready(*p_main);
}
} // namespace module

View File

@@ -0,0 +1,81 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Frickly Systems GmbH
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef DOLD_RN5893_HPP
#define DOLD_RN5893_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/isolation_monitor/Implementation.hpp>
// headers for required interface implementations
#include <generated/interfaces/serial_communication_hub/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int device_id;
int self_test_timeout_s;
bool keep_measurement_active;
bool always_publish_measurements;
bool timeout_release;
double timeout_s;
std::string broken_wire_detect;
bool storing_insulation_fault;
std::string switching_mode_indicator_relay;
std::string power_supply_type;
int response_value_alarm_kohm;
int response_value_pre_alarm_kohm;
std::string coupling_device;
std::string indicator_relay_k1_function;
std::string indicator_relay_k2_function;
bool automatic_self_test;
};
class DoldRN5893 : public Everest::ModuleBase {
public:
DoldRN5893() = delete;
DoldRN5893(const ModuleInfo& info, std::unique_ptr<isolation_monitorImplBase> p_main,
std::unique_ptr<serial_communication_hubIntf> r_serial_comm_hub, Conf& config) :
ModuleBase(info), p_main(std::move(p_main)), r_serial_comm_hub(std::move(r_serial_comm_hub)), config(config){};
const std::unique_ptr<isolation_monitorImplBase> p_main;
const std::unique_ptr<serial_communication_hubIntf> r_serial_comm_hub;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
protected:
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
// insert your protected definitions here
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
private:
friend class LdEverest;
void init();
void ready();
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
// insert your private definitions here
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
};
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
// insert other definitions here
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
} // namespace module
#endif // DOLD_RN5893_HPP

View File

@@ -0,0 +1,60 @@
.. _everest_modules_handwritten_DoldRN5893:
.. ********************************************************************************
.. Dold RN5893 IMD with UL 2231 approval - especially for DC charging stations
.. ********************************************************************************
Electrical safety must be guaranteed during the charging process. For this purpose, an unearthed DC power supply system (IT system) with insulation monitoring is set up and monitored with an insulation monitoring device (IMD).
Dold has developed a smart insulation monitoring solution for DC charging stations that also fulfils the requirements of UL 2231.
The insulation monitor RN 5893 from the VARIMETER IMD family monitors the charging process from the charging station to the vehicle in combination with the coupling device RP 5898.
- Response delay < 10 s
- Nominal voltage up to DC 1000 V
- Manipulation protection due to sealable transparent cover
Self test
=========
The Dold RN5893 supports two types of self tests: the normal self test and the extended self test.
This module only supports the "normal" self tests, not the extended self test.
When a self test is started via the IMD interface, bit 4 of the "control word 1" register (address 40001) is set.
The device will then perform a self test. If a device fault is present while the self test is running, the self test will fail.
If the device reports a state that is not "self test running" anymore and there is no device fault, the self test is considered successful.
Changes of the device state, including self tests, are reported with a short delay via Modbus. This is handled internally using the ``self_test_running`` and ``self_test_triggered`` variables of the driver.
Timeout
=======
The device supports a communication timeout, which raises a device fault if no communication is possible for a certain time.
This module supports this timeout. It is enabled by setting ``timeout_release`` to ``true`` and optionally configuring the timeout duration in seconds using ``timeout_s``, which defaults to 3s.
This value should not be smaller than 2s, as the driver updates the *Timeout* register only once per second (or slightly less frequently), which would otherwise lead to false positives.
If the device reports *Communication Fault Modbus* in the *device fault* register, the driver will try to reset the device by writing ``1`` to the *control word 1* register. After that, the driver resets the control word 1 to the previous value.
The driver will try to write the device reset command every cycle until the device fault is cleared.
Measurement and publishing modes
=================================
This driver supports three modes of operation:
- **Standard mode**: The device pauses measurements upon startup and when ``stop()`` is called. Measurements are started by calling ``start()``
- **Continuous measurement mode**: The device continuously performs measurements, bit 8 of the "control word 1" register (address 40001) is not set at any time. This is useful if the device should always alarm on isolation faults, even when no EV is connected. Measurements are still only published when ``start()`` is called, until ``stop()`` is called
- This mode is enabled by setting ``keep_measurement_active`` to ``true``
- **Always publish mode**: Like in continous measurement mode, the device continuously performs measurements. Additionally, all measurements are published, even when ``stop()`` is called. This is only useful in very specific scenarios, e.g. if a special module needs measurements all the time
- This mode is enabled by setting both ``always_publish_measurements`` and ``keep_measurement_active`` to ``true``
Device instructions
===================
- For applications following UL 2231 the parameter ``automatic_self_test`` has to be disabled (i.e. set to ``false``)
- Changes to modbus registers triggered by modbus messages may have a short delay before they can be read back over the bus. The device's internal reaction is faster than what is reported via Modbus, however
- The device state may not always report "Error" when a device fault is present (i.e. the device has an internal fault or the device fault register reports a fault), because of internal prioritization of faults. Because of this, we only check the device fault register to determine if a fault is present and report that to EVerest
- If a modbus communication timeout occurs, the device only responds to modbus requests that either read data or write a reset command

View File

@@ -0,0 +1,500 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Frickly Systems GmbH
// Copyright Pionix GmbH and Contributors to EVerest
#include "isolation_monitorImpl.hpp"
#include "registers.hpp"
namespace module {
namespace main {
void isolation_monitorImpl::init() {
}
void isolation_monitorImpl::ready() {
EVLOG_info << "Uploading configuration to device";
// Note that we continue in this initial setup even if anything fails as the main loop reconfigures the device if
// necessary
const auto success = configure_device();
if (not success) {
EVLOG_error << "Failed to configure device";
} else {
EVLOG_info << "Device configured successfully";
}
EVLOG_info << "Starting main loop";
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(MAIN_LOOP_INTERVAL_S));
if (self_test_triggered or self_test_running) {
// If the self test takes too long to start or to run, we time out here
if (std::chrono::steady_clock::now() > self_test_deadline) {
EVLOG_error << "Self test timed out";
self_test_running = false;
self_test_triggered = false;
publish_self_test_result(false);
}
// If some communication error occurs during the self test, we consider the self test failed
if (error_state_monitor->is_error_active("isolation_monitor/CommunicationFault", "")) {
EVLOG_error << "Self test failed due to communication error";
self_test_running = false;
self_test_triggered = false;
publish_self_test_result(false);
}
}
auto device_fault_and_state = read_device_fault_and_state();
if (not device_fault_and_state.has_value()) {
EVLOG_error << "Failed to read device fault and state";
continue;
}
const auto [device_fault, device_state] = device_fault_and_state.value();
EVLOG_debug << "Device status: " << to_string(device_state);
EVLOG_debug << "Device error: " << to_string(device_fault);
raise_or_clear_device_fault(device_fault);
if (device_fault == DeviceFault_30001::CommunicationFault_Modbus) {
EVLOG_info << "Device modbus timeout detected, trying to reset device and reconfigure";
if (not update_control_word1(ControlWord1Action::ResetFaults)) {
EVLOG_error << "Failed to reset device faults";
continue;
}
if (not update_control_word1()) {
EVLOG_error << "Failed to update control word 1";
continue;
}
}
// update Timeout register if enabled
if (not write_timeout_registers()) {
EVLOG_error << "Failed to write timeout register";
continue;
}
// When we trigger a self test, the device can publish a normal status before switching to self test mode, this
// is handled here
if (self_test_triggered and not self_test_running and device_state == DeviceState_30002::SelfTesting) {
EVLOG_info << "Device has started selftesting";
self_test_running = true;
}
// Once the device has entered self test mode AND then left it again (either device state is reset or some fault
// is raised)
if (self_test_triggered and self_test_running and
(device_state != DeviceState_30002::SelfTesting or device_fault != DeviceFault_30001::NoFailure)) {
// the self test is now complete, reset flags
self_test_running = false;
self_test_triggered = false;
// If no failure was reported, the self test passed
const auto self_test_result = device_fault == DeviceFault_30001::NoFailure;
publish_self_test_result(self_test_result);
EVLOG_info << "Self test completed with result: " << self_test_result;
// update the control word 1 to potentially disable measurement again (self test only works when measurement
// is not disabled)
update_control_word1();
}
// if device is initializing, raise an error as device is not ready
if (device_state == DeviceState_30002::Initializing) {
if (not error_state_monitor->is_error_active("isolation_monitor/DeviceFault", "NotReady")) {
raise_error(
error_factory->create_error("isolation_monitor/DeviceFault", "NotReady", "Device not ready"));
}
} else {
if (error_state_monitor->is_error_active("isolation_monitor/DeviceFault", "NotReady")) {
clear_error("isolation_monitor/DeviceFault", "NotReady");
}
}
// dont publish if not measuring
bool should_publish_isolation_measurement = device_state == DeviceState_30002::Measuring or
device_state == DeviceState_30002::Measuring_PreAlarmExceeded or
device_state == DeviceState_30002::Measuring_AlarmExceeded;
// dont publish if device has a fault
if (device_fault != DeviceFault_30001::NoFailure) {
should_publish_isolation_measurement = false;
}
// publish only when enabled or when always_publish_measurements is set
if (not mod->config.always_publish_measurements and not publish_enabled) {
should_publish_isolation_measurement = false;
}
if (should_publish_isolation_measurement) {
const auto isolation_measurement = read_isolation_measurement();
if (not isolation_measurement.has_value()) {
EVLOG_error << "Failed to read isolation measurement";
continue;
}
publish_isolation_measurement(isolation_measurement.value());
EVLOG_debug << "Insulation resistance: " << isolation_measurement->resistance_F_Ohm << " Ohm";
if (isolation_measurement->voltage_V) {
EVLOG_debug << "Voltage: " << *isolation_measurement->voltage_V << " V";
}
}
// If a communication fault was previously raised, we can clear it now, as we were able to read from the device.
// Also upload the configuration again, as the device might have reset/restarted
if (error_state_monitor->is_error_active("isolation_monitor/CommunicationFault", "")) {
// reconfigure device after communication fault, if this fails, keep the communication fault
if (not configure_device()) {
EVLOG_error << "Failed to reconfigure device after communication fault";
continue;
}
clear_error("isolation_monitor/CommunicationFault");
}
}
}
void isolation_monitorImpl::handle_start() {
publish_enabled = true;
if (not update_control_word1()) {
EVLOG_error << "Failed to enable measurement";
}
}
void isolation_monitorImpl::handle_stop() {
publish_enabled = false;
if (not update_control_word1()) {
EVLOG_error << "Failed to disable measurement";
}
}
void isolation_monitorImpl::handle_start_self_test(double& test_voltage_V) {
(void)test_voltage_V; // Unused parameter
// Note that we only check if a self test has been triggered by us, not if the device is currently in self test
// mode. If the device is already in self test mode, we can use the running self test to populate our self test
// result
if (self_test_running or self_test_triggered) {
EVLOG_warning << "Self test already running or triggered";
return;
}
EVLOG_info << "Starting self test";
if (not update_control_word1(ControlWord1Action::StartSelfTest)) {
publish_self_test_result(false);
EVLOG_error << "Failed to start self test";
return;
}
self_test_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(mod->config.self_test_timeout_s);
self_test_triggered = true;
// device might take some time to actually start the self test; this is set in the main
// loop when we see the device state change to self testing
self_test_running = false;
}
bool isolation_monitorImpl::configure_device() {
// disable timeout if not enabled. Note that is enabled in the write_timeout_registers function after Timeout is
// written
if (not mod->config.timeout_release) {
// Timeout release register. Write 0 to disable timeout
const auto success = write_holding_register(1, 0);
if (not success) {
EVLOG_error << "Failed to disable device timeout";
return false;
}
}
// Read current settings from device to prevent unnecessary writes (the datasheet specifies that unnecessary writes
// should be avoided to prevent wearing out the EEPROM).
// There are 11 configuration registers starting at address 2000, we read all for convenience
const auto present_settings = read_holding_registers(2000, 11);
if (not present_settings.has_value()) {
EVLOG_error << "Failed to read current configuration from device";
return false;
}
// prepare new settings based on current settings
std::vector<uint16_t> new_settings;
for (const auto& value : *present_settings) {
new_settings.push_back(static_cast<uint16_t>(value));
}
uint16_t broken_wire_detect = 0; // see datasheet
if (mod->config.broken_wire_detect == "ON") {
broken_wire_detect = 1;
} else if (mod->config.broken_wire_detect == "OFF") {
broken_wire_detect = 2;
} else if (mod->config.broken_wire_detect == "ONLY_DURING_SELF_TEST") {
broken_wire_detect = 4;
} else {
EVLOG_error << "Invalid connection monitoring configuration: " << mod->config.broken_wire_detect;
return false;
}
new_settings[0] = broken_wire_detect; // 2000
new_settings[1] = mod->config.storing_insulation_fault ? 1 : 0; // 2001
uint16_t switching_mode_indicator_relay = 0; // see datasheet
if (mod->config.switching_mode_indicator_relay == "DE_ENERGIZED_ON_TRIP") {
switching_mode_indicator_relay = 0;
} else if (mod->config.switching_mode_indicator_relay == "ENERGIZED_ON_TRIP") {
switching_mode_indicator_relay = 1;
} else {
EVLOG_error << "Invalid indicator relay switching mode configuration: "
<< mod->config.switching_mode_indicator_relay;
return false;
}
new_settings[2] = switching_mode_indicator_relay; // 2002
uint16_t power_supply_type = 0; // see datasheet
if (mod->config.power_supply_type == "AC") {
power_supply_type = 1;
} else if (mod->config.power_supply_type == "DC") {
power_supply_type = 2;
} else if (mod->config.power_supply_type == "3NAC") {
power_supply_type = 4;
} else {
EVLOG_error << "Invalid power supply type configuration: " << mod->config.power_supply_type;
return false;
}
new_settings[3] = power_supply_type; // 2003
new_settings[5] = mod->config.response_value_alarm_kohm; // 2005
new_settings[6] = mod->config.response_value_pre_alarm_kohm; // 2006
uint16_t coupling_device = 1; // see datasheet; 1 = Off
if (mod->config.coupling_device == "RP5898") {
coupling_device = 2;
}
new_settings[7] = coupling_device; // 2007
uint16_t indicator_relay_k1_function = 8; // see datasheet
if (mod->config.indicator_relay_k1_function == "INSULATION_FAULT_ALARM") {
indicator_relay_k1_function = (1U << 0);
} else if (mod->config.indicator_relay_k1_function == "INSULATION_FAULT_PREALARM") {
indicator_relay_k1_function = (1U << 1);
} else if (mod->config.indicator_relay_k1_function == "DEVICE_FAULT") {
indicator_relay_k1_function = (1U << 2);
} else if (mod->config.indicator_relay_k1_function == "INSULATION_FAULT_ALARM_OR_DEVICE_FAULT") {
indicator_relay_k1_function = (1U << 3);
} else if (mod->config.indicator_relay_k1_function == "INSULATION_FAULT_ON_DC+") {
indicator_relay_k1_function = (1U << 4);
} else if (mod->config.indicator_relay_k1_function == "INSULATION_FAULT_ON_DC+_OR_DEVICE_FAULT") {
indicator_relay_k1_function = (1U << 5);
} else {
EVLOG_error << "Invalid indicator relay K1 function configuration: " << mod->config.indicator_relay_k1_function;
return false;
}
uint16_t indicator_relay_k2_function = 8; // see datasheet
if (mod->config.indicator_relay_k2_function == "INSULATION_FAULT_ALARM") {
indicator_relay_k2_function = (1U << 0);
} else if (mod->config.indicator_relay_k2_function == "INSULATION_FAULT_PRE_ALARM") {
indicator_relay_k2_function = (1U << 1);
} else if (mod->config.indicator_relay_k2_function == "DEVICE_FAULT") {
indicator_relay_k2_function = (1U << 2);
} else if (mod->config.indicator_relay_k2_function == "INSULATION_FAULT_PRE_ALARM_OR_DEVICE_FAULT") {
indicator_relay_k2_function = (1U << 3);
} else if (mod->config.indicator_relay_k2_function == "INSULATION_FAULT_ON_DC-") {
indicator_relay_k2_function = (1U << 4);
} else if (mod->config.indicator_relay_k2_function == "INSULATION_FAULT_ON_DC-_OR_DEVICE_FAULT") {
indicator_relay_k2_function = (1U << 5);
} else {
EVLOG_error << "Invalid indicator relay K2 function configuration: " << mod->config.indicator_relay_k2_function;
return false;
}
new_settings[8] = indicator_relay_k1_function; // 2008
new_settings[9] = indicator_relay_k2_function; // 2009
new_settings[10] = mod->config.automatic_self_test ? 1 : 0; // 2010
if (not std::equal(new_settings.begin(), new_settings.end(), present_settings->begin())) {
const auto write_success = write_holding_registers(2000, new_settings);
if (not write_success) {
EVLOG_error << "Failed to write configuration to device";
return false;
}
} else {
EVLOG_debug << "Device configuration is already up to date, no need to write";
}
if (not update_control_word1(ControlWord1Action::ResetFaults)) {
EVLOG_error << "Failed to reset device faults after configuration";
return false;
}
if (not update_control_word1()) {
EVLOG_error << "Failed to set control word 1 after configuration";
return false;
}
return true;
}
std::optional<std::vector<int>> isolation_monitorImpl::read_input_registers(uint16_t first_protocol_register_address,
uint16_t register_quantity) {
const auto result = mod->r_serial_comm_hub->call_modbus_read_input_registers(
mod->config.device_id, first_protocol_register_address, register_quantity);
if (result.status_code != types::serial_comm_hub_requests::StatusCodeEnum::Success) {
raise_communication_fault();
return std::nullopt;
}
return result.value;
}
std::optional<std::vector<int>> isolation_monitorImpl::read_holding_registers(uint16_t first_protocol_register_address,
uint16_t register_quantity) {
const auto result = mod->r_serial_comm_hub->call_modbus_read_holding_registers(
mod->config.device_id, first_protocol_register_address, register_quantity);
if (result.status_code != types::serial_comm_hub_requests::StatusCodeEnum::Success) {
raise_communication_fault();
return std::nullopt;
}
return result.value;
}
bool isolation_monitorImpl::write_holding_register(uint16_t protocol_address, uint16_t value) {
const auto result =
mod->r_serial_comm_hub->call_modbus_write_single_register(mod->config.device_id, protocol_address, value);
if (result != types::serial_comm_hub_requests::StatusCodeEnum::Success) {
raise_communication_fault();
return false;
}
return true;
}
bool isolation_monitorImpl::write_holding_registers(uint16_t protocol_address, const std::vector<uint16_t>& values) {
types::serial_comm_hub_requests::VectorUint16 values_converted;
for (const auto& v : values) {
values_converted.data.push_back(v);
}
const auto result = mod->r_serial_comm_hub->call_modbus_write_multiple_registers(
mod->config.device_id, protocol_address, values_converted);
if (result != types::serial_comm_hub_requests::StatusCodeEnum::Success) {
raise_communication_fault();
return false;
}
return true;
}
void isolation_monitorImpl::raise_communication_fault() {
if (not error_state_monitor->is_error_active("isolation_monitor/CommunicationFault", "")) {
raise_error(error_factory->create_error("isolation_monitor/CommunicationFault", "", "Communication fault"));
}
}
bool isolation_monitorImpl::update_control_word1(ControlWord1Action action) {
if (action == ControlWord1Action::ResetFaults) {
return write_holding_register(0, 1U << 0); // Bit 0: reset
}
if (action == ControlWord1Action::StartSelfTest) {
// Note that the self test only works when measurement is not disabled, so we only set bit 4 here
return write_holding_register(0, 1U << 4); // Bit 4: Start self test
}
if (self_test_triggered or self_test_running) {
// if a self test was triggered, we don't want to change the control word until the self test is complete.
// One should call update_control_word1 when the self test is complete!
return true;
}
uint16_t control_word1 = 0;
// if we should not measure, set bit 8 to disable measurements
if (not publish_enabled and not mod->config.keep_measurement_active) {
control_word1 |= 1U << 8; // bit 8: Measurement off
}
return write_holding_register(0, control_word1);
}
std::optional<types::isolation_monitor::IsolationMeasurement> isolation_monitorImpl::read_isolation_measurement() {
types::isolation_monitor::IsolationMeasurement isolation_measurement;
const auto input_registers = read_input_registers(2000, 3);
if (not input_registers.has_value()) {
return std::nullopt;
}
const uint16_t raw_insulation_resistance = static_cast<uint16_t>(input_registers->at(0)); // 2000
const uint16_t raw_voltage = static_cast<uint16_t>(input_registers->at(2)); // 2002
isolation_measurement.resistance_F_Ohm =
static_cast<float>(insulation_resistance_to_ohm(raw_insulation_resistance));
// 0xFFFF means voltage out of range
if (raw_voltage != 0xFFFF) {
isolation_measurement.voltage_V = static_cast<float>(raw_voltage);
}
return isolation_measurement;
}
std::optional<std::tuple<DeviceFault_30001, DeviceState_30002>> isolation_monitorImpl::read_device_fault_and_state() {
const auto raw_registers = read_input_registers(0x0000, 2);
if (not raw_registers.has_value()) {
return std::nullopt;
}
const auto device_fault = static_cast<DeviceFault_30001>(raw_registers.value()[0]);
const auto device_state = static_cast<DeviceState_30002>(raw_registers.value()[1]);
return std::make_tuple(device_fault, device_state);
}
bool isolation_monitorImpl::write_timeout_registers() {
if (not mod->config.timeout_release) {
return true;
}
// write timeout register
if (not write_holding_register(2, mod->config.timeout_s * 1000)) {
EVLOG_error << "Failed to write Timeout register";
return false;
}
// enable timeout (note that disabling the timeout is only done in the configure_device function to reduce
// unnecessary writes)
if (not write_holding_register(1, 1)) {
EVLOG_error << "Failed to enable device timeout";
return false;
}
return true;
}
void isolation_monitorImpl::raise_or_clear_device_fault(const DeviceFault_30001& device_fault) {
if (device_fault != DeviceFault_30001::NoFailure) {
if (not error_state_monitor->is_error_active("isolation_monitor/DeviceFault", "DeviceFaultRegister")) {
EVLOG_error << "Raising device fault: " << to_string(device_fault);
raise_error(
error_factory->create_error("isolation_monitor/DeviceFault", "DeviceFaultRegister",
std::string("Device fault: ") + std::string(to_string(device_fault))));
}
} else {
if (error_state_monitor->is_error_active("isolation_monitor/DeviceFault", "DeviceFaultRegister")) {
clear_error("isolation_monitor/DeviceFault", "DeviceFaultRegister");
}
}
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,173 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Frickly Systems GmbH
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef MAIN_ISOLATION_MONITOR_IMPL_HPP
#define MAIN_ISOLATION_MONITOR_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/isolation_monitor/Implementation.hpp>
#include "../DoldRN5893.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
#include "registers.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class isolation_monitorImpl : public isolation_monitorImplBase {
public:
isolation_monitorImpl() = delete;
isolation_monitorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<DoldRN5893>& mod, Conf& config) :
isolation_monitorImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual void handle_start() override;
virtual void handle_stop() override;
virtual void handle_start_self_test(double& test_voltage_V) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<DoldRN5893>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
const int MAIN_LOOP_INTERVAL_S = 1;
/**
* @brief reads a number of input registers via modbus
* @note this raises a everest communication fault if unsuccessful
* @param first_register_address the address of the first register to read (protocol address)
* @param register_quantity the number of registers to read
* @return a vector of integers containing the register values, or std::nullopt in case of a communication error
*/
std::optional<std::vector<int>> read_input_registers(uint16_t first_protocol_register_address,
uint16_t register_quantity);
/**
* @brief reads a number of holding registers via modbus
* @note this raises a everest communication fault if unsuccessful
* @param first_register_address the address of the first register to read (protocol address)
* @param register_quantity the number of registers to read
* @return a vector of integers containing the register values, or std::nullopt in case of a communication error
*/
std::optional<std::vector<int>> read_holding_registers(uint16_t first_protocol_register_address,
uint16_t register_quantity);
/**
* @brief writes a single holding register via modbus
* @note this raises a everest communication fault if unsuccessful
* @param protocol_address the address of the register to write (protocol address)
* @param value the value to write
* @return true on success, false on communication error
*/
bool write_holding_register(uint16_t protocol_address, uint16_t value);
/**
* @brief writes multiple holding registers via modbus
* @note this raises a everest communication fault if unsuccessful
* @param protocol_address the address of the first register to write (protocol address)
* @param values the values to write
* @return true on success, false on communication error
*/
bool write_holding_registers(uint16_t protocol_address, const std::vector<uint16_t>& values);
// true if measurement should be published, set via the start/stop commands
std::atomic_bool publish_enabled = false;
// true if a self test has been triggered and the device should do a self test.
// stays true until the self test is finished or the timeout is reached
std::atomic_bool self_test_triggered = false;
// true if a self test has been triggered and the device has started the self test.
// When triggering a self test, this stays false until the device switches to self test mode.
// It is reset when self_test_triggered is reset - after the self test is finished or the timeout is reached
std::atomic_bool self_test_running = false;
// Deadline for the current self test. If the self test is not finished by this time, it is considered failed.
// Its value is only valid if self_test_triggered is true
std::chrono::steady_clock::time_point self_test_deadline;
/**
* @brief raises a everest communication fault error if not already active
* @note the fault is cleared in the main loop when communication is successful again
*/
void raise_communication_fault();
/**
* @brief raises or clears a everest device fault based on the provided device fault register value
* @param device_fault_register the current value of the device fault register
* @note the error is raised if the value is not NoFailure and cleared if it is NoFailure
*/
void raise_or_clear_device_fault(const DeviceFault_30001& device_fault_register);
/**
* @brief Configures the device according to the current config.
* Only writes the registers if the current value differs from the desired value.
* Also calls \c update_control_word1 to set the control word according to the current config and state.
* @note this also raises a everest communication fault if unsuccessful
* @return true on success, false on communication error
*/
bool configure_device();
/**
* @brief Update the timeout registers based on the config
* @note should be called once per main loop iteration
* @return true on success, false on communication error
*/
bool write_timeout_registers();
/**
* @brief reads the current isolation measurement and voltage from the device
* @note this raises a everest communication fault if unsuccessful
* @return the isolation measurement, or std::nullopt in case of a communication error
*/
std::optional<types::isolation_monitor::IsolationMeasurement> read_isolation_measurement();
/**
* @brief reads the current device fault and state from the device (first two input registers)
* @note this raises a everest communication fault if unsuccessful
* @return a tuple of device fault and device state, or std::nullopt in case of a communication error
*/
std::optional<std::tuple<DeviceFault_30001, DeviceState_30002>> read_device_fault_and_state();
enum class ControlWord1Action {
None,
StartSelfTest,
ResetFaults,
};
/**
* @brief Updates the control word 1 register (address 40001) based on the current state and config.
* Use action to trigger a specific action (self test or reset).
* @param action the action to perform, defaults to None
* @return true on success, false on communication error
*/
bool update_control_word1(ControlWord1Action action = ControlWord1Action::None);
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_ISOLATION_MONITOR_IMPL_HPP

View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Frickly Systems GmbH
// Copyright Pionix GmbH and Contributors to EVerest
#include "registers.hpp"
std::string_view to_string(DeviceFault_30001 code) noexcept {
switch (code) {
case DeviceFault_30001::NoFailure:
return "No failure";
case DeviceFault_30001::BrokenWire_L_PosNeg:
return "Broken wire detection L(+)/L(-)";
case DeviceFault_30001::BrokenWire_PE1_PE2:
return "Broken wire detection PE1/PE2";
case DeviceFault_30001::InternalFailure_TestMode_Int1:
return "Internal failure detected in test mode (Int. 1)";
case DeviceFault_30001::ParameterFailure_PotentiometerSetting:
return "Parameter failures (Incorrect setting of potentiometers on the device)";
case DeviceFault_30001::CommunicationFault_Modbus:
return "Communication fault Modbus";
case DeviceFault_30001::ChecksumFailure_EEPROM_Int2:
return "Checksum failure EEPROM (Int. 2)";
case DeviceFault_30001::InternalCommunicationFault_Int3:
return "Internal communication fault (Int. 3)";
case DeviceFault_30001::InternalError_Int4:
return "Internal error 4 (Int. 4)";
}
return "Unknown code";
}
std::string_view to_string(DeviceState_30002 state) noexcept {
switch (state) {
case DeviceState_30002::Initializing:
return "Initializing";
case DeviceState_30002::Measuring:
return "Ready and measuring";
case DeviceState_30002::ErrorMode:
return "Error mode";
case DeviceState_30002::SelfTesting:
return "Selftesting";
case DeviceState_30002::AdvancedTest:
return "Selftest in advanced test mode";
case DeviceState_30002::MeasuringStopped:
return "Measuring stopped";
case DeviceState_30002::Measuring_AlarmExceeded:
return "Measuring, alarm is exceeded";
case DeviceState_30002::Measuring_PreAlarmExceeded:
return "Measuring, pre-alarm is exceeded";
}
return "Unknown state";
}
uint32_t insulation_resistance_to_ohm(uint16_t insulation_resistance_100ohm) noexcept {
if (insulation_resistance_100ohm == 0xFFFF) {
return 2000001; // > 2MOhm
}
return static_cast<std::uint32_t>(insulation_resistance_100ohm) * 100;
}

View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Frickly Systems GmbH
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once
#include <cstdint>
#include <string>
enum class DeviceFault_30001 : std::uint16_t {
/// 0: No failure
NoFailure = 0,
/// 1: Broken wire detection L(+)/L(-)
BrokenWire_L_PosNeg = 1,
/// 2: Broken wire detection PE1/PE2
BrokenWire_PE1_PE2 = 2,
/// 3: Internal failure detected in test mode (Int. 1)
InternalFailure_TestMode_Int1 = 3,
/// 4: Parameter failures (Incorrect potentiometer settings on the device)
ParameterFailure_PotentiometerSetting = 4,
/// 9: Communication fault Modbus
CommunicationFault_Modbus = 9,
/// 10: Checksum failure EEPROM (Int. 2)
ChecksumFailure_EEPROM_Int2 = 10,
/// 11: Internal communication fault (Int. 3)
InternalCommunicationFault_Int3 = 11,
/// 12: Internal error 4 (Int. 4)
InternalError_Int4 = 12
};
std::string_view to_string(DeviceFault_30001 code) noexcept;
enum class DeviceState_30002 : std::uint8_t {
/// 0: Device initializing
Initializing = 0,
/// 1: Device is ready and in measuring mode, no response value is exceeded
Measuring = 1,
/// 2: Device in error mode
ErrorMode = 2,
/// 3: Device in selftesting
SelfTesting = 3,
/// 4: Device in advanced test
AdvancedTest = 4,
/// 5: Measuring function stopped
MeasuringStopped = 5,
/// 6: Device in measuring mode, response value alarm is exceeded
Measuring_AlarmExceeded = 6,
/// 7: Device in measuring mode, response value pre-alarm is exceeded
Measuring_PreAlarmExceeded = 7
};
std::string_view to_string(DeviceState_30002 state) noexcept;
/**
* @brief convert a insulation resistance (registers 32001 and 32004) to kOhm
* @note according to datasheet, the value 0xFFFF means > 2MOhm
*/
uint32_t insulation_resistance_to_ohm(uint16_t insulation_resistance_100ohm) noexcept;

View File

@@ -0,0 +1,123 @@
description: DOLD RN5893 IMD driver module
config:
device_id:
description: Modbus device address of DOLD RN5893, set via potentiometers on device
type: integer
self_test_timeout_s:
description: Maximum time in seconds a self-test is allowed to take
type: integer
default: 30
keep_measurement_active:
description: |
Normally, this module stops measurements when stop is called and restarts when start is called.
If this option is enabled, measurements will continue even if stop is called.
This does not affect publishing of measurements, which is still controlled by start/stop commands and
the always_publish_measurements option.
type: boolean
default: false
always_publish_measurements:
description: |
Whether measurements should always be published, no matter if start/stop commands have been received.
Note that this only works if keep_measurement_active is enabled.
type: boolean
default: false
timeout_release:
description: 40002 - Timeout release - whether the timeout on the IMD side should be enabled. This enables writing the Timeout register every cycle
type: boolean
default: false
timeout_s:
description: |
40003 - Timeout - the time in seconds after the device will trigger a fault if no communication is received.
This should not be smaller than 2 seconds, as this module writes the register every second
type: number
minimum: 2
default: 3
maximum: 10
broken_wire_detect:
description: 42001 - Broken wire detect in measuring circuit - Whether connection to L(+)/L(-) should be monitored and if so, when
type: string
enum:
- ON
- OFF
- ONLY_DURING_SELF_TEST
default: ON
storing_insulation_fault:
description: 42002 - Storing insulation fault message - See datasheet for details
type: boolean
default: false
switching_mode_indicator_relay:
description: 42003 - Switching mode of indicator relay K1 + K2 - Behaviour type of the indicator relays. See datasheet for details
type: string
enum:
- DE_ENERGIZED_ON_TRIP
- ENERGIZED_ON_TRIP
default: DE_ENERGIZED_ON_TRIP
power_supply_type:
description: 42004 - Power supply type - Type of connected power network. See datasheet for details
type: string
enum:
- AC
- DC
- 3NAC
default: DC
response_value_alarm_kohm:
description: 42006 Response value Alarm - Alarm threshold setting in kOhm
type: integer
maximum: 500
minimum: 1
default: 500
response_value_pre_alarm_kohm:
description: 42007 - Response value Pre-Alarm Pre-Alarm threshold setting in kOhm
type: integer
maximum: 500
minimum: 1
default: 500
coupling_device:
description: 42008 Coupling device - Whether a coupling device is connected and which one
type: string
enum:
- NONE
- RP5898
default: NONE
indicator_relay_k1_function:
description: 42009 - Indicator relay K1 - Function of the indicator relay K1. See datasheet for details
type: string
enum:
- INSULATION_FAULT_ALARM
- INSULATION_FAULT_PREALARM
- DEVICE_FAULT
- INSULATION_FAULT_ALARM_OR_DEVICE_FAULT
- INSULATION_FAULT_ON_DC+
- INSULATION_FAULT_ON_DC+_OR_DEVICE_FAULT
default: INSULATION_FAULT_ALARM_OR_DEVICE_FAULT
indicator_relay_k2_function:
description: 42010 Indicator relay K2 - Function of the indicator relay K2. See datasheet for details
type: string
enum:
- INSULATION_FAULT_ALARM
- INSULATION_FAULT_PREALARM
- DEVICE_FAULT
- INSULATION_FAULT_PRE_ALARM_OR_DEVICE_FAULT
- INSULATION_FAULT_ON_DC-
- INSULATION_FAULT_ON_DC-_OR_DEVICE_FAULT
default: INSULATION_FAULT_PRE_ALARM_OR_DEVICE_FAULT
automatic_self_test:
description: 42011 - Automatic self-test - Whether automatic self-test is enabled
type: boolean
default: false
provides:
main:
description: IMD Interface of DOLD RN5893
interface: isolation_monitor
requires:
serial_comm_hub:
interface: serial_communication_hub
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Frickly Systems GmbH
- Mark Oude Elberink