mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-31 13:34:22 +02:00
[linktap] Add communication timeout configuration parameter (#18679)
Signed-off-by: David Goodyear <david.goodyear@gmail.com>
This commit is contained in:
@@ -70,14 +70,15 @@ If the gateway cannot publish to openHAB, then the gateway is checked every 2 mi
|
||||
|
||||
### Gateway Configuration
|
||||
|
||||
| Name | Type | Description | Recommended Values | Required | Advanced |
|
||||
|-----------------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|----------|----------|
|
||||
| host | String | The hostname / IP address of the gateway device | | Yes | No |
|
||||
| username | String | The username if set for the gateway device | | No | No |
|
||||
| password | String | The password if set for the gateway device | | No | No |
|
||||
| enableMDNS | Switch | On connection whether the mDNS responder should be enabled on the gateway device | true | No | Yes |
|
||||
| enforceProtocolLimits | Switch | If true data outside of the allowed ranges against the protocol will be logged and not sent | true | No | Yes |
|
||||
| enableJSONComms | Switch | false by default for backwards compatibility, if using up to date firmware with no other local network applications set this to true, for more efficient communications | true | No | Yes |
|
||||
| Name | Type | Description | Recommended Values | Required | Advanced |
|
||||
|------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|----------|----------|
|
||||
| host | text | The hostname / IP address of the gateway device | | Yes | No |
|
||||
| username | text | The username if set for the gateway device | | No | No |
|
||||
| password | text | The password if set for the gateway device | | No | No |
|
||||
| enableMDNS | boolean | On connection whether the mDNS responder should be enabled on the gateway device | true | No | Yes |
|
||||
| enforceProtocolLimits | boolean | If true data outside of the allowed ranges against the protocol will be logged and not sent | true | No | Yes |
|
||||
| enableJSONComms | boolean | false by default for backwards compatibility, if using up to date firmware with no other local network applications set this to true, for more efficient communications | true | No | Yes |
|
||||
| gatewayResponseTimeout | integer | For slow or heavily loaded systems this may need increasing, if communication errors are seen (seconds allowed for responses from the gateway) | 3 | No | Yes |
|
||||
|
||||
**NOTE** When enableMDNS is enabled, upon connection to the gateway option "Enable mDNS responder" is switched on.
|
||||
|
||||
@@ -153,7 +154,7 @@ There are 4 different areas of channels:
|
||||
- **Device Model**: Q1
|
||||
|
||||
```java
|
||||
Bridge linktap:gateway:home "LinkTap GW02" [ host="192.168.0.21", enableMDNS=true, enableJSONComms=false, enforceProtocolLimits=true ] {
|
||||
Bridge linktap:gateway:home "LinkTap GW02" [ host="192.168.0.21", enableMDNS=true, enableJSONComms=false, enforceProtocolLimits=true, gatewayResponseTimeout=3 ] {
|
||||
Thing device TapValve1 "Outdoor Tap 1" [ id="D71BC52E985B1200_1", name="ValveLinker_1", enableAlerts=true ]
|
||||
Thing device TapValve2 "Outdoor Tap 2" [ id="D71BC52E985B1200_2", name="ValveLinker_2", enableAlerts=true ]
|
||||
Thing device TapValve3 "Outdoor Tap 3" [ id="D71BC52E985B1200_3", name="ValveLinker_3", enableAlerts=true ]
|
||||
|
||||
+1
@@ -28,4 +28,5 @@ public class LinkTapBridgeConfiguration {
|
||||
public boolean enableMDNS = true;
|
||||
public boolean enableJSONComms = false;
|
||||
public boolean enforceProtocolLimits = true;
|
||||
public int gatewayResponseTimeout = 3;
|
||||
}
|
||||
|
||||
+1
@@ -59,6 +59,7 @@ public class LinkTapBindingConstants {
|
||||
public static final String BRIDGE_CONFIG_MDNS_ENABLE = "enableMDNS";
|
||||
public static final String BRIDGE_CONFIG_NON_HTML_COMM_ENABLE = "enableJSONComms";
|
||||
public static final String BRIDGE_CONFIG_ENFORCE_COMM_LIMITS = "enforceProtocolLimits";
|
||||
public static final String BRIDGE_CONFIG_GATEWAY_RESPONSE_TIMEOUT = "gatewayResponseTimeout";
|
||||
|
||||
public static final String DEVICE_PROP_DEV_ID = "deviceId";
|
||||
public static final String DEVICE_PROP_DEV_NAME = "deviceName";
|
||||
|
||||
+12
-7
@@ -143,6 +143,10 @@ public class LinkTapBridgeHandler extends BaseBridgeHandler {
|
||||
return Objects.nonNull(result) ? result : key;
|
||||
}
|
||||
|
||||
public int getResponseTimeout() {
|
||||
return config.gatewayResponseTimeout;
|
||||
}
|
||||
|
||||
private void startGwPolling() {
|
||||
synchronized (schedulerLock) {
|
||||
cancelGwPolling();
|
||||
@@ -379,7 +383,7 @@ public class LinkTapBridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
final String reqData = LinkTapBindingConstants.GSON.toJson(req);
|
||||
logger.debug("{} = APP BRIDGE -> GW -> Request {}", uid, reqData);
|
||||
final String respData = api.sendRequest(host, reqData);
|
||||
final String respData = api.sendRequest(host, getResponseTimeout(), reqData);
|
||||
logger.debug("{} = APP BRIDGE -> GW -> Response {}", uid, respData);
|
||||
final GatewayDeviceResponse gwResponseFrame = LinkTapBindingConstants.GSON.fromJson(respData,
|
||||
GatewayDeviceResponse.class);
|
||||
@@ -429,7 +433,7 @@ public class LinkTapBridgeHandler extends BaseBridgeHandler {
|
||||
final WebServerApi api = WebServerApi.getInstance();
|
||||
api.setHttpClient(httpClientProvider.getHttpClient());
|
||||
try {
|
||||
final Map<String, String> bridgeProps = api.getBridgeProperities(bridgeKey);
|
||||
final Map<String, String> bridgeProps = api.getBridgeProperities(bridgeKey, getResponseTimeout());
|
||||
if (!bridgeProps.isEmpty()) {
|
||||
final String readGwId = bridgeProps.get(BRIDGE_PROP_GW_ID);
|
||||
if (readGwId != null) {
|
||||
@@ -439,7 +443,7 @@ public class LinkTapBridgeHandler extends BaseBridgeHandler {
|
||||
currentProps.putAll(bridgeProps);
|
||||
updateProperties(currentProps);
|
||||
} else {
|
||||
if (!api.unlockWebInterface(bridgeKey, config.username, config.password)) {
|
||||
if (!api.unlockWebInterface(bridgeKey, getResponseTimeout(), config.username, config.password)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
getLocalizedText("bridge.error.check-credentials"));
|
||||
return;
|
||||
@@ -481,13 +485,13 @@ public class LinkTapBridgeHandler extends BaseBridgeHandler {
|
||||
final String servletEp = BindingServlet.getServletAddress(localServerAddr,
|
||||
getLocalizedText("warning.no-http-server-port"));
|
||||
final Optional<String> servletEpOpt = (!servletEp.isEmpty()) ? Optional.of(servletEp) : Optional.empty();
|
||||
api.configureBridge(hostname, Optional.of(config.enableMDNS), Optional.of(config.enableJSONComms),
|
||||
servletEpOpt);
|
||||
api.configureBridge(hostname, getResponseTimeout(), Optional.of(config.enableMDNS),
|
||||
Optional.of(config.enableJSONComms), servletEpOpt);
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we have a response with data in if not schedule a reconnect in 15 seconds, theres no reason
|
||||
// Ensure we have a response with data in if not schedule a reconnect in 15 seconds, there's no reason
|
||||
// for a gateway with no devices.
|
||||
if (!getGatewayConfigurationFreshCheck()) {
|
||||
logger.debug("{}", getLocalizedText("bridge.info.awaiting-init"));
|
||||
@@ -532,7 +536,8 @@ public class LinkTapBridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
|
||||
private void scheduleReconnect() {
|
||||
scheduleReconnect(15);
|
||||
scheduleReconnect(getResponseTimeout() * 5); // 5 is due to the number of req/resp required for
|
||||
// connection
|
||||
}
|
||||
|
||||
public void attemptReconnectIfNeeded() {
|
||||
|
||||
+1
-1
@@ -235,7 +235,7 @@ public class LinkTapHandler extends PollingDeviceHandler {
|
||||
strStore.put(DEVICE_CHANNEL_OH_VOLUME_LIMIT, String.valueOf(targetValue));
|
||||
break;
|
||||
}
|
||||
} else if (command instanceof StringType stringCmd) {
|
||||
} else if (command instanceof StringType) {
|
||||
switch (channelUID.getId()) {
|
||||
case DEVICE_CHANNEL_CHILD_LOCK: {
|
||||
sendRequest(new LockReq(Integer.valueOf(command.toString())));
|
||||
|
||||
+1
-1
@@ -239,7 +239,7 @@ public final class TransactionProcessor {
|
||||
final String payloadJson = GSON.toJson(request);
|
||||
logger.debug("{} = APP -> GW Request {} -> Payload {}", uid, targetHost, payloadJson);
|
||||
|
||||
String response = API.sendRequest(targetHost, GSON.toJson(request));
|
||||
String response = API.sendRequest(targetHost, handler.getResponseTimeout(), GSON.toJson(request));
|
||||
logger.debug("{} = APP -> GW Response {} -> Payload {}", uid, targetHost, response.trim());
|
||||
GatewayDeviceResponse gatewayFrame = GSON.fromJson(response, GatewayDeviceResponse.class);
|
||||
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.linktap.protocol.http;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The {@link JettyTraceListener} defines a basic listener that can be utilised for logging jetty client states.
|
||||
*
|
||||
* @author David Goodyear - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class JettyTraceListener implements Request.Listener {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
public JettyTraceListener(final Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueued(@Nullable Request request) {
|
||||
if (request != null) {
|
||||
logger.trace("HTTP Comms request is queued to be processed to {}", request.getURI());
|
||||
} else {
|
||||
logger.trace("HTTP Comms request is queued to be processed");
|
||||
}
|
||||
Request.Listener.super.onQueued(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBegin(@Nullable Request request) {
|
||||
if (request != null) {
|
||||
logger.trace("HTTP Comms request is beginning to be processed to {}", request.getURI());
|
||||
} else {
|
||||
logger.trace("HTTP Comms request is beginning to be processed");
|
||||
}
|
||||
Request.Listener.super.onBegin(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable Request request) {
|
||||
if (request != null) {
|
||||
logger.trace("HTTP Comms request has been reported as successful to {}", request.getURI());
|
||||
} else {
|
||||
logger.trace("HTTP Comms request has been reported as successful");
|
||||
}
|
||||
Request.Listener.super.onSuccess(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nullable Request request, @Nullable Throwable failure) {
|
||||
if (request != null) {
|
||||
logger.trace("HTTP Comms request has failed {}", request.getHost());
|
||||
} else {
|
||||
logger.trace("HTTP Comms request has failed with error");
|
||||
}
|
||||
if (failure != null) {
|
||||
logger.trace("HTTP Comms request has failed due to cause {}", failure.toString());
|
||||
}
|
||||
Request.Listener.super.onFailure(request, failure);
|
||||
}
|
||||
}
|
||||
+31
-17
@@ -87,10 +87,10 @@ public final class WebServerApi {
|
||||
private static final String FIELD_ADMIN_USER = "admin";
|
||||
private static final String FIELD_ADMIN_USER_PWD = "adminpwd";
|
||||
|
||||
private static final int REQ_TIMEOUT_SECONDS = 3;
|
||||
private static final WebServerApi INSTANCE = new WebServerApi();
|
||||
private static final String REQ_HDR_APPLICATION_JSON = new MediaType("application", "json", "UTF-8").toString();
|
||||
private final Logger logger = LoggerFactory.getLogger(WebServerApi.class);
|
||||
private final JettyTraceListener jettyTraceListener = new JettyTraceListener(logger);
|
||||
|
||||
private @NonNullByDefault({}) HttpClient httpClient;
|
||||
private @Nullable TranslationProvider translationProvider;
|
||||
@@ -132,11 +132,11 @@ public final class WebServerApi {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getBridgeProperities(final String hostname)
|
||||
public Map<String, String> getBridgeProperities(final String hostname, final int timeoutSeconds)
|
||||
throws LinkTapException, NotTapLinkGatewayException, TransientCommunicationIssueException {
|
||||
try {
|
||||
final Request request = httpClient.newRequest(URI_HOST_PREFIX + hostname).method(HttpMethod.GET);
|
||||
final ContentResponse cr = request.timeout(REQ_TIMEOUT_SECONDS, TimeUnit.SECONDS).send();
|
||||
final ContentResponse cr = addTraceListener(request).timeout(timeoutSeconds, TimeUnit.SECONDS).send();
|
||||
if (HttpURLConnection.HTTP_OK != cr.getStatus()) {
|
||||
throw new NotTapLinkGatewayException(UNEXPECTED_STATUS_CODE);
|
||||
}
|
||||
@@ -375,8 +375,9 @@ public final class WebServerApi {
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean configureBridge(final @Nullable String hostname, final Optional<Boolean> mdnsEnable,
|
||||
final Optional<Boolean> nonHtmlEnable, final Optional<String> localServer)
|
||||
public boolean configureBridge(final @Nullable String hostname, final int timeoutSeconds,
|
||||
final Optional<Boolean> mdnsEnable, final Optional<Boolean> nonHtmlEnable,
|
||||
final Optional<String> localServer)
|
||||
throws InterruptedException, NotTapLinkGatewayException, TransientCommunicationIssueException {
|
||||
try {
|
||||
if (hostname == null) {
|
||||
@@ -384,10 +385,11 @@ public final class WebServerApi {
|
||||
}
|
||||
final String targetHost = URI_HOST_PREFIX + hostname;
|
||||
final Request request = httpClient.newRequest(targetHost).method(HttpMethod.GET);
|
||||
final ContentResponse cr = request.timeout(REQ_TIMEOUT_SECONDS, TimeUnit.SECONDS).send();
|
||||
final ContentResponse cr = addTraceListener(request).timeout(timeoutSeconds, TimeUnit.SECONDS).send();
|
||||
if (HttpURLConnection.HTTP_OK != cr.getStatus()) {
|
||||
throw new NotTapLinkGatewayException(UNEXPECTED_STATUS_CODE);
|
||||
}
|
||||
logger.trace("Validating response from Gateway web UI");
|
||||
validateHeaders(cr.getHeaders());
|
||||
final String responseData = cr.getContentAsString();
|
||||
final Document doc = Jsoup.parse(responseData);
|
||||
@@ -409,7 +411,8 @@ public final class WebServerApi {
|
||||
logger.debug("Updating mdns server settings on gateway");
|
||||
final Request mdnsRequest = httpClient
|
||||
.newRequest(targetHost + "/index.shtml?flag=4&" + mdnsEnableReqStr).method(HttpMethod.GET);
|
||||
final ContentResponse mdnsCr = mdnsRequest.timeout(REQ_TIMEOUT_SECONDS, TimeUnit.SECONDS).send();
|
||||
final ContentResponse mdnsCr = addTraceListener(mdnsRequest)
|
||||
.timeout(timeoutSeconds, TimeUnit.SECONDS).send();
|
||||
if (HttpURLConnection.HTTP_OK != mdnsCr.getStatus()) {
|
||||
throw new NotTapLinkGatewayException(UNEXPECTED_STATUS_CODE);
|
||||
}
|
||||
@@ -432,8 +435,8 @@ public final class WebServerApi {
|
||||
final Request lhttpApiRequest = httpClient
|
||||
.newRequest(targetHost + "/index.shtml?flag=5&" + localHttpApiReqStr)
|
||||
.method(HttpMethod.GET);
|
||||
final ContentResponse mdnsCr = lhttpApiRequest.timeout(REQ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.send();
|
||||
final ContentResponse mdnsCr = addTraceListener(lhttpApiRequest)
|
||||
.timeout(timeoutSeconds, TimeUnit.SECONDS).send();
|
||||
if (HttpURLConnection.HTTP_OK != mdnsCr.getStatus()) {
|
||||
throw new NotTapLinkGatewayException(UNEXPECTED_STATUS_CODE);
|
||||
}
|
||||
@@ -445,7 +448,8 @@ public final class WebServerApi {
|
||||
logger.debug("Rebooting gateway to apply new settings");
|
||||
final Request restartReq = httpClient.newRequest(targetHost + "/index.shtml?flag=0")
|
||||
.method(HttpMethod.GET);
|
||||
final ContentResponse mdnsCr = restartReq.timeout(REQ_TIMEOUT_SECONDS, TimeUnit.SECONDS).send();
|
||||
final ContentResponse mdnsCr = addTraceListener(restartReq).timeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
.send();
|
||||
if (HttpURLConnection.HTTP_OK != mdnsCr.getStatus()) {
|
||||
throw new NotTapLinkGatewayException(UNEXPECTED_STATUS_CODE);
|
||||
}
|
||||
@@ -470,7 +474,8 @@ public final class WebServerApi {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean unlockWebInterface(final String hostname, final String username, final String password)
|
||||
public boolean unlockWebInterface(final String hostname, final int timeoutSeconds, final String username,
|
||||
final String password)
|
||||
throws LinkTapException, NotTapLinkGatewayException, TransientCommunicationIssueException {
|
||||
try {
|
||||
org.eclipse.jetty.util.Fields fields = new org.eclipse.jetty.util.Fields();
|
||||
@@ -478,12 +483,13 @@ public final class WebServerApi {
|
||||
fields.put(FIELD_ADMIN_USER_PWD, password);
|
||||
final Request request = httpClient.newRequest(URI_HOST_PREFIX + hostname + "/login.shtml")
|
||||
.method(HttpMethod.POST).content(new FormContentProvider(fields));
|
||||
final ContentResponse cr = request.timeout(REQ_TIMEOUT_SECONDS, TimeUnit.SECONDS).send();
|
||||
|
||||
final ContentResponse cr = addTraceListener(request).timeout(timeoutSeconds, TimeUnit.SECONDS).send();
|
||||
if (HttpURLConnection.HTTP_OK != cr.getStatus()) {
|
||||
throw new NotTapLinkGatewayException(UNEXPECTED_STATUS_CODE);
|
||||
}
|
||||
validateHeaders(cr.getHeaders());
|
||||
return !getBridgeProperities(hostname).isEmpty();
|
||||
return !getBridgeProperities(hostname, timeoutSeconds).isEmpty();
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
} catch (TimeoutException e) {
|
||||
@@ -516,15 +522,15 @@ public final class WebServerApi {
|
||||
}
|
||||
}
|
||||
|
||||
public String sendRequest(final String hostname, final String requestBody)
|
||||
public String sendRequest(final String hostname, final int timeoutSeconds, final String requestBody)
|
||||
throws NotTapLinkGatewayException, TransientCommunicationIssueException {
|
||||
try {
|
||||
final InetAddress address = InetAddress.getByName(hostname);
|
||||
logger.trace("API Endpoint: {}", URI_HOST_PREFIX + address.getHostAddress() + "/api.shtml");
|
||||
final Request request = httpClient.POST(URI_HOST_PREFIX + address.getHostAddress() + "/api.shtml");
|
||||
request.content(new StringContentProvider(requestBody), REQ_HDR_APPLICATION_JSON);
|
||||
final Request request = httpClient.POST(URI_HOST_PREFIX + address.getHostAddress() + "/api.shtml")
|
||||
.content(new StringContentProvider(requestBody), REQ_HDR_APPLICATION_JSON);
|
||||
|
||||
final ContentResponse cr = request.timeout(REQ_TIMEOUT_SECONDS, TimeUnit.SECONDS).send();
|
||||
final ContentResponse cr = addTraceListener(request).timeout(timeoutSeconds, TimeUnit.SECONDS).send();
|
||||
if (HttpURLConnection.HTTP_OK != cr.getStatus()) {
|
||||
throw new NotTapLinkGatewayException(UNEXPECTED_STATUS_CODE);
|
||||
}
|
||||
@@ -571,4 +577,12 @@ public final class WebServerApi {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private org.eclipse.jetty.client.api.Request addTraceListener(final Request request) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
return request.onRequestQueued(jettyTraceListener).onRequestBegin(jettyTraceListener)
|
||||
.onRequestSuccess(jettyTraceListener).onRequestFailure(jettyTraceListener);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ thing-type.config.linktap.gateway.enableMDNS.label = Enable mDNS Responder
|
||||
thing-type.config.linktap.gateway.enableMDNS.description = On connection whether the mDNS responder should be enabled on the gateway device
|
||||
thing-type.config.linktap.gateway.enforceProtocolLimits.label = Enforce protocol limits
|
||||
thing-type.config.linktap.gateway.enforceProtocolLimits.description = If parameters outside the limits acceptable to the device's are sent they will be blocked and logged
|
||||
thing-type.config.linktap.gateway.gatewayResponseTimeout.label = Gateway Response Timeout
|
||||
thing-type.config.linktap.gateway.gatewayResponseTimeout.description = For slow or heavily loaded systems this may need increasing, if communication errors are seen (seconds allowed for responses from the gateway)
|
||||
thing-type.config.linktap.gateway.host.label = Hostname / IP
|
||||
thing-type.config.linktap.gateway.host.description = The hostname / IP address of the gateway device
|
||||
thing-type.config.linktap.gateway.password.label = Device Password
|
||||
|
||||
@@ -53,6 +53,13 @@
|
||||
<default>true</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="gatewayResponseTimeout" type="integer" required="true" min="3" max="30">
|
||||
<label>Gateway Response Timeout</label>
|
||||
<description>For slow or heavily loaded systems this may need increasing, if communication errors are seen (seconds
|
||||
allowed for responses from the gateway)</description>
|
||||
<default>3</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user