diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRBindingConstants.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRBindingConstants.java index 0a72355be..e60a9fa46 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRBindingConstants.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/DSMRBindingConstants.java @@ -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 diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceConfiguration.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceConfiguration.java index 2462a8728..12edfad94 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceConfiguration.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceConfiguration.java @@ -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. diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceRunnable.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceRunnable.java index 979ff0237..438b3062c 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceRunnable.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRDeviceRunnable.java @@ -12,10 +12,12 @@ */ 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 index 6e93b3765..000000000 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMREventListener.java +++ /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); -} diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRFixedConfigDevice.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRFixedConfigDevice.java index 099aecc0d..429d3e346 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRFixedConfigDevice.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRFixedConfigDevice.java @@ -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); } } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDevice.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDevice.java index efdd95c59..cacfc1381 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDevice.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDevice.java @@ -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; } /** diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRTelegramListener.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRTelegramListener.java index e314b74a6..16a3acc48 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRTelegramListener.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/DSMRTelegramListener.java @@ -13,14 +13,12 @@ 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 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 */ diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypter.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypter.java index a750450fd..bf7ea751e 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypter.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypter.java @@ -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(); + try { + final byte[] plainText = decrypt(); - reset(); - if (plainText == null) { - telegramListener - .telegramReceived(new P1Telegram(Collections.emptyList(), TelegramState.INVALID_ENCRYPTION_KEY)); - } else { - parser.parse(plainText, plainText.length); + 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; } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRBaseConnector.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRBaseConnector.java index 1eebc28fe..7159ea3bf 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRBaseConnector.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRBaseConnector.java @@ -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 index 9d85330bb..000000000 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorErrorEvent.java +++ /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(); - } -} diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorListener.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorListener.java index 219da50c0..5f71bf63d 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorListener.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRConnectorListener.java @@ -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 index 000000000..5c1c606f4 --- /dev/null +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRErrorStatus.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRSerialConnector.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRSerialConnector.java index dd670ded2..93f7124b8 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRSerialConnector.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/connector/DSMRSerialConnector.java @@ -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, ""); } } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java index 5d9394a99..8c7c84435 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/cosem/OBISIdentifier.java @@ -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; } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1Telegram.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1Telegram.java index b0dc9fbc7..c9d13a66d 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1Telegram.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1Telegram.java @@ -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 cosemObjects; - private final TelegramState telegramState; private final String rawTelegram; private final List> unknownCosemObjects; - public P1Telegram(List cosemObjects, TelegramState telegramState) { - this(cosemObjects, telegramState, "", Collections.emptyList()); + public P1Telegram(final List cosemObjects) { + this(cosemObjects, "", Collections.emptyList()); } - public P1Telegram(List cosemObjects, TelegramState telegramState, String rawTelegram, - List> unknownCosemObjects) { + public P1Telegram(final List cosemObjects, final String rawTelegram, + final List> 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 */ diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramListener.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramListener.java index 2b31facd3..6b9306d2f 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramListener.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramListener.java @@ -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); } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java index c8e040e98..e7c17071c 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParser.java @@ -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 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 cosemObjects = new ArrayList<>(); + private final List> 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 checkCRC() { + final Optional 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 P1Telegram constructTelegram() { - final List cosemObjectsCopy = new ArrayList<>(cosemObjects); + private void processTelegram() { + telegramState.ifPresentOrElse(error -> telegramListener.onError(error, ""), + () -> telegramListener.telegramReceived(constructTelegram())); + } + private P1Telegram constructTelegram() { + final List 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 objects, final Entry 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(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; } } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRBridgeDiscoveryService.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRBridgeDiscoveryService.java index 25674aa09..abc422d94 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRBridgeDiscoveryService.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRBridgeDiscoveryService.java @@ -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 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(); - } } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java index 82f5c45bc..7717699c0 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryService.java @@ -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 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 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 list) { + protected void reportUnrecognizedCosemObjects(final List 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 things, Set configuredMeterTypes) { + private void validateConfiguredMeters(final List things, final Set configuredMeterTypes) { // @formatter:off final Set 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 invalidConfigured, - List unconfiguredMeters) { + protected void reportConfigurationValidationResults(final List invalidConfigured, + final List unconfiguredMeters) { logger.info( "Possible incorrect meters configured. These are configured: {}." + "But the following unconfigured meters are found in the data received from the meter: {}", diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java index d041684a4..e4ce9c356 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRBridgeHandler.java @@ -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); + } } } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java index 47c21c956..a4c834a4c 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/handler/DSMRMeterHandler.java @@ -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 filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel); + final List 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)); } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java index 9a34868ba..fbe5acce0 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterKind.java @@ -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"; } } diff --git a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java index 32a2955c0..1ee60b793 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java +++ b/bundles/org.openhab.binding.dsmr/src/main/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterType.java @@ -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 availableCosemObjects) { + public @Nullable DSMRMeterDescriptor findCompatible(final List 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); diff --git a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/i18n/dsmr.properties b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/i18n/dsmr.properties index 56d4c4da5..e8f0da1f3 100644 --- a/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/i18n/dsmr.properties +++ b/bundles/org.openhab.binding.dsmr/src/main/resources/OH-INF/i18n/dsmr.properties @@ -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. + diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java index 593e1f86b..c84dca247 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/TelegramReaderUtil.java @@ -12,17 +12,18 @@ */ 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 = 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; + } } } diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDeviceTest.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDeviceTest.java index 4a49fcae7..cc49fe141 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDeviceTest.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/DSMRSerialAutoDeviceTest.java @@ -12,9 +12,18 @@ */ 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; } diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypterTest.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypterTest.java index e01542cb7..213ab67c4 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypterTest.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/SmartyDecrypterTest.java @@ -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 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 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))); } } diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java index 9c852d231..818fcab56 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/device/p1telegram/P1TelegramParserTest.java @@ -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, diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java index 94d75ca78..558ac6c53 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDetectorTest.java @@ -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 expectedMeters) { - final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK); + final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName); final DSMRMeterDetector detector = new DSMRMeterDetector(); final Entry, List> entry = detector.detectMeters(telegram); final Collection detectMeters = entry.getKey(); diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryServiceTest.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryServiceTest.java index 0be22cfda..859f5810d 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryServiceTest.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/discovery/DSMRMeterDiscoveryServiceTest.java @@ -12,9 +12,12 @@ */ 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> invalidConfiguredRef = new AtomicReference<>(); - AtomicReference> unconfiguredRef = new AtomicReference<>(); - DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() { + final P1Telegram expected = TelegramReaderUtil.readTelegram(EXPECTED_CONFIGURED_TELEGRAM); + final AtomicReference> invalidConfiguredRef = new AtomicReference<>(); + final AtomicReference> unconfiguredRef = new AtomicReference<>(); + final DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() { @Override - protected void reportConfigurationValidationResults(List invalidConfigured, - List unconfiguredMeters) { + protected void reportConfigurationValidationResults(final List invalidConfigured, + final List 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 invalidConfiguredMeterDescriptors = EnumSet.of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0) - .stream().map(mt -> new DSMRMeterDescriptor(mt, 0)).collect(Collectors.toList()); - List things = invalidConfiguredMeterDescriptors.stream().map(m -> thing).collect(Collectors.toList()); - AtomicReference> detectMetersRef = new AtomicReference<>(); + final List invalidConfiguredMeterDescriptors = EnumSet + .of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0).stream().map(mt -> new DSMRMeterDescriptor(mt, 0)) + .collect(Collectors.toList()); + final List things = invalidConfiguredMeterDescriptors.stream().map(m -> thing) + .collect(Collectors.toList()); + final AtomicReference> 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(); diff --git a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java index 7f9d27443..3630854fa 100644 --- a/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java +++ b/bundles/org.openhab.binding.dsmr/src/test/java/org/openhab/binding/dsmr/internal/meter/DSMRMeterTest.java @@ -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 cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK) - .getCosemObjects(); + final List 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 cosemObjects, DSMRMeterType type, int channel, int expected) { + private void assertMeterValues(final List 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 filterMeterValues = meter.filterMeterValues(cosemObjects, channel);