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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
ev_add_module(Bender_isoCHA425HV)
|
||||
ev_add_module(DoldRN5893)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user