]> git.basschouten.com Git - openhab-addons.git/commitdiff
[dsmr] Improved error handling corrupt messages, discovery additional key bug fix...
authorHilbrand Bouwkamp <hilbrand@h72.nl>
Mon, 27 Feb 2023 16:36:02 +0000 (17:36 +0100)
committerGitHub <noreply@github.com>
Mon, 27 Feb 2023 16:36:02 +0000 (17:36 +0100)
* [dsmr] Improved error handling to better handle corrupt messages.

- Fix incorrect additional key in bridge discovery service.
- When corrupted P1 telegram is received don't set directly to offline, but let the watchdog do this if to many times this happens.
This makes it a bit more lenient for receiving a bad telegram once in a while. Because some connections are not that stable.
- Simplified error handling, and listeners to one enum/interface to make it more cleaner.

Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
31 files changed:
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRBindingConstants.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceConfiguration.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceRunnable.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMREventListener.java [deleted file]
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRFixedConfigDevice.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDevice.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRTelegramListener.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypter.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRBaseConnector.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorErrorEvent.java [deleted file]
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorListener.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRErrorStatus.java [new file with mode: 0644]
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRSerialConnector.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1Telegram.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramListener.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRBridgeDiscoveryService.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java
bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java
bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/i18n/dsmr.properties
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDeviceTest.java
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypterTest.java
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryServiceTest.java
bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java

index 0a72355be9aad252fa01b9ffc7ac18e13bb56cbd..e60a9fa46df5b9de83d8decb1c5ce200db951f73 100644 (file)
@@ -47,7 +47,7 @@ public final class DSMRBindingConstants {
     public static final String CONFIGURATION_DECRYPTION_KEY = "decryptionKey";
     public static final String CONFIGURATION_DECRYPTION_KEY_EMPTY = "";
     public static final String CONFIGURATION_ADDITIONAL_KEY = "additionalKey";
-    public static final String ADDITIONAL_KEY_DEFAULT = "3000112233445566778899AABBCCDDEEFF";
+    public static final String CONFIGURATION_ADDITIONAL_KEY_DEFAULT = "3000112233445566778899AABBCCDDEEFF";
 
     private DSMRBindingConstants() {
         // Constants class
index 2462a87282f3a83cfacf89cadf85e9a62f00f88d..12edfad94ede234e7f97cc285471b9c5412bb15c 100644 (file)
@@ -55,7 +55,7 @@ public class DSMRDeviceConfiguration {
     /**
      * Austria smart meter additional decryption key
      */
-    public String additionalKey = DSMRBindingConstants.ADDITIONAL_KEY_DEFAULT;
+    public String additionalKey = DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY_DEFAULT;
 
     /**
      * When no message was received after the configured number of seconds action will be taken.
index 979ff023763949208019ef893394db125045565e..438b3062c400140b4a5a06a68d38b32d68073c5b 100644 (file)
  */
 package org.openhab.binding.dsmr.internal.device;
 
+import java.util.Optional;
 import java.util.concurrent.Semaphore;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
+import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,7 +34,7 @@ public class DSMRDeviceRunnable implements Runnable {
     private final Logger logger = LoggerFactory.getLogger(DSMRDeviceRunnable.class);
     private final Semaphore semaphore = new Semaphore(0);
     private final DSMRDevice device;
-    private final DSMREventListener portEventListener;
+    private final P1TelegramListener portEventListener;
 
     /**
      * Keeps state of running. If false run will stop.
@@ -45,7 +47,7 @@ public class DSMRDeviceRunnable implements Runnable {
      * @param device the device to control
      * @param eventListener listener to used ot report errors.
      */
-    public DSMRDeviceRunnable(DSMRDevice device, DSMREventListener eventListener) {
+    public DSMRDeviceRunnable(final DSMRDevice device, final P1TelegramListener eventListener) {
         this.device = device;
         this.portEventListener = eventListener;
     }
@@ -83,10 +85,11 @@ public class DSMRDeviceRunnable implements Runnable {
                 }
             }
             logger.trace("Device shutdown");
-        } catch (RuntimeException e) {
+        } catch (final RuntimeException e) {
             logger.warn("DSMRDeviceRunnable stopped with a RuntimeException", e);
-            portEventListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
-        } catch (InterruptedException e) {
+            portEventListener.onError(DSMRErrorStatus.SERIAL_DATA_READ_ERROR,
+                    Optional.ofNullable(e.getMessage()).orElse(""));
+        } catch (final InterruptedException e) {
             Thread.currentThread().interrupt();
         } finally {
             device.stop();
diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMREventListener.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMREventListener.java
deleted file mode 100644 (file)
index 6e93b37..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Copyright (c) 2010-2023 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.dsmr.internal.device;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
-
-/**
- * Interface for classes handling DSMR connector events.
- *
- * @author M. Volaart - Initial contribution
- * @author Hilbrand Bouwkamp - renamed classes/methods
- */
-@NonNullByDefault
-public interface DSMREventListener {
-    /**
-     * Callback for DSMRPortEvent events
-     *
-     * @param connectorErrorEvent {@link DSMRConnectorErrorEvent} that has occurred
-     */
-    public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent);
-
-    /**
-     * Callback for received P1 telegrams
-     *
-     * @param telegram the received P1 telegram
-     */
-    public void handleTelegramReceived(P1Telegram telegram);
-}
index 099aecc0d5abd4feef1e2bea14c9af57c03073c3..429d3e3462e56f21cd14013d3b8929fb7163255a 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.dsmr.internal.device;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialConnector;
 import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
+import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
 import org.openhab.core.io.transport.serial.SerialPortManager;
 
 /**
@@ -36,14 +37,15 @@ public class DSMRFixedConfigDevice implements DSMRDevice {
      * @param serialPortManager the manager to get a new serial port connecting from
      * @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
      * @param fixedPortSettings The serial port connection settings
-     * @param listener the parent {@link DSMREventListener}
+     * @param listener the parent {@link P1TelegramListener}
      * @param telegramListener listener to report found telegrams or errors
      */
-    public DSMRFixedConfigDevice(SerialPortManager serialPortManager, String serialPortName,
-            DSMRSerialSettings fixedPortSettings, DSMREventListener listener, DSMRTelegramListener telegramListener) {
+    public DSMRFixedConfigDevice(final SerialPortManager serialPortManager, final String serialPortName,
+            final DSMRSerialSettings fixedPortSettings, final P1TelegramListener listener,
+            final DSMRTelegramListener telegramListener) {
         this.fixedPortSettings = fixedPortSettings;
         this.telegramListener = telegramListener;
-        telegramListener.setDsmrEventListener(listener);
+        telegramListener.setP1TelegramListener(listener);
 
         dsmrPort = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
     }
@@ -64,7 +66,7 @@ public class DSMRFixedConfigDevice implements DSMRDevice {
     }
 
     @Override
-    public void setLenientMode(boolean lenientMode) {
+    public void setLenientMode(final boolean lenientMode) {
         telegramListener.setLenientMode(lenientMode);
     }
 }
index efdd95c5964cbc873742e76f1c1989967c169bb2..cacfc1381fa73fb7d7a34d8eaf92f2a3fe04d8d6 100644 (file)
@@ -18,10 +18,11 @@ import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialConnector;
 import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
+import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
 import org.openhab.core.io.transport.serial.SerialPortManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -34,7 +35,7 @@ import org.slf4j.LoggerFactory;
  *         settings automatically.
  */
 @NonNullByDefault
-public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
+public class DSMRSerialAutoDevice implements DSMRDevice, P1TelegramListener {
 
     /**
      * Enum to keep track of the internal state of {@link DSMRSerialAutoDevice}.
@@ -113,7 +114,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
     /**
      * The listener of the class handling the connector events
      */
-    private DSMREventListener parentListener;
+    private final P1TelegramListener parentListener;
 
     /**
      * Time in nanos the last time the baudrate was switched. This is used during discovery ignore errors retrieved
@@ -126,20 +127,20 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
      *
      * @param serialPortManager the manager to get a new serial port connecting from
      * @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
-     * @param listener the parent {@link DSMREventListener}
+     * @param listener the parent {@link P1TelegramListener}
      * @param telegramListener listener to report found telegrams or errors
      * @param scheduler the scheduler to use with the baudrate switching timers
      * @param baudrateSwitchTimeoutSeconds timeout period for when to try other baudrate settings and end the discovery
      *            of the baudrate
      */
-    public DSMRSerialAutoDevice(SerialPortManager serialPortManager, String serialPortName, DSMREventListener listener,
-            DSMRTelegramListener telegramListener, ScheduledExecutorService scheduler,
-            int baudrateSwitchTimeoutSeconds) {
+    public DSMRSerialAutoDevice(final SerialPortManager serialPortManager, final String serialPortName,
+            final P1TelegramListener listener, final DSMRTelegramListener telegramListener,
+            final ScheduledExecutorService scheduler, final int baudrateSwitchTimeoutSeconds) {
         this.parentListener = listener;
         this.scheduler = scheduler;
         this.baudrateSwitchTimeoutSeconds = baudrateSwitchTimeoutSeconds;
         this.telegramListener = telegramListener;
-        telegramListener.setDsmrEventListener(listener);
+        telegramListener.setP1TelegramListener(listener);
         dsmrConnector = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
         logger.debug("Initialized port '{}'", serialPortName);
     }
@@ -148,7 +149,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
     public void start() {
         stopDiscover(DeviceState.DISCOVER_SETTINGS);
         portSettings = DEFAULT_PORT_SETTINGS;
-        telegramListener.setDsmrEventListener(this);
+        telegramListener.setP1TelegramListener(this);
         dsmrConnector.open(portSettings);
         restartHalfTimer();
         endTimeTimer = scheduler.schedule(this::endTimeScheduledCall,
@@ -178,30 +179,28 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
      * @param telegram the details of the received telegram
      */
     @Override
-    public void handleTelegramReceived(P1Telegram telegram) {
-        if (!telegram.getCosemObjects().isEmpty()) {
-            stopDiscover(DeviceState.NORMAL);
-            parentListener.handleTelegramReceived(telegram);
-            logger.info("Start receiving telegrams on port {} with settings: {}", dsmrConnector.getPortName(),
-                    portSettings);
-        }
+    public void telegramReceived(final P1Telegram telegram) {
+        stopDiscover(DeviceState.NORMAL);
+        parentListener.telegramReceived(telegram);
+        logger.info("Start receiving telegrams on port {} with settings: {}", dsmrConnector.getPortName(),
+                portSettings);
     }
 
     /**
      * Event handler for DSMR Port events.
      *
-     * @param portEvent {@link DSMRConnectorErrorEvent} to handle
+     * @param portEvent {@link DSMRErrorStatus} to handle
      */
     @Override
-    public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
+    public void onError(final DSMRErrorStatus portEvent, final String message) {
         logger.trace("Received portEvent {}", portEvent.getEventDetails());
-        if (portEvent == DSMRConnectorErrorEvent.READ_ERROR) {
+        if (portEvent == DSMRErrorStatus.SERIAL_DATA_READ_ERROR) {
             switchBaudrate();
         } else {
             logger.debug("Error during discovery of port settings: {}, current state:{}.", portEvent.getEventDetails(),
                     state);
             stopDiscover(DeviceState.ERROR);
-            parentListener.handleErrorEvent(portEvent);
+            parentListener.onError(portEvent, message);
         }
     }
 
@@ -209,7 +208,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
      * @param lenientMode the lenientMode to set
      */
     @Override
-    public void setLenientMode(boolean lenientMode) {
+    public void setLenientMode(final boolean lenientMode) {
         telegramListener.setLenientMode(lenientMode);
     }
 
@@ -248,7 +247,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
     private void endTimeScheduledCall() {
         if (state == DeviceState.DISCOVER_SETTINGS) {
             stopDiscover(DeviceState.ERROR);
-            parentListener.handleErrorEvent(DSMRConnectorErrorEvent.DONT_EXISTS);
+            parentListener.onError(DSMRErrorStatus.PORT_DONT_EXISTS, "");
         }
     }
 
@@ -257,8 +256,9 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
      *
      * @param state the state with which the process was stopped.
      */
-    private void stopDiscover(DeviceState state) {
-        telegramListener.setDsmrEventListener(parentListener);
+    private void stopDiscover(final DeviceState state) {
+        this.state = state;
+        telegramListener.setP1TelegramListener(parentListener);
         logger.debug("Stop discovery of port settings.");
         if (halfTimeTimer != null) {
             halfTimeTimer.cancel(true);
@@ -268,7 +268,6 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
             endTimeTimer.cancel(true);
             endTimeTimer = null;
         }
-        this.state = state;
     }
 
     /**
index e314b74a61361724a894c1e3372ee3f0b45cba08..16a3acc4816c7341ab8ff14d84ec6c3c67f257c3 100644 (file)
 package org.openhab.binding.dsmr.internal.device;
 
 import java.util.List;
-import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
 import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorListener;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
 import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
@@ -40,7 +38,7 @@ public class DSMRTelegramListener implements P1TelegramListener, DSMRConnectorLi
     private final Logger logger = LoggerFactory.getLogger(DSMRTelegramListener.class);
     private final TelegramParser parser;
 
-    private @NonNullByDefault({}) DSMREventListener dsmrEventListener;
+    private @NonNullByDefault({}) P1TelegramListener p1TelegramListener;
 
     /**
      * Constructor.
@@ -62,25 +60,29 @@ public class DSMRTelegramListener implements P1TelegramListener, DSMRConnectorLi
     }
 
     /**
-     * Set the DSMR event listener.
+     * Set the P1 Telegram listener.
      *
-     * @param eventListener the listener to set
+     * @param p1TelegramListener the listener to set
      */
-    public void setDsmrEventListener(final DSMREventListener eventListener) {
-        this.dsmrEventListener = eventListener;
+    public void setP1TelegramListener(final P1TelegramListener p1TelegramListener) {
+        this.p1TelegramListener = p1TelegramListener;
     }
 
+    // Handle calls from the Connector
+
     @Override
     public void handleData(final byte[] data, final int length) {
         parser.parse(data, length);
     }
 
     @Override
-    public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
-        dsmrEventListener.handleErrorEvent(portEvent);
+    public void handleError(final DSMRErrorStatus portEvent, final String message) {
+        onError(portEvent, message);
         parser.reset();
     }
 
+    // Handle calls from the Parser
+
     /**
      * Handler for cosemObjects received in a P1 telegram
      *
@@ -88,22 +90,23 @@ public class DSMRTelegramListener implements P1TelegramListener, DSMRConnectorLi
      */
     @Override
     public void telegramReceived(final P1Telegram telegram) {
-        final TelegramState telegramState = telegram.getTelegramState();
         final List<CosemObject> cosemObjects = telegram.getCosemObjects();
 
         if (logger.isTraceEnabled()) {
-            logger.trace("Received {} Cosem Objects with state: '{}'", cosemObjects.size(), telegramState);
+            logger.trace("Received {} Cosem Objects", cosemObjects.size());
         }
-        if (telegramState == TelegramState.OK || telegramState == TelegramState.INVALID_ENCRYPTION_KEY) {
-            dsmrEventListener.handleTelegramReceived(telegram);
+        if (cosemObjects.isEmpty()) {
+            onError(DSMRErrorStatus.TELEGRAM_NO_DATA, "");
         } else {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Telegram received with error state '{}': {}", telegramState,
-                        cosemObjects.stream().map(CosemObject::toString).collect(Collectors.joining(",")));
-            }
+            p1TelegramListener.telegramReceived(telegram);
         }
     }
 
+    @Override
+    public void onError(final DSMRErrorStatus state, final String message) {
+        p1TelegramListener.onError(state, message);
+    }
+
     /**
      * @param lenientMode the lenientMode to set
      */
index a750450fdff1b9b15efdee07c3a481169631f62a..bf7ea751e98238ed490bd717af47c36d930a5204 100644 (file)
@@ -16,7 +16,8 @@ import java.nio.ByteBuffer;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
-import java.util.Collections;
+import java.util.Arrays;
+import java.util.Optional;
 
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
@@ -28,8 +29,7 @@ import javax.crypto.spec.SecretKeySpec;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
 import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
 import org.openhab.core.util.HexUtils;
@@ -53,8 +53,7 @@ public class SmartyDecrypter implements TelegramParser {
         READ_SEPARATOR_30,
         READ_FRAME_COUNTER,
         READ_PAYLOAD,
-        READ_GCM_TAG,
-        DONE_READING_TELEGRAM
+        READ_GCM_TAG
     }
 
     private static final byte START_BYTE = (byte) 0xDB;
@@ -95,7 +94,7 @@ public class SmartyDecrypter implements TelegramParser {
         this.parser = parser;
         this.telegramListener = telegramListener;
         secretKeySpec = decryptionKey.isEmpty() ? null : new SecretKeySpec(HexUtils.hexToBytes(decryptionKey), "AES");
-        addKey = HexUtils.hexToBytes(additionalKey.isBlank() ? DSMRBindingConstants.ADDITIONAL_KEY_DEFAULT
+        addKey = HexUtils.hexToBytes(additionalKey.isBlank() ? DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY_DEFAULT
                 : ((additionalKey.length() == 32 ? (ADDITIONAL_ADD_PREFIX) : "") + additionalKey));
     }
 
@@ -113,6 +112,11 @@ public class SmartyDecrypter implements TelegramParser {
     }
 
     private boolean processStateActions(final byte rawInput) {
+        // Safeguard against buffer overrun in case corrupt data is received.
+        if (ivLength == IV_BUFFER_LENGTH) {
+            reset();
+            return false;
+        }
         switch (state) {
             case WAITING_FOR_START_BYTE:
                 if (rawInput == START_BYTE) {
@@ -179,26 +183,23 @@ public class SmartyDecrypter implements TelegramParser {
                 // All input has been read.
                 cipherText.put(rawInput);
                 if (currentBytePosition >= changeToNextStateAt) {
-                    state = State.DONE_READING_TELEGRAM;
+                    state = State.WAITING_FOR_START_BYTE;
+                    return true;
                 }
                 break;
         }
-        if (state == State.DONE_READING_TELEGRAM) {
-            state = State.WAITING_FOR_START_BYTE;
-            return true;
-        }
         return false;
     }
 
     private void processCompleted() {
-        final byte[] plainText = decrypt();
-
-        reset();
-        if (plainText == null) {
-            telegramListener
-                    .telegramReceived(new P1Telegram(Collections.emptyList(), TelegramState.INVALID_ENCRYPTION_KEY));
-        } else {
-            parser.parse(plainText, plainText.length);
+        try {
+            final byte[] plainText = decrypt();
+
+            if (plainText != null) {
+                parser.parse(plainText, plainText.length);
+            }
+        } finally {
+            reset();
         }
     }
 
@@ -218,7 +219,15 @@ public class SmartyDecrypter implements TelegramParser {
             }
         } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
-            logger.warn("Decrypting smarty telegram failed: ", e);
+            if (lenientMode || logger.isDebugEnabled()) {
+                // log in lenient mode or when debug is enabled. But log to warn to also work when lenientMode is
+                // enabled.
+                logger.warn("Failed encrypted telegram: {}",
+                        HexUtils.bytesToHex(Arrays.copyOf(cipherText.array(), cipherText.position())));
+                logger.warn("Exception of failed decryption of telegram: ", e);
+            }
+            telegramListener.onError(DSMRErrorStatus.INVALID_DECRYPTION_KEY,
+                    Optional.ofNullable(e.getMessage()).orElse(""));
         }
         return null;
     }
index 1eebc28fe14e01dca0cd7bd52eca81195c5343f8..7159ea3bff3cf3536f0c6d040146210c77e18859 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.dsmr.internal.device.connector;
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Optional;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -36,7 +37,7 @@ class DSMRBaseConnector {
     /**
      * Listener to send received data and errors to.
      */
-    protected final DSMRConnectorListener dsmrConnectorListener;
+    private final DSMRConnectorListener dsmrConnectorListener;
 
     /**
      * 1Kbyte buffer for storing received data.
@@ -53,7 +54,7 @@ class DSMRBaseConnector {
      */
     private boolean open;
 
-    public DSMRBaseConnector(DSMRConnectorListener connectorListener) {
+    public DSMRBaseConnector(final DSMRConnectorListener connectorListener) {
         this.dsmrConnectorListener = connectorListener;
     }
 
@@ -68,7 +69,7 @@ class DSMRBaseConnector {
      * @param inputStream input stream to read data from
      * @throws IOException throws exception in case input stream is null
      */
-    protected void open(@Nullable InputStream inputStream) throws IOException {
+    protected void open(@Nullable final InputStream inputStream) throws IOException {
         if (inputStream == null) {
             throw new IOException("Inputstream is null");
         }
@@ -91,7 +92,7 @@ class DSMRBaseConnector {
         if (inputStream != null) {
             try {
                 inputStream.close();
-            } catch (IOException ioe) {
+            } catch (final IOException ioe) {
                 logger.debug("Failed to close reader", ioe);
             }
         }
@@ -104,12 +105,12 @@ class DSMRBaseConnector {
     protected void handleDataAvailable() {
         try {
             synchronized (readLock) {
-                BufferedInputStream localInputStream = inputStream;
+                final BufferedInputStream localInputStream = inputStream;
 
                 if (localInputStream != null) {
                     int bytesAvailable = localInputStream.available();
                     while (bytesAvailable > 0) {
-                        int bytesAvailableRead = localInputStream.read(buffer, 0,
+                        final int bytesAvailableRead = localInputStream.read(buffer, 0,
                                 Math.min(bytesAvailable, buffer.length));
 
                         if (open && bytesAvailableRead > 0) {
@@ -122,8 +123,9 @@ class DSMRBaseConnector {
                     }
                 }
             }
-        } catch (IOException e) {
-            dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
+        } catch (final IOException e) {
+            dsmrConnectorListener.handleError(DSMRErrorStatus.SERIAL_DATA_READ_ERROR,
+                    Optional.ofNullable(e.getMessage()).orElse(""));
             logger.debug("Exception on read data", e);
         }
     }
diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorErrorEvent.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorErrorEvent.java
deleted file mode 100644 (file)
index 9d85330..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2010-2023 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.dsmr.internal.device.connector;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * Error events from a connector.
- *
- * @author M. Volaart - Initial contribution
- * @author Hilbrand Bouwkamp - Reduced number of event to only errors
- */
-@NonNullByDefault
-public enum DSMRConnectorErrorEvent {
-    DONT_EXISTS,
-    IN_USE,
-    INTERNAL_ERROR,
-    NOT_COMPATIBLE,
-    READ_ERROR;
-
-    /**
-     * @return the event details
-     */
-    public String getEventDetails() {
-        return "@text/error.connector." + name().toLowerCase();
-    }
-}
index 219da50c0bf1685126b397cd6a144968a4a48983..5f71bf63d4bd380eb26e98244161fabcf00add58 100644 (file)
@@ -23,11 +23,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 public interface DSMRConnectorListener {
 
     /**
-     * Callback for {@link DSMRConnectorErrorEvent} events.
+     * Callback for {@link DSMRErrorStatus} events.
      *
-     * @param portEvent {@link DSMRConnectorErrorEvent} that has occurred
+     * @param errorStatus {@link DSMRErrorStatus} that has occurred
+     * @param message Additional error message
      */
-    public void handleErrorEvent(DSMRConnectorErrorEvent portEvent);
+    void handleError(DSMRErrorStatus errorStatus, String message);
 
     /**
      * Handle data.
diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRErrorStatus.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRErrorStatus.java
new file mode 100644 (file)
index 0000000..5c1c606
--- /dev/null
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2010-2023 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.dsmr.internal.device.connector;
+
+import java.util.Locale;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Error events from a connector.
+ *
+ * @author M. Volaart - Initial contribution
+ * @author Hilbrand Bouwkamp - Refactored all error into one enum
+ */
+@NonNullByDefault
+public enum DSMRErrorStatus {
+    /**
+     * The smarty telegram was successfully received but could not be decoded because of an invalid decryption key.
+     */
+    INVALID_DECRYPTION_KEY(false),
+    /**
+     * Serial port could not be found.
+     */
+    PORT_DONT_EXISTS(true),
+    /**
+     * Serial port is already in use by another application.
+     */
+    PORT_IN_USE(true),
+    /**
+     * Internal error in the serial port communication.
+     */
+    PORT_INTERNAL_ERROR(true),
+    /**
+     * Serial port doesn't support the configured settings.
+     */
+    PORT_NOT_COMPATIBLE(true),
+    /**
+     * Reading data from the serial port failed.
+     */
+    SERIAL_DATA_READ_ERROR(false),
+    /**
+     * The telegram CRC16 checksum failed (only DSMR V4 and up).
+     */
+    TELEGRAM_CRC_ERROR(false),
+    /**
+     * The P1 telegram has syntax errors.
+     */
+    TELEGRAM_DATA_CORRUPTION(false),
+    /**
+     * Received telegram data, but after parsing no data is present. Possibly all data corrupted.
+     */
+    TELEGRAM_NO_DATA(false);
+
+    private final boolean fatal;
+
+    private DSMRErrorStatus(final boolean fatal) {
+        this.fatal = fatal;
+    }
+
+    /**
+     * @return Returns true if this error is not something possible temporary, but something that can't be recovered
+     *         from.
+     */
+    public boolean isFatal() {
+        return fatal;
+    }
+
+    /**
+     * @return the event details
+     */
+    public String getEventDetails() {
+        return "@text/addon.dsmr.error.status." + name().toLowerCase(Locale.ROOT);
+    }
+}
index dd670ded2b981161214afbfdf198e78fbdb1f600..93f7124b8d742725d96532f070cafaf675610ec5 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.binding.dsmr.internal.device.connector;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Optional;
 import java.util.TooManyListenersException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -67,7 +68,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
     /**
      * Serial port instance.
      */
-    private AtomicReference<@Nullable SerialPort> serialPortReference = new AtomicReference<>();
+    private final AtomicReference<@Nullable SerialPort> serialPortReference = new AtomicReference<>();
 
     /**
      * DSMR Connector listener.
@@ -89,8 +90,8 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
      * @param serialPortName Device identifier of the port (e.g. /dev/ttyUSB0)
      * @param dsmrConnectorListener The listener to send error or received data from the port
      */
-    public DSMRSerialConnector(SerialPortManager portManager, String serialPortName,
-            DSMRConnectorListener dsmrConnectorListener) {
+    public DSMRSerialConnector(final SerialPortManager portManager, final String serialPortName,
+            final DSMRConnectorListener dsmrConnectorListener) {
         super(dsmrConnectorListener);
         this.portManager = portManager;
         this.serialPortName = serialPortName;
@@ -106,35 +107,35 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
      *
      * @param portSettings The serial port settings to open the port with
      */
-    public void open(DSMRSerialSettings portSettings) {
-        DSMRConnectorErrorEvent errorEvent = null;
+    public void open(final DSMRSerialSettings portSettings) {
+        DSMRErrorStatus errorStatus = null;
 
         synchronized (portLock) {
-            SerialPortIdentifier portIdentifier = portManager.getIdentifier(serialPortName);
+            final SerialPortIdentifier portIdentifier = portManager.getIdentifier(serialPortName);
             if (portIdentifier == null) {
                 logger.debug("Port {} does not exists", serialPortName);
 
-                errorEvent = DSMRConnectorErrorEvent.DONT_EXISTS;
+                errorStatus = DSMRErrorStatus.PORT_DONT_EXISTS;
             } else {
-                errorEvent = open(portSettings, portIdentifier);
+                errorStatus = open(portSettings, portIdentifier);
             }
-            if (errorEvent != null) {
+            if (errorStatus != null) {
                 // handle event within lock
-                dsmrConnectorListener.handleErrorEvent(errorEvent);
+                dsmrConnectorListener.handleError(errorStatus, "");
             }
         }
     }
 
-    private @Nullable DSMRConnectorErrorEvent open(DSMRSerialSettings portSettings,
-            SerialPortIdentifier portIdentifier) {
-        DSMRConnectorErrorEvent errorEvent = null;
+    private @Nullable DSMRErrorStatus open(final DSMRSerialSettings portSettings,
+            final SerialPortIdentifier portIdentifier) {
+        DSMRErrorStatus errorStatus = null;
 
         try {
             logger.trace("Opening port {}", serialPortName);
-            SerialPort oldSerialPort = serialPortReference.get();
+            final SerialPort oldSerialPort = serialPortReference.get();
 
             // Opening Operating System Serial Port
-            SerialPort serialPort = portIdentifier.open(DSMRBindingConstants.DSMR_PORT_NAME,
+            final SerialPort serialPort = portIdentifier.open(DSMRBindingConstants.DSMR_PORT_NAME,
                     SERIAL_PORT_READ_TIMEOUT_MILLISECONDS);
 
             // Configure Serial Port based on specified port speed
@@ -157,39 +158,39 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
 
             try {
                 serialPort.enableReceiveThreshold(SERIAL_TIMEOUT_MILLISECONDS);
-            } catch (UnsupportedCommOperationException e) {
+            } catch (final UnsupportedCommOperationException e) {
                 logger.debug("Enable receive threshold is unsupported");
             }
             try {
                 serialPort.enableReceiveTimeout(SERIAL_TIMEOUT_MILLISECONDS);
-            } catch (UnsupportedCommOperationException e) {
+            } catch (final UnsupportedCommOperationException e) {
                 logger.debug("Enable receive timeout is unsupported");
             }
             // The binding is ready, let the meter know we want to receive values
             serialPort.setRTS(true);
             if (!serialPortReference.compareAndSet(oldSerialPort, serialPort)) {
                 logger.warn("Possible bug because a new serial port value was set during opening new port.");
-                errorEvent = DSMRConnectorErrorEvent.INTERNAL_ERROR;
+                errorStatus = DSMRErrorStatus.PORT_INTERNAL_ERROR;
             }
-        } catch (IOException ioe) {
+        } catch (final IOException ioe) {
             logger.debug("Failed to get inputstream for serialPort", ioe);
 
-            errorEvent = DSMRConnectorErrorEvent.READ_ERROR;
-        } catch (TooManyListenersException tmle) {
+            errorStatus = DSMRErrorStatus.SERIAL_DATA_READ_ERROR;
+        } catch (final TooManyListenersException tmle) {
             logger.warn("Possible bug because a listener was added while one already set.", tmle);
 
-            errorEvent = DSMRConnectorErrorEvent.INTERNAL_ERROR;
-        } catch (PortInUseException piue) {
+            errorStatus = DSMRErrorStatus.PORT_INTERNAL_ERROR;
+        } catch (final PortInUseException piue) {
             logger.debug("Port already in use: {}", serialPortName, piue);
 
-            errorEvent = DSMRConnectorErrorEvent.IN_USE;
-        } catch (UnsupportedCommOperationException ucoe) {
+            errorStatus = DSMRErrorStatus.PORT_IN_USE;
+        } catch (final UnsupportedCommOperationException ucoe) {
             logger.debug("Port does not support requested port settings (invalid dsmr:portsettings parameter?): {}",
                     serialPortName, ucoe);
 
-            errorEvent = DSMRConnectorErrorEvent.NOT_COMPATIBLE;
+            errorStatus = DSMRErrorStatus.PORT_NOT_COMPATIBLE;
         }
-        return errorEvent;
+        return errorStatus;
     }
 
     /**
@@ -208,12 +209,12 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
                 serialPort.setRTS(false);
                 serialPort.removeEventListener();
                 try {
-                    InputStream inputStream = serialPort.getInputStream();
+                    final InputStream inputStream = serialPort.getInputStream();
 
                     if (inputStream != null) {
                         inputStream.close();
                     }
-                } catch (IOException ioe) {
+                } catch (final IOException ioe) {
                     logger.debug("Failed to close serial port inputstream", ioe);
                 }
                 serialPort.close();
@@ -228,22 +229,23 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
      *
      * @param portSettings the port settings to set on the serial port
      */
-    public void setSerialPortParams(DSMRSerialSettings portSettings) {
+    public void setSerialPortParams(final DSMRSerialSettings portSettings) {
         synchronized (portLock) {
             if (isOpen()) {
                 logger.debug("Update port {} with settings: {}", this.serialPortName, portSettings);
                 try {
-                    SerialPort serialPort = serialPortReference.get();
+                    final SerialPort serialPort = serialPortReference.get();
 
                     if (serialPort != null) {
                         serialPort.setSerialPortParams(portSettings.getBaudrate(), portSettings.getDataBits(),
                                 portSettings.getStopbits(), portSettings.getParity());
                     }
-                } catch (UnsupportedCommOperationException e) {
+                } catch (final UnsupportedCommOperationException e) {
                     logger.debug(
                             "Port does {} not support requested port settings (invalid dsmr:portsettings parameter?): {}",
                             serialPortName, portSettings);
-                    dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.NOT_COMPATIBLE);
+                    dsmrConnectorListener.handleError(DSMRErrorStatus.PORT_NOT_COMPATIBLE,
+                            Optional.ofNullable(e.getMessage()).orElse(""));
                 }
             } else {
                 restart(portSettings);
@@ -254,7 +256,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
     /**
      * Switch the Serial Port speed (LOW --> HIGH and vice versa).
      */
-    public void restart(DSMRSerialSettings portSettings) {
+    public void restart(final DSMRSerialSettings portSettings) {
         synchronized (portLock) {
             logger.trace("Restart port {} with settings: {}", this.serialPortName, portSettings);
             close();
@@ -263,7 +265,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
     }
 
     @Override
-    public void serialEvent(@Nullable SerialPortEvent seEvent) {
+    public void serialEvent(@Nullable final SerialPortEvent seEvent) {
         if (seEvent == null) {
             return;
         }
@@ -289,7 +291,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
                     break;
                 default: // do nothing
             }
-        } catch (RuntimeException e) {
+        } catch (final RuntimeException e) {
             logger.warn("RuntimeException during handling serial event: {}", seEvent.getEventType(), e);
         }
     }
@@ -300,10 +302,10 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
      * @param typeName type of the event, used in logging only
      * @param portEvent Serial port event that triggered the error.
      */
-    private void handleErrorEvent(String typeName, SerialPortEvent portEvent) {
+    private void handleErrorEvent(final String typeName, final SerialPortEvent portEvent) {
         if (isOpen() && portEvent.getNewValue()) {
             logger.trace("New DSMR port {} event", typeName);
-            dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
+            dsmrConnectorListener.handleError(DSMRErrorStatus.SERIAL_DATA_READ_ERROR, "");
         }
     }
 
index 5d9394a9930e39c0010e994001eedf2df2e95d45..8c7c844351a831ba89719a0f3998bb2ee0542ccc 100644 (file)
@@ -38,6 +38,11 @@ public class OBISIdentifier {
      */
     private static final Pattern OBIS_ID_PATTERN = Pattern.compile(OBISID_REGEX);
 
+    /**
+     * Value to return when an invalid int was read.
+     */
+    private static final int INVALID_INT_READ = -1;
+
     /* the six individual group values of the OBIS ID */
     private final int groupA;
     private final @Nullable Integer channel;
@@ -91,25 +96,41 @@ public class OBISIdentifier {
 
         if (m.matches()) {
             // Optional value A
-            this.groupA = m.group(2) == null ? null : Integer.parseInt(m.group(2));
+            this.groupA = safeInt(m.group(2));
 
             // Optional value B
-            this.channel = m.group(4) == null ? null : Integer.valueOf(m.group(4));
+            this.channel = safeInteger(m.group(4));
 
             // Required value C & D
-            this.groupC = Integer.parseInt(m.group(6));
-            this.groupD = Integer.parseInt(m.group(7));
+            this.groupC = safeInt(m.group(6));
+            this.groupD = safeInt(m.group(7));
 
             // Optional value E
-            this.groupE = m.group(9) == null ? null : Integer.valueOf(m.group(9));
+            this.groupE = safeInteger(m.group(9));
 
             // Optional value F
-            this.groupF = m.group(11) == null ? null : Integer.valueOf(m.group(11));
+            this.groupF = safeInteger(m.group(11));
         } else {
             throw new ParseException("Invalid OBIS identifier:" + obisIDString, 0);
         }
     }
 
+    private static int safeInt(final @Nullable String value) {
+        try {
+            return value == null ? INVALID_INT_READ : Integer.parseInt(value);
+        } catch (final NumberFormatException e) {
+            return INVALID_INT_READ;
+        }
+    }
+
+    private static @Nullable Integer safeInteger(final @Nullable String value) {
+        try {
+            return value == null ? null : Integer.valueOf(value);
+        } catch (final NumberFormatException e) {
+            return null;
+        }
+    }
+
     public boolean isConflict() {
         return conflict;
     }
index b0dc9fbc7cbd0d918ab8edd9d477eff39333e1e1..c9d13a66d2fb82481fb5acbc3168d9cfa2e838a2 100644 (file)
@@ -31,54 +31,18 @@ public class P1Telegram {
     /**
      * The TelegramState described the meta data of the P1Telegram
      */
-    public enum TelegramState {
-        /**
-         * OK. Telegram was successful received and CRC16 checksum is verified (CRC16 only for DSMR V4 and up)
-         */
-        OK("P1 telegram received OK"),
-        /**
-         * CRC_ERROR. CRC16 checksum failed (only DSMR V4 and up)
-         */
-        CRC_ERROR("CRC checksum failed for received P1 telegram"),
-        /**
-         * DATA_CORRUPTION. The P1 telegram has syntax errors.
-         */
-        DATA_CORRUPTION("Received P1 telegram is corrupted"),
-        /**
-         * P1TelegramListener. The smarty telegram was successful received but could not be decoded because of an
-         * invalid
-         * encryption key.
-         */
-        INVALID_ENCRYPTION_KEY("Failed to decrypt P1 telegram due to invalid encryption key");
-
-        /**
-         * public accessible state details
-         */
-        public final String stateDetails;
-
-        /**
-         * Constructs a new TelegramState enum
-         *
-         * @param stateDetails String containing the details of this TelegramState
-         */
-        private TelegramState(String stateDetails) {
-            this.stateDetails = stateDetails;
-        }
-    }
 
     private final List<CosemObject> cosemObjects;
-    private final TelegramState telegramState;
     private final String rawTelegram;
     private final List<Entry<String, String>> unknownCosemObjects;
 
-    public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState) {
-        this(cosemObjects, telegramState, "", Collections.emptyList());
+    public P1Telegram(final List<CosemObject> cosemObjects) {
+        this(cosemObjects, "", Collections.emptyList());
     }
 
-    public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState, String rawTelegram,
-            List<Entry<String, String>> unknownCosemObjects) {
+    public P1Telegram(final List<CosemObject> cosemObjects, final String rawTelegram,
+            final List<Entry<String, String>> unknownCosemObjects) {
         this.cosemObjects = cosemObjects;
-        this.telegramState = telegramState;
         this.rawTelegram = rawTelegram;
         this.unknownCosemObjects = unknownCosemObjects;
     }
@@ -97,13 +61,6 @@ public class P1Telegram {
         return rawTelegram;
     }
 
-    /**
-     * @return The state of the telegram
-     */
-    public TelegramState getTelegramState() {
-        return telegramState;
-    }
-
     /**
      * @return The list of CosemObject found in the telegram but not known to the binding
      */
index 2b31facd35c59a8c363af2ea70b11c19ed410f44..6b9306d2f6cb3428cb119a52fc95d5ebac0fa8bf 100644 (file)
@@ -13,6 +13,7 @@
 package org.openhab.binding.dsmr.internal.device.p1telegram;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 
 /**
  * Interface for receiving CosemObjects that come from a P1 Telegram
@@ -23,10 +24,18 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 @NonNullByDefault
 public interface P1TelegramListener {
 
+    /**
+     * Called when reading the telegram failed. Passes the failed state and optional an additional error message.
+     *
+     * @param errorStatus error state
+     * @param message optional additional message
+     */
+    void onError(DSMRErrorStatus errorStatus, String message);
+
     /**
      * Callback on received telegram.
      *
      * @param telegram The received telegram
      */
-    public void telegramReceived(P1Telegram telegram);
+    void telegramReceived(P1Telegram telegram);
 }
index c8e040e9809c20d339cb60078959f59d19345c2b..e7c17071c96deb13c4ceeef92dfb518935b59b47 100644 (file)
@@ -18,12 +18,13 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.regex.Pattern;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
 import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectFactory;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -106,7 +107,7 @@ public class P1TelegramParser implements TelegramParser {
     /**
      * Current telegram state
      */
-    private volatile TelegramState telegramState;
+    private volatile Optional<DSMRErrorStatus> telegramState = Optional.empty();
 
     /**
      * CosemObjectFactory helper class
@@ -116,7 +117,7 @@ public class P1TelegramParser implements TelegramParser {
     /**
      * Received Cosem Objects in the P1Telegram that is currently received
      */
-    private final List<CosemObject> cosemObjects = new ArrayList<>();
+    private final List<Entry<String, String>> cosemObjects = new ArrayList<>();
 
     /**
      * List of Cosem Object values that are not known to this binding.
@@ -138,18 +139,18 @@ public class P1TelegramParser implements TelegramParser {
      *
      * @param telegramListener
      */
-    public P1TelegramParser(P1TelegramListener telegramListener) {
+    public P1TelegramParser(final P1TelegramListener telegramListener) {
         this(telegramListener, false);
     }
 
-    public P1TelegramParser(P1TelegramListener telegramListener, boolean test) {
+    public P1TelegramParser(final P1TelegramListener telegramListener, final boolean test) {
         this.telegramListener = telegramListener;
         this.test = test;
 
         factory = new CosemObjectFactory();
         state = State.WAIT_FOR_START;
         crc = new CRC16(CRC16.Polynom.CRC16_IBM);
-        telegramState = TelegramState.OK;
+        telegramState = Optional.empty();
     }
 
     /**
@@ -159,7 +160,7 @@ public class P1TelegramParser implements TelegramParser {
      * @param length number of bytes to parse
      */
     @Override
-    public void parse(byte[] data, int length) {
+    public void parse(final byte[] data, final int length) {
         if (lenientMode || logger.isTraceEnabled()) {
             final String rawBlock = new String(data, 0, length, StandardCharsets.UTF_8);
 
@@ -254,10 +255,11 @@ public class P1TelegramParser implements TelegramParser {
                     if (c == '\r' || c == '/') {
                         logger.trace("telegramState {}, crcValue to check 0x{}", telegramState, crcValue);
                         // Only perform CRC check if telegram is still ok
-                        if (telegramState == TelegramState.OK && crcValue.length() > 0) {
-                            telegramState = checkCRC(telegramState);
+
+                        if (telegramState.isEmpty() && crcValue.length() > 0) {
+                            telegramState = checkCRC();
                         }
-                        telegramListener.telegramReceived(constructTelegram());
+                        processTelegram();
                         reset();
                         if (c == '/') {
                             /*
@@ -275,8 +277,8 @@ public class P1TelegramParser implements TelegramParser {
         logger.trace("State after parsing: {}", state);
     }
 
-    private TelegramState checkCRC(TelegramState currentState) {
-        final TelegramState telegramState;
+    private Optional<DSMRErrorStatus> checkCRC() {
+        final Optional<DSMRErrorStatus> telegramState;
 
         if (Pattern.matches(CRC_PATTERN, crcValue)) {
             final int crcP1Telegram = Integer.parseInt(crcValue.toString(), 16);
@@ -293,24 +295,45 @@ public class P1TelegramParser implements TelegramParser {
                 }
                 logger.trace("CRC value does not match, p1 Telegram failed");
 
-                telegramState = TelegramState.CRC_ERROR;
+                telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_CRC_ERROR);
             } else {
-                telegramState = currentState;
+                telegramState = Optional.empty();
             }
         } else {
-            telegramState = TelegramState.CRC_ERROR;
+            telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_CRC_ERROR);
         }
         return telegramState;
     }
 
+    private void processTelegram() {
+        telegramState.ifPresentOrElse(error -> telegramListener.onError(error, ""),
+                () -> telegramListener.telegramReceived(constructTelegram()));
+    }
+
     private P1Telegram constructTelegram() {
-        final List<CosemObject> cosemObjectsCopy = new ArrayList<>(cosemObjects);
+        final List<CosemObject> cosemObjectsCopy = new ArrayList<>();
 
+        cosemObjects.stream().forEach(e -> addCosemObject(cosemObjectsCopy, e));
         if (lenientMode) {
-            return new P1Telegram(cosemObjectsCopy, telegramState, rawData.toString(),
+            return new P1Telegram(cosemObjectsCopy, rawData.toString(),
                     unknownCosemObjects.isEmpty() ? Collections.emptyList() : new ArrayList<>(unknownCosemObjects));
         } else {
-            return new P1Telegram(cosemObjectsCopy, telegramState);
+            return new P1Telegram(cosemObjectsCopy);
+        }
+    }
+
+    private void addCosemObject(final List<CosemObject> objects, final Entry<String, String> cosemEntry) {
+        final String obisIdString = cosemEntry.getKey();
+        final String obisValueString = cosemEntry.getValue();
+        final CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);
+
+        if (cosemObject == null) {
+            if (lenientMode) {
+                unknownCosemObjects.add(new SimpleEntry<>(obisIdString, obisValueString));
+            }
+        } else {
+            logger.trace("Adding {} to list of Cosem Objects", cosemObject);
+            objects.add(cosemObject);
         }
     }
 
@@ -324,10 +347,11 @@ public class P1TelegramParser implements TelegramParser {
      *
      * @param c the unexpected character
      */
-    private void handleUnexpectedCharacter(char c) {
+    private void handleUnexpectedCharacter(final char c) {
         logger.debug("Unexpected character '{}' in state: {}. This P1 telegram is marked as failed", c, state);
 
-        telegramState = TelegramState.DATA_CORRUPTION;
+        telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_DATA_CORRUPTION);
+        telegramListener.onError(DSMRErrorStatus.TELEGRAM_DATA_CORRUPTION, "");
     }
 
     /**
@@ -335,7 +359,7 @@ public class P1TelegramParser implements TelegramParser {
      *
      * @param c the character to process
      */
-    private void handleCharacter(char c) {
+    private void handleCharacter(final char c) {
         switch (state) {
             case WAIT_FOR_START:
                 // ignore the data
@@ -401,17 +425,7 @@ public class P1TelegramParser implements TelegramParser {
         final String obisIdString = obisId.toString();
 
         if (!obisIdString.isEmpty()) {
-            final String obisValueString = obisValue.toString();
-            final CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);
-
-            if (cosemObject == null) {
-                if (lenientMode) {
-                    unknownCosemObjects.add(new SimpleEntry<>(obisIdString, obisValueString));
-                }
-            } else {
-                logger.trace("Adding {} to list of Cosem Objects", cosemObject);
-                cosemObjects.add(cosemObject);
-            }
+            cosemObjects.add(new SimpleEntry<String, String>(obisIdString, obisValue.toString()));
         }
         clearObisData();
     }
@@ -419,7 +433,7 @@ public class P1TelegramParser implements TelegramParser {
     /**
      * @param newState the new state to set
      */
-    private void setState(State newState) {
+    private void setState(final State newState) {
         synchronized (state) {
             switch (newState) {
                 case HEADER:
@@ -429,7 +443,7 @@ public class P1TelegramParser implements TelegramParser {
                 case WAIT_FOR_START:
                     // Clears internal state data and mark current telegram as OK
                     clearInternalData();
-                    telegramState = TelegramState.OK;
+                    telegramState = Optional.empty();
                     break;
                 case DATA_OBIS_ID:
                     // If the current state is CRLF we are processing the header and don't have a cosem object yet
@@ -448,7 +462,7 @@ public class P1TelegramParser implements TelegramParser {
     }
 
     @Override
-    public void setLenientMode(boolean lenientMode) {
+    public void setLenientMode(final boolean lenientMode) {
         this.lenientMode = lenientMode;
     }
 }
index 25674aa095f06096a2e396080454b9a85b5ecacb..abc422d94be229d83985d67669f0083bd67e34ef 100644 (file)
@@ -13,6 +13,7 @@
 package org.openhab.binding.dsmr.internal.discovery;
 
 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY;
+import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY_DEFAULT;
 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY;
 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY_EMPTY;
 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_SERIAL_PORT;
@@ -28,13 +29,12 @@ import java.util.stream.Stream;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
-import org.openhab.binding.dsmr.internal.device.DSMREventListener;
 import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
 import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
-import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
+import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
 import org.openhab.core.config.discovery.DiscoveryService;
@@ -69,7 +69,7 @@ import org.slf4j.LoggerFactory;
  */
 @NonNullByDefault
 @Component(service = DiscoveryService.class, configurationPid = "discovery.dsmr")
-public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements DSMREventListener {
+public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements P1TelegramListener {
 
     /**
      * The timeout used to switch baudrate if no valid data is received within that time frame.
@@ -133,7 +133,7 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
                     logger.debug("Start discovery on serial port: {}", currentScannedPortName);
                     //
                     final DSMRTelegramListener telegramListener = new DSMRTelegramListener("",
-                            CONFIGURATION_ADDITIONAL_KEY);
+                            CONFIGURATION_ADDITIONAL_KEY_DEFAULT);
                     final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager,
                             portIdentifier.getName(), this, telegramListener, scheduler,
                             BAUDRATE_SWITCH_TIMEOUT_SECONDS);
@@ -173,20 +173,25 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
      * @param telegram the received telegram
      */
     @Override
-    public void handleTelegramReceived(final P1Telegram telegram) {
+    public void telegramReceived(final P1Telegram telegram) {
         final List<CosemObject> cosemObjects = telegram.getCosemObjects();
 
         if (logger.isDebugEnabled()) {
             logger.debug("[{}] Received {} cosemObjects", currentScannedPortName, cosemObjects.size());
         }
-        if (telegram.getTelegramState() == TelegramState.INVALID_ENCRYPTION_KEY) {
+        final ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
+        meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
+        stopSerialPortScan();
+    }
+
+    @Override
+    public void onError(final DSMRErrorStatus portEvent, final String message) {
+        if (portEvent == DSMRErrorStatus.INVALID_DECRYPTION_KEY) {
             bridgeDiscovered(THING_TYPE_SMARTY_BRIDGE);
-            stopSerialPortScan();
-        } else if (!cosemObjects.isEmpty()) {
-            final ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
-            meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
-            stopSerialPortScan();
+        } else {
+            logger.debug("[{}] Error on port during discovery: {} - {}", currentScannedPortName, portEvent, message);
         }
+        stopSerialPortScan();
     }
 
     /**
@@ -205,7 +210,7 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
         properties.put(CONFIGURATION_SERIAL_PORT, currentScannedPortName);
         if (smarty) {
             properties.put(CONFIGURATION_DECRYPTION_KEY, CONFIGURATION_DECRYPTION_KEY_EMPTY);
-            properties.put(CONFIGURATION_ADDITIONAL_KEY, CONFIGURATION_ADDITIONAL_KEY);
+            properties.put(CONFIGURATION_ADDITIONAL_KEY, CONFIGURATION_ADDITIONAL_KEY_DEFAULT);
         }
         final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
                 .withThingType(bridgeThingTypeUID).withProperties(properties).withLabel(label).build();
@@ -215,10 +220,4 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
         thingDiscovered(discoveryResult);
         return thingUID;
     }
-
-    @Override
-    public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
-        logger.debug("[{}] Error on port during discovery: {}", currentScannedPortName, portEvent);
-        stopSerialPortScan();
-    }
 }
index 82f5c45bc67141a0f8e9358f89eb0492554aa06d..7717699c0d9e67f53277beaf5f0833311b0ecb7b 100644 (file)
@@ -21,6 +21,7 @@ import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
 import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
@@ -74,7 +75,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
     }
 
     @Override
-    public void setThingHandler(ThingHandler handler) {
+    public void setThingHandler(final ThingHandler handler) {
         if (handler instanceof DSMRBridgeHandler) {
             dsmrBridgeHandler = (DSMRBridgeHandler) handler;
         }
@@ -96,7 +97,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
     }
 
     @Override
-    public void telegramReceived(P1Telegram telegram) {
+    public void telegramReceived(final P1Telegram telegram) {
         if (logger.isDebugEnabled()) {
             logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
         }
@@ -108,7 +109,12 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
         detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID()));
     }
 
-    protected void verifyUnregisteredCosemObjects(P1Telegram telegram, List<CosemObject> list) {
+    @Override
+    public void onError(final DSMRErrorStatus state, final String message) {
+        logger.info("Telegram could not be parsed correctly, failed with state: {}, {}", state, message);
+    }
+
+    protected void verifyUnregisteredCosemObjects(final P1Telegram telegram, final List<CosemObject> list) {
         if (!list.isEmpty()) {
             if (list.stream()
                     .anyMatch(e -> e.getType() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
@@ -138,7 +144,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
      *
      * @param list Map with the unrecognized.
      */
-    protected void reportUnrecognizedCosemObjects(List<CosemObject> list) {
+    protected void reportUnrecognizedCosemObjects(final List<CosemObject> list) {
         list.forEach(c -> logger.info("Unrecognized cosem object '{}' found in the data: {}", c.getType(), c));
     }
 
@@ -158,7 +164,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
      * @param things The list of configured things
      * @param configuredMeterTypes The set of meters detected in the telegram
      */
-    private void validateConfiguredMeters(List<Thing> things, Set<DSMRMeterType> configuredMeterTypes) {
+    private void validateConfiguredMeters(final List<Thing> things, final Set<DSMRMeterType> configuredMeterTypes) {
         // @formatter:off
         final Set<DSMRMeterType> configuredMeters = things.stream()
                 .map(Thing::getHandler)
@@ -188,8 +194,8 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
      * @param invalidConfigured The list of invalid configured meters
      * @param unconfiguredMeters The list of meters that were detected, but not configured
      */
-    protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
-            List<DSMRMeterType> unconfiguredMeters) {
+    protected void reportConfigurationValidationResults(final List<DSMRMeterType> invalidConfigured,
+            final List<DSMRMeterType> unconfiguredMeters) {
         logger.info(
                 "Possible incorrect meters configured. These are configured: {}."
                         + "But the following unconfigured meters are found in the data received from the meter: {}",
index d041684a4cb1c29b0f94df06733b50c80f6bc3a3..e4ce9c3562553a763c0bf8913186eee3cfe2a2ea 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.dsmr.internal.handler;
 
+import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY;
+import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY;
 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_SMARTY_BRIDGE;
 
 import java.util.ArrayList;
@@ -21,14 +23,14 @@ import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.dsmr.internal.device.DSMRDevice;
 import org.openhab.binding.dsmr.internal.device.DSMRDeviceConfiguration;
 import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
-import org.openhab.binding.dsmr.internal.device.DSMREventListener;
 import org.openhab.binding.dsmr.internal.device.DSMRFixedConfigDevice;
 import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
 import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
-import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
@@ -41,6 +43,7 @@ import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.binding.BaseBridgeHandler;
 import org.openhab.core.thing.binding.ThingHandlerService;
 import org.openhab.core.types.Command;
+import org.openhab.core.util.HexUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,7 +55,7 @@ import org.slf4j.LoggerFactory;
  * @author Hilbrand Bouwkamp - Refactored way messages are forwarded to meters. Removed availableMeters dependency.
  */
 @NonNullByDefault
-public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventListener {
+public class DSMRBridgeHandler extends BaseBridgeHandler implements P1TelegramListener {
 
     /**
      * Factor that will be multiplied with {@link #receivedTimeoutNanos} to get the timeout factor after which the
@@ -105,6 +108,8 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
 
     private final boolean smartyMeter;
 
+    private @Nullable String lastKnownReadErrorMessage;
+
     /**
      * Constructor
      *
@@ -143,9 +148,7 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
     public void initialize() {
         final DSMRDeviceConfiguration deviceConfig = getConfigAs(DSMRDeviceConfiguration.class);
 
-        if (smartyMeter && (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32)) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
-                    "@text/error.configuration.invalidsmartykey");
+        if (smartyMeter && !validateSmartyMeterConfiguration(deviceConfig)) {
             return;
         }
 
@@ -164,6 +167,32 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
                 TimeUnit.NANOSECONDS);
     }
 
+    private boolean validateSmartyMeterConfiguration(final DSMRDeviceConfiguration deviceConfig) {
+        final boolean valid;
+        if (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "@text/addon.dsmr.error.configuration.invalidsmartykey");
+            valid = false;
+        } else if (!validDecryptionKey(deviceConfig.decryptionKey, CONFIGURATION_DECRYPTION_KEY)
+                || !validDecryptionKey(deviceConfig.additionalKey, CONFIGURATION_ADDITIONAL_KEY)) {
+            valid = false;
+        } else {
+            valid = true;
+        }
+        return valid;
+    }
+
+    private boolean validDecryptionKey(final String key, final String message) {
+        try {
+            HexUtils.hexToBytes(key);
+            return true;
+        } catch (final IllegalArgumentException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "@text/addon.dsmr.error.configuration.invalid." + message + " [" + e.getMessage() + "]");
+        }
+        return false;
+    }
+
     /**
      * Creates the {@link DSMRDevice} that corresponds with the user specified configuration.
      *
@@ -224,18 +253,21 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
         final long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;
 
         if (deltaLastReceived > receivedTimeoutNanos) {
-            logger.debug("No data received for {} seconds, restarting port if possible.",
+            logger.debug("No valid data received for {} seconds, restarting port if possible.",
                     TimeUnit.NANOSECONDS.toSeconds(deltaLastReceived));
-            if (dsmrDeviceRunnable != null) {
-                dsmrDeviceRunnable.restart();
-            }
             if (deltaLastReceived > receivedTimeoutNanos * OFFLINE_TIMEOUT_FACTOR) {
                 logger.trace("Setting device offline if not yet done, and reset last received time.");
-                if (getThing().getStatus() == ThingStatus.ONLINE) {
-                    deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.bridge.nodata");
+                if (isInitialized() && getThing().getStatus() != ThingStatus.OFFLINE) {
+                    final String lkm = lastKnownReadErrorMessage;
+                    final String message = lkm == null ? "@text/addon.dsmr.error.bridge.nodata" : lkm;
+
+                    deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, message);
                 }
                 resetLastReceivedState();
             }
+            if (dsmrDeviceRunnable != null) {
+                dsmrDeviceRunnable.restart();
+            }
         }
     }
 
@@ -243,26 +275,29 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
      * Sets the last received time of messages to the current time.
      */
     private void resetLastReceivedState() {
+        lastKnownReadErrorMessage = null;
         telegramReceivedTimeNanos = System.nanoTime();
         logger.trace("Telegram received time set: {}", telegramReceivedTimeNanos);
     }
 
     @Override
-    public synchronized void handleTelegramReceived(final P1Telegram telegram) {
-        if (telegram.getCosemObjects().isEmpty()) {
-            logger.debug("Parsing worked but something went wrong, so there were no CosemObjects:{}",
-                    telegram.getTelegramState().stateDetails);
-            deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, telegram.getTelegramState().stateDetails);
-        } else {
-            resetLastReceivedState();
-            meterValueReceived(telegram);
-        }
+    public synchronized void telegramReceived(final P1Telegram telegram) {
+        resetLastReceivedState();
+        meterValueReceived(telegram);
     }
 
     @Override
-    public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
-        if (portEvent != DSMRConnectorErrorEvent.READ_ERROR) {
-            deviceOffline(ThingStatusDetail.CONFIGURATION_ERROR, portEvent.getEventDetails());
+    public void onError(final DSMRErrorStatus errorStatus, final String message) {
+        if (errorStatus == DSMRErrorStatus.TELEGRAM_NO_DATA) {
+            logger.debug("Parsing worked but something went wrong, so there were no CosemObjects:{}", message);
+            lastKnownReadErrorMessage = errorStatus.getEventDetails();
+        } else {
+            final String errorMessage = errorStatus.getEventDetails() + ' ' + message;
+            lastKnownReadErrorMessage = errorMessage;
+            // if fatal set directly offline.
+            if (errorStatus.isFatal()) {
+                deviceOffline(ThingStatusDetail.CONFIGURATION_ERROR, errorMessage);
+            }
         }
     }
 
index 47c21c9560d3dc2bc337044a01036b4305313744..a4c834a4c8695cb336f1680407066471a62ae393 100644 (file)
@@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
@@ -34,6 +35,7 @@ import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.ThingStatusInfo;
 import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.util.ThingHandlerHelper;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.RefreshType;
 import org.openhab.core.types.State;
@@ -77,7 +79,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
      *
      * @param thing {@link Thing} to create the MeterHandler for
      */
-    public DSMRMeterHandler(Thing thing) {
+    public DSMRMeterHandler(final Thing thing) {
         super(thing);
     }
 
@@ -85,7 +87,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
      * DSMR Meter don't support handling commands
      */
     @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
+    public void handleCommand(final ChannelUID channelUID, final Command command) {
         if (command == RefreshType.REFRESH) {
             updateState();
         }
@@ -103,17 +105,17 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
 
         try {
             meterType = DSMRMeterType.valueOf(getThing().getThingTypeUID().getId().toUpperCase());
-        } catch (IllegalArgumentException iae) {
+        } catch (final IllegalArgumentException iae) {
             logger.warn(
                     "{} could not be initialized due to an invalid meterType {}. Delete this Thing if the problem persists.",
                     getThing(), getThing().getThingTypeUID().getId().toUpperCase());
             updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
-                    "@text/error.configuration.invalidmetertype");
+                    "@text/addon.dsmr.error.configuration.invalidmetertype");
             return;
         }
-        DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class);
+        final DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class);
         channel = meterType.meterKind.isChannelRelevant() ? meterConfig.channel : DSMRMeterConstants.UNKNOWN_CHANNEL;
-        DSMRMeterDescriptor meterDescriptor = new DSMRMeterDescriptor(meterType, channel);
+        final DSMRMeterDescriptor meterDescriptor = new DSMRMeterDescriptor(meterType, channel);
         meter = new DSMRMeter(meterDescriptor);
         meterWatchdog = scheduler.scheduleWithFixedDelay(this::updateState, meterConfig.refresh, meterConfig.refresh,
                 TimeUnit.SECONDS);
@@ -147,7 +149,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
                     updateState(channel, newState);
                 }
             }
-            if (getThing().getStatus() != ThingStatus.ONLINE) {
+            if (ThingHandlerHelper.isHandlerInitialized(getThing()) && getThing().getStatus() != ThingStatus.ONLINE) {
                 updateStatus(ThingStatus.ONLINE);
             }
             lastReceivedValues = Collections.emptyList();
@@ -161,18 +163,18 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
      * @param telegram The received telegram
      */
     @Override
-    public void telegramReceived(P1Telegram telegram) {
+    public void telegramReceived(final P1Telegram telegram) {
         lastReceivedValues = Collections.emptyList();
         final DSMRMeter localMeter = meter;
 
         if (localMeter == null) {
             return;
         }
-        List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
+        final List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
 
         if (filteredValues.isEmpty()) {
             if (getThing().getStatus() == ThingStatus.ONLINE) {
-                setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.thing.nodata");
+                setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/addon.dsmr.error.thing.nodata");
             }
         } else {
             if (logger.isTraceEnabled()) {
@@ -186,7 +188,12 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
     }
 
     @Override
-    public synchronized void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+    public void onError(final DSMRErrorStatus state, final String message) {
+        // Error is handled in other places.
+    }
+
+    @Override
+    public synchronized void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
                 && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
             // Set status to offline --> Thing will become online after receiving meter values
@@ -209,7 +216,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
      * @param status off line status
      * @param details off line detailed message
      */
-    private void setDeviceOffline(ThingStatusDetail status, @Nullable String details) {
+    private void setDeviceOffline(final ThingStatusDetail status, @Nullable final String details) {
         updateStatus(ThingStatus.OFFLINE, status, details);
         getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.NULL));
     }
index 9a34868badc74e063032d6c53ef4c32422ad3ba3..fbe5acce07a709a8a37898485575c7bc266d4086 100644 (file)
@@ -52,6 +52,6 @@ public enum DSMRMeterKind {
      * @return Returns the i18n label key for this meter.
      */
     public String getLabelKey() {
-        return "@text/meterKind." + name().toLowerCase() + ".label";
+        return "@text/addon.dsmr.meterKind." + name().toLowerCase() + ".label";
     }
 }
index 32a2955c0fd9e0a37b392857e0936e1085ce93ad..1ee60b793620903fe5f49fcb823a6f7883854c59 100644 (file)
@@ -388,8 +388,8 @@ public enum DSMRMeterType {
      * @param cosemObjectTypeMeterId identifier cosem object
      * @param requiredCosemObjects list of objects that are present in this meter type
      */
-    DSMRMeterType(DSMRMeterKind meterKind, CosemObjectType cosemObjectTypeMeterId,
-            CosemObjectType... requiredCosemObjects) {
+    DSMRMeterType(final DSMRMeterKind meterKind, final CosemObjectType cosemObjectTypeMeterId,
+            final CosemObjectType... requiredCosemObjects) {
         this(meterKind, cosemObjectTypeMeterId, requiredCosemObjects, new CosemObjectType[0]);
     }
 
@@ -401,8 +401,8 @@ public enum DSMRMeterType {
      * @param requiredCosemObjects list of objects that are present in this meter type
      * @param optionalCosemObjects list of objects that are optional present in this meter type
      */
-    DSMRMeterType(DSMRMeterKind meterKind, CosemObjectType cosemObjectTypeMeterId,
-            CosemObjectType[] requiredCosemObjects, CosemObjectType[] optionalCosemObjects) {
+    DSMRMeterType(final DSMRMeterKind meterKind, final CosemObjectType cosemObjectTypeMeterId,
+            final CosemObjectType[] requiredCosemObjects, final CosemObjectType[] optionalCosemObjects) {
         this.meterKind = meterKind;
         this.cosemObjectTypeMeterId = cosemObjectTypeMeterId;
         this.requiredCosemObjects = requiredCosemObjects;
@@ -427,15 +427,18 @@ public enum DSMRMeterType {
      * @param availableCosemObjects the Cosem Objects to detect if the current meter compatible
      * @return {@link DSMRMeterDescriptor} containing the identification of the compatible meter
      */
-    public @Nullable DSMRMeterDescriptor findCompatible(List<CosemObject> availableCosemObjects) {
+    public @Nullable DSMRMeterDescriptor findCompatible(final List<CosemObject> availableCosemObjects) {
         final Map<@Nullable Integer, AtomicInteger> channelCounter = new HashMap<>(3);
 
         for (final CosemObjectType objectType : requiredCosemObjects) {
             final AtomicBoolean match = new AtomicBoolean();
             availableCosemObjects.stream().filter(a -> a.getType() == objectType).forEach(b -> {
                 match.set(true);
-                channelCounter.computeIfAbsent(b.getObisIdentifier().getChannel(), t -> new AtomicInteger())
-                        .incrementAndGet();
+                final Integer channel = b.getObisIdentifier().getChannel();
+
+                if (channel != null) {
+                    channelCounter.computeIfAbsent(channel, t -> new AtomicInteger()).incrementAndGet();
+                }
             });
             if (!match.get()) {
                 logger.trace("Required objectType {} not found for meter: {}", objectType, this);
index 56d4c4da55b164e9ac6769d7e72fc193dbddb048..e8f0da1f397e55e9e95b96e5a13be3f3b2d06ed6 100644 (file)
@@ -300,27 +300,36 @@ channel-type.dsmr.waterValvePositionType.description = The water valve switch po
 
 # meter kind names
 
-meterKind.invalid.label = Invalid Meter
-meterKind.device.label = Generic DSMR Device
-meterKind.main_electricity.label = Main Electricity Meter
-meterKind.gas.label = Gas Meter
-meterKind.heating.label = Heating Meter
-meterKind.cooling.label = Cooling Meter
-meterKind.water.label = Water Meter
-meterKind.generic.label = Generic Meter
-meterKind.gj.label = GJ Meter
-meterKind.m3.label = M3 Meter
-meterKind.slave_electricity1.label = Slave Electricity Meter
-meterKind.slave_electricity2.label = Slave Electricity Meter 2
+addon.dsmr.meterKind.invalid.label = Invalid Meter
+addon.dsmr.meterKind.device.label = Generic DSMR Device
+addon.dsmr.meterKind.main_electricity.label = Main Electricity Meter
+addon.dsmr.meterKind.gas.label = Gas Meter
+addon.dsmr.meterKind.heating.label = Heating Meter
+addon.dsmr.meterKind.cooling.label = Cooling Meter
+addon.dsmr.meterKind.water.label = Water Meter
+addon.dsmr.meterKind.generic.label = Generic Meter
+addon.dsmr.meterKind.gj.label = GJ Meter
+addon.dsmr.meterKind.m3.label = M3 Meter
+addon.dsmr.meterKind.slave_electricity1.label = Slave Electricity Meter
+addon.dsmr.meterKind.slave_electricity2.label = Slave Electricity Meter 2
 
 # connector error messages
 
-error.bridge.nodata = Not receiving data from meter.
-error.configuration.invalidmetertype = The thing could not be initialized. Delete and re-add thing if the problem persists.
-error.configuration.invalidsmartykey = The given Smarty decyption key is to short. The decyption key must be 32 characters long.
-error.thing.nodata = Not receiving data from meter.
-error.connector.dont_exists = Serial port does not exist.
-error.connector.in_use = Serial port is already in use.
-error.connector.internal_error = Unexpected error, possible bug. Please report.
-error.connector.not_compatible = Serial port is not compatible.
-error.connector.read_error = Read error.
+addon.dsmr.error.bridge.nodata = Not receiving data from meter.
+addon.dsmr.error.configuration.invalidmetertype = The thing could not be initialized. Delete and re-add thing if the problem persists.
+addon.dsmr.error.configuration.invalidsmartykey = The given Smarty decyption key is to short. The decyption key must be 32 characters long.
+addon.dsmr.error.configuration.invalid.decryptionKey = The given Smarty decyption key is invalid. It contains an illegal character: {0}
+addon.dsmr.error.configuration.invalid.additionalKey = The given Smarty additional key is invalid. It contains an illegal character: {0}
+addon.dsmr.error.thing.nodata = Not receiving data from meter.
+
+addon.dsmr.error.status.invalid_decryption_key = Failed to decrypt P1 telegram due to invalid encryption key
+addon.dsmr.error.status.port_dont_exists = Serial port does not exist.
+addon.dsmr.error.status.port_in_use = Serial port is already in use.
+addon.dsmr.error.status.port_internal_error = Unexpected error, possible bug. Please report.
+addon.dsmr.error.status.port_not_compatible = Serial port is not compatible.
+addon.dsmr.error.status.parse_error = Telegram received, but parsing failed due to parse errors.
+addon.dsmr.error.status.serial_data_read_error = Reading data from the serial port failed.
+addon.dsmr.error.status.telegram_crc_error = CRC checksum failed for received P1 telegram.
+addon.dsmr.error.status.telegram_data_corruption = Received P1 telegram is corrupted. Possible bad/wrong P1 data cable?
+addon.dsmr.error.status.telegram_no_data = Received telegram data, but after parsing no data is present. Possible all data corrupted.
+
index 593e1f86bcdf06130dbbb88845eb324e589d384a..c84dca2472be5fad9dc0d43bf2c29b0bd077b9c9 100644 (file)
  */
 package org.openhab.binding.dsmr.internal;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.concurrent.atomic.AtomicReference;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
+import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
 
 /**
@@ -44,7 +45,7 @@ public final class TelegramReaderUtil {
      * @param telegramName name of the telegram file to read
      * @return The raw bytes of a telegram
      */
-    public static byte[] readRawTelegram(String telegramName) {
+    public static byte[] readRawTelegram(final String telegramName) {
         try (InputStream is = TelegramReaderUtil.class.getResourceAsStream(telegramName + TELEGRAM_EXT)) {
             if (is == null) {
                 fail("Could not find telegram file with name:" + telegramName + TELEGRAM_EXT);
@@ -62,16 +63,32 @@ public final class TelegramReaderUtil {
      * @param expectedTelegramState expected state of the telegram read
      * @return a P1Telegram object
      */
-    public static P1Telegram readTelegram(String telegramName, TelegramState expectedTelegramState) {
-        final AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>();
+    public static P1Telegram readTelegram(final String telegramName) {
         final byte[] telegram = readRawTelegram(telegramName);
-        final P1TelegramParser parser = new P1TelegramParser(p1Telegram::set, true);
+        final P1TelegramListenerImpl listener = new P1TelegramListenerImpl();
+        final P1TelegramParser parser = new P1TelegramParser(listener, true);
 
         parser.setLenientMode(true);
         parser.parse(telegram, telegram.length);
-        assertNotNull(p1Telegram.get(), "Telegram state should have been set. (Missing newline at end of message?)");
-        assertEquals(expectedTelegramState, p1Telegram.get().getTelegramState(),
-                "Expected TelegramState should be as expected");
-        return p1Telegram.get();
+        final P1Telegram p1Telegram = listener.telegram;
+
+        assertNotNull(p1Telegram, "Telegram state should have been set. (Missing newline at end of message?)");
+        assertNull(listener.state, "Expected TelegramState should not be set");
+        return p1Telegram;
+    }
+
+    public static class P1TelegramListenerImpl implements P1TelegramListener {
+        public @Nullable P1Telegram telegram;
+        public @Nullable DSMRErrorStatus state;
+
+        @Override
+        public void telegramReceived(final P1Telegram telegram) {
+            this.telegram = telegram;
+        }
+
+        @Override
+        public void onError(final DSMRErrorStatus state, final String error) {
+            this.state = state;
+        }
     }
 }
index 4a49fcae7e7c0b3bfd9f259bf3590d962fb74e3f..cc49fe1416d4f501ca7908b1196a61e4d08e14cd 100644 (file)
  */
 package org.openhab.binding.dsmr.internal.device;
 
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.*;
-import static org.mockito.Mockito.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -34,8 +43,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
 import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
 import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice.DeviceState;
-import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
+import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
+import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
 import org.openhab.core.io.transport.serial.PortInUseException;
 import org.openhab.core.io.transport.serial.SerialPort;
 import org.openhab.core.io.transport.serial.SerialPortEvent;
@@ -61,7 +71,7 @@ public class DSMRSerialAutoDeviceTest {
 
     private final SerialPortManager serialPortManager = new SerialPortManager() {
         @Override
-        public @Nullable SerialPortIdentifier getIdentifier(String name) {
+        public @Nullable SerialPortIdentifier getIdentifier(final String name) {
             assertEquals(DUMMY_PORTNAME, name, "Expect the passed serial port name");
             return mockIdentifier;
         }
@@ -84,21 +94,21 @@ public class DSMRSerialAutoDeviceTest {
     @Test
     public void testHandlingDataAndRestart() throws IOException, PortInUseException {
         mockValidSerialPort();
-        AtomicReference<@Nullable P1Telegram> telegramRef = new AtomicReference<>(null);
-        DSMREventListener listener = new DSMREventListener() {
+        final AtomicReference<@Nullable P1Telegram> telegramRef = new AtomicReference<>(null);
+        final P1TelegramListener listener = new P1TelegramListener() {
             @Override
-            public void handleTelegramReceived(P1Telegram telegram) {
+            public void telegramReceived(final P1Telegram telegram) {
                 telegramRef.set(telegram);
             }
 
             @Override
-            public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent) {
-                fail("No handleErrorEvent Expected" + connectorErrorEvent);
+            public void onError(final DSMRErrorStatus errorStatus, final String message) {
+                fail("No error status expected" + errorStatus);
             }
         };
         try (InputStream inputStream = new ByteArrayInputStream(TelegramReaderUtil.readRawTelegram(TELEGRAM_NAME))) {
             when(mockSerialPort.getInputStream()).thenReturn(inputStream);
-            DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
+            final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
                     new DSMRTelegramListener(), scheduler, 1);
             device.start();
             assertSame(DeviceState.DISCOVER_SETTINGS, device.getState(), "Expect to be starting discovery state");
@@ -117,16 +127,16 @@ public class DSMRSerialAutoDeviceTest {
 
     @Test
     public void testHandleError() throws IOException, PortInUseException {
-        AtomicReference<@Nullable DSMRConnectorErrorEvent> eventRef = new AtomicReference<>(null);
-        DSMREventListener listener = new DSMREventListener() {
+        final AtomicReference<@Nullable DSMRErrorStatus> eventRef = new AtomicReference<>(null);
+        final P1TelegramListener listener = new P1TelegramListener() {
             @Override
-            public void handleTelegramReceived(P1Telegram telegram) {
+            public void telegramReceived(final P1Telegram telegram) {
                 fail("No telegram expected:" + telegram);
             }
 
             @Override
-            public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent) {
-                eventRef.set(connectorErrorEvent);
+            public void onError(final DSMRErrorStatus errorStatus, final String message) {
+                eventRef.set(errorStatus);
             }
         };
         try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) {
@@ -137,7 +147,7 @@ public class DSMRSerialAutoDeviceTest {
             DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
                     new DSMRTelegramListener(), scheduler, 1);
             device.start();
-            assertSame(DSMRConnectorErrorEvent.IN_USE, eventRef.get(), "Expected an error");
+            assertSame(DSMRErrorStatus.PORT_IN_USE, eventRef.get(), "Expected an error");
             assertSame(DeviceState.ERROR, device.getState(), "Expect to be in error state");
             // Trigger device to restart
             mockValidSerialPort();
@@ -148,7 +158,7 @@ public class DSMRSerialAutoDeviceTest {
             device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener, new DSMRTelegramListener(),
                     scheduler, 1);
             device.start();
-            assertSame(DSMRConnectorErrorEvent.DONT_EXISTS, eventRef.get(), "Expected an error");
+            assertSame(DSMRErrorStatus.PORT_DONT_EXISTS, eventRef.get(), "Expected an error");
             assertSame(DeviceState.ERROR, device.getState(), "Expect to be in error state");
         }
     }
@@ -164,7 +174,8 @@ public class DSMRSerialAutoDeviceTest {
         private final int eventType;
         private final boolean newValue;
 
-        public MockSerialPortEvent(SerialPort mockSerialPort, int eventType, boolean oldValue, boolean newValue) {
+        public MockSerialPortEvent(final SerialPort mockSerialPort, final int eventType, final boolean oldValue,
+                final boolean newValue) {
             this.eventType = eventType;
             this.newValue = newValue;
         }
index e01542cb7dace4485b82303752b6be9e4b6a49e7..213ab67c4402c697b9cf89f40d9456bacdbdcdbe 100644 (file)
@@ -22,8 +22,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
+import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
 
 /**
  * Test class for the {@link SmartyDecrypter}.
@@ -96,10 +95,13 @@ public class SmartyDecrypterTest {
      */
     @Test
     public void testSmartyDecrypter() {
-        final AtomicReference<String> telegramResult = new AtomicReference<>("");
-        final P1TelegramListener telegramListener = telegram -> telegramResult.set(telegram.getRawTelegram());
-        final SmartyDecrypter decoder = new SmartyDecrypter(new P1TelegramParser(telegramListener),
-                new DSMRTelegramListener(KEY, ""), KEY, "");
+        final AtomicReference<String> dataRead = new AtomicReference<>();
+        final SmartyDecrypter decoder = new SmartyDecrypter(new TelegramParser() {
+            @Override
+            public void parse(final byte[] data, final int length) {
+                dataRead.set(new String(data, StandardCharsets.UTF_8));
+            }
+        }, new DSMRTelegramListener(KEY, ""), KEY, "");
         decoder.setLenientMode(true);
         final byte[] data = new byte[TELEGRAM.length];
 
@@ -110,6 +112,6 @@ public class SmartyDecrypterTest {
         decoder.parse(data, data.length);
         final String expected = new String(TelegramReaderUtil.readRawTelegram("smarty_long"), StandardCharsets.UTF_8);
 
-        assertThat("Should have correctly decrypted the telegram", telegramResult.get(), is(equalTo(expected)));
+        assertThat("Should have correctly decrypted the telegram", dataRead.get(), is(equalTo(expected)));
     }
 }
index 9c852d2317c4a57bab09a863af14973f3b28bfa2..818fcab56994690f150bd0f8d4ba1c864c7fde74 100644 (file)
@@ -21,7 +21,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
 
 /**
  * Test class for {@link P1TelegramParser}.
@@ -55,7 +54,7 @@ public class P1TelegramParserTest {
     @ParameterizedTest
     @MethodSource("data")
     public void testParsing(final String telegramName, final int numberOfCosemObjects, final int unknownObjects) {
-        final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
+        final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName);
         assertEquals(unknownObjects, telegram.getUnknownCosemObjects().size(),
                 "Should not have other than " + unknownObjects + " unknown cosem objects");
         assertEquals(numberOfCosemObjects,
index 94d75ca785db648693082a14d9b669c88f40cf7d..558ac6c533ea3e98617a937dfe84acad8697a035 100644 (file)
@@ -42,7 +42,6 @@ import org.junit.jupiter.params.provider.MethodSource;
 import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
 import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
 import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
 
@@ -77,7 +76,7 @@ public class DSMRMeterDetectorTest {
     @ParameterizedTest
     @MethodSource("data")
     public void testDetectMeters(final String telegramName, final Set<DSMRMeterType> expectedMeters) {
-        final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
+        final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName);
         final DSMRMeterDetector detector = new DSMRMeterDetector();
         final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> entry = detector.detectMeters(telegram);
         final Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
index 0be22cfda71d84552885ef690aaf1e7018f40028..859f5810d67905bcfa0114bc2bc2e370955a0c74 100644 (file)
  */
 package org.openhab.binding.dsmr.internal.discovery;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.when;
-import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.*;
+import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.DEVICE_V5;
+import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.ELECTRICITY_V4_2;
+import static org.openhab.binding.dsmr.internal.meter.DSMRMeterType.M3_V5_0;
 
 import java.util.Collections;
 import java.util.EnumSet;
@@ -32,7 +35,6 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
 import org.openhab.binding.dsmr.internal.handler.DSMRBridgeHandler;
 import org.openhab.binding.dsmr.internal.handler.DSMRMeterHandler;
 import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
@@ -63,13 +65,13 @@ public class DSMRMeterDiscoveryServiceTest {
      */
     @Test
     public void testInvalidConfiguredMeters() {
-        P1Telegram expected = TelegramReaderUtil.readTelegram(EXPECTED_CONFIGURED_TELEGRAM, TelegramState.OK);
-        AtomicReference<List<DSMRMeterType>> invalidConfiguredRef = new AtomicReference<>();
-        AtomicReference<List<DSMRMeterType>> unconfiguredRef = new AtomicReference<>();
-        DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
+        final P1Telegram expected = TelegramReaderUtil.readTelegram(EXPECTED_CONFIGURED_TELEGRAM);
+        final AtomicReference<List<DSMRMeterType>> invalidConfiguredRef = new AtomicReference<>();
+        final AtomicReference<List<DSMRMeterType>> unconfiguredRef = new AtomicReference<>();
+        final DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
             @Override
-            protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
-                    List<DSMRMeterType> unconfiguredMeters) {
+            protected void reportConfigurationValidationResults(final List<DSMRMeterType> invalidConfigured,
+                    final List<DSMRMeterType> unconfiguredMeters) {
                 super.reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
                 invalidConfiguredRef.set(invalidConfigured);
                 unconfiguredRef.set(unconfiguredMeters);
@@ -79,10 +81,13 @@ public class DSMRMeterDiscoveryServiceTest {
 
         // Mock the invalid configuration by reading a telegram that is valid for a meter that is a subset of the
         // expected meter.
-        List<DSMRMeterDescriptor> invalidConfiguredMeterDescriptors = EnumSet.of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0)
-                .stream().map(mt -> new DSMRMeterDescriptor(mt, 0)).collect(Collectors.toList());
-        List<Thing> things = invalidConfiguredMeterDescriptors.stream().map(m -> thing).collect(Collectors.toList());
-        AtomicReference<Iterator<DSMRMeterDescriptor>> detectMetersRef = new AtomicReference<>();
+        final List<DSMRMeterDescriptor> invalidConfiguredMeterDescriptors = EnumSet
+                .of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0).stream().map(mt -> new DSMRMeterDescriptor(mt, 0))
+                .collect(Collectors.toList());
+        final List<Thing> things = invalidConfiguredMeterDescriptors.stream().map(m -> thing)
+                .collect(Collectors.toList());
+        final AtomicReference<Iterator<DSMRMeterDescriptor>> detectMetersRef = new AtomicReference<>();
+
         when((meterHandler).getMeterDescriptor()).then(a -> {
             if (detectMetersRef.get() == null || !detectMetersRef.get().hasNext()) {
                 detectMetersRef.set(invalidConfiguredMeterDescriptors.iterator());
@@ -109,9 +114,9 @@ public class DSMRMeterDiscoveryServiceTest {
      */
     @Test
     public void testUnregisteredMeters() {
-        P1Telegram telegram = TelegramReaderUtil.readTelegram(UNREGISTERED_METER_TELEGRAM, TelegramState.OK);
-        AtomicBoolean unregisteredMeter = new AtomicBoolean(false);
-        DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
+        final P1Telegram telegram = TelegramReaderUtil.readTelegram(UNREGISTERED_METER_TELEGRAM);
+        final AtomicBoolean unregisteredMeter = new AtomicBoolean(false);
+        final DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
             @Override
             protected void reportUnregisteredMeters() {
                 super.reportUnregisteredMeters();
index 7f9d27443a6e3e912c14928d24fc5c3879e7ce4d..3630854fa9b37d4896e65d114dba23c7b8d634ab 100644 (file)
@@ -20,7 +20,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
 import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
-import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
 
 /**
  * Test class for {@link DSMRMeter}.
@@ -35,15 +34,15 @@ public class DSMRMeterTest {
      */
     @Test
     public void testFilterMeterValues() {
-        final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK)
-                .getCosemObjects();
+        final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50").getCosemObjects();
 
         assertMeterValues(cosemObjects, DSMRMeterType.DEVICE_V5, DSMRMeterConstants.UNKNOWN_CHANNEL, 3);
         assertMeterValues(cosemObjects, DSMRMeterType.ELECTRICITY_V5_0, 0, 29);
         assertMeterValues(cosemObjects, DSMRMeterType.M3_V5_0, 1, 3);
     }
 
-    private void assertMeterValues(List<CosemObject> cosemObjects, DSMRMeterType type, int channel, int expected) {
+    private void assertMeterValues(final List<CosemObject> cosemObjects, final DSMRMeterType type, final int channel,
+            final int expected) {
         final DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(type, channel);
         final DSMRMeter meter = new DSMRMeter(descriptor);
         final List<CosemObject> filterMeterValues = meter.filterMeterValues(cosemObjects, channel);