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