From 2fafe325f92b45d5e8c6b0f71902d5133a3475e7 Mon Sep 17 00:00:00 2001 From: Torsten Oltmanns Date: Sun, 20 Oct 2024 16:16:42 +0800 Subject: [PATCH] added Narada BMS ModBus binding --- bms-narada-modbus/.gitignore | 4 + bms-narada-modbus/pom.xml | 30 ++++ .../modbus/NaradaBmsModbusDescriptor.java | 47 ++++++ .../modbus/NaradaBmsModbusProcessor.java | 152 ++++++++++++++++++ .../src/main/resources/META-INF/beans.xml | 5 + ...irepublic.bmstoinverter.core.BMSDescriptor | 1 + bms-to-inverter-main/pom.xml | 7 + configurator/pom.xml | 7 + ...irepublic.bmstoinverter.core.BMSDescriptor | 1 + pom.xml | 1 + 10 files changed, 255 insertions(+) create mode 100644 bms-narada-modbus/.gitignore create mode 100644 bms-narada-modbus/pom.xml create mode 100644 bms-narada-modbus/src/main/java/com/airepublic/bmstoinverter/bms/narada/modbus/NaradaBmsModbusDescriptor.java create mode 100644 bms-narada-modbus/src/main/java/com/airepublic/bmstoinverter/bms/narada/modbus/NaradaBmsModbusProcessor.java create mode 100644 bms-narada-modbus/src/main/resources/META-INF/beans.xml create mode 100644 bms-narada-modbus/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.BMSDescriptor diff --git a/bms-narada-modbus/.gitignore b/bms-narada-modbus/.gitignore new file mode 100644 index 00000000..769de891 --- /dev/null +++ b/bms-narada-modbus/.gitignore @@ -0,0 +1,4 @@ +/target/ +/.classpath +/.project +/.settings/ \ No newline at end of file diff --git a/bms-narada-modbus/pom.xml b/bms-narada-modbus/pom.xml new file mode 100644 index 00000000..358e1b79 --- /dev/null +++ b/bms-narada-modbus/pom.xml @@ -0,0 +1,30 @@ + + 4.0.0 + + + com.ai-republic.bms-to-inverter + bms-to-inverter-parent + 0.0.1-SNAPSHOT + + + bms-narada-modbus + + ${project.artifactId}-${project.version} + Module for the Narada BMS ModBus support + + + UTF-8 + UTF-8 + + + + + com.ai-republic.bms-to-inverter + protocol-modbus + ${project.version} + + + + \ No newline at end of file diff --git a/bms-narada-modbus/src/main/java/com/airepublic/bmstoinverter/bms/narada/modbus/NaradaBmsModbusDescriptor.java b/bms-narada-modbus/src/main/java/com/airepublic/bmstoinverter/bms/narada/modbus/NaradaBmsModbusDescriptor.java new file mode 100644 index 00000000..440f5f52 --- /dev/null +++ b/bms-narada-modbus/src/main/java/com/airepublic/bmstoinverter/bms/narada/modbus/NaradaBmsModbusDescriptor.java @@ -0,0 +1,47 @@ +/** + * This software is free to use and to distribute in its unchanged form for private use. + * Commercial use is prohibited without an explicit license agreement of the copyright holder. + * Any changes to this software must be made solely in the project repository at https://github.com/ai-republic/bms-to-inverter. + * The copyright holder is not liable for any damages in whatever form that may occur by using this software. + * + * (c) Copyright 2022 and onwards - Torsten Oltmanns + * + * @author Torsten Oltmanns - bms-to-inverter''AT''gmail.com + */ +package com.airepublic.bmstoinverter.bms.narada.modbus; + +import com.airepublic.bmstoinverter.core.BMS; +import com.airepublic.bmstoinverter.core.BMSConfig; +import com.airepublic.bmstoinverter.core.BMSDescriptor; +import com.airepublic.bmstoinverter.core.Port; +import com.airepublic.bmstoinverter.protocol.modbus.J2ModMasterPort; + +/** + * The {@link BMSDescriptor} for the Narada BMS using the Modbus protocol. + */ +public class NaradaBmsModbusDescriptor implements BMSDescriptor { + @Override + public String getName() { + return "NARADA_MODBUS"; + } + + + @Override + public int getDefaultBaudRate() { + return 9600; + } + + + @Override + public Class getBMSClass() { + return NaradaBmsModbusProcessor.class; + } + + + @Override + public Port createPort(final BMSConfig config) { + final Port port = new J2ModMasterPort(config.getPortLocator(), config.getBaudRate()); + return port; + } + +} diff --git a/bms-narada-modbus/src/main/java/com/airepublic/bmstoinverter/bms/narada/modbus/NaradaBmsModbusProcessor.java b/bms-narada-modbus/src/main/java/com/airepublic/bmstoinverter/bms/narada/modbus/NaradaBmsModbusProcessor.java new file mode 100644 index 00000000..13db7cb2 --- /dev/null +++ b/bms-narada-modbus/src/main/java/com/airepublic/bmstoinverter/bms/narada/modbus/NaradaBmsModbusProcessor.java @@ -0,0 +1,152 @@ +/** + * This software is free to use and to distribute in its unchanged form for private use. + * Commercial use is prohibited without an explicit license agreement of the copyright holder. + * Any changes to this software must be made solely in the project repository at https://github.com/ai-republic/bms-to-inverter. + * The copyright holder is not liable for any damages in whatever form that may occur by using this software. + * + * (c) Copyright 2022 and onwards - Torsten Oltmanns + * + * @author Torsten Oltmanns - bms-to-inverter''AT''gmail.com + */ +package com.airepublic.bmstoinverter.bms.narada.modbus; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.airepublic.bmstoinverter.core.AlarmLevel; +import com.airepublic.bmstoinverter.core.BMS; +import com.airepublic.bmstoinverter.core.Port; +import com.airepublic.bmstoinverter.core.bms.data.Alarm; +import com.airepublic.bmstoinverter.core.bms.data.BatteryPack; +import com.airepublic.bmstoinverter.core.bms.data.EnergyStorage; +import com.airepublic.bmstoinverter.core.util.Util; +import com.airepublic.bmstoinverter.protocol.modbus.ModbusUtil; +import com.airepublic.bmstoinverter.protocol.modbus.ModbusUtil.RegisterCode; + +import jakarta.inject.Inject; + +/** + * The class to handle Modbus messages from a Narada {@link BMS}. + */ +public class NaradaBmsModbusProcessor extends BMS { + private final static Logger LOG = LoggerFactory.getLogger(NaradaBmsModbusProcessor.class); + @Inject + private EnergyStorage energyStorage; + + @Override + protected void collectData(final Port port) { + try { + sendMessage(port, RegisterCode.READ_INPUT_REGISTERS, 0x0FFF, 50, getBmsId(), this::readBatteryStatus); + } catch (final IOException e) { + LOG.error("Error reading from modbus!", e); + } + } + + + protected void sendMessage(final Port port, final RegisterCode functionCode, final int startAddress, final int numRegisters, final int unitId, final Consumer handler) throws IOException { + port.sendFrame(ModbusUtil.createRequestBuffer(functionCode, startAddress, numRegisters, unitId)); + handler.accept(port.receiveFrame()); + } + + + protected void readBatteryStatus(final ByteBuffer frame) { + frame.getInt(); // functionCode + frame.getInt(); // numRegisters + final int unitId = frame.getInt(); + final BatteryPack pack = energyStorage.getBatteryPack(unitId); + + pack.packVoltage = frame.getShort() / 10; // 0.01V + pack.packCurrent = frame.getShort() - 10000; // 0.1A + pack.remainingCapacitymAh = frame.getShort() * 100; // 0.1A + pack.tempAverage = frame.getShort() - 400; // 0.1C - 400 + frame.getShort(); // env temp 0.1C - 400 + final int warningFlag = frame.getChar(); + final int protectionFlag = frame.getShort(); + final int statusFlag = frame.getShort(); + pack.packSOC = frame.getShort() / 10; // 0.01% + pack.bmsCycles = frame.getShort(); + pack.packSOH = frame.getShort() / 10; // 0.01% + frame.getShort(); // history discharge capacity 10A + pack.numberOfCells = frame.getShort(); + + // determine min/max cell voltages + for (int i = 0; i < 16; i++) { + if (i < pack.numberOfCells) { + pack.cellVmV[i] = frame.getShort(); + + if (pack.cellVmV[i] > pack.maxCellmV) { + pack.maxCellmV = pack.cellVmV[i]; + pack.maxCellVNum = i; + } else if (pack.cellVmV[i] < pack.minCellmV) { + pack.minCellmV = pack.cellVmV[i]; + pack.minCellVNum = i; + } + } else { + frame.getShort(); + } + } + + // calculate cell voltage difference + pack.cellDiffmV = pack.maxCellmV - pack.minCellmV; + + pack.numOfTempSensors = frame.getShort(); + + for (int i = 0; i < 16; i++) { + if (i < pack.numOfTempSensors) { + pack.cellTemperature[i] = frame.getShort() - 400; // 0.1C - 400 + } else { + frame.getShort(); + } + } + + pack.ratedCapacitymAh = frame.getShort() * 100; // 0.1A + frame.getShort(); // remaining charge time 1min + frame.getShort(); // remaining discharge time 1min + final int cellUVStatus = frame.getShort(); // Cell undervoltage state + + readAlarms(pack, warningFlag, protectionFlag, statusFlag, cellUVStatus); + } + + + private void readAlarms(final BatteryPack pack, final int warningFlag, final int protectionFlag, final int faultStatus, final int cellUVStatus) { + // warnings + pack.alarms.put(Alarm.CELL_VOLTAGE_HIGH, Util.bit(warningFlag, 0) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.CELL_VOLTAGE_LOW, Util.bit(warningFlag, 1) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.PACK_VOLTAGE_HIGH, Util.bit(warningFlag, 2) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.PACK_VOLTAGE_LOW, Util.bit(warningFlag, 3) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.CHARGE_CURRENT_HIGH, Util.bit(warningFlag, 4) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.DISCHARGE_CURRENT_HIGH, Util.bit(warningFlag, 5) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.CELL_TEMPERATURE_HIGH, Util.bit(warningFlag, 6) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.CELL_TEMPERATURE_LOW, Util.bit(warningFlag, 7) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.ENCASING_TEMPERATURE_HIGH, Util.bit(warningFlag, 8) ? AlarmLevel.WARNING : AlarmLevel.NONE); + + pack.alarms.put(Alarm.CHARGE_MODULE_TEMPERATURE_HIGH, Util.bit(warningFlag, 10) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.SOC_LOW, Util.bit(warningFlag, 11) ? AlarmLevel.WARNING : AlarmLevel.NONE); + pack.alarms.put(Alarm.CELL_VOLTAGE_DIFFERENCE_HIGH, Util.bit(warningFlag, 12) ? AlarmLevel.WARNING : AlarmLevel.NONE); + + // alarms + pack.alarms.put(Alarm.CELL_VOLTAGE_HIGH, Util.bit(protectionFlag, 0) ? AlarmLevel.ALARM : AlarmLevel.NONE); + pack.alarms.put(Alarm.CELL_VOLTAGE_LOW, Util.bit(protectionFlag, 1) ? AlarmLevel.ALARM : AlarmLevel.NONE); + pack.alarms.put(Alarm.PACK_VOLTAGE_HIGH, Util.bit(protectionFlag, 2) ? AlarmLevel.ALARM : AlarmLevel.NONE); + pack.alarms.put(Alarm.PACK_VOLTAGE_LOW, Util.bit(protectionFlag, 3) ? AlarmLevel.ALARM : AlarmLevel.NONE); + pack.alarms.put(Alarm.FAILURE_SHORT_CIRCUIT_PROTECTION, Util.bit(protectionFlag, 4) ? AlarmLevel.ALARM : AlarmLevel.NONE); + + pack.alarms.put(Alarm.CHARGE_TEMPERATURE_HIGH, Util.bit(protectionFlag, 6) ? AlarmLevel.ALARM : AlarmLevel.NONE); + pack.alarms.put(Alarm.CHARGE_TEMPERATURE_LOW, Util.bit(protectionFlag, 7) ? AlarmLevel.ALARM : AlarmLevel.NONE); + pack.alarms.put(Alarm.DISCHARGE_TEMPERATURE_HIGH, Util.bit(protectionFlag, 8) ? AlarmLevel.ALARM : AlarmLevel.NONE); + pack.alarms.put(Alarm.DISCHARGE_TEMPERATURE_LOW, Util.bit(protectionFlag, 9) ? AlarmLevel.ALARM : AlarmLevel.NONE); + + // fault + pack.alarms.put(Alarm.FAILURE_COMMUNICATION_INTERNAL, Util.bit(faultStatus, 0) ? AlarmLevel.ALARM : AlarmLevel.NONE); + pack.alarms.put(Alarm.FAILURE_SENSOR_PACK_TEMPERATURE, Util.bit(faultStatus, 1) ? AlarmLevel.ALARM : AlarmLevel.NONE); + + pack.chargeDischargeStatus = Util.bit(faultStatus, 8) ? 1 : Util.bit(faultStatus, 9) ? 2 : 0; + pack.chargeMOSState = Util.bit(faultStatus, 9); + pack.dischargeMOSState = Util.bit(faultStatus, 10); + + } +} diff --git a/bms-narada-modbus/src/main/resources/META-INF/beans.xml b/bms-narada-modbus/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000..af2cda43 --- /dev/null +++ b/bms-narada-modbus/src/main/resources/META-INF/beans.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/bms-narada-modbus/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.BMSDescriptor b/bms-narada-modbus/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.BMSDescriptor new file mode 100644 index 00000000..422c340a --- /dev/null +++ b/bms-narada-modbus/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.BMSDescriptor @@ -0,0 +1 @@ +com.airepublic.bmstoinverter.bms.narada.modbus.NaradaBmsModbusDescriptor diff --git a/bms-to-inverter-main/pom.xml b/bms-to-inverter-main/pom.xml index 553ac833..2c8157a1 100644 --- a/bms-to-inverter-main/pom.xml +++ b/bms-to-inverter-main/pom.xml @@ -168,6 +168,13 @@ ${project.version} + + + com.ai-republic.bms-to-inverter + bms-narada-modbus + ${project.version} + + com.ai-republic.bms-to-inverter diff --git a/configurator/pom.xml b/configurator/pom.xml index e74e4d7a..e23252ce 100644 --- a/configurator/pom.xml +++ b/configurator/pom.xml @@ -129,6 +129,13 @@ bms-jk-rs485 ${project.version} + + + + com.ai-republic.bms-to-inverter + bms-narada-modbus + ${project.version} + diff --git a/configurator/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.BMSDescriptor b/configurator/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.BMSDescriptor index 5e1d3a56..9190ccc0 100644 --- a/configurator/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.BMSDescriptor +++ b/configurator/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.BMSDescriptor @@ -7,6 +7,7 @@ com.airepublic.bmstoinverter.bms.jbd.rs485.JBDBmsRS485Descriptor com.airepublic.bmstoinverter.bms.jk.can.JKBmsCANDescriptor com.airepublic.bmstoinverter.bms.jk.modbus.JKBmsModbusDescriptor com.airepublic.bmstoinverter.bms.jk.rs485.JKBmsRS485Descriptor +com.airepublic.bmstoinverter.bms.narada.modbus.NaradaBmsModbusDescriptor com.airepublic.bmstoinverter.bms.pace.can.PaceBmsCANDescriptor com.airepublic.bmstoinverter.bms.pylon.can.PylonBmsCANDescriptor com.airepublic.bmstoinverter.bms.pylon.hv.can.PylonHVBmsCANDescriptor diff --git a/pom.xml b/pom.xml index 2e9f608a..ed1f8d78 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ bms-jk-can bms-jk-modbus bms-jk-rs485 + bms-narada-modbus bms-pace-can bms-pylon-can bms-pylon-hv-can