[opengarage] Move to jetty httpclient and other small improvements (#20980)

* Move to jetty httpclient

Signed-off-by: Paul Smedley <paul@smedley.id.au>
This commit is contained in:
Paul Smedley
2026-06-16 15:17:55 +02:00
committed by GitHub
parent b8bb1448c1
commit e595906f71
3 changed files with 69 additions and 54 deletions
@@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.opengarage.internal.api.ControllerVariables; import org.openhab.binding.opengarage.internal.api.ControllerVariables;
import org.openhab.binding.opengarage.internal.api.Enums.OpenGarageCommand; import org.openhab.binding.opengarage.internal.api.Enums.OpenGarageCommand;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
@@ -57,20 +58,22 @@ public class OpenGarageHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenGarageHandler.class); private final Logger logger = LoggerFactory.getLogger(OpenGarageHandler.class);
private @NonNullByDefault({}) OpenGarageWebTargets webTargets; private @NonNullByDefault({}) OpenGarageWebTargets webTargets;
private final HttpClient httpClient;
// reference to periodically scheduled poll task // reference to periodically scheduled poll task
private Future<?> pollScheduledFuture = CompletableFuture.completedFuture(null); private Future<?> pollScheduledFuture = CompletableFuture.completedFuture(null);
// reference to one-shot poll task which gets scheduled after a garage state change command // reference to one-shot poll task which gets scheduled after a garage state change command
private Future<?> pollScheduledFutureTransition = CompletableFuture.completedFuture(null); private Future<?> pollScheduledFutureTransition = CompletableFuture.completedFuture(null);
private Instant lastTransition; private volatile Instant lastTransition;
private String lastTransitionText; private volatile String lastTransitionText;
private OpenGarageConfiguration config = new OpenGarageConfiguration(); private OpenGarageConfiguration config = new OpenGarageConfiguration();
private Gson gson = new Gson(); private Gson gson = new Gson();
public OpenGarageHandler(Thing thing) { public OpenGarageHandler(Thing thing, HttpClient httpClient) {
super(thing); super(thing);
this.httpClient = httpClient;
this.lastTransition = Instant.MIN; this.lastTransition = Instant.MIN;
this.lastTransitionText = ""; this.lastTransitionText = "";
} }
@@ -81,10 +84,10 @@ public class OpenGarageHandler extends BaseThingHandler {
logger.debug("Received command {} for thing '{}' on channel {}", command, thing.getUID().getAsString(), logger.debug("Received command {} for thing '{}' on channel {}", command, thing.getUID().getAsString(),
channelUID.getId()); channelUID.getId());
Function<Boolean, Boolean> maybeInvert = getInverter(channelUID.getId()); Function<Boolean, Boolean> maybeInvert = getInverter(channelUID.getId());
switch (channelUID.getId()) { switch (channelUID.getId()) {
case OpenGarageBindingConstants.CHANNEL_OG_STATUS: case OpenGarageBindingConstants.CHANNEL_OG_STATUS, OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH,
case OpenGarageBindingConstants.CHANNEL_OG_STATUS_SWITCH: OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER -> {
case OpenGarageBindingConstants.CHANNEL_OG_STATUS_ROLLERSHUTTER:
if (command.equals(StopMoveType.STOP) || command.equals(StopMoveType.MOVE)) { if (command.equals(StopMoveType.STOP) || command.equals(StopMoveType.MOVE)) {
changeStatus(OpenGarageCommand.CLICK); changeStatus(OpenGarageCommand.CLICK);
} else { } else {
@@ -99,8 +102,9 @@ public class OpenGarageHandler extends BaseThingHandler {
this.pollScheduledFutureTransition = this.scheduler.schedule(this::poll, this.pollScheduledFutureTransition = this.scheduler.schedule(this::poll,
this.config.doorTransitionTimeSeconds, TimeUnit.SECONDS); this.config.doorTransitionTimeSeconds, TimeUnit.SECONDS);
} }
break; }
default: default -> {
}
} }
} catch (OpenGarageCommunicationException ex) { } catch (OpenGarageCommunicationException ex) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
@@ -116,7 +120,8 @@ public class OpenGarageHandler extends BaseThingHandler {
} else { } else {
updateStatus(ThingStatus.UNKNOWN); updateStatus(ThingStatus.UNKNOWN);
int requestTimeout = Math.max(OpenGarageWebTargets.DEFAULT_TIMEOUT_MS, config.refresh * 1000); int requestTimeout = Math.max(OpenGarageWebTargets.DEFAULT_TIMEOUT_MS, config.refresh * 1000);
webTargets = new OpenGarageWebTargets(config.hostname, config.port, config.password, requestTimeout); webTargets = new OpenGarageWebTargets(httpClient, config.hostname, config.port, config.password,
requestTimeout);
this.pollScheduledFuture = this.scheduler.scheduleWithFixedDelay(this::poll, 1, config.refresh, this.pollScheduledFuture = this.scheduler.scheduleWithFixedDelay(this::poll, 1, config.refresh,
TimeUnit.SECONDS); TimeUnit.SECONDS);
} }
@@ -172,24 +177,15 @@ public class OpenGarageHandler extends BaseThingHandler {
} }
switch (controllerVariables.vehicle) { switch (controllerVariables.vehicle) {
case 0: case 0 -> updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("No vehicle detected"));
new StringType("No vehicle detected")); case 1 ->
break;
case 1:
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("Vehicle detected")); updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, new StringType("Vehicle detected"));
break; case 2 -> updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
case 2: new StringType("Vehicle status unknown"));
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE, case 3 -> updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
new StringType("Vehicle status unknown")); new StringType("Vehicle status not available"));
break; default -> logger.debug("Received unknown vehicle value: {}", controllerVariables.vehicle);
case 3:
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE,
new StringType("Vehicle status not available"));
break;
default:
logger.debug("Received unknown vehicle value: {}", controllerVariables.vehicle);
} }
updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE_STATUS, updateState(OpenGarageBindingConstants.CHANNEL_OG_VEHICLE_STATUS,
new DecimalType(controllerVariables.vehicle)); new DecimalType(controllerVariables.vehicle));
@@ -14,12 +14,15 @@ package org.openhab.binding.opengarage.internal;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/** /**
* The {@link OpenGarageHandlerFactory} is responsible for creating things and thing * The {@link OpenGarageHandlerFactory} is responsible for creating things and thing
@@ -31,6 +34,13 @@ import org.osgi.service.component.annotations.Component;
@NonNullByDefault @NonNullByDefault
public class OpenGarageHandlerFactory extends BaseThingHandlerFactory { public class OpenGarageHandlerFactory extends BaseThingHandlerFactory {
private final HttpClientFactory httpClientFactory;
@Activate
public OpenGarageHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
this.httpClientFactory = httpClientFactory;
}
@Override @Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) { public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return OpenGarageBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); return OpenGarageBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
@@ -41,7 +51,7 @@ public class OpenGarageHandlerFactory extends BaseThingHandlerFactory {
ThingTypeUID thingTypeUID = thing.getThingTypeUID(); ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(OpenGarageBindingConstants.OPENGARAGE_THING)) { if (thingTypeUID.equals(OpenGarageBindingConstants.OPENGARAGE_THING)) {
return new OpenGarageHandler(thing); return new OpenGarageHandler(thing, httpClientFactory.getCommonHttpClient());
} }
return null; return null;
@@ -12,11 +12,16 @@
*/ */
package org.openhab.binding.opengarage.internal; package org.openhab.binding.opengarage.internal;
import java.io.IOException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.opengarage.internal.api.Enums.OpenGarageCommand; import org.openhab.binding.opengarage.internal.api.Enums.OpenGarageCommand;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -34,8 +39,10 @@ public class OpenGarageWebTargets {
private String changeControllerVariablesUri; private String changeControllerVariablesUri;
private final Logger logger = LoggerFactory.getLogger(OpenGarageWebTargets.class); private final Logger logger = LoggerFactory.getLogger(OpenGarageWebTargets.class);
private int timeoutMs; private int timeoutMs;
private final HttpClient httpClient;
public OpenGarageWebTargets(String ipAddress, long port, String password, int timeoutMs) { public OpenGarageWebTargets(HttpClient httpClient, String ipAddress, long port, String password, int timeoutMs) {
this.httpClient = httpClient;
String baseUri = "http://" + ipAddress + ":" + port + "/"; String baseUri = "http://" + ipAddress + ":" + port + "/";
this.timeoutMs = timeoutMs; this.timeoutMs = timeoutMs;
this.getControllerVariablesUri = baseUri + "jc"; this.getControllerVariablesUri = baseUri + "jc";
@@ -48,21 +55,14 @@ public class OpenGarageWebTargets {
public void setControllerVariables(OpenGarageCommand request) throws OpenGarageCommunicationException { public void setControllerVariables(OpenGarageCommand request) throws OpenGarageCommunicationException {
logger.debug("Received request: {}", request); logger.debug("Received request: {}", request);
String queryParams = null;
switch (request) { String queryParams = switch (request) {
case OPEN: case OPEN -> "&open=1";
queryParams = "&open=1"; case CLOSE -> "&close=1";
break; case CLICK -> "&click=1";
case CLOSE: };
queryParams = "&close=1";
break; invoke(changeControllerVariablesUri, queryParams);
case CLICK:
queryParams = "&click=1";
break;
}
if (queryParams != null) {
invoke(changeControllerVariablesUri, queryParams);
}
} }
private String invoke(String uri) throws OpenGarageCommunicationException { private String invoke(String uri) throws OpenGarageCommunicationException {
@@ -73,21 +73,30 @@ public class OpenGarageWebTargets {
String uriWithParams = uri + params; String uriWithParams = uri + params;
logger.debug("Calling url: {}", uriWithParams); logger.debug("Calling url: {}", uriWithParams);
String response; String response;
synchronized (this) { synchronized (this) {
try { try {
response = HttpUtil.executeUrl("GET", uriWithParams, this.timeoutMs); ContentResponse contentResponse = httpClient.newRequest(uriWithParams).method(HttpMethod.GET)
} catch (IOException ex) { .timeout(this.timeoutMs, TimeUnit.MILLISECONDS).send();
if (contentResponse.getStatus() != HttpStatus.OK_200) {
throw new OpenGarageCommunicationException(
String.format("OpenGarage controller returned HTTP %d while invoking %s",
contentResponse.getStatus(), uriWithParams));
}
response = contentResponse.getContentAsString();
} catch (InterruptedException | ExecutionException | TimeoutException ex) {
logger.debug("{}", ex.getLocalizedMessage(), ex); logger.debug("{}", ex.getLocalizedMessage(), ex);
// Response will also be set to null if parsing in executeUrl fails so we use null here to make the if (ex instanceof InterruptedException) {
// error check below consistent. Thread.currentThread().interrupt();
response = null; }
throw new OpenGarageCommunicationException(
String.format("OpenGarage controller returned error while invoking %s", uriWithParams), ex);
} }
} }
if (response == null) {
throw new OpenGarageCommunicationException(
String.format("OpenGaragecontroller returned error while invoking %s", uriWithParams));
}
return response; return response;
} }
} }