]> git.basschouten.com Git - openhab-addons.git/commitdiff
[modbus] Modbus register array backed by bytes and other simplifications (#8865)
authorSami Salonen <ssalonen@gmail.com>
Thu, 26 Nov 2020 17:07:49 +0000 (19:07 +0200)
committerGitHub <noreply@github.com>
Thu, 26 Nov 2020 17:07:49 +0000 (18:07 +0100)
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
32 files changed:
bundles/org.openhab.binding.modbus.e3dc/src/main/java/org/openhab/binding/modbus/e3dc/internal/dto/DataConverter.java
bundles/org.openhab.binding.modbus.e3dc/src/main/java/org/openhab/binding/modbus/e3dc/internal/dto/EmergencyBlock.java
bundles/org.openhab.binding.modbus.e3dc/src/main/java/org/openhab/binding/modbus/e3dc/internal/dto/InfoBlock.java
bundles/org.openhab.binding.modbus.e3dc/src/main/java/org/openhab/binding/modbus/e3dc/internal/dto/PowerBlock.java
bundles/org.openhab.binding.modbus.e3dc/src/main/java/org/openhab/binding/modbus/e3dc/internal/dto/StringBlock.java
bundles/org.openhab.binding.modbus.e3dc/src/main/java/org/openhab/binding/modbus/e3dc/internal/modbus/Parser.java
bundles/org.openhab.binding.modbus.e3dc/src/test/java/org/openhab/binding/modbus/e3dc/dto/DataBlockTest.java
bundles/org.openhab.binding.modbus.e3dc/src/test/java/org/openhab/binding/modbus/e3dc/internal/handler/E3DCHandlerStateTest.java
bundles/org.openhab.binding.modbus.e3dc/src/test/java/org/openhab/binding/modbus/e3dc/util/DataConverterTest.java
bundles/org.openhab.binding.modbus.helioseasycontrols/src/main/java/org/openhab/binding/modbus/helioseasycontrols/internal/HeliosEasyControlsHandler.java
bundles/org.openhab.binding.modbus.helioseasycontrols/src/test/java/org/openhab/binding/modbus/helioseasycontrols/internal/PreparePayloadTest.java [new file with mode: 0644]
bundles/org.openhab.binding.modbus.stiebeleltron/src/main/java/org/openhab/binding/modbus/stiebeleltron/internal/handler/StiebelEltronHandler.java
bundles/org.openhab.binding.modbus.studer/src/main/java/org/openhab/binding/modbus/studer/internal/StuderHandler.java
bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/parser/CommonModelParser.java
bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/ModbusBitUtilities.java
bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/ModbusRegister.java [deleted file]
bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/ModbusRegisterArray.java
bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/ValueBuffer.java [new file with mode: 0644]
bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/internal/ModbusLibraryWrapper.java
bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/json/WriteRequestJsonUtilities.java
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesCommandToRegistersTest.java
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractBitTest.java [new file with mode: 0644]
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractFloat32Test.java [new file with mode: 0644]
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractIndividualMethodsTest.java [new file with mode: 0644]
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractInt8Test.java [new file with mode: 0644]
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractStateFromRegistersTest.java
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractStringFromRegistersTest.java [deleted file]
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractStringTest.java [new file with mode: 0644]
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/RegisterMatcher.java
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/SmokeTest.java
bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/ValueBufferTest.java [new file with mode: 0644]
itests/org.openhab.binding.modbus.tests/src/main/java/org/openhab/binding/modbus/tests/ModbusDataHandlerTest.java

index 00737b38f0b6a68ebbe59f98fc1e2e2b4bea4162..2f962be621e45fa123481e89ef5b8f5b4aacdb0c 100644 (file)
  */
 package org.openhab.binding.modbus.e3dc.internal.dto;
 
-import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.BitSet;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.io.transport.modbus.ModbusBitUtilities;
+import org.openhab.io.transport.modbus.ValueBuffer;
 
 /**
  * The {@link DataConverter} Helper class to convert bytes from modbus into desired data format
@@ -25,27 +26,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
  */
 @NonNullByDefault
 public class DataConverter {
-    private static final long MAX_INT32 = (long) Math.pow(2, Integer.SIZE);
-
-    /**
-     * Get unit16 value from 2 bytes
-     *
-     * @param wrap
-     * @return int
-     */
-    public static int getUInt16Value(ByteBuffer wrap) {
-        return Short.toUnsignedInt(wrap.getShort());
-    }
-
-    /**
-     * Get unit32 value from 4 bytes
-     *
-     * @param wrap
-     * @return long
-     */
-    public static long getLongValue(ByteBuffer wrap) {
-        return Integer.toUnsignedLong(wrap.getInt());
-    }
 
     /**
      * Get double value from 2 bytes with correction factor
@@ -53,28 +33,12 @@ public class DataConverter {
      * @param wrap
      * @return double
      */
-    public static double getUDoubleValue(ByteBuffer wrap, double factor) {
-        return round(getUInt16Value(wrap) * factor, 2);
-    }
-
-    /**
-     * Conversion done according to E3DC Modbus Specification V1.7
-     *
-     * @param wrap
-     * @return decoded long value, Long.MIN_VALUE otherwise
-     */
-    public static long getInt32Swap(ByteBuffer wrap) {
-        long a = getUInt16Value(wrap);
-        long b = getUInt16Value(wrap);
-        if (b < 32768) {
-            return b * 65536 + a;
-        } else {
-            return (MAX_INT32 - b * 65536 - a) * -1;
-        }
+    public static double getUDoubleValue(ValueBuffer wrap, double factor) {
+        return round(wrap.getUInt16() * factor, 2);
     }
 
     public static String getString(byte[] bArray) {
-        return new String(bArray, StandardCharsets.US_ASCII).trim();
+        return ModbusBitUtilities.extractStringFromBytes(bArray, 0, bArray.length, StandardCharsets.US_ASCII).trim();
     }
 
     public static int toInt(BitSet bitSet) {
@@ -93,8 +57,7 @@ public class DataConverter {
         }
 
         long factor = (long) Math.pow(10, places);
-        value = value * factor;
-        long tmp = Math.round(value);
+        long tmp = Math.round(value * factor);
         return (double) tmp / factor;
     }
 }
index ad72d09d308dd47cbacee9bcbf133ebf6db123f7..7bb27cda316fca3d6bb741a779af0e70a723e817 100644 (file)
@@ -14,13 +14,13 @@ package org.openhab.binding.modbus.e3dc.internal.dto;
 
 import static org.openhab.binding.modbus.e3dc.internal.modbus.E3DCModbusConstans.*;
 
-import java.nio.ByteBuffer;
 import java.util.BitSet;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.StringType;
+import org.openhab.io.transport.modbus.ModbusBitUtilities;
 
 /**
  * The {@link EmergencyBlock} Data object for E3DC Info Block
@@ -55,7 +55,7 @@ public class EmergencyBlock implements Data {
      */
     public EmergencyBlock(byte[] bArray) {
         // uint16 status register 40084 - possible Status Strings are defined in Constants above
-        int status = DataConverter.getUInt16Value(ByteBuffer.wrap(bArray));
+        int status = ModbusBitUtilities.extractUInt16(bArray, 0);
         if (status >= 0 && status < 5) {
             epStatus = EP_STATUS_ARRAY[status];
         } else {
index 34a1e0661870987ec81e9a9b1ec75b906b265dab..658049478ae1cfd97bcd2790c7f79a9a01835956 100644 (file)
  */
 package org.openhab.binding.modbus.e3dc.internal.dto;
 
-import java.nio.ByteBuffer;
-
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.util.HexUtils;
+import org.openhab.io.transport.modbus.ValueBuffer;
 
 /**
  * The {@link InfoBlock} Data object for E3DC Info Block
@@ -43,7 +42,7 @@ public class InfoBlock implements Data {
      */
     public InfoBlock(byte[] bArray) {
         // index handling to calculate the correct start index
-        ByteBuffer wrapper = ByteBuffer.wrap(bArray);
+        ValueBuffer wrapper = ValueBuffer.wrap(bArray);
 
         // first uint16 = 2 bytes - decode magic byte
         byte[] magicBytes = new byte[2];
@@ -52,11 +51,11 @@ public class InfoBlock implements Data {
         // first uint16 = 2 bytes - decode magic byte
 
         // unit8 (Modbus Major Version) + uint8 Modbus minor Version
-        String modbusVersion = wrapper.get() + "." + wrapper.get();
+        String modbusVersion = wrapper.getSInt8() + "." + wrapper.getSInt8();
         this.modbusVersion = new StringType(modbusVersion);
 
         // unit16 - supported registers
-        short supportedRegisters = wrapper.getShort();
+        short supportedRegisters = wrapper.getSInt16();
         this.supportedRegisters = new DecimalType(supportedRegisters);
 
         byte[] buffer = new byte[32];
index 6a86ca50327a89da7c9f850114df8085fc0551e1..686ed0f0b277078247095bafed5d0316dcc631c7 100644 (file)
@@ -12,8 +12,6 @@
  */
 package org.openhab.binding.modbus.e3dc.internal.dto;
 
-import java.nio.ByteBuffer;
-
 import javax.measure.quantity.Dimensionless;
 import javax.measure.quantity.Power;
 
@@ -21,6 +19,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.unit.SmartHomeUnits;
+import org.openhab.io.transport.modbus.ValueBuffer;
 
 /**
  * The {@link PowerBlock} Data object for E3DC Info Block
@@ -49,10 +48,10 @@ public class PowerBlock implements Data {
      */
     public PowerBlock(byte[] bArray) {
         // index handling to calculate the correct start index
-        ByteBuffer wrap = ByteBuffer.wrap(bArray);
+        ValueBuffer wrap = ValueBuffer.wrap(bArray);
 
         // int32_swap value = 4 byte
-        long pvPowerSupplyL = DataConverter.getInt32Swap(wrap);
+        long pvPowerSupplyL = wrap.getSInt32Swap();
 
         /*
          * int32_swap value don't provide negative values!
@@ -60,7 +59,7 @@ public class PowerBlock implements Data {
          * Negative value - Battery is discharging = Power supplier
          */
         pvPowerSupply = QuantityType.valueOf(pvPowerSupplyL, SmartHomeUnits.WATT);
-        long batteryPower = DataConverter.getInt32Swap(wrap);
+        long batteryPower = wrap.getSInt32Swap();
         if (batteryPower > 0) {
             // Battery is charging so Power is consumed by Battery
             batteryPowerSupply = QuantityType.valueOf(0, SmartHomeUnits.WATT);
@@ -72,7 +71,7 @@ public class PowerBlock implements Data {
         }
 
         // int32_swap value = 4 byte
-        long householdPowerConsumptionL = DataConverter.getInt32Swap(wrap);
+        long householdPowerConsumptionL = wrap.getSInt32Swap();
         householdPowerConsumption = QuantityType.valueOf(householdPowerConsumptionL, SmartHomeUnits.WATT);
 
         /*
@@ -80,7 +79,7 @@ public class PowerBlock implements Data {
          * Positive value - Power provided towards Grid = Power consumer
          * Negative value - Power requested from Grid = Power supplier
          */
-        long gridPower = DataConverter.getInt32Swap(wrap);
+        long gridPower = wrap.getSInt32Swap();
         if (gridPower > 0) {
             // Power is provided by Grid
             gridPowerSupply = QuantityType.valueOf(gridPower, SmartHomeUnits.WATT);
@@ -92,19 +91,19 @@ public class PowerBlock implements Data {
         }
 
         // int32_swap value = 4 byte
-        externalPowerSupply = QuantityType.valueOf(DataConverter.getInt32Swap(wrap), SmartHomeUnits.WATT);
+        externalPowerSupply = QuantityType.valueOf(wrap.getSInt32Swap(), SmartHomeUnits.WATT);
 
         // int32_swap value = 4 byte
-        wallboxPowerConsumption = QuantityType.valueOf(DataConverter.getInt32Swap(wrap), SmartHomeUnits.WATT);
+        wallboxPowerConsumption = QuantityType.valueOf(wrap.getSInt32Swap(), SmartHomeUnits.WATT);
 
         // int32_swap value = 4 byte
-        wallboxPVPowerConsumption = QuantityType.valueOf(DataConverter.getInt32Swap(wrap), SmartHomeUnits.WATT);
+        wallboxPVPowerConsumption = QuantityType.valueOf(wrap.getSInt32Swap(), SmartHomeUnits.WATT);
 
         // unit8 + uint8 - one register with split value for Autarky & Self Consumption
-        autarky = QuantityType.valueOf(wrap.get(), SmartHomeUnits.PERCENT);
-        selfConsumption = QuantityType.valueOf(wrap.get(), SmartHomeUnits.PERCENT);
+        autarky = QuantityType.valueOf(wrap.getSInt8(), SmartHomeUnits.PERCENT);
+        selfConsumption = QuantityType.valueOf(wrap.getSInt8(), SmartHomeUnits.PERCENT);
 
         // uint16 for Battery State of Charge
-        batterySOC = QuantityType.valueOf(wrap.getShort(), SmartHomeUnits.PERCENT);
+        batterySOC = QuantityType.valueOf(wrap.getSInt16(), SmartHomeUnits.PERCENT);
     }
 }
index 847661161657105f2ee7f500d90b5ddd45f416dc..e4a1eb572b212e060036169f8e74d2d0fa403104 100644 (file)
@@ -12,8 +12,6 @@
  */
 package org.openhab.binding.modbus.e3dc.internal.dto;
 
-import java.nio.ByteBuffer;
-
 import javax.measure.quantity.ElectricCurrent;
 import javax.measure.quantity.ElectricPotential;
 import javax.measure.quantity.Power;
@@ -22,6 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.unit.SmartHomeUnits;
+import org.openhab.io.transport.modbus.ValueBuffer;
 
 /**
  * The {@link StringBlock} Data object for E3DC Info Block
@@ -46,17 +45,17 @@ public class StringBlock implements Data {
      * @param bArray - Modbus Registers as bytes from 40096 to 40104
      */
     public StringBlock(byte[] bArray) {
-        ByteBuffer wrap = ByteBuffer.wrap(bArray);
+        ValueBuffer wrap = ValueBuffer.wrap(bArray);
         // straight forward - for each String the values Volt, Ampere and then Watt. All unt16 = 2 bytes values
-        string1Volt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.VOLT);
-        string2Volt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.VOLT);
-        string3Volt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.VOLT);
+        string1Volt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.VOLT);
+        string2Volt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.VOLT);
+        string3Volt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.VOLT);
         // E3DC Modbus Spec chapter 3.1.2, page 16 - Ampere values shall be handled with factor 0.01
         string1Ampere = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.01), SmartHomeUnits.AMPERE);
         string2Ampere = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.01), SmartHomeUnits.AMPERE);
         string3Ampere = QuantityType.valueOf(DataConverter.getUDoubleValue(wrap, 0.01), SmartHomeUnits.AMPERE);
-        string1Watt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.WATT);
-        string2Watt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.WATT);
-        string3Watt = QuantityType.valueOf(DataConverter.getUInt16Value(wrap), SmartHomeUnits.WATT);
+        string1Watt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.WATT);
+        string2Watt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.WATT);
+        string3Watt = QuantityType.valueOf(wrap.getUInt16(), SmartHomeUnits.WATT);
     }
 }
index e1524f44c4386cca0fddcd81bec5e6b06d974bb2..645f67686f3e3da3515016c535e040f29fc7317a 100644 (file)
@@ -25,7 +25,6 @@ import org.openhab.binding.modbus.e3dc.internal.dto.StringBlock;
 import org.openhab.binding.modbus.e3dc.internal.dto.WallboxArray;
 import org.openhab.binding.modbus.e3dc.internal.modbus.Data.DataType;
 import org.openhab.io.transport.modbus.AsyncModbusReadResult;
-import org.openhab.io.transport.modbus.ModbusRegister;
 import org.openhab.io.transport.modbus.ModbusRegisterArray;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -59,17 +58,10 @@ public class Parser {
     }
 
     public void handle(AsyncModbusReadResult result) {
-        byte[] newArray = new byte[size];
         long startTime = System.currentTimeMillis();
         Optional<ModbusRegisterArray> opt = result.getRegisters();
         if (opt.isPresent()) {
-            ModbusRegisterArray registers = opt.get();
-            int i = 0;
-            for (ModbusRegister reg : registers) {
-                System.arraycopy(reg.getBytes(), 0, newArray, i, 2);
-                i += 2;
-            }
-            setArray(newArray);
+            setArray(opt.get().getBytes());
 
             long duration = System.currentTimeMillis() - startTime;
             avgDuration += duration;
index 460c9c9b043b382b99228b4ca5287b5a7bc0461d..00a8ada38486fdcd6c6bf0c12919874597ce8322 100644 (file)
@@ -35,14 +35,26 @@ import org.openhab.core.library.types.OnOffType;
  */
 public class DataBlockTest {
     private Parser mc;
+    private Parser mcNegativePVSupply;
 
     @BeforeEach
     public void setup() {
-        byte[] dataBlock = new byte[] { 0, -14, 0, 0, -2, -47, -1, -1, 2, 47, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, 0, 0, 0, 99, 99, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
-                125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 0, 0 };
-        mc = new Parser(DataType.DATA);
-        mc.setArray(dataBlock);
+        {
+            byte[] dataBlock = new byte[] { 0, -14, 0, 0, -2, -47, -1, -1, 2, 47, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 99, 99, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 1, 125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 0, 0 };
+            mc = new Parser(DataType.DATA);
+            mc.setArray(dataBlock);
+        }
+        {
+            // 65098 bytes [-2, 74]
+            // 65535 bytes [-1, -1]
+            byte[] dataBlock = new byte[] { -2, -74, -1, -1, -2, -47, -1, -1, 2, 47, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 99, 99, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 1, 125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 0, 0 };
+            mcNegativePVSupply = new Parser(DataType.DATA);
+            mcNegativePVSupply.setArray(dataBlock);
+        }
     }
 
     @Test
@@ -56,6 +68,17 @@ public class DataBlockTest {
         assertEquals("303.0 W", b.batteryPowerSupply.toString(), "Battery Supply");
     }
 
+    @Test
+    public void testValidPowerBlockNegativePVSupply() {
+        Optional<Data> dataOpt = mcNegativePVSupply.parse(DataType.POWER);
+        assertTrue(dataOpt.isPresent());
+        PowerBlock b = (PowerBlock) dataOpt.get();
+        assertEquals("-330.0 W", b.pvPowerSupply.toString(), "PV Supply");
+        assertEquals("14.0 W", b.gridPowerSupply.toString(), "Grid Supply");
+        assertEquals("0.0 W", b.gridPowerConsumpition.toString(), "Grid Consumption");
+        assertEquals("303.0 W", b.batteryPowerSupply.toString(), "Battery Supply");
+    }
+
     @Test
     public void testValidWallboxBlock() {
         Optional<Data> wba = mc.parse(DataType.WALLBOX);
index dce81bf59f82ce334e93f3881fb29c36e1d5b09d..ead7e6787c7410c2fd8504133914815241c2d99b 100644 (file)
@@ -14,7 +14,6 @@ package org.openhab.binding.modbus.e3dc.internal.handler;
 
 import static org.mockito.Mockito.*;
 
-import java.nio.ByteBuffer;
 import java.util.HashMap;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -31,7 +30,6 @@ import org.openhab.core.thing.binding.ThingHandlerCallback;
 import org.openhab.io.transport.modbus.AsyncModbusFailure;
 import org.openhab.io.transport.modbus.AsyncModbusReadResult;
 import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
-import org.openhab.io.transport.modbus.ModbusRegister;
 import org.openhab.io.transport.modbus.ModbusRegisterArray;
 
 /**
@@ -92,26 +90,16 @@ public class E3DCHandlerStateTest {
                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 78, 73, 78, 73, 84, 73, 65, 76, 73, 90, 69,
                 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 49, 48, 95, 50, 48, 50, 48, 95, 48, 52,
                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
-        ByteBuffer infoWrap = ByteBuffer.wrap(infoBlockBytes);
-        ModbusRegister[] infoBlock = new ModbusRegister[infoBlockBytes.length / 2];
-        for (int i = 0; i < infoBlock.length; i++) {
-            infoBlock[i] = new ModbusRegister(infoWrap.get(), infoWrap.get());
-        }
         ModbusReadRequestBlueprint readRequest = mock(ModbusReadRequestBlueprint.class);
-        return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(infoBlock));
+        return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(infoBlockBytes));
     }
 
     private AsyncModbusReadResult getDataResult() {
         byte[] dataBlockBytes = new byte[] { 0, -14, 0, 0, -2, -47, -1, -1, 2, 47, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0,
                 0, 0, 0, 0, 0, 0, 99, 99, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                 1, 125, 2, 21, 0, 0, 0, 27, 0, 26, 0, 0, 0, 103, 0, -117, 0, 0 };
-        ByteBuffer dataWrap = ByteBuffer.wrap(dataBlockBytes);
-        ModbusRegister[] dataBlock = new ModbusRegister[dataBlockBytes.length / 2];
-        for (int i = 0; i < dataBlock.length; i++) {
-            dataBlock[i] = new ModbusRegister(dataWrap.get(), dataWrap.get());
-        }
         ModbusReadRequestBlueprint readRequest = mock(ModbusReadRequestBlueprint.class);
-        return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(dataBlock));
+        return new AsyncModbusReadResult(readRequest, new ModbusRegisterArray(dataBlockBytes));
     }
 
     private AsyncModbusFailure<ModbusReadRequestBlueprint> getFailResult() {
index bac87348121911f1d6ffcd3d9987e515a601b9bd..bef20dc55cb958cf3d36cc90c3639b1de3a7ff7f 100644 (file)
@@ -14,12 +14,12 @@ package org.openhab.binding.modbus.e3dc.util;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import java.nio.ByteBuffer;
 import java.util.BitSet;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.modbus.e3dc.internal.dto.DataConverter;
+import org.openhab.io.transport.modbus.ValueBuffer;
 
 /**
  * The {@link DataConverterTest} Test data conversions
@@ -30,17 +30,58 @@ import org.openhab.binding.modbus.e3dc.internal.dto.DataConverter;
 public class DataConverterTest {
 
     @Test
-    public void testE3DCSwapValueNegative() {
-        // Reg 69 value 65098 bytes [-2, 74]
-        // Reg 70 value 65535 bytes [-1, -1]
-        byte[] b = new byte[] { -2, -74, -1, -1 };
-        assertEquals(-330, DataConverter.getInt32Swap(ByteBuffer.wrap(b)), "Negative Value");
+    public void testRoundPositive() {
+        assertEquals(2.3, DataConverter.round(2.34, 1), 0.01);
     }
 
     @Test
-    public void testBitset() {
+    public void testRoundPositive2() {
+        assertEquals(2.4, DataConverter.round(2.37, 1), 0.01);
+    }
+
+    @Test
+    public void testRoundPositive3() {
+        assertEquals(2.4, DataConverter.round(2.35, 1), 0.01);
+    }
+
+    @Test
+    public void testRoundNegative() {
+        assertEquals(-2.3, DataConverter.round(-2.34, 1), 0.01);
+    }
+
+    @Test
+    public void testRoundNegative2() {
+        assertEquals(-2.4, DataConverter.round(-2.37, 1), 0.01);
+    }
+
+    @Test
+    public void testRoundNegative3() {
+        // rounding towards positive infinity. Note difference to testRoundPositive3
+        assertEquals(-2.3, DataConverter.round(-2.35, 1), 0.01);
+    }
+
+    @Test
+    public void testUDoubleValue() {
+        assertEquals(0.5, DataConverter.getUDoubleValue(ValueBuffer.wrap(new byte[] { 0, 5 }), 0.1), 0.01);
+    }
+
+    @Test
+    public void testUDoubleValue2() {
+        assertEquals(6159.9,
+                DataConverter.getUDoubleValue(ValueBuffer.wrap(new byte[] { (byte) 0xf0, (byte) 0x9f }), 0.1), 0.01);
+    }
+
+    @Test
+    public void testUDoubleValue3() {
+        assertEquals(123198,
+                DataConverter.getUDoubleValue(ValueBuffer.wrap(new byte[] { (byte) 0xf0, (byte) 0x9f }), 2), 0.01);
+    }
+
+    @Test
+    public void testBitsetToInt() {
         byte[] b = new byte[] { 3, 16 };
         BitSet s = BitSet.valueOf(b);
+        // Bit0 is the least significant bit to DataConverter.toInt
         assertEquals(true, s.get(0), "Bit0");
         assertEquals(true, s.get(1), "Bit1");
         assertEquals(false, s.get(2), "Bit2");
@@ -57,5 +98,10 @@ public class DataConverterTest {
         assertEquals(false, s.get(13), "Bit13");
         assertEquals(false, s.get(14), "Bit14");
         assertEquals(false, s.get(15), "Bit15");
+
+        int bitsAsInt = DataConverter.toInt(s);
+        int expected = 0b0001000000000011;
+        assertEquals(Integer.toBinaryString(expected), Integer.toBinaryString(bitsAsInt));
+        assertEquals(expected, bitsAsInt);
     }
 }
index f15ba7041888029d7310c52a5fd0f54192cbd79c..f5092ef381719f99384fe18567a1214b4545069a 100644 (file)
@@ -54,7 +54,6 @@ import org.openhab.io.transport.modbus.ModbusBitUtilities;
 import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
 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.ModbusWriteRegisterRequestBlueprint;
 import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
@@ -429,9 +428,8 @@ public class HeliosEasyControlsHandler extends BaseThingHandler {
                             lock.acquire();
                             comms.submitOneTimeWrite(
                                     new ModbusWriteRegisterRequestBlueprint(HeliosEasyControlsBindingConstants.UNIT_ID,
-                                            HeliosEasyControlsBindingConstants.START_ADDRESS,
-                                            new ModbusRegisterArray(preparePayload(payload)), true,
-                                            HeliosEasyControlsBindingConstants.MAX_TRIES),
+                                            HeliosEasyControlsBindingConstants.START_ADDRESS, preparePayload(payload),
+                                            true, HeliosEasyControlsBindingConstants.MAX_TRIES),
                                     result -> {
                                         lock.release();
                                         updateStatus(ThingStatus.ONLINE);
@@ -477,8 +475,7 @@ public class HeliosEasyControlsHandler extends BaseThingHandler {
                 String payload = v.getVariableString();
                 comms.submitOneTimeWrite(new ModbusWriteRegisterRequestBlueprint(
                         HeliosEasyControlsBindingConstants.UNIT_ID, HeliosEasyControlsBindingConstants.START_ADDRESS,
-                        new ModbusRegisterArray(preparePayload(payload)), true,
-                        HeliosEasyControlsBindingConstants.MAX_TRIES), result -> {
+                        preparePayload(payload), true, HeliosEasyControlsBindingConstants.MAX_TRIES), result -> {
                             comms.submitOneTimePoll(
                                     new ModbusReadRequestBlueprint(HeliosEasyControlsBindingConstants.UNIT_ID,
                                             ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
@@ -690,25 +687,21 @@ public class HeliosEasyControlsHandler extends BaseThingHandler {
      * @param payload The String 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
-        int l = (payload.length() + 1) / 2; // +1 because we need to include at least one termination symbol 0x00
-        if ((payload.length() + 1) % 2 != 0) {
-            l++;
-        }
-
-        ModbusRegister reg[] = new ModbusRegister[l];
-        byte[] b = payload.getBytes();
-        int ch = 0;
-        for (int i = 0; i < reg.length; i++) {
-            byte b1 = ch < b.length ? b[ch] : (byte) 0x00; // terminate with 0x00 if at the end of the payload
-            ch++;
-            byte b2 = ch < b.length ? b[ch] : (byte) 0x00;
-            ch++;
-            reg[i] = new ModbusRegister(b1, b2);
-        }
-        return reg;
+        byte[] asciiBytes = payload.getBytes(StandardCharsets.US_ASCII);
+        int bufferLength = asciiBytes.length // ascii characters
+                + 1 // NUL byte
+                + ((asciiBytes.length % 2 == 0) ? 1 : 0); // to have even number of bytes
+        assert bufferLength % 2 == 0; // Invariant, ensured above
+
+        byte[] buffer = new byte[bufferLength];
+        System.arraycopy(asciiBytes, 0, buffer, 0, asciiBytes.length);
+        // Fill in rest of bytes with NUL bytes
+        for (int i = asciiBytes.length; i < buffer.length; i++) {
+            buffer[i] = '\0';
+        }
+        return new ModbusRegisterArray(buffer);
     }
 
     /**
@@ -718,8 +711,8 @@ public class HeliosEasyControlsHandler extends BaseThingHandler {
      * @return The value or <tt>null</tt> if an error occurred
      */
     private void processResponse(HeliosVariable v, ModbusRegisterArray registers) {
-        String r = ModbusBitUtilities
-                .extractStringFromRegisters(registers, 0, registers.size() * 2, StandardCharsets.US_ASCII).toString();
+        String r = ModbusBitUtilities.extractStringFromRegisters(registers, 0, registers.size() * 2,
+                StandardCharsets.US_ASCII);
         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
         if ((parts.length == 2) && (v.getVariableString().equals(parts[0]))) {
diff --git a/bundles/org.openhab.binding.modbus.helioseasycontrols/src/test/java/org/openhab/binding/modbus/helioseasycontrols/internal/PreparePayloadTest.java b/bundles/org.openhab.binding.modbus.helioseasycontrols/src/test/java/org/openhab/binding/modbus/helioseasycontrols/internal/PreparePayloadTest.java
new file mode 100644 (file)
index 0000000..a3d3d8d
--- /dev/null
@@ -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));
+        }
+    }
+}
index de5a7c63be56ef34c5290206e06f3afcf66ca677..0913ae248a3066e9a901302263ce4afa7b004d46 100644 (file)
@@ -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.core.library.unit.SIUnits.CELSIUS;
-import static org.openhab.core.library.unit.SmartHomeUnits.KILOWATT_HOUR;
-import static org.openhab.core.library.unit.SmartHomeUnits.PERCENT;
+import static org.openhab.core.library.unit.SmartHomeUnits.*;
 
 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.ModbusReadFunctionCode;
 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.ModbusWriteRegisterRequestBlueprint;
 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
@@ -198,11 +196,9 @@ public class StiebelEltronHandler extends BaseThingHandler {
             throw new IllegalStateException("registerPollTask called without proper configuration");
         }
         // big endian byte ordering
-        byte b1 = (byte) (shortValue >> 8);
-        byte b2 = (byte) shortValue;
-
-        ModbusRegister register = new ModbusRegister(b1, b2);
-        ModbusRegisterArray data = new ModbusRegisterArray(new ModbusRegister[] { register });
+        byte hi = (byte) (shortValue >> 8);
+        byte lo = (byte) shortValue;
+        ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
 
         ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
                 false, myconfig.getMaxTries());
index 3a0d97412f2c1e49f86afbd46694eca9247c5bdb..3e291b702c93763681d5ab3c63b455d136c8383b 100644 (file)
@@ -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.VTMode;
 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.QuantityType;
 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.UnDefType;
 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.ModbusConstants.ValueType;
 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
 import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
 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
      */
     protected void handlePolledData(int registerNumber, ModbusRegisterArray registers) {
-        String hexString = registers.toHexString().toString();
-        Float quantity = parser.hexToFloat(hexString);
-        if (quantity != null) {
+        Optional<DecimalType> quantity = ModbusBitUtilities.extractStateFromRegisters(registers, 0, ValueType.FLOAT32);
+        quantity.ifPresent(value -> {
             if (type.equals(THING_TYPE_BSP)) {
                 Unit<?> unit = UNIT_CHANNELS_BSP.get(registerNumber);
                 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)) {
-                handlePolledDataXtender(registerNumber, quantity);
+                handlePolledDataXtender(registerNumber, value);
             } else if (type.equals(THING_TYPE_VARIOTRACK)) {
-                handlePolledDataVarioTrack(registerNumber, quantity);
+                handlePolledDataVarioTrack(registerNumber, value);
             } else if (type.equals(THING_TYPE_VARIOSTRING)) {
-                handlePolledDataVarioString(registerNumber, quantity);
+                handlePolledDataVarioString(registerNumber, value);
             }
-        }
+        });
         resetCommunicationError();
     }
 
@@ -327,7 +329,7 @@ public class StuderHandler extends BaseThingHandler {
      * The register array is first parsed, then each of the channels are updated
      * to the new values
      */
-    protected void handlePolledDataVarioString(int registerNumber, Float quantity) {
+    protected void handlePolledDataVarioString(int registerNumber, DecimalType quantity) {
         switch (CHANNELS_VARIOSTRING.get(registerNumber)) {
             case CHANNEL_PV_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
      * to the new values
      */
-    protected void handlePolledDataVarioTrack(int registerNumber, Float quantity) {
+    protected void handlePolledDataVarioTrack(int registerNumber, DecimalType quantity) {
         switch (CHANNELS_VARIOTRACK.get(registerNumber)) {
             case CHANNEL_MODEL_VARIOTRACK:
                 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
      * to the new values
      */
-    protected void handlePolledDataXtender(int registerNumber, Float quantity) {
+    protected void handlePolledDataXtender(int registerNumber, DecimalType quantity) {
         switch (CHANNELS_XTENDER.get(registerNumber)) {
             case CHANNEL_OPERATING_STATE:
                 ModeXtender mode = StuderParser.getModeXtenderByCode(quantity.intValue());
index fb20e82271d2b44a3b727f5145251150372f857f..17d1ddc2f7ad5a7deb6cf66bfff98b2603c0390e 100644 (file)
@@ -52,12 +52,10 @@ public class CommonModelParser extends AbstractBaseParser implements SunspecPars
         }
 
         // parse manufacturer, model and version
-        block.manufacturer = ModbusBitUtilities.extractStringFromRegisters(raw, 2, 32, Charset.forName("UTF-8"))
-                .toString();
-        block.model = ModbusBitUtilities.extractStringFromRegisters(raw, 18, 32, Charset.forName("UTF-8")).toString();
-        block.version = ModbusBitUtilities.extractStringFromRegisters(raw, 42, 16, Charset.forName("UTF-8")).toString();
-        block.serialNumber = ModbusBitUtilities.extractStringFromRegisters(raw, 50, 32, Charset.forName("UTF-8"))
-                .toString();
+        block.manufacturer = ModbusBitUtilities.extractStringFromRegisters(raw, 2, 32, Charset.forName("UTF-8"));
+        block.model = ModbusBitUtilities.extractStringFromRegisters(raw, 18, 32, Charset.forName("UTF-8"));
+        block.version = ModbusBitUtilities.extractStringFromRegisters(raw, 42, 16, Charset.forName("UTF-8"));
+        block.serialNumber = ModbusBitUtilities.extractStringFromRegisters(raw, 50, 32, Charset.forName("UTF-8"));
 
         block.deviceAddress = extractUInt16(raw, 66, 1);
 
index 6980eb68a5164e3cf546d662f0a5abe14ec95b75..b55c4eeb7e586b890dc9eb4528200dc5305dbabe 100644 (file)
@@ -14,8 +14,6 @@ package org.openhab.io.transport.modbus;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
 import java.nio.charset.Charset;
 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.OnOffType;
 import org.openhab.core.library.types.OpenClosedType;
-import org.openhab.core.library.types.StringType;
 import org.openhab.core.types.Command;
+import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
 
 /**
  * Utilities for working with binary data.
@@ -101,165 +99,522 @@ public class ModbusBitUtilities {
      */
     public static Optional<DecimalType> extractStateFromRegisters(ModbusRegisterArray registers, int index,
             ModbusConstants.ValueType type) {
-        int endBitIndex = (type.getBits() >= 16 ? 16 * index : type.getBits() * index) + type.getBits() - 1;
-        // 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()));
-        }
+        byte[] bytes = registers.getBytes();
         switch (type) {
             case BIT:
-                return Optional
-                        .of(new DecimalType((registers.getRegister(index / 16).toUnsignedShort() >> (index % 16)) & 1));
-            case INT8:
-                return Optional.of(new DecimalType(registers.getRegister(index / 2).getBytes()[1 - (index % 2)]));
-            case UINT8:
-                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)));
+                return Optional.of(new DecimalType(extractBit(bytes, index)));
+            case INT8: {
+                int registerIndex = index / 2;
+                boolean hiByte = index % 2 == 1;
+                return Optional.of(new DecimalType(extractSInt8(bytes, registerIndex, hiByte)));
             }
-            case UINT16:
-                return Optional.of(new DecimalType(registers.getRegister(index).toUnsignedShort()));
-            case INT32: {
-                ByteBuffer buff = ByteBuffer.allocate(4);
-                buff.put(registers.getRegister(index).getBytes());
-                buff.put(registers.getRegister(index + 1).getBytes());
-                return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getInt(0)));
-            }
-            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 UINT8: {
+                int registerIndex = index / 2;
+                boolean hiByte = index % 2 == 1;
+                return Optional.of(new DecimalType(extractUInt8(bytes, registerIndex, hiByte)));
             }
-            case FLOAT32: {
-                ByteBuffer buff = ByteBuffer.allocate(4);
-                buff.put(registers.getRegister(index).getBytes());
-                buff.put(registers.getRegister(index + 1).getBytes());
+            case INT16:
+                return Optional.of(new DecimalType(extractSInt16(bytes, index * 2)));
+            case UINT16:
+                return Optional.of(new DecimalType(extractUInt16(bytes, index * 2)));
+            case INT32:
+                return Optional.of(new DecimalType(extractSInt32(bytes, index * 2)));
+            case UINT32:
+                return Optional.of(new DecimalType(extractUInt32(bytes, index * 2)));
+            case FLOAT32:
                 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) {
                     // floating point NaN or infinity encountered
                     return Optional.empty();
                 }
-            }
-            case INT64: {
-                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(buff.order(ByteOrder.BIG_ENDIAN).getLong(0)));
-            }
-            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());
+            case INT64:
+                return Optional.of(new DecimalType(extractSInt64(bytes, index * 2)));
+            case UINT64:
+                return Optional.of(new DecimalType(new BigDecimal(extractUInt64(bytes, index * 2))));
+            case INT32_SWAP:
+                return Optional.of(new DecimalType(extractSInt32Swap(bytes, index * 2)));
+            case UINT32_SWAP:
+                return Optional.of(new DecimalType(extractUInt32Swap(bytes, index * 2)));
+            case FLOAT32_SWAP:
                 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) {
                     // floating point NaN or infinity encountered
                     return Optional.empty();
                 }
-            }
-            case INT64_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(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()))));
-            }
+            case INT64_SWAP:
+                return Optional.of(new DecimalType(extractSInt64Swap(bytes, index * 2)));
+            case UINT64_SWAP:
+                return Optional.of(new DecimalType(new BigDecimal(extractUInt64Swap(bytes, index * 2))));
             default:
                 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));
+        }
+    }
+
+    /**
+     * 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();
+    }
+
     /**
-     * Read data from registers and convert the result to StringType
+     * 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
      * have an odd number of characters.
      * 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.
      *
+     * 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 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.
-     * @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.
      * @return string representation queried value
      * @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) {
-        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(
-                    String.format("Index=%d with length=%d is out-of-bounds given registers of size %d", index, length,
-                            registers.size()));
+                    String.format("byteIndex=%d with length=%d is out-of-bounds given registers of size %d", byteIndex,
+                            length, bytes.length));
         }
-        if (index < 0) {
+        if (byteIndex < 0) {
             throw new IllegalArgumentException("Negative index values are not supported");
         }
         if (length < 0) {
             throw new IllegalArgumentException("Negative string length is not supported");
         }
-        byte[] buff = new byte[length];
 
-        int src = index;
-        int dest;
-        for (dest = 0; dest < length; dest++) {
+        int effectiveLength = length;
 
-            byte chr;
-            if (dest % 2 == 0) {
-                chr = (byte) ((registers.getRegister(src).getValue() >> 8));
-            } else {
-                chr = (byte) (registers.getRegister(src).getValue() & 0xff);
-                src++;
-            }
-            if (chr == 0) {
+        // Find first zero byte in registers and call reduce length such that we stop before it
+        for (int i = 0; i < length; i++) {
+            if (bytes[byteIndex + i] == '\0') {
+                effectiveLength = i;
                 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: {
                 short shortValue = numericCommand.shortValue();
                 // big endian byte ordering
-                byte b1 = (byte) (shortValue >> 8);
-                byte b2 = (byte) shortValue;
-
-                ModbusRegister register = new ModbusRegister(b1, b2);
-                return new ModbusRegisterArray(new ModbusRegister[] { register });
+                byte hi = (byte) (shortValue >> 8);
+                byte lo = (byte) shortValue;
+                return new ModbusRegisterArray(new byte[] { hi, lo });
             }
             case INT32:
             case UINT32: {
                 int intValue = numericCommand.intValue();
                 // big endian byte ordering
-                byte b1 = (byte) (intValue >> 24);
-                byte b2 = (byte) (intValue >> 16);
-                byte b3 = (byte) (intValue >> 8);
-                byte b4 = (byte) intValue;
-                ModbusRegister register = new ModbusRegister(b1, b2);
-                ModbusRegister register2 = new ModbusRegister(b3, b4);
-                return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
+                byte hi1 = (byte) (intValue >> 24);
+                byte lo1 = (byte) (intValue >> 16);
+                byte hi2 = (byte) (intValue >> 8);
+                byte lo2 = (byte) intValue;
+                return new ModbusRegisterArray(new byte[] { hi1, lo1, hi2, lo2 });
             }
             case INT32_SWAP:
             case UINT32_SWAP: {
                 int intValue = numericCommand.intValue();
                 // big endian byte ordering
-                byte b1 = (byte) (intValue >> 24);
-                byte b2 = (byte) (intValue >> 16);
-                byte b3 = (byte) (intValue >> 8);
-                byte b4 = (byte) intValue;
-                ModbusRegister register = new ModbusRegister(b3, b4);
-                ModbusRegister register2 = new ModbusRegister(b1, b2);
-                return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
+                byte hi1 = (byte) (intValue >> 24);
+                byte lo1 = (byte) (intValue >> 16);
+                byte hi2 = (byte) (intValue >> 8);
+                byte lo2 = (byte) intValue;
+                // Swapped order of registers
+                return new ModbusRegisterArray(new byte[] { hi2, lo2, hi1, lo1 });
             }
             case FLOAT32: {
                 float floatValue = numericCommand.floatValue();
                 int intBits = Float.floatToIntBits(floatValue);
                 // big endian byte ordering
-                byte b1 = (byte) (intBits >> 24);
-                byte b2 = (byte) (intBits >> 16);
-                byte b3 = (byte) (intBits >> 8);
-                byte b4 = (byte) intBits;
-                ModbusRegister register = new ModbusRegister(b1, b2);
-                ModbusRegister register2 = new ModbusRegister(b3, b4);
-                return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
+                byte hi1 = (byte) (intBits >> 24);
+                byte lo1 = (byte) (intBits >> 16);
+                byte hi2 = (byte) (intBits >> 8);
+                byte lo2 = (byte) intBits;
+                return new ModbusRegisterArray(new byte[] { hi1, lo1, hi2, lo2 });
             }
             case FLOAT32_SWAP: {
                 float floatValue = numericCommand.floatValue();
                 int intBits = Float.floatToIntBits(floatValue);
                 // big endian byte ordering
-                byte b1 = (byte) (intBits >> 24);
-                byte b2 = (byte) (intBits >> 16);
-                byte b3 = (byte) (intBits >> 8);
-                byte b4 = (byte) intBits;
-                ModbusRegister register = new ModbusRegister(b3, b4);
-                ModbusRegister register2 = new ModbusRegister(b1, b2);
-                return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
+                byte hi1 = (byte) (intBits >> 24);
+                byte lo1 = (byte) (intBits >> 16);
+                byte hi2 = (byte) (intBits >> 8);
+                byte lo2 = (byte) intBits;
+                // Swapped order of registers
+                return new ModbusRegisterArray(new byte[] { hi2, lo2, hi1, lo1 });
             }
             case INT64:
             case UINT64: {
                 long longValue = numericCommand.longValue();
                 // big endian byte ordering
-                byte b1 = (byte) (longValue >> 56);
-                byte b2 = (byte) (longValue >> 48);
-                byte b3 = (byte) (longValue >> 40);
-                byte b4 = (byte) (longValue >> 32);
-                byte b5 = (byte) (longValue >> 24);
-                byte b6 = (byte) (longValue >> 16);
-                byte b7 = (byte) (longValue >> 8);
-                byte b8 = (byte) longValue;
-                return new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister(b1, b2),
-                        new ModbusRegister(b3, b4), new ModbusRegister(b5, b6), new ModbusRegister(b7, b8) });
+                byte hi1 = (byte) (longValue >> 56);
+                byte lo1 = (byte) (longValue >> 48);
+                byte hi2 = (byte) (longValue >> 40);
+                byte lo2 = (byte) (longValue >> 32);
+                byte hi3 = (byte) (longValue >> 24);
+                byte lo3 = (byte) (longValue >> 16);
+                byte hi4 = (byte) (longValue >> 8);
+                byte lo4 = (byte) longValue;
+                return new ModbusRegisterArray(new byte[] { hi1, lo1, hi2, lo2, hi3, lo3, hi4, lo4 });
             }
             case INT64_SWAP:
             case UINT64_SWAP: {
                 long longValue = numericCommand.longValue();
                 // big endian byte ordering
-                byte b1 = (byte) (longValue >> 56);
-                byte b2 = (byte) (longValue >> 48);
-                byte b3 = (byte) (longValue >> 40);
-                byte b4 = (byte) (longValue >> 32);
-                byte b5 = (byte) (longValue >> 24);
-                byte b6 = (byte) (longValue >> 16);
-                byte b7 = (byte) (longValue >> 8);
-                byte b8 = (byte) longValue;
-                return new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister(b7, b8),
-                        new ModbusRegister(b5, b6), new ModbusRegister(b3, b4), new ModbusRegister(b1, b2) });
+                byte hi1 = (byte) (longValue >> 56);
+                byte lo1 = (byte) (longValue >> 48);
+                byte hi2 = (byte) (longValue >> 40);
+                byte lo2 = (byte) (longValue >> 32);
+                byte hi3 = (byte) (longValue >> 24);
+                byte lo3 = (byte) (longValue >> 16);
+                byte hi4 = (byte) (longValue >> 8);
+                byte lo4 = (byte) longValue;
+                // Swapped order of registers
+                return new ModbusRegisterArray(new byte[] { hi4, lo4, hi3, lo3, hi2, lo2, hi1, lo1 });
             }
             default:
                 throw new NotImplementedException(
diff --git a/bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/ModbusRegister.java b/bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/ModbusRegister.java
deleted file mode 100644 (file)
index 7b787b0..0000000
+++ /dev/null
@@ -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;
-    }
-}
index a6df40ea2772d2ee1b73bf8ef92babf9a6d98881..6246d34b2a02d19e5f8fd685a1572f4072a99fe0 100644 (file)
  */
 package org.openhab.io.transport.modbus;
 
-import java.util.Iterator;
-import java.util.stream.IntStream;
+import java.util.Arrays;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.util.HexUtils;
 
 /**
  * Immutable {@link ModbusRegisterArray} implementation
@@ -23,54 +23,60 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
  * @author Sami Salonen - Initial contribution
  */
 @NonNullByDefault
-public class ModbusRegisterArray implements Iterable<ModbusRegister> {
+public class ModbusRegisterArray {
 
-    private final ModbusRegister[] registers;
+    private final byte[] bytes;
+
+    public ModbusRegisterArray(byte... bytes) {
+        if (bytes.length % 2 != 0) {
+            throw new IllegalArgumentException();
+        }
+        this.bytes = Arrays.copyOf(bytes, bytes.length);
+    }
 
     /**
-     * Construct plain <code>ModbusRegister[]</code> array from register values
+     * Construct plain <code>ModbusRegisterArray</code> array from register values
      *
      * @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]);
+    public ModbusRegisterArray(int... 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 registers;
     }
 
     /**
-     * Construct ModbusRegisterArrayImpl from array of {@link ModbusRegister}
+     * Get register index i as unsigned integer
      *
-     * @param registers
+     * @param i register index
+     * @return register value interpreted as unsigned integer (big-endian byte ordering)
      */
-    public ModbusRegisterArray(ModbusRegister[] registers) {
-        this.registers = registers;
+    public int getRegister(int i) {
+        int hi = bytes[i * 2] & 0xff;
+        int lo = bytes[i * 2 + 1] & 0xff;
+        return ((hi << 8) | lo) & 0xffff;
     }
 
     /**
-     * Construct plain <code>ModbusRegisterArrayImpl</code> array from register values
+     * Return bytes representing the registers
      *
-     * @param registerValues register values, each <code>int</code> corresponding to one register
-     * @return
-     */
-    public ModbusRegisterArray(int... registerValues) {
-        this(registersFromValues(registerValues));
-    }
-
-    /**
-     * Return register at the given index
      *
-     * Index 0 matches first register (lowest register index).
-     * <p>
+     * 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
+     * ...
      *
-     * @param index the index of the register to be returned.
-     * @throws IndexOutOfBoundsException if the index is out of bounds.
+     * @return set of bytes
      */
-    public ModbusRegister getRegister(int index) {
-        return registers[index];
+    public byte[] getBytes() {
+        return bytes;
     }
 
     /**
@@ -79,24 +85,16 @@ public class ModbusRegisterArray implements Iterable<ModbusRegister> {
      * @return
      */
     public int size() {
-        return registers.length;
+        return bytes.length / 2;
     }
 
     @Override
     public String toString() {
-        if (registers.length == 0) {
-            return "ModbusRegisterArrayImpl(<empty>)";
+        if (bytes.length == 0) {
+            return "ModbusRegisterArray(<empty>)";
         }
-        StringBuffer buffer = new StringBuffer(registers.length * 2).append("ModbusRegisterArrayImpl(");
-        return appendHexString(buffer).append(')').toString();
-    }
-
-    /**
-     * Iterator over all the registers
-     */
-    @Override
-    public Iterator<ModbusRegister> iterator() {
-        return IntStream.range(0, size()).mapToObj(i -> getRegister(i)).iterator();
+        return new StringBuilder(bytes.length).append("ModbusRegisterArray(").append(toHexString()).append(')')
+                .toString();
     }
 
     /**
@@ -110,22 +108,6 @@ public class ModbusRegisterArray implements Iterable<ModbusRegister> {
         if (size() == 0) {
             return "";
         }
-        // Initialize capacity to (n*2 + n-1), two chars per byte + spaces in between
-        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;
+        return HexUtils.bytesToHex(getBytes());
     }
 }
diff --git a/bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/ValueBuffer.java b/bundles/org.openhab.io.transport.modbus/src/main/java/org/openhab/io/transport/modbus/ValueBuffer.java
new file mode 100644 (file)
index 0000000..bcf5aa3
--- /dev/null
@@ -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));
+    }
+}
index 2c47fdd95cd50b9a32493a9d47cd253dc5108dc1..40fe719c3d45636b26e4a8c7ea3ae304460b919f 100644 (file)
@@ -24,7 +24,6 @@ import org.openhab.io.transport.modbus.BitArray;
 import org.openhab.io.transport.modbus.ModbusReadCallback;
 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
 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.ModbusWriteCoilRequestBlueprint;
 import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
@@ -87,9 +86,9 @@ public class ModbusLibraryWrapper {
     }
 
     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++) {
-            registers[i] = new ModbusRegister(inputRegisters[i].getValue());
+            registers[i] = inputRegisters[i].getValue();
         }
         return new ModbusRegisterArray(registers);
     }
@@ -270,7 +269,7 @@ public class ModbusLibraryWrapper {
      * @return
      */
     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]);
     }
 
index a416e91e324dfea1f10bc162e6cb98e9f9da3561..f0c9eab5627a18ce6c6657f0feca8d10c2d39f50 100644 (file)
@@ -22,7 +22,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.io.transport.modbus.BitArray;
 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.ModbusWriteCoilRequestBlueprint;
 import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
@@ -197,7 +196,7 @@ public final class WriteRequestJsonUtilities {
                 }
                 // fall-through to WRITE_MULTIPLE_REGISTERS
             case WRITE_MULTIPLE_REGISTERS: {
-                ModbusRegister[] registers = new ModbusRegister[valuesElem.size()];
+                int[] registers = new int[valuesElem.size()];
                 if (registers.length == 0) {
                     throw new IllegalArgumentException("Must provide at least one register");
                 } else if (valuesElem.size() > ModbusConstants.MAX_REGISTERS_WRITE_COUNT) {
@@ -206,7 +205,7 @@ public final class WriteRequestJsonUtilities {
                                     ModbusConstants.MAX_REGISTERS_WRITE_COUNT));
                 }
                 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),
                         !writeSingle.get(), maxTries);
index e5b0819b5fc402f0b7867177d0c38e6077f2b19a..f4e562df61b530a908e76d2094f50a508d67dd21 100644 (file)
@@ -322,9 +322,9 @@ public class BitUtilitiesCommandToRegistersTest {
                 is(equalTo(expectedRegisters.length)));
         for (int i = 0; i < expectedRegisters.length; i++) {
             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)));
         }
     }
diff --git a/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractBitTest.java b/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractBitTest.java
new file mode 100644 (file)
index 0000000..8f74368
--- /dev/null
@@ -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));
+    }
+}
diff --git a/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractFloat32Test.java b/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractFloat32Test.java
new file mode 100644 (file)
index 0000000..388a946
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractIndividualMethodsTest.java b/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractIndividualMethodsTest.java
new file mode 100644 (file)
index 0000000..83f1a62
--- /dev/null
@@ -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);
+    }
+}
diff --git a/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractInt8Test.java b/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractInt8Test.java
new file mode 100644 (file)
index 0000000..811b12d
--- /dev/null
@@ -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));
+    }
+}
index 8647903b24e63cebaad94d971711371b22e1adf2..195c68c8ac752e892df8bb0212d778d59aed9fbf 100644 (file)
@@ -16,12 +16,10 @@ 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.util.Collection;
 import java.util.Collections;
 import java.util.Optional;
 import java.util.stream.Collectors;
-import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 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.io.transport.modbus.ModbusBitUtilities;
 import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
-import org.openhab.io.transport.modbus.ModbusRegister;
 import org.openhab.io.transport.modbus.ModbusRegisterArray;
 
 /**
@@ -39,12 +36,7 @@ import org.openhab.io.transport.modbus.ModbusRegisterArray;
 public class BitUtilitiesExtractStateFromRegistersTest {
 
     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));
+        return new ModbusRegisterArray(arr);
     }
 
     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("64532"), ValueType.UINT16, shortArrayToRegisterArray(4, -1004), 1 },
                 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 },
                 //
                 // INT32
@@ -364,7 +356,7 @@ public class BitUtilitiesExtractStateFromRegistersTest {
     @SuppressWarnings({ "unchecked", "rawtypes" })
     @ParameterizedTest
     @MethodSource("data")
-    public void testCommandToRegisters(Object expectedResult, ValueType type, ModbusRegisterArray registers,
+    public void testextractStateFromRegisters(Object expectedResult, ValueType type, ModbusRegisterArray registers,
             int index) {
         if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class) expectedResult)) {
             assertThrows((Class) expectedResult,
diff --git a/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractStringFromRegistersTest.java b/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractStringFromRegistersTest.java
deleted file mode 100644 (file)
index 59b9488..0000000
+++ /dev/null
@@ -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)));
-    }
-}
diff --git a/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractStringTest.java b/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/BitUtilitiesExtractStringTest.java
new file mode 100644 (file)
index 0000000..bd76286
--- /dev/null
@@ -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 ï¿½
+                new Object[] { "�", 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));
+        }
+    }
+}
index dee72dda3351ba9415d76d83975f8958f0b90026..2eb0c4a87387f795ff2c477c7b716b192109a498 100644 (file)
@@ -14,9 +14,11 @@ package org.openhab.io.transport.modbus.test;
 
 import java.util.Arrays;
 import java.util.Objects;
+import java.util.stream.IntStream;
 import java.util.stream.StreamSupport;
 
 import org.hamcrest.Description;
+import org.openhab.io.transport.modbus.ModbusRegisterArray;
 import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
 import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
 
@@ -42,7 +44,9 @@ class RegisterMatcher extends AbstractRequestComparer<ModbusWriteRegisterRequest
 
     @Override
     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();
         return Objects.deepEquals(actual, expectedRegisterValues);
     }
index 912175e4d5d6e355b0638e588b2002369ae7164c..f5c99731471c88c2810c7c3a21938d001ca2333a 100644 (file)
@@ -19,11 +19,14 @@ import static org.junit.jupiter.api.Assumptions.*;
 
 import java.io.IOException;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.InetAddress;
 import java.net.Socket;
 import java.net.SocketImpl;
 import java.net.SocketImplFactory;
+import java.net.SocketOption;
+import java.net.StandardSocketOptions;
 import java.net.UnknownHostException;
 import java.util.BitSet;
 import java.util.Optional;
@@ -120,16 +123,16 @@ public class SmokeTest extends IntegrationTestSupport {
     private void testHoldingValues(ModbusRegisterArray registers, int offsetInRegisters) {
         for (int i = 0; i < registers.size(); i++) {
             int expected = (i + offsetInRegisters) * HOLDING_REGISTER_MULTIPLIER;
-            assertThat(String.format("i=%d, expecting %d, got %d", i, registers.getRegister(i).toUnsignedShort(),
-                    expected), registers.getRegister(i).toUnsignedShort(), is(equalTo(expected)));
+            assertThat(String.format("i=%d, expecting %d, got %d", i, registers.getRegister(i), expected),
+                    registers.getRegister(i), is(equalTo(expected)));
         }
     }
 
     private void testInputValues(ModbusRegisterArray registers, int offsetInRegisters) {
         for (int i = 0; i < registers.size(); i++) {
             int expected = (i + offsetInRegisters) * INPUT_REGISTER_MULTIPLIER;
-            assertThat(String.format("i=%d, expecting %d, got %d", i, registers.getRegister(i).toUnsignedShort(),
-                    expected), registers.getRegister(i).toUnsignedShort(), is(equalTo(expected)));
+            assertThat(String.format("i=%d, expecting %d, got %d", i, registers.getRegister(i), expected),
+                    registers.getRegister(i), is(equalTo(expected)));
         }
     }
 
@@ -945,11 +948,7 @@ public class SmokeTest extends IntegrationTestSupport {
         } catch (UnknownHostException e) {
             throw new RuntimeException(e);
         }
-        return socketSpy.sockets.stream().filter(socketImpl -> {
-            Socket socket = getSocketOfSocketImpl(socketImpl);
-            return socket.getPort() == tcpModbusPort && socket.isConnected()
-                    && socket.getLocalAddress().equals(testServerAddress);
-        }).count();
+        return socketSpy.sockets.stream().filter(this::isConnectedToTestServer).count();
     }
 
     /**
@@ -972,25 +971,87 @@ public class SmokeTest extends IntegrationTestSupport {
 
     private static SocketImpl newSocksSocketImpl() {
         try {
-            Class<?> defaultSocketImpl = Class.forName("java.net.SocksSocketImpl");
-            Constructor<?> constructor = defaultSocketImpl.getDeclaredConstructor();
-            constructor.setAccessible(true);
-            return (SocketImpl) constructor.newInstance();
+            Class<?> socksSocketImplClass = Class.forName("java.net.SocksSocketImpl");
+            Class<?> socketImplClass = SocketImpl.class;
+
+            // // 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) {
             throw new RuntimeException(e);
         }
     }
 
-    /**
-     * Get Socket corresponding to SocketImpl using reflection
-     */
-    private static Socket getSocketOfSocketImpl(SocketImpl impl) {
+    private boolean isConnectedToTestServer(SocketImpl impl) {
+        final InetAddress testServerAddress;
         try {
-            Method getSocket = SocketImpl.class.getDeclaredMethod("getSocket");
-            getSocket.setAccessible(true);
-            return (Socket) getSocket.invoke(impl);
-        } catch (Exception e) {
+            testServerAddress = localAddress();
+        } catch (UnknownHostException 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);
     }
 }
diff --git a/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/ValueBufferTest.java b/bundles/org.openhab.io.transport.modbus/src/test/java/org/openhab/io/transport/modbus/test/ValueBufferTest.java
new file mode 100644 (file)
index 0000000..7f14c13
--- /dev/null
@@ -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());
+    }
+}
index 706b9f44e4fee1a1f0586fa770f40b92d2b7ba40..65e99e86403ca7e2a7bb9bfbe4a698600e87b7eb 100644 (file)
@@ -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.ModbusReadFunctionCode;
 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.ModbusResponse;
 import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
@@ -517,7 +516,7 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
     public void testOnRegistersInt16StaticTransformation() {
         ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
                 "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_ERROR, is(nullValue(State.class)));
@@ -538,8 +537,7 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
         mockTransformation("MULTIPLY", new MultiplyTransformation());
         ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
                 "0", "MULTIPLY(10)", ModbusConstants.ValueType.INT16, null,
-                new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null,
-                bundleContext);
+                new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
 
         assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(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,
                 "0", "default", ModbusConstants.ValueType.FLOAT32, null, new ModbusRegisterArray(
                         // equivalent of floating point NaN
-                        new ModbusRegister[] { new ModbusRegister((byte) 0x7f, (byte) 0xc0),
-                                new ModbusRegister((byte) 0x00, (byte) 0x00) }),
+                        new byte[] { (byte) 0x7f, (byte) 0xc0, (byte) 0x00, (byte) 0x00 }),
                 null, bundleContext);
 
         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,
                 "0", "ONOFF(10)", ModbusConstants.ValueType.INT16, null,
-                new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null,
-                bundleContext);
+                new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
 
         assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(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.getReference(), is(equalTo(50)));
         assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
-        assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
-                is(equalTo(5)));
+        assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0), is(equalTo(5)));
     }
 
     @Test
@@ -702,11 +697,11 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
             assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS)));
             assertThat(writeRequest.getReference(), is(equalTo(5412)));
             assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(3)));
-            assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
+            assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
                     is(equalTo(1)));
-            assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(1).getValue(),
+            assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(1),
                     is(equalTo(0)));
-            assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(2).getValue(),
+            assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(2),
                     is(equalTo(5)));
         }
         {
@@ -714,7 +709,7 @@ public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
             assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
             assertThat(writeRequest.getReference(), is(equalTo(555)));
             assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
-            assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
+            assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
                     is(equalTo(3)));
         }
     }