mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[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:
parent
c95380f503
commit
03e3b6aae2
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,11 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialConnector;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -34,7 +35,7 @@ import org.slf4j.LoggerFactory;
|
||||
* settings automatically.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
public class DSMRSerialAutoDevice implements DSMRDevice, P1TelegramListener {
|
||||
|
||||
/**
|
||||
* Enum to keep track of the internal state of {@link DSMRSerialAutoDevice}.
|
||||
@ -113,7 +114,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
/**
|
||||
* The listener of the class handling the connector events
|
||||
*/
|
||||
private DSMREventListener parentListener;
|
||||
private final P1TelegramListener parentListener;
|
||||
|
||||
/**
|
||||
* Time in nanos the last time the baudrate was switched. This is used during discovery ignore errors retrieved
|
||||
@ -126,20 +127,20 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
*
|
||||
* @param serialPortManager the manager to get a new serial port connecting from
|
||||
* @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
|
||||
* @param listener the parent {@link DSMREventListener}
|
||||
* @param listener the parent {@link P1TelegramListener}
|
||||
* @param telegramListener listener to report found telegrams or errors
|
||||
* @param scheduler the scheduler to use with the baudrate switching timers
|
||||
* @param baudrateSwitchTimeoutSeconds timeout period for when to try other baudrate settings and end the discovery
|
||||
* of the baudrate
|
||||
*/
|
||||
public DSMRSerialAutoDevice(SerialPortManager serialPortManager, String serialPortName, DSMREventListener listener,
|
||||
DSMRTelegramListener telegramListener, ScheduledExecutorService scheduler,
|
||||
int baudrateSwitchTimeoutSeconds) {
|
||||
public DSMRSerialAutoDevice(final SerialPortManager serialPortManager, final String serialPortName,
|
||||
final P1TelegramListener listener, final DSMRTelegramListener telegramListener,
|
||||
final ScheduledExecutorService scheduler, final int baudrateSwitchTimeoutSeconds) {
|
||||
this.parentListener = listener;
|
||||
this.scheduler = scheduler;
|
||||
this.baudrateSwitchTimeoutSeconds = baudrateSwitchTimeoutSeconds;
|
||||
this.telegramListener = telegramListener;
|
||||
telegramListener.setDsmrEventListener(listener);
|
||||
telegramListener.setP1TelegramListener(listener);
|
||||
dsmrConnector = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
|
||||
logger.debug("Initialized port '{}'", serialPortName);
|
||||
}
|
||||
@ -148,7 +149,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
public void start() {
|
||||
stopDiscover(DeviceState.DISCOVER_SETTINGS);
|
||||
portSettings = DEFAULT_PORT_SETTINGS;
|
||||
telegramListener.setDsmrEventListener(this);
|
||||
telegramListener.setP1TelegramListener(this);
|
||||
dsmrConnector.open(portSettings);
|
||||
restartHalfTimer();
|
||||
endTimeTimer = scheduler.schedule(this::endTimeScheduledCall,
|
||||
@ -178,30 +179,28 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
* @param telegram the details of the received telegram
|
||||
*/
|
||||
@Override
|
||||
public void handleTelegramReceived(P1Telegram telegram) {
|
||||
if (!telegram.getCosemObjects().isEmpty()) {
|
||||
stopDiscover(DeviceState.NORMAL);
|
||||
parentListener.handleTelegramReceived(telegram);
|
||||
logger.info("Start receiving telegrams on port {} with settings: {}", dsmrConnector.getPortName(),
|
||||
portSettings);
|
||||
}
|
||||
public void telegramReceived(final P1Telegram telegram) {
|
||||
stopDiscover(DeviceState.NORMAL);
|
||||
parentListener.telegramReceived(telegram);
|
||||
logger.info("Start receiving telegrams on port {} with settings: {}", dsmrConnector.getPortName(),
|
||||
portSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for DSMR Port events.
|
||||
*
|
||||
* @param portEvent {@link DSMRConnectorErrorEvent} to handle
|
||||
* @param portEvent {@link DSMRErrorStatus} to handle
|
||||
*/
|
||||
@Override
|
||||
public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
|
||||
public void onError(final DSMRErrorStatus portEvent, final String message) {
|
||||
logger.trace("Received portEvent {}", portEvent.getEventDetails());
|
||||
if (portEvent == DSMRConnectorErrorEvent.READ_ERROR) {
|
||||
if (portEvent == DSMRErrorStatus.SERIAL_DATA_READ_ERROR) {
|
||||
switchBaudrate();
|
||||
} else {
|
||||
logger.debug("Error during discovery of port settings: {}, current state:{}.", portEvent.getEventDetails(),
|
||||
state);
|
||||
stopDiscover(DeviceState.ERROR);
|
||||
parentListener.handleErrorEvent(portEvent);
|
||||
parentListener.onError(portEvent, message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +208,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
* @param lenientMode the lenientMode to set
|
||||
*/
|
||||
@Override
|
||||
public void setLenientMode(boolean lenientMode) {
|
||||
public void setLenientMode(final boolean lenientMode) {
|
||||
telegramListener.setLenientMode(lenientMode);
|
||||
}
|
||||
|
||||
@ -248,7 +247,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
private void endTimeScheduledCall() {
|
||||
if (state == DeviceState.DISCOVER_SETTINGS) {
|
||||
stopDiscover(DeviceState.ERROR);
|
||||
parentListener.handleErrorEvent(DSMRConnectorErrorEvent.DONT_EXISTS);
|
||||
parentListener.onError(DSMRErrorStatus.PORT_DONT_EXISTS, "");
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,8 +256,9 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
*
|
||||
* @param state the state with which the process was stopped.
|
||||
*/
|
||||
private void stopDiscover(DeviceState state) {
|
||||
telegramListener.setDsmrEventListener(parentListener);
|
||||
private void stopDiscover(final DeviceState state) {
|
||||
this.state = state;
|
||||
telegramListener.setP1TelegramListener(parentListener);
|
||||
logger.debug("Stop discovery of port settings.");
|
||||
if (halfTimeTimer != null) {
|
||||
halfTimeTimer.cancel(true);
|
||||
@ -268,7 +268,6 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
||||
endTimeTimer.cancel(true);
|
||||
endTimeTimer = null;
|
||||
}
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,14 +13,12 @@
|
||||
package org.openhab.binding.dsmr.internal.device;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorListener;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
|
||||
@ -40,7 +38,7 @@ public class DSMRTelegramListener implements P1TelegramListener, DSMRConnectorLi
|
||||
private final Logger logger = LoggerFactory.getLogger(DSMRTelegramListener.class);
|
||||
private final TelegramParser parser;
|
||||
|
||||
private @NonNullByDefault({}) DSMREventListener dsmrEventListener;
|
||||
private @NonNullByDefault({}) P1TelegramListener p1TelegramListener;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -62,25 +60,29 @@ public class DSMRTelegramListener implements P1TelegramListener, DSMRConnectorLi
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DSMR event listener.
|
||||
* Set the P1 Telegram listener.
|
||||
*
|
||||
* @param eventListener the listener to set
|
||||
* @param p1TelegramListener the listener to set
|
||||
*/
|
||||
public void setDsmrEventListener(final DSMREventListener eventListener) {
|
||||
this.dsmrEventListener = eventListener;
|
||||
public void setP1TelegramListener(final P1TelegramListener p1TelegramListener) {
|
||||
this.p1TelegramListener = p1TelegramListener;
|
||||
}
|
||||
|
||||
// Handle calls from the Connector
|
||||
|
||||
@Override
|
||||
public void handleData(final byte[] data, final int length) {
|
||||
parser.parse(data, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
|
||||
dsmrEventListener.handleErrorEvent(portEvent);
|
||||
public void handleError(final DSMRErrorStatus portEvent, final String message) {
|
||||
onError(portEvent, message);
|
||||
parser.reset();
|
||||
}
|
||||
|
||||
// Handle calls from the Parser
|
||||
|
||||
/**
|
||||
* Handler for cosemObjects received in a P1 telegram
|
||||
*
|
||||
@ -88,22 +90,23 @@ public class DSMRTelegramListener implements P1TelegramListener, DSMRConnectorLi
|
||||
*/
|
||||
@Override
|
||||
public void telegramReceived(final P1Telegram telegram) {
|
||||
final TelegramState telegramState = telegram.getTelegramState();
|
||||
final List<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
|
||||
*/
|
||||
|
@ -16,7 +16,8 @@ import java.nio.ByteBuffer;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
@ -28,8 +29,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
@ -53,8 +53,7 @@ public class SmartyDecrypter implements TelegramParser {
|
||||
READ_SEPARATOR_30,
|
||||
READ_FRAME_COUNTER,
|
||||
READ_PAYLOAD,
|
||||
READ_GCM_TAG,
|
||||
DONE_READING_TELEGRAM
|
||||
READ_GCM_TAG
|
||||
}
|
||||
|
||||
private static final byte START_BYTE = (byte) 0xDB;
|
||||
@ -95,7 +94,7 @@ public class SmartyDecrypter implements TelegramParser {
|
||||
this.parser = parser;
|
||||
this.telegramListener = telegramListener;
|
||||
secretKeySpec = decryptionKey.isEmpty() ? null : new SecretKeySpec(HexUtils.hexToBytes(decryptionKey), "AES");
|
||||
addKey = HexUtils.hexToBytes(additionalKey.isBlank() ? DSMRBindingConstants.ADDITIONAL_KEY_DEFAULT
|
||||
addKey = HexUtils.hexToBytes(additionalKey.isBlank() ? DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY_DEFAULT
|
||||
: ((additionalKey.length() == 32 ? (ADDITIONAL_ADD_PREFIX) : "") + additionalKey));
|
||||
}
|
||||
|
||||
@ -113,6 +112,11 @@ public class SmartyDecrypter implements TelegramParser {
|
||||
}
|
||||
|
||||
private boolean processStateActions(final byte rawInput) {
|
||||
// Safeguard against buffer overrun in case corrupt data is received.
|
||||
if (ivLength == IV_BUFFER_LENGTH) {
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
switch (state) {
|
||||
case WAITING_FOR_START_BYTE:
|
||||
if (rawInput == START_BYTE) {
|
||||
@ -179,26 +183,23 @@ public class SmartyDecrypter implements TelegramParser {
|
||||
// All input has been read.
|
||||
cipherText.put(rawInput);
|
||||
if (currentBytePosition >= changeToNextStateAt) {
|
||||
state = State.DONE_READING_TELEGRAM;
|
||||
state = State.WAITING_FOR_START_BYTE;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (state == State.DONE_READING_TELEGRAM) {
|
||||
state = State.WAITING_FOR_START_BYTE;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processCompleted() {
|
||||
final byte[] plainText = decrypt();
|
||||
try {
|
||||
final byte[] plainText = decrypt();
|
||||
|
||||
reset();
|
||||
if (plainText == null) {
|
||||
telegramListener
|
||||
.telegramReceived(new P1Telegram(Collections.emptyList(), TelegramState.INVALID_ENCRYPTION_KEY));
|
||||
} else {
|
||||
parser.parse(plainText, plainText.length);
|
||||
if (plainText != null) {
|
||||
parser.parse(plainText, plainText.length);
|
||||
}
|
||||
} finally {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +219,15 @@ public class SmartyDecrypter implements TelegramParser {
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
logger.warn("Decrypting smarty telegram failed: ", e);
|
||||
if (lenientMode || logger.isDebugEnabled()) {
|
||||
// log in lenient mode or when debug is enabled. But log to warn to also work when lenientMode is
|
||||
// enabled.
|
||||
logger.warn("Failed encrypted telegram: {}",
|
||||
HexUtils.bytesToHex(Arrays.copyOf(cipherText.array(), cipherText.position())));
|
||||
logger.warn("Exception of failed decryption of telegram: ", e);
|
||||
}
|
||||
telegramListener.onError(DSMRErrorStatus.INVALID_DECRYPTION_KEY,
|
||||
Optional.ofNullable(e.getMessage()).orElse(""));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
package org.openhab.binding.dsmr.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY;
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY_DEFAULT;
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY;
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY_EMPTY;
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_SERIAL_PORT;
|
||||
@ -28,13 +29,12 @@ import java.util.stream.Stream;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMREventListener;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
|
||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
@ -69,7 +69,7 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.dsmr")
|
||||
public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements DSMREventListener {
|
||||
public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements P1TelegramListener {
|
||||
|
||||
/**
|
||||
* The timeout used to switch baudrate if no valid data is received within that time frame.
|
||||
@ -133,7 +133,7 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
|
||||
logger.debug("Start discovery on serial port: {}", currentScannedPortName);
|
||||
//
|
||||
final DSMRTelegramListener telegramListener = new DSMRTelegramListener("",
|
||||
CONFIGURATION_ADDITIONAL_KEY);
|
||||
CONFIGURATION_ADDITIONAL_KEY_DEFAULT);
|
||||
final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager,
|
||||
portIdentifier.getName(), this, telegramListener, scheduler,
|
||||
BAUDRATE_SWITCH_TIMEOUT_SECONDS);
|
||||
@ -173,20 +173,25 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
|
||||
* @param telegram the received telegram
|
||||
*/
|
||||
@Override
|
||||
public void handleTelegramReceived(final P1Telegram telegram) {
|
||||
public void telegramReceived(final P1Telegram telegram) {
|
||||
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("[{}] Received {} cosemObjects", currentScannedPortName, cosemObjects.size());
|
||||
}
|
||||
if (telegram.getTelegramState() == TelegramState.INVALID_ENCRYPTION_KEY) {
|
||||
final ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
|
||||
meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
|
||||
stopSerialPortScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(final DSMRErrorStatus portEvent, final String message) {
|
||||
if (portEvent == DSMRErrorStatus.INVALID_DECRYPTION_KEY) {
|
||||
bridgeDiscovered(THING_TYPE_SMARTY_BRIDGE);
|
||||
stopSerialPortScan();
|
||||
} else if (!cosemObjects.isEmpty()) {
|
||||
final ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
|
||||
meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
|
||||
stopSerialPortScan();
|
||||
} else {
|
||||
logger.debug("[{}] Error on port during discovery: {} - {}", currentScannedPortName, portEvent, message);
|
||||
}
|
||||
stopSerialPortScan();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,7 +210,7 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
|
||||
properties.put(CONFIGURATION_SERIAL_PORT, currentScannedPortName);
|
||||
if (smarty) {
|
||||
properties.put(CONFIGURATION_DECRYPTION_KEY, CONFIGURATION_DECRYPTION_KEY_EMPTY);
|
||||
properties.put(CONFIGURATION_ADDITIONAL_KEY, CONFIGURATION_ADDITIONAL_KEY);
|
||||
properties.put(CONFIGURATION_ADDITIONAL_KEY, CONFIGURATION_ADDITIONAL_KEY_DEFAULT);
|
||||
}
|
||||
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||
.withThingType(bridgeThingTypeUID).withProperties(properties).withLabel(label).build();
|
||||
@ -215,10 +220,4 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
|
||||
thingDiscovered(discoveryResult);
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
|
||||
logger.debug("[{}] Error on port during discovery: {}", currentScannedPortName, portEvent);
|
||||
stopSerialPortScan();
|
||||
}
|
||||
}
|
||||
|
@ -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: {}",
|
||||
|
@ -12,6 +12,8 @@
|
||||
*/
|
||||
package org.openhab.binding.dsmr.internal.handler;
|
||||
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_ADDITIONAL_KEY;
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY;
|
||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_SMARTY_BRIDGE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -21,14 +23,14 @@ import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRDevice;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceConfiguration;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMREventListener;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRFixedConfigDevice;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
|
||||
import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
|
||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||
@ -41,6 +43,7 @@ import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -52,7 +55,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Hilbrand Bouwkamp - Refactored way messages are forwarded to meters. Removed availableMeters dependency.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventListener {
|
||||
public class DSMRBridgeHandler extends BaseBridgeHandler implements P1TelegramListener {
|
||||
|
||||
/**
|
||||
* Factor that will be multiplied with {@link #receivedTimeoutNanos} to get the timeout factor after which the
|
||||
@ -105,6 +108,8 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
|
||||
|
||||
private final boolean smartyMeter;
|
||||
|
||||
private @Nullable String lastKnownReadErrorMessage;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -143,9 +148,7 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
|
||||
public void initialize() {
|
||||
final DSMRDeviceConfiguration deviceConfig = getConfigAs(DSMRDeviceConfiguration.class);
|
||||
|
||||
if (smartyMeter && (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/error.configuration.invalidsmartykey");
|
||||
if (smartyMeter && !validateSmartyMeterConfiguration(deviceConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -164,6 +167,32 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
|
||||
TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
private boolean validateSmartyMeterConfiguration(final DSMRDeviceConfiguration deviceConfig) {
|
||||
final boolean valid;
|
||||
if (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/addon.dsmr.error.configuration.invalidsmartykey");
|
||||
valid = false;
|
||||
} else if (!validDecryptionKey(deviceConfig.decryptionKey, CONFIGURATION_DECRYPTION_KEY)
|
||||
|| !validDecryptionKey(deviceConfig.additionalKey, CONFIGURATION_ADDITIONAL_KEY)) {
|
||||
valid = false;
|
||||
} else {
|
||||
valid = true;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
private boolean validDecryptionKey(final String key, final String message) {
|
||||
try {
|
||||
HexUtils.hexToBytes(key);
|
||||
return true;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/addon.dsmr.error.configuration.invalid." + message + " [" + e.getMessage() + "]");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link DSMRDevice} that corresponds with the user specified configuration.
|
||||
*
|
||||
@ -224,18 +253,21 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
|
||||
final long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;
|
||||
|
||||
if (deltaLastReceived > receivedTimeoutNanos) {
|
||||
logger.debug("No data received for {} seconds, restarting port if possible.",
|
||||
logger.debug("No valid data received for {} seconds, restarting port if possible.",
|
||||
TimeUnit.NANOSECONDS.toSeconds(deltaLastReceived));
|
||||
if (dsmrDeviceRunnable != null) {
|
||||
dsmrDeviceRunnable.restart();
|
||||
}
|
||||
if (deltaLastReceived > receivedTimeoutNanos * OFFLINE_TIMEOUT_FACTOR) {
|
||||
logger.trace("Setting device offline if not yet done, and reset last received time.");
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.bridge.nodata");
|
||||
if (isInitialized() && getThing().getStatus() != ThingStatus.OFFLINE) {
|
||||
final String lkm = lastKnownReadErrorMessage;
|
||||
final String message = lkm == null ? "@text/addon.dsmr.error.bridge.nodata" : lkm;
|
||||
|
||||
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, message);
|
||||
}
|
||||
resetLastReceivedState();
|
||||
}
|
||||
if (dsmrDeviceRunnable != null) {
|
||||
dsmrDeviceRunnable.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,26 +275,29 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
|
||||
* Sets the last received time of messages to the current time.
|
||||
*/
|
||||
private void resetLastReceivedState() {
|
||||
lastKnownReadErrorMessage = null;
|
||||
telegramReceivedTimeNanos = System.nanoTime();
|
||||
logger.trace("Telegram received time set: {}", telegramReceivedTimeNanos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void handleTelegramReceived(final P1Telegram telegram) {
|
||||
if (telegram.getCosemObjects().isEmpty()) {
|
||||
logger.debug("Parsing worked but something went wrong, so there were no CosemObjects:{}",
|
||||
telegram.getTelegramState().stateDetails);
|
||||
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, telegram.getTelegramState().stateDetails);
|
||||
} else {
|
||||
resetLastReceivedState();
|
||||
meterValueReceived(telegram);
|
||||
}
|
||||
public synchronized void telegramReceived(final P1Telegram telegram) {
|
||||
resetLastReceivedState();
|
||||
meterValueReceived(telegram);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
|
||||
if (portEvent != DSMRConnectorErrorEvent.READ_ERROR) {
|
||||
deviceOffline(ThingStatusDetail.CONFIGURATION_ERROR, portEvent.getEventDetails());
|
||||
public void onError(final DSMRErrorStatus errorStatus, final String message) {
|
||||
if (errorStatus == DSMRErrorStatus.TELEGRAM_NO_DATA) {
|
||||
logger.debug("Parsing worked but something went wrong, so there were no CosemObjects:{}", message);
|
||||
lastKnownReadErrorMessage = errorStatus.getEventDetails();
|
||||
} else {
|
||||
final String errorMessage = errorStatus.getEventDetails() + ' ' + message;
|
||||
lastKnownReadErrorMessage = errorMessage;
|
||||
// if fatal set directly offline.
|
||||
if (errorStatus.isFatal()) {
|
||||
deviceOffline(ThingStatusDetail.CONFIGURATION_ERROR, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user