]> git.basschouten.com Git - openhab-addons.git/blob
f440eea0314851b54a41a2c1dcc351cb1c85f9f5
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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         // initialization 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             NetworkAddressService localNetworkAddressService = networkAddressService;
175             if (localNetworkAddressService == null) {
176                 logger.debug("NetworkAddressService not available, cannot create bridge {}", thing.getUID());
177                 updateStatus(ThingStatus.OFFLINE);
178                 return;
179             } else {
180                 localEndPoint = new InetSocketAddress(localNetworkAddressService.getPrimaryIpv4HostAddress(), 0);
181             }
182         }
183
184         updateStatus(ThingStatus.UNKNOWN);
185         client = new IPClient(ipConnectionType, ip, localSource, port, localEndPoint, useNAT, autoReconnectPeriod,
186                 secureRouting.backboneGroupKey, secureRouting.latencyToleranceMs, secureTunnel.devKey,
187                 secureTunnel.user, secureTunnel.userKey, thing.getUID(), config.getResponseTimeout(),
188                 config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), getCommandExtensionData(),
189                 this);
190
191         IPClient tmpClient = client;
192         if (tmpClient != null) {
193             tmpClient.initialize();
194         }
195
196         logger.trace("Bridge {} completed KNX scheduled initialization", thing.getUID());
197     }
198
199     @Override
200     public void dispose() {
201         Future<?> tmpInitJob = initJob;
202         if (tmpInitJob != null) {
203             while (!tmpInitJob.isDone()) {
204                 logger.trace("Bridge {}, shutdown during init, trying to cancel", thing.getUID());
205                 tmpInitJob.cancel(true);
206                 try {
207                     Thread.sleep(1000);
208                 } catch (InterruptedException e) {
209                     logger.trace("Bridge {}, cancellation interrupted", thing.getUID());
210                 }
211             }
212             initJob = null;
213         }
214         IPClient tmpClient = client;
215         if (tmpClient != null) {
216             tmpClient.dispose();
217             client = null;
218         }
219         super.dispose();
220     }
221
222     @Override
223     protected KNXClient getClient() {
224         KNXClient ret = client;
225         if (ret == null) {
226             return new NoOpClient();
227         }
228         return ret;
229     }
230 }