[boschshc] Code Enhancements (#14667)
* [boschshc] Code Enhancements This commit fixes several compiler warnings as well as several Checkstyle, FindBugs, PMD and Sonar issues. * instantiate loggers consistently * add missing logger calls * add chained exceptions to logger calls * provide central Gson instance that does not serialize or deserialize loggers * avoid catching NullPointerExceptions * extract methods where cyclomatic complexity was too high * remove unnecessary null check in combination with instanceof * rename local variables that shadow fields * add missing @NonNull and @Nullable annotations Signed-off-by: David Pace <dev@davidpace.de>
This commit is contained in:
parent
b6004a77de
commit
ed20ebb41b
bundles/org.openhab.binding.boschshc/src
main/java/org/openhab/binding/boschshc/internal
devices
discovery
serialization
services
BoschSHCSystemService.java
dto
humiditylevel/dto
roomclimatecontrol/dto
smokedetectorcheck
temperaturelevel/dto
test/java/org/openhab/binding/boschshc/internal
devices/bridge
services
@ -21,6 +21,8 @@ import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler for physical Bosch devices with configurable IDs (as opposed to system services, which have static IDs).
|
||||
@ -42,12 +44,14 @@ import org.openhab.core.thing.ThingStatusDetail;
|
||||
@NonNullByDefault
|
||||
public abstract class BoschSHCDeviceHandler extends BoschSHCHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/**
|
||||
* Bosch SHC configuration loaded from openHAB configuration.
|
||||
*/
|
||||
private @Nullable BoschSHCConfiguration config;
|
||||
|
||||
public BoschSHCDeviceHandler(Thing thing) {
|
||||
protected BoschSHCDeviceHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,6 @@ import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
@ -80,12 +79,7 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
|
||||
public final Collection<String> affectedChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable gson instance to convert a class to json string and back in derived classes.
|
||||
*/
|
||||
protected static final Gson GSON = new Gson();
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/**
|
||||
* Services of the device.
|
||||
@ -450,7 +444,7 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
|
||||
*/
|
||||
private <TState extends BoschSHCServiceState> void registerService(BoschSHCService<TState> service,
|
||||
Collection<String> affectedChannels) {
|
||||
this.services.add(new DeviceService<TState>(service, affectedChannels));
|
||||
this.services.add(new DeviceService<>(service, affectedChannels));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,10 +37,10 @@ import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
@ -51,9 +51,8 @@ import com.google.gson.JsonSyntaxException;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschHttpClient extends HttpClient {
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BoschHttpClient.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final String ipAddress;
|
||||
private final String systemPassword;
|
||||
@ -177,8 +176,10 @@ public class BoschHttpClient extends HttpClient {
|
||||
logger.debug("Online check failed with status code: {}", contentResponse.getStatus());
|
||||
return false;
|
||||
}
|
||||
} catch (TimeoutException | ExecutionException | NullPointerException e) {
|
||||
logger.debug("Online check failed because of {}!", e.getMessage());
|
||||
} catch (InterruptedException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.debug("Online check failed because of {}!", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -203,8 +204,10 @@ public class BoschHttpClient extends HttpClient {
|
||||
logger.debug("Access check failed with status code: {}", contentResponse.getStatus());
|
||||
return false;
|
||||
}
|
||||
} catch (TimeoutException | ExecutionException | NullPointerException e) {
|
||||
logger.debug("Access check failed because of {}!", e.getMessage());
|
||||
} catch (InterruptedException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.debug("Access check failed because of {}!", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -249,8 +252,8 @@ public class BoschHttpClient extends HttpClient {
|
||||
logger.info("Pairing failed with response status {}.", contentResponse.getStatus());
|
||||
return false;
|
||||
}
|
||||
} catch (TimeoutException | CertificateEncodingException | KeyStoreException | NullPointerException e) {
|
||||
logger.warn("Pairing failed with exception {}", e.getMessage());
|
||||
} catch (TimeoutException | CertificateEncodingException | KeyStoreException | RuntimeException e) {
|
||||
logger.warn("Pairing failed with exception {}", e.getMessage(), e);
|
||||
return false;
|
||||
} catch (ExecutionException e) {
|
||||
// javax.net.ssl.SSLHandshakeException: General SSLEngine problem
|
||||
@ -281,15 +284,15 @@ public class BoschHttpClient extends HttpClient {
|
||||
* @return created HTTP request instance
|
||||
*/
|
||||
public Request createRequest(String url, HttpMethod method, @Nullable Object content) {
|
||||
logger.trace("Create request for http client {}", this.toString());
|
||||
logger.trace("Create request for http client {}", this);
|
||||
|
||||
Request request = this.newRequest(url).method(method).header("Content-Type", "application/json")
|
||||
.header("api-version", "2.1") // see https://github.com/BoschSmartHome/bosch-shc-api-docs/issues/46
|
||||
.timeout(10, TimeUnit.SECONDS); // Set default timeout
|
||||
|
||||
if (content != null) {
|
||||
String body = GSON.toJson(content);
|
||||
logger.trace("create request for {} and content {}", url, content.toString());
|
||||
String body = GsonUtils.DEFAULT_GSON_INSTANCE.toJson(content);
|
||||
logger.trace("create request for {} and content {}", url, content);
|
||||
request = request.content(new StringContentProvider(body));
|
||||
} else {
|
||||
logger.trace("create request for {}", url);
|
||||
@ -314,7 +317,7 @@ public class BoschHttpClient extends HttpClient {
|
||||
Predicate<TContent> contentValidator,
|
||||
@Nullable BiFunction<Integer, String, BoschSHCException> errorResponseHandler)
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
logger.trace("Send request: {}", request.toString());
|
||||
logger.trace("Send request: {}", request);
|
||||
|
||||
ContentResponse contentResponse = request.send();
|
||||
|
||||
@ -334,7 +337,7 @@ public class BoschHttpClient extends HttpClient {
|
||||
|
||||
try {
|
||||
@Nullable
|
||||
TContent content = GSON.fromJson(textContent, responseContentClass);
|
||||
TContent content = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(textContent, responseContentClass);
|
||||
if (content == null) {
|
||||
throw new ExecutionException(String.format("Received no content in response, expected type %s",
|
||||
responseContentClass.getName()), null);
|
||||
|
@ -42,6 +42,7 @@ import org.openhab.binding.boschshc.internal.discovery.ThingDiscoveryService;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.JsonRestExceptionResponse;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@ -58,7 +59,6 @@ import org.osgi.framework.FrameworkUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@ -76,11 +76,6 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BridgeHandler.class);
|
||||
|
||||
/**
|
||||
* gson instance to convert a class to json string and back.
|
||||
*/
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Handler to do long polling.
|
||||
*/
|
||||
@ -143,6 +138,7 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
// prepare SSL key and certificates
|
||||
factory = new BoschSslUtil(ipAddress).getSslContextFactory();
|
||||
} catch (PairingFailedException e) {
|
||||
logger.debug("Error while obtaining SSL context factory.", e);
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error-ssl");
|
||||
return;
|
||||
@ -187,7 +183,7 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
try {
|
||||
httpClient.stop();
|
||||
} catch (Exception e) {
|
||||
logger.debug("HttpClient failed on bridge disposal: {}", e.getMessage());
|
||||
logger.debug("HttpClient failed on bridge disposal: {}", e.getMessage(), e);
|
||||
}
|
||||
this.httpClient = null;
|
||||
}
|
||||
@ -197,6 +193,7 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// commands are handled by individual device handlers
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,11 +259,7 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
// start long polling loop
|
||||
this.updateStatus(ThingStatus.ONLINE);
|
||||
try {
|
||||
this.longPolling.start(httpClient);
|
||||
} catch (LongPollingFailedException e) {
|
||||
this.handleLongPollFailure(e);
|
||||
}
|
||||
startLongPolling(httpClient);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE, "@text/offline.interrupted");
|
||||
@ -274,6 +267,14 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void startLongPolling(BoschHttpClient httpClient) {
|
||||
try {
|
||||
this.longPolling.start(httpClient);
|
||||
} catch (LongPollingFailedException e) {
|
||||
this.handleLongPollFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the bridge access by sending an HTTP request.
|
||||
* Does not throw any exception in case the request fails.
|
||||
@ -334,11 +335,10 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
Type collectionType = new TypeToken<ArrayList<Device>>() {
|
||||
}.getType();
|
||||
@Nullable
|
||||
List<Device> nullableDevices = gson.fromJson(content, collectionType);
|
||||
List<Device> nullableDevices = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, collectionType);
|
||||
return Optional.ofNullable(nullableDevices).orElse(Collections.emptyList());
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
logger.debug("Request devices failed because of {}!", e.getMessage());
|
||||
logger.debug("Request devices failed because of {}!", e.getMessage(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@ -371,7 +371,7 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
Type collectionType = new TypeToken<ArrayList<Room>>() {
|
||||
}.getType();
|
||||
|
||||
ArrayList<Room> rooms = gson.fromJson(content, collectionType);
|
||||
ArrayList<Room> rooms = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, collectionType);
|
||||
return Objects.requireNonNullElse(rooms, emptyRooms);
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
logger.debug("Request rooms failed because of {}!", e.getMessage());
|
||||
@ -452,7 +452,7 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
// the battery level service receives no individual state object but rather requires the DeviceServiceData
|
||||
// structure
|
||||
if ("BatteryLevel".equals(deviceServiceData.id)) {
|
||||
return gson.toJsonTree(deviceServiceData);
|
||||
return GsonUtils.DEFAULT_GSON_INSTANCE.toJsonTree(deviceServiceData);
|
||||
}
|
||||
|
||||
return deviceServiceData.state;
|
||||
@ -473,8 +473,7 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
// All children of this should implement BoschSHCHandler
|
||||
@Nullable
|
||||
ThingHandler baseHandler = childThing.getHandler();
|
||||
if (baseHandler != null && baseHandler instanceof BoschSHCHandler) {
|
||||
BoschSHCHandler handler = (BoschSHCHandler) baseHandler;
|
||||
if (baseHandler instanceof BoschSHCHandler handler) {
|
||||
@Nullable
|
||||
String deviceId = handler.getBoschID();
|
||||
|
||||
@ -530,7 +529,8 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
Request request = httpClient.createRequest(url, GET);
|
||||
|
||||
return httpClient.sendRequest(request, Device.class, Device::isValid, (Integer statusCode, String content) -> {
|
||||
JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class);
|
||||
JsonRestExceptionResponse errorResponse = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content,
|
||||
JsonRestExceptionResponse.class);
|
||||
if (errorResponse != null && JsonRestExceptionResponse.isValid(errorResponse)) {
|
||||
if (errorResponse.errorCode.equals(JsonRestExceptionResponse.ENTITY_NOT_FOUND)) {
|
||||
return new BoschSHCException("@text/offline.conf-error.invalid-device-id");
|
||||
@ -628,7 +628,8 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
int statusCode = contentResponse.getStatus();
|
||||
if (statusCode != 200) {
|
||||
JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class);
|
||||
JsonRestExceptionResponse errorResponse = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content,
|
||||
JsonRestExceptionResponse.class);
|
||||
if (errorResponse != null) {
|
||||
throw new BoschSHCException(
|
||||
String.format("State request with URL %s failed with status code %d and error code %s", url,
|
||||
|
@ -30,11 +30,10 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Handles the long polling to the Smart Home Controller.
|
||||
*
|
||||
@ -45,11 +44,6 @@ public class LongPolling {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LongPolling.class);
|
||||
|
||||
/**
|
||||
* gson instance to convert a class to json string and back.
|
||||
*/
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Executor to schedule long polls.
|
||||
*/
|
||||
@ -107,17 +101,15 @@ public class LongPolling {
|
||||
private String subscribe(BoschHttpClient httpClient) throws LongPollingFailedException {
|
||||
try {
|
||||
String url = httpClient.getBoschShcUrl("remote/json-rpc");
|
||||
JsonRpcRequest request = new JsonRpcRequest("2.0", "RE/subscribe",
|
||||
JsonRpcRequest subscriptionRequest = new JsonRpcRequest("2.0", "RE/subscribe",
|
||||
new String[] { "com/bosch/sh/remote/*", null });
|
||||
logger.debug("Subscribe: Sending request: {} - using httpClient {}", request.toString(),
|
||||
httpClient.toString());
|
||||
Request httpRequest = httpClient.createRequest(url, POST, request);
|
||||
logger.debug("Subscribe: Sending request: {} - using httpClient {}", subscriptionRequest, httpClient);
|
||||
Request httpRequest = httpClient.createRequest(url, POST, subscriptionRequest);
|
||||
SubscribeResult response = httpClient.sendRequest(httpRequest, SubscribeResult.class,
|
||||
SubscribeResult::isValid, null);
|
||||
|
||||
logger.debug("Subscribe: Got subscription ID: {} {}", response.getResult(), response.getJsonrpc());
|
||||
String subscriptionId = response.getResult();
|
||||
return subscriptionId;
|
||||
return response.getResult();
|
||||
} catch (TimeoutException | ExecutionException | BoschSHCException e) {
|
||||
throw new LongPollingFailedException(
|
||||
String.format("Error on subscribe (Http client: %s): %s", httpClient.toString(), e.getMessage()),
|
||||
@ -141,7 +133,6 @@ public class LongPolling {
|
||||
this.executeLongPoll(httpClient, subscriptionId);
|
||||
} catch (LongPollingFailedException e) {
|
||||
this.handleFailure.accept(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,15 +148,15 @@ public class LongPolling {
|
||||
|
||||
JsonRpcRequest requestContent = new JsonRpcRequest("2.0", "RE/longPoll", new String[] { subscriptionId, "20" });
|
||||
String url = httpClient.getBoschShcUrl("remote/json-rpc");
|
||||
Request request = httpClient.createRequest(url, POST, requestContent);
|
||||
Request longPollRequest = httpClient.createRequest(url, POST, requestContent);
|
||||
|
||||
// Long polling responds after 20 seconds with an empty response if no update has happened.
|
||||
// 10 second threshold was added to not time out if response from controller takes a bit longer than 20 seconds.
|
||||
request.timeout(30, TimeUnit.SECONDS);
|
||||
longPollRequest.timeout(30, TimeUnit.SECONDS);
|
||||
|
||||
this.request = request;
|
||||
this.request = longPollRequest;
|
||||
LongPolling longPolling = this;
|
||||
request.send(new BufferingResponseListener() {
|
||||
longPollRequest.send(new BufferingResponseListener() {
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
// NOTE: This handler runs inside the HTTP thread, so we schedule the response handling in a new thread
|
||||
@ -195,31 +186,20 @@ public class LongPolling {
|
||||
// Check if response was failure or success
|
||||
Throwable failure = result != null ? result.getFailure() : null;
|
||||
if (failure != null) {
|
||||
if (failure instanceof ExecutionException) {
|
||||
if (failure.getCause() instanceof AbortLongPolling) {
|
||||
logger.debug("Canceling long polling for subscription id {} because it was aborted",
|
||||
subscriptionId);
|
||||
} else {
|
||||
this.handleFailure.accept(new LongPollingFailedException(
|
||||
"Unexpected exception during long polling request", failure));
|
||||
}
|
||||
} else {
|
||||
this.handleFailure.accept(
|
||||
new LongPollingFailedException("Unexpected exception during long polling request", failure));
|
||||
}
|
||||
handleLongPollFailure(subscriptionId, failure);
|
||||
} else {
|
||||
logger.debug("Long poll response: {}", content);
|
||||
|
||||
String nextSubscriptionId = subscriptionId;
|
||||
|
||||
LongPollResult longPollResult = gson.fromJson(content, LongPollResult.class);
|
||||
LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, LongPollResult.class);
|
||||
if (longPollResult != null && longPollResult.result != null) {
|
||||
this.handleResult.accept(longPollResult);
|
||||
} else {
|
||||
logger.debug("Long poll response contained no result: {}", content);
|
||||
|
||||
// Check if we got a proper result from the SHC
|
||||
LongPollError longPollError = gson.fromJson(content, LongPollError.class);
|
||||
LongPollError longPollError = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, LongPollError.class);
|
||||
|
||||
if (longPollError != null && longPollError.error != null) {
|
||||
logger.debug("Got long poll error: {} (code: {})", longPollError.error.message,
|
||||
@ -238,6 +218,20 @@ public class LongPolling {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLongPollFailure(String subscriptionId, Throwable failure) {
|
||||
if (failure instanceof ExecutionException) {
|
||||
if (failure.getCause() instanceof AbortLongPolling) {
|
||||
logger.debug("Canceling long polling for subscription id {} because it was aborted", subscriptionId);
|
||||
} else {
|
||||
this.handleFailure.accept(
|
||||
new LongPollingFailedException("Unexpected exception during long polling request", failure));
|
||||
}
|
||||
} else {
|
||||
this.handleFailure.accept(
|
||||
new LongPollingFailedException("Unexpected exception during long polling request", failure));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private class AbortLongPolling extends BoschSHCException {
|
||||
}
|
||||
|
@ -12,8 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.climatecontrol;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SETPOINT_TEMPERATURE;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -29,20 +28,24 @@ import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A virtual device which controls up to six Bosch Smart Home radiator thermostats in a room.
|
||||
*
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class ClimateControlHandler extends BoschSHCDeviceHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private RoomClimateControlService roomClimateControlService;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param thing The Bosch Smart Home device that should be handled.
|
||||
*/
|
||||
public ClimateControlHandler(Thing thing) {
|
||||
@ -71,7 +74,7 @@ public final class ClimateControlHandler extends BoschSHCDeviceHandler {
|
||||
|
||||
/**
|
||||
* Updates the channels which are linked to the {@link TemperatureLevelService} of the device.
|
||||
*
|
||||
*
|
||||
* @param state Current state of {@link TemperatureLevelService}.
|
||||
*/
|
||||
private void updateChannels(TemperatureLevelServiceState state) {
|
||||
@ -80,7 +83,7 @@ public final class ClimateControlHandler extends BoschSHCDeviceHandler {
|
||||
|
||||
/**
|
||||
* Updates the channels which are linked to the {@link RoomClimateControlService} of the device.
|
||||
*
|
||||
*
|
||||
* @param state Current state of {@link RoomClimateControlService}.
|
||||
*/
|
||||
private void updateChannels(RoomClimateControlServiceState state) {
|
||||
@ -89,7 +92,7 @@ public final class ClimateControlHandler extends BoschSHCDeviceHandler {
|
||||
|
||||
/**
|
||||
* Sets the desired temperature for the device.
|
||||
*
|
||||
*
|
||||
* @param quantityType Command which contains the new desired temperature.
|
||||
*/
|
||||
private void updateSetpointTemperature(QuantityType<?> quantityType) {
|
||||
|
@ -28,10 +28,12 @@ import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Control of your shutter to take any position you desire.
|
||||
*
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@ -49,6 +51,8 @@ public class ShutterControlHandler extends BoschSHCDeviceHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private ShutterControlService shutterControlService;
|
||||
|
||||
public ShutterControlHandler(Thing thing) {
|
||||
|
@ -12,7 +12,11 @@
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.discovery;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -139,9 +143,9 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements T
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof BridgeHandler) {
|
||||
if (handler instanceof BridgeHandler bridgeHandler) {
|
||||
logger.trace("Set bridge handler {}", handler);
|
||||
shcBridgeHandler = (BridgeHandler) handler;
|
||||
shcBridgeHandler = bridgeHandler;
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,8 +219,9 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements T
|
||||
}
|
||||
|
||||
private String getNiceName(String name, String roomName) {
|
||||
if (!name.startsWith("-"))
|
||||
if (!name.startsWith("-")) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// convert "-IntrusionDetectionSystem-" into "Intrusion Detection System"
|
||||
// convert "-RoomClimateControl-" into "Room Climate Control myRoomName"
|
||||
|
40
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java
Normal file
40
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.boschshc.internal.serialization;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* Utilities for JSON serialization and deserialization using Google Gson.
|
||||
*
|
||||
* @author David Pace - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class GsonUtils {
|
||||
private GsonUtils() {
|
||||
// Utility Class
|
||||
}
|
||||
|
||||
/**
|
||||
* The default Gson instance to be used for serialization and deserialization.
|
||||
* <p>
|
||||
* This instance does not serialize or deserialize fields named <code>logger</code>.
|
||||
*/
|
||||
public static final Gson DEFAULT_GSON_INSTANCE = new GsonBuilder()
|
||||
.addSerializationExclusionStrategy(new LoggerExclusionStrategy())
|
||||
.addDeserializationExclusionStrategy(new LoggerExclusionStrategy()).create();
|
||||
}
|
38
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/LoggerExclusionStrategy.java
Normal file
38
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/LoggerExclusionStrategy.java
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.boschshc.internal.serialization;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
|
||||
/**
|
||||
* A GSON exclusion strategy that prevents loggers from being serialized and deserialized.
|
||||
*
|
||||
* @author David Pace - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LoggerExclusionStrategy implements ExclusionStrategy {
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipField(@NonNullByDefault({}) FieldAttributes f) {
|
||||
return "logger".equalsIgnoreCase(f.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(@NonNullByDefault({}) Class<?> clazz) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -27,23 +27,23 @@ import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
* <p>
|
||||
* The endpoints to retrieve system states are different from the ones for physical devices, i.e. they do not follow the
|
||||
* pattern
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* Instead, system services have endpoints like
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* /intrusion/states/system
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* The services of the devices and their official APIs can be found
|
||||
* <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
|
||||
*
|
||||
* @param <TState> type used for representing the service state
|
||||
*
|
||||
*
|
||||
* @author David Pace - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@ -53,7 +53,7 @@ public abstract class BoschSHCSystemService<TState extends BoschSHCServiceState>
|
||||
|
||||
/**
|
||||
* Constructs a system service instance.
|
||||
*
|
||||
*
|
||||
* @param serviceName name of the service, such as <code>intrusionDetectionService</code>
|
||||
* @param stateClass the class representing states of the system
|
||||
* @param endpoint the part of the URL after <code>https://{IP}:8444/smarthome/</code>, e.g.
|
||||
|
@ -13,10 +13,10 @@
|
||||
package org.openhab.binding.boschshc.internal.services.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@ -27,15 +27,7 @@ import com.google.gson.annotations.SerializedName;
|
||||
*/
|
||||
public class BoschSHCServiceState {
|
||||
|
||||
/**
|
||||
* gson instance to convert a class to json string and back.
|
||||
*/
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
/**
|
||||
* Logger marked as transient to exclude the logger from JSON serialization.
|
||||
*/
|
||||
private final transient Logger logger = LoggerFactory.getLogger(BoschSHCServiceState.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/**
|
||||
* State type. Initialized when instance is created.
|
||||
@ -70,7 +62,7 @@ public class BoschSHCServiceState {
|
||||
|
||||
public static <TState extends BoschSHCServiceState> @Nullable TState fromJson(String json,
|
||||
Class<TState> stateClass) {
|
||||
var state = GSON.fromJson(json, stateClass);
|
||||
var state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass);
|
||||
if (state == null || !state.isValid()) {
|
||||
return null;
|
||||
}
|
||||
@ -80,7 +72,7 @@ public class BoschSHCServiceState {
|
||||
|
||||
public static <TState extends BoschSHCServiceState> @Nullable TState fromJson(JsonElement json,
|
||||
Class<TState> stateClass) {
|
||||
var state = GSON.fromJson(json, stateClass);
|
||||
var state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass);
|
||||
if (state == null || !state.isValid()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -14,7 +14,9 @@ package org.openhab.binding.boschshc.internal.services.humiditylevel.dto;
|
||||
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.humiditylevel.HumidityLevelService;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.types.State;
|
||||
@ -36,6 +38,6 @@ public class HumidityLevelServiceState extends BoschSHCServiceState {
|
||||
public double humidity;
|
||||
|
||||
public State getHumidityState() {
|
||||
return new QuantityType<Dimensionless>(this.humidity, Units.PERCENT);
|
||||
return new QuantityType<@NonNull Dimensionless>(this.humidity, Units.PERCENT);
|
||||
}
|
||||
}
|
||||
|
@ -14,37 +14,39 @@ package org.openhab.binding.boschshc.internal.services.roomclimatecontrol.dto;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.RoomClimateControlService;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* State for {@link RoomClimateControlService} to get and set the desired temperature of a room.
|
||||
*
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
public class RoomClimateControlServiceState extends BoschSHCServiceState {
|
||||
|
||||
private static final String TYPE = "climateControlState";
|
||||
private static final String CLIMATE_CONTROL_STATE_TYPE = "climateControlState";
|
||||
|
||||
public RoomClimateControlServiceState() {
|
||||
super(TYPE);
|
||||
super(CLIMATE_CONTROL_STATE_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param setpointTemperature Desired temperature (in degree celsius).
|
||||
*/
|
||||
public RoomClimateControlServiceState(double setpointTemperature) {
|
||||
super(TYPE);
|
||||
super(CLIMATE_CONTROL_STATE_TYPE);
|
||||
this.setpointTemperature = setpointTemperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Desired temperature (in degree celsius).
|
||||
*
|
||||
*
|
||||
* @apiNote Min: 5.0, Max: 30.0.
|
||||
* @apiNote Can be set in 0.5 steps.
|
||||
*/
|
||||
@ -52,10 +54,10 @@ public class RoomClimateControlServiceState extends BoschSHCServiceState {
|
||||
|
||||
/**
|
||||
* Desired temperature state to set for a thing.
|
||||
*
|
||||
*
|
||||
* @return Desired temperature state to set for a thing.
|
||||
*/
|
||||
public State getSetpointTemperatureState() {
|
||||
return new QuantityType<Temperature>(this.setpointTemperature, SIUnits.CELSIUS);
|
||||
return new QuantityType<@NonNull Temperature>(this.setpointTemperature, SIUnits.CELSIUS);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Possible states for a smoke detector.
|
||||
*
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
|
@ -14,6 +14,7 @@ package org.openhab.binding.boschshc.internal.services.temperaturelevel.dto;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
@ -21,7 +22,7 @@ import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* TemperatureLevel service state.
|
||||
*
|
||||
*
|
||||
* @author Christian Oeing - Initial contribution
|
||||
*/
|
||||
public class TemperatureLevelServiceState extends BoschSHCServiceState {
|
||||
@ -37,10 +38,10 @@ public class TemperatureLevelServiceState extends BoschSHCServiceState {
|
||||
|
||||
/**
|
||||
* Current temperature state to set for a thing.
|
||||
*
|
||||
*
|
||||
* @return Current temperature state to use for a thing.
|
||||
*/
|
||||
public State getTemperatureState() {
|
||||
return new QuantityType<Temperature>(this.temperature, SIUnits.CELSIUS);
|
||||
return new QuantityType<@NonNull Temperature>(this.temperature, SIUnits.CELSIUS);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
@ -91,7 +92,7 @@ class BridgeHandlerTest {
|
||||
fixture.setCallback(thingHandlerCallback);
|
||||
|
||||
Configuration bridgeConfiguration = new Configuration();
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
Map<@Nullable String, @Nullable Object> properties = new HashMap<>();
|
||||
properties.put("ipAddress", "localhost");
|
||||
properties.put("password", "test");
|
||||
bridgeConfiguration.setProperties(properties);
|
||||
|
@ -16,8 +16,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for LongPollResult
|
||||
@ -26,11 +25,10 @@ import com.google.gson.Gson;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LongPollResultTest {
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Test
|
||||
void noResultsForErrorResult() {
|
||||
LongPollResult longPollResult = gson.fromJson(
|
||||
LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(
|
||||
"{\"jsonrpc\":\"2.0\", \"error\": { \"code\":-32001, \"message\":\"No subscription with id: e8fei62b0-0\" } }",
|
||||
LongPollResult.class);
|
||||
assertNotNull(longPollResult);
|
||||
|
@ -15,8 +15,10 @@ package org.openhab.binding.boschshc.internal.services.batterylevel;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Fault;
|
||||
@ -58,7 +60,7 @@ class BatteryLevelTest {
|
||||
deviceServiceData.faults = faults;
|
||||
assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData));
|
||||
|
||||
ArrayList<Fault> entries = new ArrayList<>();
|
||||
List<@Nullable Fault> entries = new ArrayList<>();
|
||||
faults.entries = entries;
|
||||
assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData));
|
||||
|
||||
|
@ -16,8 +16,8 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
@ -49,18 +49,19 @@ class TestState2 extends BoschSHCServiceState {
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BoschSHCServiceStateTest {
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Test
|
||||
public void fromJsonNullStateForDifferentType() {
|
||||
var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"differentState\"}", JsonObject.class),
|
||||
var state = BoschSHCServiceState.fromJson(
|
||||
GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"differentState\"}", JsonObject.class),
|
||||
TestState.class);
|
||||
assertEquals(null, state);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromJsonStateObjectForValidJson() {
|
||||
var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class),
|
||||
var state = BoschSHCServiceState.fromJson(
|
||||
GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"testState\"}", JsonObject.class),
|
||||
TestState.class);
|
||||
assertNotEquals(null, state);
|
||||
}
|
||||
@ -70,8 +71,11 @@ public class BoschSHCServiceStateTest {
|
||||
*/
|
||||
@Test
|
||||
public void fromJsonStateObjectForValidJsonAfterOtherState() {
|
||||
BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class), TestState.class);
|
||||
var state2 = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState2\"}", JsonObject.class),
|
||||
BoschSHCServiceState.fromJson(
|
||||
GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"testState\"}", JsonObject.class),
|
||||
TestState.class);
|
||||
var state2 = BoschSHCServiceState.fromJson(
|
||||
GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"testState2\"}", JsonObject.class),
|
||||
TestState2.class);
|
||||
assertNotEquals(null, state2);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user