]> git.basschouten.com Git - openhab-addons.git/blob
318f58f14945ae400498496f6508bb3e31884c71
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.knx.internal.handler;
14
15 import java.util.Map;
16 import java.util.Random;
17 import java.util.concurrent.Future;
18 import java.util.concurrent.ScheduledExecutorService;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.knx.internal.client.DeviceInspector;
25 import org.openhab.binding.knx.internal.client.DeviceInspector.Result;
26 import org.openhab.binding.knx.internal.client.KNXClient;
27 import org.openhab.binding.knx.internal.config.DeviceConfig;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.thing.ThingStatusInfo;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import tuwien.auto.calimero.IndividualAddress;
38 import tuwien.auto.calimero.KNXException;
39 import tuwien.auto.calimero.KNXFormatException;
40
41 /**
42  * Base class for KNX thing handlers.
43  *
44  * @author Simon Kaufmann - initial contribution and API
45  *
46  */
47 @NonNullByDefault
48 public abstract class AbstractKNXThingHandler extends BaseThingHandler implements GroupAddressListener {
49
50     private static final int INITIAL_PING_DELAY = 5;
51     private final Logger logger = LoggerFactory.getLogger(AbstractKNXThingHandler.class);
52
53     protected @Nullable IndividualAddress address;
54     private @Nullable Future<?> descriptionJob;
55     private boolean filledDescription = false;
56     private final Random random = new Random();
57
58     private @Nullable ScheduledFuture<?> pollingJob;
59
60     public AbstractKNXThingHandler(Thing thing) {
61         super(thing);
62     }
63
64     protected final ScheduledExecutorService getScheduler() {
65         return getBridgeHandler().getScheduler();
66     }
67
68     protected final ScheduledExecutorService getBackgroundScheduler() {
69         return getBridgeHandler().getBackgroundScheduler();
70     }
71
72     protected final KNXBridgeBaseThingHandler getBridgeHandler() {
73         Bridge bridge = getBridge();
74         if (bridge != null) {
75             KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
76             if (handler != null) {
77                 return handler;
78             }
79         }
80         throw new IllegalStateException("The bridge must not be null and must be initialized");
81     }
82
83     protected final KNXClient getClient() {
84         return getBridgeHandler().getClient();
85     }
86
87     protected final boolean describeDevice(@Nullable IndividualAddress address) {
88         if (address == null) {
89             return false;
90         }
91         DeviceInspector inspector = new DeviceInspector(getClient().getDeviceInfoClient(), address);
92         Result result = inspector.readDeviceInfo();
93         if (result != null) {
94             Map<String, String> properties = editProperties();
95             properties.putAll(result.getProperties());
96             updateProperties(properties);
97             return true;
98         }
99         return false;
100     }
101
102     protected final String asduToHex(byte[] asdu) {
103         final char[] hexCode = "0123456789ABCDEF".toCharArray();
104         StringBuilder sb = new StringBuilder(2 + asdu.length * 2);
105         sb.append("0x");
106         for (byte b : asdu) {
107             sb.append(hexCode[(b >> 4) & 0xF]);
108             sb.append(hexCode[(b & 0xF)]);
109         }
110         return sb.toString();
111     }
112
113     protected final void restart() {
114         if (address != null) {
115             getClient().restartNetworkDevice(address);
116         }
117     }
118
119     @Override
120     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
121         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
122             attachToClient();
123         } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
124             detachFromClient();
125             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
126         }
127     }
128
129     @Override
130     public void initialize() {
131         attachToClient();
132     }
133
134     @Override
135     public void dispose() {
136         detachFromClient();
137     }
138
139     protected abstract void scheduleReadJobs();
140
141     protected abstract void cancelReadFutures();
142
143     private void pollDeviceStatus() {
144         try {
145             if (address != null && getClient().isConnected()) {
146                 logger.debug("Polling individual address '{}'", address);
147                 boolean isReachable = getClient().isReachable(address);
148                 if (isReachable) {
149                     updateStatus(ThingStatus.ONLINE);
150                     DeviceConfig config = getConfigAs(DeviceConfig.class);
151                     if (!filledDescription && config.getFetch()) {
152                         Future<?> descriptionJob = this.descriptionJob;
153                         if (descriptionJob == null || descriptionJob.isCancelled()) {
154                             long initialDelay = Math.round(config.getPingInterval().longValue() * random.nextFloat());
155                             this.descriptionJob = getBackgroundScheduler().schedule(() -> {
156                                 filledDescription = describeDevice(address);
157                             }, initialDelay, TimeUnit.SECONDS);
158                         }
159                     }
160                 } else {
161                     updateStatus(ThingStatus.OFFLINE);
162                 }
163             }
164         } catch (KNXException e) {
165             logger.debug("An error occurred while testing the reachability of a thing '{}'", getThing().getUID(), e);
166             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
167         }
168     }
169
170     protected void attachToClient() {
171         if (!getClient().isConnected()) {
172             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
173             return;
174         }
175         DeviceConfig config = getConfigAs(DeviceConfig.class);
176         try {
177             if (!config.getAddress().isEmpty()) {
178                 updateStatus(ThingStatus.UNKNOWN);
179                 address = new IndividualAddress(config.getAddress());
180
181                 long pingInterval = config.getPingInterval().longValue();
182                 long initialPingDelay = Math.round(INITIAL_PING_DELAY * random.nextFloat());
183
184                 ScheduledFuture<?> pollingJob = this.pollingJob;
185                 if ((pollingJob == null || pollingJob.isCancelled())) {
186                     logger.debug("'{}' will be polled every {}s", getThing().getUID(), pingInterval);
187                     this.pollingJob = getBackgroundScheduler().scheduleWithFixedDelay(() -> pollDeviceStatus(),
188                             initialPingDelay, pingInterval, TimeUnit.SECONDS);
189                 }
190             } else {
191                 updateStatus(ThingStatus.ONLINE);
192             }
193         } catch (KNXFormatException e) {
194             logger.debug("An exception occurred while setting the individual address '{}'", config.getAddress(), e);
195             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage());
196         }
197         getClient().registerGroupAddressListener(this);
198         scheduleReadJobs();
199     }
200
201     protected void detachFromClient() {
202         final var pollingJobSynced = pollingJob;
203         if (pollingJobSynced != null) {
204             pollingJobSynced.cancel(true);
205             pollingJob = null;
206         }
207         final var descriptionJobSynced = descriptionJob;
208         if (descriptionJobSynced != null) {
209             descriptionJobSynced.cancel(true);
210             descriptionJob = null;
211         }
212         cancelReadFutures();
213         Bridge bridge = getBridge();
214         if (bridge != null) {
215             KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
216             if (handler != null) {
217                 handler.getClient().unregisterGroupAddressListener(this);
218             }
219         }
220     }
221 }