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