* [dsmr] Improved error handling to better handle corrupt messages.
- Fix incorrect additional key in bridge discovery service.
- When corrupted P1 telegram is received don't set directly to offline, but let the watchdog do this if to many times this happens.
This makes it a bit more lenient for receiving a bad telegram once in a while. Because some connections are not that stable.
- Simplified error handling, and listeners to one enum/interface to make it more cleaner.
Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
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
/**
* 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.
*/
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;
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.
* @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;
}
}
}
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();
+++ /dev/null
-/**
- * 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);
-}
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;
/**
* @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);
}
}
@Override
- public void setLenientMode(boolean lenientMode) {
+ public void setLenientMode(final boolean lenientMode) {
telegramListener.setLenientMode(lenientMode);
}
}
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;
* 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}.
/**
* 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
*
* @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);
}
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,
* @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);
}
}
* @param lenientMode the lenientMode to set
*/
@Override
- public void setLenientMode(boolean lenientMode) {
+ public void setLenientMode(final boolean lenientMode) {
telegramListener.setLenientMode(lenientMode);
}
private void endTimeScheduledCall() {
if (state == DeviceState.DISCOVER_SETTINGS) {
stopDiscover(DeviceState.ERROR);
- parentListener.handleErrorEvent(DSMRConnectorErrorEvent.DONT_EXISTS);
+ parentListener.onError(DSMRErrorStatus.PORT_DONT_EXISTS, "");
}
}
*
* @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);
endTimeTimer.cancel(true);
endTimeTimer = null;
}
- this.state = state;
}
/**
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;
private final Logger logger = LoggerFactory.getLogger(DSMRTelegramListener.class);
private final TelegramParser parser;
- private @NonNullByDefault({}) DSMREventListener dsmrEventListener;
+ private @NonNullByDefault({}) P1TelegramListener p1TelegramListener;
/**
* Constructor.
}
/**
- * 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
*
*/
@Override
public void telegramReceived(final P1Telegram telegram) {
- final TelegramState telegramState = telegram.getTelegramState();
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
if (logger.isTraceEnabled()) {
- logger.trace("Received {} Cosem Objects with state: '{}'", cosemObjects.size(), telegramState);
+ logger.trace("Received {} Cosem Objects", cosemObjects.size());
}
- if (telegramState == TelegramState.OK || telegramState == TelegramState.INVALID_ENCRYPTION_KEY) {
- dsmrEventListener.handleTelegramReceived(telegram);
+ if (cosemObjects.isEmpty()) {
+ onError(DSMRErrorStatus.TELEGRAM_NO_DATA, "");
} else {
- if (logger.isDebugEnabled()) {
- logger.debug("Telegram received with error state '{}': {}", telegramState,
- cosemObjects.stream().map(CosemObject::toString).collect(Collectors.joining(",")));
- }
+ p1TelegramListener.telegramReceived(telegram);
}
}
+ @Override
+ public void onError(final DSMRErrorStatus state, final String message) {
+ p1TelegramListener.onError(state, message);
+ }
+
/**
* @param lenientMode the lenientMode to set
*/
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;
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;
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;
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));
}
}
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) {
// All input has been read.
cipherText.put(rawInput);
if (currentBytePosition >= changeToNextStateAt) {
- state = State.DONE_READING_TELEGRAM;
+ state = State.WAITING_FOR_START_BYTE;
+ return true;
}
break;
}
- if (state == State.DONE_READING_TELEGRAM) {
- state = State.WAITING_FOR_START_BYTE;
- return true;
- }
return false;
}
private void processCompleted() {
- final byte[] plainText = decrypt();
-
- reset();
- if (plainText == null) {
- telegramListener
- .telegramReceived(new P1Telegram(Collections.emptyList(), TelegramState.INVALID_ENCRYPTION_KEY));
- } else {
- parser.parse(plainText, plainText.length);
+ try {
+ final byte[] plainText = decrypt();
+
+ if (plainText != null) {
+ parser.parse(plainText, plainText.length);
+ }
+ } finally {
+ reset();
}
}
}
} 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;
}
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;
/**
* Listener to send received data and errors to.
*/
- protected final DSMRConnectorListener dsmrConnectorListener;
+ private final DSMRConnectorListener dsmrConnectorListener;
/**
* 1Kbyte buffer for storing received data.
*/
private boolean open;
- public DSMRBaseConnector(DSMRConnectorListener connectorListener) {
+ public DSMRBaseConnector(final DSMRConnectorListener connectorListener) {
this.dsmrConnectorListener = connectorListener;
}
* @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");
}
if (inputStream != null) {
try {
inputStream.close();
- } catch (IOException ioe) {
+ } catch (final IOException ioe) {
logger.debug("Failed to close reader", ioe);
}
}
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) {
}
}
}
- } 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);
}
}
+++ /dev/null
-/**
- * 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();
- }
-}
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.
--- /dev/null
+/**
+ * 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);
+ }
+}
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;
/**
* Serial port instance.
*/
- private AtomicReference<@Nullable SerialPort> serialPortReference = new AtomicReference<>();
+ private final AtomicReference<@Nullable SerialPort> serialPortReference = new AtomicReference<>();
/**
* DSMR Connector listener.
* @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;
*
* @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
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;
}
/**
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();
*
* @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);
/**
* 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();
}
@Override
- public void serialEvent(@Nullable SerialPortEvent seEvent) {
+ public void serialEvent(@Nullable final SerialPortEvent seEvent) {
if (seEvent == null) {
return;
}
break;
default: // do nothing
}
- } catch (RuntimeException e) {
+ } catch (final RuntimeException e) {
logger.warn("RuntimeException during handling serial event: {}", seEvent.getEventType(), e);
}
}
* @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, "");
}
}
*/
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;
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;
}
/**
* The TelegramState described the meta data of the P1Telegram
*/
- public enum TelegramState {
- /**
- * OK. Telegram was successful received and CRC16 checksum is verified (CRC16 only for DSMR V4 and up)
- */
- OK("P1 telegram received OK"),
- /**
- * CRC_ERROR. CRC16 checksum failed (only DSMR V4 and up)
- */
- CRC_ERROR("CRC checksum failed for received P1 telegram"),
- /**
- * DATA_CORRUPTION. The P1 telegram has syntax errors.
- */
- DATA_CORRUPTION("Received P1 telegram is corrupted"),
- /**
- * P1TelegramListener. The smarty telegram was successful received but could not be decoded because of an
- * invalid
- * encryption key.
- */
- INVALID_ENCRYPTION_KEY("Failed to decrypt P1 telegram due to invalid encryption key");
-
- /**
- * public accessible state details
- */
- public final String stateDetails;
-
- /**
- * Constructs a new TelegramState enum
- *
- * @param stateDetails String containing the details of this TelegramState
- */
- private TelegramState(String stateDetails) {
- this.stateDetails = stateDetails;
- }
- }
private final List<CosemObject> cosemObjects;
- private final TelegramState telegramState;
private final String rawTelegram;
private final List<Entry<String, String>> unknownCosemObjects;
- public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState) {
- this(cosemObjects, telegramState, "", Collections.emptyList());
+ public P1Telegram(final List<CosemObject> cosemObjects) {
+ this(cosemObjects, "", Collections.emptyList());
}
- public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState, String rawTelegram,
- List<Entry<String, String>> unknownCosemObjects) {
+ public P1Telegram(final List<CosemObject> cosemObjects, final String rawTelegram,
+ final List<Entry<String, String>> unknownCosemObjects) {
this.cosemObjects = cosemObjects;
- this.telegramState = telegramState;
this.rawTelegram = rawTelegram;
this.unknownCosemObjects = unknownCosemObjects;
}
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
*/
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
@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);
}
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;
/**
* Current telegram state
*/
- private volatile TelegramState telegramState;
+ private volatile Optional<DSMRErrorStatus> telegramState = Optional.empty();
/**
* CosemObjectFactory helper class
/**
* Received Cosem Objects in the P1Telegram that is currently received
*/
- private final List<CosemObject> cosemObjects = new ArrayList<>();
+ private final List<Entry<String, String>> cosemObjects = new ArrayList<>();
/**
* List of Cosem Object values that are not known to this binding.
*
* @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();
}
/**
* @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);
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 == '/') {
/*
logger.trace("State after parsing: {}", state);
}
- private TelegramState checkCRC(TelegramState currentState) {
- final TelegramState telegramState;
+ private Optional<DSMRErrorStatus> checkCRC() {
+ final Optional<DSMRErrorStatus> telegramState;
if (Pattern.matches(CRC_PATTERN, crcValue)) {
final int crcP1Telegram = Integer.parseInt(crcValue.toString(), 16);
}
logger.trace("CRC value does not match, p1 Telegram failed");
- telegramState = TelegramState.CRC_ERROR;
+ telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_CRC_ERROR);
} else {
- telegramState = currentState;
+ telegramState = Optional.empty();
}
} else {
- telegramState = TelegramState.CRC_ERROR;
+ telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_CRC_ERROR);
}
return telegramState;
}
+ private void processTelegram() {
+ telegramState.ifPresentOrElse(error -> telegramListener.onError(error, ""),
+ () -> telegramListener.telegramReceived(constructTelegram()));
+ }
+
private P1Telegram constructTelegram() {
- final List<CosemObject> cosemObjectsCopy = new ArrayList<>(cosemObjects);
+ final List<CosemObject> cosemObjectsCopy = new ArrayList<>();
+ cosemObjects.stream().forEach(e -> addCosemObject(cosemObjectsCopy, e));
if (lenientMode) {
- return new P1Telegram(cosemObjectsCopy, telegramState, rawData.toString(),
+ return new P1Telegram(cosemObjectsCopy, rawData.toString(),
unknownCosemObjects.isEmpty() ? Collections.emptyList() : new ArrayList<>(unknownCosemObjects));
} else {
- return new P1Telegram(cosemObjectsCopy, telegramState);
+ return new P1Telegram(cosemObjectsCopy);
+ }
+ }
+
+ private void addCosemObject(final List<CosemObject> objects, final Entry<String, String> cosemEntry) {
+ final String obisIdString = cosemEntry.getKey();
+ final String obisValueString = cosemEntry.getValue();
+ final CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);
+
+ if (cosemObject == null) {
+ if (lenientMode) {
+ unknownCosemObjects.add(new SimpleEntry<>(obisIdString, obisValueString));
+ }
+ } else {
+ logger.trace("Adding {} to list of Cosem Objects", cosemObject);
+ objects.add(cosemObject);
}
}
*
* @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, "");
}
/**
*
* @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
final String obisIdString = obisId.toString();
if (!obisIdString.isEmpty()) {
- final String obisValueString = obisValue.toString();
- final CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);
-
- if (cosemObject == null) {
- if (lenientMode) {
- unknownCosemObjects.add(new SimpleEntry<>(obisIdString, obisValueString));
- }
- } else {
- logger.trace("Adding {} to list of Cosem Objects", cosemObject);
- cosemObjects.add(cosemObject);
- }
+ cosemObjects.add(new SimpleEntry<String, String>(obisIdString, obisValue.toString()));
}
clearObisData();
}
/**
* @param newState the new state to set
*/
- private void setState(State newState) {
+ private void setState(final State newState) {
synchronized (state) {
switch (newState) {
case HEADER:
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
}
@Override
- public void setLenientMode(boolean lenientMode) {
+ public void setLenientMode(final boolean lenientMode) {
this.lenientMode = lenientMode;
}
}
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;
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;
*/
@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.
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);
* @param telegram the received telegram
*/
@Override
- public void handleTelegramReceived(final P1Telegram telegram) {
+ public void telegramReceived(final P1Telegram telegram) {
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
if (logger.isDebugEnabled()) {
logger.debug("[{}] Received {} cosemObjects", currentScannedPortName, cosemObjects.size());
}
- if (telegram.getTelegramState() == TelegramState.INVALID_ENCRYPTION_KEY) {
+ final ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
+ meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
+ stopSerialPortScan();
+ }
+
+ @Override
+ public void onError(final DSMRErrorStatus portEvent, final String message) {
+ if (portEvent == DSMRErrorStatus.INVALID_DECRYPTION_KEY) {
bridgeDiscovered(THING_TYPE_SMARTY_BRIDGE);
- stopSerialPortScan();
- } else if (!cosemObjects.isEmpty()) {
- final ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
- meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
- stopSerialPortScan();
+ } else {
+ logger.debug("[{}] Error on port during discovery: {} - {}", currentScannedPortName, portEvent, message);
}
+ stopSerialPortScan();
}
/**
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();
thingDiscovered(discoveryResult);
return thingUID;
}
-
- @Override
- public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
- logger.debug("[{}] Error on port during discovery: {}", currentScannedPortName, portEvent);
- stopSerialPortScan();
- }
}
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;
}
@Override
- public void setThingHandler(ThingHandler handler) {
+ public void setThingHandler(final ThingHandler handler) {
if (handler instanceof DSMRBridgeHandler) {
dsmrBridgeHandler = (DSMRBridgeHandler) handler;
}
}
@Override
- public void telegramReceived(P1Telegram telegram) {
+ public void telegramReceived(final P1Telegram telegram) {
if (logger.isDebugEnabled()) {
logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
}
detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID()));
}
- protected void verifyUnregisteredCosemObjects(P1Telegram telegram, List<CosemObject> list) {
+ @Override
+ public void onError(final DSMRErrorStatus state, final String message) {
+ logger.info("Telegram could not be parsed correctly, failed with state: {}, {}", state, message);
+ }
+
+ protected void verifyUnregisteredCosemObjects(final P1Telegram telegram, final List<CosemObject> list) {
if (!list.isEmpty()) {
if (list.stream()
.anyMatch(e -> e.getType() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
*
* @param list Map with the unrecognized.
*/
- protected void reportUnrecognizedCosemObjects(List<CosemObject> list) {
+ protected void reportUnrecognizedCosemObjects(final List<CosemObject> list) {
list.forEach(c -> logger.info("Unrecognized cosem object '{}' found in the data: {}", c.getType(), c));
}
* @param things The list of configured things
* @param configuredMeterTypes The set of meters detected in the telegram
*/
- private void validateConfiguredMeters(List<Thing> things, Set<DSMRMeterType> configuredMeterTypes) {
+ private void validateConfiguredMeters(final List<Thing> things, final Set<DSMRMeterType> configuredMeterTypes) {
// @formatter:off
final Set<DSMRMeterType> configuredMeters = things.stream()
.map(Thing::getHandler)
* @param invalidConfigured The list of invalid configured meters
* @param unconfiguredMeters The list of meters that were detected, but not configured
*/
- protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
- List<DSMRMeterType> unconfiguredMeters) {
+ protected void reportConfigurationValidationResults(final List<DSMRMeterType> invalidConfigured,
+ final List<DSMRMeterType> unconfiguredMeters) {
logger.info(
"Possible incorrect meters configured. These are configured: {}."
+ "But the following unconfigured meters are found in the data received from the meter: {}",
*/
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;
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;
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;
* @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
private final boolean smartyMeter;
+ private @Nullable String lastKnownReadErrorMessage;
+
/**
* Constructor
*
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;
}
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.
*
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();
+ }
}
}
* 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);
+ }
}
}
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;
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;
*
* @param thing {@link Thing} to create the MeterHandler for
*/
- public DSMRMeterHandler(Thing thing) {
+ public DSMRMeterHandler(final Thing thing) {
super(thing);
}
* 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();
}
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);
updateState(channel, newState);
}
}
- if (getThing().getStatus() != ThingStatus.ONLINE) {
+ if (ThingHandlerHelper.isHandlerInitialized(getThing()) && getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
lastReceivedValues = Collections.emptyList();
* @param telegram The received telegram
*/
@Override
- public void telegramReceived(P1Telegram telegram) {
+ public void telegramReceived(final P1Telegram telegram) {
lastReceivedValues = Collections.emptyList();
final DSMRMeter localMeter = meter;
if (localMeter == null) {
return;
}
- List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
+ final List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
if (filteredValues.isEmpty()) {
if (getThing().getStatus() == ThingStatus.ONLINE) {
- setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.thing.nodata");
+ setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/addon.dsmr.error.thing.nodata");
}
} else {
if (logger.isTraceEnabled()) {
}
@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
* @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));
}
* @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";
}
}
* @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]);
}
* @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;
* @param availableCosemObjects the Cosem Objects to detect if the current meter compatible
* @return {@link DSMRMeterDescriptor} containing the identification of the compatible meter
*/
- public @Nullable DSMRMeterDescriptor findCompatible(List<CosemObject> availableCosemObjects) {
+ public @Nullable DSMRMeterDescriptor findCompatible(final List<CosemObject> availableCosemObjects) {
final Map<@Nullable Integer, AtomicInteger> channelCounter = new HashMap<>(3);
for (final CosemObjectType objectType : requiredCosemObjects) {
final AtomicBoolean match = new AtomicBoolean();
availableCosemObjects.stream().filter(a -> a.getType() == objectType).forEach(b -> {
match.set(true);
- channelCounter.computeIfAbsent(b.getObisIdentifier().getChannel(), t -> new AtomicInteger())
- .incrementAndGet();
+ final Integer channel = b.getObisIdentifier().getChannel();
+
+ if (channel != null) {
+ channelCounter.computeIfAbsent(channel, t -> new AtomicInteger()).incrementAndGet();
+ }
});
if (!match.get()) {
logger.trace("Required objectType {} not found for meter: {}", objectType, this);
# 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.
+
*/
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;
/**
* @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);
* @param expectedTelegramState expected state of the telegram read
* @return a P1Telegram object
*/
- public static P1Telegram readTelegram(String telegramName, TelegramState expectedTelegramState) {
- final AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>();
+ public static P1Telegram readTelegram(final String telegramName) {
final byte[] telegram = readRawTelegram(telegramName);
- final P1TelegramParser parser = new P1TelegramParser(p1Telegram::set, true);
+ final P1TelegramListenerImpl listener = new P1TelegramListenerImpl();
+ final P1TelegramParser parser = new P1TelegramParser(listener, true);
parser.setLenientMode(true);
parser.parse(telegram, telegram.length);
- assertNotNull(p1Telegram.get(), "Telegram state should have been set. (Missing newline at end of message?)");
- assertEquals(expectedTelegramState, p1Telegram.get().getTelegramState(),
- "Expected TelegramState should be as expected");
- return p1Telegram.get();
+ final P1Telegram p1Telegram = listener.telegram;
+
+ assertNotNull(p1Telegram, "Telegram state should have been set. (Missing newline at end of message?)");
+ assertNull(listener.state, "Expected TelegramState should not be set");
+ return p1Telegram;
+ }
+
+ public static class P1TelegramListenerImpl implements P1TelegramListener {
+ public @Nullable P1Telegram telegram;
+ public @Nullable DSMRErrorStatus state;
+
+ @Override
+ public void telegramReceived(final P1Telegram telegram) {
+ this.telegram = telegram;
+ }
+
+ @Override
+ public void onError(final DSMRErrorStatus state, final String error) {
+ this.state = state;
+ }
}
}
*/
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;
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;
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;
}
@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");
@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[] {})) {
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();
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");
}
}
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;
}
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}.
*/
@Test
public void testSmartyDecrypter() {
- final AtomicReference<String> telegramResult = new AtomicReference<>("");
- final P1TelegramListener telegramListener = telegram -> telegramResult.set(telegram.getRawTelegram());
- final SmartyDecrypter decoder = new SmartyDecrypter(new P1TelegramParser(telegramListener),
- new DSMRTelegramListener(KEY, ""), KEY, "");
+ final AtomicReference<String> dataRead = new AtomicReference<>();
+ final SmartyDecrypter decoder = new SmartyDecrypter(new TelegramParser() {
+ @Override
+ public void parse(final byte[] data, final int length) {
+ dataRead.set(new String(data, StandardCharsets.UTF_8));
+ }
+ }, new DSMRTelegramListener(KEY, ""), KEY, "");
decoder.setLenientMode(true);
final byte[] data = new byte[TELEGRAM.length];
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)));
}
}
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}.
@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,
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;
@ParameterizedTest
@MethodSource("data")
public void testDetectMeters(final String telegramName, final Set<DSMRMeterType> expectedMeters) {
- final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
+ final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName);
final DSMRMeterDetector detector = new DSMRMeterDetector();
final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> entry = detector.detectMeters(telegram);
final Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
*/
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;
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;
*/
@Test
public void testInvalidConfiguredMeters() {
- P1Telegram expected = TelegramReaderUtil.readTelegram(EXPECTED_CONFIGURED_TELEGRAM, TelegramState.OK);
- AtomicReference<List<DSMRMeterType>> invalidConfiguredRef = new AtomicReference<>();
- AtomicReference<List<DSMRMeterType>> unconfiguredRef = new AtomicReference<>();
- DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
+ final P1Telegram expected = TelegramReaderUtil.readTelegram(EXPECTED_CONFIGURED_TELEGRAM);
+ final AtomicReference<List<DSMRMeterType>> invalidConfiguredRef = new AtomicReference<>();
+ final AtomicReference<List<DSMRMeterType>> unconfiguredRef = new AtomicReference<>();
+ final DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
@Override
- protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
- List<DSMRMeterType> unconfiguredMeters) {
+ protected void reportConfigurationValidationResults(final List<DSMRMeterType> invalidConfigured,
+ final List<DSMRMeterType> unconfiguredMeters) {
super.reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
invalidConfiguredRef.set(invalidConfigured);
unconfiguredRef.set(unconfiguredMeters);
// Mock the invalid configuration by reading a telegram that is valid for a meter that is a subset of the
// expected meter.
- List<DSMRMeterDescriptor> invalidConfiguredMeterDescriptors = EnumSet.of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0)
- .stream().map(mt -> new DSMRMeterDescriptor(mt, 0)).collect(Collectors.toList());
- List<Thing> things = invalidConfiguredMeterDescriptors.stream().map(m -> thing).collect(Collectors.toList());
- AtomicReference<Iterator<DSMRMeterDescriptor>> detectMetersRef = new AtomicReference<>();
+ final List<DSMRMeterDescriptor> invalidConfiguredMeterDescriptors = EnumSet
+ .of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0).stream().map(mt -> new DSMRMeterDescriptor(mt, 0))
+ .collect(Collectors.toList());
+ final List<Thing> things = invalidConfiguredMeterDescriptors.stream().map(m -> thing)
+ .collect(Collectors.toList());
+ final AtomicReference<Iterator<DSMRMeterDescriptor>> detectMetersRef = new AtomicReference<>();
+
when((meterHandler).getMeterDescriptor()).then(a -> {
if (detectMetersRef.get() == null || !detectMetersRef.get().hasNext()) {
detectMetersRef.set(invalidConfiguredMeterDescriptors.iterator());
*/
@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();
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}.
*/
@Test
public void testFilterMeterValues() {
- final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK)
- .getCosemObjects();
+ final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50").getCosemObjects();
assertMeterValues(cosemObjects, DSMRMeterType.DEVICE_V5, DSMRMeterConstants.UNKNOWN_CHANNEL, 3);
assertMeterValues(cosemObjects, DSMRMeterType.ELECTRICITY_V5_0, 0, 29);
assertMeterValues(cosemObjects, DSMRMeterType.M3_V5_0, 1, 3);
}
- private void assertMeterValues(List<CosemObject> cosemObjects, DSMRMeterType type, int channel, int expected) {
+ private void assertMeterValues(final List<CosemObject> cosemObjects, final DSMRMeterType type, final int channel,
+ final int expected) {
final DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(type, channel);
final DSMRMeter meter = new DSMRMeter(descriptor);
final List<CosemObject> filterMeterValues = meter.filterMeterValues(cosemObjects, channel);