]> git.basschouten.com Git - openhab-addons.git/commitdiff
[insteon] Refactor msg definition/factory and product data classes (#17537)
authorJeremy <jsetton@users.noreply.github.com>
Thu, 10 Oct 2024 19:15:31 +0000 (15:15 -0400)
committerGitHub <noreply@github.com>
Thu, 10 Oct 2024 19:15:31 +0000 (21:15 +0200)
Signed-off-by: jsetton <jeremy.setton@gmail.com>
17 files changed:
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/DebugCommand.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonModem.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonScene.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/ProductData.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/ProductDataRegistry.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/RampRate.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBReader.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/discovery/InsteonDiscoveryService.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonBridgeHandler.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/transport/message/DataType.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/transport/message/Direction.java [new file with mode: 0644]
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/transport/message/Field.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/transport/message/Msg.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/transport/message/MsgDefinition.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/transport/message/MsgDefinitionRegistry.java
bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/transport/message/MsgFactory.java
bundles/org.openhab.binding.insteon/src/main/resources/msg-definitions.xml

index d35e495b61c3048d487009ecb44b499eee1bb2b5..4e899fed3b2042587c6afe97d23eaf35274f14f4 100644 (file)
@@ -33,10 +33,10 @@ import org.openhab.binding.insteon.internal.device.InsteonScene;
 import org.openhab.binding.insteon.internal.device.X10Address;
 import org.openhab.binding.insteon.internal.device.X10Device;
 import org.openhab.binding.insteon.internal.transport.PortListener;
+import org.openhab.binding.insteon.internal.transport.message.Direction;
 import org.openhab.binding.insteon.internal.transport.message.FieldException;
 import org.openhab.binding.insteon.internal.transport.message.InvalidMessageTypeException;
 import org.openhab.binding.insteon.internal.transport.message.Msg;
-import org.openhab.binding.insteon.internal.transport.message.Msg.Direction;
 import org.openhab.binding.insteon.internal.transport.message.MsgDefinitionRegistry;
 import org.openhab.binding.insteon.internal.utils.HexUtils;
 import org.openhab.core.io.console.Console;
index b41212d553a216adc5863ea93a0a0ee2d486a975..d3328d8cd8e740d40a426fd398331d542727161f 100644 (file)
@@ -245,7 +245,7 @@ public class InsteonModem extends BaseDevice<InsteonAddress, InsteonBridgeHandle
         int deviceCategory = msg.getInt("DeviceCategory");
         int subCategory = msg.getInt("DeviceSubCategory");
 
-        ProductData productData = ProductDataRegistry.getInstance().getProductData(deviceCategory, subCategory);
+        ProductData productData = ProductData.makeInsteonProduct(deviceCategory, subCategory);
         productData.setFirmwareVersion(msg.getInt("FirmwareVersion"));
 
         DeviceType deviceType = productData.getDeviceType();
index 33c19aab860e8b86652f394c27721ecea23b1194..e7810eeca7f0b78f72fb2ce591f8e6fdae72d8de 100644 (file)
@@ -87,9 +87,9 @@ public class InsteonScene implements Scene {
     }
 
     public State getState() {
-        return getEntries().stream().allMatch(entry -> entry.getState() == UnDefType.NULL) ? UnDefType.NULL
-                : OnOffType.from(getEntries().stream().filter(entry -> entry.getState() != UnDefType.NULL)
-                        .allMatch(entry -> entry.getState().equals(entry.getOnState())));
+        return getEntries().stream().noneMatch(SceneEntry::isStateDefined) ? UnDefType.NULL
+                : OnOffType
+                        .from(getEntries().stream().filter(SceneEntry::isStateDefined).allMatch(SceneEntry::isStateOn));
     }
 
     public boolean hasEntry(InsteonAddress address) {
@@ -354,6 +354,14 @@ public class InsteonScene implements Scene {
             return state;
         }
 
+        public boolean isStateDefined() {
+            return !UnDefType.NULL.equals(state);
+        }
+
+        public boolean isStateOn() {
+            return getOnState().equals(state);
+        }
+
         public void setState(State state) {
             this.state = state;
         }
index 358c08328b67044fc854f25a8e6017f758566abc..39baf5d856724a402c26a90258fed13eb8302d5c 100644 (file)
@@ -231,21 +231,9 @@ public class ProductData {
         ProductData productData = new ProductData();
         productData.setDeviceCategory(deviceCategory);
         productData.setSubCategory(subCategory);
-        return productData;
-    }
-
-    /**
-     * Factory method for creating a ProductData for an Insteon product
-     *
-     * @param deviceCategory the Insteon device category
-     * @param subCategory the Insteon device subcategory
-     * @param srcData the source product data to use
-     * @return the product data
-     */
-    public static ProductData makeInsteonProduct(int deviceCategory, int subCategory, @Nullable ProductData srcData) {
-        ProductData productData = makeInsteonProduct(deviceCategory, subCategory);
-        if (srcData != null) {
-            productData.update(srcData);
+        ProductData resourceData = ProductDataRegistry.getInstance().getProductData(deviceCategory, subCategory);
+        if (resourceData != null) {
+            productData.update(resourceData);
         }
         return productData;
     }
index 6c2a7e18370734424c594b3154364d750519cdd7..ef201b4230efaa4a1eeeba34b7b5d66d1ae8e7fe 100644 (file)
@@ -45,9 +45,9 @@ public class ProductDataRegistry extends InsteonResourceLoader {
      *
      * @param deviceCategory device category to match
      * @param subCategory device subcategory to match
-     * @return product data matching provided parameters
+     * @return product data if matching provided parameters, otherwise null
      */
-    public ProductData getProductData(int deviceCategory, int subCategory) {
+    public @Nullable ProductData getProductData(int deviceCategory, int subCategory) {
         int productId = getProductId(deviceCategory, subCategory);
         if (!products.containsKey(productId)) {
             logger.warn("unknown product for devCat:{} subCat:{} in device products xml file",
@@ -55,19 +55,7 @@ public class ProductDataRegistry extends InsteonResourceLoader {
             // fallback to matching product id using device category only
             productId = getProductId(deviceCategory, ProductData.SUB_CATEGORY_UNKNOWN);
         }
-
-        return ProductData.makeInsteonProduct(deviceCategory, subCategory, products.get(productId));
-    }
-
-    /**
-     * Returns the device type for a given dev/sub category
-     *
-     * @param deviceCategory device category to match
-     * @param subCategory device subcategory to match
-     * @return device type matching provided parameters
-     */
-    public @Nullable DeviceType getDeviceType(int deviceCategory, int subCategory) {
-        return getProductData(deviceCategory, subCategory).getDeviceType();
+        return products.get(productId);
     }
 
     /**
@@ -143,7 +131,9 @@ public class ProductDataRegistry extends InsteonResourceLoader {
             logger.warn("overwriting previous definition of product {}", products.get(productId));
         }
 
-        ProductData productData = ProductData.makeInsteonProduct(deviceCategory, subCategory);
+        ProductData productData = new ProductData();
+        productData.setDeviceCategory(deviceCategory);
+        productData.setSubCategory(subCategory);
         productData.setProductKey(productKey);
         productData.setFirstRecordLocation(firstRecord);
 
index 7a9079ae1d6d458cc59d9443e0bbb82849d41293..d622162b70901874ee43ae057ab3bdf18c2c1424 100644 (file)
@@ -17,6 +17,7 @@ import static org.openhab.binding.insteon.internal.InsteonBindingConstants.*;
 import java.text.DecimalFormat;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -64,6 +65,8 @@ public enum RampRate {
     SEC_0_2(0x1E, 0.2),
     INSTANT(0x1F, 0.1);
 
+    private static final List<String> SUPPORTED_FEATURE_TYPES = List.of(FEATURE_TYPE_GENERIC_DIMMER);
+
     private static final Map<Integer, RampRate> VALUE_MAP = Arrays.stream(values())
             .collect(Collectors.toUnmodifiableMap(rate -> rate.value, Function.identity()));
 
@@ -105,7 +108,7 @@ public enum RampRate {
      * @return true if supported
      */
     public static boolean supportsFeatureType(String featureType) {
-        return FEATURE_TYPE_GENERIC_DIMMER.equals(featureType);
+        return SUPPORTED_FEATURE_TYPES.contains(featureType);
     }
 
     /**
index e748c24cb604d26228062a6cc304a4f30be617f9..07c9e4aa6cd7bb5f7a3b6032a9a0dc80a08d5e89 100644 (file)
@@ -24,7 +24,6 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.insteon.internal.device.InsteonAddress;
 import org.openhab.binding.insteon.internal.device.InsteonModem;
 import org.openhab.binding.insteon.internal.device.ProductData;
-import org.openhab.binding.insteon.internal.device.ProductDataRegistry;
 import org.openhab.binding.insteon.internal.transport.PortListener;
 import org.openhab.binding.insteon.internal.transport.message.FieldException;
 import org.openhab.binding.insteon.internal.transport.message.InvalidMessageTypeException;
@@ -290,7 +289,7 @@ public class ModemDBReader implements PortListener {
         int subCategory = Byte.toUnsignedInt(toAddr.getMiddleByte());
         int firmware = Byte.toUnsignedInt(toAddr.getLowByte());
         int hardware = msg.getInt("command2");
-        ProductData productData = ProductDataRegistry.getInstance().getProductData(deviceCategory, subCategory);
+        ProductData productData = ProductData.makeInsteonProduct(deviceCategory, subCategory);
         productData.setFirmwareVersion(firmware);
         productData.setHardwareVersion(hardware);
         // set product data if in modem db
index b3af9b20fa34825272e0476fa6e8f6b2ab78de4c..8009e9dab416b8212f0e4730bb33f895206b4226 100644 (file)
@@ -62,8 +62,7 @@ public class InsteonDiscoveryService extends AbstractDiscoveryService {
 
     public void discoverInsteonDevice(InsteonAddress address, @Nullable ProductData productData) {
         InsteonModem modem = handler.getModem();
-        if (handler.isDeviceDiscoveryEnabled() && modem != null && modem.getDB().hasEntry(address)
-                && !modem.hasDevice(address)) {
+        if (modem != null && modem.getDB().hasEntry(address) && !modem.hasDevice(address)) {
             addInsteonDevice(address, productData);
         } else {
             removeInsteonDevice(address);
@@ -72,8 +71,7 @@ public class InsteonDiscoveryService extends AbstractDiscoveryService {
 
     public void discoverInsteonScene(int group) {
         InsteonModem modem = handler.getModem();
-        if (handler.isSceneDiscoveryEnabled() && modem != null && modem.getDB().hasBroadcastGroup(group)
-                && !modem.hasScene(group)) {
+        if (modem != null && modem.getDB().hasBroadcastGroup(group) && !modem.hasScene(group)) {
             addInsteonScene(group);
         } else {
             removeInsteonScene(group);
index 6e8f63e29203d985cede3f1d6e0bfb28961d4967..e3975e9ceb9089f70b2715badd35a4f47845f220 100644 (file)
@@ -284,14 +284,14 @@ public class InsteonBridgeHandler extends InsteonBaseThingHandler implements Bri
 
     protected void discoverInsteonDevice(InsteonAddress address, @Nullable ProductData productData) {
         InsteonDiscoveryService discoveryService = getDiscoveryService();
-        if (discoveryService != null) {
+        if (discoveryService != null && isDeviceDiscoveryEnabled()) {
             scheduler.execute(() -> discoveryService.discoverInsteonDevice(address, productData));
         }
     }
 
     protected void discoverInsteonScene(int group) {
         InsteonDiscoveryService discoveryService = getDiscoveryService();
-        if (discoveryService != null) {
+        if (discoveryService != null && isSceneDiscoveryEnabled()) {
             scheduler.execute(() -> discoveryService.discoverInsteonScene(group));
         }
     }
index 3b6d753debe82b7742e5b9692add550b360d25d9..c0333e763a73c489558744890d636b585956ec5e 100644 (file)
@@ -18,6 +18,7 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 
 /**
  * Defines the data types that can be used in the fields of a message.
@@ -29,8 +30,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 @NonNullByDefault
 public enum DataType {
     BYTE("byte", 1),
-    ADDRESS("address", 3),
-    INVALID("invalid", -1);
+    ADDRESS("address", 3);
 
     private static final Map<String, DataType> NAME_MAP = Arrays.stream(values())
             .collect(Collectors.toUnmodifiableMap(type -> type.name, Function.identity()));
@@ -55,9 +55,9 @@ public enum DataType {
      * Factory method for getting a DataType from the data type name
      *
      * @param name the data type name
-     * @return the data type
+     * @return the data type if defined, otherwise null
      */
-    public static DataType get(String name) {
-        return NAME_MAP.getOrDefault(name, DataType.INVALID);
+    public static @Nullable DataType get(String name) {
+        return NAME_MAP.get(name);
     }
 }
diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/transport/message/Direction.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/transport/message/Direction.java
new file mode 100644 (file)
index 0000000..b27c43b
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2010-2024 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.insteon.internal.transport.message;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link Direction} represents a message direction
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public enum Direction {
+    FROM_MODEM("IN"),
+    TO_MODEM("OUT");
+
+    private final String label;
+
+    private Direction(String label) {
+        this.label = label;
+    }
+
+    @Override
+    public String toString() {
+        return label;
+    }
+
+    public static @Nullable Direction get(String value) {
+        try {
+            return Direction.valueOf(value);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+}
index 2fbf6ee28f27d4a473541611d29082e041cf3dcb..447a0dd082777d4fa6427a6d014a7d087f483322 100644 (file)
@@ -34,6 +34,12 @@ public final class Field {
     private final int offset;
     private final DataType type;
 
+    public Field(String name, int offset, DataType type) {
+        this.name = name;
+        this.offset = offset;
+        this.type = type;
+    }
+
     public String getName() {
         return name;
     }
@@ -42,16 +48,6 @@ public final class Field {
         return offset;
     }
 
-    public DataType getType() {
-        return type;
-    }
-
-    public Field(String name, DataType type, int offset) {
-        this.name = name;
-        this.type = type;
-        this.offset = offset;
-    }
-
     private void check(int len, DataType t) throws FieldException {
         if (offset + type.getSize() > len) {
             throw new FieldException("field write beyond end of msg");
@@ -61,16 +57,16 @@ public final class Field {
         }
     }
 
-    public void set(byte[] data, Object o) throws FieldException {
+    public void set(byte[] data, String value) throws FieldException, IllegalArgumentException {
         switch (type) {
             case BYTE:
-                setByte(data, (Byte) o);
+                byte b = value.isEmpty() ? 0x00 : (byte) HexUtils.toInteger(value);
+                setByte(data, b);
                 break;
             case ADDRESS:
-                setAddress(data, (InsteonAddress) o);
+                InsteonAddress address = value.isEmpty() ? InsteonAddress.UNKNOWN : new InsteonAddress(value);
+                setAddress(data, address);
                 break;
-            default:
-                throw new FieldException("field data type unknown");
         }
     }
 
@@ -141,8 +137,6 @@ public final class Field {
                 case ADDRESS:
                     s += getAddress(data).toString();
                     break;
-                default:
-                    throw new FieldException("field data type unknown");
             }
         } catch (FieldException e) {
             s += "NULL";
index 4ca24bce91d35da1f8d95f3839b1c6674034bebe..25d64a2561962a10b5725a618775947fcb251540 100644 (file)
@@ -38,38 +38,21 @@ import org.slf4j.LoggerFactory;
  */
 @NonNullByDefault
 public class Msg {
-
-    public static enum Direction {
-        TO_MODEM,
-        FROM_MODEM
-    }
-
     private final Logger logger = LoggerFactory.getLogger(Msg.class);
 
-    private byte[] data;
-    private int headerLength;
-    private Direction direction;
-    private MsgDefinition definition = new MsgDefinition();
+    private final byte[] data;
+    private final MsgDefinition definition;
     private long quietTime = 0;
     private boolean replayed = false;
     private long timestamp = System.currentTimeMillis();
 
-    public Msg(int headerLength, int dataLength, Direction direction) {
-        this.data = new byte[dataLength];
-        this.headerLength = headerLength;
-        this.direction = direction;
-    }
-
-    public Msg(Msg msg, byte[] data, int dataLength) {
-        this.data = Arrays.copyOf(data, dataLength);
-        this.headerLength = msg.headerLength;
-        this.direction = msg.direction;
-        // the message definition usually doesn't change, but just to be sure...
-        this.definition = new MsgDefinition(msg.definition);
+    public Msg(byte[] data, MsgDefinition definition) {
+        this.data = Arrays.copyOf(data, definition.getLength());
+        this.definition = definition;
     }
 
-    public Msg(Msg msg) {
-        this(msg, msg.data, msg.data.length);
+    public Msg(MsgDefinition definition) {
+        this(definition.getData(), definition);
     }
 
     public byte[] getData() {
@@ -81,15 +64,11 @@ public class Msg {
     }
 
     public int getHeaderLength() {
-        return headerLength;
+        return definition.getHeaderLength();
     }
 
     public Direction getDirection() {
-        return direction;
-    }
-
-    public MsgDefinition getDefinition() {
-        return definition;
+        return definition.getDirection();
     }
 
     public long getQuietTime() {
@@ -129,11 +108,11 @@ public class Msg {
     }
 
     public boolean isInbound() {
-        return direction == Direction.FROM_MODEM;
+        return getDirection() == Direction.FROM_MODEM;
     }
 
     public boolean isOutbound() {
-        return direction == Direction.TO_MODEM;
+        return getDirection() == Direction.TO_MODEM;
     }
 
     public boolean isEcho() {
@@ -244,10 +223,6 @@ public class Msg {
         return replayed;
     }
 
-    public void setDefinition(MsgDefinition definition) {
-        this.definition = definition;
-    }
-
     public void setQuietTime(long quietTime) {
         this.quietTime = quietTime;
     }
@@ -256,10 +231,6 @@ public class Msg {
         this.replayed = replayed;
     }
 
-    public void addField(Field f) {
-        definition.addField(f);
-    }
-
     public boolean containsField(String key) {
         return definition.containsField(key);
     }
@@ -283,12 +254,11 @@ public class Msg {
     /**
      * Sets a byte at a specific field
      *
-     * @param key the string key in the message definition
+     * @param key the name of the field
      * @param value the byte to put
      */
     public void setByte(String key, byte value) throws FieldException {
-        Field field = definition.getField(key);
-        field.setByte(data, value);
+        definition.getField(key).setByte(data, value);
     }
 
     /**
@@ -443,27 +413,6 @@ public class Msg {
         return getInt(key, 4);
     }
 
-    /**
-     * Returns a byte as a hex string
-     *
-     * @param key the name of the field
-     * @return the hex string
-     */
-    public String getHexString(String key) throws FieldException {
-        return HexUtils.getHexString(getByte(key));
-    }
-
-    /**
-     * Returns a byte array starting from a certain field as a hex string
-     *
-     * @param key the name of the field
-     * @param numBytes number of bytes to get
-     * @return the hex string
-     */
-    public String getHexString(String key, int numBytes) throws FieldException {
-        return HexUtils.getHexString(getBytes(key, numBytes), numBytes);
-    }
-
     /**
      * Returns group based on specific message characteristics
      *
@@ -644,7 +593,7 @@ public class Msg {
 
     @Override
     public String toString() {
-        String s = (direction == Direction.TO_MODEM) ? "OUT:" : "IN:";
+        String s = definition.getDirection() + ":";
         for (Field field : definition.getFields()) {
             if ("messageFlags".equals(field.getName())) {
                 s += field.toString(data) + "=" + getType() + ":" + getHopsLeft() + ":" + getMaxHops() + "|";
@@ -668,8 +617,8 @@ public class Msg {
             return null;
         }
         return Optional
-                .ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(buf[1], isExtended, Direction.FROM_MODEM))
-                .filter(template -> template.getLength() == msgLen).map(template -> new Msg(template, buf, msgLen))
+                .ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(buf[1], isExtended, Direction.FROM_MODEM))
+                .filter(definition -> definition.getLength() == msgLen).map(definition -> new Msg(buf, definition))
                 .orElse(null);
     }
 
@@ -678,10 +627,12 @@ public class Msg {
      *
      * @param cmd the message command received
      * @return the length of the header to expect
+     * @throws InvalidMessageTypeException
      */
-    public static int getHeaderLength(byte cmd) {
-        return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(cmd, Direction.FROM_MODEM))
-                .map(Msg::getHeaderLength).orElse(-1);
+    public static int getHeaderLength(byte cmd) throws InvalidMessageTypeException {
+        return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(cmd, Direction.FROM_MODEM))
+                .map(MsgDefinition::getHeaderLength)
+                .orElseThrow(() -> new InvalidMessageTypeException("unknown message command"));
     }
 
     /**
@@ -690,32 +641,27 @@ public class Msg {
      * @param cmd the message command received
      * @param isExtended if is an extended message
      * @return message length, or -1 if length cannot be determined
+     * @throws InvalidMessageTypeException
      */
-    public static int getMessageLength(byte cmd, boolean isExtended) {
+    public static int getMessageLength(byte cmd, boolean isExtended) throws InvalidMessageTypeException {
         return Optional
-                .ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(cmd, isExtended, Direction.FROM_MODEM))
-                .map(Msg::getLength).orElse(-1);
+                .ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(cmd, isExtended, Direction.FROM_MODEM))
+                .map(MsgDefinition::getLength)
+                .orElseThrow(() -> new InvalidMessageTypeException("unknown message command"));
     }
 
     /**
      * Factory method to determine if a message is extended
      *
      * @param buf the received bytes
-     * @param len the number of bytes received so far
-     * @param headerLength the known length of the header
-     * @return true if it is definitely extended, false if cannot be
-     *         determined or if it is a standard message
+     * @param headerLength the header length
+     * @return true if is extended
      */
-    public static boolean isExtended(byte[] buf, int len, int headerLength) {
-        if (headerLength <= 2) {
+    public static boolean isExtended(byte[] buf, int headerLength) {
+        if (headerLength <= 2 || buf.length < headerLength) {
             return false;
-        } // extended messages are longer
-        if (len < headerLength) {
-            return false;
-        } // not enough data to tell if extended
-        byte flags = buf[headerLength - 1]; // last byte says flags
-        boolean isExtended = BinaryUtils.isBitSet(flags & 0xFF, 4);
-        return isExtended;
+        }
+        return BinaryUtils.isBitSet(buf[headerLength - 1] & 0xFF, 4);
     }
 
     /**
@@ -726,7 +672,7 @@ public class Msg {
      * @throws InvalidMessageTypeException
      */
     public static Msg makeMessage(byte cmd) throws InvalidMessageTypeException {
-        return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(cmd, Direction.TO_MODEM))
+        return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(cmd, Direction.TO_MODEM))
                 .map(Msg::new).orElseThrow(() -> new InvalidMessageTypeException(
                         "unknown message command: " + HexUtils.getHexString(cmd)));
     }
@@ -739,7 +685,7 @@ public class Msg {
      * @throws InvalidMessageTypeException
      */
     public static Msg makeMessage(String type) throws InvalidMessageTypeException {
-        return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(type)).map(Msg::new)
+        return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(type)).map(Msg::new)
                 .orElseThrow(() -> new InvalidMessageTypeException("unknown message type: " + type));
     }
 
index 2d295e4063fc41cb47be8f2b3f7242c92d5ca7d9..75a51cc08816dc300a5a24e073d4f7ca3652fe02 100644 (file)
  */
 package org.openhab.binding.insteon.internal.transport.message;
 
-import java.util.Comparator;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.insteon.internal.utils.BinaryUtils;
 
 /**
  * Definition (layout) of an Insteon message. Says which bytes go where.
@@ -30,34 +29,34 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
  */
 @NonNullByDefault
 public class MsgDefinition {
-    private Map<String, Field> fields = new HashMap<>();
+    private final byte[] data;
+    private final int headerLength;
+    private final Direction direction;
+    private final Map<String, Field> fields;
 
-    MsgDefinition() {
+    public MsgDefinition(byte[] data, int headerLength, Direction direction, Map<String, Field> fields) {
+        this.data = data;
+        this.headerLength = headerLength;
+        this.direction = direction;
+        this.fields = fields;
     }
 
-    MsgDefinition(MsgDefinition definition) {
-        fields = new HashMap<>(definition.fields);
+    public byte[] getData() {
+        return data;
     }
 
-    public List<Field> getFields() {
-        return fields.values().stream().sorted(Comparator.comparing(Field::getOffset)).toList();
+    public int getLength() {
+        return data.length;
     }
 
-    public boolean containsField(String name) {
-        return fields.containsKey(name);
+    public int getHeaderLength() {
+        return headerLength;
     }
 
-    public void addField(Field field) {
-        fields.put(field.getName(), field);
+    public Direction getDirection() {
+        return direction;
     }
 
-    /**
-     * Finds field of a given name
-     *
-     * @param name name of the field to search for
-     * @return reference to field
-     * @throws FieldException if no such field can be found
-     */
     public Field getField(String name) throws FieldException {
         Field field = fields.get(name);
         if (field == null) {
@@ -65,4 +64,41 @@ public class MsgDefinition {
         }
         return field;
     }
+
+    public List<Field> getFields() {
+        return fields.values().stream().toList();
+    }
+
+    public boolean containsField(String name) {
+        return fields.containsKey(name);
+    }
+
+    public byte getByte(String name) throws FieldException {
+        return getField(name).getByte(data);
+    }
+
+    public byte getCommand() {
+        try {
+            return getByte("Cmd");
+        } catch (FieldException e) {
+            return (byte) 0xFF;
+        }
+    }
+
+    public boolean isExtended() {
+        try {
+            return BinaryUtils.isBitSet(getByte("messageFlags"), 4);
+        } catch (FieldException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public String toString() {
+        String s = direction + ":";
+        for (Field field : getFields()) {
+            s += field.toString(data) + "|";
+        }
+        return s;
+    }
 }
index 445b2c9a824ec1707145944660c4c046fcf7d7fe..107bc2b2d37742996079d45e5063e3f05c7607e0 100644 (file)
@@ -14,14 +14,10 @@ package org.openhab.binding.insteon.internal.transport.message;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Map.Entry;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.insteon.internal.InsteonResourceLoader;
-import org.openhab.binding.insteon.internal.device.InsteonAddress;
-import org.openhab.binding.insteon.internal.transport.message.Msg.Direction;
-import org.openhab.binding.insteon.internal.utils.HexUtils;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
@@ -39,44 +35,46 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
     private static final MsgDefinitionRegistry MSG_DEFINITION_REGISTRY = new MsgDefinitionRegistry();
     private static final String RESOURCE_NAME = "/msg-definitions.xml";
 
-    private Map<String, Msg> definitions = new LinkedHashMap<>();
+    private Map<String, MsgDefinition> definitions = new LinkedHashMap<>();
 
     private MsgDefinitionRegistry() {
         super(RESOURCE_NAME);
     }
 
     /**
-     * Returns message template for a given type
+     * Returns message definition for a given type
      *
      * @param type message type to match
-     * @return message template if found, otherwise null
+     * @return message definition if found, otherwise null
      */
-    public @Nullable Msg getTemplate(String type) {
+    public @Nullable MsgDefinition getDefinition(String type) {
         return definitions.get(type);
     }
 
     /**
-     * Returns message template for a given command and direction
+     * Returns message definition for a given command and direction
      *
      * @param cmd message command to match
      * @param direction message direction to match
-     * @return message template if found, otherwise null
+     * @return message definition if found, otherwise null
      */
-    public @Nullable Msg getTemplate(byte cmd, Direction direction) {
-        return getTemplate(cmd, null, direction);
+    public @Nullable MsgDefinition getDefinition(byte cmd, Direction direction) {
+        return getDefinition(cmd, null, direction);
     }
 
     /**
-     * Returns message template for a given command, extended flag and direction
+     * Returns message definition for a given command, extended flag and direction
      *
      * @param cmd message command to match
      * @param isExtended if message is extended
      * @param direction message direction to match
-     * @return message template if found, otherwise null
+     * @return message definition if found, otherwise null
      */
-    public @Nullable Msg getTemplate(byte cmd, @Nullable Boolean isExtended, Direction direction) {
-        return definitions.values().stream().filter(msg -> msg.getCommand() == cmd && msg.getDirection() == direction
-                && (isExtended == null || msg.isExtended() == isExtended)).findFirst().orElse(null);
+    public @Nullable MsgDefinition getDefinition(byte cmd, @Nullable Boolean isExtended, Direction direction) {
+        return definitions.values().stream()
+                .filter(definition -> definition.getCommand() == cmd && definition.getDirection() == direction
+                        && (isExtended == null || definition.isExtended() == isExtended))
+                .findFirst().orElse(null);
     }
 
     /**
@@ -84,7 +82,7 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
      *
      * @return currently known message definitions
      */
-    public Map<String, Msg> getDefinitions() {
+    public Map<String, MsgDefinition> getDefinitions() {
         return definitions;
     }
 
@@ -131,12 +129,22 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
      * @throws SAXException
      */
     private void parseMsgDefinition(Element element) throws SAXException {
-        LinkedHashMap<Field, Object> fields = new LinkedHashMap<>();
         String name = element.getAttribute("name");
-        Direction direction = Direction.valueOf(element.getAttribute("direction"));
-        int length = element.hasAttribute("length") ? Integer.parseInt(element.getAttribute("length")) : 0;
+        if (name.isEmpty()) {
+            throw new SAXException("undefined message definition name");
+        }
+        Direction direction = Direction.get(element.getAttribute("direction"));
+        if (direction == null) {
+            throw new SAXException("invalid direction for message definition " + name);
+        }
+        int length = getAttributeAsInteger(element, "length", 0);
+        if (length == 0) {
+            throw new SAXException("undefined length for message definition " + name);
+        }
         int headerLength = 0;
         int offset = 0;
+        byte[] data = new byte[length];
+        Map<String, Field> fields = new LinkedHashMap<>();
 
         NodeList nodes = element.getChildNodes();
         for (int i = 0; i < nodes.getLength(); i++) {
@@ -144,27 +152,26 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
             if (node.getNodeType() == Node.ELEMENT_NODE) {
                 Element child = (Element) node;
                 String nodeName = child.getNodeName();
-                if ("header".equals(nodeName)) {
-                    headerLength = parseHeader(child, fields);
-                    // Increment the offset by the header length
-                    offset += headerLength;
-                } else {
+                if (!"header".equals(nodeName)) {
                     // Increment the offset by the field data type length
-                    offset += parseField(child, offset, fields);
+                    offset += parseField(child, offset, data, fields);
+                } else if (offset == 0) {
+                    headerLength = parseHeader(child, data, fields);
+                    // Set the offset to the header length
+                    offset = headerLength;
                 }
             }
         }
-        if (length == 0) {
-            length = offset;
-        } else if (offset != length) {
-            throw new SAXException("actual msg length " + offset + " differs from given msg length " + length);
+        if (headerLength == 0) {
+            throw new SAXException("undefined header for message definition " + name);
+        }
+        if (offset != length) {
+            throw new SAXException("actual msg length " + offset + " differs from given length " + length);
         }
 
-        try {
-            Msg msg = makeMsgTemplate(fields, headerLength, length, direction);
-            definitions.put(name, msg);
-        } catch (FieldException e) {
-            throw new SAXException("failed to create message definition " + name + ":", e);
+        MsgDefinition definition = new MsgDefinition(data, headerLength, direction, fields);
+        if (definitions.put(name, definition) != null) {
+            logger.warn("duplicate message definition {}", name);
         }
     }
 
@@ -172,12 +179,16 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
      * Parses header node
      *
      * @param element element to parse
+     * @param data msg data to update
      * @param fields fields map to update
      * @return header length
      * @throws SAXException
      */
-    private int parseHeader(Element element, LinkedHashMap<Field, Object> fields) throws SAXException {
-        int length = Integer.parseInt(element.getAttribute("length"));
+    private int parseHeader(Element element, byte[] data, Map<String, Field> fields) throws SAXException {
+        int length = getAttributeAsInteger(element, "length", 0);
+        if (length == 0) {
+            throw new SAXException("undefined header length");
+        }
         int offset = 0;
 
         NodeList nodes = element.getChildNodes();
@@ -186,10 +197,10 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
             if (node.getNodeType() == Node.ELEMENT_NODE) {
                 Element child = (Element) node;
                 // Increment the offset by the field data type length
-                offset += parseField(child, offset, fields);
+                offset += parseField(child, offset, data, fields);
             }
         }
-        if (length != offset) {
+        if (offset != length) {
             throw new SAXException("actual header length " + offset + " differs from given length " + length);
         }
         return length;
@@ -200,93 +211,27 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
      *
      * @param element element to parse
      * @param offset msg offset
+     * @param data msg data to update
      * @param fields fields map to update
      * @return field data type length
      * @throws SAXException
      */
-    private int parseField(Element element, int offset, LinkedHashMap<Field, Object> fields) throws SAXException {
+    private int parseField(Element element, int offset, byte[] data, Map<String, Field> fields) throws SAXException {
         String name = element.getAttribute("name");
-        if (name == null) {
-            throw new SAXException("undefined field name");
-        }
         DataType dataType = DataType.get(element.getNodeName());
-        Field field = new Field(name, dataType, offset);
-        Object value = getFieldValue(dataType, element.getTextContent().trim());
-        fields.put(field, value);
-        return dataType.getSize();
-    }
-
-    /**
-     * Returns field value
-     *
-     * @param dataType field data type
-     * @param value value to convert
-     * @return field value
-     * @throws SAXException
-     */
-    private Object getFieldValue(DataType dataType, String value) throws SAXException {
-        switch (dataType) {
-            case BYTE:
-                return getByteValue(value);
-            case ADDRESS:
-                return getAddressValue(value);
-            default:
-                throw new SAXException("invalid field data type");
-        }
-    }
-
-    /**
-     * Returns field value as a byte
-     *
-     * @param value value to convert
-     * @return byte
-     * @throws SAXException
-     */
-    private byte getByteValue(String value) throws SAXException {
-        try {
-            return value.isEmpty() ? 0x00 : (byte) HexUtils.toInteger(value);
-        } catch (NumberFormatException e) {
-            throw new SAXException("invalid field byte value: " + value);
+        if (dataType == null) {
+            throw new SAXException("invalid field data type");
         }
-    }
-
-    /**
-     * Returns field value as an insteon address
-     *
-     * @param value value to convert
-     * @return insteon address
-     * @throws SAXException
-     */
-    private InsteonAddress getAddressValue(String value) throws SAXException {
+        Field field = new Field(name, offset, dataType);
         try {
-            return value.isEmpty() ? InsteonAddress.UNKNOWN : new InsteonAddress(value);
-        } catch (IllegalArgumentException e) {
-            throw new SAXException("invalid field address value: " + value);
+            field.set(data, element.getTextContent());
+        } catch (FieldException | IllegalArgumentException e) {
+            throw new SAXException("failed to set field data:", e);
         }
-    }
-
-    /**
-     * Returns new message template
-     *
-     * @param fields msg fields
-     * @param length msg length
-     * @param headerLength header length
-     * @param direction msg direction
-     * @return new msg template
-     * @throws FieldException
-     */
-    private Msg makeMsgTemplate(Map<Field, Object> fields, int headerLength, int length, Direction direction)
-            throws FieldException {
-        Msg msg = new Msg(headerLength, length, direction);
-        for (Entry<Field, Object> entry : fields.entrySet()) {
-            Field field = entry.getKey();
-            byte[] data = msg.getData();
-            field.set(data, entry.getValue());
-            if (!field.getName().isEmpty()) {
-                msg.addField(field);
-            }
+        if (!name.isEmpty()) {
+            fields.put(name, field);
         }
-        return msg;
+        return dataType.getSize();
     }
 
     /**
index 5394ec3cdb0e0279d25ca60c274d06131e6e6ef0..0555e04cce30f52c21bd03c8436a07935be75017 100644 (file)
@@ -97,8 +97,7 @@ public class MsgFactory {
             logger.trace("got pure nack!");
             removeFromBuffer(1);
             try {
-                msg = Msg.makeMessage("PureNACK");
-                return msg;
+                return Msg.makeMessage("PureNACK");
             } catch (InvalidMessageTypeException e) {
                 return null;
             }
@@ -112,39 +111,23 @@ public class MsgFactory {
         // If not, we return null, and expect this method to be called again
         // when more data has come in.
         if (end > 1) {
-            // we have some data, but do we have enough to read the entire header?
-            int headerLength = Msg.getHeaderLength(buf[1]);
-            boolean isExtended = Msg.isExtended(buf, end, headerLength);
-            logger.trace("header length expected: {} extended: {}", headerLength, isExtended);
-            if (headerLength < 0) {
-                if (logger.isDebugEnabled()) {
-                    logger.debug("got unknown command code: {}", HexUtils.getHexString(buf[1]));
-                }
-                removeFromBuffer(1); // get rid of the leading 0x02 so draining works
-                bail();
-            } else if (headerLength >= 2) {
+            try {
+                int headerLength = Msg.getHeaderLength(buf[1]);
+                logger.trace("header length expected: {}", headerLength);
                 if (end >= headerLength) {
-                    // only when the header is complete do we know that isExtended is correct!
+                    boolean isExtended = Msg.isExtended(buf, headerLength);
                     int msgLen = Msg.getMessageLength(buf[1], isExtended);
-                    logger.trace("msgLen expected: {}", msgLen);
-                    if (msgLen < 0) {
-                        // Cannot make sense out of the combined command code & isExtended flag.
-                        if (logger.isDebugEnabled()) {
-                            logger.debug("got unknown command code/ext flag: {}", HexUtils.getHexString(buf[1]));
-                        }
-                        removeFromBuffer(1);
-                        bail();
-                    } else if (msgLen > 0) {
-                        if (end >= msgLen) {
-                            msg = Msg.createMessage(buf, msgLen, isExtended);
-                            removeFromBuffer(msgLen);
-                        }
-                    } else { // should never happen
-                        logger.warn("invalid message length, internal error!");
+                    logger.trace("msgLen expected: {} extended: {}", msgLen, isExtended);
+                    if (end >= msgLen) {
+                        msg = Msg.createMessage(buf, msgLen, isExtended);
+                        removeFromBuffer(msgLen);
                     }
                 }
-            } else { // should never happen
-                logger.warn("invalid header length, internal error!");
+            } catch (InvalidMessageTypeException e) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("got unknown command code: {}", HexUtils.getHexString(buf[1]));
+                }
+                bail();
             }
         }
         // indicate no more messages available in buffer if empty or undefined message
@@ -159,14 +142,11 @@ public class MsgFactory {
     }
 
     private void bail() throws IOException {
-        drainBuffer(); // this will drain until end or it finds the next message start
-        throw new IOException("bad data received");
-    }
-
-    private void drainBuffer() {
-        while (end > 0 && buf[0] != 0x02) {
+        // drain buffer until end or the next message start
+        do {
             removeFromBuffer(1);
-        }
+        } while (end > 0 && buf[0] != 0x02);
+        throw new IOException("bad data received");
     }
 
     private void removeFromBuffer(int len) {
@@ -174,7 +154,9 @@ public class MsgFactory {
         if (l > end) {
             l = end;
         }
-        System.arraycopy(buf, l, buf, 0, end + 1 - l);
-        end -= l;
+        if (l > 0) {
+            System.arraycopy(buf, l, buf, 0, end + 1 - l);
+            end -= l;
+        }
     }
 }
index dad8f26207fdb8f89eae9f337d896564964dac7f..81736a02a765a68b73dd5c20513484c060650a68 100644 (file)
                </header>
                <byte name="ALLLinkGroup"/>
                <byte name="ALLLinkCommand"/>
-               <byte name="ALLLinkCommand2">0x00</byte>
+               <byte name="ALLLinkCommand2"/>
        </msg>
        <msg name="SendALLLinkCommandReply" length="6" direction="FROM_MODEM">
                <header length="2">
                </header>
                <byte name="command1"/>
                <byte name="command2"/>
-               <byte name="userData1">0x00</byte>
-               <byte name="userData2">0x00</byte>
-               <byte name="userData3">0x00</byte>
-               <byte name="userData4">0x00</byte>
-               <byte name="userData5">0x00</byte>
-               <byte name="userData6">0x00</byte>
-               <byte name="userData7">0x00</byte>
-               <byte name="userData8">0x00</byte>
-               <byte name="userData9">0x00</byte>
-               <byte name="userData10">0x00</byte>
-               <byte name="userData11">0x00</byte>
-               <byte name="userData12">0x00</byte>
-               <byte name="userData13">0x00</byte>
-               <byte name="userData14">0x00</byte>
+               <byte name="userData1"/>
+               <byte name="userData2"/>
+               <byte name="userData3"/>
+               <byte name="userData4"/>
+               <byte name="userData5"/>
+               <byte name="userData6"/>
+               <byte name="userData7"/>
+               <byte name="userData8"/>
+               <byte name="userData9"/>
+               <byte name="userData10"/>
+               <byte name="userData11"/>
+               <byte name="userData12"/>
+               <byte name="userData13"/>
+               <byte name="userData14"/>
        </msg>
        <msg name="SendExtendedMessageReply" length="23" direction="FROM_MODEM">
                <header length="6">
                        <byte>0x02</byte>
                        <byte name="Cmd">0x63</byte>
                </header>
-               <byte name="rawX10"></byte>
-               <byte name="X10Flag">0x00</byte>
+               <byte name="rawX10"/>
+               <byte name="X10Flag"/>
        </msg>
        <msg name="SendX10MessageReply" length="5" direction="FROM_MODEM">
                <header length="2">
                        <byte>0x02</byte>
                        <byte name="Cmd">0x63</byte>
                </header>
-               <byte name="rawX10"></byte>
-               <byte name="X10Flag">0x00</byte>
+               <byte name="rawX10"/>
+               <byte name="X10Flag"/>
                <byte name="ACK/NACK"/>
        </msg>
        <msg name="StartALLLinking" length="4" direction="TO_MODEM">
                        <byte>0x02</byte>
                        <byte name="Cmd">0x64</byte>
                </header>
-               <byte name="LinkCode"></byte>
-               <byte name="ALLLinkGroup">0x00</byte>
+               <byte name="LinkCode"/>
+               <byte name="ALLLinkGroup"/>
        </msg>
        <msg name="StartALLLinkingReply" length="5" direction="FROM_MODEM">
                <header length="2">
                        <byte>0x02</byte>
                        <byte name="Cmd">0x64</byte>
                </header>
-               <byte name="LinkCode"></byte>
-               <byte name="ALLLinkGroup">0x00</byte>
+               <byte name="LinkCode"/>
+               <byte name="ALLLinkGroup"/>
                <byte name="ACK/NACK"/>
        </msg>
        <msg name="CancelALLLinking" length="2" direction="TO_MODEM">
                <byte name="RecordFlags"/>
                <byte name="ALLLinkGroup"/>
                <address name="LinkAddr"/>
-               <byte name="LinkData1">0x00</byte>
-               <byte name="LinkData2">0x00</byte>
-               <byte name="LinkData3">0x00</byte>
+               <byte name="LinkData1"/>
+               <byte name="LinkData2"/>
+               <byte name="LinkData3"/>
        </msg>
        <msg name="WriteDatabaseRecordReply" length="13" direction="FROM_MODEM">
                <header length="2">
                        <byte>0x02</byte>
                        <byte name="Cmd">0x79</byte>
                </header>
-               <byte name="LinkData1">0x00</byte>
-               <byte name="LinkData2">0x00</byte>
-               <byte name="LinkData3">0x00</byte>
+               <byte name="LinkData1"/>
+               <byte name="LinkData2"/>
+               <byte name="LinkData3"/>
        </msg>
        <msg name="SetNextLinkDataReply" length="6" direction="FROM_MODEM">
                <header length="2">