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,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "Bender_isoCHA425HV.hpp"
namespace module {
void Bender_isoCHA425HV::init() {
invoke_init(*p_main);
}
void Bender_isoCHA425HV::ready() {
invoke_ready(*p_main);
}
} // namespace module

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef BENDER_ISO_CHA425HV_HPP
#define BENDER_ISO_CHA425HV_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 {};
class Bender_isoCHA425HV : public Everest::ModuleBase {
public:
Bender_isoCHA425HV() = delete;
Bender_isoCHA425HV(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 // BENDER_ISO_CHA425HV_HPP

View File

@@ -0,0 +1,21 @@
#
# 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
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,50 @@
.. _everest_modules_handwritten_Bender_isoCHA425HV:
.. ******************
.. Bender_isoCHA425HV
.. ******************
IMD driver for Bender isoCHA IMD devices
Default settings
================
By default the Bender IsoCHA type insulation monitor devices come pre-configured with the following settings:
* 19200 baud / even parity / 1 stop-bit
* bus address: 3
If you want to change those settings, you need to use the manufacturer's configuration software or change the settings on the device's LCD display and buttons.
Bus termination
----------------
The Bender IsoCHA devices come with an internal 120 Ohms switchable bus termination resistor on the modbus connection terminals. To use it, put the switch into the upper (on) position.
Settings overwritten by the driver
----------------------------------
During initialization, this driver overwrites the following settings:
* set "pre-alarm" resistor R1 (default = 600k) [register 3005 and following]
* set "alarm" resistor R2 to slightly lower than R1 (= -10k) [register 3007]
* disable low-voltage alarm [register 3008 and following]
* disable overvoltage alarm [register 3010 and following]
* set mode to "dc" [register 3023 and following]
* disable automatic self-tests [register 3021 and following]
* disable line voltage test [register 3024]
* disable device (only if the configuration option ``threshold_resistance_kohm`` is set to its maximum of 600 [k Ohms]) [register 3026]
Startup and operation
=====================
To start the device and begin insulation (and voltage) measurements, send a "start" command via the device's MQTT interface. The device will then begin sending its measurement data once a second.
After the desired measurement has been retrieved, disable the device via an MQTT "stop" command on its interface.
Devices with firmware version newer than 5.00 support a faster self test mode to reduce CableCheck time. This will automatically be used if supported by firmware.
Using the internal alarm functions and relays
=============================================
The external alarm relays should be used to ensure shut down of the DC HV voltage independently from the Linux host. Make sure to configure the thresholds correctly.

View File

@@ -0,0 +1,442 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "isolation_monitorImpl.hpp"
#include "everest/logging.hpp"
#include <chrono>
#include <fmt/core.h>
#include <thread>
#include <utils/date.hpp>
namespace module {
namespace main {
void isolation_monitorImpl::init() {
}
void isolation_monitorImpl::configure_device() {
// Query device name and version
bool successful = true;
int selftest_enable_at_start_value = config.selftest_enable_at_start ? 1 : 0;
if (config.disable_device_on_stop and config.selftest_enable_at_start) {
EVLOG_warning << "disable_device_on_stop configuration option and "
"selftest_enable_at_start are incompatible. Self test at "
"start will be disabled.";
selftest_enable_at_start_value = 0;
}
do {
successful = true;
successful &= send_to_imd(3000, (config.voltage_to_earth_monitoring_alarm_enable ? 1 : 0));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3005, config.r1_prealarm_kohm);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3007, config.r2_alarm_kohm);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3008, (config.undervoltage_alarm_enable ? 1 : 0));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3009, config.undervoltage_alarm_threshold_V);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3010, (config.overvoltage_alarm_enable ? 1 : 0));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3011, config.overvoltage_alarm_threshold_V);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3012, (config.alarm_memory_enable ? 1 : 0));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3013, (config.relais_r1_mode ? 1 : 0));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3014, (config.relais_r2_mode ? 1 : 0));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3018, (config.delay_startup_device ? 1 : 0));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3019, config.delay_t_on_k1_k2);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3020, config.delay_t_off_k1_k2);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3023, (config.chademo_mode ? 1 : 0));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3024, (config.selftest_enable_gridconnection ? 1 : 0));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3025, selftest_enable_at_start_value);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3027, config.relay_k1_alarm_assignment);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
successful &= send_to_imd(3028, config.relay_k2_alarm_assignment);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
// start up enable the device if the configuration option is not set
if (!config.disable_device_on_stop) {
successful &= send_to_imd(3026, 1);
}
// Give device time to process startup command
std::this_thread::sleep_for(std::chrono::milliseconds(50));
if (successful) {
EVLOG_info << "IMD Device: " << read_device_name() << " (" << read_firmware_version() << ")";
faster_cable_check_supported = check_for_faster_cablecheck();
if (faster_cable_check_supported) {
EVLOG_info << "Supports faster cable check method";
enable_faster_cable_check_mode();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
} else {
EVLOG_info << "Does not support faster cable check method, falling "
"back to long self test. This may create "
"timeouts with certain cars in CableCheck. Consider "
"upgrading to at least firmware 5.00";
disable_faster_cable_check_mode();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
} else {
// allow the system to recover and don't hog the MODBUS
std::this_thread::sleep_for(std::chrono::seconds(10));
}
} while (not successful);
}
void isolation_monitorImpl::start_self_test() {
if (last_test != TestType::ExternalTest) {
// Wait a bit to ensure device is ready (not processing previous read
// operations) This prevents conflicts with the regular reading loop
std::this_thread::sleep_for(std::chrono::milliseconds(50));
if (mod->r_serial_comm_hub->call_modbus_write_multiple_registers(
config.imd_device_id, static_cast<int>(8005),
(types::serial_comm_hub_requests::VectorUint16){{0x5445}}) !=
types::serial_comm_hub_requests::StatusCodeEnum::Success) {
set_deviceFault("Failed to start self test");
self_test_started = false;
return;
}
// Give device time to process the self-test command
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
self_test_started = true;
self_test_timeout = 30;
}
void isolation_monitorImpl::set_deviceFault(const std::string& message) {
if (!error_state_monitor->is_error_active("isolation_monitor/DeviceFault", "")) {
auto error =
error_factory->create_error("isolation_monitor/DeviceFault", "", message, Everest::error::Severity::High);
raise_error(error);
}
}
void isolation_monitorImpl::clear_deviceFault() {
if (error_state_monitor->is_error_active("isolation_monitor/DeviceFault", "")) {
clear_error("isolation_monitor/DeviceFault");
}
}
bool isolation_monitorImpl::enable_faster_cable_check_mode() {
return send_to_imd(3021, 3);
}
bool isolation_monitorImpl::disable_faster_cable_check_mode() {
return send_to_imd(3021, 0);
}
bool isolation_monitorImpl::send_to_imd(const uint16_t& command, const uint16_t& value) {
if (mod->r_serial_comm_hub->call_modbus_write_multiple_registers(
config.imd_device_id, static_cast<int>(command),
(types::serial_comm_hub_requests::VectorUint16){{value}}) !=
types::serial_comm_hub_requests::StatusCodeEnum::Success) {
EVLOG_error << "ModBus Command failed: " << command << " Value: " << value;
set_deviceFault("Failed to write registers");
return false;
}
return true;
}
void isolation_monitorImpl::ready() {
this->configure_device();
bool self_test_running = false;
bool need_to_disable_device = false;
int device_disabled_timeout_s = 10;
while (true) {
read_imd_values();
if (self_test_started) {
self_test_timeout--;
if (self_test_timeout <= 0) {
// a time out happend
self_test_started = false;
need_to_disable_device = true;
// publish failed result
EVLOG_warning << "Self test timed out";
publish_self_test_result(false);
}
if (self_test_running) {
if (last_test not_eq TestType::ExternalTest) {
// Self test is done
self_test_running = false;
self_test_started = false;
need_to_disable_device = true;
// was it successfull? If there is no error, it was...
bool result = true;
if (last_alarm == AlarmType::DeviceError) {
result = false;
}
EVLOG_info << "Self test completed: " << result;
publish_self_test_result(result);
}
} else {
if (last_test == TestType::ExternalTest) {
self_test_running = true;
}
}
}
if (not error_state_monitor->is_error_active("isolation_monitor/DeviceFault", "")) {
std::this_thread::sleep_for(std::chrono::seconds(1));
if (need_to_disable_device) {
device_disabled_timeout_s--;
if (device_disabled_timeout_s <= 0) {
need_to_disable_device = false;
device_disabled_timeout_s = 10;
// disable the device if the configuration option is set and we are not publishing
// aka we are in a measuring cycle (start called)
if (not enable_publishing && config.disable_device_on_stop) {
if (not send_to_imd(3026, 0)) {
EVLOG_error << "Can't disable the device: " << read_device_name();
} else {
EVLOG_info << "Device disabled after self test since we didn't start measuring cycle "
"(timeout 10s)";
}
}
}
}
} else {
std::this_thread::sleep_for(std::chrono::seconds(10));
}
}
}
void isolation_monitorImpl::handle_start() {
enable_publishing = true;
if (config.disable_device_on_stop) {
if (not send_to_imd(3026, 1)) {
EVLOG_error << "Can't enable the device: " << read_device_name();
} else {
EVLOG_info << "Device enabled for measurements";
}
}
}
void isolation_monitorImpl::handle_stop() {
enable_publishing = false;
if (config.disable_device_on_stop) {
if (not send_to_imd(3026, 0)) {
EVLOG_error << "Can't disable the device: " << read_device_name();
} else {
EVLOG_info << "Device disabled after measurements";
}
}
}
void isolation_monitorImpl::handle_start_self_test(double& test_voltage_V) {
EVLOG_info << "IMD Starting self-test...";
// make sure that the device is on
if (config.disable_device_on_stop) {
if (not send_to_imd(3026, 1)) {
EVLOG_error << "Can't enable the device: " << read_device_name();
} else {
EVLOG_info << "Device enabled for self test";
}
}
start_self_test();
}
void isolation_monitorImpl::read_imd_values() {
// Read rF
auto rf = read_register(ImdRegisters::RESISTANCE_R_F_OHM);
EVLOG_debug << "Resistance: " << to_string(rf);
last_test = rf.test;
last_alarm = rf.alarm;
// Small delay between reads to avoid overwhelming the device
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// Read Voltage U_N (always needed)
auto voltage = read_register(ImdRegisters::VOLTAGE_U_N_V);
EVLOG_debug << "Voltage: " << to_string(voltage);
isolation_monitorImpl::MeasurementValue voltage_to_earth_l1e;
isolation_monitorImpl::MeasurementValue voltage_to_earth_l2e;
// Read Voltage to Earth L1E and L2E only if the device is not in self test
// mode and only it we are in a measuring cycle. We have seen that Bender
// sometimes is overwhelmed
if ((last_test == TestType::NoTest) and (enable_publishing or config.always_publish_measurements)) {
// VOLTAGE_U_L1E_V (1016) and VOLTAGE_U_L2E_V (1020) are consecutive (4
// registers apart) Read 8 registers starting from 1016 to get both
// measurements in a single operation
types::serial_comm_hub_requests::Result register_response =
mod->r_serial_comm_hub->call_modbus_read_holding_registers(
config.imd_device_id, static_cast<int>(ImdRegisters::VOLTAGE_U_L1E_V), 8);
if (register_response.status_code == types::serial_comm_hub_requests::StatusCodeEnum::Success and
register_response.value.has_value() and register_response.value.value().size() == 8) {
const auto& reg_value_int = register_response.value.value();
// Convert std::vector<int> to std::vector<uint16_t>
std::vector<uint16_t> reg_value(reg_value_int.begin(), reg_value_int.end());
// Parse voltage U_L1E from registers 0-3
voltage_to_earth_l1e = parse_register_data(reg_value, 0);
EVLOG_debug << "Voltage to Earth L1E: " << to_string(voltage_to_earth_l1e);
// Parse voltage U_L2E from registers 4-7
voltage_to_earth_l2e = parse_register_data(reg_value, 4);
EVLOG_debug << "Voltage to Earth L2E: " << to_string(voltage_to_earth_l2e);
} else {
// Fallback to individual reads
std::this_thread::sleep_for(std::chrono::milliseconds(10));
voltage_to_earth_l1e = read_register(ImdRegisters::VOLTAGE_U_L1E_V);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
voltage_to_earth_l2e = read_register(ImdRegisters::VOLTAGE_U_L2E_V);
}
}
if (last_alarm == AlarmType::DeviceError) {
set_deviceFault("Device Error");
}
bool valid_readings = true;
if (enable_publishing or config.always_publish_measurements) {
types::isolation_monitor::IsolationMeasurement m;
if (voltage.valid not_eq ValidType::Invalid) {
m.voltage_V = voltage.value;
} else {
valid_readings = false;
}
if (voltage_to_earth_l1e.valid not_eq ValidType::Invalid) {
m.voltage_to_earth_l1e_V = voltage_to_earth_l1e.value;
} else {
valid_readings = false;
}
if (voltage_to_earth_l2e.valid not_eq ValidType::Invalid) {
m.voltage_to_earth_l2e_V = voltage_to_earth_l2e.value;
} else {
valid_readings = false;
}
if (rf.valid not_eq ValidType::Invalid) {
m.resistance_F_Ohm = rf.value;
} else {
valid_readings = false;
}
// do not publish values if in error state or the device is in self test
// mode
if (last_test == TestType::NoTest) {
if (not error_state_monitor->is_error_active("isolation_monitor/DeviceFault", "")) {
publish_isolation_measurement(m);
}
}
}
// let at least one round of values unpublished not to use unstable ones
if (last_alarm not_eq AlarmType::DeviceError and valid_readings and
error_state_monitor->is_error_active("isolation_monitor/DeviceFault", "")) {
clear_deviceFault();
configure_device();
}
}
std::string isolation_monitorImpl::read_device_name() {
auto result = mod->r_serial_comm_hub->call_modbus_read_holding_registers(
config.imd_device_id, static_cast<std::underlying_type_t<ImdRegisters>>(ImdRegisters::DEVICE_NAME), 10);
std::string device_name;
if (result.status_code == types::serial_comm_hub_requests::StatusCodeEnum::Success) {
for (int character : result.value.value()) {
device_name.push_back(static_cast<char>(character >> 8));
device_name.push_back(static_cast<char>(character & 0xFF));
}
} else {
set_deviceFault("Can't read device name");
}
// Strip and new lines that may be present in device name
std::replace(device_name.begin(), device_name.end(), '\n', ' ');
return device_name;
}
std::string isolation_monitorImpl::read_firmware_version() {
auto result = mod->r_serial_comm_hub->call_modbus_read_holding_registers(
config.imd_device_id, static_cast<std::underlying_type_t<ImdRegisters>>(ImdRegisters::DEVICE_FIRMWARE_VERSION),
6);
std::string firmware_version;
if (result.status_code == types::serial_comm_hub_requests::StatusCodeEnum::Success) {
firmware_version = fmt::format("Ident: {} | Version {}, Date: {}-{}-{} | Modbus Driver: {}",
result.value.value()[0], result.value.value()[1], result.value.value()[2],
result.value.value()[3], result.value.value()[4], result.value.value()[5]);
} else {
set_deviceFault("Can't read the firmware version");
}
return firmware_version;
}
bool isolation_monitorImpl::check_for_faster_cablecheck() {
auto result = mod->r_serial_comm_hub->call_modbus_read_holding_registers(
config.imd_device_id,
static_cast<std::underlying_type_t<ImdRegisters>>(ImdRegisters::DEVICE_FIRMWARE_VERSION) + 1, 1);
std::string firmware_version;
if (result.status_code == types::serial_comm_hub_requests::StatusCodeEnum::Success) {
if (result.value.value()[0] > 500) {
return true;
}
} else {
set_deviceFault("Could not check if the device supports fast cable check");
}
return false;
}
isolation_monitorImpl::MeasurementValue isolation_monitorImpl::read_register(const ImdRegisters start_register) {
types::serial_comm_hub_requests::Result register_response{};
register_response = mod->r_serial_comm_hub->call_modbus_read_holding_registers(
config.imd_device_id, static_cast<std::underlying_type<ImdRegisters>::type>(start_register), 4);
MeasurementValue m;
if (register_response.status_code != types::serial_comm_hub_requests::StatusCodeEnum::Success) {
set_deviceFault("Can't read registers");
}
if (not register_response.value.has_value() or register_response.value.value().size() not_eq 4) {
// force an error since we did not received fully the registers
m.alarm = AlarmType::DeviceError;
return m;
}
// Convert std::vector<int> to std::vector<uint16_t>
const auto& reg_value_int = register_response.value.value();
std::vector<uint16_t> reg_value(reg_value_int.begin(), reg_value_int.end());
return parse_register_data(reg_value, 0);
}
isolation_monitorImpl::MeasurementValue
isolation_monitorImpl::parse_register_data(const std::vector<uint16_t>& reg_value, size_t offset) {
MeasurementValue m;
if (reg_value.size() < offset + 4) {
m.alarm = AlarmType::DeviceError;
return m;
}
m.alarm = to_alarm_type(reg_value.at(offset + 2) >> 8);
m.test = to_test_type(reg_value.at(offset + 2) >> 8);
m.unit = to_unit_type(reg_value.at(offset + 2) & 0xFF);
m.valid = to_valid_type(reg_value.at(offset + 2) & 0xFF);
m.description = to_channel_description(reg_value.at(offset + 3));
uint32_t value{0};
value += reg_value.at(offset + 0) << 16;
value += reg_value.at(offset + 1);
auto val = *reinterpret_cast<float*>(&value);
m.value = val;
return m;
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,425 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 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 "../Bender_isoCHA425HV.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {
int imd_device_id;
int r1_prealarm_kohm;
int r2_alarm_kohm;
bool undervoltage_alarm_enable;
int undervoltage_alarm_threshold_V;
bool overvoltage_alarm_enable;
int overvoltage_alarm_threshold_V;
bool alarm_memory_enable;
bool relais_r1_mode;
bool relais_r2_mode;
int delay_startup_device;
int delay_t_on_k1_k2;
int delay_t_off_k1_k2;
bool chademo_mode;
bool selftest_enable_gridconnection;
bool selftest_enable_at_start;
int relay_k1_alarm_assignment;
int relay_k2_alarm_assignment;
bool always_publish_measurements;
bool voltage_to_earth_monitoring_alarm_enable;
bool disable_device_on_stop;
};
class isolation_monitorImpl : public isolation_monitorImplBase {
public:
isolation_monitorImpl() = delete;
isolation_monitorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Bender_isoCHA425HV>& 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<Bender_isoCHA425HV>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
enum class ImdRegisters : uint16_t {
RESISTANCE_R_F_OHM = 1000,
VOLTAGE_U_N_V = 1008,
VOLTAGE_U_L1E_V = 1016,
VOLTAGE_U_L2E_V = 1020,
RESISTANCE_R_FU_OHM = 1028,
DEVICE_NAME = 9800,
DEVICE_FIRMWARE_VERSION = 9820,
};
enum class AlarmType {
NoAlarm,
PreWarning,
DeviceError,
Warning,
Alarm,
Reserved,
};
std::string to_string(AlarmType a) {
switch (a) {
case AlarmType::NoAlarm:
return "NoAlarm";
case AlarmType::PreWarning:
return "PreWarning";
case AlarmType::DeviceError:
return "DeviceError";
case AlarmType::Warning:
return "Warning";
case AlarmType::Alarm:
return "Alarm";
case AlarmType::Reserved:
return "Reserved";
}
return "Reserved";
};
enum class TestType {
NoTest,
InternalTest,
ExternalTest,
};
std::string to_string(TestType a) {
switch (a) {
case TestType::NoTest:
return "NoTest";
case TestType::InternalTest:
return "InternalTest";
case TestType::ExternalTest:
return "ExternalTest";
}
return "NoTest";
};
enum class ValidType {
TrueValue,
TrueValueIsSmaller,
TrueValueIsBigger,
Invalid,
};
std::string to_string(ValidType a) {
switch (a) {
case ValidType::TrueValue:
return "TrueValue";
case ValidType::TrueValueIsSmaller:
return "TrueValueIsSmaller";
case ValidType::TrueValueIsBigger:
return "TrueValueIsBigger";
case ValidType::Invalid:
return "Invalid";
}
return "Invalid";
};
enum class UnitType {
Invalid,
NoUnit,
Ohm,
Ampere,
Volt,
Percent,
Hertz,
Baud,
Farad,
Henry,
DegC,
DegF,
Seconds,
Minutes,
Hours,
Days,
Months,
};
std::string to_string(UnitType a) {
switch (a) {
case UnitType::Invalid:
return "Invalid";
case UnitType::NoUnit:
return "NoUnit";
case UnitType::Ohm:
return "Ohm";
case UnitType::Ampere:
return "Ampere";
case UnitType::Volt:
return "Volt";
case UnitType::Percent:
return "Percent";
case UnitType::Hertz:
return "Hertz";
case UnitType::Baud:
return "Baud";
case UnitType::Farad:
return "Farad";
case UnitType::Henry:
return "Henry";
case UnitType::DegC:
return "DegC";
case UnitType::DegF:
return "DegF";
case UnitType::Seconds:
return "Seconds";
case UnitType::Minutes:
return "Minutes";
case UnitType::Hours:
return "Hours";
case UnitType::Days:
return "Days";
case UnitType::Months:
return "Months";
}
return "Invalid";
};
enum class ChannelDescription {
Undefined,
IsolationError,
IsolationError_rF,
Voltage,
UnderVoltage,
OverVoltage,
Capacity,
IsolationError_Zi,
GridConnection,
EarthConnection,
DeviceErrorIsometer,
DeviceError,
OwnAddress,
};
std::string to_string(ChannelDescription a) {
switch (a) {
case ChannelDescription::Undefined:
return "Undefined";
case ChannelDescription::IsolationError:
return "IsolationError";
case ChannelDescription::IsolationError_rF:
return "IsolationError_rF";
case ChannelDescription::Voltage:
return "Voltage";
case ChannelDescription::UnderVoltage:
return "UnderVoltage";
case ChannelDescription::OverVoltage:
return "OverVoltage";
case ChannelDescription::Capacity:
return "Capacity";
case ChannelDescription::IsolationError_Zi:
return "IsolationError_Zi";
case ChannelDescription::GridConnection:
return "GridConnection";
case ChannelDescription::EarthConnection:
return "EarthConnection";
case ChannelDescription::DeviceErrorIsometer:
return "DeviceErrorIsometer";
case ChannelDescription::DeviceError:
return "DeviceError";
case ChannelDescription::OwnAddress:
return "OwnAddress";
}
return "Undefined";
};
ChannelDescription to_channel_description(uint16_t raw) {
switch (raw) {
case 1:
return ChannelDescription::IsolationError;
case 71:
return ChannelDescription::IsolationError_rF;
case 76:
return ChannelDescription::Voltage;
case 77:
return ChannelDescription::UnderVoltage;
case 78:
return ChannelDescription::OverVoltage;
case 82:
return ChannelDescription::Capacity;
case 86:
return ChannelDescription::IsolationError_Zi;
case 101:
return ChannelDescription::GridConnection;
case 102:
return ChannelDescription::EarthConnection;
case 115:
return ChannelDescription::DeviceErrorIsometer;
case 129:
return ChannelDescription::DeviceError;
case 145:
return ChannelDescription::OwnAddress;
default:
return ChannelDescription::Undefined;
}
};
UnitType to_unit_type(uint8_t raw) {
uint8_t t = raw & 0x1F;
switch (t) {
case 0:
return UnitType::Invalid;
case 1:
return UnitType::NoUnit;
case 2:
return UnitType::Ohm;
case 3:
return UnitType::Ampere;
case 4:
return UnitType::Volt;
case 5:
return UnitType::Percent;
case 6:
return UnitType::Hertz;
case 7:
return UnitType::Baud;
case 8:
return UnitType::Farad;
case 9:
return UnitType::Henry;
case 10:
return UnitType::DegC;
case 11:
return UnitType::DegF;
case 12:
return UnitType::Seconds;
case 13:
return UnitType::Minutes;
case 14:
return UnitType::Hours;
case 15:
return UnitType::Days;
case 16:
return UnitType::Months;
}
return UnitType::Invalid;
};
ValidType to_valid_type(uint8_t raw) {
uint8_t t = raw >> 6;
if (t == 1) {
return ValidType::TrueValueIsSmaller;
} else if (t == 2) {
return ValidType::TrueValueIsBigger;
} else if (t == 3) {
return ValidType::Invalid;
} else {
return ValidType::TrueValue;
}
};
TestType to_test_type(uint8_t raw) {
uint8_t t = raw >> 6;
if (t == 1) {
return TestType::InternalTest;
} else if (t == 2) {
return TestType::ExternalTest;
} else {
return TestType::NoTest;
}
};
AlarmType to_alarm_type(uint8_t raw) {
uint8_t t = raw & 0x07;
if (t == 0x00) {
return AlarmType::NoAlarm;
} else if (t == 0x01) {
return AlarmType::PreWarning;
} else if (t == 0x02 and (raw & 0xC0) == 0x00) {
return AlarmType::DeviceError;
} else if (t == 0x04) {
return AlarmType::Warning;
} else if (t == 0x05) {
return AlarmType::Alarm;
} else {
return AlarmType::Reserved;
}
};
struct MeasurementValue {
float value{0.};
AlarmType alarm{AlarmType::NoAlarm};
TestType test{TestType::NoTest};
UnitType unit{UnitType::Invalid};
ValidType valid{ValidType::Invalid};
ChannelDescription description{ChannelDescription::Undefined};
};
MeasurementValue read_register(const ImdRegisters start_register);
MeasurementValue parse_register_data(const std::vector<uint16_t>& reg_value, size_t offset);
std::string to_string(MeasurementValue m) {
return fmt::format(" {} [{}] [{}] [{}] [{}] [{}]", m.value, to_string(m.unit), to_string(m.valid),
to_string(m.description), to_string(m.alarm), to_string(m.test));
};
types::isolation_monitor::IsolationMeasurement isolation_measurement_data{};
std::thread output_thread;
std::atomic_bool enable_publishing{false};
std::atomic_bool self_test_started{false};
std::atomic_int self_test_timeout{0};
TestType last_test{TestType::NoTest};
AlarmType last_alarm{AlarmType::NoAlarm};
void set_deviceFault(const std::string& message);
void clear_deviceFault();
void configure_device();
void start_measurements();
void stop_measurements();
void start_self_test();
bool send_to_imd(const uint16_t& command, const uint16_t& value);
void read_imd_values();
std::string read_device_name();
std::string read_firmware_version();
bool check_for_faster_cablecheck();
bool enable_faster_cable_check_mode();
bool disable_faster_cable_check_mode();
bool faster_cable_check_supported{false};
// 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,139 @@
description: IMD driver for Bender isoCHA425HV IMD devices.
provides:
main:
description: Implementation of the driver functionality
interface: isolation_monitor
config:
imd_device_id:
description: The IMD's address on the ModBus
type: integer
minimum: 0
maximum: 255
default: 3
r1_prealarm_kohm:
description: >-
3005 The IMD's internal measurement resistance for signaling an alarm in kiloohms
(the internal resistance is only used for switching the IMDs relays,
EVerest handles any isolation issue internaly inside the EVSE manager)
Note the maximum value is 600k on isoCHA and 500k on isoEV units.
type: integer
minimum: 15
maximum: 600
default: 250
r2_alarm_kohm:
description: >-
3007 This value must be smaller than r1_prealarm_kohm
type: integer
minimum: 15
maximum: 600
default: 100
undervoltage_alarm_enable:
description: >-
3008 Enable under voltage alarm
type: boolean
default: false
undervoltage_alarm_threshold_V:
description: >-
3009 Threshold in volt for the under voltage alarm
type: integer
default: 30
overvoltage_alarm_enable:
description: >-
3010 Enable over voltage alarm
type: boolean
default: false
overvoltage_alarm_threshold_V:
description: >-
3011 Threshold in volt for the over voltage alarm
type: integer
default: 1050
alarm_memory_enable:
description: >-
3012 Enable memory for alarm events
type: boolean
default: false
relais_r1_mode:
description: >-
3013 false: normally open, true: normally closed
type: boolean
default: false
relais_r2_mode:
description: >-
3014 false: normally open, true: normally closed
type: boolean
default: false
delay_startup_device:
description: >-
3018 Delay when device is starting up
type: integer
maximum: 10
minimum: 0
default: 5
delay_t_on_k1_k2:
description: >-
3019 delay for switching relais on
type: integer
maximum: 99
minimum: 0
default: 5
delay_t_off_k1_k2:
description: >-
3020 delay for switching relais off
type: integer
maximum: 99
minimum: 0
default: 5
chademo_mode:
description: >-
3023: false: CCS, true: ChaDeMo
type: boolean
default: false
selftest_enable_gridconnection:
description: >-
3024: Test grid connection while testing device nEt
type: boolean
default: false
selftest_enable_at_start:
description: >-
3025 Perform self test at start up of device
type: boolean
default: true
relay_k1_alarm_assignment:
description: >-
3027 Alarm assignment of relay K1 "r1". Bitmask: S.AL(11) Ue>(10) Ce>(9) test(8) U>(7) U<(6) -R2<(5) +R2<(4) -R1<(3) +R1<(2) Err(1)
type: integer
minimum: 0
maximum: 65535
default: 0
relay_k2_alarm_assignment:
description: >-
3028 Alarm assignment of relay K2 "r2". Bitmask: S.AL(11) Ue>(10) Ce>(9) test(8) U>(7) U<(6) -R2<(5) +R2<(4) -R1<(3) +R1<(2) Err(1)
type: integer
minimum: 0
maximum: 65535
default: 0
always_publish_measurements:
description: >-
Always publish measurement even if stopped by EVerest
type: boolean
default: false
voltage_to_earth_monitoring_alarm_enable:
description: >-
3000 Enable over voltage to earth monitoring alarm
type: boolean
default: false
disable_device_on_stop:
description: >-
Disable the device upon stop measurement
type: boolean
default: false
requires:
serial_comm_hub:
interface: serial_communication_hub
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Cornelius Claussen
- Florin Mihut
- Jan Christoph Habig
- Lars Dieckmann

View File

@@ -0,0 +1,2 @@
ev_add_module(Bender_isoCHA425HV)
ev_add_module(DoldRN5893)

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