]> git.basschouten.com Git - openhab-addons.git/blob
f5aa5d6ecc18c2c3c3f16ed01547bd6a63106cfc
[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.net.InetSocketAddress;
16 import java.text.MessageFormat;
17 import java.util.concurrent.Future;
18
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.knx.internal.KNXBindingConstants;
22 import org.openhab.binding.knx.internal.client.IPClient;
23 import org.openhab.binding.knx.internal.client.KNXClient;
24 import org.openhab.binding.knx.internal.client.NoOpClient;
25 import org.openhab.binding.knx.internal.config.IPBridgeConfiguration;
26 import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider;
27 import org.openhab.core.net.NetworkAddressService;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.ThingStatus;
30 import org.openhab.core.thing.ThingStatusDetail;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import tuwien.auto.calimero.secure.KnxSecureException;
35
36 /**
37  * The {@link IPBridgeThingHandler} is responsible for handling commands, which are
38  * sent to one of the channels. It implements a KNX/IP Gateway, that either acts as a
39  * conduit for other {@link DeviceThingHandler}s, or for Channels that are
40  * directly defined on the bridge
41  *
42  * @author Karel Goderis - Initial contribution
43  * @author Simon Kaufmann - Refactoring and cleanup
44  */
45 @NonNullByDefault
46 public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler {
47     private static final String MODE_ROUTER = "ROUTER";
48     private static final String MODE_TUNNEL = "TUNNEL";
49     private static final String MODE_SECURE_ROUTER = "SECUREROUTER";
50     private static final String MODE_SECURE_TUNNEL = "SECURETUNNEL";
51     private @Nullable Future<?> initJob = null;
52
53     private final Logger logger = LoggerFactory.getLogger(IPBridgeThingHandler.class);
54
55     private @Nullable IPClient client = null;
56     private @Nullable final NetworkAddressService networkAddressService;
57
58     public IPBridgeThingHandler(Bridge bridge, @Nullable NetworkAddressService networkAddressService) {
59         super(bridge);
60         this.networkAddressService = networkAddressService;
61     }
62
63     @Override
64     public void initialize() {
65         // initialisation would take too long and show a warning during binding startup
66         // KNX secure is adding serious delay
67         updateStatus(ThingStatus.UNKNOWN);
68         initJob = scheduler.submit(this::initializeLater);
69     }
70
71     public void initializeLater() {
72         IPBridgeConfiguration config = getConfigAs(IPBridgeConfiguration.class);
73         boolean securityAvailable = false;
74         try {
75             securityAvailable = initializeSecurity(config.getRouterBackboneKey(),
76                     config.getTunnelDeviceAuthentication(), config.getTunnelUserId(), config.getTunnelUserPassword());
77             if (securityAvailable) {
78                 logger.debug("KNX secure: router backboneGroupKey is {} set",
79                         ((secureRouting.backboneGroupKey.length == 16) ? "properly" : "not"));
80                 boolean tunnelOk = ((secureTunnel.user > 0) && (secureTunnel.devKey.length == 16)
81                         && (secureTunnel.userKey.length == 16));
82                 logger.debug("KNX secure: tunnel keys are {} set", (tunnelOk ? "properly" : "not"));
83             } else {
84                 logger.debug("KNX security not configured");
85             }
86         } catch (KnxSecureException e) {
87             logger.debug("{}, {}", thing.getUID(), e.toString());
88
89             Throwable cause = e.getCause();
90             if (cause == null) {
91                 cause = e;
92             }
93             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
94                     KNXTranslationProvider.I18N.getLocalizedException(cause));
95             return;
96         }
97
98         int autoReconnectPeriod = config.getAutoReconnectPeriod();
99         if (autoReconnectPeriod != 0 && autoReconnectPeriod < 30) {
100             logger.info("autoReconnectPeriod for {} set to {}s, allowed range is 0 (never) or >30", thing.getUID(),
101                     autoReconnectPeriod);
102             autoReconnectPeriod = 30;
103             config.setAutoReconnectPeriod(autoReconnectPeriod);
104         }
105         String localSource = config.getLocalSourceAddr();
106         String connectionTypeString = config.getType();
107         int port = config.getPortNumber();
108         String ip = config.getIpAddress();
109         InetSocketAddress localEndPoint = null;
110         boolean useNAT = false;
111
112         IPClient.IpConnectionType ipConnectionType;
113         if (MODE_TUNNEL.equalsIgnoreCase(connectionTypeString)) {
114             useNAT = config.getUseNAT();
115             ipConnectionType = IPClient.IpConnectionType.TUNNEL;
116         } else if (MODE_SECURE_TUNNEL.equalsIgnoreCase(connectionTypeString)) {
117             useNAT = config.getUseNAT();
118             ipConnectionType = IPClient.IpConnectionType.SECURE_TUNNEL;
119
120             if (!securityAvailable) {
121                 logger.warn("Bridge {} missing security configuration for secure tunnel", thing.getUID());
122                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
123                         "@text/error.knx-secure-tunnel-config-missing");
124                 return;
125             }
126             boolean tunnelOk = ((secureTunnel.user > 0) && (secureTunnel.devKey.length == 16)
127                     && (secureTunnel.userKey.length == 16));
128             if (!tunnelOk) {
129                 logger.warn("Bridge {} incomplete security configuration for secure tunnel", thing.getUID());
130                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
131                         "@text/error.knx-secure-tunnel-config-incomplete");
132                 return;
133             }
134
135             logger.debug("KNX secure tunneling needs a few seconds to establish connection");
136             // user id, key, devAuth are already stored
137         } else if (MODE_ROUTER.equalsIgnoreCase(connectionTypeString)) {
138             useNAT = false;
139             if (ip.isEmpty()) {
140                 ip = KNXBindingConstants.DEFAULT_MULTICAST_IP;
141             }
142             ipConnectionType = IPClient.IpConnectionType.ROUTER;
143         } else if (MODE_SECURE_ROUTER.equalsIgnoreCase(connectionTypeString)) {
144             useNAT = false;
145             if (ip.isEmpty()) {
146                 ip = KNXBindingConstants.DEFAULT_MULTICAST_IP;
147             }
148             ipConnectionType = IPClient.IpConnectionType.SECURE_ROUTER;
149
150             if (!securityAvailable) {
151                 logger.warn("Bridge {} missing security configuration for secure routing", thing.getUID());
152                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
153                         "@text/error.knx-secure-routing-config-missing");
154                 return;
155             }
156             if (secureRouting.backboneGroupKey.length != 16) {
157                 // failed to read shared backbone group key from config
158                 logger.warn("Bridge {} invalid security configuration for secure routing", thing.getUID());
159                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
160                         "@text/error.knx-secure-routing-backbonegroupkey-invalid");
161                 return;
162             }
163             logger.debug("KNX secure routing needs a few seconds to establish connection");
164         } else {
165             logger.debug("Bridge {} unknown connection type", thing.getUID());
166             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
167                     MessageFormat.format("@text/knx-unknown-ip-connection-type", connectionTypeString));
168             return;
169         }
170
171         if (!config.getLocalIp().isEmpty()) {
172             localEndPoint = new InetSocketAddress(config.getLocalIp(), 0);
173         } else {
174             if (networkAddressService == null) {
175                 logger.debug("NetworkAddressService not available, cannot create bridge {}", thing.getUID());
176                 updateStatus(ThingStatus.OFFLINE);
177                 return;
178             }
179             localEndPoint = new InetSocketAddress(networkAddressService.getPrimaryIpv4HostAddress(), 0);
180         }
181
182         updateStatus(ThingStatus.UNKNOWN);
183         client = new IPClient(ipConnectionType, ip, localSource, port, localEndPoint, useNAT, autoReconnectPeriod,
184                 secureRouting.backboneGroupKey, secureRouting.latencyToleranceMs, secureTunnel.devKey,
185                 secureTunnel.user, secureTunnel.userKey, thing.getUID(), config.getResponseTimeout(),
186                 config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), this);
187
188         final var tmpClient = client;
189         if (tmpClient != null) {
190             tmpClient.initialize();
191         }
192
193         logger.trace("Bridge {} completed KNX scheduled initialization", thing.getUID());
194     }
195
196     @Override
197     public void dispose() {
198         final var tmpInitJob = initJob;
199         if (tmpInitJob != null) {
200             while (!tmpInitJob.isDone()) {
201                 logger.trace("Bridge {}, shutdown during init, trying to cancel", thing.getUID());
202                 tmpInitJob.cancel(true);
203                 try {
204                     Thread.sleep(1000);
205                 } catch (InterruptedException e) {
206                     logger.trace("Bridge {}, cancellation interrupted", thing.getUID());
207                 }
208             }
209             initJob = null;
210         }
211         final var tmpClient = client;
212         if (tmpClient != null) {
213             tmpClient.dispose();
214             client = null;
215         }
216         super.dispose();
217     }
218
219     @Override
220     protected KNXClient getClient() {
221         KNXClient ret = client;
222         if (ret == null) {
223             return new NoOpClient();
224         }
225         return ret;
226     }
227 }