diff --git a/bundles/org.openhab.binding.boschshc/README.md b/bundles/org.openhab.binding.boschshc/README.md index 0cb265319b87b..28b791ba7af81 100644 --- a/bundles/org.openhab.binding.boschshc/README.md +++ b/bundles/org.openhab.binding.boschshc/README.md @@ -3,14 +3,16 @@ Binding for the Bosch Smart Home. - [Bosch Smart Home Binding](#bosch-smart-home-binding) + - [Changelog](#changelog) - [Supported Things](#supported-things) - - [Bosch In-Wall switches & Bosch Smart Plugs](#bosch-in-wall-switches--bosch-smart-plugs) - - [Bosch TwinGuard smoke detector](#bosch-twinguard-smoke-detector) - - [Bosch Window/Door contacts](#bosch-windowdoor-contacts) - - [Bosch Motion Detector](#bosch-motion-detector) - - [Bosch Shutter Control in-wall](#bosch-shutter-control-in-wall) - - [Bosch Thermostat](#bosch-thermostat) - - [Bosch Climate Control](#bosch-climate-control) + - [In-Wall switches & Smart Plugs](#in-wall-switches--smart-plugs) + - [TwinGuard smoke detector](#twinguard-smoke-detector) + - [Door/Window contact](#doorwindow-contact) + - [Motion Detector](#motion-detector) + - [Shutter Control](#shutter-control) + - [Thermostat](#thermostat) + - [Climate Control](#climate-control) + - [Wall Thermostat](#wall-thermostat) - [Limitations](#limitations) - [Discovery](#discovery) - [Bridge Configuration](#bridge-configuration) @@ -20,72 +22,98 @@ Binding for the Bosch Smart Home. ## Supported Things -### Bosch In-Wall switches & Bosch Smart Plugs +### In-Wall switches & Smart Plugs + +A simple light control. **Thing Type ID**: `in-wall-switch` -| Channel Type ID | Item Type | Description | -|--------------------|---------------|----------------------------------------------| -| power-switch | Switch | Current state of the switch. | -| power-consumption | Number:Power | Current power consumption (W) of the device. | -| energy-consumption | Number:Energy | Energy consumption of the device. | +| Channel Type ID | Item Type | Writable | Description | +| ------------------ | ------------- | :------: | -------------------------------------------- | +| power-switch | Switch | ☑ | Current state of the switch. | +| power-consumption | Number:Power | ☐ | Current power consumption (W) of the device. | +| energy-consumption | Number:Energy | ☐ | Energy consumption of the device. | + +### TwinGuard smoke detector -### Bosch TwinGuard smoke detector +The Twinguard smoke detector warns you in case of fire and constantly monitors the air. **Thing Type ID**: `twinguard` -| Channel Type ID | Item Type | Description | -|--------------------|----------------------|---------------------------------------------------------------------------------------------------| -| temperature | Number:Temperature | Current measured temperature. | -| temperature-rating | String | Rating of the currently measured temperature. | -| humidity | Number:Dimensionless | Current measured humidity. | -| humidity-rating | String | Rating of current measured humidity. | -| purity | Number:Dimensionless | Purity of the air (ppm). Range from 500 to 5500 ppm. A higher value indicates a higher pollution. | -| purity-rating | String | Rating of current measured purity. | -| air-description | String | Overall description of the air quality. | -| combined-rating | String | Combined rating of the air quality. | +| Channel Type ID | Item Type | Writable | Description | +| ------------------ | -------------------- | :------: | ------------------------------------------------------------------------------------------------- | +| temperature | Number:Temperature | ☐ | Current measured temperature. | +| temperature-rating | String | ☐ | Rating of the currently measured temperature. | +| humidity | Number:Dimensionless | ☐ | Current measured humidity (0 to 100). | +| humidity-rating | String | ☐ | Rating of current measured humidity. | +| purity | Number:Dimensionless | ☐ | Purity of the air (ppm). Range from 500 to 5500 ppm. A higher value indicates a higher pollution. | +| purity-rating | String | ☐ | Rating of current measured purity. | +| air-description | String | ☐ | Overall description of the air quality. | +| combined-rating | String | ☐ | Combined rating of the air quality. | -### Bosch Window/Door contacts +### Door/Window contact + +Detects open windows and doors. **Thing Type ID**: `window-contact` -| Channel Type ID | Item Type | Description | -|-----------------|-----------|------------------------------| -| contact | Contact | Contact state of the device. | +| Channel Type ID | Item Type | Writable | Description | +| --------------- | --------- | :------: | ---------------------------- | +| contact | Contact | ☐ | Contact state of the device. | + +### Motion Detector -### Bosch Motion Detector +Detects every movement through an intelligent combination of passive infra-red technology and an additional temperature sensor. **Thing Type ID**: `motion-detector` -| Channel Type ID | Item Type | Description | -|-----------------|-----------|--------------------------------| -| latest-motion | DateTime | The date of the latest motion. | +| Channel Type ID | Item Type | Writable | Description | +| --------------- | --------- | :------: | ------------------------------ | +| latest-motion | DateTime | ☐ | The date of the latest motion. | -### Bosch Shutter Control in-wall +### Shutter Control + +Control of your shutter to take any position you desire. **Thing Type ID**: `shutter-control` -| Channel Type ID | Item Type | Description | -|-----------------|---------------|------------------------------------------| -| level | Rollershutter | Current open ratio (0 to 100, Step 0.5). | +| Channel Type ID | Item Type | Writable | Description | +| --------------- | ------------- | :------: | ---------------------------------------- | +| level | Rollershutter | ☑ | Current open ratio (0 to 100, Step 0.5). | + +### Thermostat -### Bosch Thermostat +Radiator thermostat **Thing Type ID**: `thermostat` -| Channel Type ID | Item Type | Description | -|-----------------------|----------------------|------------------------------------------------| -| temperature | Number:Temperature | Current measured temperature. | -| valve-tappet-position | Number:Dimensionless | Current open ratio of valve tappet (0 to 100). | +| Channel Type ID | Item Type | Writable | Description | +| --------------------- | -------------------- | :------: | ---------------------------------------------- | +| temperature | Number:Temperature | ☐ | Current measured temperature. | +| valve-tappet-position | Number:Dimensionless | ☐ | Current open ratio of valve tappet (0 to 100). | +| child-lock | Switch | ☑ | Indicates if child lock is active. | + +### Climate Control -### Bosch Climate Control +A virtual device which controls up to six Bosch Smart Home radiator thermostats in a room. **Thing Type ID**: `climate-control` -| Channel Type ID | Item Type | Description | -|----------------------|--------------------|-------------------------------| -| temperature | Number:Temperature | Current measured temperature. | -| setpoint-temperature | Number:Temperature | Desired temperature. | +| Channel Type ID | Item Type | Writable | Description | +| -------------------- | ------------------ | :------: | ----------------------------- | +| temperature | Number:Temperature | ☐ | Current measured temperature. | +| setpoint-temperature | Number:Temperature | ☑ | Desired temperature. | + +### Wall Thermostat + +Display of the current room temperature as well as the relative humidity in the room. + +**Thing Type ID**: `wall-thermostat` + +| Channel Type ID | Item Type | Writable | Description | +| --------------- | -------------------- | :------: | ------------------------------------- | +| temperature | Number:Temperature | ☐ | Current measured temperature. | +| humidity | Number:Dimensionless | ☐ | Current measured humidity (0 to 100). | ## Limitations @@ -112,9 +140,11 @@ This certificate is used for pairing between the Bridge and the Bosch Smart Home Bosch IDs for found devices are displayed in the openHAB log on bootup (`OPENHAB_FOLDER/userdata/logs/openhab.log`) The log can also be called using the following command. + ``` tail -f /var/log/openhab/openhab.log /var/log/openhab/events.log ``` + Alternatively, the log can be viewed using the OpenHab Log Viewer (frontail) via http://openhab:9001. Example: diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java index 47e85f9fcf46c..ab30a07499a57 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java @@ -21,6 +21,7 @@ * * @author Stefan Kästle - Initial contribution * @author Christian Oeing - added Shutter Control, ThermostatHandler + * @author Christian Oeing - Added WallThermostatHandler */ @NonNullByDefault public class BoschSHCBindingConstants { @@ -37,6 +38,7 @@ public class BoschSHCBindingConstants { public static final ThingTypeUID THING_TYPE_SHUTTER_CONTROL = new ThingTypeUID(BINDING_ID, "shutter-control"); public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat"); public static final ThingTypeUID THING_TYPE_CLIMATE_CONTROL = new ThingTypeUID(BINDING_ID, "climate-control"); + public static final ThingTypeUID THING_TYPE_WALL_THERMOSTAT = new ThingTypeUID(BINDING_ID, "wall-thermostat"); // List of all Channel IDs // Auto-generated from thing-types.xml via script, don't modify @@ -56,4 +58,5 @@ public class BoschSHCBindingConstants { public static final String CHANNEL_LEVEL = "level"; public static final String CHANNEL_VALVE_TAPPET_POSITION = "valve-tappet-position"; public static final String CHANNEL_SETPOINT_TEMPERATURE = "setpoint-temperature"; + public static final String CHANNEL_CHILD_LOCK = "child-lock"; } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java index 98e6d856effb0..0500f08394662 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java @@ -22,7 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler; +import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.services.BoschSHCService; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; @@ -117,16 +117,34 @@ public BoschSHCHandler(Thing thing) { */ @Override public void initialize() { - this.config = getConfigAs(BoschSHCConfiguration.class); + var config = this.config = getConfigAs(BoschSHCConfiguration.class); + String deviceId = config.id; + if (deviceId == null || deviceId.isEmpty()) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error.empty-device-id"); + return; + } + + // Try to get device info to make sure the device exists try { - this.initializeServices(); + var bridgeHandler = this.getBridgeHandler(); + var info = bridgeHandler.getDeviceInfo(deviceId); + logger.trace("Device initialized:\n{}", info.toString()); + } catch (InterruptedException | TimeoutException | ExecutionException | BoschSHCException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + return; + } - // Mark immediately as online - if the bridge is online, the thing is too. - this.updateStatus(ThingStatus.ONLINE); + // Initialize device services + try { + this.initializeServices(); } catch (BoschSHCException e) { this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + return; } + + this.updateStatus(ThingStatus.ONLINE); } /** @@ -142,18 +160,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { // Refresh state of services that affect the channel for (DeviceService deviceService : this.services) { if (deviceService.affectedChannels.contains(channelUID.getIdWithoutGroup())) { - try { - deviceService.service.refreshState(); - } catch (TimeoutException | ExecutionException | BoschSHCException e) { - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, - String.format("Error when trying to refresh state from service %s: %s", - deviceService.service.getServiceName(), e.getMessage())); - } catch (InterruptedException e) { - this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, - String.format("Interrupted refresh state from service %s: %s", - deviceService.service.getServiceName(), e.getMessage())); - Thread.currentThread().interrupt(); - } + this.refreshServiceState(deviceService.service); } } } @@ -187,14 +194,16 @@ protected void initializeServices() throws BoschSHCException { * @return Bridge handler for this thing handler. Null if no or an invalid bridge was set in the configuration. * @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set. */ - protected BoschSHCBridgeHandler getBridgeHandler() throws BoschSHCException { + protected BridgeHandler getBridgeHandler() throws BoschSHCException { Bridge bridge = this.getBridge(); if (bridge == null) { - throw new BoschSHCException(String.format("No valid bridge set for %s", this.getThing())); + throw new BoschSHCException(String.format("No valid bridge set for %s (%s)", this.getThing().getLabel(), + this.getThing().getUID().getAsString())); } - BoschSHCBridgeHandler bridgeHandler = (BoschSHCBridgeHandler) bridge.getHandler(); + BridgeHandler bridgeHandler = (BridgeHandler) bridge.getHandler(); if (bridgeHandler == null) { - throw new BoschSHCException(String.format("Bridge of %s has no valid bridge handler", this.getThing())); + throw new BoschSHCException(String.format("Bridge of %s (%s) has no valid bridge handler", + this.getThing().getLabel(), this.getThing().getUID().getAsString())); } return bridgeHandler; } @@ -213,7 +222,7 @@ protected BoschSHCBridgeHandler getBridgeHandler() throws BoschSHCException { return null; } try { - BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler(); + BridgeHandler bridgeHandler = this.getBridgeHandler(); return bridgeHandler.getState(deviceId, stateName, classOfT); } catch (TimeoutException | ExecutionException | BoschSHCException e) { this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, @@ -264,7 +273,7 @@ protected , TState extends BoschSHCServ protected , TState extends BoschSHCServiceState> void registerService( TService service, Consumer stateUpdateListener, Collection affectedChannels) throws BoschSHCException { - BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler(); + BridgeHandler bridgeHandler = this.getBridgeHandler(); String deviceId = this.getBoschID(); if (deviceId == null) { @@ -299,6 +308,54 @@ protected , TState extends BoschSHCServ } } + /** + * Lets a service handle a received command. + * Sets the status of the device to offline if handling the command fails. + * + * @param Type of service. + * @param Type of service state. + * @param service Service which should handle command. + * @param command Command to handle. + */ + protected , TState extends BoschSHCServiceState> void handleServiceCommand( + TService service, Command command) { + try { + if (command instanceof RefreshType) { + this.refreshServiceState(service); + } else { + TState state = service.handleCommand(command); + this.updateServiceState(service, state); + } + } catch (BoschSHCException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + String.format("Error when service %s should handle command %s: %s", service.getServiceName(), + command.getClass().getName(), e.getMessage())); + } + } + + /** + * Requests a service to refresh its state. + * Sets the device offline if request fails. + * + * @param Type of service. + * @param Type of service state. + * @param service Service to refresh state for. + */ + private , TState extends BoschSHCServiceState> void refreshServiceState( + TService service) { + try { + service.refreshState(); + } catch (TimeoutException | ExecutionException | BoschSHCException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + String.format("Error when trying to refresh state from service %s: %s", service.getServiceName(), + e.getMessage())); + } catch (InterruptedException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, String + .format("Interrupted refresh state from service %s: %s", service.getServiceName(), e.getMessage())); + Thread.currentThread().interrupt(); + } + } + /** * Registers a service of this device. * diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java index 19a1e94198250..556a4fff69f5c 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java @@ -12,14 +12,7 @@ */ package org.openhab.binding.boschshc.internal.devices; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_MOTION_DETECTOR; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_SHC; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_THERMOSTAT; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_TWINGUARD; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*; import java.util.Collection; import java.util.List; @@ -27,13 +20,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler; +import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler; import org.openhab.binding.boschshc.internal.devices.climatecontrol.ClimateControlHandler; -import org.openhab.binding.boschshc.internal.devices.inwallswitch.BoschInWallSwitchHandler; +import org.openhab.binding.boschshc.internal.devices.lightcontrol.LightControlHandler; import org.openhab.binding.boschshc.internal.devices.motiondetector.MotionDetectorHandler; import org.openhab.binding.boschshc.internal.devices.shuttercontrol.ShutterControlHandler; import org.openhab.binding.boschshc.internal.devices.thermostat.ThermostatHandler; -import org.openhab.binding.boschshc.internal.devices.twinguard.BoschTwinguardHandler; +import org.openhab.binding.boschshc.internal.devices.twinguard.TwinguardHandler; +import org.openhab.binding.boschshc.internal.devices.wallthermostat.WallThermostatHandler; import org.openhab.binding.boschshc.internal.devices.windowcontact.WindowContactHandler; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -50,6 +44,7 @@ * * @author Stefan Kästle - Initial contribution * @author Christian Oeing - Added Shutter Control and ThermostatHandler; refactored handler mapping + * @author Christian Oeing - Added WallThermostatHandler */ @NonNullByDefault @Component(configurationPid = "binding.boschshc", service = ThingHandlerFactory.class) @@ -66,14 +61,15 @@ public ThingTypeHandlerMapping(ThingTypeUID thingTypeUID, Function SUPPORTED_THING_TYPES = List.of( - new ThingTypeHandlerMapping(THING_TYPE_SHC, thing -> new BoschSHCBridgeHandler((Bridge) thing)), - new ThingTypeHandlerMapping(THING_TYPE_INWALL_SWITCH, BoschInWallSwitchHandler::new), - new ThingTypeHandlerMapping(THING_TYPE_TWINGUARD, BoschTwinguardHandler::new), + new ThingTypeHandlerMapping(THING_TYPE_SHC, thing -> new BridgeHandler((Bridge) thing)), + new ThingTypeHandlerMapping(THING_TYPE_INWALL_SWITCH, LightControlHandler::new), + new ThingTypeHandlerMapping(THING_TYPE_TWINGUARD, TwinguardHandler::new), new ThingTypeHandlerMapping(THING_TYPE_WINDOW_CONTACT, WindowContactHandler::new), new ThingTypeHandlerMapping(THING_TYPE_MOTION_DETECTOR, MotionDetectorHandler::new), new ThingTypeHandlerMapping(THING_TYPE_SHUTTER_CONTROL, ShutterControlHandler::new), new ThingTypeHandlerMapping(THING_TYPE_THERMOSTAT, ThermostatHandler::new), - new ThingTypeHandlerMapping(THING_TYPE_CLIMATE_CONTROL, ClimateControlHandler::new)); + new ThingTypeHandlerMapping(THING_TYPE_CLIMATE_CONTROL, ClimateControlHandler::new), + new ThingTypeHandlerMapping(THING_TYPE_WALL_THERMOSTAT, WallThermostatHandler::new)); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java index f342153d55d76..b6c7e345578b0 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java @@ -24,6 +24,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BiFunction; +import java.util.function.Predicate; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -34,6 +36,7 @@ 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.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -251,7 +254,7 @@ public Request createRequest(String url, HttpMethod method, @Nullable Object con if (content != null) { String body = GSON.toJson(content); - logger.trace("create request for {} and content {}", url, body); + logger.trace("create request for {} and content {}", url, content.toString()); request = request.content(new StringContentProvider(body)); } else { logger.trace("create request for {}", url); @@ -265,26 +268,45 @@ public Request createRequest(String url, HttpMethod method, @Nullable Object con * * @param request Request to send * @param responseContentClass Type of expected response + * @param contentValidator Checks if the parsed response is valid + * @param errorResponseHandler Optional ustom error response handling. If not provided a generic exception is thrown * @throws ExecutionException in case of invalid HTTP request result * @throws TimeoutException in case of an HTTP request timeout * @throws InterruptedException in case of an interrupt + * @throws BoschSHCException in case of a custom handled error response */ - public TContent sendRequest(Request request, Class responseContentClass) - throws InterruptedException, TimeoutException, ExecutionException { + public TContent sendRequest(Request request, Class responseContentClass, + Predicate contentValidator, + @Nullable BiFunction errorResponseHandler) + throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException { logger.trace("Send request: {}", request.toString()); ContentResponse contentResponse = request.send(); - logger.debug("Received response: {} - status: {}", contentResponse.getContentAsString(), - contentResponse.getStatus()); + String textContent = contentResponse.getContentAsString(); + + Integer statusCode = contentResponse.getStatus(); + if (!HttpStatus.getCode(statusCode).isSuccess()) { + if (errorResponseHandler != null) { + throw errorResponseHandler.apply(statusCode, textContent); + } else { + throw new ExecutionException(String.format("Request failed with status code %s", statusCode), null); + } + } + + logger.debug("Received response: {} - status: {}", textContent, statusCode); try { @Nullable - TContent content = GSON.fromJson(contentResponse.getContentAsString(), responseContentClass); + TContent content = GSON.fromJson(textContent, responseContentClass); if (content == null) { throw new ExecutionException(String.format("Received no content in response, expected type %s", responseContentClass.getName()), null); } + if (!contentValidator.test(content)) { + throw new ExecutionException(String.format("Received invalid content for type %s: %s", + responseContentClass.getName(), content), null); + } return content; } catch (JsonSyntaxException e) { throw new ExecutionException(String.format("Received invalid content in response, expected type %s: %s", diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeConfiguration.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeConfiguration.java similarity index 84% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeConfiguration.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeConfiguration.java index 3943f1432846a..92187160a0a14 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeConfiguration.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeConfiguration.java @@ -15,12 +15,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link BoschSHCBridgeConfiguration} class contains fields mapping thing configuration parameters. + * The {@link BridgeConfiguration} class contains fields mapping thing configuration parameters. * * @author Stefan Kästle - Initial contribution */ @NonNullByDefault -public class BoschSHCBridgeConfiguration { +public class BridgeConfiguration { /** * IP address of the Bosch Smart Home Controller diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java similarity index 87% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeHandler.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java index c575d7586d456..f04272c1ccad7 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschSHCBridgeHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java @@ -61,9 +61,9 @@ * @author Christian Oeing - refactorings of e.g. server registration */ @NonNullByDefault -public class BoschSHCBridgeHandler extends BaseBridgeHandler { +public class BridgeHandler extends BaseBridgeHandler { - private final Logger logger = LoggerFactory.getLogger(BoschSHCBridgeHandler.class); + private final Logger logger = LoggerFactory.getLogger(BridgeHandler.class); /** * gson instance to convert a class to json string and back. @@ -79,7 +79,7 @@ public class BoschSHCBridgeHandler extends BaseBridgeHandler { private @Nullable ScheduledFuture scheduledPairing; - public BoschSHCBridgeHandler(Bridge bridge) { + public BridgeHandler(Bridge bridge) { super(bridge); this.longPolling = new LongPolling(this.scheduler, this::handleLongPollResult, this::handleLongPollFailure); @@ -91,7 +91,7 @@ public void initialize() { FrameworkUtil.getBundle(getClass()).getVersion()); // Read configuration - BoschSHCBridgeConfiguration config = getConfigAs(BoschSHCBridgeConfiguration.class); + BridgeConfiguration config = getConfigAs(BridgeConfiguration.class); String ipAddress = config.ipAddress.trim(); if (ipAddress.isEmpty()) { @@ -274,8 +274,8 @@ private boolean getDevices() throws InterruptedException { for (Device d : devices) { // Write found devices into openhab.log until we have implemented auto discovery logger.info("Found device: name={} id={}", d.name, d.id); - if (d.deviceSerivceIDs != null) { - for (String s : d.deviceSerivceIDs) { + if (d.deviceServiceIds != null) { + for (String s : d.deviceServiceIds) { logger.info(".... service: {}", s); } } @@ -300,7 +300,14 @@ private boolean getDevices() throws InterruptedException { private void handleLongPollResult(LongPollResult result) { for (DeviceStatusUpdate update : result.result) { if (update != null && update.state != null) { - logger.debug("Got update for {}", update.deviceId); + logger.debug("Got update of type {}: {}", update.type, update.state); + + var updateDeviceId = update.deviceId; + if (updateDeviceId == null) { + continue; + } + + logger.debug("Got update for {}", updateDeviceId); boolean handled = false; @@ -315,10 +322,11 @@ private void handleLongPollResult(LongPollResult result) { String deviceId = handler.getBoschID(); handled = true; - logger.debug("Registered device: {} - looking for {}", deviceId, update.deviceId); + logger.debug("Registered device: {} - looking for {}", deviceId, updateDeviceId); - if (deviceId != null && update.deviceId.equals(deviceId)) { - logger.debug("Found child: {} - calling processUpdate with {}", handler, update.state); + if (deviceId != null && updateDeviceId.equals(deviceId)) { + logger.debug("Found child: {} - calling processUpdate (id: {}) with {}", handler, update.id, + update.state); handler.processUpdate(update.id, update.state); } } else { @@ -327,7 +335,7 @@ private void handleLongPollResult(LongPollResult result) { } if (!handled) { - logger.debug("Could not find a thing for device ID: {}", update.deviceId); + logger.debug("Could not find a thing for device ID: {}", updateDeviceId); } } } @@ -400,6 +408,34 @@ private boolean getRooms() throws InterruptedException { } } + public Device getDeviceInfo(String deviceId) + throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException { + @Nullable + BoschHttpClient httpClient = this.httpClient; + if (httpClient == null) { + throw new BoschSHCException("HTTP client not initialized"); + } + + String url = httpClient.getBoschSmartHomeUrl(String.format("devices/%s", deviceId)); + 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); + if (errorResponse != null && JsonRestExceptionResponse.isValid(errorResponse)) { + if (errorResponse.errorCode.equals(JsonRestExceptionResponse.ENTITY_NOT_FOUND)) { + return new BoschSHCException("@text/offline.conf-error.invalid-device-id"); + } else { + return new BoschSHCException( + String.format("Request for info of device %s failed with status code %d and error code %s", + deviceId, errorResponse.statusCode, errorResponse.errorCode)); + } + } else { + return new BoschSHCException(String.format("Request for info for device %s failed with status code %d", + deviceId, statusCode)); + } + }); + } + /** * Query the Bosch Smart Home Controller for the state of the given thing. * @@ -445,7 +481,7 @@ private boolean getRooms() throws InterruptedException { } @Nullable - T state = gson.fromJson(content, stateClass); + T state = BoschSHCServiceState.fromJson(content, stateClass); if (state == null) { throw new BoschSHCException(String.format("Received invalid, expected type %s", stateClass.getName())); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java index 9b7b254493eb3..1e34c96eb6cc3 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java @@ -109,14 +109,16 @@ private String subscribe(BoschHttpClient httpClient) throws LongPollingFailedExc String url = httpClient.getBoschShcUrl("remote/json-rpc"); JsonRpcRequest request = new JsonRpcRequest("2.0", "RE/subscribe", new String[] { "com/bosch/sh/remote/*", null }); - logger.debug("Subscribe: Sending request: {} - using httpClient {}", gson.toJson(request), httpClient); + logger.debug("Subscribe: Sending request: {} - using httpClient {}", request.toString(), + httpClient.toString()); Request httpRequest = httpClient.createRequest(url, POST, request); - SubscribeResult response = httpClient.sendRequest(httpRequest, SubscribeResult.class); + 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; - } catch (TimeoutException | ExecutionException e) { + } catch (TimeoutException | ExecutionException | BoschSHCException e) { throw new LongPollingFailedException( String.format("Error on subscribe (Http client: %s): %s", httpClient.toString(), e.getMessage()), e); diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Device.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Device.java index 562e075ed3dd3..6e616a6d4cde3 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Device.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/Device.java @@ -45,7 +45,7 @@ public class Device { public String rootDeviceId; public String id; - public List deviceSerivceIDs; + public List deviceServiceIds; public String manufacturer; public String roomId; public String deviceModel; @@ -54,4 +54,18 @@ public class Device { public String name; public String status; public List childDeviceIds; + + public static Boolean isValid(Device obj) { + return obj != null && obj.id != null; + } + + @Override + public String toString() { + return String.format( + "Type %s; RootDeviceId: %s; Id: %s; Device Service Ids: %s; Manufacturer: %s; Room Id: %s; Device Model: %s; Serial: %s; Profile: %s; Name: %s; Status: %s; Child Device Ids: %s ", + this.type, this.rootDeviceId, this.id, + this.deviceServiceIds != null ? String.join(", ", this.deviceServiceIds) : "null", this.manufacturer, + this.roomId, this.deviceModel, this.serial, this.profile, this.name, this.status, + this.childDeviceIds != null ? String.join(", ", this.childDeviceIds) : "null"); + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java index 649f3de5bfda4..6cce6f455f8db 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/DeviceStatusUpdate.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.boschshc.internal.devices.bridge.dto; +import org.eclipse.jdt.annotation.Nullable; + import com.google.gson.JsonElement; import com.google.gson.annotations.SerializedName; @@ -47,7 +49,7 @@ public class DeviceStatusUpdate { /** * Id of device the update is for. */ - public String deviceId; + public @Nullable String deviceId; @Override public String toString() { diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/SubscribeResult.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/SubscribeResult.java index c0df59e65480c..e920decd07b1a 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/SubscribeResult.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/SubscribeResult.java @@ -30,4 +30,8 @@ public String getResult() { public String getJsonrpc() { return this.jsonrpc; } + + public static Boolean isValid(SubscribeResult obj) { + return obj != null && obj.result != null && obj.jsonrpc != null; + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/BoschInWallSwitchHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/LightControlHandler.java similarity index 51% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/BoschInWallSwitchHandler.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/LightControlHandler.java index b5a4ebd449ca5..9825db8f43c94 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/BoschInWallSwitchHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/LightControlHandler.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.boschshc.internal.devices.inwallswitch; +package org.openhab.binding.boschshc.internal.devices.lightcontrol; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*; @@ -21,8 +21,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; -import org.openhab.binding.boschshc.internal.devices.inwallswitch.dto.PowerMeterState; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.powermeter.PowerMeterService; +import org.openhab.binding.boschshc.internal.services.powermeter.dto.PowerMeterServiceState; import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchService; import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchState; import org.openhab.binding.boschshc.internal.services.powerswitch.dto.PowerSwitchServiceState; @@ -32,22 +33,19 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; -import com.google.gson.JsonElement; - /** - * Represents Bosch in-wall switches. + * A simple light control. * * @author Stefan Kästle - Initial contribution */ @NonNullByDefault -public class BoschInWallSwitchHandler extends BoschSHCHandler { +public class LightControlHandler extends BoschSHCHandler { private final PowerSwitchService powerSwitchService; - public BoschInWallSwitchHandler(Thing thing) { + public LightControlHandler(Thing thing) { super(thing); this.powerSwitchService = new PowerSwitchService(); } @@ -57,46 +55,32 @@ protected void initializeServices() throws BoschSHCException { super.initializeServices(); this.registerService(this.powerSwitchService, this::updateChannels, List.of(CHANNEL_POWER_SWITCH)); + this.createService(PowerMeterService::new, this::updateChannels, + List.of(CHANNEL_POWER_CONSUMPTION, CHANNEL_ENERGY_CONSUMPTION)); } @Override public void handleCommand(ChannelUID channelUID, Command command) { super.handleCommand(channelUID, command); - logger.debug("Handle command for: {} - {}", channelUID.getThingUID(), command); - - if (command instanceof RefreshType) { - switch (channelUID.getId()) { - case CHANNEL_POWER_CONSUMPTION: { - PowerMeterState state = this.getState("PowerMeter", PowerMeterState.class); - if (state != null) { - updatePowerMeterState(state); - } - break; + switch (channelUID.getId()) { + case CHANNEL_POWER_SWITCH: + if (command instanceof OnOffType) { + updatePowerSwitchState((OnOffType) command); } - case CHANNEL_ENERGY_CONSUMPTION: - // Nothing to do here, since the same update is received from POWER_CONSUMPTION - break; - default: - logger.warn("Received refresh request for unsupported channel: {}", channelUID); - } - } else { - switch (channelUID.getId()) { - case CHANNEL_POWER_SWITCH: - if (command instanceof OnOffType) { - updatePowerSwitchState((OnOffType) command); - } - break; - } + break; } } - void updatePowerMeterState(PowerMeterState state) { - logger.debug("Parsed power meter state of {}: energy {} - power {}", this.getBoschID(), state.energyConsumption, - state.energyConsumption); - - updateState(CHANNEL_POWER_CONSUMPTION, new QuantityType(state.powerConsumption, Units.WATT)); - updateState(CHANNEL_ENERGY_CONSUMPTION, new QuantityType(state.energyConsumption, Units.WATT_HOUR)); + /** + * Updates the channels which are linked to the {@link PowerMeterService} of the device. + * + * @param state Current state of {@link PowerMeterService}. + */ + private void updateChannels(PowerMeterServiceState state) { + super.updateState(CHANNEL_POWER_CONSUMPTION, new QuantityType(state.powerConsumption, Units.WATT)); + super.updateState(CHANNEL_ENERGY_CONSUMPTION, + new QuantityType(state.energyConsumption, Units.WATT_HOUR)); } /** @@ -114,20 +98,4 @@ private void updatePowerSwitchState(OnOffType command) { state.switchState = PowerSwitchState.valueOf(command.toFullString()); this.updateServiceState(this.powerSwitchService, state); } - - @Override - public void processUpdate(String id, JsonElement state) { - super.processUpdate(id, state); - - logger.debug("in-wall switch: received update: ID {} state {}", id, state); - - if (id.equals("PowerMeter")) { - PowerMeterState powerMeterState = GSON.fromJson(state, PowerMeterState.class); - if (powerMeterState == null) { - logger.warn("Received unknown update in in-wall switch: {}", state); - } else { - updatePowerMeterState(powerMeterState); - } - } - } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandler.java index 6e3f564fe7f1e..a5dc356d5f7b4 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/MotionDetectorHandler.java @@ -14,22 +14,22 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_LATEST_MOTION; +import java.util.List; + import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; -import org.openhab.binding.boschshc.internal.devices.motiondetector.dto.LatestMotionState; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.latestmotion.LatestMotionService; +import org.openhab.binding.boschshc.internal.services.latestmotion.dto.LatestMotionServiceState; import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; - -import com.google.gson.JsonElement; /** - * MotionDetectorHandler + * Detects every movement through an intelligent combination of passive infra-red technology and an additional + * temperature sensor. * * @author Stefan Kästle - Initial contribution + * @author Christian Oeing - Use service instead of custom logic */ @NonNullByDefault public class MotionDetectorHandler extends BoschSHCHandler { @@ -39,34 +39,14 @@ public MotionDetectorHandler(Thing thing) { } @Override - public void handleCommand(ChannelUID channelUID, Command command) { - logger.debug("Handle command for: {} - {}", channelUID.getThingUID(), command); + protected void initializeServices() throws BoschSHCException { + super.initializeServices(); - if (CHANNEL_LATEST_MOTION.equals(channelUID.getId())) { - if (command instanceof RefreshType) { - LatestMotionState state = this.getState("LatestMotion", LatestMotionState.class); - if (state != null) { - updateLatestMotionState(state); - } - } - } + this.createService(LatestMotionService::new, this::updateChannels, List.of(CHANNEL_LATEST_MOTION)); } - void updateLatestMotionState(LatestMotionState state) { + private void updateChannels(LatestMotionServiceState state) { DateTimeType date = new DateTimeType(state.latestMotionDetected); updateState(CHANNEL_LATEST_MOTION, date); } - - @Override - public void processUpdate(String id, JsonElement state) { - logger.debug("Motion detector: received update: {} {}", id, state); - - @Nullable - LatestMotionState latestMotionState = GSON.fromJson(state, LatestMotionState.class); - if (latestMotionState == null) { - logger.warn("Received unknown update in in-wall switch: {}", state); - return; - } - updateLatestMotionState(latestMotionState); - } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java index 6b54503744e20..3dc767b4e2216 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java @@ -30,7 +30,7 @@ import org.openhab.core.types.Command; /** - * Handler for a shutter control device + * Control of your shutter to take any position you desire. * * @author Christian Oeing - Initial contribution */ diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandler.java index 5f71040558522..7e2b0e26d3ceb 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/thermostat/ThermostatHandler.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.boschshc.internal.devices.thermostat; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_CHILD_LOCK; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_VALVE_TAPPET_POSITION; @@ -20,11 +21,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.childlock.ChildLockService; +import org.openhab.binding.boschshc.internal.services.childlock.dto.ChildLockServiceState; import org.openhab.binding.boschshc.internal.services.temperaturelevel.TemperatureLevelService; import org.openhab.binding.boschshc.internal.services.temperaturelevel.dto.TemperatureLevelServiceState; import org.openhab.binding.boschshc.internal.services.valvetappet.ValveTappetService; import org.openhab.binding.boschshc.internal.services.valvetappet.dto.ValveTappetServiceState; +import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; /** * Handler for a thermostat device. @@ -34,18 +39,34 @@ @NonNullByDefault public final class ThermostatHandler extends BoschSHCHandler { + private ChildLockService childLockService; + public ThermostatHandler(Thing thing) { super(thing); + this.childLockService = new ChildLockService(); } @Override protected void initializeServices() throws BoschSHCException { this.createService(TemperatureLevelService::new, this::updateChannels, List.of(CHANNEL_TEMPERATURE)); this.createService(ValveTappetService::new, this::updateChannels, List.of(CHANNEL_VALVE_TAPPET_POSITION)); + this.registerService(this.childLockService, this::updateChannels, List.of(CHANNEL_CHILD_LOCK)); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + + switch (channelUID.getId()) { + case CHANNEL_CHILD_LOCK: + this.handleServiceCommand(this.childLockService, command); + break; + } } /** - * Updates the channels which are linked to the {@link TemperatureLevelService} of the device. + * Updates the channels which are linked to the {@link TemperatureLevelService} + * of the device. * * @param state Current state of {@link TemperatureLevelService}. */ @@ -54,11 +75,22 @@ private void updateChannels(TemperatureLevelServiceState state) { } /** - * Updates the channels which are linked to the {@link ValveTappetService} of the device. + * Updates the channels which are linked to the {@link ValveTappetService} of + * the device. * * @param state Current state of {@link ValveTappetService}. */ private void updateChannels(ValveTappetServiceState state) { super.updateState(CHANNEL_VALVE_TAPPET_POSITION, state.getPositionState()); } + + /** + * Updates the channels which are linked to the {@link ChildLockService} of the + * device. + * + * @param state Current state of {@link ChildLockService}. + */ + private void updateChannels(ChildLockServiceState state) { + super.updateState(CHANNEL_CHILD_LOCK, state.getActiveState()); + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/BoschTwinguardHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/BoschTwinguardHandler.java deleted file mode 100644 index b2f5a1a83e0ea..0000000000000 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/BoschTwinguardHandler.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) 2010-2021 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.devices.twinguard; - -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*; - -import javax.measure.quantity.Dimensionless; -import javax.measure.quantity.Temperature; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; -import org.openhab.binding.boschshc.internal.devices.twinguard.dto.AirQualityLevelState; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.library.unit.Units; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; - -import com.google.gson.JsonElement; -import com.google.gson.JsonSyntaxException; - -/** - * The {@link BoschSHCHandler} is responsible for handling commands for the TwinGuard handler. - * - * @author Stefan Kästle - Initial contribution - */ -@NonNullByDefault -public class BoschTwinguardHandler extends BoschSHCHandler { - - public BoschTwinguardHandler(Thing thing) { - super(thing); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - Bridge bridge = this.getBridge(); - - if (bridge != null) { - logger.debug("Handle command for: {} - {}", channelUID.getThingUID(), command); - - if (command instanceof RefreshType) { - AirQualityLevelState state = this.getState("AirQualityLevel", AirQualityLevelState.class); - if (state != null) { - updateAirQualityState(state); - } - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Bridge is NUL"); - } - } - - void updateAirQualityState(AirQualityLevelState state) { - updateState(CHANNEL_TEMPERATURE, new QuantityType(state.temperature, SIUnits.CELSIUS)); - updateState(CHANNEL_TEMPERATURE_RATING, new StringType(state.temperatureRating)); - updateState(CHANNEL_HUMIDITY, new QuantityType(state.humidity, Units.PERCENT)); - updateState(CHANNEL_HUMIDITY_RATING, new StringType(state.humidityRating)); - updateState(CHANNEL_PURITY, new QuantityType(state.purity, Units.PARTS_PER_MILLION)); - updateState(CHANNEL_AIR_DESCRIPTION, new StringType(state.description)); - updateState(CHANNEL_PURITY_RATING, new StringType(state.purityRating)); - updateState(CHANNEL_COMBINED_RATING, new StringType(state.combinedRating)); - } - - @Override - public void processUpdate(String id, JsonElement state) throws JsonSyntaxException { - logger.debug("Twinguard: received update: {} {}", id, state); - - AirQualityLevelState parsed = GSON.fromJson(state, AirQualityLevelState.class); - if (parsed == null) { - logger.warn("Received unknown update in in-wall switch: {}", state); - return; - } - - logger.debug("Parsed switch state of {}: {}", this.getBoschID(), parsed); - updateAirQualityState(parsed); - } -} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/TwinguardHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/TwinguardHandler.java new file mode 100644 index 0000000000000..0b10500fce67e --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/TwinguardHandler.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2021 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.devices.twinguard; + +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_AIR_DESCRIPTION; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_COMBINED_RATING; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_HUMIDITY; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_HUMIDITY_RATING; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PURITY; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PURITY_RATING; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE_RATING; + +import java.util.List; + +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.airqualitylevel.AirQualityLevelService; +import org.openhab.binding.boschshc.internal.services.airqualitylevel.dto.AirQualityLevelServiceState; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Thing; + +/** + * The Twinguard smoke detector warns you in case of fire and constantly monitors the air. + * + * @author Stefan Kästle - Initial contribution + * @author Christian Oeing - Use service instead of custom logic + */ +@NonNullByDefault +public class TwinguardHandler extends BoschSHCHandler { + + public TwinguardHandler(Thing thing) { + super(thing); + } + + @Override + protected void initializeServices() throws BoschSHCException { + super.initializeServices(); + + this.createService(AirQualityLevelService::new, this::updateChannels, + List.of(CHANNEL_TEMPERATURE, CHANNEL_TEMPERATURE_RATING, CHANNEL_HUMIDITY, CHANNEL_HUMIDITY_RATING, + CHANNEL_PURITY, CHANNEL_PURITY_RATING, CHANNEL_AIR_DESCRIPTION, CHANNEL_COMBINED_RATING)); + } + + private void updateChannels(AirQualityLevelServiceState state) { + updateState(CHANNEL_TEMPERATURE, new QuantityType(state.temperature, SIUnits.CELSIUS)); + updateState(CHANNEL_TEMPERATURE_RATING, new StringType(state.temperatureRating)); + updateState(CHANNEL_HUMIDITY, new QuantityType(state.humidity, Units.PERCENT)); + updateState(CHANNEL_HUMIDITY_RATING, new StringType(state.humidityRating)); + updateState(CHANNEL_PURITY, new QuantityType(state.purity, Units.PARTS_PER_MILLION)); + updateState(CHANNEL_PURITY_RATING, new StringType(state.purityRating)); + updateState(CHANNEL_AIR_DESCRIPTION, new StringType(state.description)); + updateState(CHANNEL_COMBINED_RATING, new StringType(state.combinedRating)); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/wallthermostat/WallThermostatHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/wallthermostat/WallThermostatHandler.java new file mode 100644 index 0000000000000..401bf6bcc2c68 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/wallthermostat/WallThermostatHandler.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2021 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.devices.wallthermostat; + +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_HUMIDITY; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.humiditylevel.HumidityLevelService; +import org.openhab.binding.boschshc.internal.services.humiditylevel.dto.HumidityLevelServiceState; +import org.openhab.binding.boschshc.internal.services.temperaturelevel.TemperatureLevelService; +import org.openhab.binding.boschshc.internal.services.temperaturelevel.dto.TemperatureLevelServiceState; +import org.openhab.core.thing.Thing; + +/** + * Handler for a wall thermostat device. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public final class WallThermostatHandler extends BoschSHCHandler { + + public WallThermostatHandler(Thing thing) { + super(thing); + } + + @Override + protected void initializeServices() throws BoschSHCException { + this.createService(TemperatureLevelService::new, this::updateChannels, List.of(CHANNEL_TEMPERATURE)); + this.createService(HumidityLevelService::new, this::updateChannels, List.of(CHANNEL_HUMIDITY)); + } + + /** + * 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) { + super.updateState(CHANNEL_TEMPERATURE, state.getTemperatureState()); + } + + /** + * Updates the channels which are linked to the {@link HumidityLevelService} of the device. + * + * @param state Current state of {@link HumidityLevelService}. + */ + private void updateChannels(HumidityLevelServiceState state) { + super.updateState(CHANNEL_HUMIDITY, state.getHumidityState()); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandler.java index 90b978a20430d..502187e5ed409 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/windowcontact/WindowContactHandler.java @@ -27,7 +27,7 @@ import org.openhab.core.types.State; /** - * The {@link BoschSHCHandler} is responsible for handling Bosch window/door contacts. + * Detects open windows and doors. * * @author Stefan Kästle - Initial contribution */ diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCService.java index b58d1c3240656..9a9e045ca8827 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCService.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCService.java @@ -18,25 +18,26 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler; +import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; +import org.openhab.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; import com.google.gson.JsonElement; /** - * Base class of a service of a Bosch Smart Home device. - * The services of the devices and their official APIs can be found here: https://apidocs.bosch-smarthome.com/local/ + * Base class of a service of a Bosch Smart Home device. The services of the + * devices and their official APIs can be found here: + * https://apidocs.bosch-smarthome.com/local/ * * @author Christian Oeing - Initial contribution */ @NonNullByDefault public abstract class BoschSHCService { - private final Logger logger = LoggerFactory.getLogger(BoschSHCService.class); + protected final Logger logger = LoggerFactory.getLogger(BoschSHCService.class); /** * Unique service name @@ -48,15 +49,10 @@ public abstract class BoschSHCService { */ private final Class stateClass; - /** - * gson instance to convert a class to json string and back. - */ - private final Gson gson = new Gson(); - /** * Bridge to use for communication from/to the device */ - private @Nullable BoschSHCBridgeHandler bridgeHandler; + private @Nullable BridgeHandler bridgeHandler; /** * Id of device the service belongs to @@ -72,7 +68,8 @@ public abstract class BoschSHCService { * Constructor * * @param serviceName Unique name of the service. - * @param stateClass State class that this service uses for data transfers from/to the device. + * @param stateClass State class that this service uses for data transfers + * from/to the device. */ protected BoschSHCService(String serviceName, Class stateClass) { this.serviceName = serviceName; @@ -84,9 +81,10 @@ protected BoschSHCService(String serviceName, Class stateClass) { * * @param bridgeHandler Bridge to use for communication from/to the device * @param deviceId Id of device this service is for - * @param stateUpdateListener Function to call when a state update was received from the device. + * @param stateUpdateListener Function to call when a state update was received + * from the device. */ - public void initialize(BoschSHCBridgeHandler bridgeHandler, String deviceId, + public void initialize(BridgeHandler bridgeHandler, String deviceId, @Nullable Consumer stateUpdateListener) { this.bridgeHandler = bridgeHandler; this.deviceId = deviceId; @@ -142,7 +140,7 @@ public void refreshState() throws InterruptedException, TimeoutException, Execut if (deviceId == null) { return null; } - BoschSHCBridgeHandler bridgeHandler = this.bridgeHandler; + BridgeHandler bridgeHandler = this.bridgeHandler; if (bridgeHandler == null) { return null; } @@ -162,7 +160,7 @@ public void setState(TState state) throws InterruptedException, TimeoutException if (deviceId == null) { return; } - BoschSHCBridgeHandler bridgeHandler = this.bridgeHandler; + BridgeHandler bridgeHandler = this.bridgeHandler; if (bridgeHandler == null) { return; } @@ -176,7 +174,7 @@ public void setState(TState state) throws InterruptedException, TimeoutException */ public void onStateUpdate(JsonElement stateData) { @Nullable - TState state = gson.fromJson(stateData, this.stateClass); + TState state = BoschSHCServiceState.fromJson(stateData, this.stateClass); if (state == null) { this.logger.warn("Received invalid, expected type {}", this.stateClass.getName()); return; @@ -195,4 +193,16 @@ private void onStateUpdate(TState state) { stateUpdateListener.accept(state); } } + + /** + * Allows a service to handle a command and create a new state out of it. + * The new state still has to be set via setState. + * + * @param command Command to handle + * @throws BoschSHCException If service can not handle command + */ + public TState handleCommand(Command command) throws BoschSHCException { + throw new BoschSHCException( + String.format("%s: Can not handle command %s", this.getServiceName(), command.getClass().getName())); + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/AirQualityLevelService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/AirQualityLevelService.java new file mode 100644 index 0000000000000..7c864606df341 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/AirQualityLevelService.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2021 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.services.airqualitylevel; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.airqualitylevel.dto.AirQualityLevelServiceState; + +/** + * This service constantly measures key air quality values to help you create a healthy room climate. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class AirQualityLevelService extends BoschSHCService { + + public AirQualityLevelService() { + super("AirQualityLevel", AirQualityLevelServiceState.class); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/dto/AirQualityLevelState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/dto/AirQualityLevelServiceState.java similarity index 89% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/dto/AirQualityLevelState.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/dto/AirQualityLevelServiceState.java index 724377b53e011..dd26764ce5d10 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/twinguard/dto/AirQualityLevelState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/airqualitylevel/dto/AirQualityLevelServiceState.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.boschshc.internal.devices.twinguard.dto; +package org.openhab.binding.boschshc.internal.services.airqualitylevel.dto; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; @@ -19,9 +19,9 @@ * * @author Stefan Kästle - Initial contribution */ -public class AirQualityLevelState extends BoschSHCServiceState { +public class AirQualityLevelServiceState extends BoschSHCServiceState { - public AirQualityLevelState() { + public AirQualityLevelServiceState() { super("airQualityLevelState"); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/ChildLockService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/ChildLockService.java new file mode 100644 index 0000000000000..079ad8e5caa8d --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/ChildLockService.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2021 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.services.childlock; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.childlock.dto.ChildLockServiceState; +import org.openhab.binding.boschshc.internal.services.childlock.dto.ChildLockState; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.types.Command; + +/** + * Indicates if child lock of device is active. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class ChildLockService extends BoschSHCService { + public ChildLockService() { + super("Thermostat", ChildLockServiceState.class); + } + + @Override + public ChildLockServiceState handleCommand(Command command) throws BoschSHCException { + if (command instanceof OnOffType) { + ChildLockServiceState state = new ChildLockServiceState(); + state.childLock = ChildLockState.valueOf(command.toFullString()); + return state; + } else { + return super.handleCommand(command); + } + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockServiceState.java new file mode 100644 index 0000000000000..1a1bc34c48fa7 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockServiceState.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2021 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.services.childlock.dto; + +import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; +import org.openhab.core.library.types.OnOffType; + +/** + * State for {@link ChildLockService} to activate and deactivate the child lock + * of a device. + * + * @author Christian Oeing - Initial contribution + */ +public class ChildLockServiceState extends BoschSHCServiceState { + public ChildLockServiceState() { + super("childLockState"); + } + + public ChildLockState childLock; + + public OnOffType getActiveState() { + return OnOffType.from(this.childLock.toString()); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockState.java new file mode 100644 index 0000000000000..d53b4054231a4 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/childlock/dto/ChildLockState.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2010-2021 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.services.childlock.dto; + +/** + * Possible values for {@link ChildLockServiceState}. + * + * @author Christian Oeing - Initial contribution + */ +public enum ChildLockState { + ON, + OFF +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java index 0ffbfaf14859e..b9aa2ee016f58 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java @@ -12,6 +12,12 @@ */ package org.openhab.binding.boschshc.internal.services.dto; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.annotations.SerializedName; /** @@ -20,14 +26,62 @@ * @author Christian Oeing - Initial contribution */ public class BoschSHCServiceState { + + /** + * gson instance to convert a class to json string and back. + */ + private static final Gson gson = new Gson(); + + private static final Logger logger = LoggerFactory.getLogger(BoschSHCServiceState.class); + + /** + * State type. Initialized when instance is created. + */ + private @Nullable String stateType = null; + @SerializedName("@type") private final String type; protected BoschSHCServiceState(String type) { this.type = type; + + if (stateType == null) { + stateType = type; + } } public String getType() { return type; } + + protected boolean isValid() { + String expectedType = stateType; + if (expectedType == null || !expectedType.equals(this.type)) { + var className = this.getClass().getName(); + logger.debug("Expected state type {} for state class {}, received {}", expectedType, className, this.type); + return false; + } + + return true; + } + + public static @Nullable TState fromJson(String json, + Class stateClass) { + var state = gson.fromJson(json, stateClass); + if (state == null || !state.isValid()) { + return null; + } + + return state; + } + + public static @Nullable TState fromJson(JsonElement json, + Class stateClass) { + var state = gson.fromJson(json, stateClass); + if (state == null || !state.isValid()) { + return null; + } + + return state; + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponse.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponse.java index cb8994bad4442..f19d71eb12673 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponse.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/JsonRestExceptionResponse.java @@ -18,6 +18,12 @@ * @author Christian Oeing - Initial contribution */ public class JsonRestExceptionResponse extends BoschSHCServiceState { + + /** + * The entity could not be found. One of the defined path parameters was invalid. + */ + public static final String ENTITY_NOT_FOUND = "ENTITY_NOT_FOUND"; + public JsonRestExceptionResponse() { super("JsonRestExceptionResponseEntity"); this.errorCode = ""; @@ -32,5 +38,9 @@ public JsonRestExceptionResponse() { /** * The HTTP status of the error. */ - public int statusCode; + public Integer statusCode; + + public static boolean isValid(JsonRestExceptionResponse obj) { + return obj != null && obj.errorCode != null && obj.statusCode != null; + } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/HumidityLevelService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/HumidityLevelService.java new file mode 100644 index 0000000000000..a2a5bf9fb7533 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/HumidityLevelService.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2021 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.services.humiditylevel; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.humiditylevel.dto.HumidityLevelServiceState; + +/** + * Measures the humidity at a central point in the room. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class HumidityLevelService extends BoschSHCService { + + public HumidityLevelService() { + super("HumidityLevel", HumidityLevelServiceState.class); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java new file mode 100644 index 0000000000000..bd8f249296c53 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2021 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.services.humiditylevel.dto; + +import javax.measure.quantity.Dimensionless; + +import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; + +/** + * State for {@link HumidityLevelService} to get and set the desired temperature of a room. + * + * @author Christian Oeing - Initial contribution + */ +public class HumidityLevelServiceState extends BoschSHCServiceState { + + public HumidityLevelServiceState() { + super("humidityLevelState"); + } + + /** + * Current measured humidity. + */ + public double humidity; + + public State getHumidityState() { + return new QuantityType(this.humidity, Units.PERCENT); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/LatestMotionService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/LatestMotionService.java new file mode 100644 index 0000000000000..8e4e941558b52 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/LatestMotionService.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2021 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.services.latestmotion; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.latestmotion.dto.LatestMotionServiceState; + +/** + * Detects every movement through an intelligent combination of passive infra-red technology and an additional + * temperature sensor. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class LatestMotionService extends BoschSHCService { + + public LatestMotionService() { + super("LatestMotion", LatestMotionServiceState.class); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/dto/LatestMotionState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/dto/LatestMotionServiceState.java similarity index 83% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/dto/LatestMotionState.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/dto/LatestMotionServiceState.java index 38658342c3b88..40f591450db58 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/motiondetector/dto/LatestMotionState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/latestmotion/dto/LatestMotionServiceState.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.boschshc.internal.devices.motiondetector.dto; +package org.openhab.binding.boschshc.internal.services.latestmotion.dto; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; @@ -33,9 +33,9 @@ * * @author Stefan Kästle - Initial contribution */ -public class LatestMotionState extends BoschSHCServiceState { +public class LatestMotionServiceState extends BoschSHCServiceState { - public LatestMotionState() { + public LatestMotionServiceState() { super("latestMotionState"); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/PowerMeterService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/PowerMeterService.java new file mode 100644 index 0000000000000..099eea7f04dd8 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/PowerMeterService.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2021 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.services.powermeter; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.boschshc.internal.services.BoschSHCService; +import org.openhab.binding.boschshc.internal.services.powermeter.dto.PowerMeterServiceState; + +/** + * With this service you always have an eye on energy consumption. + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class PowerMeterService extends BoschSHCService { + + public PowerMeterService() { + super("PowerMeter", PowerMeterServiceState.class); + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/dto/PowerMeterState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/dto/PowerMeterServiceState.java similarity index 74% rename from bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/dto/PowerMeterState.java rename to bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/dto/PowerMeterServiceState.java index 9e0ba54f824ed..21b62509fa8b4 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/inwallswitch/dto/PowerMeterState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/powermeter/dto/PowerMeterServiceState.java @@ -10,18 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.boschshc.internal.devices.inwallswitch.dto; +package org.openhab.binding.boschshc.internal.services.powermeter.dto; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; /** - * PowerMeterState + * State for {@link PowerMeterService} * * @author Stefan Kästle - Initial contribution */ -public class PowerMeterState extends BoschSHCServiceState { +public class PowerMeterServiceState extends BoschSHCServiceState { - public PowerMeterState() { + public PowerMeterServiceState() { super("powerMeterState"); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties index 9946491aaa9d3..329090d4d84bc 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties @@ -10,3 +10,7 @@ offline.conf-error-ssl = The SSL connection to the Bosch Smart Home Controller i offline.long-polling-failed.http-client-null = Long polling failed and could not be restarted because http client is null. offline.long-polling-failed.trying-to-reconnect = Long polling failed, will try to reconnect. offline.interrupted = Conneting to Bosch Smart Home Controller was interrupted. + +offline.conf-error.empty-device-id = No device ID set. + +offline.conf-error.invalid-device-id = Device ID is invalid. diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_de.properties b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_de.properties index 9c493160f5344..9990709602593 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_de.properties +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc_de.properties @@ -7,3 +7,5 @@ binding.boschshc.description = Dieses Binding integriert das Bosch Smart Home Sy offline.conf-error-pairing = Bitte betätigen Sie den Taster am Bosch Smart Home Controller zum automatischen Verbinden. offline.not-reachable = Smart Home Controller ist nicht erreichbar. offline.conf-error-ssl = Die SSL Verbindung zum Bosch Smart Home Controller ist nicht möglich. +offline.conf-error.empty-device-id = Keine Geräte ID gesetzt. +offline.conf-error.invalid-device-id = Geräte ID ist ungültig. diff --git a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml index aa1e30609b087..0af967fe0a45b 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml @@ -18,7 +18,7 @@ - Bosch In-wall switch for light control + A simple light control. @@ -36,7 +36,7 @@ - Bosch TwinGuard environmental sensor + The Twinguard smoke detector warns you in case of fire and constantly monitors the air. @@ -58,8 +58,8 @@ - - Bosch Contact for windows and doors + + Detects open windows and doors. @@ -75,7 +75,8 @@ - Bosch Motion Detector + Detects every movement through an intelligent combination of passive infra-red technology and an + additional temperature sensor. @@ -91,7 +92,7 @@ - Bosch Shutter Control + Control of your shutter to take any position you desire. @@ -107,11 +108,12 @@ - Bosch Thermostat + Radiator thermostat + @@ -124,7 +126,7 @@ - Bosch Climate Control. This is a virtual device which is automatically created for all rooms that have + This is a virtual device which is automatically created for all rooms that have thermostats in it. @@ -136,6 +138,23 @@ + + + + + + + Display of the current room temperature as well as the relative humidity in the room. + + + + + + + + + + Number:Temperature @@ -259,4 +278,10 @@ + + Switch + + Indicates if it is possible to set the desired temperature on the device. + + diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java index 8fa5c61a268a0..4972d1f715d86 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClientTest.java @@ -107,7 +107,7 @@ void sendRequest() { Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET); // Null pointer exception is expected, because localhost will not answer request assertThrows(NullPointerException.class, () -> { - httpClient.sendRequest(request, SubscribeResult.class); + httpClient.sendRequest(request, SubscribeResult.class, SubscribeResult::isValid, null); }); } } diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java new file mode 100644 index 0000000000000..17914d3f4e790 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2021 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.services.dto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +/** + * Test class + * + * @author Christian Oeing - Initial contribution + */ +class TestState extends BoschSHCServiceState { + public TestState() { + super("testState"); + } +} + +/** + * Test class + * + * @author Christian Oeing - Initial contribution + */ +class TestState2 extends BoschSHCServiceState { + public TestState2() { + super("testState2"); + } +} + +/** + * Unit tests for BoschSHCServiceStateTest + * + * @author Christian Oeing - Initial contribution + */ +@NonNullByDefault +public class BoschSHCServiceStateTest { + private final Gson gson = new Gson(); + + @Test + public void fromJson_nullStateForDifferentType() { + var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"differentState\"}", JsonObject.class), + TestState.class); + assertEquals(null, state); + } + + @Test + public void fromJson_stateObjectForValidJson() { + var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class), + TestState.class); + assertNotEquals(null, state); + } + + /** + * This checks for a bug we had where the expected type stayed the same for different state classes + */ + @Test + public void fromJson_stateObjectForValidJsonAfterOtherState() { + BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class), TestState.class); + var state2 = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState2\"}", JsonObject.class), + TestState2.class); + assertNotEquals(null, state2); + } +}