[modbus] Modbus register array backed by bytes and other simplifications (#8865)
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
This commit is contained in:
@@ -12,11 +12,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.modbus.e3dc.internal.dto;
|
package org.openhab.binding.modbus.e3dc.internal.dto;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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
|
* 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
|
@NonNullByDefault
|
||||||
public class DataConverter {
|
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
|
* Get double value from 2 bytes with correction factor
|
||||||
@@ -53,28 +33,12 @@ public class DataConverter {
|
|||||||
* @param wrap
|
* @param wrap
|
||||||
* @return double
|
* @return double
|
||||||
*/
|
*/
|
||||||
public static double getUDoubleValue(ByteBuffer wrap, double factor) {
|
public static double getUDoubleValue(ValueBuffer wrap, double factor) {
|
||||||
return round(getUInt16Value(wrap) * factor, 2);
|
return round(wrap.getUInt16() * 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 String getString(byte[] bArray) {
|
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) {
|
public static int toInt(BitSet bitSet) {
|
||||||
@@ -93,8 +57,7 @@ public class DataConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long factor = (long) Math.pow(10, places);
|
long factor = (long) Math.pow(10, places);
|
||||||
value = value * factor;
|
long tmp = Math.round(value * factor);
|
||||||
long tmp = Math.round(value);
|
|
||||||
return (double) tmp / factor;
|
return (double) tmp / factor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ package org.openhab.binding.modbus.e3dc.internal.dto;
|
|||||||
|
|
||||||
import static org.openhab.binding.modbus.e3dc.internal.modbus.E3DCModbusConstans.*;
|
import static org.openhab.binding.modbus.e3dc.internal.modbus.E3DCModbusConstans.*;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
|
import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.StringType;
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link EmergencyBlock} Data object for E3DC Info Block
|
* The {@link EmergencyBlock} Data object for E3DC Info Block
|
||||||
@@ -55,7 +55,7 @@ public class EmergencyBlock implements Data {
|
|||||||
*/
|
*/
|
||||||
public EmergencyBlock(byte[] bArray) {
|
public EmergencyBlock(byte[] bArray) {
|
||||||
// uint16 status register 40084 - possible Status Strings are defined in Constants above
|
// 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) {
|
if (status >= 0 && status < 5) {
|
||||||
epStatus = EP_STATUS_ARRAY[status];
|
epStatus = EP_STATUS_ARRAY[status];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -12,13 +12,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.modbus.e3dc.internal.dto;
|
package org.openhab.binding.modbus.e3dc.internal.dto;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
|
import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.StringType;
|
import org.openhab.core.library.types.StringType;
|
||||||
import org.openhab.core.util.HexUtils;
|
import org.openhab.core.util.HexUtils;
|
||||||
|
import org.openhab.io.transport.modbus.ValueBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link InfoBlock} Data object for E3DC Info Block
|
* The {@link InfoBlock} Data object for E3DC Info Block
|
||||||
@@ -43,7 +42,7 @@ public class InfoBlock implements Data {
|
|||||||
*/
|
*/
|
||||||
public InfoBlock(byte[] bArray) {
|
public InfoBlock(byte[] bArray) {
|
||||||
// index handling to calculate the correct start index
|
// 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
|
// first uint16 = 2 bytes - decode magic byte
|
||||||
byte[] magicBytes = new byte[2];
|
byte[] magicBytes = new byte[2];
|
||||||
@@ -52,11 +51,11 @@ public class InfoBlock implements Data {
|
|||||||
// first uint16 = 2 bytes - decode magic byte
|
// first uint16 = 2 bytes - decode magic byte
|
||||||
|
|
||||||
// unit8 (Modbus Major Version) + uint8 Modbus minor Version
|
// 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);
|
this.modbusVersion = new StringType(modbusVersion);
|
||||||
|
|
||||||
// unit16 - supported registers
|
// unit16 - supported registers
|
||||||
short supportedRegisters = wrapper.getShort();
|
short supportedRegisters = wrapper.getSInt16();
|
||||||
this.supportedRegisters = new DecimalType(supportedRegisters);
|
this.supportedRegisters = new DecimalType(supportedRegisters);
|
||||||
|
|
||||||
byte[] buffer = new byte[32];
|
byte[] buffer = new byte[32];
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.modbus.e3dc.internal.dto;
|
package org.openhab.binding.modbus.e3dc.internal.dto;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import javax.measure.quantity.Dimensionless;
|
import javax.measure.quantity.Dimensionless;
|
||||||
import javax.measure.quantity.Power;
|
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.binding.modbus.e3dc.internal.modbus.Data;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||||
|
import org.openhab.io.transport.modbus.ValueBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link PowerBlock} Data object for E3DC Info Block
|
* The {@link PowerBlock} Data object for E3DC Info Block
|
||||||
@@ -49,10 +48,10 @@ public class PowerBlock implements Data {
|
|||||||
*/
|
*/
|
||||||
public PowerBlock(byte[] bArray) {
|
public PowerBlock(byte[] bArray) {
|
||||||
// index handling to calculate the correct start index
|
// index handling to calculate the correct start index
|
||||||
ByteBuffer wrap = ByteBuffer.wrap(bArray);
|
ValueBuffer wrap = ValueBuffer.wrap(bArray);
|
||||||
|
|
||||||
// int32_swap value = 4 byte
|
// int32_swap value = 4 byte
|
||||||
long pvPowerSupplyL = DataConverter.getInt32Swap(wrap);
|
long pvPowerSupplyL = wrap.getSInt32Swap();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* int32_swap value don't provide negative values!
|
* int32_swap value don't provide negative values!
|
||||||
@@ -60,7 +59,7 @@ public class PowerBlock implements Data {
|
|||||||
* Negative value - Battery is discharging = Power supplier
|
* Negative value - Battery is discharging = Power supplier
|
||||||
*/
|
*/
|
||||||
pvPowerSupply = QuantityType.valueOf(pvPowerSupplyL, SmartHomeUnits.WATT);
|
pvPowerSupply = QuantityType.valueOf(pvPowerSupplyL, SmartHomeUnits.WATT);
|
||||||
long batteryPower = DataConverter.getInt32Swap(wrap);
|
long batteryPower = wrap.getSInt32Swap();
|
||||||
if (batteryPower > 0) {
|
if (batteryPower > 0) {
|
||||||
// Battery is charging so Power is consumed by Battery
|
// Battery is charging so Power is consumed by Battery
|
||||||
batteryPowerSupply = QuantityType.valueOf(0, SmartHomeUnits.WATT);
|
batteryPowerSupply = QuantityType.valueOf(0, SmartHomeUnits.WATT);
|
||||||
@@ -72,7 +71,7 @@ public class PowerBlock implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// int32_swap value = 4 byte
|
// int32_swap value = 4 byte
|
||||||
long householdPowerConsumptionL = DataConverter.getInt32Swap(wrap);
|
long householdPowerConsumptionL = wrap.getSInt32Swap();
|
||||||
householdPowerConsumption = QuantityType.valueOf(householdPowerConsumptionL, SmartHomeUnits.WATT);
|
householdPowerConsumption = QuantityType.valueOf(householdPowerConsumptionL, SmartHomeUnits.WATT);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -80,7 +79,7 @@ public class PowerBlock implements Data {
|
|||||||
* Positive value - Power provided towards Grid = Power consumer
|
* Positive value - Power provided towards Grid = Power consumer
|
||||||
* Negative value - Power requested from Grid = Power supplier
|
* Negative value - Power requested from Grid = Power supplier
|
||||||
*/
|
*/
|
||||||
long gridPower = DataConverter.getInt32Swap(wrap);
|
long gridPower = wrap.getSInt32Swap();
|
||||||
if (gridPower > 0) {
|
if (gridPower > 0) {
|
||||||
// Power is provided by Grid
|
// Power is provided by Grid
|
||||||
gridPowerSupply = QuantityType.valueOf(gridPower, SmartHomeUnits.WATT);
|
gridPowerSupply = QuantityType.valueOf(gridPower, SmartHomeUnits.WATT);
|
||||||
@@ -92,19 +91,19 @@ public class PowerBlock implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// int32_swap value = 4 byte
|
// 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
|
// 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
|
// 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
|
// unit8 + uint8 - one register with split value for Autarky & Self Consumption
|
||||||
autarky = QuantityType.valueOf(wrap.get(), SmartHomeUnits.PERCENT);
|
autarky = QuantityType.valueOf(wrap.getSInt8(), SmartHomeUnits.PERCENT);
|
||||||
selfConsumption = QuantityType.valueOf(wrap.get(), SmartHomeUnits.PERCENT);
|
selfConsumption = QuantityType.valueOf(wrap.getSInt8(), SmartHomeUnits.PERCENT);
|
||||||
|
|
||||||
// uint16 for Battery State of Charge
|
// uint16 for Battery State of Charge
|
||||||
batterySOC = QuantityType.valueOf(wrap.getShort(), SmartHomeUnits.PERCENT);
|
batterySOC = QuantityType.valueOf(wrap.getSInt16(), SmartHomeUnits.PERCENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.modbus.e3dc.internal.dto;
|
package org.openhab.binding.modbus.e3dc.internal.dto;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import javax.measure.quantity.ElectricCurrent;
|
import javax.measure.quantity.ElectricCurrent;
|
||||||
import javax.measure.quantity.ElectricPotential;
|
import javax.measure.quantity.ElectricPotential;
|
||||||
import javax.measure.quantity.Power;
|
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.binding.modbus.e3dc.internal.modbus.Data;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||||
|
import org.openhab.io.transport.modbus.ValueBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link StringBlock} Data object for E3DC Info Block
|
* 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
|
* @param bArray - Modbus Registers as bytes from 40096 to 40104
|
||||||
*/
|
*/
|
||||||
public StringBlock(byte[] bArray) {
|
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
|
// 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);
|
string1Volt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.VOLT);
|
||||||
string2Volt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.VOLT);
|
string2Volt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.VOLT);
|
||||||
string3Volt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), 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
|
// 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);
|
string1Ampere = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.01), SmartHomeUnits.AMPERE);
|
||||||
string2Ampere = 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);
|
string3Ampere = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.01), SmartHomeUnits.AMPERE);
|
||||||
string1Watt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.WATT);
|
string1Watt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.WATT);
|
||||||
string2Watt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.WATT);
|
string2Watt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.WATT);
|
||||||
string3Watt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.WATT);
|
string3Watt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.WATT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.dto.WallboxArray;
|
||||||
import org.openhab.binding.modbus.e3dc.internal.modbus.Data.DataType;
|
import org.openhab.binding.modbus.e3dc.internal.modbus.Data.DataType;
|
||||||
import org.openhab.io.transport.modbus.AsyncModbusReadResult;
|
import org.openhab.io.transport.modbus.AsyncModbusReadResult;
|
||||||
import org.openhab.io.transport.modbus.ModbusRegister;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -59,17 +58,10 @@ public class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void handle(AsyncModbusReadResult result) {
|
public void handle(AsyncModbusReadResult result) {
|
||||||
byte[] newArray = new byte[size];
|
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
Optional<ModbusRegisterArray> opt = result.getRegisters();
|
Optional<ModbusRegisterArray> opt = result.getRegisters();
|
||||||
if (opt.isPresent()) {
|
if (opt.isPresent()) {
|
||||||
ModbusRegisterArray registers = opt.get();
|
setArray(opt.get().getBytes());
|
||||||
int i = 0;
|
|
||||||
for (ModbusRegister reg : registers) {
|
|
||||||
System.arraycopy(reg.getBytes(), 0, newArray, i, 2);
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
setArray(newArray);
|
|
||||||
|
|
||||||
long duration = System.currentTimeMillis() - startTime;
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
avgDuration += duration;
|
avgDuration += duration;
|
||||||
|
|||||||
@@ -35,14 +35,26 @@ import org.openhab.core.library.types.OnOffType;
|
|||||||
*/
|
*/
|
||||||
public class DataBlockTest {
|
public class DataBlockTest {
|
||||||
private Parser mc;
|
private Parser mc;
|
||||||
|
private Parser mcNegativePVSupply;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setup() {
|
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,
|
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,
|
||||||
125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 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,
|
||||||
mc = new Parser(DataType.DATA);
|
0, 0, 1, 125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 0, 0 };
|
||||||
mc.setArray(dataBlock);
|
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
|
@Test
|
||||||
@@ -56,6 +68,17 @@ public class DataBlockTest {
|
|||||||
assertEquals("303.0 W", b.batteryPowerSupply.toString(), "Battery Supply");
|
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
|
@Test
|
||||||
public void testValidWallboxBlock() {
|
public void testValidWallboxBlock() {
|
||||||
Optional<Data> wba = mc.parse(DataType.WALLBOX);
|
Optional<Data> wba = mc.parse(DataType.WALLBOX);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ package org.openhab.binding.modbus.e3dc.internal.handler;
|
|||||||
|
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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.AsyncModbusFailure;
|
||||||
import org.openhab.io.transport.modbus.AsyncModbusReadResult;
|
import org.openhab.io.transport.modbus.AsyncModbusReadResult;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.ModbusRegister;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
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,
|
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,
|
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 };
|
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);
|
ModbusReadRequestBlueprint readRequest = mock(ModbusReadRequestBlueprint.class);
|
||||||
return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(infoBlock));
|
return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(infoBlockBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncModbusReadResult getDataResult() {
|
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,
|
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,
|
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 };
|
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);
|
ModbusReadRequestBlueprint readRequest = mock(ModbusReadRequestBlueprint.class);
|
||||||
return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(dataBlock));
|
return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(dataBlockBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncModbusFailure<ModbusReadRequestBlueprint> getFailResult() {
|
private AsyncModbusFailure<ModbusReadRequestBlueprint> getFailResult() {
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ package org.openhab.binding.modbus.e3dc.util;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.openhab.binding.modbus.e3dc.internal.dto.DataConverter;
|
import org.openhab.binding.modbus.e3dc.internal.dto.DataConverter;
|
||||||
|
import org.openhab.io.transport.modbus.ValueBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link DataConverterTest} Test data conversions
|
* The {@link DataConverterTest} Test data conversions
|
||||||
@@ -30,17 +30,58 @@ import org.openhab.binding.modbus.e3dc.internal.dto.DataConverter;
|
|||||||
public class DataConverterTest {
|
public class DataConverterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testE3DCSwapValueNegative() {
|
public void testRoundPositive() {
|
||||||
// Reg 69 value 65098 bytes [-2, 74]
|
assertEquals(2.3, DataConverter.round(2.34, 1), 0.01);
|
||||||
// Reg 70 value 65535 bytes [-1, -1]
|
|
||||||
byte[] b = new byte[] { -2, -74, -1, -1 };
|
|
||||||
assertEquals(-330, DataConverter.getInt32Swap(ByteBuffer.wrap(b)), "Negative Value");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 };
|
byte[] b = new byte[] { 3, 16 };
|
||||||
BitSet s = BitSet.valueOf(b);
|
BitSet s = BitSet.valueOf(b);
|
||||||
|
// Bit0 is the least significant bit to DataConverter.toInt
|
||||||
assertEquals(true, s.get(0), "Bit0");
|
assertEquals(true, s.get(0), "Bit0");
|
||||||
assertEquals(true, s.get(1), "Bit1");
|
assertEquals(true, s.get(1), "Bit1");
|
||||||
assertEquals(false, s.get(2), "Bit2");
|
assertEquals(false, s.get(2), "Bit2");
|
||||||
@@ -57,5 +98,10 @@ public class DataConverterTest {
|
|||||||
assertEquals(false, s.get(13), "Bit13");
|
assertEquals(false, s.get(13), "Bit13");
|
||||||
assertEquals(false, s.get(14), "Bit14");
|
assertEquals(false, s.get(14), "Bit14");
|
||||||
assertEquals(false, s.get(15), "Bit15");
|
assertEquals(false, s.get(15), "Bit15");
|
||||||
|
|
||||||
|
int bitsAsInt = DataConverter.toInt(s);
|
||||||
|
int expected = 0b0001000000000011;
|
||||||
|
assertEquals(Integer.toBinaryString(expected), Integer.toBinaryString(bitsAsInt));
|
||||||
|
assertEquals(expected, bitsAsInt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
|||||||
import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
|
import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.ModbusRegister;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
|
import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
|
||||||
@@ -429,9 +428,8 @@ public class HeliosEasyControlsHandler extends BaseThingHandler {
|
|||||||
lock.acquire();
|
lock.acquire();
|
||||||
comms.submitOneTimeWrite(
|
comms.submitOneTimeWrite(
|
||||||
new ModbusWriteRegisterRequestBlueprint(HeliosEasyControlsBindingConstants.UNIT_ID,
|
new ModbusWriteRegisterRequestBlueprint(HeliosEasyControlsBindingConstants.UNIT_ID,
|
||||||
HeliosEasyControlsBindingConstants.START_ADDRESS,
|
HeliosEasyControlsBindingConstants.START_ADDRESS, preparePayload(payload),
|
||||||
new ModbusRegisterArray(preparePayload(payload)), true,
|
true, HeliosEasyControlsBindingConstants.MAX_TRIES),
|
||||||
HeliosEasyControlsBindingConstants.MAX_TRIES),
|
|
||||||
result -> {
|
result -> {
|
||||||
lock.release();
|
lock.release();
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
@@ -477,8 +475,7 @@ public class HeliosEasyControlsHandler extends BaseThingHandler {
|
|||||||
String payload = v.getVariableString();
|
String payload = v.getVariableString();
|
||||||
comms.submitOneTimeWrite(new ModbusWriteRegisterRequestBlueprint(
|
comms.submitOneTimeWrite(new ModbusWriteRegisterRequestBlueprint(
|
||||||
HeliosEasyControlsBindingConstants.UNIT_ID, HeliosEasyControlsBindingConstants.START_ADDRESS,
|
HeliosEasyControlsBindingConstants.UNIT_ID, HeliosEasyControlsBindingConstants.START_ADDRESS,
|
||||||
new ModbusRegisterArray(preparePayload(payload)), true,
|
preparePayload(payload), true, HeliosEasyControlsBindingConstants.MAX_TRIES), result -> {
|
||||||
HeliosEasyControlsBindingConstants.MAX_TRIES), result -> {
|
|
||||||
comms.submitOneTimePoll(
|
comms.submitOneTimePoll(
|
||||||
new ModbusReadRequestBlueprint(HeliosEasyControlsBindingConstants.UNIT_ID,
|
new ModbusReadRequestBlueprint(HeliosEasyControlsBindingConstants.UNIT_ID,
|
||||||
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
||||||
@@ -690,25 +687,21 @@ public class HeliosEasyControlsHandler extends BaseThingHandler {
|
|||||||
* @param payload The String representation of the payload
|
* @param payload The String representation of the payload
|
||||||
* @return The Register representation of the payload
|
* @return The Register representation of the payload
|
||||||
*/
|
*/
|
||||||
private ModbusRegister[] preparePayload(String payload) {
|
private static ModbusRegisterArray preparePayload(String payload) {
|
||||||
|
|
||||||
// determine number of registers
|
// determine number of registers
|
||||||
int l = (payload.length() + 1) / 2; // +1 because we need to include at least one termination symbol 0x00
|
byte[] asciiBytes = payload.getBytes(StandardCharsets.US_ASCII);
|
||||||
if ((payload.length() + 1) % 2 != 0) {
|
int bufferLength = asciiBytes.length // ascii characters
|
||||||
l++;
|
+ 1 // NUL byte
|
||||||
}
|
+ ((asciiBytes.length % 2 == 0) ? 1 : 0); // to have even number of bytes
|
||||||
|
assert bufferLength % 2 == 0; // Invariant, ensured above
|
||||||
|
|
||||||
ModbusRegister reg[] = new ModbusRegister[l];
|
byte[] buffer = new byte[bufferLength];
|
||||||
byte[] b = payload.getBytes();
|
System.arraycopy(asciiBytes, 0, buffer, 0, asciiBytes.length);
|
||||||
int ch = 0;
|
// Fill in rest of bytes with NUL bytes
|
||||||
for (int i = 0; i < reg.length; i++) {
|
for (int i = asciiBytes.length; i < buffer.length; i++) {
|
||||||
byte b1 = ch < b.length ? b[ch] : (byte) 0x00; // terminate with 0x00 if at the end of the payload
|
buffer[i] = '\0';
|
||||||
ch++;
|
|
||||||
byte b2 = ch < b.length ? b[ch] : (byte) 0x00;
|
|
||||||
ch++;
|
|
||||||
reg[i] = new ModbusRegister(b1, b2);
|
|
||||||
}
|
}
|
||||||
return reg;
|
return new ModbusRegisterArray(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -718,8 +711,8 @@ public class HeliosEasyControlsHandler extends BaseThingHandler {
|
|||||||
* @return The value or <tt>null</tt> if an error occurred
|
* @return The value or <tt>null</tt> if an error occurred
|
||||||
*/
|
*/
|
||||||
private void processResponse(HeliosVariable v, ModbusRegisterArray registers) {
|
private void processResponse(HeliosVariable v, ModbusRegisterArray registers) {
|
||||||
String r = ModbusBitUtilities
|
String r = ModbusBitUtilities.extractStringFromRegisters(registers, 0, registers.size() * 2,
|
||||||
.extractStringFromRegisters(registers, 0, registers.size() * 2, StandardCharsets.US_ASCII).toString();
|
StandardCharsets.US_ASCII);
|
||||||
String[] parts = r.split("=", 2); // remove the part "vXXXX=" from the string
|
String[] parts = r.split("=", 2); // remove the part "vXXXX=" from the string
|
||||||
// making sure we have a proper response and the response matches the requested variable
|
// making sure we have a proper response and the response matches the requested variable
|
||||||
if ((parts.length == 2) && (v.getVariableString().equals(parts[0]))) {
|
if ((parts.length == 2) && (v.getVariableString().equals(parts[0]))) {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.modbus.helioseasycontrols.internal;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sami Salonen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class PreparePayloadTest {
|
||||||
|
|
||||||
|
private Method preparePayloadMethod;
|
||||||
|
|
||||||
|
public PreparePayloadTest() throws NoSuchMethodException, SecurityException {
|
||||||
|
preparePayloadMethod = HeliosEasyControlsHandler.class.getDeclaredMethod("preparePayload", String.class);
|
||||||
|
preparePayloadMethod.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModbusRegisterArray preparePayload(String payload) {
|
||||||
|
try {
|
||||||
|
return (ModbusRegisterArray) preparePayloadMethod.invoke(null, payload);
|
||||||
|
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
fail("Reflection failure:" + e.getMessage());
|
||||||
|
throw new RuntimeException(); // to make compiler happy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
return Collections.unmodifiableList(Stream
|
||||||
|
// Due to nul byte, full register full of nul bytes added
|
||||||
|
.of(new Object[] { "v00020=a", new ModbusRegisterArray(0x7630, 0x3030, 0x3230, 0x3d61, 0x0000) },
|
||||||
|
new Object[] { "v00020=aa", new ModbusRegisterArray(0x7630, 0x3030, 0x3230, 0x3d61, 0x6100) })
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("data")
|
||||||
|
public void testPreparePayload(String payload, ModbusRegisterArray expectedRegisters) {
|
||||||
|
ModbusRegisterArray actual = preparePayload(payload);
|
||||||
|
|
||||||
|
assertEquals(actual.size(), expectedRegisters.size(), String.format("payload=%s", payload));
|
||||||
|
for (int i = 0; i < expectedRegisters.size(); i++) {
|
||||||
|
int expectedRegisterDataUnsigned = expectedRegisters.getRegister(i);
|
||||||
|
int actualUnsigned = actual.getRegister(i);
|
||||||
|
|
||||||
|
assertEquals(expectedRegisterDataUnsigned, actualUnsigned,
|
||||||
|
String.format("register index i=%d, payload=%s", i, payload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,7 @@ package org.openhab.binding.modbus.stiebeleltron.internal.handler;
|
|||||||
|
|
||||||
import static org.openhab.binding.modbus.stiebeleltron.internal.StiebelEltronBindingConstants.*;
|
import static org.openhab.binding.modbus.stiebeleltron.internal.StiebelEltronBindingConstants.*;
|
||||||
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
|
||||||
import static org.openhab.core.library.unit.SmartHomeUnits.KILOWATT_HOUR;
|
import static org.openhab.core.library.unit.SmartHomeUnits.*;
|
||||||
import static org.openhab.core.library.unit.SmartHomeUnits.PERCENT;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -52,7 +51,6 @@ import org.openhab.io.transport.modbus.AsyncModbusFailure;
|
|||||||
import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
|
import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.ModbusRegister;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
|
||||||
@@ -198,11 +196,9 @@ public class StiebelEltronHandler extends BaseThingHandler {
|
|||||||
throw new IllegalStateException("registerPollTask called without proper configuration");
|
throw new IllegalStateException("registerPollTask called without proper configuration");
|
||||||
}
|
}
|
||||||
// big endian byte ordering
|
// big endian byte ordering
|
||||||
byte b1 = (byte) (shortValue >> 8);
|
byte hi = (byte) (shortValue >> 8);
|
||||||
byte b2 = (byte) shortValue;
|
byte lo = (byte) shortValue;
|
||||||
|
ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
|
||||||
ModbusRegister register = new ModbusRegister(b1, b2);
|
|
||||||
ModbusRegisterArray data = new ModbusRegisterArray(new ModbusRegister[] { register });
|
|
||||||
|
|
||||||
ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
|
ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
|
||||||
false, myconfig.getMaxTries());
|
false, myconfig.getMaxTries());
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.openhab.binding.modbus.studer.internal.StuderParser.ModeXtender;
|
|||||||
import org.openhab.binding.modbus.studer.internal.StuderParser.VSMode;
|
import org.openhab.binding.modbus.studer.internal.StuderParser.VSMode;
|
||||||
import org.openhab.binding.modbus.studer.internal.StuderParser.VTMode;
|
import org.openhab.binding.modbus.studer.internal.StuderParser.VTMode;
|
||||||
import org.openhab.binding.modbus.studer.internal.StuderParser.VTType;
|
import org.openhab.binding.modbus.studer.internal.StuderParser.VTType;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
import org.openhab.core.library.types.StringType;
|
import org.openhab.core.library.types.StringType;
|
||||||
@@ -43,7 +44,9 @@ import org.openhab.core.types.Command;
|
|||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
import org.openhab.core.types.UnDefType;
|
import org.openhab.core.types.UnDefType;
|
||||||
import org.openhab.io.transport.modbus.AsyncModbusFailure;
|
import org.openhab.io.transport.modbus.AsyncModbusFailure;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
||||||
import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
|
import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
@@ -303,22 +306,21 @@ public class StuderHandler extends BaseThingHandler {
|
|||||||
* @param registers byte array read from the modbus slave
|
* @param registers byte array read from the modbus slave
|
||||||
*/
|
*/
|
||||||
protected void handlePolledData(int registerNumber, ModbusRegisterArray registers) {
|
protected void handlePolledData(int registerNumber, ModbusRegisterArray registers) {
|
||||||
String hexString = registers.toHexString().toString();
|
Optional<DecimalType> quantity = ModbusBitUtilities.extractStateFromRegisters(registers, 0, ValueType.FLOAT32);
|
||||||
Float quantity = parser.hexToFloat(hexString);
|
quantity.ifPresent(value -> {
|
||||||
if (quantity != null) {
|
|
||||||
if (type.equals(THING_TYPE_BSP)) {
|
if (type.equals(THING_TYPE_BSP)) {
|
||||||
Unit<?> unit = UNIT_CHANNELS_BSP.get(registerNumber);
|
Unit<?> unit = UNIT_CHANNELS_BSP.get(registerNumber);
|
||||||
if (unit != null) {
|
if (unit != null) {
|
||||||
internalUpdateState(CHANNELS_BSP.get(registerNumber), new QuantityType<>(quantity, unit));
|
internalUpdateState(CHANNELS_BSP.get(registerNumber), new QuantityType<>(value, unit));
|
||||||
}
|
}
|
||||||
} else if (type.equals(THING_TYPE_XTENDER)) {
|
} else if (type.equals(THING_TYPE_XTENDER)) {
|
||||||
handlePolledDataXtender(registerNumber, quantity);
|
handlePolledDataXtender(registerNumber, value);
|
||||||
} else if (type.equals(THING_TYPE_VARIOTRACK)) {
|
} else if (type.equals(THING_TYPE_VARIOTRACK)) {
|
||||||
handlePolledDataVarioTrack(registerNumber, quantity);
|
handlePolledDataVarioTrack(registerNumber, value);
|
||||||
} else if (type.equals(THING_TYPE_VARIOSTRING)) {
|
} else if (type.equals(THING_TYPE_VARIOSTRING)) {
|
||||||
handlePolledDataVarioString(registerNumber, quantity);
|
handlePolledDataVarioString(registerNumber, value);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
resetCommunicationError();
|
resetCommunicationError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +329,7 @@ public class StuderHandler extends BaseThingHandler {
|
|||||||
* The register array is first parsed, then each of the channels are updated
|
* The register array is first parsed, then each of the channels are updated
|
||||||
* to the new values
|
* to the new values
|
||||||
*/
|
*/
|
||||||
protected void handlePolledDataVarioString(int registerNumber, Float quantity) {
|
protected void handlePolledDataVarioString(int registerNumber, DecimalType quantity) {
|
||||||
switch (CHANNELS_VARIOSTRING.get(registerNumber)) {
|
switch (CHANNELS_VARIOSTRING.get(registerNumber)) {
|
||||||
case CHANNEL_PV_OPERATING_MODE:
|
case CHANNEL_PV_OPERATING_MODE:
|
||||||
case CHANNEL_PV1_OPERATING_MODE:
|
case CHANNEL_PV1_OPERATING_MODE:
|
||||||
@@ -356,7 +358,7 @@ public class StuderHandler extends BaseThingHandler {
|
|||||||
* The register array is first parsed, then each of the channels are updated
|
* The register array is first parsed, then each of the channels are updated
|
||||||
* to the new values
|
* to the new values
|
||||||
*/
|
*/
|
||||||
protected void handlePolledDataVarioTrack(int registerNumber, Float quantity) {
|
protected void handlePolledDataVarioTrack(int registerNumber, DecimalType quantity) {
|
||||||
switch (CHANNELS_VARIOTRACK.get(registerNumber)) {
|
switch (CHANNELS_VARIOTRACK.get(registerNumber)) {
|
||||||
case CHANNEL_MODEL_VARIOTRACK:
|
case CHANNEL_MODEL_VARIOTRACK:
|
||||||
VTType type = StuderParser.getVTTypeByCode(quantity.intValue());
|
VTType type = StuderParser.getVTTypeByCode(quantity.intValue());
|
||||||
@@ -393,7 +395,7 @@ public class StuderHandler extends BaseThingHandler {
|
|||||||
* The register array is first parsed, then each of the channels are updated
|
* The register array is first parsed, then each of the channels are updated
|
||||||
* to the new values
|
* to the new values
|
||||||
*/
|
*/
|
||||||
protected void handlePolledDataXtender(int registerNumber, Float quantity) {
|
protected void handlePolledDataXtender(int registerNumber, DecimalType quantity) {
|
||||||
switch (CHANNELS_XTENDER.get(registerNumber)) {
|
switch (CHANNELS_XTENDER.get(registerNumber)) {
|
||||||
case CHANNEL_OPERATING_STATE:
|
case CHANNEL_OPERATING_STATE:
|
||||||
ModeXtender mode = StuderParser.getModeXtenderByCode(quantity.intValue());
|
ModeXtender mode = StuderParser.getModeXtenderByCode(quantity.intValue());
|
||||||
|
|||||||
@@ -52,12 +52,10 @@ public class CommonModelParser extends AbstractBaseParser implements SunspecPars
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse manufacturer, model and version
|
// parse manufacturer, model and version
|
||||||
block.manufacturer = ModbusBitUtilities.extractStringFromRegisters(raw, 2, 32, Charset.forName("UTF-8"))
|
block.manufacturer = ModbusBitUtilities.extractStringFromRegisters(raw, 2, 32, Charset.forName("UTF-8"));
|
||||||
.toString();
|
block.model = ModbusBitUtilities.extractStringFromRegisters(raw, 18, 32, Charset.forName("UTF-8"));
|
||||||
block.model = ModbusBitUtilities.extractStringFromRegisters(raw, 18, 32, Charset.forName("UTF-8")).toString();
|
block.version = ModbusBitUtilities.extractStringFromRegisters(raw, 42, 16, Charset.forName("UTF-8"));
|
||||||
block.version = ModbusBitUtilities.extractStringFromRegisters(raw, 42, 16, Charset.forName("UTF-8")).toString();
|
block.serialNumber = ModbusBitUtilities.extractStringFromRegisters(raw, 50, 32, Charset.forName("UTF-8"));
|
||||||
block.serialNumber = ModbusBitUtilities.extractStringFromRegisters(raw, 50, 32, Charset.forName("UTF-8"))
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
block.deviceAddress = extractUInt16(raw, 66, 1);
|
block.deviceAddress = extractUInt16(raw, 66, 1);
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ package org.openhab.io.transport.modbus;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -24,8 +22,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.OpenClosedType;
|
import org.openhab.core.library.types.OpenClosedType;
|
||||||
import org.openhab.core.library.types.StringType;
|
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for working with binary data.
|
* Utilities for working with binary data.
|
||||||
@@ -101,165 +99,522 @@ public class ModbusBitUtilities {
|
|||||||
*/
|
*/
|
||||||
public static Optional<DecimalType> extractStateFromRegisters(ModbusRegisterArray registers, int index,
|
public static Optional<DecimalType> extractStateFromRegisters(ModbusRegisterArray registers, int index,
|
||||||
ModbusConstants.ValueType type) {
|
ModbusConstants.ValueType type) {
|
||||||
int endBitIndex = (type.getBits() >= 16 ? 16 * index : type.getBits() * index) + type.getBits() - 1;
|
byte[] bytes = registers.getBytes();
|
||||||
// each register has 16 bits
|
|
||||||
int lastValidIndex = registers.size() * 16 - 1;
|
|
||||||
if (endBitIndex > lastValidIndex || index < 0) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
String.format("Index=%d with type=%s is out-of-bounds given registers of size %d", index, type,
|
|
||||||
registers.size()));
|
|
||||||
}
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case BIT:
|
case BIT:
|
||||||
return Optional
|
return Optional.of(new DecimalType(extractBit(bytes, index)));
|
||||||
.of(new DecimalType((registers.getRegister(index / 16).toUnsignedShort() >> (index % 16)) & 1));
|
case INT8: {
|
||||||
case INT8:
|
int registerIndex = index / 2;
|
||||||
return Optional.of(new DecimalType(registers.getRegister(index / 2).getBytes()[1 - (index % 2)]));
|
boolean hiByte = index % 2 == 1;
|
||||||
case UINT8:
|
return Optional.of(new DecimalType(extractSInt8(bytes, registerIndex, hiByte)));
|
||||||
return Optional.of(new DecimalType(
|
|
||||||
(registers.getRegister(index / 2).toUnsignedShort() >> (8 * (index % 2))) & 0xff));
|
|
||||||
case INT16: {
|
|
||||||
ByteBuffer buff = ByteBuffer.allocate(2);
|
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
|
||||||
return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getShort(0)));
|
|
||||||
}
|
}
|
||||||
|
case UINT8: {
|
||||||
|
int registerIndex = index / 2;
|
||||||
|
boolean hiByte = index % 2 == 1;
|
||||||
|
return Optional.of(new DecimalType(extractUInt8(bytes, registerIndex, hiByte)));
|
||||||
|
}
|
||||||
|
case INT16:
|
||||||
|
return Optional.of(new DecimalType(extractSInt16(bytes, index * 2)));
|
||||||
case UINT16:
|
case UINT16:
|
||||||
return Optional.of(new DecimalType(registers.getRegister(index).toUnsignedShort()));
|
return Optional.of(new DecimalType(extractUInt16(bytes, index * 2)));
|
||||||
case INT32: {
|
case INT32:
|
||||||
ByteBuffer buff = ByteBuffer.allocate(4);
|
return Optional.of(new DecimalType(extractSInt32(bytes, index * 2)));
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
case UINT32:
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
return Optional.of(new DecimalType(extractUInt32(bytes, index * 2)));
|
||||||
return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getInt(0)));
|
case FLOAT32:
|
||||||
}
|
|
||||||
case UINT32: {
|
|
||||||
ByteBuffer buff = ByteBuffer.allocate(8);
|
|
||||||
buff.position(4);
|
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
|
||||||
return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getLong(0)));
|
|
||||||
}
|
|
||||||
case FLOAT32: {
|
|
||||||
ByteBuffer buff = ByteBuffer.allocate(4);
|
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
|
||||||
try {
|
try {
|
||||||
return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getFloat(0)));
|
return Optional.of(new DecimalType(extractFloat32(bytes, index * 2)));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
// floating point NaN or infinity encountered
|
// floating point NaN or infinity encountered
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
case INT64:
|
||||||
case INT64: {
|
return Optional.of(new DecimalType(extractSInt64(bytes, index * 2)));
|
||||||
ByteBuffer buff = ByteBuffer.allocate(8);
|
case UINT64:
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
return Optional.of(new DecimalType(new BigDecimal(extractUInt64(bytes, index * 2))));
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
case INT32_SWAP:
|
||||||
buff.put(registers.getRegister(index + 2).getBytes());
|
return Optional.of(new DecimalType(extractSInt32Swap(bytes, index * 2)));
|
||||||
buff.put(registers.getRegister(index + 3).getBytes());
|
case UINT32_SWAP:
|
||||||
return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getLong(0)));
|
return Optional.of(new DecimalType(extractUInt32Swap(bytes, index * 2)));
|
||||||
}
|
case FLOAT32_SWAP:
|
||||||
case UINT64: {
|
|
||||||
ByteBuffer buff = ByteBuffer.allocate(8);
|
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
|
||||||
buff.put(registers.getRegister(index + 2).getBytes());
|
|
||||||
buff.put(registers.getRegister(index + 3).getBytes());
|
|
||||||
return Optional.of(
|
|
||||||
new DecimalType(new BigDecimal(new BigInteger(1, buff.order(ByteOrder.BIG_ENDIAN).array()))));
|
|
||||||
}
|
|
||||||
case INT32_SWAP: {
|
|
||||||
ByteBuffer buff = ByteBuffer.allocate(4);
|
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
|
||||||
return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getInt(0)));
|
|
||||||
}
|
|
||||||
case UINT32_SWAP: {
|
|
||||||
ByteBuffer buff = ByteBuffer.allocate(8);
|
|
||||||
buff.position(4);
|
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
|
||||||
return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getLong(0)));
|
|
||||||
}
|
|
||||||
case FLOAT32_SWAP: {
|
|
||||||
ByteBuffer buff = ByteBuffer.allocate(4);
|
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
|
||||||
try {
|
try {
|
||||||
return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getFloat(0)));
|
return Optional.of(new DecimalType(extractFloat32Swap(bytes, index * 2)));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
// floating point NaN or infinity encountered
|
// floating point NaN or infinity encountered
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
case INT64_SWAP:
|
||||||
case INT64_SWAP: {
|
return Optional.of(new DecimalType(extractSInt64Swap(bytes, index * 2)));
|
||||||
ByteBuffer buff = ByteBuffer.allocate(8);
|
case UINT64_SWAP:
|
||||||
buff.put(registers.getRegister(index + 3).getBytes());
|
return Optional.of(new DecimalType(new BigDecimal(extractUInt64Swap(bytes, index * 2))));
|
||||||
buff.put(registers.getRegister(index + 2).getBytes());
|
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
|
||||||
return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getLong(0)));
|
|
||||||
}
|
|
||||||
case UINT64_SWAP: {
|
|
||||||
ByteBuffer buff = ByteBuffer.allocate(8);
|
|
||||||
buff.put(registers.getRegister(index + 3).getBytes());
|
|
||||||
buff.put(registers.getRegister(index + 2).getBytes());
|
|
||||||
buff.put(registers.getRegister(index + 1).getBytes());
|
|
||||||
buff.put(registers.getRegister(index).getBytes());
|
|
||||||
return Optional.of(
|
|
||||||
new DecimalType(new BigDecimal(new BigInteger(1, buff.order(ByteOrder.BIG_ENDIAN).array()))));
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException(type.getConfigValue());
|
throw new IllegalArgumentException(type.getConfigValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void assertIndexAndType(byte[] bytes, int index, ValueType type) {
|
||||||
|
int typeBits = type.getBits();
|
||||||
|
// for 8-bit types and larger, index specifies the index of the byte. For bits, index specifies the index of the
|
||||||
|
// bit (of the whole data)
|
||||||
|
int indexPositionAsBitIndex = Math.min(type.getBits(), 8) * index;
|
||||||
|
int endBitIndex = indexPositionAsBitIndex + typeBits - 1;
|
||||||
|
int lastValidIndex = bytes.length * 8 - 1;
|
||||||
|
if (endBitIndex > lastValidIndex || index < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Index=%d with type=%s is out-of-bounds given registers of size %d ", index, type,
|
||||||
|
bytes.length / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read data from registers and convert the result to StringType
|
* Extract single bit from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* - indices between 0...15 (inclusive) represent bits of the first register
|
||||||
|
* - indices between 16...31 (inclusive) represent bits of the second register, etc.
|
||||||
|
* - index 0 refers to the least significant bit of the first register
|
||||||
|
* - index 1 refers to the second least significant bit of the first register, etc.
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of bit
|
||||||
|
* @return 0 when bit is set, 1 otherwise
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static int extractBit(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.BIT);
|
||||||
|
int registerIndex = index / 16;
|
||||||
|
int bitIndexWithinRegister = index % 16;
|
||||||
|
return extractBit(bytes, registerIndex, bitIndexWithinRegister);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract single bit from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* bitIndexWithinRegister between 0...15 (inclusive) represent bits of the first register, where 0 refers to the
|
||||||
|
* least significant bit of the register, index 1 refers to the second least significant bit of the register, etc.
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param registerIndex index of register. First register has index of 0.
|
||||||
|
* @param bitIndexWithinRegister bit index within the register
|
||||||
|
* @return 0 when bit is set, 1 otherwise
|
||||||
|
* @throws IllegalArgumentException when registerIndex and/or bitIndexWithinRegister is out of bounds
|
||||||
|
*/
|
||||||
|
public static int extractBit(byte[] bytes, int registerIndex, int bitIndexWithinRegister) {
|
||||||
|
if (bitIndexWithinRegister < 0 || bitIndexWithinRegister > 15) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("bitIndexWithinRegister=%d is out-of-bounds (max 15)", bitIndexWithinRegister));
|
||||||
|
} else if (registerIndex < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("registerIndex=%d is out-of-bounds", bitIndexWithinRegister));
|
||||||
|
}
|
||||||
|
boolean hiByte = bitIndexWithinRegister >= 8;
|
||||||
|
int indexWithinByte = bitIndexWithinRegister % 8;
|
||||||
|
int byteIndex = 2 * registerIndex + (hiByte ? 0 : 1);
|
||||||
|
if (byteIndex >= bytes.length) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"registerIndex=%d, bitIndexWithinRegister=%d is out-of-bounds with registers of size %d",
|
||||||
|
registerIndex, bitIndexWithinRegister, bytes.length / 2));
|
||||||
|
}
|
||||||
|
return ((bytes[byteIndex] >>> indexWithinByte) & 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 8-bit integer (byte) from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param registerIndex index of register. First register has index of 0.
|
||||||
|
* @param hiByte whether to extract hi byte or lo byte
|
||||||
|
* @return 0 when bit is set, 1 otherwise
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static byte extractSInt8(byte[] bytes, int registerIndex, boolean hiByte) {
|
||||||
|
int byteIndex = 2 * registerIndex + (hiByte ? 0 : 1);
|
||||||
|
byte signed = extractSInt8(bytes, byteIndex);
|
||||||
|
return signed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 8-bit integer (byte) from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* - index 0 refers to low byte of the first register, 1 high byte of first register
|
||||||
|
* - index 2 refers to low byte of the second register, 3 high byte of second register, etc.
|
||||||
|
* - it is assumed that each high and low byte is encoded in most significant bit first order
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param registerIndex index of register. First register has index of 0.
|
||||||
|
* @param index index of the byte in registers
|
||||||
|
* @return 0 when bit is set, 1 otherwise
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static byte extractSInt8(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.INT8);
|
||||||
|
byte signed = bytes[index];
|
||||||
|
return signed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 8-bit integer (byte) from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param registerIndex index of register. First register has index of 0.
|
||||||
|
* @param hiByte whether to extract hi byte or lo byte
|
||||||
|
* @return 0 when bit is set, 1 otherwise
|
||||||
|
* @throws IllegalArgumentException when registerIndex is out of bounds
|
||||||
|
*/
|
||||||
|
public static short extractUInt8(byte[] bytes, int registerIndex, boolean hiByte) {
|
||||||
|
int byteIndex = 2 * registerIndex + (hiByte ? 0 : 1);
|
||||||
|
short unsigned = extractUInt8(bytes, byteIndex);
|
||||||
|
return unsigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 8-bit integer (byte) from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* - index 0 refers to low byte of the first register, 1 high byte of first register
|
||||||
|
* - index 2 refers to low byte of the second register, 3 high byte of second register, etc.
|
||||||
|
* - it is assumed that each high and low byte is encoded in most significant bit first order
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param registerIndex index of register. First register has index of 0.
|
||||||
|
* @param index index of the byte in registers
|
||||||
|
* @return 0 when bit is set, 1 otherwise
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static short extractUInt8(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.UINT8);
|
||||||
|
int signed = extractSInt8(bytes, index);
|
||||||
|
short unsigned = (short) (signed & 0xff);
|
||||||
|
assert unsigned >= 0;
|
||||||
|
return unsigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 16-bit integer (short) from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of register. First register has index of 0.
|
||||||
|
* @return register with index interpreted as 16 bit signed integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static short extractSInt16(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.INT16);
|
||||||
|
int hi = (bytes[index] & 0xff);
|
||||||
|
int lo = (bytes[index + 1] & 0xff);
|
||||||
|
short signed = (short) ((hi << 8) | lo);
|
||||||
|
return signed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 16-bit integer from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of register. First register has index of 0.
|
||||||
|
* @return register with index interpreted as 16 bit unsigned integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static int extractUInt16(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.UINT16);
|
||||||
|
int signed = extractSInt16(bytes, index);
|
||||||
|
int unsigned = signed & 0xffff;
|
||||||
|
assert unsigned >= 0;
|
||||||
|
return unsigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 32-bit integer from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index) and (index+1) interpreted as 32 bit signed integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static int extractSInt32(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.INT32);
|
||||||
|
int hi1 = bytes[index + 0] & 0xff;
|
||||||
|
int lo1 = bytes[index + 1] & 0xff;
|
||||||
|
int hi2 = bytes[index + 2] & 0xff;
|
||||||
|
int lo2 = bytes[index + 3] & 0xff;
|
||||||
|
int signed = (hi1 << 24) | (lo1 << 16) | (hi2 << 8) | lo2;
|
||||||
|
return signed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 32-bit integer from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index) and (index+1) interpreted as 32 bit unsigned integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static long extractUInt32(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.UINT32);
|
||||||
|
long signed = extractSInt32(bytes, index);
|
||||||
|
long unsigned = signed & 0xffff_ffffL;
|
||||||
|
assert unsigned >= 0;
|
||||||
|
return unsigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 32-bit integer from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order.
|
||||||
|
*
|
||||||
|
* This is identical with extractSInt32, but with registers swapped.
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index+1), (index) interpreted as 32 bit signed integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static int extractSInt32Swap(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.INT32_SWAP);
|
||||||
|
// swapped order of registers, high 16 bits *follow* low 16 bits
|
||||||
|
int hi1 = bytes[index + 2] & 0xff;
|
||||||
|
int lo1 = bytes[index + 3] & 0xff;
|
||||||
|
int hi2 = bytes[index + 0] & 0xff;
|
||||||
|
int lo2 = bytes[index + 1] & 0xff;
|
||||||
|
int signed = (hi1 << 24) | (lo1 << 16) | (hi2 << 8) | lo2;
|
||||||
|
return signed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 32-bit integer from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order.
|
||||||
|
*
|
||||||
|
* This is identical with extractUInt32, but with registers swapped.
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index+1), (index) interpreted as 32 bit unsigned integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static long extractUInt32Swap(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.UINT32_SWAP);
|
||||||
|
long signed = extractSInt32Swap(bytes, index);
|
||||||
|
long unsigned = signed & 0xffff_ffffL;
|
||||||
|
assert unsigned >= 0;
|
||||||
|
return unsigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 64-bit integer from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order.
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index), (index+1), (index+2), (index+3) interpreted as 64 bit signed integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static long extractSInt64(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.INT64);
|
||||||
|
byte hi1 = (byte) (bytes[index + 0] & 0xff);
|
||||||
|
byte lo1 = (byte) (bytes[index + 1] & 0xff);
|
||||||
|
byte hi2 = (byte) (bytes[index + 2] & 0xff);
|
||||||
|
byte lo2 = (byte) (bytes[index + 3] & 0xff);
|
||||||
|
byte hi3 = (byte) (bytes[index + 4] & 0xff);
|
||||||
|
byte lo3 = (byte) (bytes[index + 5] & 0xff);
|
||||||
|
byte hi4 = (byte) (bytes[index + 6] & 0xff);
|
||||||
|
byte lo4 = (byte) (bytes[index + 7] & 0xff);
|
||||||
|
return new BigInteger(new byte[] { hi1, lo1, hi2, lo2, hi3, lo3, hi4, lo4 }).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 64-bit integer from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order.
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index), (index+1), (index+2), (index+3) interpreted as 64 bit unsigned integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static BigInteger extractUInt64(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.UINT64);
|
||||||
|
byte hi1 = (byte) (bytes[index + 0] & 0xff);
|
||||||
|
byte lo1 = (byte) (bytes[index + 1] & 0xff);
|
||||||
|
byte hi2 = (byte) (bytes[index + 2] & 0xff);
|
||||||
|
byte lo2 = (byte) (bytes[index + 3] & 0xff);
|
||||||
|
byte hi3 = (byte) (bytes[index + 4] & 0xff);
|
||||||
|
byte lo3 = (byte) (bytes[index + 5] & 0xff);
|
||||||
|
byte hi4 = (byte) (bytes[index + 6] & 0xff);
|
||||||
|
byte lo4 = (byte) (bytes[index + 7] & 0xff);
|
||||||
|
return new BigInteger(1, new byte[] { hi1, lo1, hi2, lo2, hi3, lo3, hi4, lo4 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 64-bit integer from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order.
|
||||||
|
*
|
||||||
|
* This is identical with extractInt64, but with registers swapped (registers with higher index before lower index).
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index+3), (index+2), (index+1), (index) interpreted as 64 bit signed integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static long extractSInt64Swap(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.INT64_SWAP);
|
||||||
|
// Swapped order of registers
|
||||||
|
byte hi1 = (byte) (bytes[index + 6] & 0xff);
|
||||||
|
byte lo1 = (byte) (bytes[index + 7] & 0xff);
|
||||||
|
byte hi2 = (byte) (bytes[index + 4] & 0xff);
|
||||||
|
byte lo2 = (byte) (bytes[index + 5] & 0xff);
|
||||||
|
byte hi3 = (byte) (bytes[index + 2] & 0xff);
|
||||||
|
byte lo3 = (byte) (bytes[index + 3] & 0xff);
|
||||||
|
byte hi4 = (byte) (bytes[index + 0] & 0xff);
|
||||||
|
byte lo4 = (byte) (bytes[index + 1] & 0xff);
|
||||||
|
return new BigInteger(new byte[] { hi1, lo1, hi2, lo2, hi3, lo3, hi4, lo4 }).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 64-bit integer from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order.
|
||||||
|
*
|
||||||
|
* This is identical with extractUInt64, but with registers swapped (registers with higher index before lower
|
||||||
|
* index).
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index+3), (index+2), (index+1), (index) interpreted as 64 bit unsigned integer
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static BigInteger extractUInt64Swap(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.UINT64_SWAP);
|
||||||
|
// Swapped order of registers
|
||||||
|
byte hi1 = (byte) (bytes[index + 6] & 0xff);
|
||||||
|
byte lo1 = (byte) (bytes[index + 7] & 0xff);
|
||||||
|
byte hi2 = (byte) (bytes[index + 4] & 0xff);
|
||||||
|
byte lo2 = (byte) (bytes[index + 5] & 0xff);
|
||||||
|
byte hi3 = (byte) (bytes[index + 2] & 0xff);
|
||||||
|
byte lo3 = (byte) (bytes[index + 3] & 0xff);
|
||||||
|
byte hi4 = (byte) (bytes[index + 0] & 0xff);
|
||||||
|
byte lo4 = (byte) (bytes[index + 1] & 0xff);
|
||||||
|
return new BigInteger(1, new byte[] { hi1, lo1, hi2, lo2, hi3, lo3, hi4, lo4 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract single-precision 32-bit IEEE 754 floating point from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order.
|
||||||
|
*
|
||||||
|
* Note that this method can return floating point NaN and floating point infinity.
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index), (index+1), (index+2), (index+3) interpreted as single-precision 32-bit IEEE 754
|
||||||
|
* floating point
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static float extractFloat32(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.FLOAT32);
|
||||||
|
int hi1 = bytes[index + 0] & 0xff;
|
||||||
|
int lo1 = bytes[index + 1] & 0xff;
|
||||||
|
int hi2 = bytes[index + 2] & 0xff;
|
||||||
|
int lo2 = bytes[index + 3] & 0xff;
|
||||||
|
int bits32 = (hi1 << 24) | (lo1 << 16) | (hi2 << 8) | lo2;
|
||||||
|
return Float.intBitsToFloat(bits32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract single-precision 32-bit IEEE 754 floating point from registers represented by sequence of bytes
|
||||||
|
*
|
||||||
|
* It is assumed that each register is encoded in most significant bit first order.
|
||||||
|
*
|
||||||
|
* This is identical with extractFloat32, but with registers swapped (registers with higher index before lower
|
||||||
|
* index).
|
||||||
|
*
|
||||||
|
* Note that this method can return floating point NaN and floating point infinity.
|
||||||
|
*
|
||||||
|
* @param bytes registers represented by sequence of bytes
|
||||||
|
* @param index index of first register. First register has index of 0.
|
||||||
|
* @return registers (index+3), (index+2), (index+1), (index) interpreted as single-precision 32-bit IEEE 754
|
||||||
|
* floating point
|
||||||
|
* @throws IllegalArgumentException when index is out of bounds
|
||||||
|
*/
|
||||||
|
public static float extractFloat32Swap(byte[] bytes, int index) {
|
||||||
|
assertIndexAndType(bytes, index, ValueType.FLOAT32_SWAP);
|
||||||
|
// swapped order of registers, high 16 bits *follow* low 16 bits
|
||||||
|
int hi1 = bytes[index + 2] & 0xff;
|
||||||
|
int lo1 = bytes[index + 3] & 0xff;
|
||||||
|
int hi2 = bytes[index + 0] & 0xff;
|
||||||
|
int lo2 = bytes[index + 1] & 0xff;
|
||||||
|
int bits32 = (hi1 << 24) | (lo1 << 16) | (hi2 << 8) | lo2;
|
||||||
|
return Float.intBitsToFloat(bits32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data from registers and convert the result to String
|
||||||
* Strings should start the the first byte of a register, but could
|
* Strings should start the the first byte of a register, but could
|
||||||
* have an odd number of characters.
|
* have an odd number of characters.
|
||||||
* Raw byte array values are converted using the charset parameter
|
* Raw byte array values are converted using the charset parameter
|
||||||
* and a maximum of length bytes are read. However reading stops at the first
|
* and a maximum of length bytes are read. However reading stops at the first
|
||||||
* NUL byte encountered.
|
* NUL byte encountered.
|
||||||
*
|
*
|
||||||
|
* Registers are read in big-endian order, i.e. two registers consisting 4 bytes (ab, cd) are parsed as sequence of
|
||||||
|
* bytes (a,b,c,d).
|
||||||
|
*
|
||||||
* @param registers list of registers, each register represent 16bit of data
|
* @param registers list of registers, each register represent 16bit of data
|
||||||
* @param index zero based register index. Registers are handled as 16bit registers,
|
* @param registerIndex zero based register index. Registers are handled as 16bit registers,
|
||||||
* this parameter defines the starting register.
|
* this parameter defines the starting register.
|
||||||
* @param length maximum length of string in 8bit characters.
|
* @param length maximum length of string in 8bit characters (number of bytes considered)
|
||||||
* @param charset the character set used to construct the string.
|
* @param charset the character set used to construct the string.
|
||||||
* @return string representation queried value
|
* @return string representation queried value
|
||||||
* @throws IllegalArgumentException when <tt>index</tt> is out of bounds of registers
|
* @throws IllegalArgumentException when <tt>index</tt> is out of bounds of registers
|
||||||
*/
|
*/
|
||||||
public static StringType extractStringFromRegisters(ModbusRegisterArray registers, int index, int length,
|
public static String extractStringFromRegisters(ModbusRegisterArray registers, int registerIndex, int length,
|
||||||
Charset charset) {
|
Charset charset) {
|
||||||
if (index * 2 + length > registers.size() * 2) {
|
return extractStringFromBytes(registers.getBytes(), registerIndex * 2, length, charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data from bytes and convert the result to String
|
||||||
|
*
|
||||||
|
* Raw byte array values are converted using the charset parameter
|
||||||
|
* and a maximum of length bytes are read. However reading stops at the first
|
||||||
|
* NUL byte encountered.
|
||||||
|
*
|
||||||
|
* @param bytes bytes representing the registers
|
||||||
|
* @param byteIndex zero based byte index
|
||||||
|
* @param length maximum length of string in 8bit characters (number of bytes considered)
|
||||||
|
* @param charset the character set used to construct the string.
|
||||||
|
* @return string representation queried value
|
||||||
|
* @throws IllegalArgumentException when <tt>index</tt> is out of bounds of registers
|
||||||
|
*/
|
||||||
|
public static String extractStringFromBytes(byte[] bytes, int byteIndex, int length, Charset charset) {
|
||||||
|
if (byteIndex + length > bytes.length) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
String.format("Index=%d with length=%d is out-of-bounds given registers of size %d", index, length,
|
String.format("byteIndex=%d with length=%d is out-of-bounds given registers of size %d", byteIndex,
|
||||||
registers.size()));
|
length, bytes.length));
|
||||||
}
|
}
|
||||||
if (index < 0) {
|
if (byteIndex < 0) {
|
||||||
throw new IllegalArgumentException("Negative index values are not supported");
|
throw new IllegalArgumentException("Negative index values are not supported");
|
||||||
}
|
}
|
||||||
if (length < 0) {
|
if (length < 0) {
|
||||||
throw new IllegalArgumentException("Negative string length is not supported");
|
throw new IllegalArgumentException("Negative string length is not supported");
|
||||||
}
|
}
|
||||||
byte[] buff = new byte[length];
|
|
||||||
|
|
||||||
int src = index;
|
int effectiveLength = length;
|
||||||
int dest;
|
|
||||||
for (dest = 0; dest < length; dest++) {
|
|
||||||
|
|
||||||
byte chr;
|
// Find first zero byte in registers and call reduce length such that we stop before it
|
||||||
if (dest % 2 == 0) {
|
for (int i = 0; i < length; i++) {
|
||||||
chr = (byte) ((registers.getRegister(src).getValue() >> 8));
|
if (bytes[byteIndex + i] == '\0') {
|
||||||
} else {
|
effectiveLength = i;
|
||||||
chr = (byte) (registers.getRegister(src).getValue() & 0xff);
|
|
||||||
src++;
|
|
||||||
}
|
|
||||||
if (chr == 0) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
buff[dest] = chr;
|
|
||||||
}
|
}
|
||||||
return new StringType(new String(buff, 0, dest, charset));
|
|
||||||
|
return new String(bytes, byteIndex, effectiveLength, charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,89 +647,80 @@ public class ModbusBitUtilities {
|
|||||||
case UINT16: {
|
case UINT16: {
|
||||||
short shortValue = numericCommand.shortValue();
|
short shortValue = numericCommand.shortValue();
|
||||||
// big endian byte ordering
|
// big endian byte ordering
|
||||||
byte b1 = (byte) (shortValue >> 8);
|
byte hi = (byte) (shortValue >> 8);
|
||||||
byte b2 = (byte) shortValue;
|
byte lo = (byte) shortValue;
|
||||||
|
return new ModbusRegisterArray(new byte[] { hi, lo });
|
||||||
ModbusRegister register = new ModbusRegister(b1, b2);
|
|
||||||
return new ModbusRegisterArray(new ModbusRegister[] { register });
|
|
||||||
}
|
}
|
||||||
case INT32:
|
case INT32:
|
||||||
case UINT32: {
|
case UINT32: {
|
||||||
int intValue = numericCommand.intValue();
|
int intValue = numericCommand.intValue();
|
||||||
// big endian byte ordering
|
// big endian byte ordering
|
||||||
byte b1 = (byte) (intValue >> 24);
|
byte hi1 = (byte) (intValue >> 24);
|
||||||
byte b2 = (byte) (intValue >> 16);
|
byte lo1 = (byte) (intValue >> 16);
|
||||||
byte b3 = (byte) (intValue >> 8);
|
byte hi2 = (byte) (intValue >> 8);
|
||||||
byte b4 = (byte) intValue;
|
byte lo2 = (byte) intValue;
|
||||||
ModbusRegister register = new ModbusRegister(b1, b2);
|
return new ModbusRegisterArray(new byte[] { hi1, lo1, hi2, lo2 });
|
||||||
ModbusRegister register2 = new ModbusRegister(b3, b4);
|
|
||||||
return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
|
|
||||||
}
|
}
|
||||||
case INT32_SWAP:
|
case INT32_SWAP:
|
||||||
case UINT32_SWAP: {
|
case UINT32_SWAP: {
|
||||||
int intValue = numericCommand.intValue();
|
int intValue = numericCommand.intValue();
|
||||||
// big endian byte ordering
|
// big endian byte ordering
|
||||||
byte b1 = (byte) (intValue >> 24);
|
byte hi1 = (byte) (intValue >> 24);
|
||||||
byte b2 = (byte) (intValue >> 16);
|
byte lo1 = (byte) (intValue >> 16);
|
||||||
byte b3 = (byte) (intValue >> 8);
|
byte hi2 = (byte) (intValue >> 8);
|
||||||
byte b4 = (byte) intValue;
|
byte lo2 = (byte) intValue;
|
||||||
ModbusRegister register = new ModbusRegister(b3, b4);
|
// Swapped order of registers
|
||||||
ModbusRegister register2 = new ModbusRegister(b1, b2);
|
return new ModbusRegisterArray(new byte[] { hi2, lo2, hi1, lo1 });
|
||||||
return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
|
|
||||||
}
|
}
|
||||||
case FLOAT32: {
|
case FLOAT32: {
|
||||||
float floatValue = numericCommand.floatValue();
|
float floatValue = numericCommand.floatValue();
|
||||||
int intBits = Float.floatToIntBits(floatValue);
|
int intBits = Float.floatToIntBits(floatValue);
|
||||||
// big endian byte ordering
|
// big endian byte ordering
|
||||||
byte b1 = (byte) (intBits >> 24);
|
byte hi1 = (byte) (intBits >> 24);
|
||||||
byte b2 = (byte) (intBits >> 16);
|
byte lo1 = (byte) (intBits >> 16);
|
||||||
byte b3 = (byte) (intBits >> 8);
|
byte hi2 = (byte) (intBits >> 8);
|
||||||
byte b4 = (byte) intBits;
|
byte lo2 = (byte) intBits;
|
||||||
ModbusRegister register = new ModbusRegister(b1, b2);
|
return new ModbusRegisterArray(new byte[] { hi1, lo1, hi2, lo2 });
|
||||||
ModbusRegister register2 = new ModbusRegister(b3, b4);
|
|
||||||
return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
|
|
||||||
}
|
}
|
||||||
case FLOAT32_SWAP: {
|
case FLOAT32_SWAP: {
|
||||||
float floatValue = numericCommand.floatValue();
|
float floatValue = numericCommand.floatValue();
|
||||||
int intBits = Float.floatToIntBits(floatValue);
|
int intBits = Float.floatToIntBits(floatValue);
|
||||||
// big endian byte ordering
|
// big endian byte ordering
|
||||||
byte b1 = (byte) (intBits >> 24);
|
byte hi1 = (byte) (intBits >> 24);
|
||||||
byte b2 = (byte) (intBits >> 16);
|
byte lo1 = (byte) (intBits >> 16);
|
||||||
byte b3 = (byte) (intBits >> 8);
|
byte hi2 = (byte) (intBits >> 8);
|
||||||
byte b4 = (byte) intBits;
|
byte lo2 = (byte) intBits;
|
||||||
ModbusRegister register = new ModbusRegister(b3, b4);
|
// Swapped order of registers
|
||||||
ModbusRegister register2 = new ModbusRegister(b1, b2);
|
return new ModbusRegisterArray(new byte[] { hi2, lo2, hi1, lo1 });
|
||||||
return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
|
|
||||||
}
|
}
|
||||||
case INT64:
|
case INT64:
|
||||||
case UINT64: {
|
case UINT64: {
|
||||||
long longValue = numericCommand.longValue();
|
long longValue = numericCommand.longValue();
|
||||||
// big endian byte ordering
|
// big endian byte ordering
|
||||||
byte b1 = (byte) (longValue >> 56);
|
byte hi1 = (byte) (longValue >> 56);
|
||||||
byte b2 = (byte) (longValue >> 48);
|
byte lo1 = (byte) (longValue >> 48);
|
||||||
byte b3 = (byte) (longValue >> 40);
|
byte hi2 = (byte) (longValue >> 40);
|
||||||
byte b4 = (byte) (longValue >> 32);
|
byte lo2 = (byte) (longValue >> 32);
|
||||||
byte b5 = (byte) (longValue >> 24);
|
byte hi3 = (byte) (longValue >> 24);
|
||||||
byte b6 = (byte) (longValue >> 16);
|
byte lo3 = (byte) (longValue >> 16);
|
||||||
byte b7 = (byte) (longValue >> 8);
|
byte hi4 = (byte) (longValue >> 8);
|
||||||
byte b8 = (byte) longValue;
|
byte lo4 = (byte) longValue;
|
||||||
return new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister(b1, b2),
|
return new ModbusRegisterArray(new byte[] { hi1, lo1, hi2, lo2, hi3, lo3, hi4, lo4 });
|
||||||
new ModbusRegister(b3, b4), new ModbusRegister(b5, b6), new ModbusRegister(b7, b8) });
|
|
||||||
}
|
}
|
||||||
case INT64_SWAP:
|
case INT64_SWAP:
|
||||||
case UINT64_SWAP: {
|
case UINT64_SWAP: {
|
||||||
long longValue = numericCommand.longValue();
|
long longValue = numericCommand.longValue();
|
||||||
// big endian byte ordering
|
// big endian byte ordering
|
||||||
byte b1 = (byte) (longValue >> 56);
|
byte hi1 = (byte) (longValue >> 56);
|
||||||
byte b2 = (byte) (longValue >> 48);
|
byte lo1 = (byte) (longValue >> 48);
|
||||||
byte b3 = (byte) (longValue >> 40);
|
byte hi2 = (byte) (longValue >> 40);
|
||||||
byte b4 = (byte) (longValue >> 32);
|
byte lo2 = (byte) (longValue >> 32);
|
||||||
byte b5 = (byte) (longValue >> 24);
|
byte hi3 = (byte) (longValue >> 24);
|
||||||
byte b6 = (byte) (longValue >> 16);
|
byte lo3 = (byte) (longValue >> 16);
|
||||||
byte b7 = (byte) (longValue >> 8);
|
byte hi4 = (byte) (longValue >> 8);
|
||||||
byte b8 = (byte) longValue;
|
byte lo4 = (byte) longValue;
|
||||||
return new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister(b7, b8),
|
// Swapped order of registers
|
||||||
new ModbusRegister(b5, b6), new ModbusRegister(b3, b4), new ModbusRegister(b1, b2) });
|
return new ModbusRegisterArray(new byte[] { hi4, lo4, hi3, lo3, hi2, lo2, hi1, lo1 });
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException(
|
throw new NotImplementedException(
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.io.transport.modbus;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
|
|
||||||
import net.wimpi.modbus.procimg.SimpleInputRegister;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic {@link ModbusRegister} implementation
|
|
||||||
*
|
|
||||||
* @author Sami Salonen - Initial contribution
|
|
||||||
*/
|
|
||||||
@NonNullByDefault
|
|
||||||
public class ModbusRegister {
|
|
||||||
|
|
||||||
private final SimpleInputRegister wrapped;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance for bytes
|
|
||||||
*
|
|
||||||
* @param b1 the first (hi) byte of the word.
|
|
||||||
* @param b2 the second (low) byte of the word.
|
|
||||||
*/
|
|
||||||
public ModbusRegister(byte b1, byte b2) {
|
|
||||||
wrapped = new SimpleInputRegister(b1, b2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct register for at
|
|
||||||
*
|
|
||||||
* @param val value representing register data. The <code>int</code> will be downcasted to <code>short</code>.
|
|
||||||
*/
|
|
||||||
public ModbusRegister(int val) {
|
|
||||||
wrapped = new SimpleInputRegister(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get raw data represented by this register. Since register is 16 bits, array of length 2 will be returned.
|
|
||||||
*
|
|
||||||
* @return byte array of length 2, high byte first.
|
|
||||||
*/
|
|
||||||
public byte[] getBytes() {
|
|
||||||
return wrapped.toBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of this register as integer representing 16 bit data parsed as signed integer.
|
|
||||||
*
|
|
||||||
* @return the register content as unsigned integer
|
|
||||||
*/
|
|
||||||
public int getValue() {
|
|
||||||
return wrapped.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of this register as integer representing 16 bit data parsed as unsigned integer.
|
|
||||||
*
|
|
||||||
* @return the register content as unsigned integer
|
|
||||||
*/
|
|
||||||
public int toUnsignedShort() {
|
|
||||||
return wrapped.toUnsignedShort();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuffer buffer = new StringBuffer("ModbusRegisterImpl(");
|
|
||||||
buffer.append("uint16=").append(toUnsignedShort()).append(", hex=");
|
|
||||||
return appendHexString(buffer).append(')').toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the register value as hex string
|
|
||||||
*
|
|
||||||
* For example, 12 34
|
|
||||||
*
|
|
||||||
* @return string representing the register data
|
|
||||||
*/
|
|
||||||
public String toHexString() {
|
|
||||||
StringBuffer buffer = new StringBuffer(5);
|
|
||||||
return appendHexString(buffer).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the register value as hex string to the given StringBuffer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public StringBuffer appendHexString(StringBuffer buffer) {
|
|
||||||
byte[] bytes = getBytes();
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
byte b = bytes[i];
|
|
||||||
String byteHex = Long.toHexString(b & 0xff);
|
|
||||||
if ((b & 0xff) < 0x10) {
|
|
||||||
buffer.append('0');
|
|
||||||
}
|
|
||||||
buffer.append(byteHex);
|
|
||||||
if (i == 0) {
|
|
||||||
buffer.append(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,10 +12,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.io.transport.modbus;
|
package org.openhab.io.transport.modbus;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Arrays;
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.util.HexUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable {@link ModbusRegisterArray} implementation
|
* Immutable {@link ModbusRegisterArray} implementation
|
||||||
@@ -23,54 +23,60 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
* @author Sami Salonen - Initial contribution
|
* @author Sami Salonen - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class ModbusRegisterArray implements Iterable<ModbusRegister> {
|
public class ModbusRegisterArray {
|
||||||
|
|
||||||
private final ModbusRegister[] registers;
|
private final byte[] bytes;
|
||||||
|
|
||||||
/**
|
public ModbusRegisterArray(byte... bytes) {
|
||||||
* Construct plain <code>ModbusRegister[]</code> array from register values
|
if (bytes.length % 2 != 0) {
|
||||||
*
|
throw new IllegalArgumentException();
|
||||||
* @param registerValues register values, each <code>int</code> corresponding to one register
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static ModbusRegister[] registersFromValues(int... registerValues) {
|
|
||||||
ModbusRegister[] registers = new ModbusRegister[registerValues.length];
|
|
||||||
for (int i = 0; i < registerValues.length; i++) {
|
|
||||||
registers[i] = new ModbusRegister(registerValues[i]);
|
|
||||||
}
|
}
|
||||||
return registers;
|
this.bytes = Arrays.copyOf(bytes, bytes.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct ModbusRegisterArrayImpl from array of {@link ModbusRegister}
|
* Construct plain <code>ModbusRegisterArray</code> array from register values
|
||||||
*
|
|
||||||
* @param registers
|
|
||||||
*/
|
|
||||||
public ModbusRegisterArray(ModbusRegister[] registers) {
|
|
||||||
this.registers = registers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct plain <code>ModbusRegisterArrayImpl</code> array from register values
|
|
||||||
*
|
*
|
||||||
* @param registerValues register values, each <code>int</code> corresponding to one register
|
* @param registerValues register values, each <code>int</code> corresponding to one register
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public ModbusRegisterArray(int... registerValues) {
|
public ModbusRegisterArray(int... registerValues) {
|
||||||
this(registersFromValues(registerValues));
|
bytes = new byte[registerValues.length * 2];
|
||||||
|
for (int registerIndex = 0; registerIndex < registerValues.length; registerIndex++) {
|
||||||
|
int register = registerValues[registerIndex] & 0xffff;
|
||||||
|
// hi-byte
|
||||||
|
bytes[registerIndex * 2] = (byte) (register >> 8);
|
||||||
|
// lo byte
|
||||||
|
bytes[registerIndex * 2 + 1] = (byte) register;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return register at the given index
|
* Get register index i as unsigned integer
|
||||||
*
|
*
|
||||||
* Index 0 matches first register (lowest register index).
|
* @param i register index
|
||||||
* <p>
|
* @return register value interpreted as unsigned integer (big-endian byte ordering)
|
||||||
*
|
|
||||||
* @param index the index of the register to be returned.
|
|
||||||
* @throws IndexOutOfBoundsException if the index is out of bounds.
|
|
||||||
*/
|
*/
|
||||||
public ModbusRegister getRegister(int index) {
|
public int getRegister(int i) {
|
||||||
return registers[index];
|
int hi = bytes[i * 2] & 0xff;
|
||||||
|
int lo = bytes[i * 2 + 1] & 0xff;
|
||||||
|
return ((hi << 8) | lo) & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return bytes representing the registers
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Index 0: hi-byte of 1st register
|
||||||
|
* Index 1: low-byte of 1st register
|
||||||
|
* Index 3: hi-byte of 2nd register
|
||||||
|
* Index 4: low-byte of 2nd register
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* @return set of bytes
|
||||||
|
*/
|
||||||
|
public byte[] getBytes() {
|
||||||
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,24 +85,16 @@ public class ModbusRegisterArray implements Iterable<ModbusRegister> {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public int size() {
|
public int size() {
|
||||||
return registers.length;
|
return bytes.length / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (registers.length == 0) {
|
if (bytes.length == 0) {
|
||||||
return "ModbusRegisterArrayImpl(<empty>)";
|
return "ModbusRegisterArray(<empty>)";
|
||||||
}
|
}
|
||||||
StringBuffer buffer = new StringBuffer(registers.length * 2).append("ModbusRegisterArrayImpl(");
|
return new StringBuilder(bytes.length).append("ModbusRegisterArray(").append(toHexString()).append(')')
|
||||||
return appendHexString(buffer).append(')').toString();
|
.toString();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterator over all the registers
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Iterator<ModbusRegister> iterator() {
|
|
||||||
return IntStream.range(0, size()).mapToObj(i -> getRegister(i)).iterator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,22 +108,6 @@ public class ModbusRegisterArray implements Iterable<ModbusRegister> {
|
|||||||
if (size() == 0) {
|
if (size() == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
// Initialize capacity to (n*2 + n-1), two chars per byte + spaces in between
|
return HexUtils.bytesToHex(getBytes());
|
||||||
StringBuffer buffer = new StringBuffer(size() * 2 + (size() - 1));
|
|
||||||
return appendHexString(buffer).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the register data as hex string to the given StringBuffer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public StringBuffer appendHexString(StringBuffer buffer) {
|
|
||||||
IntStream.range(0, size()).forEachOrdered(index -> {
|
|
||||||
getRegister(index).appendHexString(buffer);
|
|
||||||
if (index < size() - 1) {
|
|
||||||
buffer.append(' ');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.io.transport.modbus;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.BufferOverflowException;
|
||||||
|
import java.nio.InvalidMarkException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ByteBuffer-like interface for working with different types of data stored in byte arrays
|
||||||
|
*
|
||||||
|
* @author Sami Salonen - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ValueBuffer {
|
||||||
|
private final byte[] bytes;
|
||||||
|
private final AtomicInteger byteIndex = new AtomicInteger();
|
||||||
|
private volatile AtomicReference<@Nullable AtomicInteger> mark = new AtomicReference<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap modbus registers and create a new instance of ValueBuffer
|
||||||
|
*
|
||||||
|
* The instance will have position of 0.
|
||||||
|
*
|
||||||
|
* @param array set of registers
|
||||||
|
* @return new instance of ValueBuffer referencing bytes represented by modbus register array
|
||||||
|
*/
|
||||||
|
public static ValueBuffer wrap(ModbusRegisterArray array) {
|
||||||
|
return new ValueBuffer(array.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap given bytes and create a new instance of ValueBuffer
|
||||||
|
*
|
||||||
|
* The instance will have position of 0.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param array set of bytes to wrap
|
||||||
|
* @return new instance of ValueBuffer referencing bytes
|
||||||
|
*/
|
||||||
|
public static ValueBuffer wrap(byte[] array) {
|
||||||
|
return new ValueBuffer(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueBuffer(byte[] bytes) {
|
||||||
|
this.bytes = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this buffer's position.
|
||||||
|
*
|
||||||
|
* @return The position of this buffer
|
||||||
|
*/
|
||||||
|
public int position() {
|
||||||
|
return byteIndex.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets this buffer's position. If the mark is defined and larger than the new position then it is discarded.
|
||||||
|
*
|
||||||
|
* @return this buffer
|
||||||
|
*/
|
||||||
|
public ValueBuffer position(int byteIndex) {
|
||||||
|
this.mark.getAndUpdate(curMark -> {
|
||||||
|
if (curMark == null) {
|
||||||
|
return null;
|
||||||
|
} else if (curMark.get() > byteIndex) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return curMark;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.byteIndex.set(byteIndex);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets this buffer's mark at its position.
|
||||||
|
*
|
||||||
|
* @return this buffer
|
||||||
|
*/
|
||||||
|
public ValueBuffer mark() {
|
||||||
|
mark = new AtomicReference<>(new AtomicInteger(byteIndex.get()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets this buffer's position to the previously-marked position.
|
||||||
|
* Invoking this method neither changes nor discards the mark's value.
|
||||||
|
*
|
||||||
|
* @return this buffer
|
||||||
|
* @throws InvalidMarkException If the mark has not been set
|
||||||
|
*/
|
||||||
|
public ValueBuffer reset() throws InvalidMarkException {
|
||||||
|
int mark = Optional.ofNullable(this.mark.get()).map(i -> i.get()).orElse(-1);
|
||||||
|
if (mark < 0) {
|
||||||
|
throw new InvalidMarkException();
|
||||||
|
}
|
||||||
|
byteIndex.set(mark);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes between the current position and the end.
|
||||||
|
*
|
||||||
|
* @return The number of bytes remaining in this buffer
|
||||||
|
*/
|
||||||
|
public int remaining() {
|
||||||
|
return bytes.length - byteIndex.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns underlying bytes
|
||||||
|
*
|
||||||
|
* @return reference to underlying bytes
|
||||||
|
*/
|
||||||
|
public byte[] array() {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether there are any bytes left between current position and the end
|
||||||
|
*
|
||||||
|
* @return true if, and only if, there is at least one byte remaining in this buffer
|
||||||
|
*/
|
||||||
|
public boolean hasRemaining() {
|
||||||
|
return remaining() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starting from current position, read dst.length number of bytes and copy the data to dst
|
||||||
|
*
|
||||||
|
* @param dst copied bytes
|
||||||
|
* @return this buffer
|
||||||
|
* @throws BufferOverflowException If there is insufficient space in this buffer for the remaining bytes in the
|
||||||
|
* source buffer
|
||||||
|
*/
|
||||||
|
public ValueBuffer get(byte[] dst) {
|
||||||
|
int start = byteIndex.getAndAdd(dst.length);
|
||||||
|
try {
|
||||||
|
System.arraycopy(bytes, start, dst, 0, dst.length);
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
throw new BufferOverflowException();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 8-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* @return signed 8-bit integer (byte)
|
||||||
|
* @see ModbusBitUtilities.extractSInt8
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public byte getSInt8() {
|
||||||
|
return ModbusBitUtilities.extractSInt8(bytes, byteIndex.getAndAdd(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 8-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* @return unsigned 8-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractUInt8
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public short getUInt8() {
|
||||||
|
return ModbusBitUtilities.extractUInt8(bytes, byteIndex.getAndAdd(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 16-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* @return signed 16-bit integer (short)
|
||||||
|
* @see ModbusBitUtilities.extractSInt16
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public short getSInt16() {
|
||||||
|
return ModbusBitUtilities.extractSInt16(bytes, byteIndex.getAndAdd(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 16-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* @return unsigned 16-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractUInt16
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public int getUInt16() {
|
||||||
|
return ModbusBitUtilities.extractUInt16(bytes, byteIndex.getAndAdd(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 32-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* @return signed 32-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractSInt32
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public int getSInt32() {
|
||||||
|
return ModbusBitUtilities.extractSInt32(bytes, byteIndex.getAndAdd(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 32-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* @return unsigned 32-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractUInt32
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public long getUInt32() {
|
||||||
|
return ModbusBitUtilities.extractUInt32(bytes, byteIndex.getAndAdd(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 32-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* This is identical with getSInt32, but with registers swapped.
|
||||||
|
*
|
||||||
|
* @return signed 32-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractSInt32Swap
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public int getSInt32Swap() {
|
||||||
|
return ModbusBitUtilities.extractSInt32Swap(bytes, byteIndex.getAndAdd(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 32-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* This is identical with getUInt32, but with registers swapped.
|
||||||
|
*
|
||||||
|
* @return unsigned 32-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractUInt32Swap
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public long getUInt32Swap() {
|
||||||
|
return ModbusBitUtilities.extractUInt32Swap(bytes, byteIndex.getAndAdd(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 64-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* @return signed 64-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractInt64
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public long getSInt64() {
|
||||||
|
return ModbusBitUtilities.extractSInt64(bytes, byteIndex.getAndAdd(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 64-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* @return unsigned 64-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractUInt64
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public BigInteger getUInt64() {
|
||||||
|
return ModbusBitUtilities.extractUInt64(bytes, byteIndex.getAndAdd(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract signed 64-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* This is identical with getSInt64, but with registers swapped.
|
||||||
|
*
|
||||||
|
* @return signed 64-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractInt64Swap
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public long getSInt64Swap() {
|
||||||
|
return ModbusBitUtilities.extractSInt64Swap(bytes, byteIndex.getAndAdd(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract unsigned 64-bit integer at current position, and advance position.
|
||||||
|
*
|
||||||
|
* This is identical with getUInt64, but with registers swapped.
|
||||||
|
*
|
||||||
|
* @return unsigned 64-bit integer
|
||||||
|
* @see ModbusBitUtilities.extractUInt64Swap
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public BigInteger getUInt64Swap() {
|
||||||
|
return ModbusBitUtilities.extractUInt64Swap(bytes, byteIndex.getAndAdd(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract single-precision 32-bit IEEE 754 floating point at current position, and advance position.
|
||||||
|
*
|
||||||
|
* Note that this method can return floating point NaN and floating point infinity.
|
||||||
|
*
|
||||||
|
* @return single-precision 32-bit IEEE 754 floating point
|
||||||
|
* @see ModbusBitUtilities.extractFloat32
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public float getFloat32() {
|
||||||
|
return ModbusBitUtilities.extractFloat32(bytes, byteIndex.getAndAdd(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract single-precision 32-bit IEEE 754 floating point at current position, and advance position.
|
||||||
|
*
|
||||||
|
* This is identical with getFloat32, but with registers swapped.
|
||||||
|
*
|
||||||
|
* Note that this method can return floating point NaN and floating point infinity.
|
||||||
|
*
|
||||||
|
* @return single-precision 32-bit IEEE 754 floating point
|
||||||
|
* @see ModbusBitUtilities.extractFloat32
|
||||||
|
* @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
|
||||||
|
*/
|
||||||
|
public float getFloat32Swap() {
|
||||||
|
return ModbusBitUtilities.extractFloat32Swap(bytes, byteIndex.getAndAdd(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ import org.openhab.io.transport.modbus.BitArray;
|
|||||||
import org.openhab.io.transport.modbus.ModbusReadCallback;
|
import org.openhab.io.transport.modbus.ModbusReadCallback;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.ModbusRegister;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
|
||||||
@@ -87,9 +86,9 @@ public class ModbusLibraryWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static ModbusRegisterArray modbusRegisterArrayFromInputRegisters(InputRegister[] inputRegisters) {
|
private static ModbusRegisterArray modbusRegisterArrayFromInputRegisters(InputRegister[] inputRegisters) {
|
||||||
ModbusRegister[] registers = new ModbusRegister[inputRegisters.length];
|
int[] registers = new int[inputRegisters.length];
|
||||||
for (int i = 0; i < inputRegisters.length; i++) {
|
for (int i = 0; i < inputRegisters.length; i++) {
|
||||||
registers[i] = new ModbusRegister(inputRegisters[i].getValue());
|
registers[i] = inputRegisters[i].getValue();
|
||||||
}
|
}
|
||||||
return new ModbusRegisterArray(registers);
|
return new ModbusRegisterArray(registers);
|
||||||
}
|
}
|
||||||
@@ -270,7 +269,7 @@ public class ModbusLibraryWrapper {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static Register[] convertRegisters(ModbusRegisterArray arr) {
|
public static Register[] convertRegisters(ModbusRegisterArray arr) {
|
||||||
return IntStream.range(0, arr.size()).mapToObj(i -> new SimpleInputRegister(arr.getRegister(i).getValue()))
|
return IntStream.range(0, arr.size()).mapToObj(i -> new SimpleInputRegister(arr.getRegister(i)))
|
||||||
.collect(Collectors.toList()).toArray(new Register[0]);
|
.collect(Collectors.toList()).toArray(new Register[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.io.transport.modbus.BitArray;
|
import org.openhab.io.transport.modbus.BitArray;
|
||||||
import org.openhab.io.transport.modbus.ModbusConstants;
|
import org.openhab.io.transport.modbus.ModbusConstants;
|
||||||
import org.openhab.io.transport.modbus.ModbusRegister;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
|
import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
|
||||||
@@ -197,7 +196,7 @@ public final class WriteRequestJsonUtilities {
|
|||||||
}
|
}
|
||||||
// fall-through to WRITE_MULTIPLE_REGISTERS
|
// fall-through to WRITE_MULTIPLE_REGISTERS
|
||||||
case WRITE_MULTIPLE_REGISTERS: {
|
case WRITE_MULTIPLE_REGISTERS: {
|
||||||
ModbusRegister[] registers = new ModbusRegister[valuesElem.size()];
|
int[] registers = new int[valuesElem.size()];
|
||||||
if (registers.length == 0) {
|
if (registers.length == 0) {
|
||||||
throw new IllegalArgumentException("Must provide at least one register");
|
throw new IllegalArgumentException("Must provide at least one register");
|
||||||
} else if (valuesElem.size() > ModbusConstants.MAX_REGISTERS_WRITE_COUNT) {
|
} else if (valuesElem.size() > ModbusConstants.MAX_REGISTERS_WRITE_COUNT) {
|
||||||
@@ -206,7 +205,7 @@ public final class WriteRequestJsonUtilities {
|
|||||||
ModbusConstants.MAX_REGISTERS_WRITE_COUNT));
|
ModbusConstants.MAX_REGISTERS_WRITE_COUNT));
|
||||||
}
|
}
|
||||||
for (int i = 0; i < valuesElem.size(); i++) {
|
for (int i = 0; i < valuesElem.size(); i++) {
|
||||||
registers[i] = new ModbusRegister(valuesElem.get(i).getAsInt());
|
registers[i] = valuesElem.get(i).getAsInt();
|
||||||
}
|
}
|
||||||
return new ModbusWriteRegisterRequestBlueprint(unitId, address, new ModbusRegisterArray(registers),
|
return new ModbusWriteRegisterRequestBlueprint(unitId, address, new ModbusRegisterArray(registers),
|
||||||
!writeSingle.get(), maxTries);
|
!writeSingle.get(), maxTries);
|
||||||
|
|||||||
@@ -322,9 +322,9 @@ public class BitUtilitiesCommandToRegistersTest {
|
|||||||
is(equalTo(expectedRegisters.length)));
|
is(equalTo(expectedRegisters.length)));
|
||||||
for (int i = 0; i < expectedRegisters.length; i++) {
|
for (int i = 0; i < expectedRegisters.length; i++) {
|
||||||
int expectedRegisterDataUnsigned = expectedRegisters[i] & 0xffff;
|
int expectedRegisterDataUnsigned = expectedRegisters[i] & 0xffff;
|
||||||
int actual = registers.getRegister(i).getValue();
|
int actualUnsigned = registers.getRegister(i);
|
||||||
|
|
||||||
assertThat(String.format("register index i=%d, command=%s, type=%s", i, command, type), actual,
|
assertThat(String.format("register index i=%d, command=%s, type=%s", i, command, type), actualUnsigned,
|
||||||
is(equalTo(expectedRegisterDataUnsigned)));
|
is(equalTo(expectedRegisterDataUnsigned)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.io.transport.modbus.test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Tests for extractBit
|
||||||
|
*
|
||||||
|
* @author Sami Salonen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class BitUtilitiesExtractBitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractBitWithRegisterIndexAndBitIndex() {
|
||||||
|
byte[] bytes = new byte[] { 0b00100001, // hi byte of 1st register
|
||||||
|
0b00100101, // lo byte of 1st register
|
||||||
|
0b00110001, // hi byte of 2nd register
|
||||||
|
0b00101001 }; // lo byte of 2nd register
|
||||||
|
|
||||||
|
{
|
||||||
|
int registerIndex = 0;
|
||||||
|
int[] expectedBitsFromLSBtoMSB = new int[] { //
|
||||||
|
1, 0, 1, 0, 0, 1, 0, 0, // lo byte, with increasing significance
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0 // hi byte, with increasing significance
|
||||||
|
};
|
||||||
|
for (int bitIndex = 0; bitIndex < expectedBitsFromLSBtoMSB.length; bitIndex++) {
|
||||||
|
assertEquals(expectedBitsFromLSBtoMSB[bitIndex],
|
||||||
|
ModbusBitUtilities.extractBit(bytes, registerIndex, bitIndex),
|
||||||
|
String.format("bitIndex=%d", bitIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
int registerIndex = 1;
|
||||||
|
int[] expectedBitsFromLSBtoMSB = new int[] { //
|
||||||
|
1, 0, 0, 1, 0, 1, 0, 0, // lo byte, with increasing significance
|
||||||
|
1, 0, 0, 0, 1, 1, 0, 0 // hi byte, with increasing significance
|
||||||
|
};
|
||||||
|
for (int bitIndex = 0; bitIndex < expectedBitsFromLSBtoMSB.length; bitIndex++) {
|
||||||
|
assertEquals(expectedBitsFromLSBtoMSB[bitIndex],
|
||||||
|
ModbusBitUtilities.extractBit(bytes, registerIndex, bitIndex),
|
||||||
|
String.format("bitIndex=%d", bitIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractBitWithRegisterIndexAndBitIndexOOB() {
|
||||||
|
|
||||||
|
byte[] bytes = new byte[] { 0b00100001, // hi byte of 1st register
|
||||||
|
0b00100101, // lo byte of 1st register
|
||||||
|
0b00110001, // hi byte of 2nd register
|
||||||
|
0b00101001 }; // lo byte of 2nd register
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractBit(bytes, 3, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractBitWithRegisterIndexAndBitIndexOOB2() {
|
||||||
|
byte[] bytes = new byte[] { 0b00100001, // hi byte of 1st register
|
||||||
|
0b00100101, // lo byte of 1st register
|
||||||
|
0b00110001, // hi byte of 2nd register
|
||||||
|
0b00101001 }; // lo byte of 2nd register
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractBit(bytes, 0, 17));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractBitWithRegisterIndexAndBitIndexOOB3() {
|
||||||
|
byte[] bytes = new byte[] { 0b00100001, // hi byte of 1st register
|
||||||
|
0b00100101, // lo byte of 1st register
|
||||||
|
0b00110001, // hi byte of 2nd register
|
||||||
|
0b00101001 }; // lo byte of 2nd register
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractBit(bytes, 0, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractBitWithRegisterIndexAndBitIndexOOB4() {
|
||||||
|
byte[] bytes = new byte[] { 0b00100001, // hi byte of 1st register
|
||||||
|
0b00100101, // lo byte of 1st register
|
||||||
|
0b00110001, // hi byte of 2nd register
|
||||||
|
0b00101001 }; // lo byte of 2nd register
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractBit(bytes, -1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractBitWithSingleIndex() {
|
||||||
|
byte[] bytes = new byte[] { 0b00100001, // hi byte of 1st register
|
||||||
|
0b00100101, // lo byte of 1st register
|
||||||
|
0b00110001, // hi byte of 2nd register
|
||||||
|
0b00101001 }; // lo byte of 2nd register
|
||||||
|
int[] expectedBits = new int[] { //
|
||||||
|
1, 0, 1, 0, 0, 1, 0, 0, // 1st register: lo byte, with increasing significance
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, // 1st register: hi byte, with increasing significance
|
||||||
|
1, 0, 0, 1, 0, 1, 0, 0, // 2nd register: lo byte, with increasing significance
|
||||||
|
1, 0, 0, 0, 1, 1, 0, 0 // 2nd register: hi byte, with increasing significance
|
||||||
|
};
|
||||||
|
for (int bitIndex = 0; bitIndex < expectedBits.length; bitIndex++) {
|
||||||
|
assertEquals(expectedBits[bitIndex], ModbusBitUtilities.extractBit(bytes, bitIndex),
|
||||||
|
String.format("bitIndex=%d", bitIndex));
|
||||||
|
assertEquals(expectedBits[bitIndex], ModbusBitUtilities.extractBit(bytes, bitIndex),
|
||||||
|
String.format("bitIndex=%d", bitIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractBitWithSingleIndexOOB() {
|
||||||
|
byte[] bytes = new byte[] { 0b00100001, // hi byte of 1st register
|
||||||
|
0b00100101, // lo byte of 1st register
|
||||||
|
0b00110001, // hi byte of 2nd register
|
||||||
|
0b00101001 }; // lo byte of 2nd register
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractBit(bytes, 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractBitWithSingleIndexOOB2() {
|
||||||
|
byte[] bytes = new byte[] { 0b00100001, // hi byte of 1st register
|
||||||
|
0b00100101, // lo byte of 1st register
|
||||||
|
0b00110001, // hi byte of 2nd register
|
||||||
|
0b00101001 }; // lo byte of 2nd register
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractBit(bytes, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.io.transport.modbus.test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Tests for 'special' float values such as infinity and NaN. These are not covered in detail in
|
||||||
|
* {@link BitUtilitiesExtractIndividualMethodsTest} and
|
||||||
|
* {@link BitUtilitiesExtractStateFromRegistersTest}
|
||||||
|
*
|
||||||
|
* @author Sami Salonen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class BitUtilitiesExtractFloat32Test {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a byte array with byteOffset number of zeroes, followed by 32bit of data represented by data
|
||||||
|
*
|
||||||
|
* @param data actual data payload
|
||||||
|
* @param byteOffset number of zeros padded
|
||||||
|
* @return byte array of size 4 + byteOffset
|
||||||
|
*/
|
||||||
|
private static byte[] bytes(int data, int byteOffset) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(4 + byteOffset);
|
||||||
|
for (int i = 0; i < byteOffset; i++) {
|
||||||
|
buffer.put((byte) 0);
|
||||||
|
}
|
||||||
|
buffer.putInt(data);
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testFloat(float number) {
|
||||||
|
int data = Float.floatToIntBits(number);
|
||||||
|
for (int byteOffset = 0; byteOffset < 5; byteOffset++) {
|
||||||
|
byte[] bytes = bytes(data, byteOffset);
|
||||||
|
float actual = ModbusBitUtilities.extractFloat32(bytes, byteOffset);
|
||||||
|
float expected = Float.intBitsToFloat(data);
|
||||||
|
// Strict comparison of the float values with the exception of NaN
|
||||||
|
assertTrue(Float.isNaN(expected) ? Float.isNaN(actual) : expected == actual,
|
||||||
|
String.format("Testing %f (%s) with offset %d, got %f (%s)", expected, Integer.toBinaryString(data),
|
||||||
|
byteOffset, actual, Integer.toBinaryString(Float.floatToRawIntBits(actual))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractFloat32Inf() {
|
||||||
|
testFloat(Float.POSITIVE_INFINITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractFloat32NegInf() {
|
||||||
|
testFloat(Float.NEGATIVE_INFINITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractFloat32NaN() {
|
||||||
|
testFloat(Float.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractFloat32Regular() {
|
||||||
|
testFloat(1.3f);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.io.transport.modbus.test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.Stream.Builder;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sami Salonen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class BitUtilitiesExtractIndividualMethodsTest {
|
||||||
|
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
// We use test data from BitUtilitiesExtractStateFromRegistersTest
|
||||||
|
// In BitUtilitiesExtractStateFromRegistersTest the data is aligned to registers
|
||||||
|
//
|
||||||
|
// Here (in registerVariations) we generate offsetted variations of the byte data
|
||||||
|
// to test extractXX which can operate on data aligned on byte-level, not just data aligned on-register level
|
||||||
|
Collection<Object[]> data = BitUtilitiesExtractStateFromRegistersTest.data();
|
||||||
|
return data.stream().flatMap(values -> {
|
||||||
|
Object expectedResult = values[0];
|
||||||
|
ValueType type = (ValueType) values[1];
|
||||||
|
ModbusRegisterArray registers = (ModbusRegisterArray) values[2];
|
||||||
|
int index = (int) values[3];
|
||||||
|
return registerVariations(expectedResult, type, registers, index);
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestData(ValueType type) {
|
||||||
|
return data().stream().filter(values -> (ValueType) values[1] == type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate register variations for extractXX functions
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return entries of (byte[], byteIndex)
|
||||||
|
*/
|
||||||
|
private static Stream<Object[]> registerVariations(Object expectedResult, ValueType type,
|
||||||
|
ModbusRegisterArray registers, int index) {
|
||||||
|
byte[] origBytes = registers.getBytes();
|
||||||
|
int origRegisterIndex = index;
|
||||||
|
int origByteIndex = origRegisterIndex * 2;
|
||||||
|
|
||||||
|
Builder<Object[]> streamBuilder = Stream.builder();
|
||||||
|
for (int offset = 0; offset < 5; offset++) {
|
||||||
|
int byteIndex = origByteIndex + offset;
|
||||||
|
byte[] bytesOffsetted = new byte[origBytes.length + offset];
|
||||||
|
for (int i = 0; i < bytesOffsetted.length; i++) {
|
||||||
|
bytesOffsetted[i] = 99;
|
||||||
|
}
|
||||||
|
System.arraycopy(origBytes, 0, bytesOffsetted, offset, origBytes.length);
|
||||||
|
// offsetted:
|
||||||
|
streamBuilder.add(new Object[] { expectedResult, type, bytesOffsetted, byteIndex });
|
||||||
|
|
||||||
|
// offsetted, with no extra bytes following
|
||||||
|
// (this is only done for successfull cases to avoid copyOfRange padding with zeros
|
||||||
|
if (!(expectedResult instanceof Class)) {
|
||||||
|
byte[] bytesOffsettedCutExtra = Arrays.copyOfRange(bytesOffsetted, 0, byteIndex + type.getBits() / 8);
|
||||||
|
if (bytesOffsettedCutExtra.length != bytesOffsetted.length) {
|
||||||
|
streamBuilder.add(new Object[] { expectedResult, type, bytesOffsettedCutExtra, byteIndex });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return streamBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testIndividual(Object expectedResult, ValueType type, byte[] bytes, int byteIndex,
|
||||||
|
Supplier<Number> methodUnderTest, Function<DecimalType, Number> expectedPrimitive) {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex, methodUnderTest, expectedPrimitive, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void testIndividual(Object expectedResult, ValueType type, byte[] bytes, int byteIndex,
|
||||||
|
Supplier<Number> methodUnderTest, Function<DecimalType, Number> expectedPrimitive,
|
||||||
|
@Nullable Number defaultWhenEmptyOptional) {
|
||||||
|
String testExplanation = String.format("bytes=%s, byteIndex=%d, type=%s", Arrays.toString(bytes), byteIndex,
|
||||||
|
type);
|
||||||
|
final Object expectedNumber;
|
||||||
|
if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class<?>) expectedResult)) {
|
||||||
|
assertThrows((Class<? extends Throwable>) expectedResult, () -> methodUnderTest.get());
|
||||||
|
} else if (expectedResult instanceof Optional<?>) {
|
||||||
|
assertTrue(!((Optional<?>) expectedResult).isPresent());
|
||||||
|
if (defaultWhenEmptyOptional == null) {
|
||||||
|
fail("Should provide defaultWhenEmptyOptional");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
DecimalType expectedDecimal = (DecimalType) expectedResult;
|
||||||
|
expectedNumber = expectedPrimitive.apply(expectedDecimal);
|
||||||
|
assertEquals(expectedNumber, methodUnderTest.get(), testExplanation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataSInt16() {
|
||||||
|
return filteredTestData(ValueType.INT16);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataSInt16")
|
||||||
|
public void testExtractIndividualSInt16(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractSInt16(bytes, byteIndex),
|
||||||
|
decimal -> decimal.shortValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataUInt16() {
|
||||||
|
return filteredTestData(ValueType.UINT16);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataUInt16")
|
||||||
|
public void testExtractIndividualUInt16(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractUInt16(bytes, byteIndex),
|
||||||
|
decimal -> decimal.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataSInt32() {
|
||||||
|
return filteredTestData(ValueType.INT32);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataSInt32")
|
||||||
|
public void testExtractIndividualSInt32(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractSInt32(bytes, byteIndex),
|
||||||
|
decimal -> decimal.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataUInt32() {
|
||||||
|
return filteredTestData(ValueType.UINT32);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataUInt32")
|
||||||
|
public void testExtractIndividualUInt32(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractUInt32(bytes, byteIndex),
|
||||||
|
decimal -> decimal.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataSInt32Swap() {
|
||||||
|
return filteredTestData(ValueType.INT32_SWAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataSInt32Swap")
|
||||||
|
public void testExtractIndividualSInt32Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex,
|
||||||
|
() -> ModbusBitUtilities.extractSInt32Swap(bytes, byteIndex), decimal -> decimal.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataUInt32Swap() {
|
||||||
|
return filteredTestData(ValueType.UINT32_SWAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataUInt32Swap")
|
||||||
|
public void testExtractIndividualUInt32Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex,
|
||||||
|
() -> ModbusBitUtilities.extractUInt32Swap(bytes, byteIndex), decimal -> decimal.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataSInt64() {
|
||||||
|
return filteredTestData(ValueType.INT64);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataSInt64")
|
||||||
|
public void testExtractIndividualSInt64(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractSInt64(bytes, byteIndex),
|
||||||
|
decimal -> decimal.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataUInt64() {
|
||||||
|
return filteredTestData(ValueType.UINT64);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataUInt64")
|
||||||
|
public void testExtractIndividualUInt64(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractUInt64(bytes, byteIndex),
|
||||||
|
decimal -> decimal.toBigDecimal().toBigIntegerExact());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataSInt64Swap() {
|
||||||
|
return filteredTestData(ValueType.INT64_SWAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataSInt64Swap")
|
||||||
|
public void testExtractIndividualSInt64Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex,
|
||||||
|
() -> ModbusBitUtilities.extractSInt64Swap(bytes, byteIndex), decimal -> decimal.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataUInt64Swap() {
|
||||||
|
return filteredTestData(ValueType.UINT64_SWAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataUInt64Swap")
|
||||||
|
public void testExtractIndividualUInt64Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex,
|
||||||
|
() -> ModbusBitUtilities.extractUInt64Swap(bytes, byteIndex),
|
||||||
|
decimal -> decimal.toBigDecimal().toBigIntegerExact());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataFloat32() {
|
||||||
|
return filteredTestData(ValueType.FLOAT32);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataFloat32")
|
||||||
|
public void testExtractIndividualFloat32(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex,
|
||||||
|
() -> ModbusBitUtilities.extractFloat32(bytes, byteIndex), decimal -> decimal.floatValue(), Float.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> filteredTestDataFloat32Swap() {
|
||||||
|
return filteredTestData(ValueType.FLOAT32_SWAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filteredTestDataFloat32Swap")
|
||||||
|
public void testExtractIndividualFloat32Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
|
||||||
|
throws InstantiationException, IllegalAccessException {
|
||||||
|
testIndividual(expectedResult, type, bytes, byteIndex,
|
||||||
|
() -> ModbusBitUtilities.extractFloat32Swap(bytes, byteIndex), decimal -> decimal.floatValue(),
|
||||||
|
Float.NaN);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.io.transport.modbus.test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Tests for extractSInt8 and extractUInt8
|
||||||
|
*
|
||||||
|
* @author Sami Salonen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class BitUtilitiesExtractInt8Test {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractSInt8WithSingleIndex() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3 };
|
||||||
|
assertEquals(-1, ModbusBitUtilities.extractSInt8(bytes, 0));
|
||||||
|
assertEquals(2, ModbusBitUtilities.extractSInt8(bytes, 1));
|
||||||
|
assertEquals(3, ModbusBitUtilities.extractSInt8(bytes, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractSInt8WithSingleIndexOOB() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3 };
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractSInt8(bytes, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractSInt8WithSingleIndexOOB2() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3 };
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractSInt8(bytes, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractSInt8WithRegisterIndexAndHiByte() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3, 4 };
|
||||||
|
assertEquals(-1, ModbusBitUtilities.extractSInt8(bytes, 0, true));
|
||||||
|
assertEquals(2, ModbusBitUtilities.extractSInt8(bytes, 0, false));
|
||||||
|
assertEquals(3, ModbusBitUtilities.extractSInt8(bytes, 1, true));
|
||||||
|
assertEquals(4, ModbusBitUtilities.extractSInt8(bytes, 1, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractSInt8WithRegisterIndexAndHiByteOOB() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3, 4 };
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractSInt8(bytes, 2, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractSInt8WithRegisterIndexAndHiByteOOB2() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3, 4 };
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractSInt8(bytes, -1, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// unsigned int8 follows
|
||||||
|
//
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractUInt8WithSingleIndex() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3 };
|
||||||
|
assertEquals(255, ModbusBitUtilities.extractUInt8(bytes, 0));
|
||||||
|
assertEquals(2, ModbusBitUtilities.extractUInt8(bytes, 1));
|
||||||
|
assertEquals(3, ModbusBitUtilities.extractUInt8(bytes, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractUInt8WithSingleIndexOOB() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3 };
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractUInt8(bytes, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractUInt8WithSingleIndexOOB2() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3 };
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractUInt8(bytes, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractUInt8WithRegisterIndexAndHiByte() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3, 4 };
|
||||||
|
assertEquals(255, ModbusBitUtilities.extractUInt8(bytes, 0, true));
|
||||||
|
assertEquals(2, ModbusBitUtilities.extractUInt8(bytes, 0, false));
|
||||||
|
assertEquals(3, ModbusBitUtilities.extractUInt8(bytes, 1, true));
|
||||||
|
assertEquals(4, ModbusBitUtilities.extractUInt8(bytes, 1, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractUInt8WithRegisterIndexAndHiByteOOB() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3, 4 };
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractUInt8(bytes, 2, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractUInt8WithRegisterIndexAndHiByteOOB2() {
|
||||||
|
byte[] bytes = new byte[] { -1, 2, 3, 4 };
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ModbusBitUtilities.extractUInt8(bytes, 255, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,12 +16,10 @@ import static org.hamcrest.CoreMatchers.*;
|
|||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNull;
|
import org.eclipse.jdt.annotation.NonNull;
|
||||||
@@ -30,7 +28,6 @@ import org.junit.jupiter.params.provider.MethodSource;
|
|||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
||||||
import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
|
import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
|
||||||
import org.openhab.io.transport.modbus.ModbusRegister;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,12 +36,7 @@ import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
|||||||
public class BitUtilitiesExtractStateFromRegistersTest {
|
public class BitUtilitiesExtractStateFromRegistersTest {
|
||||||
|
|
||||||
private static ModbusRegisterArray shortArrayToRegisterArray(int... arr) {
|
private static ModbusRegisterArray shortArrayToRegisterArray(int... arr) {
|
||||||
ModbusRegister[] tmp = new ModbusRegister[0];
|
return new ModbusRegisterArray(arr);
|
||||||
return new ModbusRegisterArray(IntStream.of(arr).mapToObj(val -> {
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(2);
|
|
||||||
buffer.putShort((short) val);
|
|
||||||
return new ModbusRegister(buffer.get(0), buffer.get(1));
|
|
||||||
}).collect(Collectors.toList()).toArray(tmp));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Collection<Object[]> data() {
|
public static Collection<Object[]> data() {
|
||||||
@@ -118,7 +110,7 @@ public class BitUtilitiesExtractStateFromRegistersTest {
|
|||||||
new Object[] { new DecimalType("64000"), ValueType.UINT16, shortArrayToRegisterArray(64000), 0 },
|
new Object[] { new DecimalType("64000"), ValueType.UINT16, shortArrayToRegisterArray(64000), 0 },
|
||||||
new Object[] { new DecimalType("64532"), ValueType.UINT16, shortArrayToRegisterArray(4, -1004), 1 },
|
new Object[] { new DecimalType("64532"), ValueType.UINT16, shortArrayToRegisterArray(4, -1004), 1 },
|
||||||
new Object[] { new DecimalType("64532"), ValueType.UINT16, shortArrayToRegisterArray(-1004, 4), 0 },
|
new Object[] { new DecimalType("64532"), ValueType.UINT16, shortArrayToRegisterArray(-1004, 4), 0 },
|
||||||
new Object[] { IllegalArgumentException.class, ValueType.INT16, shortArrayToRegisterArray(4, -1004),
|
new Object[] { IllegalArgumentException.class, ValueType.UINT16, shortArrayToRegisterArray(4, -1004),
|
||||||
2 },
|
2 },
|
||||||
//
|
//
|
||||||
// INT32
|
// INT32
|
||||||
@@ -364,7 +356,7 @@ public class BitUtilitiesExtractStateFromRegistersTest {
|
|||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
public void testCommandToRegisters(Object expectedResult, ValueType type, ModbusRegisterArray registers,
|
public void testextractStateFromRegisters(Object expectedResult, ValueType type, ModbusRegisterArray registers,
|
||||||
int index) {
|
int index) {
|
||||||
if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) {
|
if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) {
|
||||||
assertThrows((Class) expectedResult,
|
assertThrows((Class) expectedResult,
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.io.transport.modbus.test;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
|
||||||
import org.openhab.core.library.types.StringType;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegister;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Parameters;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Sami Salonen - Initial contribution
|
|
||||||
*/
|
|
||||||
public class BitUtilitiesExtractStringFromRegistersTest {
|
|
||||||
|
|
||||||
private static ModbusRegisterArray shortArrayToRegisterArray(int... arr) {
|
|
||||||
ModbusRegister[] tmp = new ModbusRegister[0];
|
|
||||||
return new ModbusRegisterArray(IntStream.of(arr).mapToObj(val -> {
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(2);
|
|
||||||
buffer.putShort((short) val);
|
|
||||||
return new ModbusRegister(buffer.get(0), buffer.get(1));
|
|
||||||
}).collect(Collectors.toList()).toArray(tmp));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Parameters
|
|
||||||
public static Collection<Object[]> data() {
|
|
||||||
return Collections.unmodifiableList(Stream.of(
|
|
||||||
new Object[] { new StringType(""), shortArrayToRegisterArray(0), 0, 0, StandardCharsets.UTF_8 },
|
|
||||||
new Object[] { new StringType("hello"), shortArrayToRegisterArray(0x6865, 0x6c6c, 0x6f00), 0, 5,
|
|
||||||
StandardCharsets.UTF_8 },
|
|
||||||
new Object[] { new StringType("hello "), shortArrayToRegisterArray(0, 0, 0x6865, 0x6c6c, 0x6f20, 0, 0),
|
|
||||||
2, 6, StandardCharsets.UTF_8 },
|
|
||||||
new Object[] { new StringType("hello"),
|
|
||||||
shortArrayToRegisterArray(0x6865, 0x6c6c, 0x6f00, 0x0000, 0x0000), 0, 10,
|
|
||||||
StandardCharsets.UTF_8 },
|
|
||||||
new Object[] { new StringType("árvíztűrő tükörfúrógép"),
|
|
||||||
shortArrayToRegisterArray(0xc3a1, 0x7276, 0xc3ad, 0x7a74, 0xc5b1, 0x72c5, 0x9120, 0x74c3,
|
|
||||||
0xbc6b, 0xc3b6, 0x7266, 0xc3ba, 0x72c3, 0xb367, 0xc3a9, 0x7000),
|
|
||||||
0, 32, StandardCharsets.UTF_8 },
|
|
||||||
new Object[] { new StringType("árvíztűrő tükörfúrógép"),
|
|
||||||
shortArrayToRegisterArray(0xe172, 0x76ed, 0x7a74, 0xfb72, 0xf520, 0x74fc, 0x6bf6, 0x7266,
|
|
||||||
0xfa72, 0xf367, 0xe970),
|
|
||||||
0, 22, Charset.forName("ISO-8859-2") },
|
|
||||||
|
|
||||||
// Invalid values
|
|
||||||
new Object[] { IllegalArgumentException.class, shortArrayToRegisterArray(0, 0), 2, 4,
|
|
||||||
StandardCharsets.UTF_8 },
|
|
||||||
new Object[] { IllegalArgumentException.class, shortArrayToRegisterArray(0, 0), 0, -1,
|
|
||||||
StandardCharsets.UTF_8 },
|
|
||||||
new Object[] { IllegalArgumentException.class, shortArrayToRegisterArray(0, 0), 0, 5,
|
|
||||||
StandardCharsets.UTF_8 })
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("data")
|
|
||||||
public void testExtractStringFromRegisters(Object expectedResult, ModbusRegisterArray registers, int index,
|
|
||||||
int length, Charset charset) {
|
|
||||||
if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) {
|
|
||||||
assertThrows((Class) expectedResult,
|
|
||||||
() -> ModbusBitUtilities.extractStringFromRegisters(registers, index, length, charset));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringType actualState = ModbusBitUtilities.extractStringFromRegisters(registers, index, length, charset);
|
|
||||||
assertThat(String.format("registers=%s, index=%d, length=%d", registers, index, length), actualState,
|
|
||||||
is(equalTo(expectedResult)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.io.transport.modbus.test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.Stream.Builder;
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusBitUtilities;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sami Salonen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class BitUtilitiesExtractStringTest {
|
||||||
|
|
||||||
|
private static ModbusRegisterArray shortArrayToRegisterArray(int... arr) {
|
||||||
|
return new ModbusRegisterArray(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
return Collections.unmodifiableList(Stream.of(
|
||||||
|
new Object[] { "", shortArrayToRegisterArray(0), 0, 0, StandardCharsets.UTF_8 },
|
||||||
|
new Object[] { "hello", shortArrayToRegisterArray(0x6865, 0x6c6c, 0x6f00), 0, 5,
|
||||||
|
StandardCharsets.UTF_8 },
|
||||||
|
new Object[] { "he", shortArrayToRegisterArray(0x6865, 0x6c6c, 0x6f00), 0, 2, StandardCharsets.UTF_8 }, // limited
|
||||||
|
// by
|
||||||
|
// count=2
|
||||||
|
new Object[] { "hello ", shortArrayToRegisterArray(0, 0, 0x6865, 0x6c6c, 0x6f20, 0, 0), 2, 6,
|
||||||
|
StandardCharsets.UTF_8 },
|
||||||
|
new Object[] { "hello", shortArrayToRegisterArray(0x6865, 0x6c6c, 0x6f00, 0x0000, 0x0000), 0, 10,
|
||||||
|
StandardCharsets.UTF_8 },
|
||||||
|
new Object[] { "árvíztűrő tükörfúrógép",
|
||||||
|
shortArrayToRegisterArray(0xc3a1, 0x7276, 0xc3ad, 0x7a74, 0xc5b1, 0x72c5, 0x9120, 0x74c3,
|
||||||
|
0xbc6b, 0xc3b6, 0x7266, 0xc3ba, 0x72c3, 0xb367, 0xc3a9, 0x7000),
|
||||||
|
0, 32, StandardCharsets.UTF_8 },
|
||||||
|
new Object[] { "你好,世界",
|
||||||
|
shortArrayToRegisterArray(0xe4bd, 0xa0e5, 0xa5bd, 0xefbc, 0x8ce4, 0xb896, 0xe795, 0x8c00), 0,
|
||||||
|
16, StandardCharsets.UTF_8 },
|
||||||
|
new Object[] { "árvíztűrő tükörfúrógép",
|
||||||
|
shortArrayToRegisterArray(0xe172, 0x76ed, 0x7a74, 0xfb72, 0xf520, 0x74fc, 0x6bf6, 0x7266,
|
||||||
|
0xfa72, 0xf367, 0xe970),
|
||||||
|
0, 22, Charset.forName("ISO-8859-2") },
|
||||||
|
// Example where registers contain 0 byte in between -- only the data preceding zero byte is parsed
|
||||||
|
new Object[] { "hello", shortArrayToRegisterArray(0x6865, 0x6c6c, 0x6f00, 0x776f, 0x726c, 0x64), 0, 10,
|
||||||
|
StandardCharsets.UTF_8 },
|
||||||
|
|
||||||
|
// Invalid values
|
||||||
|
// 0xe4 = "ä" in extended ascii but not covered by US_ASCII. Will be replaced by <20>
|
||||||
|
new Object[] { "<EFBFBD>", shortArrayToRegisterArray(0xe400), 0, 2, StandardCharsets.US_ASCII },
|
||||||
|
// out of bounds
|
||||||
|
new Object[] { IllegalArgumentException.class, shortArrayToRegisterArray(0, 0), 2, 4,
|
||||||
|
StandardCharsets.UTF_8 },
|
||||||
|
// negative index
|
||||||
|
new Object[] { IllegalArgumentException.class, shortArrayToRegisterArray(0, 0), 0, -1,
|
||||||
|
StandardCharsets.UTF_8 },
|
||||||
|
// out of bounds
|
||||||
|
new Object[] { IllegalArgumentException.class, shortArrayToRegisterArray(0, 0), 0, 5,
|
||||||
|
StandardCharsets.UTF_8 })
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Object[]> dataWithByteVariations() {
|
||||||
|
return data().stream().flatMap(vals -> {
|
||||||
|
Object expected = vals[0];
|
||||||
|
ModbusRegisterArray registers = (ModbusRegisterArray) vals[1];
|
||||||
|
int index = (int) vals[2];
|
||||||
|
int length = (int) vals[3];
|
||||||
|
Charset charset = (Charset) vals[4];
|
||||||
|
|
||||||
|
byte[] origBytes = registers.getBytes();
|
||||||
|
int origRegisterIndex = index;
|
||||||
|
int origByteIndex = origRegisterIndex * 2;
|
||||||
|
|
||||||
|
Builder<Object[]> streamBuilder = Stream.builder();
|
||||||
|
for (int offset = 0; offset < 5; offset++) {
|
||||||
|
byte[] bytesOffsetted = new byte[origBytes.length + offset];
|
||||||
|
System.arraycopy(origBytes, 0, bytesOffsetted, offset, origBytes.length);
|
||||||
|
streamBuilder.add(
|
||||||
|
new Object[] { expected, offset, bytesOffsetted, origByteIndex + offset, length, charset });
|
||||||
|
}
|
||||||
|
Stream<Object[]> variations = streamBuilder.build();
|
||||||
|
return variations;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("data")
|
||||||
|
public void testExtractStringFromRegisters(Object expectedResult, ModbusRegisterArray registers, int index,
|
||||||
|
int length, Charset charset) {
|
||||||
|
if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) {
|
||||||
|
assertThrows((Class) expectedResult,
|
||||||
|
() -> ModbusBitUtilities.extractStringFromRegisters(registers, index, length, charset));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
String actualState = ModbusBitUtilities.extractStringFromRegisters(registers, index, length, charset);
|
||||||
|
assertEquals(actualState, expectedResult,
|
||||||
|
String.format("registers=%s, index=%d, length=%d", registers, index, length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("dataWithByteVariations")
|
||||||
|
public void testExtractStringFromBytes(Object expectedResult, int byteOffset, byte[] bytes, int byteIndex,
|
||||||
|
int length, Charset charset) {
|
||||||
|
if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) {
|
||||||
|
assertThrows((Class) expectedResult,
|
||||||
|
() -> ModbusBitUtilities.extractStringFromBytes(bytes, byteIndex, length, charset));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
String actualState = ModbusBitUtilities.extractStringFromBytes(bytes, byteIndex, length, charset);
|
||||||
|
assertEquals(actualState, expectedResult, String.format("registers=%s, index=%d, length=%d, byteIndex=%d",
|
||||||
|
bytes, byteIndex, length, byteIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,9 +14,11 @@ package org.openhab.io.transport.modbus.test;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
|
import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
|
||||||
|
|
||||||
@@ -42,7 +44,9 @@ class RegisterMatcher extends AbstractRequestComparer<ModbusWriteRegisterRequest
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doMatchData(ModbusWriteRegisterRequestBlueprint item) {
|
protected boolean doMatchData(ModbusWriteRegisterRequestBlueprint item) {
|
||||||
Object[] actual = StreamSupport.stream(item.getRegisters().spliterator(), false).map(r -> r.getValue())
|
ModbusRegisterArray registers = item.getRegisters();
|
||||||
|
Object[] actual = StreamSupport
|
||||||
|
.stream(IntStream.range(0, registers.size()).mapToObj(registers::getRegister).spliterator(), false)
|
||||||
.toArray();
|
.toArray();
|
||||||
return Objects.deepEquals(actual, expectedRegisterValues);
|
return Objects.deepEquals(actual, expectedRegisterValues);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,14 @@ import static org.junit.jupiter.api.Assumptions.*;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketImpl;
|
import java.net.SocketImpl;
|
||||||
import java.net.SocketImplFactory;
|
import java.net.SocketImplFactory;
|
||||||
|
import java.net.SocketOption;
|
||||||
|
import java.net.StandardSocketOptions;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -120,16 +123,16 @@ public class SmokeTest extends IntegrationTestSupport {
|
|||||||
private void testHoldingValues(ModbusRegisterArray registers, int offsetInRegisters) {
|
private void testHoldingValues(ModbusRegisterArray registers, int offsetInRegisters) {
|
||||||
for (int i = 0; i < registers.size(); i++) {
|
for (int i = 0; i < registers.size(); i++) {
|
||||||
int expected = (i + offsetInRegisters) * HOLDING_REGISTER_MULTIPLIER;
|
int expected = (i + offsetInRegisters) * HOLDING_REGISTER_MULTIPLIER;
|
||||||
assertThat(String.format("i=%d, expecting %d, got %d", i, registers.getRegister(i).toUnsignedShort(),
|
assertThat(String.format("i=%d, expecting %d, got %d", i, registers.getRegister(i), expected),
|
||||||
expected), registers.getRegister(i).toUnsignedShort(), is(equalTo(expected)));
|
registers.getRegister(i), is(equalTo(expected)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testInputValues(ModbusRegisterArray registers, int offsetInRegisters) {
|
private void testInputValues(ModbusRegisterArray registers, int offsetInRegisters) {
|
||||||
for (int i = 0; i < registers.size(); i++) {
|
for (int i = 0; i < registers.size(); i++) {
|
||||||
int expected = (i + offsetInRegisters) * INPUT_REGISTER_MULTIPLIER;
|
int expected = (i + offsetInRegisters) * INPUT_REGISTER_MULTIPLIER;
|
||||||
assertThat(String.format("i=%d, expecting %d, got %d", i, registers.getRegister(i).toUnsignedShort(),
|
assertThat(String.format("i=%d, expecting %d, got %d", i, registers.getRegister(i), expected),
|
||||||
expected), registers.getRegister(i).toUnsignedShort(), is(equalTo(expected)));
|
registers.getRegister(i), is(equalTo(expected)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,11 +948,7 @@ public class SmokeTest extends IntegrationTestSupport {
|
|||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
return socketSpy.sockets.stream().filter(socketImpl -> {
|
return socketSpy.sockets.stream().filter(this::isConnectedToTestServer).count();
|
||||||
Socket socket = getSocketOfSocketImpl(socketImpl);
|
|
||||||
return socket.getPort() == tcpModbusPort && socket.isConnected()
|
|
||||||
&& socket.getLocalAddress().equals(testServerAddress);
|
|
||||||
}).count();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -972,25 +971,87 @@ public class SmokeTest extends IntegrationTestSupport {
|
|||||||
|
|
||||||
private static SocketImpl newSocksSocketImpl() {
|
private static SocketImpl newSocksSocketImpl() {
|
||||||
try {
|
try {
|
||||||
Class<?> defaultSocketImpl = Class.forName("java.net.SocksSocketImpl");
|
Class<?> socksSocketImplClass = Class.forName("java.net.SocksSocketImpl");
|
||||||
Constructor<?> constructor = defaultSocketImpl.getDeclaredConstructor();
|
Class<?> socketImplClass = SocketImpl.class;
|
||||||
constructor.setAccessible(true);
|
|
||||||
return (SocketImpl) constructor.newInstance();
|
// // For Debugging
|
||||||
|
// for (Method method : socketImplClass.getDeclaredMethods()) {
|
||||||
|
// LoggerFactory.getLogger("foobar")
|
||||||
|
// .error("SocketImpl." + method.getName() + Arrays.toString(method.getParameters()));
|
||||||
|
// }
|
||||||
|
// for (Constructor constructor : socketImplClass.getDeclaredConstructors()) {
|
||||||
|
// LoggerFactory.getLogger("foobar")
|
||||||
|
// .error("SocketImpl." + constructor.getName() + Arrays.toString(constructor.getParameters()));
|
||||||
|
// }
|
||||||
|
// for (Method method : socksSocketImplClass.getDeclaredMethods()) {
|
||||||
|
// LoggerFactory.getLogger("foobar")
|
||||||
|
// .error("SocksSocketImpl." + method.getName() + Arrays.toString(method.getParameters()));
|
||||||
|
// }
|
||||||
|
// for (Constructor constructor : socksSocketImplClass.getDeclaredConstructors()) {
|
||||||
|
// LoggerFactory.getLogger("foobar").error(
|
||||||
|
// "SocksSocketImpl." + constructor.getName() + Arrays.toString(constructor.getParameters()));
|
||||||
|
// }
|
||||||
|
|
||||||
|
try {
|
||||||
|
Constructor<?> constructor = socksSocketImplClass.getDeclaredConstructor();
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return (SocketImpl) constructor.newInstance();
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// Newer Javas (Java 14->) do not have default constructor 'SocksSocketImpl()'
|
||||||
|
// Instead we use "static SocketImpl.createPlatformSocketImpl" and "SocksSocketImpl(SocketImpl)
|
||||||
|
Method socketImplCreateMethod = socketImplClass.getDeclaredMethod("createPlatformSocketImpl",
|
||||||
|
boolean.class);
|
||||||
|
socketImplCreateMethod.setAccessible(true);
|
||||||
|
Object socketImpl = socketImplCreateMethod.invoke(/* null since we deal with static method */ null,
|
||||||
|
/* server */false);
|
||||||
|
|
||||||
|
Constructor<?> socksSocketImplConstructor = socksSocketImplClass
|
||||||
|
.getDeclaredConstructor(socketImplClass);
|
||||||
|
socksSocketImplConstructor.setAccessible(true);
|
||||||
|
return (SocketImpl) socksSocketImplConstructor.newInstance(socketImpl);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private boolean isConnectedToTestServer(SocketImpl impl) {
|
||||||
* Get Socket corresponding to SocketImpl using reflection
|
final InetAddress testServerAddress;
|
||||||
*/
|
|
||||||
private static Socket getSocketOfSocketImpl(SocketImpl impl) {
|
|
||||||
try {
|
try {
|
||||||
Method getSocket = SocketImpl.class.getDeclaredMethod("getSocket");
|
testServerAddress = localAddress();
|
||||||
getSocket.setAccessible(true);
|
} catch (UnknownHostException e) {
|
||||||
return (Socket) getSocket.invoke(impl);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int port;
|
||||||
|
boolean connected = true;
|
||||||
|
final InetAddress address;
|
||||||
|
try {
|
||||||
|
Method getPort = SocketImpl.class.getDeclaredMethod("getPort");
|
||||||
|
getPort.setAccessible(true);
|
||||||
|
port = (int) getPort.invoke(impl);
|
||||||
|
|
||||||
|
Method getInetAddressMethod = SocketImpl.class.getDeclaredMethod("getInetAddress");
|
||||||
|
getInetAddressMethod.setAccessible(true);
|
||||||
|
address = (InetAddress) getInetAddressMethod.invoke(impl);
|
||||||
|
|
||||||
|
// hacky (but java8-14 compatible) way to know if socket is open
|
||||||
|
// SocketImpl.getOption throws IOException when socket is closed
|
||||||
|
Method getOption = SocketImpl.class.getDeclaredMethod("getOption", SocketOption.class);
|
||||||
|
getOption.setAccessible(true);
|
||||||
|
try {
|
||||||
|
getOption.invoke(impl, StandardSocketOptions.SO_KEEPALIVE);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
if (e.getTargetException() instanceof IOException) {
|
||||||
|
connected = false;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InvocationTargetException | SecurityException | IllegalArgumentException | IllegalAccessException
|
||||||
|
| NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return port == tcpModbusPort && connected && address.equals(testServerAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.io.transport.modbus.test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.nio.BufferOverflowException;
|
||||||
|
import java.nio.InvalidMarkException;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.io.transport.modbus.ValueBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sami Salonen - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ValueBufferTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInt32Int8() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFC, 0x14, 3, -1, -2 });
|
||||||
|
assertEquals(7, wrap.remaining());
|
||||||
|
assertTrue(wrap.hasRemaining());
|
||||||
|
|
||||||
|
assertEquals(-1004, wrap.getSInt32());
|
||||||
|
assertEquals(3, wrap.remaining());
|
||||||
|
assertTrue(wrap.hasRemaining());
|
||||||
|
|
||||||
|
assertEquals(3, wrap.getSInt8());
|
||||||
|
assertEquals(2, wrap.remaining());
|
||||||
|
assertTrue(wrap.hasRemaining());
|
||||||
|
|
||||||
|
assertEquals(-1, wrap.getSInt8());
|
||||||
|
assertEquals(1, wrap.remaining());
|
||||||
|
assertTrue(wrap.hasRemaining());
|
||||||
|
|
||||||
|
assertEquals(254, wrap.getUInt8());
|
||||||
|
assertEquals(0, wrap.remaining());
|
||||||
|
assertFalse(wrap.hasRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOutOfBounds() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFC, 0x14, 3, -1, -2 });
|
||||||
|
wrap.position(7);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> wrap.getSInt8());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOutOfBound2s() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFC, 0x14, 3, -1, -2 });
|
||||||
|
wrap.position(6);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> wrap.getSInt16());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMarkReset() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFC, 0x14, 3, -1, -2 });
|
||||||
|
wrap.mark();
|
||||||
|
assertEquals(-1004, wrap.getSInt32());
|
||||||
|
wrap.reset();
|
||||||
|
assertEquals(4294966292L, wrap.getUInt32());
|
||||||
|
wrap.mark();
|
||||||
|
assertEquals(3, wrap.getSInt8());
|
||||||
|
wrap.reset();
|
||||||
|
assertEquals(3, wrap.getSInt8());
|
||||||
|
assertEquals(-1, wrap.getSInt8());
|
||||||
|
assertEquals(254, wrap.getUInt8());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMarkHigherThanPosition() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFC, 0x14, 3, -1, -2 });
|
||||||
|
assertEquals(-1004, wrap.getSInt32());
|
||||||
|
wrap.position(4);
|
||||||
|
wrap.mark();
|
||||||
|
assertEquals(4, wrap.position());
|
||||||
|
|
||||||
|
// mark = position
|
||||||
|
wrap.position(4);
|
||||||
|
assertEquals(4, wrap.position());
|
||||||
|
wrap.reset();
|
||||||
|
assertEquals(4, wrap.position());
|
||||||
|
|
||||||
|
// position < mark
|
||||||
|
wrap.position(3); // Mark is removed here
|
||||||
|
assertEquals(3, wrap.position());
|
||||||
|
boolean caughtException = false;
|
||||||
|
try {
|
||||||
|
wrap.reset();
|
||||||
|
} catch (InvalidMarkException e) {
|
||||||
|
// OK, expected
|
||||||
|
caughtException = true;
|
||||||
|
}
|
||||||
|
assertTrue(caughtException);
|
||||||
|
assertEquals(3, wrap.position());
|
||||||
|
|
||||||
|
// Mark is removed. Reset unaccessible even with original position of 4
|
||||||
|
wrap.position(4);
|
||||||
|
assertEquals(4, wrap.position());
|
||||||
|
caughtException = false;
|
||||||
|
try {
|
||||||
|
wrap.reset();
|
||||||
|
} catch (InvalidMarkException e) {
|
||||||
|
// OK, expected
|
||||||
|
caughtException = true;
|
||||||
|
}
|
||||||
|
assertTrue(caughtException);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMarkLowerThanPosition() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFC, 0x14, 3, -1, -2 });
|
||||||
|
assertEquals(-1004, wrap.getSInt32());
|
||||||
|
wrap.position(4);
|
||||||
|
wrap.mark();
|
||||||
|
assertEquals(4, wrap.position());
|
||||||
|
|
||||||
|
// mark = position
|
||||||
|
wrap.position(4);
|
||||||
|
assertEquals(4, wrap.position());
|
||||||
|
wrap.reset();
|
||||||
|
assertEquals(4, wrap.position());
|
||||||
|
|
||||||
|
// mark > position
|
||||||
|
wrap.position(6);
|
||||||
|
assertEquals(6, wrap.position());
|
||||||
|
wrap.reset();
|
||||||
|
assertEquals(4, wrap.position());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPosition() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { 0, 0, 0, 1, 3, -1, -2 });
|
||||||
|
assertEquals(0, wrap.position());
|
||||||
|
|
||||||
|
wrap.position(4);
|
||||||
|
assertEquals(4, wrap.position());
|
||||||
|
assertEquals(3, wrap.getSInt8());
|
||||||
|
assertEquals(5, wrap.position());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBulkGetBufferOverflow() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { 0, 0 });
|
||||||
|
byte[] threeBytes = new byte[3];
|
||||||
|
assertThrows(BufferOverflowException.class, () -> wrap.get(threeBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBulkGetAtCapacity() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { 1, 2 });
|
||||||
|
byte[] twoBytes = new byte[2];
|
||||||
|
wrap.get(twoBytes);
|
||||||
|
assertEquals(1, twoBytes[0]);
|
||||||
|
assertEquals(2, twoBytes[1]);
|
||||||
|
assertEquals(2, wrap.position());
|
||||||
|
assertFalse(wrap.hasRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBulkGet() {
|
||||||
|
ValueBuffer wrap = ValueBuffer.wrap(new byte[] { 1, 2, 3 });
|
||||||
|
byte[] onebyte = new byte[1];
|
||||||
|
wrap.get(onebyte);
|
||||||
|
assertEquals(1, onebyte[0]);
|
||||||
|
assertEquals(1, wrap.position());
|
||||||
|
|
||||||
|
// non-zero position
|
||||||
|
byte[] twoBytes = new byte[2];
|
||||||
|
wrap.position(1);
|
||||||
|
wrap.get(twoBytes);
|
||||||
|
assertEquals(2, twoBytes[0]);
|
||||||
|
assertEquals(3, twoBytes[1]);
|
||||||
|
assertEquals(3, wrap.position());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,7 +68,6 @@ import org.openhab.io.transport.modbus.ModbusConstants;
|
|||||||
import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
|
import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
|
||||||
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
|
||||||
import org.openhab.io.transport.modbus.ModbusRegister;
|
|
||||||
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
import org.openhab.io.transport.modbus.ModbusRegisterArray;
|
||||||
import org.openhab.io.transport.modbus.ModbusResponse;
|
import org.openhab.io.transport.modbus.ModbusResponse;
|
||||||
import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
|
import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
|
||||||
@@ -517,7 +516,7 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
|
|||||||
public void testOnRegistersInt16StaticTransformation() {
|
public void testOnRegistersInt16StaticTransformation() {
|
||||||
ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
||||||
"0", "-3", ModbusConstants.ValueType.INT16, null,
|
"0", "-3", ModbusConstants.ValueType.INT16, null,
|
||||||
new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null);
|
new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null);
|
||||||
|
|
||||||
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
|
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
|
||||||
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
|
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
|
||||||
@@ -538,8 +537,7 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
|
|||||||
mockTransformation("MULTIPLY", new MultiplyTransformation());
|
mockTransformation("MULTIPLY", new MultiplyTransformation());
|
||||||
ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
||||||
"0", "MULTIPLY(10)", ModbusConstants.ValueType.INT16, null,
|
"0", "MULTIPLY(10)", ModbusConstants.ValueType.INT16, null,
|
||||||
new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null,
|
new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
|
||||||
bundleContext);
|
|
||||||
|
|
||||||
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
|
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
|
||||||
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
|
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
|
||||||
@@ -561,8 +559,7 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
|
|||||||
ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
||||||
"0", "default", ModbusConstants.ValueType.FLOAT32, null, new ModbusRegisterArray(
|
"0", "default", ModbusConstants.ValueType.FLOAT32, null, new ModbusRegisterArray(
|
||||||
// equivalent of floating point NaN
|
// equivalent of floating point NaN
|
||||||
new ModbusRegister[] { new ModbusRegister((byte) 0x7f, (byte) 0xc0),
|
new byte[] { (byte) 0x7f, (byte) 0xc0, (byte) 0x00, (byte) 0x00 }),
|
||||||
new ModbusRegister((byte) 0x00, (byte) 0x00) }),
|
|
||||||
null, bundleContext);
|
null, bundleContext);
|
||||||
|
|
||||||
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
|
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
|
||||||
@@ -588,8 +585,7 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
|
|||||||
});
|
});
|
||||||
ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
||||||
"0", "ONOFF(10)", ModbusConstants.ValueType.INT16, null,
|
"0", "ONOFF(10)", ModbusConstants.ValueType.INT16, null,
|
||||||
new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null,
|
new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
|
||||||
bundleContext);
|
|
||||||
|
|
||||||
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
|
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
|
||||||
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
|
assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
|
||||||
@@ -667,8 +663,7 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
|
|||||||
assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
|
assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
|
||||||
assertThat(writeRequest.getReference(), is(equalTo(50)));
|
assertThat(writeRequest.getReference(), is(equalTo(50)));
|
||||||
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
|
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
|
||||||
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
|
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0), is(equalTo(5)));
|
||||||
is(equalTo(5)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -702,11 +697,11 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
|
|||||||
assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS)));
|
assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS)));
|
||||||
assertThat(writeRequest.getReference(), is(equalTo(5412)));
|
assertThat(writeRequest.getReference(), is(equalTo(5412)));
|
||||||
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(3)));
|
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(3)));
|
||||||
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
|
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
|
||||||
is(equalTo(1)));
|
is(equalTo(1)));
|
||||||
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(1).getValue(),
|
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(1),
|
||||||
is(equalTo(0)));
|
is(equalTo(0)));
|
||||||
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(2).getValue(),
|
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(2),
|
||||||
is(equalTo(5)));
|
is(equalTo(5)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -714,7 +709,7 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
|
|||||||
assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
|
assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
|
||||||
assertThat(writeRequest.getReference(), is(equalTo(555)));
|
assertThat(writeRequest.getReference(), is(equalTo(555)));
|
||||||
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
|
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
|
||||||
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
|
assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
|
||||||
is(equalTo(3)));
|
is(equalTo(3)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user