]> git.basschouten.com Git - openhab-addons.git/blob
b362cc3dab60fdc067fa6eab5c6494998a13ec42
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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 a 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 & 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 final NetworkAddressService networkAddressService;
57
58     public IPBridgeThingHandler(Bridge bridge, 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(() -> {
69             initializeLater();
70         });
71     }
72
73     public void initializeLater() {
74         IPBridgeConfiguration config = getConfigAs(IPBridgeConfiguration.class);
75         boolean securityAvailable = false;
76         try {
77             securityAvailable = initializeSecurity(config.getRouterBackboneKey(),
78                     config.getTunnelDeviceAuthentication(), config.getTunnelUserId(), config.getTunnelUserPassword());
79             if (securityAvailable) {
80                 logger.debug("KNX secure: router backboneGroupKey is {} set",
81                         ((secureRouting.backboneGroupKey.length == 16) ? "properly" : "not"));
82                 boolean tunnelOk = ((secureTunnel.user > 0) && (secureTunnel.devKey.length == 16)
83                         && (secureTunnel.userKey.length == 16));
84                 logger.debug("KNX secure: tunnel keys are {} set", (tunnelOk ? "properly" : "not"));
85             } else {
86                 logger.debug("KNX security not configured");
87             }
88         } catch (KnxSecureException e) {
89             logger.debug("{}, {}", thing.getUID(), e.toString());
90
91             Throwable cause = e.getCause();
92             if (cause == null) {
93                 cause = e;
94             }
95             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
96                     KNXTranslationProvider.I18N.getLocalizedException(cause));
97             return;
98         }
99
100         int autoReconnectPeriod = config.getAutoReconnectPeriod();
101         if (autoReconnectPeriod != 0 && autoReconnectPeriod < 30) {
102             logger.info("autoReconnectPeriod for {} set to {}s, allowed range is 0 (never) or >30", thing.getUID(),
103                     autoReconnectPeriod);
104             autoReconnectPeriod = 30;
105             config.setAutoReconnectPeriod(autoReconnectPeriod);
106         }
107         String localSource = config.getLocalSourceAddr();
108         String connectionTypeString = config.getType();
109         int port = config.getPortNumber().intValue();
110         String ip = config.getIpAddress();
111         InetSocketAddress localEndPoint = null;
112         boolean useNAT = false;
113
114         IPClient.IpConnectionType ipConnectionType;
115         if (MODE_TUNNEL.equalsIgnoreCase(connectionTypeString)) {
116             useNAT = config.getUseNAT();
117             ipConnectionType = IPClient.IpConnectionType.TUNNEL;
118         } else if (MODE_SECURE_TUNNEL.equalsIgnoreCase(connectionTypeString)) {
119             useNAT = config.getUseNAT();
120             ipConnectionType = IPClient.IpConnectionType.SECURE_TUNNEL;
121
122             if (!securityAvailable) {
123                 logger.warn("Bridge {} missing security configuration for secure tunnel", thing.getUID());
124                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
125                         "@text/error.knx-secure-tunnel-config-missing");
126                 return;
127             }
128             boolean tunnelOk = ((secureTunnel.user > 0) && (secureTunnel.devKey.length == 16)
129                     && (secureTunnel.userKey.length == 16));
130             if (!tunnelOk) {
131                 logger.warn("Bridge {} incomplete security configuration for secure tunnel", thing.getUID());
132                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
133                         "@text/error.knx-secure-tunnel-config-incomplete");
134                 return;
135             }
136
137             logger.debug("KNX secure tunneling needs a few seconds to establish connection");
138             // user id, key, devAuth are already stored
139         } else if (MODE_ROUTER.equalsIgnoreCase(connectionTypeString)) {
140             useNAT = false;
141             if (ip.isEmpty()) {
142                 ip = KNXBindingConstants.DEFAULT_MULTICAST_IP;
143             }
144             ipConnectionType = IPClient.IpConnectionType.ROUTER;
145         } else if (MODE_SECURE_ROUTER.equalsIgnoreCase(connectionTypeString)) {
146             useNAT = false;
147             if (ip.isEmpty()) {
148                 ip = KNXBindingConstants.DEFAULT_MULTICAST_IP;
149             }
150             ipConnectionType = IPClient.IpConnectionType.SECURE_ROUTER;
151
152             if (!securityAvailable) {
153                 logger.warn("Bridge {} missing security configuration for secure routing", thing.getUID());
154                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
155                         "@text/error.knx-secure-routing-config-missing");
156                 return;
157             }
158             if (secureRouting.backboneGroupKey.length != 16) {
159                 // failed to read shared backbone group key from config
160                 logger.warn("Bridge {} invalid security configuration for secure routing", thing.getUID());
161                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
162                         "@text/error.knx-secure-routing-backbonegroupkey-invalid");
163                 return;
164             }
165             logger.debug("KNX secure routing needs a few seconds to establish connection");
166         } else {
167             logger.debug("Bridge {} unknown connection type", thing.getUID());
168             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
169                     MessageFormat.format("@text/knx-unknown-ip-connection-type", connectionTypeString));
170             return;
171         }
172
173         if (!config.getLocalIp().isEmpty()) {
174             localEndPoint = new InetSocketAddress(config.getLocalIp(), 0);
175         } else {
176             localEndPoint = new InetSocketAddress(networkAddressService.getPrimaryIpv4HostAddress(), 0);
177         }
178
179         updateStatus(ThingStatus.UNKNOWN);
180         client = new IPClient(ipConnectionType, ip, localSource, port, localEndPoint, useNAT, autoReconnectPeriod,
181                 secureRouting.backboneGroupKey, secureRouting.latencyToleranceMs, secureTunnel.devKey,
182                 secureTunnel.user, secureTunnel.userKey, thing.getUID(), config.getResponseTimeout().intValue(),
183                 config.getReadingPause().intValue(), config.getReadRetriesLimit().intValue(), getScheduler(), this);
184
185         final var tmpClient = client;
186         if (tmpClient != null) {
187             tmpClient.initialize();
188         }
189
190         logger.trace("Bridge {} completed KNX scheduled initialization", thing.getUID());
191     }
192
193     @Override
194     public void dispose() {
195         final var tmpInitJob = initJob;
196         if (tmpInitJob != null) {
197             while (!tmpInitJob.isDone()) {
198                 logger.trace("Bridge {}, shutdown during init, trying to cancel", thing.getUID());
199                 tmpInitJob.cancel(true);
200                 try {
201                     Thread.sleep(1000);
202                 } catch (InterruptedException e) {
203                     logger.trace("Bridge {}, cancellation interrupted", thing.getUID());
204                 }
205             }
206             initJob = null;
207         }
208         final var tmpClient = client;
209         if (tmpClient != null) {
210             tmpClient.dispose();
211             client = null;
212         }
213         super.dispose();
214     }
215
216     @Override
217     protected KNXClient getClient() {
218         KNXClient ret = client;
219         if (ret == null) {
220             return new NoOpClient();
221         }
222         return ret;
223     }
224 }