[dsmr] Improved error handling corrupt messages, discovery additional key bug fix (#14325)

* [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>
This commit is contained in:
Hilbrand Bouwkamp 2023-02-27 17:36:02 +01:00 committed by GitHub
parent c95380f503
commit 03e3b6aae2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 563 additions and 443 deletions

View File

@ -47,7 +47,7 @@ public final class DSMRBindingConstants {
public static final String CONFIGURATION_DECRYPTION_KEY = "decryptionKey";
public static final String CONFIGURATION_DECRYPTION_KEY_EMPTY = "";
public static final String CONFIGURATION_ADDITIONAL_KEY = "additionalKey";
public static final String ADDITIONAL_KEY_DEFAULT = "3000112233445566778899AABBCCDDEEFF";
public static final String CONFIGURATION_ADDITIONAL_KEY_DEFAULT = "3000112233445566778899AABBCCDDEEFF";
private DSMRBindingConstants() {
// Constants class

View File

@ -55,7 +55,7 @@ public class DSMRDeviceConfiguration {
/**
* Austria smart meter additional decryption key
*/
public String additionalKey = DSMRBindingConstants.ADDITIONAL_KEY_DEFAULT;
public String additionalKey = DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY_DEFAULT;
/**
* When no message was received after the configured number of seconds action will be taken.

View File

@ -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();

View File

@ -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);
}

View File

@ -15,6 +15,7 @@ package org.openhab.binding.dsmr.internal.device;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialConnector;
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
import org.openhab.core.io.transport.serial.SerialPortManager;
/**
@ -36,14 +37,15 @@ public class DSMRFixedConfigDevice implements DSMRDevice {
* @param serialPortManager the manager to get a new serial port connecting from
* @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
* @param fixedPortSettings The serial port connection settings
* @param listener the parent {@link DSMREventListener}
* @param listener the parent {@link P1TelegramListener}
* @param telegramListener listener to report found telegrams or errors
*/
public DSMRFixedConfigDevice(SerialPortManager serialPortManager, String serialPortName,
DSMRSerialSettings fixedPortSettings, DSMREventListener listener, DSMRTelegramListener telegramListener) {
public DSMRFixedConfigDevice(final SerialPortManager serialPortManager, final String serialPortName,
final DSMRSerialSettings fixedPortSettings, final P1TelegramListener listener,
final DSMRTelegramListener telegramListener) {
this.fixedPortSettings = fixedPortSettings;
this.telegramListener = telegramListener;
telegramListener.setDsmrEventListener(listener);
telegramListener.setP1TelegramListener(listener);
dsmrPort = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
}
@ -64,7 +66,7 @@ public class DSMRFixedConfigDevice implements DSMRDevice {
}
@Override
public void setLenientMode(boolean lenientMode) {
public void setLenientMode(final boolean lenientMode) {
telegramListener.setLenientMode(lenientMode);
}
}

View File

@ -18,10 +18,11 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialConnector;
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -34,7 +35,7 @@ import org.slf4j.LoggerFactory;
* settings automatically.
*/
@NonNullByDefault
public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
public class DSMRSerialAutoDevice implements DSMRDevice, P1TelegramListener {
/**
* Enum to keep track of the internal state of {@link DSMRSerialAutoDevice}.
@ -113,7 +114,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
/**
* The listener of the class handling the connector events
*/
private DSMREventListener parentListener;
private final P1TelegramListener parentListener;
/**
* Time in nanos the last time the baudrate was switched. This is used during discovery ignore errors retrieved
@ -126,20 +127,20 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
*
* @param serialPortManager the manager to get a new serial port connecting from
* @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
* @param listener the parent {@link DSMREventListener}
* @param listener the parent {@link P1TelegramListener}
* @param telegramListener listener to report found telegrams or errors
* @param scheduler the scheduler to use with the baudrate switching timers
* @param baudrateSwitchTimeoutSeconds timeout period for when to try other baudrate settings and end the discovery
* of the baudrate
*/
public DSMRSerialAutoDevice(SerialPortManager serialPortManager, String serialPortName, DSMREventListener listener,
DSMRTelegramListener telegramListener, ScheduledExecutorService scheduler,
int baudrateSwitchTimeoutSeconds) {
public DSMRSerialAutoDevice(final SerialPortManager serialPortManager, final String serialPortName,
final P1TelegramListener listener, final DSMRTelegramListener telegramListener,
final ScheduledExecutorService scheduler, final int baudrateSwitchTimeoutSeconds) {
this.parentListener = listener;
this.scheduler = scheduler;
this.baudrateSwitchTimeoutSeconds = baudrateSwitchTimeoutSeconds;
this.telegramListener = telegramListener;
telegramListener.setDsmrEventListener(listener);
telegramListener.setP1TelegramListener(listener);
dsmrConnector = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
logger.debug("Initialized port '{}'", serialPortName);
}
@ -148,7 +149,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
public void start() {
stopDiscover(DeviceState.DISCOVER_SETTINGS);
portSettings = DEFAULT_PORT_SETTINGS;
telegramListener.setDsmrEventListener(this);
telegramListener.setP1TelegramListener(this);
dsmrConnector.open(portSettings);
restartHalfTimer();
endTimeTimer = scheduler.schedule(this::endTimeScheduledCall,
@ -178,30 +179,28 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
* @param telegram the details of the received telegram
*/
@Override
public void handleTelegramReceived(P1Telegram telegram) {
if (!telegram.getCosemObjects().isEmpty()) {
public void telegramReceived(final P1Telegram telegram) {
stopDiscover(DeviceState.NORMAL);
parentListener.handleTelegramReceived(telegram);
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;
}
/**

View File

@ -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,20 +90,21 @@ public class DSMRTelegramListener implements P1TelegramListener, DSMRConnectorLi
*/
@Override
public void telegramReceived(final P1Telegram telegram) {
final TelegramState telegramState = telegram.getTelegramState();
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
if (logger.isTraceEnabled()) {
logger.trace("Received {} Cosem Objects with state: '{}'", cosemObjects.size(), telegramState);
logger.trace("Received {} Cosem Objects", cosemObjects.size());
}
if (telegramState == TelegramState.OK || telegramState == TelegramState.INVALID_ENCRYPTION_KEY) {
dsmrEventListener.handleTelegramReceived(telegram);
if (cosemObjects.isEmpty()) {
onError(DSMRErrorStatus.TELEGRAM_NO_DATA, "");
} else {
if (logger.isDebugEnabled()) {
logger.debug("Telegram received with error state '{}': {}", telegramState,
cosemObjects.stream().map(CosemObject::toString).collect(Collectors.joining(",")));
p1TelegramListener.telegramReceived(telegram);
}
}
@Override
public void onError(final DSMRErrorStatus state, final String message) {
p1TelegramListener.onError(state, message);
}
/**

View File

@ -16,7 +16,8 @@ import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Arrays;
import java.util.Optional;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@ -28,8 +29,7 @@ import javax.crypto.spec.SecretKeySpec;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
import org.openhab.core.util.HexUtils;
@ -53,8 +53,7 @@ public class SmartyDecrypter implements TelegramParser {
READ_SEPARATOR_30,
READ_FRAME_COUNTER,
READ_PAYLOAD,
READ_GCM_TAG,
DONE_READING_TELEGRAM
READ_GCM_TAG
}
private static final byte START_BYTE = (byte) 0xDB;
@ -95,7 +94,7 @@ public class SmartyDecrypter implements TelegramParser {
this.parser = parser;
this.telegramListener = telegramListener;
secretKeySpec = decryptionKey.isEmpty() ? null : new SecretKeySpec(HexUtils.hexToBytes(decryptionKey), "AES");
addKey = HexUtils.hexToBytes(additionalKey.isBlank() ? DSMRBindingConstants.ADDITIONAL_KEY_DEFAULT
addKey = HexUtils.hexToBytes(additionalKey.isBlank() ? DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY_DEFAULT
: ((additionalKey.length() == 32 ? (ADDITIONAL_ADD_PREFIX) : "") + additionalKey));
}
@ -113,6 +112,11 @@ public class SmartyDecrypter implements TelegramParser {
}
private boolean processStateActions(final byte rawInput) {
// Safeguard against buffer overrun in case corrupt data is received.
if (ivLength == IV_BUFFER_LENGTH) {
reset();
return false;
}
switch (state) {
case WAITING_FOR_START_BYTE:
if (rawInput == START_BYTE) {
@ -179,27 +183,24 @@ public class SmartyDecrypter implements TelegramParser {
// All input has been read.
cipherText.put(rawInput);
if (currentBytePosition >= changeToNextStateAt) {
state = State.DONE_READING_TELEGRAM;
}
break;
}
if (state == State.DONE_READING_TELEGRAM) {
state = State.WAITING_FOR_START_BYTE;
return true;
}
break;
}
return false;
}
private void processCompleted() {
try {
final byte[] plainText = decrypt();
reset();
if (plainText == null) {
telegramListener
.telegramReceived(new P1Telegram(Collections.emptyList(), TelegramState.INVALID_ENCRYPTION_KEY));
} else {
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;
}

View File

@ -15,6 +15,7 @@ package org.openhab.binding.dsmr.internal.device.connector;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -36,7 +37,7 @@ class DSMRBaseConnector {
/**
* Listener to send received data and errors to.
*/
protected final DSMRConnectorListener dsmrConnectorListener;
private final DSMRConnectorListener dsmrConnectorListener;
/**
* 1Kbyte buffer for storing received data.
@ -53,7 +54,7 @@ class DSMRBaseConnector {
*/
private boolean open;
public DSMRBaseConnector(DSMRConnectorListener connectorListener) {
public DSMRBaseConnector(final DSMRConnectorListener connectorListener) {
this.dsmrConnectorListener = connectorListener;
}
@ -68,7 +69,7 @@ class DSMRBaseConnector {
* @param inputStream input stream to read data from
* @throws IOException throws exception in case input stream is null
*/
protected void open(@Nullable InputStream inputStream) throws IOException {
protected void open(@Nullable final InputStream inputStream) throws IOException {
if (inputStream == null) {
throw new IOException("Inputstream is null");
}
@ -91,7 +92,7 @@ class DSMRBaseConnector {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ioe) {
} catch (final IOException ioe) {
logger.debug("Failed to close reader", ioe);
}
}
@ -104,12 +105,12 @@ class DSMRBaseConnector {
protected void handleDataAvailable() {
try {
synchronized (readLock) {
BufferedInputStream localInputStream = inputStream;
final BufferedInputStream localInputStream = inputStream;
if (localInputStream != null) {
int bytesAvailable = localInputStream.available();
while (bytesAvailable > 0) {
int bytesAvailableRead = localInputStream.read(buffer, 0,
final int bytesAvailableRead = localInputStream.read(buffer, 0,
Math.min(bytesAvailable, buffer.length));
if (open && bytesAvailableRead > 0) {
@ -122,8 +123,9 @@ class DSMRBaseConnector {
}
}
}
} catch (IOException e) {
dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
} catch (final IOException e) {
dsmrConnectorListener.handleError(DSMRErrorStatus.SERIAL_DATA_READ_ERROR,
Optional.ofNullable(e.getMessage()).orElse(""));
logger.debug("Exception on read data", e);
}
}

View File

@ -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();
}
}

View File

@ -23,11 +23,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
public interface DSMRConnectorListener {
/**
* Callback for {@link DSMRConnectorErrorEvent} events.
* Callback for {@link DSMRErrorStatus} events.
*
* @param portEvent {@link DSMRConnectorErrorEvent} that has occurred
* @param errorStatus {@link DSMRErrorStatus} that has occurred
* @param message Additional error message
*/
public void handleErrorEvent(DSMRConnectorErrorEvent portEvent);
void handleError(DSMRErrorStatus errorStatus, String message);
/**
* Handle data.

View File

@ -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);
}
}

View File

@ -14,6 +14,7 @@ package org.openhab.binding.dsmr.internal.device.connector;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import java.util.TooManyListenersException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@ -67,7 +68,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
/**
* Serial port instance.
*/
private AtomicReference<@Nullable SerialPort> serialPortReference = new AtomicReference<>();
private final AtomicReference<@Nullable SerialPort> serialPortReference = new AtomicReference<>();
/**
* DSMR Connector listener.
@ -89,8 +90,8 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
* @param serialPortName Device identifier of the port (e.g. /dev/ttyUSB0)
* @param dsmrConnectorListener The listener to send error or received data from the port
*/
public DSMRSerialConnector(SerialPortManager portManager, String serialPortName,
DSMRConnectorListener dsmrConnectorListener) {
public DSMRSerialConnector(final SerialPortManager portManager, final String serialPortName,
final DSMRConnectorListener dsmrConnectorListener) {
super(dsmrConnectorListener);
this.portManager = portManager;
this.serialPortName = serialPortName;
@ -106,35 +107,35 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
*
* @param portSettings The serial port settings to open the port with
*/
public void open(DSMRSerialSettings portSettings) {
DSMRConnectorErrorEvent errorEvent = null;
public void open(final DSMRSerialSettings portSettings) {
DSMRErrorStatus errorStatus = null;
synchronized (portLock) {
SerialPortIdentifier portIdentifier = portManager.getIdentifier(serialPortName);
final SerialPortIdentifier portIdentifier = portManager.getIdentifier(serialPortName);
if (portIdentifier == null) {
logger.debug("Port {} does not exists", serialPortName);
errorEvent = DSMRConnectorErrorEvent.DONT_EXISTS;
errorStatus = DSMRErrorStatus.PORT_DONT_EXISTS;
} else {
errorEvent = open(portSettings, portIdentifier);
errorStatus = open(portSettings, portIdentifier);
}
if (errorEvent != null) {
if (errorStatus != null) {
// handle event within lock
dsmrConnectorListener.handleErrorEvent(errorEvent);
dsmrConnectorListener.handleError(errorStatus, "");
}
}
}
private @Nullable DSMRConnectorErrorEvent open(DSMRSerialSettings portSettings,
SerialPortIdentifier portIdentifier) {
DSMRConnectorErrorEvent errorEvent = null;
private @Nullable DSMRErrorStatus open(final DSMRSerialSettings portSettings,
final SerialPortIdentifier portIdentifier) {
DSMRErrorStatus errorStatus = null;
try {
logger.trace("Opening port {}", serialPortName);
SerialPort oldSerialPort = serialPortReference.get();
final SerialPort oldSerialPort = serialPortReference.get();
// Opening Operating System Serial Port
SerialPort serialPort = portIdentifier.open(DSMRBindingConstants.DSMR_PORT_NAME,
final SerialPort serialPort = portIdentifier.open(DSMRBindingConstants.DSMR_PORT_NAME,
SERIAL_PORT_READ_TIMEOUT_MILLISECONDS);
// Configure Serial Port based on specified port speed
@ -157,39 +158,39 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
try {
serialPort.enableReceiveThreshold(SERIAL_TIMEOUT_MILLISECONDS);
} catch (UnsupportedCommOperationException e) {
} catch (final UnsupportedCommOperationException e) {
logger.debug("Enable receive threshold is unsupported");
}
try {
serialPort.enableReceiveTimeout(SERIAL_TIMEOUT_MILLISECONDS);
} catch (UnsupportedCommOperationException e) {
} catch (final UnsupportedCommOperationException e) {
logger.debug("Enable receive timeout is unsupported");
}
// The binding is ready, let the meter know we want to receive values
serialPort.setRTS(true);
if (!serialPortReference.compareAndSet(oldSerialPort, serialPort)) {
logger.warn("Possible bug because a new serial port value was set during opening new port.");
errorEvent = DSMRConnectorErrorEvent.INTERNAL_ERROR;
errorStatus = DSMRErrorStatus.PORT_INTERNAL_ERROR;
}
} catch (IOException ioe) {
} catch (final IOException ioe) {
logger.debug("Failed to get inputstream for serialPort", ioe);
errorEvent = DSMRConnectorErrorEvent.READ_ERROR;
} catch (TooManyListenersException tmle) {
errorStatus = DSMRErrorStatus.SERIAL_DATA_READ_ERROR;
} catch (final TooManyListenersException tmle) {
logger.warn("Possible bug because a listener was added while one already set.", tmle);
errorEvent = DSMRConnectorErrorEvent.INTERNAL_ERROR;
} catch (PortInUseException piue) {
errorStatus = DSMRErrorStatus.PORT_INTERNAL_ERROR;
} catch (final PortInUseException piue) {
logger.debug("Port already in use: {}", serialPortName, piue);
errorEvent = DSMRConnectorErrorEvent.IN_USE;
} catch (UnsupportedCommOperationException ucoe) {
errorStatus = DSMRErrorStatus.PORT_IN_USE;
} catch (final UnsupportedCommOperationException ucoe) {
logger.debug("Port does not support requested port settings (invalid dsmr:portsettings parameter?): {}",
serialPortName, ucoe);
errorEvent = DSMRConnectorErrorEvent.NOT_COMPATIBLE;
errorStatus = DSMRErrorStatus.PORT_NOT_COMPATIBLE;
}
return errorEvent;
return errorStatus;
}
/**
@ -208,12 +209,12 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
serialPort.setRTS(false);
serialPort.removeEventListener();
try {
InputStream inputStream = serialPort.getInputStream();
final InputStream inputStream = serialPort.getInputStream();
if (inputStream != null) {
inputStream.close();
}
} catch (IOException ioe) {
} catch (final IOException ioe) {
logger.debug("Failed to close serial port inputstream", ioe);
}
serialPort.close();
@ -228,22 +229,23 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
*
* @param portSettings the port settings to set on the serial port
*/
public void setSerialPortParams(DSMRSerialSettings portSettings) {
public void setSerialPortParams(final DSMRSerialSettings portSettings) {
synchronized (portLock) {
if (isOpen()) {
logger.debug("Update port {} with settings: {}", this.serialPortName, portSettings);
try {
SerialPort serialPort = serialPortReference.get();
final SerialPort serialPort = serialPortReference.get();
if (serialPort != null) {
serialPort.setSerialPortParams(portSettings.getBaudrate(), portSettings.getDataBits(),
portSettings.getStopbits(), portSettings.getParity());
}
} catch (UnsupportedCommOperationException e) {
} catch (final UnsupportedCommOperationException e) {
logger.debug(
"Port does {} not support requested port settings (invalid dsmr:portsettings parameter?): {}",
serialPortName, portSettings);
dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.NOT_COMPATIBLE);
dsmrConnectorListener.handleError(DSMRErrorStatus.PORT_NOT_COMPATIBLE,
Optional.ofNullable(e.getMessage()).orElse(""));
}
} else {
restart(portSettings);
@ -254,7 +256,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
/**
* Switch the Serial Port speed (LOW --> HIGH and vice versa).
*/
public void restart(DSMRSerialSettings portSettings) {
public void restart(final DSMRSerialSettings portSettings) {
synchronized (portLock) {
logger.trace("Restart port {} with settings: {}", this.serialPortName, portSettings);
close();
@ -263,7 +265,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
}
@Override
public void serialEvent(@Nullable SerialPortEvent seEvent) {
public void serialEvent(@Nullable final SerialPortEvent seEvent) {
if (seEvent == null) {
return;
}
@ -289,7 +291,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
break;
default: // do nothing
}
} catch (RuntimeException e) {
} catch (final RuntimeException e) {
logger.warn("RuntimeException during handling serial event: {}", seEvent.getEventType(), e);
}
}
@ -300,10 +302,10 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
* @param typeName type of the event, used in logging only
* @param portEvent Serial port event that triggered the error.
*/
private void handleErrorEvent(String typeName, SerialPortEvent portEvent) {
private void handleErrorEvent(final String typeName, final SerialPortEvent portEvent) {
if (isOpen() && portEvent.getNewValue()) {
logger.trace("New DSMR port {} event", typeName);
dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
dsmrConnectorListener.handleError(DSMRErrorStatus.SERIAL_DATA_READ_ERROR, "");
}
}

View File

@ -38,6 +38,11 @@ public class OBISIdentifier {
*/
private static final Pattern OBIS_ID_PATTERN = Pattern.compile(OBISID_REGEX);
/**
* Value to return when an invalid int was read.
*/
private static final int INVALID_INT_READ = -1;
/* the six individual group values of the OBIS ID */
private final int groupA;
private final @Nullable Integer channel;
@ -91,25 +96,41 @@ public class OBISIdentifier {
if (m.matches()) {
// Optional value A
this.groupA = m.group(2) == null ? null : Integer.parseInt(m.group(2));
this.groupA = safeInt(m.group(2));
// Optional value B
this.channel = m.group(4) == null ? null : Integer.valueOf(m.group(4));
this.channel = safeInteger(m.group(4));
// Required value C & D
this.groupC = Integer.parseInt(m.group(6));
this.groupD = Integer.parseInt(m.group(7));
this.groupC = safeInt(m.group(6));
this.groupD = safeInt(m.group(7));
// Optional value E
this.groupE = m.group(9) == null ? null : Integer.valueOf(m.group(9));
this.groupE = safeInteger(m.group(9));
// Optional value F
this.groupF = m.group(11) == null ? null : Integer.valueOf(m.group(11));
this.groupF = safeInteger(m.group(11));
} else {
throw new ParseException("Invalid OBIS identifier:" + obisIDString, 0);
}
}
private static int safeInt(final @Nullable String value) {
try {
return value == null ? INVALID_INT_READ : Integer.parseInt(value);
} catch (final NumberFormatException e) {
return INVALID_INT_READ;
}
}
private static @Nullable Integer safeInteger(final @Nullable String value) {
try {
return value == null ? null : Integer.valueOf(value);
} catch (final NumberFormatException e) {
return null;
}
}
public boolean isConflict() {
return conflict;
}

View File

@ -31,54 +31,18 @@ public class P1Telegram {
/**
* The TelegramState described the meta data of the P1Telegram
*/
public enum TelegramState {
/**
* OK. Telegram was successful received and CRC16 checksum is verified (CRC16 only for DSMR V4 and up)
*/
OK("P1 telegram received OK"),
/**
* CRC_ERROR. CRC16 checksum failed (only DSMR V4 and up)
*/
CRC_ERROR("CRC checksum failed for received P1 telegram"),
/**
* DATA_CORRUPTION. The P1 telegram has syntax errors.
*/
DATA_CORRUPTION("Received P1 telegram is corrupted"),
/**
* P1TelegramListener. The smarty telegram was successful received but could not be decoded because of an
* invalid
* encryption key.
*/
INVALID_ENCRYPTION_KEY("Failed to decrypt P1 telegram due to invalid encryption key");
/**
* public accessible state details
*/
public final String stateDetails;
/**
* Constructs a new TelegramState enum
*
* @param stateDetails String containing the details of this TelegramState
*/
private TelegramState(String stateDetails) {
this.stateDetails = stateDetails;
}
}
private final List<CosemObject> cosemObjects;
private final TelegramState telegramState;
private final String rawTelegram;
private final List<Entry<String, String>> unknownCosemObjects;
public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState) {
this(cosemObjects, telegramState, "", Collections.emptyList());
public P1Telegram(final List<CosemObject> cosemObjects) {
this(cosemObjects, "", Collections.emptyList());
}
public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState, String rawTelegram,
List<Entry<String, String>> unknownCosemObjects) {
public P1Telegram(final List<CosemObject> cosemObjects, final String rawTelegram,
final List<Entry<String, String>> unknownCosemObjects) {
this.cosemObjects = cosemObjects;
this.telegramState = telegramState;
this.rawTelegram = rawTelegram;
this.unknownCosemObjects = unknownCosemObjects;
}
@ -97,13 +61,6 @@ public class P1Telegram {
return rawTelegram;
}
/**
* @return The state of the telegram
*/
public TelegramState getTelegramState() {
return telegramState;
}
/**
* @return The list of CosemObject found in the telegram but not known to the binding
*/

View File

@ -13,6 +13,7 @@
package org.openhab.binding.dsmr.internal.device.p1telegram;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
/**
* Interface for receiving CosemObjects that come from a P1 Telegram
@ -23,10 +24,18 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
@NonNullByDefault
public interface P1TelegramListener {
/**
* Called when reading the telegram failed. Passes the failed state and optional an additional error message.
*
* @param errorStatus error state
* @param message optional additional message
*/
void onError(DSMRErrorStatus errorStatus, String message);
/**
* Callback on received telegram.
*
* @param telegram The received telegram
*/
public void telegramReceived(P1Telegram telegram);
void telegramReceived(P1Telegram telegram);
}

View File

@ -18,12 +18,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectFactory;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -106,7 +107,7 @@ public class P1TelegramParser implements TelegramParser {
/**
* Current telegram state
*/
private volatile TelegramState telegramState;
private volatile Optional<DSMRErrorStatus> telegramState = Optional.empty();
/**
* CosemObjectFactory helper class
@ -116,7 +117,7 @@ public class P1TelegramParser implements TelegramParser {
/**
* Received Cosem Objects in the P1Telegram that is currently received
*/
private final List<CosemObject> cosemObjects = new ArrayList<>();
private final List<Entry<String, String>> cosemObjects = new ArrayList<>();
/**
* List of Cosem Object values that are not known to this binding.
@ -138,18 +139,18 @@ public class P1TelegramParser implements TelegramParser {
*
* @param telegramListener
*/
public P1TelegramParser(P1TelegramListener telegramListener) {
public P1TelegramParser(final P1TelegramListener telegramListener) {
this(telegramListener, false);
}
public P1TelegramParser(P1TelegramListener telegramListener, boolean test) {
public P1TelegramParser(final P1TelegramListener telegramListener, final boolean test) {
this.telegramListener = telegramListener;
this.test = test;
factory = new CosemObjectFactory();
state = State.WAIT_FOR_START;
crc = new CRC16(CRC16.Polynom.CRC16_IBM);
telegramState = TelegramState.OK;
telegramState = Optional.empty();
}
/**
@ -159,7 +160,7 @@ public class P1TelegramParser implements TelegramParser {
* @param length number of bytes to parse
*/
@Override
public void parse(byte[] data, int length) {
public void parse(final byte[] data, final int length) {
if (lenientMode || logger.isTraceEnabled()) {
final String rawBlock = new String(data, 0, length, StandardCharsets.UTF_8);
@ -254,10 +255,11 @@ public class P1TelegramParser implements TelegramParser {
if (c == '\r' || c == '/') {
logger.trace("telegramState {}, crcValue to check 0x{}", telegramState, crcValue);
// Only perform CRC check if telegram is still ok
if (telegramState == TelegramState.OK && crcValue.length() > 0) {
telegramState = checkCRC(telegramState);
if (telegramState.isEmpty() && crcValue.length() > 0) {
telegramState = checkCRC();
}
telegramListener.telegramReceived(constructTelegram());
processTelegram();
reset();
if (c == '/') {
/*
@ -275,8 +277,8 @@ public class P1TelegramParser implements TelegramParser {
logger.trace("State after parsing: {}", state);
}
private TelegramState checkCRC(TelegramState currentState) {
final TelegramState telegramState;
private Optional<DSMRErrorStatus> checkCRC() {
final Optional<DSMRErrorStatus> telegramState;
if (Pattern.matches(CRC_PATTERN, crcValue)) {
final int crcP1Telegram = Integer.parseInt(crcValue.toString(), 16);
@ -293,24 +295,45 @@ public class P1TelegramParser implements TelegramParser {
}
logger.trace("CRC value does not match, p1 Telegram failed");
telegramState = TelegramState.CRC_ERROR;
telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_CRC_ERROR);
} else {
telegramState = currentState;
telegramState = Optional.empty();
}
} else {
telegramState = TelegramState.CRC_ERROR;
telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_CRC_ERROR);
}
return telegramState;
}
private P1Telegram constructTelegram() {
final List<CosemObject> cosemObjectsCopy = new ArrayList<>(cosemObjects);
private void processTelegram() {
telegramState.ifPresentOrElse(error -> telegramListener.onError(error, ""),
() -> telegramListener.telegramReceived(constructTelegram()));
}
private P1Telegram constructTelegram() {
final List<CosemObject> cosemObjectsCopy = new ArrayList<>();
cosemObjects.stream().forEach(e -> addCosemObject(cosemObjectsCopy, e));
if (lenientMode) {
return new P1Telegram(cosemObjectsCopy, telegramState, rawData.toString(),
return new P1Telegram(cosemObjectsCopy, rawData.toString(),
unknownCosemObjects.isEmpty() ? Collections.emptyList() : new ArrayList<>(unknownCosemObjects));
} else {
return new P1Telegram(cosemObjectsCopy, telegramState);
return new P1Telegram(cosemObjectsCopy);
}
}
private void addCosemObject(final List<CosemObject> objects, final Entry<String, String> cosemEntry) {
final String obisIdString = cosemEntry.getKey();
final String obisValueString = cosemEntry.getValue();
final CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);
if (cosemObject == null) {
if (lenientMode) {
unknownCosemObjects.add(new SimpleEntry<>(obisIdString, obisValueString));
}
} else {
logger.trace("Adding {} to list of Cosem Objects", cosemObject);
objects.add(cosemObject);
}
}
@ -324,10 +347,11 @@ public class P1TelegramParser implements TelegramParser {
*
* @param c the unexpected character
*/
private void handleUnexpectedCharacter(char c) {
private void handleUnexpectedCharacter(final char c) {
logger.debug("Unexpected character '{}' in state: {}. This P1 telegram is marked as failed", c, state);
telegramState = TelegramState.DATA_CORRUPTION;
telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_DATA_CORRUPTION);
telegramListener.onError(DSMRErrorStatus.TELEGRAM_DATA_CORRUPTION, "");
}
/**
@ -335,7 +359,7 @@ public class P1TelegramParser implements TelegramParser {
*
* @param c the character to process
*/
private void handleCharacter(char c) {
private void handleCharacter(final char c) {
switch (state) {
case WAIT_FOR_START:
// ignore the data
@ -401,17 +425,7 @@ public class P1TelegramParser implements TelegramParser {
final String obisIdString = obisId.toString();
if (!obisIdString.isEmpty()) {
final String obisValueString = obisValue.toString();
final CosemObject cosemObject = factory.getCosemObject(obisIdString, obisValueString);
if (cosemObject == null) {
if (lenientMode) {
unknownCosemObjects.add(new SimpleEntry<>(obisIdString, obisValueString));
}
} else {
logger.trace("Adding {} to list of Cosem Objects", cosemObject);
cosemObjects.add(cosemObject);
}
cosemObjects.add(new SimpleEntry<String, String>(obisIdString, obisValue.toString()));
}
clearObisData();
}
@ -419,7 +433,7 @@ public class P1TelegramParser implements TelegramParser {
/**
* @param newState the new state to set
*/
private void setState(State newState) {
private void setState(final State newState) {
synchronized (state) {
switch (newState) {
case HEADER:
@ -429,7 +443,7 @@ public class P1TelegramParser implements TelegramParser {
case WAIT_FOR_START:
// Clears internal state data and mark current telegram as OK
clearInternalData();
telegramState = TelegramState.OK;
telegramState = Optional.empty();
break;
case DATA_OBIS_ID:
// If the current state is CRLF we are processing the header and don't have a cosem object yet
@ -448,7 +462,7 @@ public class P1TelegramParser implements TelegramParser {
}
@Override
public void setLenientMode(boolean lenientMode) {
public void setLenientMode(final boolean lenientMode) {
this.lenientMode = lenientMode;
}
}

View File

@ -13,6 +13,7 @@
package org.openhab.binding.dsmr.internal.discovery;
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY;
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY_DEFAULT;
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY;
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY_EMPTY;
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_SERIAL_PORT;
@ -28,13 +29,12 @@ import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
import org.openhab.binding.dsmr.internal.device.DSMREventListener;
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
@ -69,7 +69,7 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, configurationPid = "discovery.dsmr")
public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements DSMREventListener {
public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements P1TelegramListener {
/**
* The timeout used to switch baudrate if no valid data is received within that time frame.
@ -133,7 +133,7 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
logger.debug("Start discovery on serial port: {}", currentScannedPortName);
//
final DSMRTelegramListener telegramListener = new DSMRTelegramListener("",
CONFIGURATION_ADDITIONAL_KEY);
CONFIGURATION_ADDITIONAL_KEY_DEFAULT);
final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager,
portIdentifier.getName(), this, telegramListener, scheduler,
BAUDRATE_SWITCH_TIMEOUT_SECONDS);
@ -173,20 +173,25 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
* @param telegram the received telegram
*/
@Override
public void handleTelegramReceived(final P1Telegram telegram) {
public void telegramReceived(final P1Telegram telegram) {
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
if (logger.isDebugEnabled()) {
logger.debug("[{}] Received {} cosemObjects", currentScannedPortName, cosemObjects.size());
}
if (telegram.getTelegramState() == TelegramState.INVALID_ENCRYPTION_KEY) {
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();
}
@Override
public void onError(final DSMRErrorStatus portEvent, final String message) {
if (portEvent == DSMRErrorStatus.INVALID_DECRYPTION_KEY) {
bridgeDiscovered(THING_TYPE_SMARTY_BRIDGE);
} 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();
}
}

View File

@ -21,6 +21,7 @@ import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
@ -74,7 +75,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
}
@Override
public void setThingHandler(ThingHandler handler) {
public void setThingHandler(final ThingHandler handler) {
if (handler instanceof DSMRBridgeHandler) {
dsmrBridgeHandler = (DSMRBridgeHandler) handler;
}
@ -96,7 +97,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
}
@Override
public void telegramReceived(P1Telegram telegram) {
public void telegramReceived(final P1Telegram telegram) {
if (logger.isDebugEnabled()) {
logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
}
@ -108,7 +109,12 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
detectedMeters.getKey().forEach(m -> meterDiscovered(m, dsmrBridgeHandler.getThing().getUID()));
}
protected void verifyUnregisteredCosemObjects(P1Telegram telegram, List<CosemObject> list) {
@Override
public void onError(final DSMRErrorStatus state, final String message) {
logger.info("Telegram could not be parsed correctly, failed with state: {}, {}", state, message);
}
protected void verifyUnregisteredCosemObjects(final P1Telegram telegram, final List<CosemObject> list) {
if (!list.isEmpty()) {
if (list.stream()
.anyMatch(e -> e.getType() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
@ -138,7 +144,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
*
* @param list Map with the unrecognized.
*/
protected void reportUnrecognizedCosemObjects(List<CosemObject> list) {
protected void reportUnrecognizedCosemObjects(final List<CosemObject> list) {
list.forEach(c -> logger.info("Unrecognized cosem object '{}' found in the data: {}", c.getType(), c));
}
@ -158,7 +164,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
* @param things The list of configured things
* @param configuredMeterTypes The set of meters detected in the telegram
*/
private void validateConfiguredMeters(List<Thing> things, Set<DSMRMeterType> configuredMeterTypes) {
private void validateConfiguredMeters(final List<Thing> things, final Set<DSMRMeterType> configuredMeterTypes) {
// @formatter:off
final Set<DSMRMeterType> configuredMeters = things.stream()
.map(Thing::getHandler)
@ -188,8 +194,8 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
* @param invalidConfigured The list of invalid configured meters
* @param unconfiguredMeters The list of meters that were detected, but not configured
*/
protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
List<DSMRMeterType> unconfiguredMeters) {
protected void reportConfigurationValidationResults(final List<DSMRMeterType> invalidConfigured,
final List<DSMRMeterType> unconfiguredMeters) {
logger.info(
"Possible incorrect meters configured. These are configured: {}."
+ "But the following unconfigured meters are found in the data received from the meter: {}",

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.dsmr.internal.handler;
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY;
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY;
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_SMARTY_BRIDGE;
import java.util.ArrayList;
@ -21,14 +23,14 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.dsmr.internal.device.DSMRDevice;
import org.openhab.binding.dsmr.internal.device.DSMRDeviceConfiguration;
import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
import org.openhab.binding.dsmr.internal.device.DSMREventListener;
import org.openhab.binding.dsmr.internal.device.DSMRFixedConfigDevice;
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
@ -41,6 +43,7 @@ import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -52,7 +55,7 @@ import org.slf4j.LoggerFactory;
* @author Hilbrand Bouwkamp - Refactored way messages are forwarded to meters. Removed availableMeters dependency.
*/
@NonNullByDefault
public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventListener {
public class DSMRBridgeHandler extends BaseBridgeHandler implements P1TelegramListener {
/**
* Factor that will be multiplied with {@link #receivedTimeoutNanos} to get the timeout factor after which the
@ -105,6 +108,8 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
private final boolean smartyMeter;
private @Nullable String lastKnownReadErrorMessage;
/**
* Constructor
*
@ -143,9 +148,7 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
public void initialize() {
final DSMRDeviceConfiguration deviceConfig = getConfigAs(DSMRDeviceConfiguration.class);
if (smartyMeter && (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/error.configuration.invalidsmartykey");
if (smartyMeter && !validateSmartyMeterConfiguration(deviceConfig)) {
return;
}
@ -164,6 +167,32 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
TimeUnit.NANOSECONDS);
}
private boolean validateSmartyMeterConfiguration(final DSMRDeviceConfiguration deviceConfig) {
final boolean valid;
if (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/addon.dsmr.error.configuration.invalidsmartykey");
valid = false;
} else if (!validDecryptionKey(deviceConfig.decryptionKey, CONFIGURATION_DECRYPTION_KEY)
|| !validDecryptionKey(deviceConfig.additionalKey, CONFIGURATION_ADDITIONAL_KEY)) {
valid = false;
} else {
valid = true;
}
return valid;
}
private boolean validDecryptionKey(final String key, final String message) {
try {
HexUtils.hexToBytes(key);
return true;
} catch (final IllegalArgumentException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/addon.dsmr.error.configuration.invalid." + message + " [" + e.getMessage() + "]");
}
return false;
}
/**
* Creates the {@link DSMRDevice} that corresponds with the user specified configuration.
*
@ -224,18 +253,21 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
final long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;
if (deltaLastReceived > receivedTimeoutNanos) {
logger.debug("No data received for {} seconds, restarting port if possible.",
logger.debug("No valid data received for {} seconds, restarting port if possible.",
TimeUnit.NANOSECONDS.toSeconds(deltaLastReceived));
if (dsmrDeviceRunnable != null) {
dsmrDeviceRunnable.restart();
}
if (deltaLastReceived > receivedTimeoutNanos * OFFLINE_TIMEOUT_FACTOR) {
logger.trace("Setting device offline if not yet done, and reset last received time.");
if (getThing().getStatus() == ThingStatus.ONLINE) {
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.bridge.nodata");
if (isInitialized() && getThing().getStatus() != ThingStatus.OFFLINE) {
final String lkm = lastKnownReadErrorMessage;
final String message = lkm == null ? "@text/addon.dsmr.error.bridge.nodata" : lkm;
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, message);
}
resetLastReceivedState();
}
if (dsmrDeviceRunnable != null) {
dsmrDeviceRunnable.restart();
}
}
}
@ -243,26 +275,29 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
* Sets the last received time of messages to the current time.
*/
private void resetLastReceivedState() {
lastKnownReadErrorMessage = null;
telegramReceivedTimeNanos = System.nanoTime();
logger.trace("Telegram received time set: {}", telegramReceivedTimeNanos);
}
@Override
public synchronized void handleTelegramReceived(final P1Telegram telegram) {
if (telegram.getCosemObjects().isEmpty()) {
logger.debug("Parsing worked but something went wrong, so there were no CosemObjects:{}",
telegram.getTelegramState().stateDetails);
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, telegram.getTelegramState().stateDetails);
} else {
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);
}
}
}

View File

@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
@ -34,6 +35,7 @@ import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.util.ThingHandlerHelper;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
@ -77,7 +79,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
*
* @param thing {@link Thing} to create the MeterHandler for
*/
public DSMRMeterHandler(Thing thing) {
public DSMRMeterHandler(final Thing thing) {
super(thing);
}
@ -85,7 +87,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
* DSMR Meter don't support handling commands
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
public void handleCommand(final ChannelUID channelUID, final Command command) {
if (command == RefreshType.REFRESH) {
updateState();
}
@ -103,17 +105,17 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
try {
meterType = DSMRMeterType.valueOf(getThing().getThingTypeUID().getId().toUpperCase());
} catch (IllegalArgumentException iae) {
} catch (final IllegalArgumentException iae) {
logger.warn(
"{} could not be initialized due to an invalid meterType {}. Delete this Thing if the problem persists.",
getThing(), getThing().getThingTypeUID().getId().toUpperCase());
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/error.configuration.invalidmetertype");
"@text/addon.dsmr.error.configuration.invalidmetertype");
return;
}
DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class);
final DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class);
channel = meterType.meterKind.isChannelRelevant() ? meterConfig.channel : DSMRMeterConstants.UNKNOWN_CHANNEL;
DSMRMeterDescriptor meterDescriptor = new DSMRMeterDescriptor(meterType, channel);
final DSMRMeterDescriptor meterDescriptor = new DSMRMeterDescriptor(meterType, channel);
meter = new DSMRMeter(meterDescriptor);
meterWatchdog = scheduler.scheduleWithFixedDelay(this::updateState, meterConfig.refresh, meterConfig.refresh,
TimeUnit.SECONDS);
@ -147,7 +149,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
updateState(channel, newState);
}
}
if (getThing().getStatus() != ThingStatus.ONLINE) {
if (ThingHandlerHelper.isHandlerInitialized(getThing()) && getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
lastReceivedValues = Collections.emptyList();
@ -161,18 +163,18 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
* @param telegram The received telegram
*/
@Override
public void telegramReceived(P1Telegram telegram) {
public void telegramReceived(final P1Telegram telegram) {
lastReceivedValues = Collections.emptyList();
final DSMRMeter localMeter = meter;
if (localMeter == null) {
return;
}
List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
final List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
if (filteredValues.isEmpty()) {
if (getThing().getStatus() == ThingStatus.ONLINE) {
setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.thing.nodata");
setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/addon.dsmr.error.thing.nodata");
}
} else {
if (logger.isTraceEnabled()) {
@ -186,7 +188,12 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
}
@Override
public synchronized void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
public void onError(final DSMRErrorStatus state, final String message) {
// Error is handled in other places.
}
@Override
public synchronized void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
// Set status to offline --> Thing will become online after receiving meter values
@ -209,7 +216,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
* @param status off line status
* @param details off line detailed message
*/
private void setDeviceOffline(ThingStatusDetail status, @Nullable String details) {
private void setDeviceOffline(final ThingStatusDetail status, @Nullable final String details) {
updateStatus(ThingStatus.OFFLINE, status, details);
getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.NULL));
}

View File

@ -52,6 +52,6 @@ public enum DSMRMeterKind {
* @return Returns the i18n label key for this meter.
*/
public String getLabelKey() {
return "@text/meterKind." + name().toLowerCase() + ".label";
return "@text/addon.dsmr.meterKind." + name().toLowerCase() + ".label";
}
}

View File

@ -388,8 +388,8 @@ public enum DSMRMeterType {
* @param cosemObjectTypeMeterId identifier cosem object
* @param requiredCosemObjects list of objects that are present in this meter type
*/
DSMRMeterType(DSMRMeterKind meterKind, CosemObjectType cosemObjectTypeMeterId,
CosemObjectType... requiredCosemObjects) {
DSMRMeterType(final DSMRMeterKind meterKind, final CosemObjectType cosemObjectTypeMeterId,
final CosemObjectType... requiredCosemObjects) {
this(meterKind, cosemObjectTypeMeterId, requiredCosemObjects, new CosemObjectType[0]);
}
@ -401,8 +401,8 @@ public enum DSMRMeterType {
* @param requiredCosemObjects list of objects that are present in this meter type
* @param optionalCosemObjects list of objects that are optional present in this meter type
*/
DSMRMeterType(DSMRMeterKind meterKind, CosemObjectType cosemObjectTypeMeterId,
CosemObjectType[] requiredCosemObjects, CosemObjectType[] optionalCosemObjects) {
DSMRMeterType(final DSMRMeterKind meterKind, final CosemObjectType cosemObjectTypeMeterId,
final CosemObjectType[] requiredCosemObjects, final CosemObjectType[] optionalCosemObjects) {
this.meterKind = meterKind;
this.cosemObjectTypeMeterId = cosemObjectTypeMeterId;
this.requiredCosemObjects = requiredCosemObjects;
@ -427,15 +427,18 @@ public enum DSMRMeterType {
* @param availableCosemObjects the Cosem Objects to detect if the current meter compatible
* @return {@link DSMRMeterDescriptor} containing the identification of the compatible meter
*/
public @Nullable DSMRMeterDescriptor findCompatible(List<CosemObject> availableCosemObjects) {
public @Nullable DSMRMeterDescriptor findCompatible(final List<CosemObject> availableCosemObjects) {
final Map<@Nullable Integer, AtomicInteger> channelCounter = new HashMap<>(3);
for (final CosemObjectType objectType : requiredCosemObjects) {
final AtomicBoolean match = new AtomicBoolean();
availableCosemObjects.stream().filter(a -> a.getType() == objectType).forEach(b -> {
match.set(true);
channelCounter.computeIfAbsent(b.getObisIdentifier().getChannel(), t -> new AtomicInteger())
.incrementAndGet();
final Integer channel = b.getObisIdentifier().getChannel();
if (channel != null) {
channelCounter.computeIfAbsent(channel, t -> new AtomicInteger()).incrementAndGet();
}
});
if (!match.get()) {
logger.trace("Required objectType {} not found for meter: {}", objectType, this);

View File

@ -300,27 +300,36 @@ channel-type.dsmr.waterValvePositionType.description = The water valve switch po
# meter kind names
meterKind.invalid.label = Invalid Meter
meterKind.device.label = Generic DSMR Device
meterKind.main_electricity.label = Main Electricity Meter
meterKind.gas.label = Gas Meter
meterKind.heating.label = Heating Meter
meterKind.cooling.label = Cooling Meter
meterKind.water.label = Water Meter
meterKind.generic.label = Generic Meter
meterKind.gj.label = GJ Meter
meterKind.m3.label = M3 Meter
meterKind.slave_electricity1.label = Slave Electricity Meter
meterKind.slave_electricity2.label = Slave Electricity Meter 2
addon.dsmr.meterKind.invalid.label = Invalid Meter
addon.dsmr.meterKind.device.label = Generic DSMR Device
addon.dsmr.meterKind.main_electricity.label = Main Electricity Meter
addon.dsmr.meterKind.gas.label = Gas Meter
addon.dsmr.meterKind.heating.label = Heating Meter
addon.dsmr.meterKind.cooling.label = Cooling Meter
addon.dsmr.meterKind.water.label = Water Meter
addon.dsmr.meterKind.generic.label = Generic Meter
addon.dsmr.meterKind.gj.label = GJ Meter
addon.dsmr.meterKind.m3.label = M3 Meter
addon.dsmr.meterKind.slave_electricity1.label = Slave Electricity Meter
addon.dsmr.meterKind.slave_electricity2.label = Slave Electricity Meter 2
# connector error messages
error.bridge.nodata = Not receiving data from meter.
error.configuration.invalidmetertype = The thing could not be initialized. Delete and re-add thing if the problem persists.
error.configuration.invalidsmartykey = The given Smarty decyption key is to short. The decyption key must be 32 characters long.
error.thing.nodata = Not receiving data from meter.
error.connector.dont_exists = Serial port does not exist.
error.connector.in_use = Serial port is already in use.
error.connector.internal_error = Unexpected error, possible bug. Please report.
error.connector.not_compatible = Serial port is not compatible.
error.connector.read_error = Read error.
addon.dsmr.error.bridge.nodata = Not receiving data from meter.
addon.dsmr.error.configuration.invalidmetertype = The thing could not be initialized. Delete and re-add thing if the problem persists.
addon.dsmr.error.configuration.invalidsmartykey = The given Smarty decyption key is to short. The decyption key must be 32 characters long.
addon.dsmr.error.configuration.invalid.decryptionKey = The given Smarty decyption key is invalid. It contains an illegal character: {0}
addon.dsmr.error.configuration.invalid.additionalKey = The given Smarty additional key is invalid. It contains an illegal character: {0}
addon.dsmr.error.thing.nodata = Not receiving data from meter.
addon.dsmr.error.status.invalid_decryption_key = Failed to decrypt P1 telegram due to invalid encryption key
addon.dsmr.error.status.port_dont_exists = Serial port does not exist.
addon.dsmr.error.status.port_in_use = Serial port is already in use.
addon.dsmr.error.status.port_internal_error = Unexpected error, possible bug. Please report.
addon.dsmr.error.status.port_not_compatible = Serial port is not compatible.
addon.dsmr.error.status.parse_error = Telegram received, but parsing failed due to parse errors.
addon.dsmr.error.status.serial_data_read_error = Reading data from the serial port failed.
addon.dsmr.error.status.telegram_crc_error = CRC checksum failed for received P1 telegram.
addon.dsmr.error.status.telegram_data_corruption = Received P1 telegram is corrupted. Possible bad/wrong P1 data cable?
addon.dsmr.error.status.telegram_no_data = Received telegram data, but after parsing no data is present. Possible all data corrupted.

View File

@ -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> 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;
}
}
}

View File

@ -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;
}

View File

@ -22,8 +22,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
/**
* Test class for the {@link SmartyDecrypter}.
@ -96,10 +95,13 @@ public class SmartyDecrypterTest {
*/
@Test
public void testSmartyDecrypter() {
final AtomicReference<String> telegramResult = new AtomicReference<>("");
final P1TelegramListener telegramListener = telegram -> telegramResult.set(telegram.getRawTelegram());
final SmartyDecrypter decoder = new SmartyDecrypter(new P1TelegramParser(telegramListener),
new DSMRTelegramListener(KEY, ""), KEY, "");
final AtomicReference<String> dataRead = new AtomicReference<>();
final SmartyDecrypter decoder = new SmartyDecrypter(new TelegramParser() {
@Override
public void parse(final byte[] data, final int length) {
dataRead.set(new String(data, StandardCharsets.UTF_8));
}
}, new DSMRTelegramListener(KEY, ""), KEY, "");
decoder.setLenientMode(true);
final byte[] data = new byte[TELEGRAM.length];
@ -110,6 +112,6 @@ public class SmartyDecrypterTest {
decoder.parse(data, data.length);
final String expected = new String(TelegramReaderUtil.readRawTelegram("smarty_long"), StandardCharsets.UTF_8);
assertThat("Should have correctly decrypted the telegram", telegramResult.get(), is(equalTo(expected)));
assertThat("Should have correctly decrypted the telegram", dataRead.get(), is(equalTo(expected)));
}
}

View File

@ -21,7 +21,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
/**
* Test class for {@link P1TelegramParser}.
@ -55,7 +54,7 @@ public class P1TelegramParserTest {
@ParameterizedTest
@MethodSource("data")
public void testParsing(final String telegramName, final int numberOfCosemObjects, final int unknownObjects) {
final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName);
assertEquals(unknownObjects, telegram.getUnknownCosemObjects().size(),
"Should not have other than " + unknownObjects + " unknown cosem objects");
assertEquals(numberOfCosemObjects,

View File

@ -42,7 +42,6 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
@ -77,7 +76,7 @@ public class DSMRMeterDetectorTest {
@ParameterizedTest
@MethodSource("data")
public void testDetectMeters(final String telegramName, final Set<DSMRMeterType> expectedMeters) {
final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName, TelegramState.OK);
final P1Telegram telegram = TelegramReaderUtil.readTelegram(telegramName);
final DSMRMeterDetector detector = new DSMRMeterDetector();
final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> entry = detector.detectMeters(telegram);
final Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();

View File

@ -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<List<DSMRMeterType>> invalidConfiguredRef = new AtomicReference<>();
AtomicReference<List<DSMRMeterType>> unconfiguredRef = new AtomicReference<>();
DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
final P1Telegram expected = TelegramReaderUtil.readTelegram(EXPECTED_CONFIGURED_TELEGRAM);
final AtomicReference<List<DSMRMeterType>> invalidConfiguredRef = new AtomicReference<>();
final AtomicReference<List<DSMRMeterType>> unconfiguredRef = new AtomicReference<>();
final DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
@Override
protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
List<DSMRMeterType> unconfiguredMeters) {
protected void reportConfigurationValidationResults(final List<DSMRMeterType> invalidConfigured,
final List<DSMRMeterType> unconfiguredMeters) {
super.reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
invalidConfiguredRef.set(invalidConfigured);
unconfiguredRef.set(unconfiguredMeters);
@ -79,10 +81,13 @@ public class DSMRMeterDiscoveryServiceTest {
// Mock the invalid configuration by reading a telegram that is valid for a meter that is a subset of the
// expected meter.
List<DSMRMeterDescriptor> invalidConfiguredMeterDescriptors = EnumSet.of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0)
.stream().map(mt -> new DSMRMeterDescriptor(mt, 0)).collect(Collectors.toList());
List<Thing> things = invalidConfiguredMeterDescriptors.stream().map(m -> thing).collect(Collectors.toList());
AtomicReference<Iterator<DSMRMeterDescriptor>> detectMetersRef = new AtomicReference<>();
final List<DSMRMeterDescriptor> invalidConfiguredMeterDescriptors = EnumSet
.of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0).stream().map(mt -> new DSMRMeterDescriptor(mt, 0))
.collect(Collectors.toList());
final List<Thing> things = invalidConfiguredMeterDescriptors.stream().map(m -> thing)
.collect(Collectors.toList());
final AtomicReference<Iterator<DSMRMeterDescriptor>> detectMetersRef = new AtomicReference<>();
when((meterHandler).getMeterDescriptor()).then(a -> {
if (detectMetersRef.get() == null || !detectMetersRef.get().hasNext()) {
detectMetersRef.set(invalidConfiguredMeterDescriptors.iterator());
@ -109,9 +114,9 @@ public class DSMRMeterDiscoveryServiceTest {
*/
@Test
public void testUnregisteredMeters() {
P1Telegram telegram = TelegramReaderUtil.readTelegram(UNREGISTERED_METER_TELEGRAM, TelegramState.OK);
AtomicBoolean unregisteredMeter = new AtomicBoolean(false);
DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
final P1Telegram telegram = TelegramReaderUtil.readTelegram(UNREGISTERED_METER_TELEGRAM);
final AtomicBoolean unregisteredMeter = new AtomicBoolean(false);
final DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
@Override
protected void reportUnregisteredMeters() {
super.reportUnregisteredMeters();

View File

@ -20,7 +20,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
/**
* Test class for {@link DSMRMeter}.
@ -35,15 +34,15 @@ public class DSMRMeterTest {
*/
@Test
public void testFilterMeterValues() {
final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK)
.getCosemObjects();
final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50").getCosemObjects();
assertMeterValues(cosemObjects, DSMRMeterType.DEVICE_V5, DSMRMeterConstants.UNKNOWN_CHANNEL, 3);
assertMeterValues(cosemObjects, DSMRMeterType.ELECTRICITY_V5_0, 0, 29);
assertMeterValues(cosemObjects, DSMRMeterType.M3_V5_0, 1, 3);
}
private void assertMeterValues(List<CosemObject> cosemObjects, DSMRMeterType type, int channel, int expected) {
private void assertMeterValues(final List<CosemObject> cosemObjects, final DSMRMeterType type, final int channel,
final int expected) {
final DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(type, channel);
final DSMRMeter meter = new DSMRMeter(descriptor);
final List<CosemObject> filterMeterValues = meter.filterMeterValues(cosemObjects, channel);