[modbus] Modbus register array backed by bytes and other simplifications (#8865)

Signed-off-by: Sami Salonen <ssalonen@gmail.com>
This commit is contained in:
Sami Salonen
2020-11-26 19:07:49 +02:00
committed by GitHub
parent ff038bc9bb
commit b806ed45d0
32 changed files with 2140 additions and 655 deletions

View File

@@ -12,11 +12,12 @@
*/
package org.openhab.binding.modbus.e3dc.internal.dto;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.BitSet;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.io.transport.modbus.ModbusBitUtilities;
import org.openhab.io.transport.modbus.ValueBuffer;
/**
* The {@link DataConverter} Helper class to convert bytes from modbus into desired data format
@@ -25,27 +26,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/
@NonNullByDefault
public class DataConverter {
private static final long MAX_INT32 = (long) Math.pow(2, Integer.SIZE);
/**
* Get unit16 value from 2 bytes
*
* @param wrap
* @return int
*/
public static int getUInt16Value(ByteBuffer wrap) {
return Short.toUnsignedInt(wrap.getShort());
}
/**
* Get unit32 value from 4 bytes
*
* @param wrap
* @return long
*/
public static long getLongValue(ByteBuffer wrap) {
return Integer.toUnsignedLong(wrap.getInt());
}
/**
* Get double value from 2 bytes with correction factor
@@ -53,28 +33,12 @@ public class DataConverter {
* @param wrap
* @return double
*/
public static double getUDoubleValue(ByteBuffer wrap, double factor) {
return round(getUInt16Value(wrap) * factor, 2);
}
/**
* Conversion done according to E3DC Modbus Specification V1.7
*
* @param wrap
* @return decoded long value, Long.MIN_VALUE otherwise
*/
public static long getInt32Swap(ByteBuffer wrap) {
long a = getUInt16Value(wrap);
long b = getUInt16Value(wrap);
if (b < 32768) {
return b * 65536 + a;
} else {
return (MAX_INT32 - b * 65536 - a) * -1;
}
public static double getUDoubleValue(ValueBuffer wrap, double factor) {
return round(wrap.getUInt16() * factor, 2);
}
public static String getString(byte[] bArray) {
return new String(bArray, StandardCharsets.US_ASCII).trim();
return ModbusBitUtilities.extractStringFromBytes(bArray, 0, bArray.length, StandardCharsets.US_ASCII).trim();
}
public static int toInt(BitSet bitSet) {
@@ -93,8 +57,7 @@ public class DataConverter {
}
long factor = (long) Math.pow(10, places);
value = value * factor;
long tmp = Math.round(value);
long tmp = Math.round(value * factor);
return (double) tmp / factor;
}
}

View File

@@ -14,13 +14,13 @@ package org.openhab.binding.modbus.e3dc.internal.dto;
import static org.openhab.binding.modbus.e3dc.internal.modbus.E3DCModbusConstans.*;
import java.nio.ByteBuffer;
import java.util.BitSet;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.io.transport.modbus.ModbusBitUtilities;
/**
* The {@link EmergencyBlock} Data object for E3DC Info Block
@@ -55,7 +55,7 @@ public class EmergencyBlock implements Data {
*/
public EmergencyBlock(byte[] bArray) {
// uint16 status register 40084 - possible Status Strings are defined in Constants above
int status = DataConverter.getUInt16Value(ByteBuffer.wrap(bArray));
int status = ModbusBitUtilities.extractUInt16(bArray, 0);
if (status >= 0 && status < 5) {
epStatus = EP_STATUS_ARRAY[status];
} else {

View File

@@ -12,13 +12,12 @@
*/
package org.openhab.binding.modbus.e3dc.internal.dto;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.util.HexUtils;
import org.openhab.io.transport.modbus.ValueBuffer;
/**
* The {@link InfoBlock} Data object for E3DC Info Block
@@ -43,7 +42,7 @@ public class InfoBlock implements Data {
*/
public InfoBlock(byte[] bArray) {
// index handling to calculate the correct start index
ByteBuffer wrapper = ByteBuffer.wrap(bArray);
ValueBuffer wrapper = ValueBuffer.wrap(bArray);
// first uint16 = 2 bytes - decode magic byte
byte[] magicBytes = new byte[2];
@@ -52,11 +51,11 @@ public class InfoBlock implements Data {
// first uint16 = 2 bytes - decode magic byte
// unit8 (Modbus Major Version) + uint8 Modbus minor Version
String modbusVersion = wrapper.get() + "." + wrapper.get();
String modbusVersion = wrapper.getSInt8() + "." + wrapper.getSInt8();
this.modbusVersion = new StringType(modbusVersion);
// unit16 - supported registers
short supportedRegisters = wrapper.getShort();
short supportedRegisters = wrapper.getSInt16();
this.supportedRegisters = new DecimalType(supportedRegisters);
byte[] buffer = new byte[32];

View File

@@ -12,8 +12,6 @@
*/
package org.openhab.binding.modbus.e3dc.internal.dto;
import java.nio.ByteBuffer;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Power;
@@ -21,6 +19,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.io.transport.modbus.ValueBuffer;
/**
* The {@link PowerBlock} Data object for E3DC Info Block
@@ -49,10 +48,10 @@ public class PowerBlock implements Data {
*/
public PowerBlock(byte[] bArray) {
// index handling to calculate the correct start index
ByteBuffer wrap = ByteBuffer.wrap(bArray);
ValueBuffer wrap = ValueBuffer.wrap(bArray);
// int32_swap value = 4 byte
long pvPowerSupplyL = DataConverter.getInt32Swap(wrap);
long pvPowerSupplyL = wrap.getSInt32Swap();
/*
* int32_swap value don't provide negative values!
@@ -60,7 +59,7 @@ public class PowerBlock implements Data {
* Negative value - Battery is discharging = Power supplier
*/
pvPowerSupply = QuantityType.valueOf(pvPowerSupplyL, SmartHomeUnits.WATT);
long batteryPower = DataConverter.getInt32Swap(wrap);
long batteryPower = wrap.getSInt32Swap();
if (batteryPower > 0) {
// Battery is charging so Power is consumed by Battery
batteryPowerSupply = QuantityType.valueOf(0, SmartHomeUnits.WATT);
@@ -72,7 +71,7 @@ public class PowerBlock implements Data {
}
// int32_swap value = 4 byte
long householdPowerConsumptionL = DataConverter.getInt32Swap(wrap);
long householdPowerConsumptionL = wrap.getSInt32Swap();
householdPowerConsumption = QuantityType.valueOf(householdPowerConsumptionL, SmartHomeUnits.WATT);
/*
@@ -80,7 +79,7 @@ public class PowerBlock implements Data {
* Positive value - Power provided towards Grid = Power consumer
* Negative value - Power requested from Grid = Power supplier
*/
long gridPower = DataConverter.getInt32Swap(wrap);
long gridPower = wrap.getSInt32Swap();
if (gridPower > 0) {
// Power is provided by Grid
gridPowerSupply = QuantityType.valueOf(gridPower, SmartHomeUnits.WATT);
@@ -92,19 +91,19 @@ public class PowerBlock implements Data {
}
// int32_swap value = 4 byte
externalPowerSupply = QuantityType.valueOf(DataConverter.getInt32Swap(wrap), SmartHomeUnits.WATT);
externalPowerSupply = QuantityType.valueOf(wrap.getSInt32Swap(), SmartHomeUnits.WATT);
// int32_swap value = 4 byte
wallboxPowerConsumption = QuantityType.valueOf(DataConverter.getInt32Swap(wrap), SmartHomeUnits.WATT);
wallboxPowerConsumption = QuantityType.valueOf(wrap.getSInt32Swap(), SmartHomeUnits.WATT);
// int32_swap value = 4 byte
wallboxPVPowerConsumption = QuantityType.valueOf(DataConverter.getInt32Swap(wrap), SmartHomeUnits.WATT);
wallboxPVPowerConsumption = QuantityType.valueOf(wrap.getSInt32Swap(), SmartHomeUnits.WATT);
// unit8 + uint8 - one register with split value for Autarky & Self Consumption
autarky = QuantityType.valueOf(wrap.get(), SmartHomeUnits.PERCENT);
selfConsumption = QuantityType.valueOf(wrap.get(), SmartHomeUnits.PERCENT);
autarky = QuantityType.valueOf(wrap.getSInt8(), SmartHomeUnits.PERCENT);
selfConsumption = QuantityType.valueOf(wrap.getSInt8(), SmartHomeUnits.PERCENT);
// uint16 for Battery State of Charge
batterySOC = QuantityType.valueOf(wrap.getShort(), SmartHomeUnits.PERCENT);
batterySOC = QuantityType.valueOf(wrap.getSInt16(), SmartHomeUnits.PERCENT);
}
}

View File

@@ -12,8 +12,6 @@
*/
package org.openhab.binding.modbus.e3dc.internal.dto;
import java.nio.ByteBuffer;
import javax.measure.quantity.ElectricCurrent;
import javax.measure.quantity.ElectricPotential;
import javax.measure.quantity.Power;
@@ -22,6 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.io.transport.modbus.ValueBuffer;
/**
* The {@link StringBlock} Data object for E3DC Info Block
@@ -46,17 +45,17 @@ public class StringBlock implements Data {
* @param bArray - Modbus Registers as bytes from 40096 to 40104
*/
public StringBlock(byte[] bArray) {
ByteBuffer wrap = ByteBuffer.wrap(bArray);
ValueBuffer wrap = ValueBuffer.wrap(bArray);
// straight forward - for each String the values Volt, Ampere and then Watt. All unt16 = 2 bytes values
string1Volt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.VOLT);
string2Volt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.VOLT);
string3Volt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.VOLT);
string1Volt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.VOLT);
string2Volt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.VOLT);
string3Volt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.VOLT);
// E3DC Modbus Spec chapter 3.1.2, page 16 - Ampere values shall be handled with factor 0.01
string1Ampere = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.01), SmartHomeUnits.AMPERE);
string2Ampere = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.01), SmartHomeUnits.AMPERE);
string3Ampere = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.01), SmartHomeUnits.AMPERE);
string1Watt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.WATT);
string2Watt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.WATT);
string3Watt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.WATT);
string1Watt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.WATT);
string2Watt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.WATT);
string3Watt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.WATT);
}
}

View File

@@ -25,7 +25,6 @@ import org.openhab.binding.modbus.e3dc.internal.dto.StringBlock;
import org.openhab.binding.modbus.e3dc.internal.dto.WallboxArray;
import org.openhab.binding.modbus.e3dc.internal.modbus.Data.DataType;
import org.openhab.io.transport.modbus.AsyncModbusReadResult;
import org.openhab.io.transport.modbus.ModbusRegister;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -59,17 +58,10 @@ public class Parser {
}
public void handle(AsyncModbusReadResult result) {
byte[] newArray = new byte[size];
long startTime = System.currentTimeMillis();
Optional<ModbusRegisterArray> opt = result.getRegisters();
if (opt.isPresent()) {
ModbusRegisterArray registers = opt.get();
int i = 0;
for (ModbusRegister reg : registers) {
System.arraycopy(reg.getBytes(), 0, newArray, i, 2);
i += 2;
}
setArray(newArray);
setArray(opt.get().getBytes());
long duration = System.currentTimeMillis() - startTime;
avgDuration += duration;

View File

@@ -35,14 +35,26 @@ import org.openhab.core.library.types.OnOffType;
*/
public class DataBlockTest {
private Parser mc;
private Parser mcNegativePVSupply;
@BeforeEach
public void setup() {
byte[] dataBlock = new byte[] { 0, -14, 0, 0, -2, -47, -1, -1, 2, 47, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 99, 99, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 0, 0 };
mc = new Parser(DataType.DATA);
mc.setArray(dataBlock);
{
byte[] dataBlock = new byte[] { 0, -14, 0, 0, -2, -47, -1, -1, 2, 47, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 99, 99, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 0, 0 };
mc = new Parser(DataType.DATA);
mc.setArray(dataBlock);
}
{
// 65098 bytes [-2, 74]
// 65535 bytes [-1, -1]
byte[] dataBlock = new byte[] { -2, -74, -1, -1, -2, -47, -1, -1, 2, 47, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 99, 99, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 0, 0 };
mcNegativePVSupply = new Parser(DataType.DATA);
mcNegativePVSupply.setArray(dataBlock);
}
}
@Test
@@ -56,6 +68,17 @@ public class DataBlockTest {
assertEquals("303.0 W", b.batteryPowerSupply.toString(), "Battery Supply");
}
@Test
public void testValidPowerBlockNegativePVSupply() {
Optional<Data> dataOpt = mcNegativePVSupply.parse(DataType.POWER);
assertTrue(dataOpt.isPresent());
PowerBlock b = (PowerBlock) dataOpt.get();
assertEquals("-330.0 W", b.pvPowerSupply.toString(), "PV Supply");
assertEquals("14.0 W", b.gridPowerSupply.toString(), "Grid Supply");
assertEquals("0.0 W", b.gridPowerConsumpition.toString(), "Grid Consumption");
assertEquals("303.0 W", b.batteryPowerSupply.toString(), "Battery Supply");
}
@Test
public void testValidWallboxBlock() {
Optional<Data> wba = mc.parse(DataType.WALLBOX);

View File

@@ -14,7 +14,6 @@ package org.openhab.binding.modbus.e3dc.internal.handler;
import static org.mockito.Mockito.*;
import java.nio.ByteBuffer;
import java.util.HashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -31,7 +30,6 @@ import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.io.transport.modbus.AsyncModbusFailure;
import org.openhab.io.transport.modbus.AsyncModbusReadResult;
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.io.transport.modbus.ModbusRegister;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
/**
@@ -92,26 +90,16 @@ public class E3DCHandlerStateTest {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 78, 73, 78, 73, 84, 73, 65, 76, 73, 90, 69,
68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 49, 48, 95, 50, 48, 50, 48, 95, 48, 52,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
ByteBuffer infoWrap = ByteBuffer.wrap(infoBlockBytes);
ModbusRegister[] infoBlock = new ModbusRegister[infoBlockBytes.length / 2];
for (int i = 0; i < infoBlock.length; i++) {
infoBlock[i] = new ModbusRegister(infoWrap.get(), infoWrap.get());
}
ModbusReadRequestBlueprint readRequest = mock(ModbusReadRequestBlueprint.class);
return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(infoBlock));
return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(infoBlockBytes));
}
private AsyncModbusReadResult getDataResult() {
byte[] dataBlockBytes = new byte[] { 0, -14, 0, 0, -2, -47, -1, -1, 2, 47, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 99, 99, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 0, 0 };
ByteBuffer dataWrap = ByteBuffer.wrap(dataBlockBytes);
ModbusRegister[] dataBlock = new ModbusRegister[dataBlockBytes.length / 2];
for (int i = 0; i < dataBlock.length; i++) {
dataBlock[i] = new ModbusRegister(dataWrap.get(), dataWrap.get());
}
ModbusReadRequestBlueprint readRequest = mock(ModbusReadRequestBlueprint.class);
return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(dataBlock));
return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(dataBlockBytes));
}
private AsyncModbusFailure<ModbusReadRequestBlueprint> getFailResult() {

View File

@@ -14,12 +14,12 @@ package org.openhab.binding.modbus.e3dc.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.nio.ByteBuffer;
import java.util.BitSet;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.modbus.e3dc.internal.dto.DataConverter;
import org.openhab.io.transport.modbus.ValueBuffer;
/**
* The {@link DataConverterTest} Test data conversions
@@ -30,17 +30,58 @@ import org.openhab.binding.modbus.e3dc.internal.dto.DataConverter;
public class DataConverterTest {
@Test
public void testE3DCSwapValueNegative() {
// Reg 69 value 65098 bytes [-2, 74]
// Reg 70 value 65535 bytes [-1, -1]
byte[] b = new byte[] { -2, -74, -1, -1 };
assertEquals(-330, DataConverter.getInt32Swap(ByteBuffer.wrap(b)), "Negative Value");
public void testRoundPositive() {
assertEquals(2.3, DataConverter.round(2.34, 1), 0.01);
}
@Test
public void testBitset() {
public void testRoundPositive2() {
assertEquals(2.4, DataConverter.round(2.37, 1), 0.01);
}
@Test
public void testRoundPositive3() {
assertEquals(2.4, DataConverter.round(2.35, 1), 0.01);
}
@Test
public void testRoundNegative() {
assertEquals(-2.3, DataConverter.round(-2.34, 1), 0.01);
}
@Test
public void testRoundNegative2() {
assertEquals(-2.4, DataConverter.round(-2.37, 1), 0.01);
}
@Test
public void testRoundNegative3() {
// rounding towards positive infinity. Note difference to testRoundPositive3
assertEquals(-2.3, DataConverter.round(-2.35, 1), 0.01);
}
@Test
public void testUDoubleValue() {
assertEquals(0.5, DataConverter.getUDoubleValue(ValueBuffer.wrap(new byte[] { 0, 5 }), 0.1), 0.01);
}
@Test
public void testUDoubleValue2() {
assertEquals(6159.9,
DataConverter.getUDoubleValue(ValueBuffer.wrap(new byte[] { (byte) 0xf0, (byte) 0x9f }), 0.1), 0.01);
}
@Test
public void testUDoubleValue3() {
assertEquals(123198,
DataConverter.getUDoubleValue(ValueBuffer.wrap(new byte[] { (byte) 0xf0, (byte) 0x9f }), 2), 0.01);
}
@Test
public void testBitsetToInt() {
byte[] b = new byte[] { 3, 16 };
BitSet s = BitSet.valueOf(b);
// Bit0 is the least significant bit to DataConverter.toInt
assertEquals(true, s.get(0), "Bit0");
assertEquals(true, s.get(1), "Bit1");
assertEquals(false, s.get(2), "Bit2");
@@ -57,5 +98,10 @@ public class DataConverterTest {
assertEquals(false, s.get(13), "Bit13");
assertEquals(false, s.get(14), "Bit14");
assertEquals(false, s.get(15), "Bit15");
int bitsAsInt = DataConverter.toInt(s);
int expected = 0b0001000000000011;
assertEquals(Integer.toBinaryString(expected), Integer.toBinaryString(bitsAsInt));
assertEquals(expected, bitsAsInt);
}
}