2 * Copyright (c) 2010-2023 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.knx.internal.handler;
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;
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;
38 import tuwien.auto.calimero.IndividualAddress;
39 import tuwien.auto.calimero.KNXException;
40 import tuwien.auto.calimero.KNXFormatException;
43 * Base class for KNX thing handlers.
45 * @author Simon Kaufmann - initial contribution and API
49 public abstract class AbstractKNXThingHandler extends BaseThingHandler implements GroupAddressListener {
51 private static final int INITIAL_PING_DELAY = 5;
52 private final Logger logger = LoggerFactory.getLogger(AbstractKNXThingHandler.class);
54 protected @Nullable IndividualAddress address;
55 private @Nullable Future<?> descriptionJob;
56 private boolean filledDescription = false;
57 private final Random random = new Random();
59 private @Nullable ScheduledFuture<?> pollingJob;
61 public AbstractKNXThingHandler(Thing thing) {
65 protected final ScheduledExecutorService getScheduler() {
66 return getBridgeHandler().getScheduler();
69 protected final ScheduledExecutorService getBackgroundScheduler() {
70 return getBridgeHandler().getBackgroundScheduler();
73 protected final KNXBridgeBaseThingHandler getBridgeHandler() {
74 Bridge bridge = getBridge();
76 KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
77 if (handler != null) {
81 throw new IllegalStateException("The bridge must not be null and must be initialized");
84 protected final KNXClient getClient() {
85 return getBridgeHandler().getClient();
88 protected final boolean describeDevice(@Nullable IndividualAddress address) {
89 if (address == null) {
92 DeviceInspector inspector = new DeviceInspector(getClient().getDeviceInfoClient(), address);
93 Result result = inspector.readDeviceInfo();
95 Map<String, String> properties = editProperties();
96 properties.putAll(result.getProperties());
97 updateProperties(properties);
103 protected final String asduToHex(byte[] asdu) {
104 final char[] hexCode = "0123456789ABCDEF".toCharArray();
105 StringBuilder sb = new StringBuilder(2 + asdu.length * 2);
107 for (byte b : asdu) {
108 sb.append(hexCode[(b >> 4) & 0xF]);
109 sb.append(hexCode[(b & 0xF)]);
111 return sb.toString();
114 protected final void restart() {
115 if (address != null) {
116 getClient().restartNetworkDevice(address);
121 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
122 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
124 } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
126 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
131 public void initialize() {
136 public void dispose() {
140 protected abstract void scheduleReadJobs();
142 protected abstract void cancelReadFutures();
144 private void pollDeviceStatus() {
146 if (address != null && getClient().isConnected()) {
147 logger.debug("Polling individual address '{}'", address);
148 boolean isReachable = getClient().isReachable(address);
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);
162 updateStatus(ThingStatus.OFFLINE);
165 } catch (KNXException e) {
166 logger.debug("An error occurred while testing the reachability of a thing '{}': {}", getThing().getUID(),
168 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
169 KNXTranslationProvider.I18N.getLocalizedException(e));
173 protected void attachToClient() {
174 if (!getClient().isConnected()) {
175 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
178 DeviceConfig config = getConfigAs(DeviceConfig.class);
180 if (!config.getAddress().isEmpty()) {
181 updateStatus(ThingStatus.UNKNOWN);
182 address = new IndividualAddress(config.getAddress());
184 long pingInterval = config.getPingInterval();
185 long initialPingDelay = Math.round(INITIAL_PING_DELAY * random.nextFloat());
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);
194 updateStatus(ThingStatus.ONLINE);
196 } catch (KNXFormatException e) {
197 logger.debug("An exception occurred while setting the individual address '{}': {}", config.getAddress(),
199 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
200 KNXTranslationProvider.I18N.getLocalizedException(e));
202 getClient().registerGroupAddressListener(this);
206 protected void detachFromClient() {
207 final var pollingJobSynced = pollingJob;
208 if (pollingJobSynced != null) {
209 pollingJobSynced.cancel(true);
212 final var descriptionJobSynced = descriptionJob;
213 if (descriptionJobSynced != null) {
214 descriptionJobSynced.cancel(true);
215 descriptionJob = null;
218 Bridge bridge = getBridge();
219 if (bridge != null) {
220 KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
221 if (handler != null) {
222 handler.getClient().unregisterGroupAddressListener(this);