diff --git a/bms-to-inverter-main/pom.xml b/bms-to-inverter-main/pom.xml
index 80201b0c..662bf294 100644
--- a/bms-to-inverter-main/pom.xml
+++ b/bms-to-inverter-main/pom.xml
@@ -276,6 +276,13 @@
${project.version}
+
+
+ com.ai-republic.bms-to-inverter
+ inverter-growatt-hv-can
+ ${project.version}
+
+
com.ai-republic.bms-to-inverter
diff --git a/configurator/pom.xml b/configurator/pom.xml
index 9a6d6075..1cfa3561 100644
--- a/configurator/pom.xml
+++ b/configurator/pom.xml
@@ -245,6 +245,13 @@
${project.version}
+
+
+ com.ai-republic.bms-to-inverter
+ inverter-growatt-hv-can
+ ${project.version}
+
+
com.ai-republic.bms-to-inverter
diff --git a/configurator/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.InverterDescriptor b/configurator/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.InverterDescriptor
index ccf50795..fb7f8b34 100644
--- a/configurator/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.InverterDescriptor
+++ b/configurator/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.InverterDescriptor
@@ -3,6 +3,7 @@ com.airepublic.bmstoinverter.inverter.deye.can.DeyeInverterCANDescriptor
com.airepublic.bmstoinverter.inverter.huawei.modbus.HuaweiInverterModbusDescriptor
com.airepublic.bmstoinverter.inverter.goodwe.can.GoodweInverterCANDescriptor
com.airepublic.bmstoinverter.inverter.growatt.can.GrowattInverterCANDescriptor
+com.airepublic.bmstoinverter.inverter.growatthv.can.GrowattHVInverterCANProcessor
com.airepublic.bmstoinverter.inverter.growatt.modbus.GrowattInverterModbusDescriptor
com.airepublic.bmstoinverter.inverter.pylon.can.PylonInverterCANDescriptor
com.airepublic.bmstoinverter.inverter.pylonhv.can.PylonHVInverterCANDescriptor
diff --git a/core-api/src/main/java/com/airepublic/bmstoinverter/core/bms/data/BatteryPack.java b/core-api/src/main/java/com/airepublic/bmstoinverter/core/bms/data/BatteryPack.java
index b3c739ea..624e5fa2 100644
--- a/core-api/src/main/java/com/airepublic/bmstoinverter/core/bms/data/BatteryPack.java
+++ b/core-api/src/main/java/com/airepublic/bmstoinverter/core/bms/data/BatteryPack.java
@@ -23,28 +23,25 @@
*/
public class BatteryPack {
public final Map alarms = new HashMap<>();
- // data from 0x53
/** Battery type: 0=lithium iron, 1=ternary lithium, 2=lithium titanate */
public int type;
- // data from 0x50
/** Capacity of each cell (1mAh) */
public int ratedCapacitymAh;
/** Nominal cell voltage (1mV) */
public int ratedCellmV;
- // data from 0x5A
/** Maximum total voltage (0.1V) */
public int maxPackVoltageLimit;
/** Minimum total voltage (0.1V) */
public int minPackVoltageLimit;
- // data from 0x5B
/** Maximum total charge current (0.1A) */
public int maxPackChargeCurrent;
/** Maximum total discharge current (-0.1A) */
public int maxPackDischargeCurrent;
+ /** Maximum total charge voltage (0.1V) */
+ public int maxChargeVoltage;
- // data from 0x90
/** Total pack voltage (0.1 V) */
public int packVoltage;
/** Current in (+) or out (-) of pack (0.1 A) */
@@ -193,6 +190,12 @@ public final void setAlarm(final Alarm alarm, final AlarmLevel level) {
* @return the {@link AlarmLevel} or null if not present
*/
public AlarmLevel getAlarmLevel(final Alarm alarm) {
- return alarms.get(alarm);
+ final AlarmLevel level = alarms.get(alarm);
+
+ if (level == null) {
+ return AlarmLevel.NONE;
+ }
+
+ return level;
}
}
diff --git a/inverter-growatt-hv-can/pom.xml b/inverter-growatt-hv-can/pom.xml
new file mode 100644
index 00000000..b761823d
--- /dev/null
+++ b/inverter-growatt-hv-can/pom.xml
@@ -0,0 +1,35 @@
+
+ 4.0.0
+
+ com.ai-republic.bms-to-inverter
+ bms-to-inverter-parent
+ 0.0.1-SNAPSHOT
+
+
+ inverter-growatt-hv-can
+
+ ${project.artifactId}-${project.version}
+ Module for the Growatt high voltage inverter CAN support
+
+
+ UTF-8
+ UTF-8
+
+
+
+
+ com.ai-republic.bms-to-inverter
+ core-api
+ ${project.version}
+
+
+
+ com.ai-republic.bms-to-inverter
+ protocol-can
+ ${project.version}
+
+
+
+
\ No newline at end of file
diff --git a/inverter-growatt-hv-can/src/main/java/com/airepublic/bmstoinverter/inverter/growatthv/can/GrowattHVInverterCANDescriptor.java b/inverter-growatt-hv-can/src/main/java/com/airepublic/bmstoinverter/inverter/growatthv/can/GrowattHVInverterCANDescriptor.java
new file mode 100644
index 00000000..d31e91ba
--- /dev/null
+++ b/inverter-growatt-hv-can/src/main/java/com/airepublic/bmstoinverter/inverter/growatthv/can/GrowattHVInverterCANDescriptor.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.inverter.growatthv.can;
+
+import com.airepublic.bmstoinverter.core.Inverter;
+import com.airepublic.bmstoinverter.core.InverterConfig;
+import com.airepublic.bmstoinverter.core.InverterDescriptor;
+import com.airepublic.bmstoinverter.core.Port;
+import com.airepublic.bmstoinverter.protocol.can.JavaCANPort;
+
+/**
+ * The {@link InverterDescriptor} for the Growatt HV {@link Inverter} using the CAN protocol.
+ */
+public class GrowattHVInverterCANDescriptor implements InverterDescriptor {
+ @Override
+ public String getName() {
+ return "GROWATT_HV_CAN";
+ }
+
+
+ @Override
+ public int getDefaultBaudRate() {
+ return 500000;
+ }
+
+
+ @Override
+ public Class extends Inverter> getInverterClass() {
+ return GrowattHVInverterCANProcessor.class;
+ }
+
+
+ @Override
+ public Port createPort(final InverterConfig config) {
+ final Port port = new JavaCANPort(config.getPortLocator(), config.getBaudRate());
+ return port;
+ }
+
+}
diff --git a/inverter-growatt-hv-can/src/main/java/com/airepublic/bmstoinverter/inverter/growatthv/can/GrowattHVInverterCANProcessor.java b/inverter-growatt-hv-can/src/main/java/com/airepublic/bmstoinverter/inverter/growatthv/can/GrowattHVInverterCANProcessor.java
new file mode 100644
index 00000000..52f4cec1
--- /dev/null
+++ b/inverter-growatt-hv-can/src/main/java/com/airepublic/bmstoinverter/inverter/growatthv/can/GrowattHVInverterCANProcessor.java
@@ -0,0 +1,347 @@
+/**
+ * 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.inverter.growatthv.can;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.airepublic.bmstoinverter.core.AlarmLevel;
+import com.airepublic.bmstoinverter.core.Inverter;
+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.protocol.can.CANPort;
+import com.airepublic.bmstoinverter.core.util.BitUtil;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+/**
+ * The class to handle CAN messages for a Growatt HV {@link Inverter}.
+ */
+@ApplicationScoped
+public class GrowattHVInverterCANProcessor extends Inverter {
+ private final static Logger LOG = LoggerFactory.getLogger(GrowattHVInverterCANProcessor.class);
+
+ @Override
+ protected ByteBuffer readRequest(final Port port) throws IOException {
+ return port.receiveFrame();
+ }
+
+
+ @Override
+ protected void sendFrame(final Port port, final ByteBuffer frame) throws IOException {
+ ((CANPort) port).sendExtendedFrame(frame);
+ }
+
+
+ @Override
+ protected List createSendFrames(final ByteBuffer requestFrame, final BatteryPack aggregatedPack) {
+ final List sendFrames = new ArrayList<>();
+
+ try {
+ // 0x3110
+ sendFrames.add(sendChargeDischargeLimits(aggregatedPack));
+ // 0x3120
+ sendFrames.add(sendAlarms(aggregatedPack));
+ // 0x3130
+ sendFrames.add(sendBatteryStatus(aggregatedPack));
+ // 0x3140
+ sendFrames.add(sendBatteryCapacity(aggregatedPack));
+ // 0x3150
+ sendFrames.add(sendWorkingParams(aggregatedPack));
+ // 0x3160
+ sendFrames.add(sendFaultAndVoltageNumbers(aggregatedPack));
+ // 0x3170
+ sendFrames.add(sendMinMaxCellTemperatures(aggregatedPack));
+
+ } catch (final Throwable e) {
+ LOG.error("Error creating send frames: ", e);
+ }
+
+ return sendFrames;
+ }
+
+
+ protected ByteBuffer prepareSendFrame(final int frameId) {
+ final ByteBuffer sendFrame = ByteBuffer.allocateDirect(16).order(ByteOrder.BIG_ENDIAN);
+ sendFrame.putInt(frameId);
+
+ // header
+ sendFrame.put((byte) 0x08) // data length
+ .put((byte) 0) // flags
+ .putShort((short) 0); // skip 2 bytes
+
+ return sendFrame;
+ }
+
+
+ // 0x3110
+ private ByteBuffer sendChargeDischargeLimits(final BatteryPack pack) throws IOException {
+ final ByteBuffer frame = prepareSendFrame(0x00003110);
+
+ // Charge cutoff voltage (0.1V)
+ frame.putChar((char) (pack.maxChargeVoltage != 0 ? pack.maxChargeVoltage : pack.maxPackVoltageLimit));
+ // Max charge current (0.1A) offset 0A
+ frame.putChar((char) pack.maxPackChargeCurrent);
+ // Max discharge current (0.1A) offset -3000A
+ frame.putChar((char) (pack.maxPackDischargeCurrent * -1));
+
+ // Battery status
+ short status = 0x0000;
+ switch (pack.chargeDischargeStatus) {
+ case 0: {
+ // standby/idle
+ status = BitUtil.setBit(status, 0, false); // Byte 7 bit 0
+ status = BitUtil.setBit(status, 1, true); // Byte 7 bit 1
+ }
+ break;
+ case 1: {
+ // discharging
+ status = BitUtil.setBit(status, 0, true); // Byte 7 bit 0
+ status = BitUtil.setBit(status, 1, false); // Byte 7 bit 1
+ }
+ break;
+ case 2: {
+ // charging
+ status = BitUtil.setBit(status, 0, true); // Byte 7 bit 0
+ status = BitUtil.setBit(status, 1, true); // Byte 7 bit 1
+ }
+ break;
+ case 3: {
+ // battery sleeping state
+ status = BitUtil.setBit(status, 4, true); // Byte 7 bit 4
+ }
+ break;
+ }
+
+ // fault flag
+ status = BitUtil.setBit(status, 2, false); // Byte 7 bit 2
+ // cell balancing state
+ status = BitUtil.setBit(status, 3, pack.cellBalanceActive); // Byte 7 bit 3
+
+ LOG.debug("Sending max/min charge and discharge voltage and current limits: {}", Port.printBuffer(frame));
+ return frame;
+ }
+
+
+ // 0x3120
+ private ByteBuffer sendAlarms(final BatteryPack pack) throws IOException {
+ final ByteBuffer frame = prepareSendFrame(0x00003120);
+
+ // Protection
+ int protection = 0x00000000;
+ protection = BitUtil.setBit(protection, 1, pack.getAlarmLevel(Alarm.PACK_VOLTAGE_LOW) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 2, pack.getAlarmLevel(Alarm.PACK_VOLTAGE_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 3, pack.getAlarmLevel(Alarm.CELL_VOLTAGE_LOW) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 4, pack.getAlarmLevel(Alarm.CELL_VOLTAGE_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 5, pack.getAlarmLevel(Alarm.FAILURE_SHORT_CIRCUIT_PROTECTION) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 6, pack.getAlarmLevel(Alarm.CHARGE_CURRENT_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 7, pack.getAlarmLevel(Alarm.DISCHARGE_CURRENT_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 8, pack.getAlarmLevel(Alarm.DISCHARGE_VOLTAGE_LOW) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 9, pack.getAlarmLevel(Alarm.CHARGE_VOLTAGE_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 10, pack.getAlarmLevel(Alarm.CELL_VOLTAGE_DIFFERENCE_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 11, pack.getAlarmLevel(Alarm.FAILURE_OTHER) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 12, pack.getAlarmLevel(Alarm.CHARGE_TEMPERATURE_LOW) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 13, pack.getAlarmLevel(Alarm.DISCHARGE_TEMPERATURE_LOW) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 14, pack.getAlarmLevel(Alarm.CHARGE_TEMPERATURE_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 15, pack.getAlarmLevel(Alarm.DISCHARGE_TEMPERATURE_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 16, pack.getAlarmLevel(Alarm.SOC_LOW) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 17, pack.getAlarmLevel(Alarm.TEMPERATURE_SENSOR_DIFFERENCE_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 18, pack.getAlarmLevel(Alarm.CHARGE_MODULE_TEMPERATURE_HIGH) == AlarmLevel.ALARM);
+ protection = BitUtil.setBit(protection, 19, pack.getAlarmLevel(Alarm.ENCASING_TEMPERATURE_HIGH) == AlarmLevel.ALARM);
+
+ frame.putInt(protection);
+
+ // Alarm
+ int alarm = 0x00000000;
+ alarm = BitUtil.setBit(alarm, 0, pack.getAlarmLevel(Alarm.FAILURE_COMMUNICATION_INTERNAL) == AlarmLevel.WARNING);
+
+ alarm = BitUtil.setBit(alarm, 2, pack.getAlarmLevel(Alarm.CELL_VOLTAGE_DIFFERENCE_HIGH) == AlarmLevel.WARNING);
+
+ alarm = BitUtil.setBit(alarm, 4, pack.getAlarmLevel(Alarm.CHARGE_TEMPERATURE_LOW) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 5, pack.getAlarmLevel(Alarm.DISCHARGE_TEMPERATURE_LOW) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 6, pack.getAlarmLevel(Alarm.CHARGE_TEMPERATURE_HIGH) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 7, pack.getAlarmLevel(Alarm.DISCHARGE_TEMPERATURE_HIGH) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 8, pack.getAlarmLevel(Alarm.DISCHARGE_VOLTAGE_LOW) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 9, pack.getAlarmLevel(Alarm.PACK_VOLTAGE_LOW) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 10, pack.getAlarmLevel(Alarm.PACK_VOLTAGE_HIGH) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 11, pack.getAlarmLevel(Alarm.CELL_VOLTAGE_LOW) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 12, pack.getAlarmLevel(Alarm.CELL_VOLTAGE_HIGH) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 13, pack.getAlarmLevel(Alarm.CHARGE_VOLTAGE_HIGH) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 14, pack.getAlarmLevel(Alarm.CHARGE_CURRENT_HIGH) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 15, pack.getAlarmLevel(Alarm.DISCHARGE_CURRENT_HIGH) == AlarmLevel.WARNING);
+
+ alarm = BitUtil.setBit(alarm, 17, pack.getAlarmLevel(Alarm.SOC_LOW) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 18, pack.getAlarmLevel(Alarm.TEMPERATURE_SENSOR_DIFFERENCE_HIGH) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 19, pack.getAlarmLevel(Alarm.CHARGE_MODULE_TEMPERATURE_HIGH) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 20, pack.getAlarmLevel(Alarm.ENCASING_TEMPERATURE_HIGH) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 21, pack.getAlarmLevel(Alarm.FAILURE_COMMUNICATION_EXTERNAL) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 22, pack.getAlarmLevel(Alarm.FAILURE_COMMUNICATION_INTERNAL) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 23, pack.getAlarmLevel(Alarm.FAILURE_SHORT_CIRCUIT_PROTECTION) == AlarmLevel.WARNING);
+ alarm = BitUtil.setBit(alarm, 24, pack.getAlarmLevel(Alarm.SOC_LOW) == AlarmLevel.WARNING);
+
+ frame.putInt(alarm);
+
+ LOG.debug("Sending alarms: {}", Port.printBuffer(frame));
+ return frame;
+ }
+
+
+ // 0x3130
+ private ByteBuffer sendBatteryStatus(final BatteryPack pack) throws IOException {
+ final ByteBuffer frame = prepareSendFrame(0x00003130);
+
+ // Battery voltage (0.1V)
+ frame.putChar((char) pack.packVoltage);
+ // Battery current (0.1A) offset -3000A
+ frame.putShort((short) pack.packCurrent);
+ // second level temperature (0.1 Celcius) offset -100C
+ frame.putShort((short) pack.tempAverage);
+ // Battery SOC (1%)
+ frame.put((byte) (pack.packSOC / 10));
+ // Battery SOH (1%)
+ frame.put((byte) (pack.packSOH / 10));
+
+ LOG.debug("Sending battery status: {}", Port.printBuffer(frame));
+ return frame;
+ }
+
+
+ // 0x3140
+ private ByteBuffer sendBatteryCapacity(final BatteryPack pack) {
+ final ByteBuffer frame = prepareSendFrame(0x00003140);
+
+ // Current battery energy (10mAH)
+ frame.putChar((char) (pack.remainingCapacitymAh / 10));
+ // Rated battery energy (10mAH)
+ frame.putChar((char) (pack.ratedCapacitymAh / 10));
+ // manufacturer code
+ frame.putChar(pack.manufacturerCode.charAt(0));
+ // cycle count
+ frame.putChar((char) pack.bmsCycles);
+
+ LOG.debug("Sending battery capacity: {}", Port.printBuffer(frame));
+ return frame;
+ }
+
+
+ // 0x3150
+ private ByteBuffer sendWorkingParams(final BatteryPack pack) {
+ final ByteBuffer frame = prepareSendFrame(0x00003150);
+
+ // max discharge voltage (0.1V)
+ frame.putChar((char) pack.minPackVoltageLimit);
+ // case temperature (0.1C)
+ frame.putChar((char) pack.tempAverage);
+ // number of cells
+ frame.putChar((char) getEnergyStorage().getBatteryPacks().stream().mapToInt(p -> p.numberOfCells).sum());
+ // number of packs
+ frame.putChar((char) getEnergyStorage().getBatteryPacks().size());
+
+ LOG.debug("Sending working params: {}", Port.printBuffer(frame));
+ return frame;
+ }
+
+
+ // 0x3160
+ private ByteBuffer sendFaultAndVoltageNumbers(final BatteryPack pack) {
+ final ByteBuffer frame = prepareSendFrame(0x00003160);
+ short faultFlags = 0x0000;
+
+ faultFlags = BitUtil.setBit(faultFlags, 0, pack.getAlarmLevel(Alarm.FAILURE_SENSOR_PACK_VOLTAGE) != AlarmLevel.NONE);
+ faultFlags = BitUtil.setBit(faultFlags, 1, pack.getAlarmLevel(Alarm.FAILURE_SENSOR_CELL_TEMPERATURE) != AlarmLevel.NONE);
+ faultFlags = BitUtil.setBit(faultFlags, 2, pack.getAlarmLevel(Alarm.FAILURE_COMMUNICATION_INTERNAL) != AlarmLevel.NONE);
+
+ // fault flags
+ frame.putShort(faultFlags);
+
+ int minCellmV = Integer.MAX_VALUE;
+ int maxCellmV = Integer.MIN_VALUE;
+ byte maxVBatteryPackNumber = 0;
+ byte minVBatteryPackNumber = 0;
+
+ for (int i = 0; i < getEnergyStorage().getBatteryPacks().size(); i++) {
+ final BatteryPack p = getEnergyStorage().getBatteryPacks().get(i);
+ final int packMaxCellmV = Arrays.stream(p.cellVmV).max().orElse(Integer.MIN_VALUE);
+ final int packMinCellmV = Arrays.stream(p.cellVmV).min().orElse(Integer.MAX_VALUE);
+
+ if (packMinCellmV < minCellmV) {
+ minCellmV = packMinCellmV;
+ minVBatteryPackNumber = (byte) i;
+ }
+
+ if (packMaxCellmV > maxCellmV) {
+ maxCellmV = packMaxCellmV;
+ maxVBatteryPackNumber = (byte) i;
+ }
+ }
+
+ // number of module with max cell voltage
+ frame.put(maxVBatteryPackNumber);
+ // cell number with max voltage
+ frame.put((byte) pack.maxCellVNum);
+ // number of module with min cell voltage
+ frame.put(minVBatteryPackNumber);
+ // cell number with min voltage
+ frame.put((byte) pack.minCellVNum);
+ // min cell temperature
+ frame.put((byte) pack.tempMin);
+
+ LOG.debug("Sending fault and voltage numbers: {}", Port.printBuffer(frame));
+ return frame;
+ }
+
+
+ // 0x3170
+ private ByteBuffer sendMinMaxCellTemperatures(final BatteryPack pack) {
+ final ByteBuffer frame = prepareSendFrame(0x00003170);
+
+ int minTemp = Integer.MAX_VALUE;
+ int maxTemp = Integer.MIN_VALUE;
+ byte minCellNo = 0;
+ byte maxCellNo = 0;
+ byte minTempPackNumber = 0;
+ byte maxTempPackNumber = 0;
+
+ for (int i = 0; i < getEnergyStorage().getBatteryPacks().size(); i++) {
+ final BatteryPack p = getEnergyStorage().getBatteryPacks().get(i);
+
+ if (p.tempMin < minTemp) {
+ minTemp = p.tempMin;
+ minCellNo = (byte) p.tempMinCellNum;
+ minTempPackNumber = (byte) i;
+ }
+
+ if (p.tempMax > maxTemp) {
+ maxTemp = p.tempMax;
+ maxCellNo = (byte) p.tempMaxCellNum;
+ maxTempPackNumber = (byte) i;
+ }
+ }
+
+ frame.put(maxTempPackNumber);
+ frame.put(maxCellNo);
+ frame.put(minTempPackNumber);
+ frame.put(minCellNo);
+
+ LOG.debug("Sending min/max cell temperaturs: {}", Port.printBuffer(frame));
+
+ return frame;
+ }
+
+}
diff --git a/inverter-growatt-hv-can/src/main/resources/META-INF/beans.xml b/inverter-growatt-hv-can/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000..e5609411
--- /dev/null
+++ b/inverter-growatt-hv-can/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/inverter-growatt-hv-can/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.InverterDescriptor b/inverter-growatt-hv-can/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.InverterDescriptor
new file mode 100644
index 00000000..e6752559
--- /dev/null
+++ b/inverter-growatt-hv-can/src/main/resources/META-INF/services/com.airepublic.bmstoinverter.core.InverterDescriptor
@@ -0,0 +1 @@
+com.airepublic.bmstoinverter.inverter.growatthv.can.GrowattHVInverterCANProcessor
diff --git a/pom.xml b/pom.xml
index f5186075..ad4612e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,7 @@
inverter-dummy
inverter-goodwe-can
inverter-growatt-can
+ inverter-growatt-hv-can
inverter-growatt-modbus
inverter-growatt-rs485
inverter-pylon-can