]> git.basschouten.com Git - openhab-addons.git/blob
cd969781045deb15f0855cd55269cb0a4ade1f54
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.dsmr.internal.handler;
14
15 import static org.openhab.binding.dsmr.internal.DSMRBindingConstants.THING_TYPE_SMARTY_BRIDGE;
16
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
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;
43
44 /**
45  * The {@link DSMRBridgeHandler} is responsible for handling commands, which are
46  * sent to one of the channels.
47  *
48  * @author M. Volaart - Initial contribution
49  * @author Hilbrand Bouwkamp - Refactored way messages are forwarded to meters. Removed availableMeters dependency.
50  */
51 @NonNullByDefault
52 public class DSMRBridgeHandler extends BaseBridgeHandler implements DSMREventListener {
53
54     /**
55      * Factor that will be multiplied with {@link #receivedTimeoutNanos} to get the timeout factor after which the
56      * device is set off line.
57      */
58     private static final int OFFLINE_TIMEOUT_FACTOR = 10;
59
60     private final Logger logger = LoggerFactory.getLogger(DSMRBridgeHandler.class);
61
62     /**
63      * Additional meter listeners to get received meter values.
64      */
65     private final List<P1TelegramListener> meterListeners = new ArrayList<>();
66
67     /**
68      * Serial Port Manager.
69      */
70     private final SerialPortManager serialPortManager;
71
72     /**
73      * The dsmrDevice managing the connection and handling telegrams.
74      */
75     private @NonNullByDefault({}) DSMRDevice dsmrDevice;
76
77     /**
78      * Long running process that controls the DSMR device connection.
79      */
80     private @NonNullByDefault({}) DSMRDeviceRunnable dsmrDeviceRunnable;
81
82     /**
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.
85      */
86     private @NonNullByDefault({}) Thread dsmrDeviceThread;
87
88     /**
89      * Watchdog to check if messages received and restart if necessary.
90      */
91     private @NonNullByDefault({}) ScheduledFuture<?> watchdog;
92
93     /**
94      * Number of nanoseconds after which a timeout is triggered when no messages received.
95      */
96     private long receivedTimeoutNanos;
97
98     /**
99      * Timestamp in nanoseconds of last P1 telegram received
100      */
101     private volatile long telegramReceivedTimeNanos;
102
103     private final boolean smartyMeter;
104
105     /**
106      * Constructor
107      *
108      * @param bridge the Bridge ThingType
109      * @param serialPortManager The Serial port manager
110      */
111     public DSMRBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
112         super(bridge);
113         this.serialPortManager = serialPortManager;
114         smartyMeter = THING_TYPE_SMARTY_BRIDGE.equals(bridge.getThingTypeUID());
115     }
116
117     /**
118      * The {@link DSMRBridgeHandler} does not support handling commands.
119      *
120      * @param channelUID the {@link ChannelUID} of the channel to which the command was sent
121      * @param command the {@link Command}
122      */
123     @Override
124     public void handleCommand(ChannelUID channelUID, Command command) {
125         // DSMRBridgeHandler does not support commands
126     }
127
128     /**
129      * Initializes this {@link DSMRBridgeHandler}.
130      *
131      * This method will get the corresponding configuration and initialize and start the corresponding
132      * {@link DSMRDevice}.
133      */
134     @Override
135     public void initialize() {
136         final DSMRDeviceConfiguration deviceConfig = getConfigAs(DSMRDeviceConfiguration.class);
137
138         if (smartyMeter && (deviceConfig.decryptionKey == null || deviceConfig.decryptionKey.length() != 32)) {
139             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
140                     "@text/error.configuration.invalidsmartykey");
141             return;
142         }
143
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);
157     }
158
159     /**
160      * Creates the {@link DSMRDevice} that corresponds with the user specified configuration.
161      *
162      * @param deviceConfig device configuration
163      * @return Specific {@link DSMRDevice} instance
164      */
165     private DSMRDevice createDevice(DSMRDeviceConfiguration deviceConfig) {
166         final DSMRDevice dsmrDevice;
167
168         if (smartyMeter) {
169             dsmrDevice = new DSMRFixedConfigDevice(serialPortManager, deviceConfig.serialPort,
170                     DSMRSerialSettings.HIGH_SPEED_SETTINGS, this, new DSMRTelegramListener(deviceConfig.decryptionKey));
171         } else {
172             final DSMRTelegramListener telegramListener = new DSMRTelegramListener();
173
174             if (deviceConfig.isSerialFixedSettings()) {
175                 dsmrDevice = new DSMRFixedConfigDevice(serialPortManager, deviceConfig.serialPort,
176                         DSMRSerialSettings.getPortSettingsFromConfiguration(deviceConfig), this, telegramListener);
177             } else {
178                 dsmrDevice = new DSMRSerialAutoDevice(serialPortManager, deviceConfig.serialPort, this,
179                         telegramListener, scheduler, deviceConfig.receivedTimeout);
180             }
181         }
182         return dsmrDevice;
183     }
184
185     /**
186      * Registers a meter listener.
187      *
188      * @param meterListener the meter discovery listener to add
189      * @return true if listener is added, false otherwise
190      */
191     public boolean registerDSMRMeterListener(P1TelegramListener meterListener) {
192         logger.trace("Register DSMRMeterListener");
193         return meterListeners.add(meterListener);
194     }
195
196     /**
197      * Unregisters a meter listener
198      *
199      * @param meterListener the meter discovery listener to remove
200      * @return true is listener is removed, false otherwise
201      */
202     public boolean unregisterDSMRMeterListener(P1TelegramListener meterListener) {
203         logger.trace("Unregister DSMRMeterListener");
204         return meterListeners.remove(meterListener);
205     }
206
207     /**
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.
212      */
213     private void alive() {
214         logger.trace("Bridge alive check with #{} children.", getThing().getThings().size());
215         long deltaLastReceived = System.nanoTime() - telegramReceivedTimeNanos;
216
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();
222             }
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");
227                 }
228                 resetLastReceivedState();
229             }
230         }
231     }
232
233     /**
234      * Sets the last received time of messages to the current time.
235      */
236     private void resetLastReceivedState() {
237         telegramReceivedTimeNanos = System.nanoTime();
238         logger.trace("Telegram received time set: {}", telegramReceivedTimeNanos);
239     }
240
241     @Override
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);
247         } else {
248             resetLastReceivedState();
249             meterValueReceived(telegram);
250         }
251     }
252
253     @Override
254     public void handleErrorEvent(DSMRConnectorErrorEvent portEvent) {
255         if (portEvent != DSMRConnectorErrorEvent.READ_ERROR) {
256             deviceOffline(ThingStatusDetail.CONFIGURATION_ERROR, portEvent.getEventDetails());
257         }
258     }
259
260     /**
261      * Method to forward the last received messages to the bound meters and to the meterListeners.
262      *
263      * @param telegram received meter values.
264      */
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());
271             }
272             DSMRMeterHandler dsmrMeterHandler = (DSMRMeterHandler) child.getHandler();
273
274             if (dsmrMeterHandler instanceof DSMRMeterHandler) {
275                 dsmrMeterHandler.telegramReceived(telegram);
276             }
277         });
278         meterListeners.forEach(m -> m.telegramReceived(telegram));
279     }
280
281     @Override
282     public void dispose() {
283         if (watchdog != null) {
284             watchdog.cancel(true);
285             watchdog = null;
286         }
287         if (dsmrDeviceRunnable != null) {
288             dsmrDeviceRunnable.stop();
289         }
290     }
291
292     /**
293      * @param lenientMode the lenientMode to set
294      */
295     public void setLenientMode(boolean lenientMode) {
296         logger.trace("SetLenientMode: {}", lenientMode);
297         if (dsmrDevice != null) {
298             dsmrDevice.setLenientMode(lenientMode);
299         }
300     }
301
302     /**
303      * Convenience method to set device off line.
304      *
305      * @param status off line status
306      * @param details off line detailed message
307      */
308     private void deviceOffline(ThingStatusDetail status, String details) {
309         updateStatus(ThingStatus.OFFLINE, status, details);
310     }
311 }