2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.dsmr.internal.handler;
15 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_SMARTY_BRIDGE;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.dsmr.internal.device.DSMRDevice;
24 import org.openhab.binding.dsmr.internal.device.DSMRDeviceConfiguration;
25 import org.openhab.binding.dsmr.internal.device.DSMRDeviceRunnable;
26 import org.openhab.binding.dsmr.internal.device.DSMREventListener;
27 import org.openhab.binding.dsmr.internal.device.DSMRFixedConfigDevice;
28 import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice;
29 import org.openhab.binding.dsmr.internal.device.DSMRTelegramListener;
30 import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
31 import org.openhab.binding.dsmr.internal.device.connector.DSMRSerialSettings;
32 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
33 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
34 import org.openhab.core.io.transport.serial.SerialPortManager;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.binding.BaseBridgeHandler;
40 import org.openhab.core.types.Command;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link DSMRBridgeHandler} is responsible for handling commands, which are
46 * sent to one of the channels.
48 * @author M. Volaart - Initial contribution
49 * @author Hilbrand Bouwkamp - Refactored way messages are forwarded to meters. Removed availableMeters dependency.
52 public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventListener {
55 * Factor that will be multiplied with {@link #receivedTimeoutNanos} to get the timeout factor after which the
56 * device is set off line.
58 private static final int OFFLINE_TIMEOUT_FACTOR = 10;
60 private final Logger logger = LoggerFactory.getLogger(DSMRBridgeHandler.class);
63 * Additional meter listeners to get received meter values.
65 private final List<P1TelegramListener> meterListeners = new ArrayList<>();
68 * Serial Port Manager.
70 private final SerialPortManager serialPortManager;
73 * The dsmrDevice managing the connection and handling telegrams.
75 private @NonNullByDefault({}) DSMRDevice dsmrDevice;
78 * Long running process that controls the DSMR device connection.
80 private @NonNullByDefault({}) DSMRDeviceRunnable dsmrDeviceRunnable;
83 * Thread for {@link DSMRDeviceRunnable}. A thread is used because the {@link DSMRDeviceRunnable} is a blocking
84 * process that runs as long as the thing is not disposed.
86 private @NonNullByDefault({}) Thread dsmrDeviceThread;
89 * Watchdog to check if messages received and restart if necessary.
91 private @NonNullByDefault({}) ScheduledFuture<?> watchdog;
94 * Number of nanoseconds after which a timeout is triggered when no messages received.
96 private long receivedTimeoutNanos;
99 * Timestamp in nanoseconds of last P1 telegram received
101 private volatile long telegramReceivedTimeNanos;
103 private final boolean smartyMeter;
108 * @param bridge the Bridge ThingType
109 * @param serialPortManager The Serial port manager
111 public DSMRBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
113 this.serialPortManager = serialPortManager;
114 smartyMeter = THING_TYPE_SMARTY_BRIDGE.equals(bridge.getThingTypeUID());
118 * The {@link DSMRBridgeHandler} does not support handling commands.
120 * @param channelUID the {@link ChannelUID} of the channel to which the command was sent
121 * @param command the {@link Command}
124 public void handleCommand(ChannelUID channelUID, Command command) {
125 // DSMRBridgeHandler does not support commands
129 * Initializes this {@link DSMRBridgeHandler}.
131 * This method will get the corresponding configuration and initialize and start the corresponding
132 * {@link DSMRDevice}.
135 public void initialize() {
136 final DSMRDeviceConfiguration deviceConfig = getConfigAs(DSMRDeviceConfiguration.class);
138 if (smartyMeter && (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32)) {
139 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
140 "@text/error.configuration.invalidsmartykey");
144 logger.trace("Using configuration {}", deviceConfig);
145 updateStatus(ThingStatus.UNKNOWN);
146 receivedTimeoutNanos = TimeUnit.SECONDS.toNanos(deviceConfig.receivedTimeout);
147 final DSMRDevice dsmrDevice = createDevice(deviceConfig);
148 resetLastReceivedState();
149 this.dsmrDevice = dsmrDevice; // otherwise Eclipse will give a null pointer error on the next line :-(
150 dsmrDeviceRunnable = new DSMRDeviceRunnable(dsmrDevice, this);
151 dsmrDeviceThread = new Thread(dsmrDeviceRunnable);
152 dsmrDeviceThread.setName("OH-binding-" + getThing().getUID());
153 dsmrDeviceThread.setDaemon(true);
154 dsmrDeviceThread.start();
155 watchdog = scheduler.scheduleWithFixedDelay(this::alive, receivedTimeoutNanos, receivedTimeoutNanos,
156 TimeUnit.NANOSECONDS);
160 * Creates the {@link DSMRDevice} that corresponds with the user specified configuration.
162 * @param deviceConfig device configuration
163 * @return Specific {@link DSMRDevice} instance
165 private DSMRDevice createDevice(DSMRDeviceConfiguration deviceConfig) {
166 final DSMRDevice dsmrDevice;
169 dsmrDevice = new DSMRFixedConfigDevice(serialPortManager, deviceConfig.serialPort,
170 DSMRSerialSettings.HIGH_SPEED_SETTINGS, this, new DSMRTelegramListener(deviceConfig.decryptionKey));
172 final DSMRTelegramListener telegramListener = new DSMRTelegramListener();
174 if (deviceConfig.isSerialFixedSettings()) {
175 dsmrDevice = new DSMRFixedConfigDevice(serialPortManager, deviceConfig.serialPort,
176 DSMRSerialSettings.getPortSettingsFromConfiguration(deviceConfig), this, telegramListener);
178 dsmrDevice = new DSMRSerialAutoDevice(serialPortManager, deviceConfig.serialPort, this,
179 telegramListener, scheduler, deviceConfig.receivedTimeout);
186 * Registers a meter listener.
188 * @param meterListener the meter discovery listener to add
189 * @return true if listener is added, false otherwise
191 public boolean registerDSMRMeterListener(P1TelegramListener meterListener) {
192 logger.trace("Register DSMRMeterListener");
193 return meterListeners.add(meterListener);
197 * Unregisters a meter listener
199 * @param meterListener the meter discovery listener to remove
200 * @return true is listener is removed, false otherwise
202 public boolean unregisterDSMRMeterListener(P1TelegramListener meterListener) {
203 logger.trace("Unregister DSMRMeterListener");
204 return meterListeners.remove(meterListener);
208 * Watchdog method that is run with the scheduler and checks if meter values were received. If the timeout is
209 * exceeded the device is restarted. If the off line timeout factor is exceeded the device is set off line. By not
210 * setting the device on first exceed off line their is some slack in the system and it won't flip on and offline in
211 * case of an unstable system.
213 private void alive() {
214 logger.trace("Bridge alive check with #{} children.", getThing().getThings().size());
215 long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;
217 if (deltaLastReceived > receivedTimeoutNanos) {
218 logger.debug("No data received for {} seconds, restarting port if possible.",
219 TimeUnit.NANOSECONDS.toSeconds(deltaLastReceived));
220 if (dsmrDeviceRunnable != null) {
221 dsmrDeviceRunnable.restart();
223 if (deltaLastReceived > receivedTimeoutNanos * OFFLINE_TIMEOUT_FACTOR) {
224 logger.trace("Setting device offline if not yet done, and reset last received time.");
225 if (getThing().getStatus() == ThingStatus.ONLINE) {
226 deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.bridge.nodata");
228 resetLastReceivedState();
234 * Sets the last received time of messages to the current time.
236 private void resetLastReceivedState() {
237 telegramReceivedTimeNanos = System.nanoTime();
238 logger.trace("Telegram received time set: {}", telegramReceivedTimeNanos);
242 public synchronized void handleTelegramReceived(P1Telegram telegram) {
243 if (telegram.getCosemObjects().isEmpty()) {
244 logger.debug("Parsing worked but something went wrong, so there were no CosemObjects:{}",
245 telegram.getTelegramState().stateDetails);
246 deviceOffline(ThingStatusDetail.COMMUNICATION_ERROR, telegram.getTelegramState().stateDetails);
248 resetLastReceivedState();
249 meterValueReceived(telegram);
254 public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
255 if (portEvent != DSMRConnectorErrorEvent.READ_ERROR) {
256 deviceOffline(ThingStatusDetail.CONFIGURATION_ERROR, portEvent.getEventDetails());
261 * Method to forward the last received messages to the bound meters and to the meterListeners.
263 * @param telegram received meter values.
265 private void meterValueReceived(P1Telegram telegram) {
266 updateStatus(ThingStatus.ONLINE);
267 getThing().getThings().forEach(child -> {
268 if (logger.isTraceEnabled()) {
269 logger.trace("Update child:{} with {} objects", child.getThingTypeUID().getId(),
270 telegram.getCosemObjects().size());
272 DSMRMeterHandler dsmrMeterHandler = (DSMRMeterHandler) child.getHandler();
274 if (dsmrMeterHandler instanceof DSMRMeterHandler) {
275 dsmrMeterHandler.telegramReceived(telegram);
278 meterListeners.forEach(m -> m.telegramReceived(telegram));
282 public void dispose() {
283 if (watchdog != null) {
284 watchdog.cancel(true);
287 if (dsmrDeviceRunnable != null) {
288 dsmrDeviceRunnable.stop();
293 * @param lenientMode the lenientMode to set
295 public void setLenientMode(boolean lenientMode) {
296 logger.trace("SetLenientMode: {}", lenientMode);
297 if (dsmrDevice != null) {
298 dsmrDevice.setLenientMode(lenientMode);
303 * Convenience method to set device off line.
305 * @param status off line status
306 * @param details off line detailed message
308 private void deviceOffline(ThingStatusDetail status, String details) {
309 updateStatus(ThingStatus.OFFLINE, status, details);