2 * Copyright (c) 2010-2022 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.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;
37 import tuwien.auto.calimero.IndividualAddress;
38 import tuwien.auto.calimero.KNXException;
39 import tuwien.auto.calimero.KNXFormatException;
42 * Base class for KNX thing handlers.
44 * @author Simon Kaufmann - initial contribution and API
48 public abstract class AbstractKNXThingHandler extends BaseThingHandler implements GroupAddressListener {
50 private static final int INITIAL_PING_DELAY = 5;
51 private final Logger logger = LoggerFactory.getLogger(AbstractKNXThingHandler.class);
53 protected @Nullable IndividualAddress address;
54 private @Nullable Future<?> descriptionJob;
55 private boolean filledDescription = false;
56 private final Random random = new Random();
58 private @Nullable ScheduledFuture<?> pollingJob;
60 public AbstractKNXThingHandler(Thing thing) {
64 protected final ScheduledExecutorService getScheduler() {
65 return getBridgeHandler().getScheduler();
68 protected final ScheduledExecutorService getBackgroundScheduler() {
69 return getBridgeHandler().getBackgroundScheduler();
72 protected final KNXBridgeBaseThingHandler getBridgeHandler() {
73 Bridge bridge = getBridge();
75 KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
76 if (handler != null) {
80 throw new IllegalStateException("The bridge must not be null and must be initialized");
83 protected final KNXClient getClient() {
84 return getBridgeHandler().getClient();
87 protected final boolean describeDevice(@Nullable IndividualAddress address) {
88 if (address == null) {
91 DeviceInspector inspector = new DeviceInspector(getClient().getDeviceInfoClient(), address);
92 Result result = inspector.readDeviceInfo();
94 Map<String, String> properties = editProperties();
95 properties.putAll(result.getProperties());
96 updateProperties(properties);
102 protected final String asduToHex(byte[] asdu) {
103 final char[] hexCode = "0123456789ABCDEF".toCharArray();
104 StringBuilder sb = new StringBuilder(2 + asdu.length * 2);
106 for (byte b : asdu) {
107 sb.append(hexCode[(b >> 4) & 0xF]);
108 sb.append(hexCode[(b & 0xF)]);
110 return sb.toString();
113 protected final void restart() {
114 if (address != null) {
115 getClient().restartNetworkDevice(address);
120 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
121 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
123 } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
125 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
130 public void initialize() {
135 public void dispose() {
139 protected abstract void scheduleReadJobs();
141 protected abstract void cancelReadFutures();
143 private void pollDeviceStatus() {
145 if (address != null && getClient().isConnected()) {
146 logger.debug("Polling individual address '{}'", address);
147 boolean isReachable = getClient().isReachable(address);
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);
161 updateStatus(ThingStatus.OFFLINE);
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());
170 protected void attachToClient() {
171 if (!getClient().isConnected()) {
172 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
175 DeviceConfig config = getConfigAs(DeviceConfig.class);
177 if (!config.getAddress().isEmpty()) {
178 updateStatus(ThingStatus.UNKNOWN);
179 address = new IndividualAddress(config.getAddress());
181 long pingInterval = config.getPingInterval().longValue();
182 long initialPingDelay = Math.round(INITIAL_PING_DELAY * random.nextFloat());
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);
191 updateStatus(ThingStatus.ONLINE);
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());
197 getClient().registerGroupAddressListener(this);
201 protected void detachFromClient() {
202 final var pollingJobSynced = pollingJob;
203 if (pollingJobSynced != null) {
204 pollingJobSynced.cancel(true);
207 final var descriptionJobSynced = descriptionJob;
208 if (descriptionJobSynced != null) {
209 descriptionJobSynced.cancel(true);
210 descriptionJob = null;
213 Bridge bridge = getBridge();
214 if (bridge != null) {
215 KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
216 if (handler != null) {
217 handler.getClient().unregisterGroupAddressListener(this);