[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 = "decryptionKey";
|
||||||
public static final String CONFIGURATION_DECRYPTION_KEY_EMPTY = "";
|
public static final String CONFIGURATION_DECRYPTION_KEY_EMPTY = "";
|
||||||
public static final String CONFIGURATION_ADDITIONAL_KEY = "additionalKey";
|
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() {
|
private DSMRBindingConstants() {
|
||||||
// Constants class
|
// Constants class
|
||||||
|
|||||||
@ -55,7 +55,7 @@ public class DSMRDeviceConfiguration {
|
|||||||
/**
|
/**
|
||||||
* Austria smart meter additional decryption key
|
* 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.
|
* 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;
|
package org.openhab.binding.dsmr.internal.device;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ public class DSMRDeviceRunnable implements Runnable {
|
|||||||
private final Logger logger = LoggerFactory.getLogger(DSMRDeviceRunnable.class);
|
private final Logger logger = LoggerFactory.getLogger(DSMRDeviceRunnable.class);
|
||||||
private final Semaphore semaphore = new Semaphore(0);
|
private final Semaphore semaphore = new Semaphore(0);
|
||||||
private final DSMRDevice device;
|
private final DSMRDevice device;
|
||||||
private final DSMREventListener portEventListener;
|
private final P1TelegramListener portEventListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps state of running. If false run will stop.
|
* 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 device the device to control
|
||||||
* @param eventListener listener to used ot report errors.
|
* @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.device = device;
|
||||||
this.portEventListener = eventListener;
|
this.portEventListener = eventListener;
|
||||||
}
|
}
|
||||||
@ -83,10 +85,11 @@ public class DSMRDeviceRunnable implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.trace("Device shutdown");
|
logger.trace("Device shutdown");
|
||||||
} catch (RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
logger.warn("DSMRDeviceRunnable stopped with a RuntimeException", e);
|
logger.warn("DSMRDeviceRunnable stopped with a RuntimeException", e);
|
||||||
portEventListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
|
portEventListener.onError(DSMRErrorStatus.SERIAL_DATA_READ_ERROR,
|
||||||
} catch (InterruptedException e) {
|
Optional.ofNullable(e.getMessage()).orElse(""));
|
||||||
|
} catch (final InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
} finally {
|
} finally {
|
||||||
device.stop();
|
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.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialConnector;
|
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.connector.DSMRSerialSettings;
|
||||||
|
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
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 serialPortManager the manager to get a new serial port connecting from
|
||||||
* @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
|
* @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
|
||||||
* @param fixedPortSettings The serial port connection settings
|
* @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
|
* @param telegramListener listener to report found telegrams or errors
|
||||||
*/
|
*/
|
||||||
public DSMRFixedConfigDevice(SerialPortManager serialPortManager, String serialPortName,
|
public DSMRFixedConfigDevice(final SerialPortManager serialPortManager, final String serialPortName,
|
||||||
DSMRSerialSettings fixedPortSettings, DSMREventListener listener, DSMRTelegramListener telegramListener) {
|
final DSMRSerialSettings fixedPortSettings, final P1TelegramListener listener,
|
||||||
|
final DSMRTelegramListener telegramListener) {
|
||||||
this.fixedPortSettings = fixedPortSettings;
|
this.fixedPortSettings = fixedPortSettings;
|
||||||
this.telegramListener = telegramListener;
|
this.telegramListener = telegramListener;
|
||||||
telegramListener.setDsmrEventListener(listener);
|
telegramListener.setP1TelegramListener(listener);
|
||||||
|
|
||||||
dsmrPort = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
|
dsmrPort = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
|
||||||
}
|
}
|
||||||
@ -64,7 +66,7 @@ public class DSMRFixedConfigDevice implements DSMRDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLenientMode(boolean lenientMode) {
|
public void setLenientMode(final boolean lenientMode) {
|
||||||
telegramListener.setLenientMode(lenientMode);
|
telegramListener.setLenientMode(lenientMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,10 +18,11 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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.DSMRSerialConnector;
|
||||||
import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
|
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.P1Telegram;
|
||||||
|
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
||||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -34,7 +35,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
* settings automatically.
|
* settings automatically.
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
public class DSMRSerialAutoDevice implements DSMRDevice, P1TelegramListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum to keep track of the internal state of {@link DSMRSerialAutoDevice}.
|
* 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
|
* 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
|
* 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 serialPortManager the manager to get a new serial port connecting from
|
||||||
* @param serialPortName the port name (e.g. /dev/ttyUSB0 or COM1)
|
* @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 telegramListener listener to report found telegrams or errors
|
||||||
* @param scheduler the scheduler to use with the baudrate switching timers
|
* @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
|
* @param baudrateSwitchTimeoutSeconds timeout period for when to try other baudrate settings and end the discovery
|
||||||
* of the baudrate
|
* of the baudrate
|
||||||
*/
|
*/
|
||||||
public DSMRSerialAutoDevice(SerialPortManager serialPortManager, String serialPortName, DSMREventListener listener,
|
public DSMRSerialAutoDevice(final SerialPortManager serialPortManager, final String serialPortName,
|
||||||
DSMRTelegramListener telegramListener, ScheduledExecutorService scheduler,
|
final P1TelegramListener listener, final DSMRTelegramListener telegramListener,
|
||||||
int baudrateSwitchTimeoutSeconds) {
|
final ScheduledExecutorService scheduler, final int baudrateSwitchTimeoutSeconds) {
|
||||||
this.parentListener = listener;
|
this.parentListener = listener;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.baudrateSwitchTimeoutSeconds = baudrateSwitchTimeoutSeconds;
|
this.baudrateSwitchTimeoutSeconds = baudrateSwitchTimeoutSeconds;
|
||||||
this.telegramListener = telegramListener;
|
this.telegramListener = telegramListener;
|
||||||
telegramListener.setDsmrEventListener(listener);
|
telegramListener.setP1TelegramListener(listener);
|
||||||
dsmrConnector = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
|
dsmrConnector = new DSMRSerialConnector(serialPortManager, serialPortName, telegramListener);
|
||||||
logger.debug("Initialized port '{}'", serialPortName);
|
logger.debug("Initialized port '{}'", serialPortName);
|
||||||
}
|
}
|
||||||
@ -148,7 +149,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
|||||||
public void start() {
|
public void start() {
|
||||||
stopDiscover(DeviceState.DISCOVER_SETTINGS);
|
stopDiscover(DeviceState.DISCOVER_SETTINGS);
|
||||||
portSettings = DEFAULT_PORT_SETTINGS;
|
portSettings = DEFAULT_PORT_SETTINGS;
|
||||||
telegramListener.setDsmrEventListener(this);
|
telegramListener.setP1TelegramListener(this);
|
||||||
dsmrConnector.open(portSettings);
|
dsmrConnector.open(portSettings);
|
||||||
restartHalfTimer();
|
restartHalfTimer();
|
||||||
endTimeTimer = scheduler.schedule(this::endTimeScheduledCall,
|
endTimeTimer = scheduler.schedule(this::endTimeScheduledCall,
|
||||||
@ -178,30 +179,28 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
|||||||
* @param telegram the details of the received telegram
|
* @param telegram the details of the received telegram
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void handleTelegramReceived(P1Telegram telegram) {
|
public void telegramReceived(final P1Telegram telegram) {
|
||||||
if (!telegram.getCosemObjects().isEmpty()) {
|
stopDiscover(DeviceState.NORMAL);
|
||||||
stopDiscover(DeviceState.NORMAL);
|
parentListener.telegramReceived(telegram);
|
||||||
parentListener.handleTelegramReceived(telegram);
|
logger.info("Start receiving telegrams on port {} with settings: {}", dsmrConnector.getPortName(),
|
||||||
logger.info("Start receiving telegrams on port {} with settings: {}", dsmrConnector.getPortName(),
|
portSettings);
|
||||||
portSettings);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for DSMR Port events.
|
* Event handler for DSMR Port events.
|
||||||
*
|
*
|
||||||
* @param portEvent {@link DSMRConnectorErrorEvent} to handle
|
* @param portEvent {@link DSMRErrorStatus} to handle
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
|
public void onError(final DSMRErrorStatus portEvent, final String message) {
|
||||||
logger.trace("Received portEvent {}", portEvent.getEventDetails());
|
logger.trace("Received portEvent {}", portEvent.getEventDetails());
|
||||||
if (portEvent == DSMRConnectorErrorEvent.READ_ERROR) {
|
if (portEvent == DSMRErrorStatus.SERIAL_DATA_READ_ERROR) {
|
||||||
switchBaudrate();
|
switchBaudrate();
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Error during discovery of port settings: {}, current state:{}.", portEvent.getEventDetails(),
|
logger.debug("Error during discovery of port settings: {}, current state:{}.", portEvent.getEventDetails(),
|
||||||
state);
|
state);
|
||||||
stopDiscover(DeviceState.ERROR);
|
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
|
* @param lenientMode the lenientMode to set
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setLenientMode(boolean lenientMode) {
|
public void setLenientMode(final boolean lenientMode) {
|
||||||
telegramListener.setLenientMode(lenientMode);
|
telegramListener.setLenientMode(lenientMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,7 +247,7 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
|||||||
private void endTimeScheduledCall() {
|
private void endTimeScheduledCall() {
|
||||||
if (state == DeviceState.DISCOVER_SETTINGS) {
|
if (state == DeviceState.DISCOVER_SETTINGS) {
|
||||||
stopDiscover(DeviceState.ERROR);
|
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.
|
* @param state the state with which the process was stopped.
|
||||||
*/
|
*/
|
||||||
private void stopDiscover(DeviceState state) {
|
private void stopDiscover(final DeviceState state) {
|
||||||
telegramListener.setDsmrEventListener(parentListener);
|
this.state = state;
|
||||||
|
telegramListener.setP1TelegramListener(parentListener);
|
||||||
logger.debug("Stop discovery of port settings.");
|
logger.debug("Stop discovery of port settings.");
|
||||||
if (halfTimeTimer != null) {
|
if (halfTimeTimer != null) {
|
||||||
halfTimeTimer.cancel(true);
|
halfTimeTimer.cancel(true);
|
||||||
@ -268,7 +268,6 @@ public class DSMRSerialAutoDevice implements DSMRDevice, DSMREventListener {
|
|||||||
endTimeTimer.cancel(true);
|
endTimeTimer.cancel(true);
|
||||||
endTimeTimer = null;
|
endTimeTimer = null;
|
||||||
}
|
}
|
||||||
this.state = state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -13,14 +13,12 @@
|
|||||||
package org.openhab.binding.dsmr.internal.device;
|
package org.openhab.binding.dsmr.internal.device;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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.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.cosem.CosemObject;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
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.P1TelegramListener;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
|
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
|
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 Logger logger = LoggerFactory.getLogger(DSMRTelegramListener.class);
|
||||||
private final TelegramParser parser;
|
private final TelegramParser parser;
|
||||||
|
|
||||||
private @NonNullByDefault({}) DSMREventListener dsmrEventListener;
|
private @NonNullByDefault({}) P1TelegramListener p1TelegramListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* 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) {
|
public void setP1TelegramListener(final P1TelegramListener p1TelegramListener) {
|
||||||
this.dsmrEventListener = eventListener;
|
this.p1TelegramListener = p1TelegramListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle calls from the Connector
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleData(final byte[] data, final int length) {
|
public void handleData(final byte[] data, final int length) {
|
||||||
parser.parse(data, length);
|
parser.parse(data, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
|
public void handleError(final DSMRErrorStatus portEvent, final String message) {
|
||||||
dsmrEventListener.handleErrorEvent(portEvent);
|
onError(portEvent, message);
|
||||||
parser.reset();
|
parser.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle calls from the Parser
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for cosemObjects received in a P1 telegram
|
* Handler for cosemObjects received in a P1 telegram
|
||||||
*
|
*
|
||||||
@ -88,22 +90,23 @@ public class DSMRTelegramListener implements P1TelegramListener, DSMRConnectorLi
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void telegramReceived(final P1Telegram telegram) {
|
public void telegramReceived(final P1Telegram telegram) {
|
||||||
final TelegramState telegramState = telegram.getTelegramState();
|
|
||||||
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
|
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
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) {
|
if (cosemObjects.isEmpty()) {
|
||||||
dsmrEventListener.handleTelegramReceived(telegram);
|
onError(DSMRErrorStatus.TELEGRAM_NO_DATA, "");
|
||||||
} else {
|
} else {
|
||||||
if (logger.isDebugEnabled()) {
|
p1TelegramListener.telegramReceived(telegram);
|
||||||
logger.debug("Telegram received with error state '{}': {}", telegramState,
|
|
||||||
cosemObjects.stream().map(CosemObject::toString).collect(Collectors.joining(",")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(final DSMRErrorStatus state, final String message) {
|
||||||
|
p1TelegramListener.onError(state, message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param lenientMode the lenientMode to set
|
* @param lenientMode the lenientMode to set
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -16,7 +16,8 @@ import java.nio.ByteBuffer;
|
|||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Collections;
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
@ -28,8 +29,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
|
import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
|
||||||
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.P1TelegramListener;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
|
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
|
||||||
import org.openhab.core.util.HexUtils;
|
import org.openhab.core.util.HexUtils;
|
||||||
@ -53,8 +53,7 @@ public class SmartyDecrypter implements TelegramParser {
|
|||||||
READ_SEPARATOR_30,
|
READ_SEPARATOR_30,
|
||||||
READ_FRAME_COUNTER,
|
READ_FRAME_COUNTER,
|
||||||
READ_PAYLOAD,
|
READ_PAYLOAD,
|
||||||
READ_GCM_TAG,
|
READ_GCM_TAG
|
||||||
DONE_READING_TELEGRAM
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final byte START_BYTE = (byte) 0xDB;
|
private static final byte START_BYTE = (byte) 0xDB;
|
||||||
@ -95,7 +94,7 @@ public class SmartyDecrypter implements TelegramParser {
|
|||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
this.telegramListener = telegramListener;
|
this.telegramListener = telegramListener;
|
||||||
secretKeySpec = decryptionKey.isEmpty() ? null : new SecretKeySpec(HexUtils.hexToBytes(decryptionKey), "AES");
|
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));
|
: ((additionalKey.length() == 32 ? (ADDITIONAL_ADD_PREFIX) : "") + additionalKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +112,11 @@ public class SmartyDecrypter implements TelegramParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean processStateActions(final byte rawInput) {
|
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) {
|
switch (state) {
|
||||||
case WAITING_FOR_START_BYTE:
|
case WAITING_FOR_START_BYTE:
|
||||||
if (rawInput == START_BYTE) {
|
if (rawInput == START_BYTE) {
|
||||||
@ -179,26 +183,23 @@ public class SmartyDecrypter implements TelegramParser {
|
|||||||
// All input has been read.
|
// All input has been read.
|
||||||
cipherText.put(rawInput);
|
cipherText.put(rawInput);
|
||||||
if (currentBytePosition >= changeToNextStateAt) {
|
if (currentBytePosition >= changeToNextStateAt) {
|
||||||
state = State.DONE_READING_TELEGRAM;
|
state = State.WAITING_FOR_START_BYTE;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (state == State.DONE_READING_TELEGRAM) {
|
|
||||||
state = State.WAITING_FOR_START_BYTE;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processCompleted() {
|
private void processCompleted() {
|
||||||
final byte[] plainText = decrypt();
|
try {
|
||||||
|
final byte[] plainText = decrypt();
|
||||||
|
|
||||||
reset();
|
if (plainText != null) {
|
||||||
if (plainText == null) {
|
parser.parse(plainText, plainText.length);
|
||||||
telegramListener
|
}
|
||||||
.telegramReceived(new P1Telegram(Collections.emptyList(), TelegramState.INVALID_ENCRYPTION_KEY));
|
} finally {
|
||||||
} else {
|
reset();
|
||||||
parser.parse(plainText, plainText.length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +219,15 @@ public class SmartyDecrypter implements TelegramParser {
|
|||||||
}
|
}
|
||||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
| 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ package org.openhab.binding.dsmr.internal.device.connector;
|
|||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
@ -36,7 +37,7 @@ class DSMRBaseConnector {
|
|||||||
/**
|
/**
|
||||||
* Listener to send received data and errors to.
|
* Listener to send received data and errors to.
|
||||||
*/
|
*/
|
||||||
protected final DSMRConnectorListener dsmrConnectorListener;
|
private final DSMRConnectorListener dsmrConnectorListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1Kbyte buffer for storing received data.
|
* 1Kbyte buffer for storing received data.
|
||||||
@ -53,7 +54,7 @@ class DSMRBaseConnector {
|
|||||||
*/
|
*/
|
||||||
private boolean open;
|
private boolean open;
|
||||||
|
|
||||||
public DSMRBaseConnector(DSMRConnectorListener connectorListener) {
|
public DSMRBaseConnector(final DSMRConnectorListener connectorListener) {
|
||||||
this.dsmrConnectorListener = connectorListener;
|
this.dsmrConnectorListener = connectorListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ class DSMRBaseConnector {
|
|||||||
* @param inputStream input stream to read data from
|
* @param inputStream input stream to read data from
|
||||||
* @throws IOException throws exception in case input stream is null
|
* @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) {
|
if (inputStream == null) {
|
||||||
throw new IOException("Inputstream is null");
|
throw new IOException("Inputstream is null");
|
||||||
}
|
}
|
||||||
@ -91,7 +92,7 @@ class DSMRBaseConnector {
|
|||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
try {
|
try {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
} catch (IOException ioe) {
|
} catch (final IOException ioe) {
|
||||||
logger.debug("Failed to close reader", ioe);
|
logger.debug("Failed to close reader", ioe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,12 +105,12 @@ class DSMRBaseConnector {
|
|||||||
protected void handleDataAvailable() {
|
protected void handleDataAvailable() {
|
||||||
try {
|
try {
|
||||||
synchronized (readLock) {
|
synchronized (readLock) {
|
||||||
BufferedInputStream localInputStream = inputStream;
|
final BufferedInputStream localInputStream = inputStream;
|
||||||
|
|
||||||
if (localInputStream != null) {
|
if (localInputStream != null) {
|
||||||
int bytesAvailable = localInputStream.available();
|
int bytesAvailable = localInputStream.available();
|
||||||
while (bytesAvailable > 0) {
|
while (bytesAvailable > 0) {
|
||||||
int bytesAvailableRead = localInputStream.read(buffer, 0,
|
final int bytesAvailableRead = localInputStream.read(buffer, 0,
|
||||||
Math.min(bytesAvailable, buffer.length));
|
Math.min(bytesAvailable, buffer.length));
|
||||||
|
|
||||||
if (open && bytesAvailableRead > 0) {
|
if (open && bytesAvailableRead > 0) {
|
||||||
@ -122,8 +123,9 @@ class DSMRBaseConnector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.READ_ERROR);
|
dsmrConnectorListener.handleError(DSMRErrorStatus.SERIAL_DATA_READ_ERROR,
|
||||||
|
Optional.ofNullable(e.getMessage()).orElse(""));
|
||||||
logger.debug("Exception on read data", e);
|
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 {
|
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.
|
* 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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.TooManyListenersException;
|
import java.util.TooManyListenersException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
@ -67,7 +68,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
|
|||||||
/**
|
/**
|
||||||
* Serial port instance.
|
* Serial port instance.
|
||||||
*/
|
*/
|
||||||
private AtomicReference<@Nullable SerialPort> serialPortReference = new AtomicReference<>();
|
private final AtomicReference<@Nullable SerialPort> serialPortReference = new AtomicReference<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DSMR Connector listener.
|
* 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 serialPortName Device identifier of the port (e.g. /dev/ttyUSB0)
|
||||||
* @param dsmrConnectorListener The listener to send error or received data from the port
|
* @param dsmrConnectorListener The listener to send error or received data from the port
|
||||||
*/
|
*/
|
||||||
public DSMRSerialConnector(SerialPortManager portManager, String serialPortName,
|
public DSMRSerialConnector(final SerialPortManager portManager, final String serialPortName,
|
||||||
DSMRConnectorListener dsmrConnectorListener) {
|
final DSMRConnectorListener dsmrConnectorListener) {
|
||||||
super(dsmrConnectorListener);
|
super(dsmrConnectorListener);
|
||||||
this.portManager = portManager;
|
this.portManager = portManager;
|
||||||
this.serialPortName = serialPortName;
|
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
|
* @param portSettings The serial port settings to open the port with
|
||||||
*/
|
*/
|
||||||
public void open(DSMRSerialSettings portSettings) {
|
public void open(final DSMRSerialSettings portSettings) {
|
||||||
DSMRConnectorErrorEvent errorEvent = null;
|
DSMRErrorStatus errorStatus = null;
|
||||||
|
|
||||||
synchronized (portLock) {
|
synchronized (portLock) {
|
||||||
SerialPortIdentifier portIdentifier = portManager.getIdentifier(serialPortName);
|
final SerialPortIdentifier portIdentifier = portManager.getIdentifier(serialPortName);
|
||||||
if (portIdentifier == null) {
|
if (portIdentifier == null) {
|
||||||
logger.debug("Port {} does not exists", serialPortName);
|
logger.debug("Port {} does not exists", serialPortName);
|
||||||
|
|
||||||
errorEvent = DSMRConnectorErrorEvent.DONT_EXISTS;
|
errorStatus = DSMRErrorStatus.PORT_DONT_EXISTS;
|
||||||
} else {
|
} else {
|
||||||
errorEvent = open(portSettings, portIdentifier);
|
errorStatus = open(portSettings, portIdentifier);
|
||||||
}
|
}
|
||||||
if (errorEvent != null) {
|
if (errorStatus != null) {
|
||||||
// handle event within lock
|
// handle event within lock
|
||||||
dsmrConnectorListener.handleErrorEvent(errorEvent);
|
dsmrConnectorListener.handleError(errorStatus, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable DSMRConnectorErrorEvent open(DSMRSerialSettings portSettings,
|
private @Nullable DSMRErrorStatus open(final DSMRSerialSettings portSettings,
|
||||||
SerialPortIdentifier portIdentifier) {
|
final SerialPortIdentifier portIdentifier) {
|
||||||
DSMRConnectorErrorEvent errorEvent = null;
|
DSMRErrorStatus errorStatus = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.trace("Opening port {}", serialPortName);
|
logger.trace("Opening port {}", serialPortName);
|
||||||
SerialPort oldSerialPort = serialPortReference.get();
|
final SerialPort oldSerialPort = serialPortReference.get();
|
||||||
|
|
||||||
// Opening Operating System Serial Port
|
// 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);
|
SERIAL_PORT_READ_TIMEOUT_MILLISECONDS);
|
||||||
|
|
||||||
// Configure Serial Port based on specified port speed
|
// Configure Serial Port based on specified port speed
|
||||||
@ -157,39 +158,39 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
serialPort.enableReceiveThreshold(SERIAL_TIMEOUT_MILLISECONDS);
|
serialPort.enableReceiveThreshold(SERIAL_TIMEOUT_MILLISECONDS);
|
||||||
} catch (UnsupportedCommOperationException e) {
|
} catch (final UnsupportedCommOperationException e) {
|
||||||
logger.debug("Enable receive threshold is unsupported");
|
logger.debug("Enable receive threshold is unsupported");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
serialPort.enableReceiveTimeout(SERIAL_TIMEOUT_MILLISECONDS);
|
serialPort.enableReceiveTimeout(SERIAL_TIMEOUT_MILLISECONDS);
|
||||||
} catch (UnsupportedCommOperationException e) {
|
} catch (final UnsupportedCommOperationException e) {
|
||||||
logger.debug("Enable receive timeout is unsupported");
|
logger.debug("Enable receive timeout is unsupported");
|
||||||
}
|
}
|
||||||
// The binding is ready, let the meter know we want to receive values
|
// The binding is ready, let the meter know we want to receive values
|
||||||
serialPort.setRTS(true);
|
serialPort.setRTS(true);
|
||||||
if (!serialPortReference.compareAndSet(oldSerialPort, serialPort)) {
|
if (!serialPortReference.compareAndSet(oldSerialPort, serialPort)) {
|
||||||
logger.warn("Possible bug because a new serial port value was set during opening new port.");
|
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);
|
logger.debug("Failed to get inputstream for serialPort", ioe);
|
||||||
|
|
||||||
errorEvent = DSMRConnectorErrorEvent.READ_ERROR;
|
errorStatus = DSMRErrorStatus.SERIAL_DATA_READ_ERROR;
|
||||||
} catch (TooManyListenersException tmle) {
|
} catch (final TooManyListenersException tmle) {
|
||||||
logger.warn("Possible bug because a listener was added while one already set.", tmle);
|
logger.warn("Possible bug because a listener was added while one already set.", tmle);
|
||||||
|
|
||||||
errorEvent = DSMRConnectorErrorEvent.INTERNAL_ERROR;
|
errorStatus = DSMRErrorStatus.PORT_INTERNAL_ERROR;
|
||||||
} catch (PortInUseException piue) {
|
} catch (final PortInUseException piue) {
|
||||||
logger.debug("Port already in use: {}", serialPortName, piue);
|
logger.debug("Port already in use: {}", serialPortName, piue);
|
||||||
|
|
||||||
errorEvent = DSMRConnectorErrorEvent.IN_USE;
|
errorStatus = DSMRErrorStatus.PORT_IN_USE;
|
||||||
} catch (UnsupportedCommOperationException ucoe) {
|
} catch (final UnsupportedCommOperationException ucoe) {
|
||||||
logger.debug("Port does not support requested port settings (invalid dsmr:portsettings parameter?): {}",
|
logger.debug("Port does not support requested port settings (invalid dsmr:portsettings parameter?): {}",
|
||||||
serialPortName, ucoe);
|
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.setRTS(false);
|
||||||
serialPort.removeEventListener();
|
serialPort.removeEventListener();
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = serialPort.getInputStream();
|
final InputStream inputStream = serialPort.getInputStream();
|
||||||
|
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (final IOException ioe) {
|
||||||
logger.debug("Failed to close serial port inputstream", ioe);
|
logger.debug("Failed to close serial port inputstream", ioe);
|
||||||
}
|
}
|
||||||
serialPort.close();
|
serialPort.close();
|
||||||
@ -228,22 +229,23 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
|
|||||||
*
|
*
|
||||||
* @param portSettings the port settings to set on the serial port
|
* @param portSettings the port settings to set on the serial port
|
||||||
*/
|
*/
|
||||||
public void setSerialPortParams(DSMRSerialSettings portSettings) {
|
public void setSerialPortParams(final DSMRSerialSettings portSettings) {
|
||||||
synchronized (portLock) {
|
synchronized (portLock) {
|
||||||
if (isOpen()) {
|
if (isOpen()) {
|
||||||
logger.debug("Update port {} with settings: {}", this.serialPortName, portSettings);
|
logger.debug("Update port {} with settings: {}", this.serialPortName, portSettings);
|
||||||
try {
|
try {
|
||||||
SerialPort serialPort = serialPortReference.get();
|
final SerialPort serialPort = serialPortReference.get();
|
||||||
|
|
||||||
if (serialPort != null) {
|
if (serialPort != null) {
|
||||||
serialPort.setSerialPortParams(portSettings.getBaudrate(), portSettings.getDataBits(),
|
serialPort.setSerialPortParams(portSettings.getBaudrate(), portSettings.getDataBits(),
|
||||||
portSettings.getStopbits(), portSettings.getParity());
|
portSettings.getStopbits(), portSettings.getParity());
|
||||||
}
|
}
|
||||||
} catch (UnsupportedCommOperationException e) {
|
} catch (final UnsupportedCommOperationException e) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Port does {} not support requested port settings (invalid dsmr:portsettings parameter?): {}",
|
"Port does {} not support requested port settings (invalid dsmr:portsettings parameter?): {}",
|
||||||
serialPortName, portSettings);
|
serialPortName, portSettings);
|
||||||
dsmrConnectorListener.handleErrorEvent(DSMRConnectorErrorEvent.NOT_COMPATIBLE);
|
dsmrConnectorListener.handleError(DSMRErrorStatus.PORT_NOT_COMPATIBLE,
|
||||||
|
Optional.ofNullable(e.getMessage()).orElse(""));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
restart(portSettings);
|
restart(portSettings);
|
||||||
@ -254,7 +256,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
|
|||||||
/**
|
/**
|
||||||
* Switch the Serial Port speed (LOW --> HIGH and vice versa).
|
* Switch the Serial Port speed (LOW --> HIGH and vice versa).
|
||||||
*/
|
*/
|
||||||
public void restart(DSMRSerialSettings portSettings) {
|
public void restart(final DSMRSerialSettings portSettings) {
|
||||||
synchronized (portLock) {
|
synchronized (portLock) {
|
||||||
logger.trace("Restart port {} with settings: {}", this.serialPortName, portSettings);
|
logger.trace("Restart port {} with settings: {}", this.serialPortName, portSettings);
|
||||||
close();
|
close();
|
||||||
@ -263,7 +265,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serialEvent(@Nullable SerialPortEvent seEvent) {
|
public void serialEvent(@Nullable final SerialPortEvent seEvent) {
|
||||||
if (seEvent == null) {
|
if (seEvent == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -289,7 +291,7 @@ public class DSMRSerialConnector extends DSMRBaseConnector implements SerialPort
|
|||||||
break;
|
break;
|
||||||
default: // do nothing
|
default: // do nothing
|
||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
logger.warn("RuntimeException during handling serial event: {}", seEvent.getEventType(), 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 typeName type of the event, used in logging only
|
||||||
* @param portEvent Serial port event that triggered the error.
|
* @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()) {
|
if (isOpen() && portEvent.getNewValue()) {
|
||||||
logger.trace("New DSMR port {} event", typeName);
|
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);
|
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 */
|
/* the six individual group values of the OBIS ID */
|
||||||
private final int groupA;
|
private final int groupA;
|
||||||
private final @Nullable Integer channel;
|
private final @Nullable Integer channel;
|
||||||
@ -91,25 +96,41 @@ public class OBISIdentifier {
|
|||||||
|
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
// Optional value A
|
// Optional value A
|
||||||
this.groupA = m.group(2) == null ? null : Integer.parseInt(m.group(2));
|
this.groupA = safeInt(m.group(2));
|
||||||
|
|
||||||
// Optional value B
|
// 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
|
// Required value C & D
|
||||||
this.groupC = Integer.parseInt(m.group(6));
|
this.groupC = safeInt(m.group(6));
|
||||||
this.groupD = Integer.parseInt(m.group(7));
|
this.groupD = safeInt(m.group(7));
|
||||||
|
|
||||||
// Optional value E
|
// Optional value E
|
||||||
this.groupE = m.group(9) == null ? null : Integer.valueOf(m.group(9));
|
this.groupE = safeInteger(m.group(9));
|
||||||
|
|
||||||
// Optional value F
|
// Optional value F
|
||||||
this.groupF = m.group(11) == null ? null : Integer.valueOf(m.group(11));
|
this.groupF = safeInteger(m.group(11));
|
||||||
} else {
|
} else {
|
||||||
throw new ParseException("Invalid OBIS identifier:" + obisIDString, 0);
|
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() {
|
public boolean isConflict() {
|
||||||
return conflict;
|
return conflict;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,54 +31,18 @@ public class P1Telegram {
|
|||||||
/**
|
/**
|
||||||
* The TelegramState described the meta data of the 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 List<CosemObject> cosemObjects;
|
||||||
private final TelegramState telegramState;
|
|
||||||
private final String rawTelegram;
|
private final String rawTelegram;
|
||||||
private final List<Entry<String, String>> unknownCosemObjects;
|
private final List<Entry<String, String>> unknownCosemObjects;
|
||||||
|
|
||||||
public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState) {
|
public P1Telegram(final List<CosemObject> cosemObjects) {
|
||||||
this(cosemObjects, telegramState, "", Collections.emptyList());
|
this(cosemObjects, "", Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public P1Telegram(List<CosemObject> cosemObjects, TelegramState telegramState, String rawTelegram,
|
public P1Telegram(final List<CosemObject> cosemObjects, final String rawTelegram,
|
||||||
List<Entry<String, String>> unknownCosemObjects) {
|
final List<Entry<String, String>> unknownCosemObjects) {
|
||||||
this.cosemObjects = cosemObjects;
|
this.cosemObjects = cosemObjects;
|
||||||
this.telegramState = telegramState;
|
|
||||||
this.rawTelegram = rawTelegram;
|
this.rawTelegram = rawTelegram;
|
||||||
this.unknownCosemObjects = unknownCosemObjects;
|
this.unknownCosemObjects = unknownCosemObjects;
|
||||||
}
|
}
|
||||||
@ -97,13 +61,6 @@ public class P1Telegram {
|
|||||||
return rawTelegram;
|
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
|
* @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;
|
package org.openhab.binding.dsmr.internal.device.p1telegram;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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
|
* Interface for receiving CosemObjects that come from a P1 Telegram
|
||||||
@ -23,10 +24,18 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public interface P1TelegramListener {
|
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.
|
* Callback on received telegram.
|
||||||
*
|
*
|
||||||
* @param telegram The 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.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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.CosemObject;
|
||||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectFactory;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -106,7 +107,7 @@ public class P1TelegramParser implements TelegramParser {
|
|||||||
/**
|
/**
|
||||||
* Current telegram state
|
* Current telegram state
|
||||||
*/
|
*/
|
||||||
private volatile TelegramState telegramState;
|
private volatile Optional<DSMRErrorStatus> telegramState = Optional.empty();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CosemObjectFactory helper class
|
* CosemObjectFactory helper class
|
||||||
@ -116,7 +117,7 @@ public class P1TelegramParser implements TelegramParser {
|
|||||||
/**
|
/**
|
||||||
* Received Cosem Objects in the P1Telegram that is currently received
|
* 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.
|
* List of Cosem Object values that are not known to this binding.
|
||||||
@ -138,18 +139,18 @@ public class P1TelegramParser implements TelegramParser {
|
|||||||
*
|
*
|
||||||
* @param telegramListener
|
* @param telegramListener
|
||||||
*/
|
*/
|
||||||
public P1TelegramParser(P1TelegramListener telegramListener) {
|
public P1TelegramParser(final P1TelegramListener telegramListener) {
|
||||||
this(telegramListener, false);
|
this(telegramListener, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public P1TelegramParser(P1TelegramListener telegramListener, boolean test) {
|
public P1TelegramParser(final P1TelegramListener telegramListener, final boolean test) {
|
||||||
this.telegramListener = telegramListener;
|
this.telegramListener = telegramListener;
|
||||||
this.test = test;
|
this.test = test;
|
||||||
|
|
||||||
factory = new CosemObjectFactory();
|
factory = new CosemObjectFactory();
|
||||||
state = State.WAIT_FOR_START;
|
state = State.WAIT_FOR_START;
|
||||||
crc = new CRC16(CRC16.Polynom.CRC16_IBM);
|
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
|
* @param length number of bytes to parse
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void parse(byte[] data, int length) {
|
public void parse(final byte[] data, final int length) {
|
||||||
if (lenientMode || logger.isTraceEnabled()) {
|
if (lenientMode || logger.isTraceEnabled()) {
|
||||||
final String rawBlock = new String(data, 0, length, StandardCharsets.UTF_8);
|
final String rawBlock = new String(data, 0, length, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
@ -254,10 +255,11 @@ public class P1TelegramParser implements TelegramParser {
|
|||||||
if (c == '\r' || c == '/') {
|
if (c == '\r' || c == '/') {
|
||||||
logger.trace("telegramState {}, crcValue to check 0x{}", telegramState, crcValue);
|
logger.trace("telegramState {}, crcValue to check 0x{}", telegramState, crcValue);
|
||||||
// Only perform CRC check if telegram is still ok
|
// 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();
|
reset();
|
||||||
if (c == '/') {
|
if (c == '/') {
|
||||||
/*
|
/*
|
||||||
@ -275,8 +277,8 @@ public class P1TelegramParser implements TelegramParser {
|
|||||||
logger.trace("State after parsing: {}", state);
|
logger.trace("State after parsing: {}", state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TelegramState checkCRC(TelegramState currentState) {
|
private Optional<DSMRErrorStatus> checkCRC() {
|
||||||
final TelegramState telegramState;
|
final Optional<DSMRErrorStatus> telegramState;
|
||||||
|
|
||||||
if (Pattern.matches(CRC_PATTERN, crcValue)) {
|
if (Pattern.matches(CRC_PATTERN, crcValue)) {
|
||||||
final int crcP1Telegram = Integer.parseInt(crcValue.toString(), 16);
|
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");
|
logger.trace("CRC value does not match, p1 Telegram failed");
|
||||||
|
|
||||||
telegramState = TelegramState.CRC_ERROR;
|
telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_CRC_ERROR);
|
||||||
} else {
|
} else {
|
||||||
telegramState = currentState;
|
telegramState = Optional.empty();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
telegramState = TelegramState.CRC_ERROR;
|
telegramState = Optional.of(DSMRErrorStatus.TELEGRAM_CRC_ERROR);
|
||||||
}
|
}
|
||||||
return telegramState;
|
return telegramState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private P1Telegram constructTelegram() {
|
private void processTelegram() {
|
||||||
final List<CosemObject> cosemObjectsCopy = new ArrayList<>(cosemObjects);
|
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) {
|
if (lenientMode) {
|
||||||
return new P1Telegram(cosemObjectsCopy, telegramState, rawData.toString(),
|
return new P1Telegram(cosemObjectsCopy, rawData.toString(),
|
||||||
unknownCosemObjects.isEmpty() ? Collections.emptyList() : new ArrayList<>(unknownCosemObjects));
|
unknownCosemObjects.isEmpty() ? Collections.emptyList() : new ArrayList<>(unknownCosemObjects));
|
||||||
} else {
|
} 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
|
* @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);
|
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
|
* @param c the character to process
|
||||||
*/
|
*/
|
||||||
private void handleCharacter(char c) {
|
private void handleCharacter(final char c) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case WAIT_FOR_START:
|
case WAIT_FOR_START:
|
||||||
// ignore the data
|
// ignore the data
|
||||||
@ -401,17 +425,7 @@ public class P1TelegramParser implements TelegramParser {
|
|||||||
final String obisIdString = obisId.toString();
|
final String obisIdString = obisId.toString();
|
||||||
|
|
||||||
if (!obisIdString.isEmpty()) {
|
if (!obisIdString.isEmpty()) {
|
||||||
final String obisValueString = obisValue.toString();
|
cosemObjects.add(new SimpleEntry<String, String>(obisIdString, 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
clearObisData();
|
clearObisData();
|
||||||
}
|
}
|
||||||
@ -419,7 +433,7 @@ public class P1TelegramParser implements TelegramParser {
|
|||||||
/**
|
/**
|
||||||
* @param newState the new state to set
|
* @param newState the new state to set
|
||||||
*/
|
*/
|
||||||
private void setState(State newState) {
|
private void setState(final State newState) {
|
||||||
synchronized (state) {
|
synchronized (state) {
|
||||||
switch (newState) {
|
switch (newState) {
|
||||||
case HEADER:
|
case HEADER:
|
||||||
@ -429,7 +443,7 @@ public class P1TelegramParser implements TelegramParser {
|
|||||||
case WAIT_FOR_START:
|
case WAIT_FOR_START:
|
||||||
// Clears internal state data and mark current telegram as OK
|
// Clears internal state data and mark current telegram as OK
|
||||||
clearInternalData();
|
clearInternalData();
|
||||||
telegramState = TelegramState.OK;
|
telegramState = Optional.empty();
|
||||||
break;
|
break;
|
||||||
case DATA_OBIS_ID:
|
case DATA_OBIS_ID:
|
||||||
// If the current state is CRLF we are processing the header and don't have a cosem object yet
|
// 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
|
@Override
|
||||||
public void setLenientMode(boolean lenientMode) {
|
public void setLenientMode(final boolean lenientMode) {
|
||||||
this.lenientMode = lenientMode;
|
this.lenientMode = lenientMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
package org.openhab.binding.dsmr.internal.discovery;
|
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;
|
||||||
|
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;
|
||||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY_EMPTY;
|
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_DECRYPTION_KEY_EMPTY;
|
||||||
import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.CONFIGURATION_SERIAL_PORT;
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
|
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.DSMRSerialAutoDevice;
|
||||||
import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
|
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.cosem.CosemObject;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
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.DiscoveryResult;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
import org.openhab.core.config.discovery.DiscoveryService;
|
import org.openhab.core.config.discovery.DiscoveryService;
|
||||||
@ -69,7 +69,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.dsmr")
|
@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.
|
* 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);
|
logger.debug("Start discovery on serial port: {}", currentScannedPortName);
|
||||||
//
|
//
|
||||||
final DSMRTelegramListener telegramListener = new DSMRTelegramListener("",
|
final DSMRTelegramListener telegramListener = new DSMRTelegramListener("",
|
||||||
CONFIGURATION_ADDITIONAL_KEY);
|
CONFIGURATION_ADDITIONAL_KEY_DEFAULT);
|
||||||
final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager,
|
final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager,
|
||||||
portIdentifier.getName(), this, telegramListener, scheduler,
|
portIdentifier.getName(), this, telegramListener, scheduler,
|
||||||
BAUDRATE_SWITCH_TIMEOUT_SECONDS);
|
BAUDRATE_SWITCH_TIMEOUT_SECONDS);
|
||||||
@ -173,20 +173,25 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
|
|||||||
* @param telegram the received telegram
|
* @param telegram the received telegram
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void handleTelegramReceived(final P1Telegram telegram) {
|
public void telegramReceived(final P1Telegram telegram) {
|
||||||
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
|
final List<CosemObject> cosemObjects = telegram.getCosemObjects();
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("[{}] Received {} cosemObjects", currentScannedPortName, cosemObjects.size());
|
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);
|
bridgeDiscovered(THING_TYPE_SMARTY_BRIDGE);
|
||||||
stopSerialPortScan();
|
} else {
|
||||||
} else if (!cosemObjects.isEmpty()) {
|
logger.debug("[{}] Error on port during discovery: {} - {}", currentScannedPortName, portEvent, message);
|
||||||
final ThingUID bridgeThingUID = bridgeDiscovered(THING_TYPE_DSMR_BRIDGE);
|
|
||||||
meterDetector.detectMeters(telegram).getKey().forEach(m -> meterDiscovered(m, bridgeThingUID));
|
|
||||||
stopSerialPortScan();
|
|
||||||
}
|
}
|
||||||
|
stopSerialPortScan();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -205,7 +210,7 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
|
|||||||
properties.put(CONFIGURATION_SERIAL_PORT, currentScannedPortName);
|
properties.put(CONFIGURATION_SERIAL_PORT, currentScannedPortName);
|
||||||
if (smarty) {
|
if (smarty) {
|
||||||
properties.put(CONFIGURATION_DECRYPTION_KEY, CONFIGURATION_DECRYPTION_KEY_EMPTY);
|
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)
|
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||||
.withThingType(bridgeThingTypeUID).withProperties(properties).withLabel(label).build();
|
.withThingType(bridgeThingTypeUID).withProperties(properties).withLabel(label).build();
|
||||||
@ -215,10 +220,4 @@ public class DSMRBridgeDiscoveryService extends DSMRDiscoveryService implements
|
|||||||
thingDiscovered(discoveryResult);
|
thingDiscovered(discoveryResult);
|
||||||
return thingUID;
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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.CosemObject;
|
||||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
|
import org.openhab.binding.dsmr.internal.device.cosem.CosemObjectType;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||||
@ -74,7 +75,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setThingHandler(ThingHandler handler) {
|
public void setThingHandler(final ThingHandler handler) {
|
||||||
if (handler instanceof DSMRBridgeHandler) {
|
if (handler instanceof DSMRBridgeHandler) {
|
||||||
dsmrBridgeHandler = (DSMRBridgeHandler) handler;
|
dsmrBridgeHandler = (DSMRBridgeHandler) handler;
|
||||||
}
|
}
|
||||||
@ -96,7 +97,7 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void telegramReceived(P1Telegram telegram) {
|
public void telegramReceived(final P1Telegram telegram) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Detect meters from #{} objects", telegram.getCosemObjects().size());
|
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()));
|
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.isEmpty()) {
|
||||||
if (list.stream()
|
if (list.stream()
|
||||||
.anyMatch(e -> e.getType() == CosemObjectType.METER_EQUIPMENT_IDENTIFIER
|
.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.
|
* @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));
|
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 things The list of configured things
|
||||||
* @param configuredMeterTypes The set of meters detected in the telegram
|
* @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
|
// @formatter:off
|
||||||
final Set<DSMRMeterType> configuredMeters = things.stream()
|
final Set<DSMRMeterType> configuredMeters = things.stream()
|
||||||
.map(Thing::getHandler)
|
.map(Thing::getHandler)
|
||||||
@ -188,8 +194,8 @@ public class DSMRMeterDiscoveryService extends DSMRDiscoveryService implements P
|
|||||||
* @param invalidConfigured The list of invalid configured meters
|
* @param invalidConfigured The list of invalid configured meters
|
||||||
* @param unconfiguredMeters The list of meters that were detected, but not configured
|
* @param unconfiguredMeters The list of meters that were detected, but not configured
|
||||||
*/
|
*/
|
||||||
protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
|
protected void reportConfigurationValidationResults(final List<DSMRMeterType> invalidConfigured,
|
||||||
List<DSMRMeterType> unconfiguredMeters) {
|
final List<DSMRMeterType> unconfiguredMeters) {
|
||||||
logger.info(
|
logger.info(
|
||||||
"Possible incorrect meters configured. These are configured: {}."
|
"Possible incorrect meters configured. These are configured: {}."
|
||||||
+ "But the following unconfigured meters are found in the data received from the meter: {}",
|
+ "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;
|
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 static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_SMARTY_BRIDGE;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -21,14 +23,14 @@ import java.util.concurrent.ScheduledFuture;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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.DSMRDevice;
|
||||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceConfiguration;
|
import org.openhab.binding.dsmr.internal.device.DSMRDeviceConfiguration;
|
||||||
import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
|
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.DSMRFixedConfigDevice;
|
||||||
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
|
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
|
||||||
import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
|
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.connector.DSMRSerialSettings;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
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.BaseBridgeHandler;
|
||||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.util.HexUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.
|
* @author Hilbrand Bouwkamp - Refactored way messages are forwarded to meters. Removed availableMeters dependency.
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@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
|
* 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 final boolean smartyMeter;
|
||||||
|
|
||||||
|
private @Nullable String lastKnownReadErrorMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -143,9 +148,7 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
|
|||||||
public void initialize() {
|
public void initialize() {
|
||||||
final DSMRDeviceConfiguration deviceConfig = getConfigAs(DSMRDeviceConfiguration.class);
|
final DSMRDeviceConfiguration deviceConfig = getConfigAs(DSMRDeviceConfiguration.class);
|
||||||
|
|
||||||
if (smartyMeter && (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32)) {
|
if (smartyMeter && !validateSmartyMeterConfiguration(deviceConfig)) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"@text/error.configuration.invalidsmartykey");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +167,32 @@ public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventLis
|
|||||||
TimeUnit.NANOSECONDS);
|
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.
|
* 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;
|
final long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;
|
||||||
|
|
||||||
if (deltaLastReceived > receivedTimeoutNanos) {
|
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));
|
TimeUnit.NANOSECONDS.toSeconds(deltaLastReceived));
|
||||||
if (dsmrDeviceRunnable != null) {
|
|
||||||
dsmrDeviceRunnable.restart();
|
|
||||||
}
|
|
||||||
if (deltaLastReceived > receivedTimeoutNanos * OFFLINE_TIMEOUT_FACTOR) {
|
if (deltaLastReceived > receivedTimeoutNanos * OFFLINE_TIMEOUT_FACTOR) {
|
||||||
logger.trace("Setting device offline if not yet done, and reset last received time.");
|
logger.trace("Setting device offline if not yet done, and reset last received time.");
|
||||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
if (isInitialized() && getThing().getStatus() != ThingStatus.OFFLINE) {
|
||||||
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.bridge.nodata");
|
final String lkm = lastKnownReadErrorMessage;
|
||||||
|
final String message = lkm == null ? "@text/addon.dsmr.error.bridge.nodata" : lkm;
|
||||||
|
|
||||||
|
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, message);
|
||||||
}
|
}
|
||||||
resetLastReceivedState();
|
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.
|
* Sets the last received time of messages to the current time.
|
||||||
*/
|
*/
|
||||||
private void resetLastReceivedState() {
|
private void resetLastReceivedState() {
|
||||||
|
lastKnownReadErrorMessage = null;
|
||||||
telegramReceivedTimeNanos = System.nanoTime();
|
telegramReceivedTimeNanos = System.nanoTime();
|
||||||
logger.trace("Telegram received time set: {}", telegramReceivedTimeNanos);
|
logger.trace("Telegram received time set: {}", telegramReceivedTimeNanos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void handleTelegramReceived(final P1Telegram telegram) {
|
public synchronized void telegramReceived(final P1Telegram telegram) {
|
||||||
if (telegram.getCosemObjects().isEmpty()) {
|
resetLastReceivedState();
|
||||||
logger.debug("Parsing worked but something went wrong, so there were no CosemObjects:{}",
|
meterValueReceived(telegram);
|
||||||
telegram.getTelegramState().stateDetails);
|
|
||||||
deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, telegram.getTelegramState().stateDetails);
|
|
||||||
} else {
|
|
||||||
resetLastReceivedState();
|
|
||||||
meterValueReceived(telegram);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleErrorEvent(final DSMRConnectorErrorEvent portEvent) {
|
public void onError(final DSMRErrorStatus errorStatus, final String message) {
|
||||||
if (portEvent != DSMRConnectorErrorEvent.READ_ERROR) {
|
if (errorStatus == DSMRErrorStatus.TELEGRAM_NO_DATA) {
|
||||||
deviceOffline(ThingStatusDetail.CONFIGURATION_ERROR, portEvent.getEventDetails());
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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.CosemObject;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
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.ThingStatusDetail;
|
||||||
import org.openhab.core.thing.ThingStatusInfo;
|
import org.openhab.core.thing.ThingStatusInfo;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
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.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
import org.openhab.core.types.State;
|
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
|
* @param thing {@link Thing} to create the MeterHandler for
|
||||||
*/
|
*/
|
||||||
public DSMRMeterHandler(Thing thing) {
|
public DSMRMeterHandler(final Thing thing) {
|
||||||
super(thing);
|
super(thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +87,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
|
|||||||
* DSMR Meter don't support handling commands
|
* DSMR Meter don't support handling commands
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||||
if (command == RefreshType.REFRESH) {
|
if (command == RefreshType.REFRESH) {
|
||||||
updateState();
|
updateState();
|
||||||
}
|
}
|
||||||
@ -103,17 +105,17 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
meterType = DSMRMeterType.valueOf(getThing().getThingTypeUID().getId().toUpperCase());
|
meterType = DSMRMeterType.valueOf(getThing().getThingTypeUID().getId().toUpperCase());
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (final IllegalArgumentException iae) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"{} could not be initialized due to an invalid meterType {}. Delete this Thing if the problem persists.",
|
"{} could not be initialized due to an invalid meterType {}. Delete this Thing if the problem persists.",
|
||||||
getThing(), getThing().getThingTypeUID().getId().toUpperCase());
|
getThing(), getThing().getThingTypeUID().getId().toUpperCase());
|
||||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
|
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
"@text/error.configuration.invalidmetertype");
|
"@text/addon.dsmr.error.configuration.invalidmetertype");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class);
|
final DSMRMeterConfiguration meterConfig = getConfigAs(DSMRMeterConfiguration.class);
|
||||||
channel = meterType.meterKind.isChannelRelevant() ? meterConfig.channel : DSMRMeterConstants.UNKNOWN_CHANNEL;
|
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);
|
meter = new DSMRMeter(meterDescriptor);
|
||||||
meterWatchdog = scheduler.scheduleWithFixedDelay(this::updateState, meterConfig.refresh, meterConfig.refresh,
|
meterWatchdog = scheduler.scheduleWithFixedDelay(this::updateState, meterConfig.refresh, meterConfig.refresh,
|
||||||
TimeUnit.SECONDS);
|
TimeUnit.SECONDS);
|
||||||
@ -147,7 +149,7 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
|
|||||||
updateState(channel, newState);
|
updateState(channel, newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
if (ThingHandlerHelper.isHandlerInitialized(getThing()) && getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
}
|
}
|
||||||
lastReceivedValues = Collections.emptyList();
|
lastReceivedValues = Collections.emptyList();
|
||||||
@ -161,18 +163,18 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
|
|||||||
* @param telegram The received telegram
|
* @param telegram The received telegram
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void telegramReceived(P1Telegram telegram) {
|
public void telegramReceived(final P1Telegram telegram) {
|
||||||
lastReceivedValues = Collections.emptyList();
|
lastReceivedValues = Collections.emptyList();
|
||||||
final DSMRMeter localMeter = meter;
|
final DSMRMeter localMeter = meter;
|
||||||
|
|
||||||
if (localMeter == null) {
|
if (localMeter == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
|
final List<CosemObject> filteredValues = localMeter.filterMeterValues(telegram.getCosemObjects(), channel);
|
||||||
|
|
||||||
if (filteredValues.isEmpty()) {
|
if (filteredValues.isEmpty()) {
|
||||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||||
setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.thing.nodata");
|
setDeviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/addon.dsmr.error.thing.nodata");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
@ -186,7 +188,12 @@ public class DSMRMeterHandler extends BaseThingHandler implements P1TelegramList
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
|
||||||
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
|
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
|
||||||
// Set status to offline --> Thing will become online after receiving meter values
|
// 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 status off line status
|
||||||
* @param details off line detailed message
|
* @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);
|
updateStatus(ThingStatus.OFFLINE, status, details);
|
||||||
getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.NULL));
|
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.
|
* @return Returns the i18n label key for this meter.
|
||||||
*/
|
*/
|
||||||
public String getLabelKey() {
|
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 cosemObjectTypeMeterId identifier cosem object
|
||||||
* @param requiredCosemObjects list of objects that are present in this meter type
|
* @param requiredCosemObjects list of objects that are present in this meter type
|
||||||
*/
|
*/
|
||||||
DSMRMeterType(DSMRMeterKind meterKind, CosemObjectType cosemObjectTypeMeterId,
|
DSMRMeterType(final DSMRMeterKind meterKind, final CosemObjectType cosemObjectTypeMeterId,
|
||||||
CosemObjectType... requiredCosemObjects) {
|
final CosemObjectType... requiredCosemObjects) {
|
||||||
this(meterKind, cosemObjectTypeMeterId, requiredCosemObjects, new CosemObjectType[0]);
|
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 requiredCosemObjects list of objects that are present in this meter type
|
||||||
* @param optionalCosemObjects list of objects that are optional present in this meter type
|
* @param optionalCosemObjects list of objects that are optional present in this meter type
|
||||||
*/
|
*/
|
||||||
DSMRMeterType(DSMRMeterKind meterKind, CosemObjectType cosemObjectTypeMeterId,
|
DSMRMeterType(final DSMRMeterKind meterKind, final CosemObjectType cosemObjectTypeMeterId,
|
||||||
CosemObjectType[] requiredCosemObjects, CosemObjectType[] optionalCosemObjects) {
|
final CosemObjectType[] requiredCosemObjects, final CosemObjectType[] optionalCosemObjects) {
|
||||||
this.meterKind = meterKind;
|
this.meterKind = meterKind;
|
||||||
this.cosemObjectTypeMeterId = cosemObjectTypeMeterId;
|
this.cosemObjectTypeMeterId = cosemObjectTypeMeterId;
|
||||||
this.requiredCosemObjects = requiredCosemObjects;
|
this.requiredCosemObjects = requiredCosemObjects;
|
||||||
@ -427,15 +427,18 @@ public enum DSMRMeterType {
|
|||||||
* @param availableCosemObjects the Cosem Objects to detect if the current meter compatible
|
* @param availableCosemObjects the Cosem Objects to detect if the current meter compatible
|
||||||
* @return {@link DSMRMeterDescriptor} containing the identification of the compatible meter
|
* @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);
|
final Map<@Nullable Integer, AtomicInteger> channelCounter = new HashMap<>(3);
|
||||||
|
|
||||||
for (final CosemObjectType objectType : requiredCosemObjects) {
|
for (final CosemObjectType objectType : requiredCosemObjects) {
|
||||||
final AtomicBoolean match = new AtomicBoolean();
|
final AtomicBoolean match = new AtomicBoolean();
|
||||||
availableCosemObjects.stream().filter(a -> a.getType() == objectType).forEach(b -> {
|
availableCosemObjects.stream().filter(a -> a.getType() == objectType).forEach(b -> {
|
||||||
match.set(true);
|
match.set(true);
|
||||||
channelCounter.computeIfAbsent(b.getObisIdentifier().getChannel(), t -> new AtomicInteger())
|
final Integer channel = b.getObisIdentifier().getChannel();
|
||||||
.incrementAndGet();
|
|
||||||
|
if (channel != null) {
|
||||||
|
channelCounter.computeIfAbsent(channel, t -> new AtomicInteger()).incrementAndGet();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (!match.get()) {
|
if (!match.get()) {
|
||||||
logger.trace("Required objectType {} not found for meter: {}", objectType, this);
|
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
|
# meter kind names
|
||||||
|
|
||||||
meterKind.invalid.label = Invalid Meter
|
addon.dsmr.meterKind.invalid.label = Invalid Meter
|
||||||
meterKind.device.label = Generic DSMR Device
|
addon.dsmr.meterKind.device.label = Generic DSMR Device
|
||||||
meterKind.main_electricity.label = Main Electricity Meter
|
addon.dsmr.meterKind.main_electricity.label = Main Electricity Meter
|
||||||
meterKind.gas.label = Gas Meter
|
addon.dsmr.meterKind.gas.label = Gas Meter
|
||||||
meterKind.heating.label = Heating Meter
|
addon.dsmr.meterKind.heating.label = Heating Meter
|
||||||
meterKind.cooling.label = Cooling Meter
|
addon.dsmr.meterKind.cooling.label = Cooling Meter
|
||||||
meterKind.water.label = Water Meter
|
addon.dsmr.meterKind.water.label = Water Meter
|
||||||
meterKind.generic.label = Generic Meter
|
addon.dsmr.meterKind.generic.label = Generic Meter
|
||||||
meterKind.gj.label = GJ Meter
|
addon.dsmr.meterKind.gj.label = GJ Meter
|
||||||
meterKind.m3.label = M3 Meter
|
addon.dsmr.meterKind.m3.label = M3 Meter
|
||||||
meterKind.slave_electricity1.label = Slave Electricity Meter
|
addon.dsmr.meterKind.slave_electricity1.label = Slave Electricity Meter
|
||||||
meterKind.slave_electricity2.label = Slave Electricity Meter 2
|
addon.dsmr.meterKind.slave_electricity2.label = Slave Electricity Meter 2
|
||||||
|
|
||||||
# connector error messages
|
# connector error messages
|
||||||
|
|
||||||
error.bridge.nodata = Not receiving data from meter.
|
addon.dsmr.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.
|
addon.dsmr.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.
|
addon.dsmr.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.
|
addon.dsmr.error.configuration.invalid.decryptionKey = The given Smarty decyption key is invalid. It contains an illegal character: {0}
|
||||||
error.connector.dont_exists = Serial port does not exist.
|
addon.dsmr.error.configuration.invalid.additionalKey = The given Smarty additional key is invalid. It contains an illegal character: {0}
|
||||||
error.connector.in_use = Serial port is already in use.
|
addon.dsmr.error.thing.nodata = Not receiving data from meter.
|
||||||
error.connector.internal_error = Unexpected error, possible bug. Please report.
|
|
||||||
error.connector.not_compatible = Serial port is not compatible.
|
addon.dsmr.error.status.invalid_decryption_key = Failed to decrypt P1 telegram due to invalid encryption key
|
||||||
error.connector.read_error = Read error.
|
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;
|
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.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
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;
|
||||||
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.P1TelegramParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +45,7 @@ public final class TelegramReaderUtil {
|
|||||||
* @param telegramName name of the telegram file to read
|
* @param telegramName name of the telegram file to read
|
||||||
* @return The raw bytes of a telegram
|
* @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)) {
|
try (InputStream is = TelegramReaderUtil.class.getResourceAsStream(telegramName + TELEGRAM_EXT)) {
|
||||||
if (is == null) {
|
if (is == null) {
|
||||||
fail("Could not find telegram file with name:" + telegramName + TELEGRAM_EXT);
|
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
|
* @param expectedTelegramState expected state of the telegram read
|
||||||
* @return a P1Telegram object
|
* @return a P1Telegram object
|
||||||
*/
|
*/
|
||||||
public static P1Telegram readTelegram(String telegramName, TelegramState expectedTelegramState) {
|
public static P1Telegram readTelegram(final String telegramName) {
|
||||||
final AtomicReference<P1Telegram> p1Telegram = new AtomicReference<>();
|
|
||||||
final byte[] telegram = readRawTelegram(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.setLenientMode(true);
|
||||||
parser.parse(telegram, telegram.length);
|
parser.parse(telegram, telegram.length);
|
||||||
assertNotNull(p1Telegram.get(), "Telegram state should have been set. (Missing newline at end of message?)");
|
final P1Telegram p1Telegram = listener.telegram;
|
||||||
assertEquals(expectedTelegramState, p1Telegram.get().getTelegramState(),
|
|
||||||
"Expected TelegramState should be as expected");
|
assertNotNull(p1Telegram, "Telegram state should have been set. (Missing newline at end of message?)");
|
||||||
return p1Telegram.get();
|
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;
|
package org.openhab.binding.dsmr.internal.device;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.ArgumentMatchers.*;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.mockito.Mockito.*;
|
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.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
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.DSMRBindingConstants;
|
||||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||||
import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice.DeviceState;
|
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.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.PortInUseException;
|
||||||
import org.openhab.core.io.transport.serial.SerialPort;
|
import org.openhab.core.io.transport.serial.SerialPort;
|
||||||
import org.openhab.core.io.transport.serial.SerialPortEvent;
|
import org.openhab.core.io.transport.serial.SerialPortEvent;
|
||||||
@ -61,7 +71,7 @@ public class DSMRSerialAutoDeviceTest {
|
|||||||
|
|
||||||
private final SerialPortManager serialPortManager = new SerialPortManager() {
|
private final SerialPortManager serialPortManager = new SerialPortManager() {
|
||||||
@Override
|
@Override
|
||||||
public @Nullable SerialPortIdentifier getIdentifier(String name) {
|
public @Nullable SerialPortIdentifier getIdentifier(final String name) {
|
||||||
assertEquals(DUMMY_PORTNAME, name, "Expect the passed serial port name");
|
assertEquals(DUMMY_PORTNAME, name, "Expect the passed serial port name");
|
||||||
return mockIdentifier;
|
return mockIdentifier;
|
||||||
}
|
}
|
||||||
@ -84,21 +94,21 @@ public class DSMRSerialAutoDeviceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testHandlingDataAndRestart() throws IOException, PortInUseException {
|
public void testHandlingDataAndRestart() throws IOException, PortInUseException {
|
||||||
mockValidSerialPort();
|
mockValidSerialPort();
|
||||||
AtomicReference<@Nullable P1Telegram> telegramRef = new AtomicReference<>(null);
|
final AtomicReference<@Nullable P1Telegram> telegramRef = new AtomicReference<>(null);
|
||||||
DSMREventListener listener = new DSMREventListener() {
|
final P1TelegramListener listener = new P1TelegramListener() {
|
||||||
@Override
|
@Override
|
||||||
public void handleTelegramReceived(P1Telegram telegram) {
|
public void telegramReceived(final P1Telegram telegram) {
|
||||||
telegramRef.set(telegram);
|
telegramRef.set(telegram);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent) {
|
public void onError(final DSMRErrorStatus errorStatus, final String message) {
|
||||||
fail("No handleErrorEvent Expected" + connectorErrorEvent);
|
fail("No error status expected" + errorStatus);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try (InputStream inputStream = new ByteArrayInputStream(TelegramReaderUtil.readRawTelegram(TELEGRAM_NAME))) {
|
try (InputStream inputStream = new ByteArrayInputStream(TelegramReaderUtil.readRawTelegram(TELEGRAM_NAME))) {
|
||||||
when(mockSerialPort.getInputStream()).thenReturn(inputStream);
|
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);
|
new DSMRTelegramListener(), scheduler, 1);
|
||||||
device.start();
|
device.start();
|
||||||
assertSame(DeviceState.DISCOVER_SETTINGS, device.getState(), "Expect to be starting discovery state");
|
assertSame(DeviceState.DISCOVER_SETTINGS, device.getState(), "Expect to be starting discovery state");
|
||||||
@ -117,16 +127,16 @@ public class DSMRSerialAutoDeviceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHandleError() throws IOException, PortInUseException {
|
public void testHandleError() throws IOException, PortInUseException {
|
||||||
AtomicReference<@Nullable DSMRConnectorErrorEvent> eventRef = new AtomicReference<>(null);
|
final AtomicReference<@Nullable DSMRErrorStatus> eventRef = new AtomicReference<>(null);
|
||||||
DSMREventListener listener = new DSMREventListener() {
|
final P1TelegramListener listener = new P1TelegramListener() {
|
||||||
@Override
|
@Override
|
||||||
public void handleTelegramReceived(P1Telegram telegram) {
|
public void telegramReceived(final P1Telegram telegram) {
|
||||||
fail("No telegram expected:" + telegram);
|
fail("No telegram expected:" + telegram);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent) {
|
public void onError(final DSMRErrorStatus errorStatus, final String message) {
|
||||||
eventRef.set(connectorErrorEvent);
|
eventRef.set(errorStatus);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) {
|
try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) {
|
||||||
@ -137,7 +147,7 @@ public class DSMRSerialAutoDeviceTest {
|
|||||||
DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
|
DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
|
||||||
new DSMRTelegramListener(), scheduler, 1);
|
new DSMRTelegramListener(), scheduler, 1);
|
||||||
device.start();
|
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");
|
assertSame(DeviceState.ERROR, device.getState(), "Expect to be in error state");
|
||||||
// Trigger device to restart
|
// Trigger device to restart
|
||||||
mockValidSerialPort();
|
mockValidSerialPort();
|
||||||
@ -148,7 +158,7 @@ public class DSMRSerialAutoDeviceTest {
|
|||||||
device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener, new DSMRTelegramListener(),
|
device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener, new DSMRTelegramListener(),
|
||||||
scheduler, 1);
|
scheduler, 1);
|
||||||
device.start();
|
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");
|
assertSame(DeviceState.ERROR, device.getState(), "Expect to be in error state");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +174,8 @@ public class DSMRSerialAutoDeviceTest {
|
|||||||
private final int eventType;
|
private final int eventType;
|
||||||
private final boolean newValue;
|
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.eventType = eventType;
|
||||||
this.newValue = newValue;
|
this.newValue = newValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
|
import org.openhab.binding.dsmr.internal.device.p1telegram.TelegramParser;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class for the {@link SmartyDecrypter}.
|
* Test class for the {@link SmartyDecrypter}.
|
||||||
@ -96,10 +95,13 @@ public class SmartyDecrypterTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testSmartyDecrypter() {
|
public void testSmartyDecrypter() {
|
||||||
final AtomicReference<String> telegramResult = new AtomicReference<>("");
|
final AtomicReference<String> dataRead = new AtomicReference<>();
|
||||||
final P1TelegramListener telegramListener = telegram -> telegramResult.set(telegram.getRawTelegram());
|
final SmartyDecrypter decoder = new SmartyDecrypter(new TelegramParser() {
|
||||||
final SmartyDecrypter decoder = new SmartyDecrypter(new P1TelegramParser(telegramListener),
|
@Override
|
||||||
new DSMRTelegramListener(KEY, ""), KEY, "");
|
public void parse(final byte[] data, final int length) {
|
||||||
|
dataRead.set(new String(data, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}, new DSMRTelegramListener(KEY, ""), KEY, "");
|
||||||
decoder.setLenientMode(true);
|
decoder.setLenientMode(true);
|
||||||
final byte[] data = new byte[TELEGRAM.length];
|
final byte[] data = new byte[TELEGRAM.length];
|
||||||
|
|
||||||
@ -110,6 +112,6 @@ public class SmartyDecrypterTest {
|
|||||||
decoder.parse(data, data.length);
|
decoder.parse(data, data.length);
|
||||||
final String expected = new String(TelegramReaderUtil.readRawTelegram("smarty_long"), StandardCharsets.UTF_8);
|
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.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class for {@link P1TelegramParser}.
|
* Test class for {@link P1TelegramParser}.
|
||||||
@ -55,7 +54,7 @@ public class P1TelegramParserTest {
|
|||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
public void testParsing(final String telegramName, final int numberOfCosemObjects, final int unknownObjects) {
|
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(),
|
assertEquals(unknownObjects, telegram.getUnknownCosemObjects().size(),
|
||||||
"Should not have other than " + unknownObjects + " unknown cosem objects");
|
"Should not have other than " + unknownObjects + " unknown cosem objects");
|
||||||
assertEquals(numberOfCosemObjects,
|
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.TelegramReaderUtil;
|
||||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
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;
|
||||||
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.DSMRMeterDescriptor;
|
||||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
import org.openhab.binding.dsmr.internal.meter.DSMRMeterType;
|
||||||
|
|
||||||
@ -77,7 +76,7 @@ public class DSMRMeterDetectorTest {
|
|||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
public void testDetectMeters(final String telegramName, final Set<DSMRMeterType> expectedMeters) {
|
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 DSMRMeterDetector detector = new DSMRMeterDetector();
|
||||||
final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> entry = detector.detectMeters(telegram);
|
final Entry<Collection<DSMRMeterDescriptor>, List<CosemObject>> entry = detector.detectMeters(telegram);
|
||||||
final Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
|
final Collection<DSMRMeterDescriptor> detectMeters = entry.getKey();
|
||||||
|
|||||||
@ -12,9 +12,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.dsmr.internal.discovery;
|
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.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.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
@ -32,7 +35,6 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
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;
|
||||||
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.DSMRBridgeHandler;
|
||||||
import org.openhab.binding.dsmr.internal.handler.DSMRMeterHandler;
|
import org.openhab.binding.dsmr.internal.handler.DSMRMeterHandler;
|
||||||
import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
|
import org.openhab.binding.dsmr.internal.meter.DSMRMeterDescriptor;
|
||||||
@ -63,13 +65,13 @@ public class DSMRMeterDiscoveryServiceTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidConfiguredMeters() {
|
public void testInvalidConfiguredMeters() {
|
||||||
P1Telegram expected = TelegramReaderUtil.readTelegram(EXPECTED_CONFIGURED_TELEGRAM, TelegramState.OK);
|
final P1Telegram expected = TelegramReaderUtil.readTelegram(EXPECTED_CONFIGURED_TELEGRAM);
|
||||||
AtomicReference<List<DSMRMeterType>> invalidConfiguredRef = new AtomicReference<>();
|
final AtomicReference<List<DSMRMeterType>> invalidConfiguredRef = new AtomicReference<>();
|
||||||
AtomicReference<List<DSMRMeterType>> unconfiguredRef = new AtomicReference<>();
|
final AtomicReference<List<DSMRMeterType>> unconfiguredRef = new AtomicReference<>();
|
||||||
DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
|
final DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
|
||||||
@Override
|
@Override
|
||||||
protected void reportConfigurationValidationResults(List<DSMRMeterType> invalidConfigured,
|
protected void reportConfigurationValidationResults(final List<DSMRMeterType> invalidConfigured,
|
||||||
List<DSMRMeterType> unconfiguredMeters) {
|
final List<DSMRMeterType> unconfiguredMeters) {
|
||||||
super.reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
|
super.reportConfigurationValidationResults(invalidConfigured, unconfiguredMeters);
|
||||||
invalidConfiguredRef.set(invalidConfigured);
|
invalidConfiguredRef.set(invalidConfigured);
|
||||||
unconfiguredRef.set(unconfiguredMeters);
|
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
|
// Mock the invalid configuration by reading a telegram that is valid for a meter that is a subset of the
|
||||||
// expected meter.
|
// expected meter.
|
||||||
List<DSMRMeterDescriptor> invalidConfiguredMeterDescriptors = EnumSet.of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0)
|
final List<DSMRMeterDescriptor> invalidConfiguredMeterDescriptors = EnumSet
|
||||||
.stream().map(mt -> new DSMRMeterDescriptor(mt, 0)).collect(Collectors.toList());
|
.of(DEVICE_V5, ELECTRICITY_V4_2, M3_V5_0).stream().map(mt -> new DSMRMeterDescriptor(mt, 0))
|
||||||
List<Thing> things = invalidConfiguredMeterDescriptors.stream().map(m -> thing).collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
AtomicReference<Iterator<DSMRMeterDescriptor>> detectMetersRef = new AtomicReference<>();
|
final List<Thing> things = invalidConfiguredMeterDescriptors.stream().map(m -> thing)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
final AtomicReference<Iterator<DSMRMeterDescriptor>> detectMetersRef = new AtomicReference<>();
|
||||||
|
|
||||||
when((meterHandler).getMeterDescriptor()).then(a -> {
|
when((meterHandler).getMeterDescriptor()).then(a -> {
|
||||||
if (detectMetersRef.get() == null || !detectMetersRef.get().hasNext()) {
|
if (detectMetersRef.get() == null || !detectMetersRef.get().hasNext()) {
|
||||||
detectMetersRef.set(invalidConfiguredMeterDescriptors.iterator());
|
detectMetersRef.set(invalidConfiguredMeterDescriptors.iterator());
|
||||||
@ -109,9 +114,9 @@ public class DSMRMeterDiscoveryServiceTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUnregisteredMeters() {
|
public void testUnregisteredMeters() {
|
||||||
P1Telegram telegram = TelegramReaderUtil.readTelegram(UNREGISTERED_METER_TELEGRAM, TelegramState.OK);
|
final P1Telegram telegram = TelegramReaderUtil.readTelegram(UNREGISTERED_METER_TELEGRAM);
|
||||||
AtomicBoolean unregisteredMeter = new AtomicBoolean(false);
|
final AtomicBoolean unregisteredMeter = new AtomicBoolean(false);
|
||||||
DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
|
final DSMRMeterDiscoveryService service = new DSMRMeterDiscoveryService() {
|
||||||
@Override
|
@Override
|
||||||
protected void reportUnregisteredMeters() {
|
protected void reportUnregisteredMeters() {
|
||||||
super.reportUnregisteredMeters();
|
super.reportUnregisteredMeters();
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
|
||||||
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
import org.openhab.binding.dsmr.internal.device.cosem.CosemObject;
|
||||||
import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram.TelegramState;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class for {@link DSMRMeter}.
|
* Test class for {@link DSMRMeter}.
|
||||||
@ -35,15 +34,15 @@ public class DSMRMeterTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testFilterMeterValues() {
|
public void testFilterMeterValues() {
|
||||||
final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50", TelegramState.OK)
|
final List<CosemObject> cosemObjects = TelegramReaderUtil.readTelegram("dsmr_50").getCosemObjects();
|
||||||
.getCosemObjects();
|
|
||||||
|
|
||||||
assertMeterValues(cosemObjects, DSMRMeterType.DEVICE_V5, DSMRMeterConstants.UNKNOWN_CHANNEL, 3);
|
assertMeterValues(cosemObjects, DSMRMeterType.DEVICE_V5, DSMRMeterConstants.UNKNOWN_CHANNEL, 3);
|
||||||
assertMeterValues(cosemObjects, DSMRMeterType.ELECTRICITY_V5_0, 0, 29);
|
assertMeterValues(cosemObjects, DSMRMeterType.ELECTRICITY_V5_0, 0, 29);
|
||||||
assertMeterValues(cosemObjects, DSMRMeterType.M3_V5_0, 1, 3);
|
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 DSMRMeterDescriptor descriptor = new DSMRMeterDescriptor(type, channel);
|
||||||
final DSMRMeter meter = new DSMRMeter(descriptor);
|
final DSMRMeter meter = new DSMRMeter(descriptor);
|
||||||
final List<CosemObject> filterMeterValues = meter.filterMeterValues(cosemObjects, channel);
|
final List<CosemObject> filterMeterValues = meter.filterMeterValues(cosemObjects, channel);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user