Skip to content

Commit 8590d30

Browse files
committed
added HNGCE_MODBUS BMS binding
1 parent 0dc72e9 commit 8590d30

12 files changed

Lines changed: 303 additions & 3 deletions

File tree

bms-hngce-modbus/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/target/
2+
/.settings/
3+
/.classpath
4+
/.project

bms-hngce-modbus/pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.ai-republic.bms-to-inverter</groupId>
9+
<artifactId>bms-to-inverter-parent</artifactId>
10+
<version>0.0.1-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>bms-hngce-modbus</artifactId>
14+
15+
<name>${project.artifactId}-${project.version}</name>
16+
<description>Module for the Huawei BMS ModBus support</description>
17+
18+
<properties>
19+
<encoding>UTF-8</encoding>
20+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
21+
</properties>
22+
23+
<dependencies>
24+
<dependency>
25+
<groupId>com.ai-republic.bms-to-inverter</groupId>
26+
<artifactId>protocol-modbus</artifactId>
27+
<version>${project.version}</version>
28+
</dependency>
29+
</dependencies>
30+
31+
</project>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.airepublic.bmstoinverter.bms.hngce.modbus;
2+
3+
import com.airepublic.bmstoinverter.core.BMS;
4+
import com.airepublic.bmstoinverter.core.BMSConfig;
5+
import com.airepublic.bmstoinverter.core.BMSDescriptor;
6+
import com.airepublic.bmstoinverter.core.Port;
7+
import com.airepublic.bmstoinverter.protocol.modbus.J2ModMasterPort;
8+
9+
public class HngceBmsModbusDescriptor implements BMSDescriptor {
10+
11+
@Override
12+
public String getName() {
13+
return "HNGCE_MODBUS";
14+
}
15+
16+
17+
@Override
18+
public int getDefaultBaudRate() {
19+
return 9600;
20+
}
21+
22+
23+
@Override
24+
public Class<? extends BMS> getBMSClass() {
25+
return HngceBmsModbusProcessor.class;
26+
}
27+
28+
29+
@Override
30+
public Port createPort(final BMSConfig config) {
31+
return new J2ModMasterPort(config.getPortLocator(), config.getBaudRate());
32+
}
33+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package com.airepublic.bmstoinverter.bms.hngce.modbus;
2+
3+
import java.io.IOException;
4+
import java.nio.ByteBuffer;
5+
import java.nio.ByteOrder;
6+
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import com.airepublic.bmstoinverter.core.AlarmLevel;
11+
import com.airepublic.bmstoinverter.core.BMS;
12+
import com.airepublic.bmstoinverter.core.NoDataAvailableException;
13+
import com.airepublic.bmstoinverter.core.Port;
14+
import com.airepublic.bmstoinverter.core.TooManyInvalidFramesException;
15+
import com.airepublic.bmstoinverter.core.bms.data.Alarm;
16+
import com.airepublic.bmstoinverter.core.bms.data.BatteryPack;
17+
18+
public class HngceBmsModbusProcessor extends BMS {
19+
private static final Logger LOG = LoggerFactory.getLogger(HngceBmsModbusProcessor.class);
20+
21+
private static final int FUNCTION_READ_HOLDING_REGISTERS = 3;
22+
private static final int START_REGISTER = 0x0000;
23+
private static final int NUM_REGISTERS = 39;
24+
25+
@Override
26+
protected void collectData(final Port port) throws IOException, TooManyInvalidFramesException, NoDataAvailableException {
27+
final int address = getBmsId();
28+
29+
// Buffer format: [functionCode, startRegister, numRegisters, address]
30+
final ByteBuffer requestFrame = ByteBuffer.allocate(16);
31+
requestFrame.order(ByteOrder.LITTLE_ENDIAN);
32+
requestFrame.putInt(FUNCTION_READ_HOLDING_REGISTERS);
33+
requestFrame.putInt(START_REGISTER);
34+
requestFrame.putInt(NUM_REGISTERS);
35+
requestFrame.putInt(address);
36+
requestFrame.rewind();
37+
38+
port.sendFrame(requestFrame);
39+
40+
final ByteBuffer responseFrame = port.receiveFrame();
41+
42+
if (responseFrame != null && responseFrame.remaining() >= 12) {
43+
parseResponse(responseFrame);
44+
} else {
45+
throw new NoDataAvailableException();
46+
}
47+
}
48+
49+
50+
private void parseResponse(final ByteBuffer data) {
51+
final BatteryPack pack = getBatteryPack(0);
52+
if (pack == null) {
53+
LOG.error("BMS: BatteryPack not found");
54+
return;
55+
}
56+
57+
// Response header: [functionCode(int), numRegisters(int), unitId(int)]
58+
data.getInt(); // function code
59+
data.getInt(); // number of registers
60+
final int unitId = data.getInt();
61+
62+
// Read register values (each as int, but only lower 16 bits are valid)
63+
final int[] regs = new int[NUM_REGISTERS];
64+
for (int i = 0; i < NUM_REGISTERS && data.remaining() >= 4; i++) {
65+
regs[i] = data.getInt() & 0xFFFF;
66+
}
67+
68+
// HNGCE raw values: voltage/100 = V, current/100 = A
69+
// Framework displays: packVoltage/10, packCurrent/10
70+
// So we divide raw by 10 to get correct display
71+
72+
// Reg 0: Total Voltage (raw/100 = V) -> divide by 10 for framework display
73+
pack.packVoltage = regs[0] / 10; // 4908 -> 490 -> displays as 49.0V
74+
75+
// Reg 1: Current (signed, raw/100 = A) -> divide by 10 for framework display
76+
final short currentRaw = (short) regs[1];
77+
pack.packCurrent = currentRaw / 10; // -437 -> -43 -> displays as -4.3A
78+
79+
// Reg 2-17: Cell voltages (mV) - keep as-is, framework divides by 1000
80+
int cellCount = 0;
81+
int minV = Integer.MAX_VALUE, maxV = 0;
82+
int minCell = 0, maxCell = 0;
83+
84+
for (int i = 0; i < 16; i++) {
85+
final int cellV = regs[2 + i];
86+
pack.cellVmV[i] = cellV;
87+
if (cellV > 0) {
88+
cellCount++;
89+
if (cellV < minV) {
90+
minV = cellV;
91+
minCell = i + 1;
92+
}
93+
if (cellV > maxV) {
94+
maxV = cellV;
95+
maxCell = i + 1;
96+
}
97+
}
98+
}
99+
100+
pack.numberOfCells = cellCount;
101+
pack.minCellmV = minV != Integer.MAX_VALUE ? minV : 0;
102+
pack.maxCellmV = maxV;
103+
pack.minCellVNum = minCell;
104+
pack.maxCellVNum = maxCell;
105+
pack.cellDiffmV = pack.maxCellmV - pack.minCellmV;
106+
107+
// Reg 18-20: Temperatures (C) -> framework expects 0.1C
108+
pack.cellTemperature[0] = regs[18] * 10;
109+
pack.cellTemperature[1] = regs[19] * 10;
110+
pack.cellTemperature[2] = regs[20] * 10;
111+
pack.numOfTempSensors = 3;
112+
pack.tempAverage = (pack.cellTemperature[0] + pack.cellTemperature[1] + pack.cellTemperature[2]) / 3;
113+
pack.tempMax = Math.max(Math.max(pack.cellTemperature[0], pack.cellTemperature[1]), pack.cellTemperature[2]);
114+
pack.tempMin = Math.min(Math.min(pack.cellTemperature[0], pack.cellTemperature[1]), pack.cellTemperature[2]);
115+
116+
// Reg 21: Remaining capacity (Ah) -> store as mAh
117+
pack.remainingCapacitymAh = regs[21] * 1000;
118+
119+
// Reg 22: SOH (%)
120+
pack.packSOH = regs[22] * 10;
121+
122+
// Reg 23: Max Current (A) -> framework expects 0.1A units
123+
pack.maxPackDischargeCurrent = regs[23];
124+
pack.maxPackChargeCurrent = regs[23];
125+
126+
// Reg 24: SOC (%) -> framework expects 0.1% units
127+
pack.packSOC = regs[24] * 10;
128+
129+
// Reg 25: Status (2=discharge, 3=charge)
130+
pack.chargeDischargeStatus = regs[25] == 3 ? 1 : regs[25] == 2 ? 2 : 0;
131+
132+
// Reg 26-27: Alarms
133+
parseAlarms(pack, regs[26], regs[27]);
134+
135+
// Reg 30: Cycle count
136+
pack.bmsCycles = regs[30];
137+
138+
// Reg 37: Total capacity
139+
final int capRaw = regs[37];
140+
pack.ratedCapacitymAh = capRaw > 1000 ? capRaw * 100 : capRaw * 1000;
141+
142+
pack.manufacturerCode = "HNGCE";
143+
144+
LOG.info("BMS #{}: {}V, {}A, SOC={}%, Cells={}, Temp={}C, Cycles={}",
145+
unitId,
146+
String.format("%.2f", regs[0] / 100.0), // Use raw for accurate LOG
147+
String.format("%.2f", (short) regs[1] / 100.0),
148+
regs[24],
149+
pack.numberOfCells,
150+
regs[18],
151+
pack.bmsCycles);
152+
}
153+
154+
155+
private void parseAlarms(final BatteryPack pack, final int warnings, final int protections) {
156+
if ((warnings & 0x02) != 0) {
157+
pack.setAlarm(Alarm.CELL_VOLTAGE_HIGH, AlarmLevel.WARNING);
158+
}
159+
if ((warnings & 0x08) != 0) {
160+
pack.setAlarm(Alarm.CELL_VOLTAGE_LOW, AlarmLevel.WARNING);
161+
}
162+
if ((warnings & 0x10) != 0) {
163+
pack.setAlarm(Alarm.CHARGE_CURRENT_HIGH, AlarmLevel.WARNING);
164+
}
165+
if ((warnings & 0x20) != 0) {
166+
pack.setAlarm(Alarm.DISCHARGE_CURRENT_HIGH, AlarmLevel.WARNING);
167+
}
168+
if ((warnings & 0x100) != 0) {
169+
pack.setAlarm(Alarm.CHARGE_TEMPERATURE_HIGH, AlarmLevel.WARNING);
170+
}
171+
if ((warnings & 0x200) != 0) {
172+
pack.setAlarm(Alarm.DISCHARGE_TEMPERATURE_HIGH, AlarmLevel.WARNING);
173+
}
174+
if ((warnings & 0x400) != 0) {
175+
pack.setAlarm(Alarm.CHARGE_TEMPERATURE_LOW, AlarmLevel.WARNING);
176+
}
177+
if ((warnings & 0x800) != 0) {
178+
pack.setAlarm(Alarm.DISCHARGE_TEMPERATURE_LOW, AlarmLevel.WARNING);
179+
}
180+
if ((warnings & 0x1000) != 0) {
181+
pack.setAlarm(Alarm.SOC_LOW, AlarmLevel.WARNING);
182+
}
183+
184+
if ((protections & 0x02) != 0) {
185+
pack.setAlarm(Alarm.CELL_VOLTAGE_HIGH, AlarmLevel.ALARM);
186+
}
187+
if ((protections & 0x08) != 0) {
188+
pack.setAlarm(Alarm.CELL_VOLTAGE_LOW, AlarmLevel.ALARM);
189+
}
190+
if ((protections & 0x10) != 0) {
191+
pack.setAlarm(Alarm.CHARGE_CURRENT_HIGH, AlarmLevel.ALARM);
192+
}
193+
if ((protections & 0x20) != 0) {
194+
pack.setAlarm(Alarm.DISCHARGE_CURRENT_HIGH, AlarmLevel.ALARM);
195+
}
196+
if ((protections & 0x100) != 0) {
197+
pack.setAlarm(Alarm.CHARGE_TEMPERATURE_HIGH, AlarmLevel.ALARM);
198+
}
199+
if ((protections & 0x200) != 0) {
200+
pack.setAlarm(Alarm.DISCHARGE_TEMPERATURE_HIGH, AlarmLevel.ALARM);
201+
}
202+
if ((protections & 0x400) != 0) {
203+
pack.setAlarm(Alarm.CHARGE_TEMPERATURE_LOW, AlarmLevel.ALARM);
204+
}
205+
if ((protections & 0x800) != 0) {
206+
pack.setAlarm(Alarm.DISCHARGE_TEMPERATURE_LOW, AlarmLevel.ALARM);
207+
}
208+
}
209+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
5+
bean-discovery-mode="all">
6+
</beans>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.airepublic.bmstoinverter.bms.hngce.modbus.HngceBmsModbusDescriptor

bms-to-inverter-main/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@
154154
<version>${project.version}</version>
155155
</dependency>
156156

157+
<!-- #################### HNGCE (MODBUS) ################### -->
158+
<dependency>
159+
<groupId>com.ai-republic.bms-to-inverter</groupId>
160+
<artifactId>bms-hngce-modbus</artifactId>
161+
<version>${project.version}</version>
162+
</dependency>
163+
157164
<!-- #################### HUAWEI (MODBUS) ################### -->
158165
<dependency>
159166
<groupId>com.ai-republic.bms-to-inverter</groupId>
10.5 KB
Binary file not shown.

configurator/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@
117117
<version>${project.version}</version>
118118
</dependency>
119119

120+
<!-- #################### HNGCE (MODBUS) ################### -->
121+
<dependency>
122+
<groupId>com.ai-republic.bms-to-inverter</groupId>
123+
<artifactId>bms-hngce-modbus</artifactId>
124+
<version>${project.version}</version>
125+
</dependency>
126+
120127
<!-- #################### HUAWEI (MODBUS) ################### -->
121128
<dependency>
122129
<groupId>com.ai-republic.bms-to-inverter</groupId>

configurator/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.BMSDescriptor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ com.airepublic.bmstoinverter.bms.discover.can.DiscoverBmsCANDescriptor
55
com.airepublic.bmstoinverter.bms.dummy.DummyBmsDescriptor
66
com.airepublic.bmstoinverter.bms.growatt.can.GrowattBmsCANDescriptor
77
com.airepublic.bmstoinverter.bms.growatthv.can.GrowattHVBmsCANDescriptor
8+
com.airepublic.bmstoinverter.bms.hngce.modbus.HngceBmsModbusDescriptor
89
com.airepublic.bmstoinverter.bms.huawei.modbus.HuaweiBmsModbusDescriptor
910
com.airepublic.bmstoinverter.bms.jbd.rs485.JBDBmsRS485Descriptor
1011
com.airepublic.bmstoinverter.bms.jk.can.JKBmsCANDescriptor

0 commit comments

Comments
 (0)