- [Wall Thermostat](#wall-thermostat)
- [Security Camera 360](#security-camera-360)
- [Security Camera Eyes](#security-camera-eyes)
+ - [Intrusion Detection System](#intrusion-detection-system)
- [Limitations](#limitations)
- [Discovery](#discovery)
- [Bridge Configuration](#bridge-configuration)
| privacy-mode | Switch | ☑ | If privacy mode is enabled, the camera is disabled and vice versa. |
| camera-notification | Switch | ☑ | Enables or disables notifications for the camera. |
+### Intrusion Detection System
+
+Allows to retrieve notifications in case of intrusions. The system can be armed and disarmed and alarms can be muted.
+
+**Thing Type ID**: `intrusion-detection-system`
+
+| Channel Type ID | Item Type | Writable | Description |
+| ---------------------------- | -------------------- | :------: | -------------------------------------------------------------- |
+| system-availability | Switch | ☐ | Indicates whether the intrusion detection system is available. |
+| arming-state | String | ☐ | Read-only channel to retrieve the current arming state. Possible values are `SYSTEM_ARMING`, `SYSTEM_ARMED` and `SYSTEM_DISARMED`. |
+| alarm-state | String | ☐ | Read-only channel to retrieve the current alarm state. Possible values are `ALARM_OFF`, `PRE_ALARM`, `ALARM_ON`, `ALARM_MUTED` and `UNKNOWN`. |
+| active-configuration-profile | String | ☐ | The name of the active configuration profile used for the intrusion detection system. |
+| arm-action | String | ☑ | Arms the intrusion detection system using the given profile ID (default is "0"). |
+| disarm-action | Switch | ☑ | Disarms the intrusion detection system when an ON command is received. |
+| mute-action | Switch | ☑ | Mutes the alarm when an ON command is received. |
+
## Limitations
- Discovery of Things
* @author Stefan Kästle - Initial contribution
* @author Christian Oeing - added Shutter Control, ThermostatHandler
* @author Christian Oeing - Added WallThermostatHandler
+ * @author David Pace - Added cameras and intrusion detection system
*/
@NonNullByDefault
public class BoschSHCBindingConstants {
public static final ThingTypeUID THING_TYPE_WALL_THERMOSTAT = new ThingTypeUID(BINDING_ID, "wall-thermostat");
public static final ThingTypeUID THING_TYPE_CAMERA_360 = new ThingTypeUID(BINDING_ID, "security-camera-360");
public static final ThingTypeUID THING_TYPE_CAMERA_EYES = new ThingTypeUID(BINDING_ID, "security-camera-eyes");
+ public static final ThingTypeUID THING_TYPE_INTRUSION_DETECTION_SYSTEM = new ThingTypeUID(BINDING_ID,
+ "intrusion-detection-system");
// List of all Channel IDs
// Auto-generated from thing-types.xml via script, don't modify
public static final String CHANNEL_CHILD_LOCK = "child-lock";
public static final String CHANNEL_PRIVACY_MODE = "privacy-mode";
public static final String CHANNEL_CAMERA_NOTIFICATION = "camera-notification";
+ public static final String CHANNEL_SYSTEM_AVAILABILITY = "system-availability";
+ public static final String CHANNEL_ARMING_STATE = "arming-state";
+ public static final String CHANNEL_ALARM_STATE = "alarm-state";
+ public static final String CHANNEL_ACTIVE_CONFIGURATION_PROFILE = "active-configuration-profile";
+ public static final String CHANNEL_ARM_ACTION = "arm-action";
+ public static final String CHANNEL_DISARM_ACTION = "disarm-action";
+ public static final String CHANNEL_MUTE_ACTION = "mute-action";
+
+ // static device/service names
+ public static final String SERVICE_INTRUSION_DETECTION = "intrusionDetectionSystem";
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+
+/**
+ * Handler for physical Bosch devices with configurable IDs (as opposed to system services, which have static IDs).
+ * <p>
+ * The device ID of physical devices has to be configured in the thing configuration.
+ * <p>
+ * Examples for physical device IDs are:
+ *
+ * <pre>
+ * hdm:Cameras:d20354de-44b5-3acc-924c-24c98d59da42
+ * hdm:ZigBee:000d6f0016d1cdae
+ * </pre>
+ *
+ * @author Stefan Kästle - Initial contribution
+ * @author Christian Oeing - refactorings of e.g. server registration
+ * @author David Pace - Handler abstraction
+ *
+ */
+@NonNullByDefault
+public class BoschSHCDeviceHandler extends BoschSHCHandler {
+
+ /**
+ * Bosch SHC configuration loaded from openHAB configuration.
+ */
+ private @Nullable BoschSHCConfiguration config;
+
+ public BoschSHCDeviceHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+
+ var config = this.config = getConfigAs(BoschSHCConfiguration.class);
+
+ String deviceId = config.id;
+ if (deviceId == null || deviceId.isBlank()) {
+ 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 {
+ var bridgeHandler = this.getBridgeHandler();
+ var info = bridgeHandler.getDeviceInfo(deviceId);
+ logger.trace("Device initialized:\n{}", info);
+ } catch (TimeoutException | ExecutionException | BoschSHCException e) {
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+ return;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
+ return;
+ }
+
+ super.initialize();
+ }
+
+ /**
+ * Returns the unique id of the Bosch device.
+ *
+ * @return Unique id of the Bosch device.
+ */
+ public @Nullable String getBoschID() {
+ if (config != null) {
+ return config.id;
+ }
+
+ return null;
+ }
+}
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.AbstractBoschSHCService;
+import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCService;
+import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCServiceWithRequestBody;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
import org.openhab.core.thing.Bridge;
/**
* The {@link BoschSHCHandler} represents Bosch Things. Each type of device
- * inherits from this abstract thing handler.
+ * or system service inherits from this abstract thing handler.
*
* @author Stefan Kästle - Initial contribution
* @author Christian Oeing - refactorings of e.g. server registration
+ * @author David Pace - Handler abstraction
*/
@NonNullByDefault
public abstract class BoschSHCHandler extends BaseThingHandler {
protected final Logger logger = LoggerFactory.getLogger(getClass());
- /**
- * Bosch SHC configuration loaded from openHAB configuration.
- */
- private @Nullable BoschSHCConfiguration config;
-
/**
* Services of the device.
*/
private List<DeviceService<? extends BoschSHCServiceState>> services = new ArrayList<>();
- public BoschSHCHandler(Thing thing) {
+ protected BoschSHCHandler(Thing thing) {
super(thing);
}
/**
- * Returns the unique id of the Bosch device.
+ * Returns the unique id of the Bosch device or service.
+ * <p>
+ * For physical devices, the ID looks like
+ *
+ * <pre>
+ * hdm:Cameras:d20354de-44b5-3acc-924c-24c98d59da42
+ * hdm:ZigBee:000d6f0016d1c087
+ * </pre>
+ *
+ * For virtual devices / services, static IDs like the following are used:
+ *
+ * <pre>
+ * ventilationService
+ * smokeDetectionSystem
+ * intrusionDetectionSystem
+ * </pre>
*
- * @return Unique id of the Bosch device.
+ * @return Unique ID of the Bosch device or service.
*/
- public @Nullable String getBoschID() {
- BoschSHCConfiguration config = this.config;
- if (config != null) {
- return config.id;
- } else {
- return null;
- }
- }
+ public abstract @Nullable String getBoschID();
/**
* Initializes this handler. Use this method to register all services of the device with
*/
@Override
public void initialize() {
- 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 {
- 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;
- }
// Initialize device services
try {
protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void registerService(
TService service, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels)
throws BoschSHCException {
- BridgeHandler bridgeHandler = this.getBridgeHandler();
+ registerService(service, stateUpdateListener, affectedChannels, false);
+ }
+
+ /**
+ * Registers a service for this device.
+ *
+ * @param <TService> Type of service.
+ * @param <TState> Type of service state.
+ * @param service Service to register.
+ * @param stateUpdateListener Function to call when a state update was received
+ * from the device.
+ * @param affectedChannels Channels which are affected by the state of this
+ * service.
+ * @param shouldFetchInitialState indicates whether the initial state should be actively requested from the device
+ * or service. Useful if state updates are not included in long poll results.
+ * @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set.
+ * @throws BoschSHCException If no device id is set.
+ */
+ protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void registerService(
+ TService service, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels,
+ boolean shouldFetchInitialState) throws BoschSHCException {
+
+ String deviceId = verifyBoschID();
+ service.initialize(getBridgeHandler(), deviceId, stateUpdateListener);
+ this.registerService(service, affectedChannels);
+
+ if (shouldFetchInitialState) {
+ fetchInitialState(service, stateUpdateListener);
+ }
+ }
+
+ /**
+ * Actively requests the initial state for the given service. This is required if long poll results do not contain
+ * status updates for the given service.
+ *
+ * @param <TService> Type of the service for which the state should be obtained
+ * @param <TState> Type of the objects to serialize and deserialize the service state
+ * @param service Service for which the state should be requested
+ * @param stateUpdateListener Function to process the obtained state
+ */
+ private <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void fetchInitialState(
+ TService service, Consumer<TState> stateUpdateListener) {
+
+ try {
+ @Nullable
+ TState serviceState = service.getState();
+ if (serviceState != null) {
+ stateUpdateListener.accept(serviceState);
+ }
+ } catch (TimeoutException | ExecutionException | BoschSHCException e) {
+ logger.debug("Could not retrieve the initial state for service {} of device {}", service.getServiceName(),
+ getBoschID());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.debug("Could not retrieve the initial state for service {} of device {}", service.getServiceName(),
+ getBoschID());
+ }
+ }
+
+ /**
+ * Registers a write-only service that does not receive states from the bridge.
+ * <p>
+ * Examples for such services are the actions of the intrusion detection service.
+ *
+ * @param <TService> Type of service.
+ * @param service Service to register.
+ * @throws BoschSHCException If no device ID is set.
+ */
+ protected <TService extends AbstractBoschSHCService> void registerStatelessService(TService service)
+ throws BoschSHCException {
+
+ String deviceId = verifyBoschID();
+ service.initialize(getBridgeHandler(), deviceId);
+ // do not register in service list because the service can not receive state updates
+ }
+ /**
+ * Verifies that a Bosch device or service ID is set and throws an exception if this is not the case.
+ *
+ * @return the Bosch ID, if present
+ * @throws BoschSHCException if no Bosch ID is set
+ */
+ private String verifyBoschID() throws BoschSHCException {
String deviceId = this.getBoschID();
if (deviceId == null) {
throw new BoschSHCException(
String.format("Could not register service for %s, no device id set", this.getThing()));
}
-
- service.initialize(bridgeHandler, deviceId, stateUpdateListener);
- this.registerService(service, affectedChannels);
+ return deviceId;
}
/**
Collection<String> affectedChannels) {
this.services.add(new DeviceService<TState>(service, affectedChannels));
}
+
+ /**
+ * Sends a HTTP POST request with empty body.
+ *
+ * @param <TService> Type of service.
+ * @param service Service implementing the action
+ */
+ protected <TService extends AbstractStatelessBoschSHCService> void postAction(TService service) {
+ try {
+ service.postAction();
+ } catch (ExecutionException | TimeoutException e) {
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ String.format("Error while triggering action %s", service.getEndpoint()));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ String.format("Error while triggering action %s", service.getEndpoint()));
+ }
+ }
+
+ /**
+ * Sends a HTTP POST request with the given request body.
+ *
+ * @param <TService> Type of service.
+ * @param <TState> Type of the request to be sent.
+ * @param service Service implementing the action
+ * @param request Request object to be serialized to JSON
+ */
+ protected <TService extends AbstractStatelessBoschSHCServiceWithRequestBody<TState>, TState extends BoschSHCServiceState> void postAction(
+ TService service, TState request) {
+ try {
+ service.postAction(request);
+ } catch (ExecutionException | TimeoutException e) {
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ String.format("Error while triggering action %s", service.getEndpoint()));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ String.format("Error while triggering action %s", service.getEndpoint()));
+ }
+ }
}
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
import org.openhab.binding.boschshc.internal.devices.camera.CameraHandler;
import org.openhab.binding.boschshc.internal.devices.climatecontrol.ClimateControlHandler;
+import org.openhab.binding.boschshc.internal.devices.intrusion.IntrusionDetectionHandler;
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;
* @author Stefan Kästle - Initial contribution
* @author Christian Oeing - Added Shutter Control and ThermostatHandler; refactored handler mapping
* @author Christian Oeing - Added WallThermostatHandler
+ * @author David Pace - Added cameras and intrusion detection system
*/
@NonNullByDefault
@Component(configurationPid = "binding.boschshc", service = ThingHandlerFactory.class)
new ThingTypeHandlerMapping(THING_TYPE_CLIMATE_CONTROL, ClimateControlHandler::new),
new ThingTypeHandlerMapping(THING_TYPE_WALL_THERMOSTAT, WallThermostatHandler::new),
new ThingTypeHandlerMapping(THING_TYPE_CAMERA_360, CameraHandler::new),
- new ThingTypeHandlerMapping(THING_TYPE_CAMERA_EYES, CameraHandler::new));
+ new ThingTypeHandlerMapping(THING_TYPE_CAMERA_EYES, CameraHandler::new),
+ new ThingTypeHandlerMapping(THING_TYPE_INTRUSION_DETECTION_SYSTEM, IntrusionDetectionHandler::new));
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
* @author Stefan Kästle - Initial contribution
* @author Gerd Zanker - added HttpClient with pairing support
* @author Christian Oeing - refactorings of e.g. server registration
+ * @author David Pace - Added support for custom endpoints and HTTP POST requests
*/
@NonNullByDefault
public class BridgeHandler extends BaseBridgeHandler {
*/
private final LongPolling longPolling;
- private @Nullable BoschHttpClient httpClient;
+ /**
+ * HTTP client for all communications to and from the bridge.
+ * <p>
+ * This member is package-protected to enable mocking in unit tests.
+ */
+ /* package */ @Nullable
+ BoschHttpClient httpClient;
private @Nullable ScheduledFuture<?> scheduledPairing;
private void handleLongPollResult(LongPollResult result) {
for (DeviceStatusUpdate update : result.result) {
if (update != null && update.state != null) {
- logger.debug("Got update of type {}: {}", update.type, update.state);
+ logger.debug("Got update for service {} of type {}: {}", update.id, update.type, update.state);
var updateDeviceId = update.deviceId;
if (updateDeviceId == null) {
continue;
}
- logger.debug("Got update for {}", updateDeviceId);
+ logger.debug("Got update for device {}", updateDeviceId);
boolean handled = false;
}
/**
- * Query the Bosch Smart Home Controller for the state of the given thing.
+ * Query the Bosch Smart Home Controller for the state of the given device.
+ * <p>
+ * The URL used for retrieving the state has the following structure:
+ *
+ * <pre>
+ * https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
+ * </pre>
*
* @param deviceId Id of device to get state for
* @param stateName Name of the state to query
* @param stateClass Class to convert the resulting JSON to
+ * @return the deserialized state object, may be <code>null</code>
* @throws ExecutionException
* @throws TimeoutException
* @throws InterruptedException
}
String url = httpClient.getServiceUrl(stateName, deviceId);
- Request request = httpClient.createRequest(url, GET).header("Accept", "application/json");
+ logger.debug("getState(): Requesting \"{}\" from Bosch: {} via {}", stateName, deviceId, url);
+ return getState(httpClient, url, stateClass);
+ }
- logger.debug("refreshState: Requesting \"{}\" from Bosch: {} via {}", stateName, deviceId, url);
+ /**
+ * Queries the Bosch Smart Home Controller for the state using an explicit endpoint.
+ *
+ * @param <T> Type to which the resulting JSON should be deserialized to
+ * @param endpoint The destination endpoint part of the URL
+ * @param stateClass Class to convert the resulting JSON to
+ * @return the deserialized state object, may be <code>null</code>
+ * @throws InterruptedException
+ * @throws TimeoutException
+ * @throws ExecutionException
+ * @throws BoschSHCException
+ */
+ public <T extends BoschSHCServiceState> @Nullable T getState(String endpoint, Class<T> stateClass)
+ throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+ @Nullable
+ BoschHttpClient httpClient = this.httpClient;
+ if (httpClient == null) {
+ logger.warn("HttpClient not initialized");
+ return null;
+ }
+
+ String url = httpClient.getBoschSmartHomeUrl(endpoint);
+ logger.debug("getState(): Requesting from Bosch: {}", url);
+ return getState(httpClient, url, stateClass);
+ }
+
+ /**
+ * Sends a HTTP GET request in order to retrieve a state from the Bosch Smart Home Controller.
+ *
+ * @param <T> Type to which the resulting JSON should be deserialized to
+ * @param httpClient HTTP client used for sending the request
+ * @param url URL at which the state should be retrieved
+ * @param stateClass Class to convert the resulting JSON to
+ * @return the deserialized state object, may be <code>null</code>
+ * @throws InterruptedException
+ * @throws TimeoutException
+ * @throws ExecutionException
+ * @throws BoschSHCException
+ */
+ protected <T extends BoschSHCServiceState> @Nullable T getState(BoschHttpClient httpClient, String url,
+ Class<T> stateClass) throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+ Request request = httpClient.createRequest(url, GET).header("Accept", "application/json");
ContentResponse contentResponse = request.send();
String content = contentResponse.getContentAsString();
- logger.debug("refreshState: Request complete: [{}] - return code: {}", content, contentResponse.getStatus());
+ logger.debug("getState(): Request complete: [{}] - return code: {}", content, contentResponse.getStatus());
int statusCode = contentResponse.getStatus();
if (statusCode != 200) {
JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class);
if (errorResponse != null) {
- throw new BoschSHCException(String.format(
- "State request for service %s of device %s failed with status code %d and error code %s",
- stateName, deviceId, errorResponse.statusCode, errorResponse.errorCode));
+ throw new BoschSHCException(
+ String.format("State request with URL %s failed with status code %d and error code %s", url,
+ errorResponse.statusCode, errorResponse.errorCode));
} else {
throw new BoschSHCException(
- String.format("State request for service %s of device %s failed with status code %d", stateName,
- deviceId, statusCode));
+ String.format("State request with URL %s failed with status code %d", url, statusCode));
}
}
// Send request
return request.send();
}
+
+ /**
+ * Sends a HTTP POST request without a request body to the given endpoint.
+ *
+ * @param endpoint The destination endpoint part of the URL
+ * @return the HTTP response
+ * @throws InterruptedException
+ * @throws TimeoutException
+ * @throws ExecutionException
+ */
+ public @Nullable Response postAction(String endpoint)
+ throws InterruptedException, TimeoutException, ExecutionException {
+ return postAction(endpoint, null);
+ }
+
+ /**
+ * Sends a HTTP POST request with a request body to the given endpoint.
+ *
+ * @param <T> Type of the request
+ * @param endpoint The destination endpoint part of the URL
+ * @param requestBody object representing the request body to be sent, may be <code>null</code>
+ * @return the HTTP response
+ * @throws InterruptedException
+ * @throws TimeoutException
+ * @throws ExecutionException
+ */
+ public <T extends BoschSHCServiceState> @Nullable Response postAction(String endpoint, @Nullable T requestBody)
+ throws InterruptedException, TimeoutException, ExecutionException {
+ @Nullable
+ BoschHttpClient httpClient = this.httpClient;
+ if (httpClient == null) {
+ logger.warn("HttpClient not initialized");
+ return null;
+ }
+
+ String url = httpClient.getBoschSmartHomeUrl(endpoint);
+ Request request = httpClient.createRequest(url, POST, requestBody);
+ return request.send();
+ }
}
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PRIVACY_MODE;
import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
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.BoschSHCDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationService;
import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationState;
*
*/
@NonNullByDefault
-public class CameraHandler extends BoschSHCHandler {
+public class CameraHandler extends BoschSHCDeviceHandler {
private PrivacyModeService privacyModeService;
private CameraNotificationService cameraNotificationService;
protected void initializeServices() throws BoschSHCException {
super.initializeServices();
- this.registerService(this.privacyModeService, this::updateChannels, List.of(CHANNEL_PRIVACY_MODE));
- this.registerService(this.cameraNotificationService, this::updateChannels,
- List.of(CHANNEL_CAMERA_NOTIFICATION));
- }
-
- @Override
- public void initialize() {
- super.initialize();
- requestInitialStates();
- }
-
- /**
- * Requests the initial states for relevant services.
- * <p>
- * If this is not done, items associated with the corresponding channels with stay in an uninitialized state
- * (<code>null</code>).
- * This in turn leads to events not being fired properly when switches are used in the UI.
- * <p>
- * Unfortunately the long poll results do not contain camera-related updates, so this is the current approach
- * to get the initial states.
- */
- private void requestInitialStates() {
- requestInitialPrivacyState();
- requestInitialNotificationState();
- }
-
- private void requestInitialPrivacyState() {
- try {
- @Nullable
- PrivacyModeServiceState serviceState = privacyModeService.getState();
- if (serviceState != null) {
- super.updateState(CHANNEL_PRIVACY_MODE, serviceState.value.toOnOffType());
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- logger.debug("Could not retrieve the initial privacy state of camera {}", getBoschID());
- } catch (TimeoutException | ExecutionException | BoschSHCException e) {
- logger.debug("Could not retrieve the initial privacy state of camera {}", getBoschID());
- }
- }
-
- private void requestInitialNotificationState() {
- try {
- @Nullable
- CameraNotificationServiceState serviceState = cameraNotificationService.getState();
- if (serviceState != null) {
- super.updateState(CHANNEL_CAMERA_NOTIFICATION, serviceState.value.toOnOffType());
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- logger.debug("Could not retrieve the initial notification state of camera {}", getBoschID());
- } catch (TimeoutException | ExecutionException | BoschSHCException e) {
- logger.debug("Could not retrieve the initial notification state of camera {}", getBoschID());
- }
+ this.registerService(this.privacyModeService, this::updateChannels, List.of(CHANNEL_PRIVACY_MODE), true);
+ this.registerService(this.cameraNotificationService, this::updateChannels, List.of(CHANNEL_CAMERA_NOTIFICATION),
+ true);
}
@Override
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.RoomClimateControlService;
import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.dto.RoomClimateControlServiceState;
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
-public final class ClimateControlHandler extends BoschSHCHandler {
+public final class ClimateControlHandler extends BoschSHCDeviceHandler {
private RoomClimateControlService roomClimateControlService;
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion;
+
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ACTIVE_CONFIGURATION_PROFILE;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ALARM_STATE;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ARMING_STATE;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ARM_ACTION;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_DISARM_ACTION;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_MUTE_ACTION;
+import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SYSTEM_AVAILABILITY;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.intrusion.IntrusionDetectionControlStateService;
+import org.openhab.binding.boschshc.internal.services.intrusion.IntrusionDetectionSystemStateService;
+import org.openhab.binding.boschshc.internal.services.intrusion.SurveillanceAlarmService;
+import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.ArmActionService;
+import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
+import org.openhab.binding.boschshc.internal.services.intrusion.actions.disarm.DisarmActionService;
+import org.openhab.binding.boschshc.internal.services.intrusion.actions.mute.MuteActionService;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionControlState;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionSystemState;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.SurveillanceAlarmState;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+
+/**
+ * Handler for the intrusion detection alarm system.
+ * <p>
+ * It supports
+ * <ul>
+ * <li>Obtaining the current intrusion detection system state</li>
+ * <li>Receiving updates related to the detection control state</li>
+ * <li>Receiving updates related to surveillance alarm events</li>
+ * <li>Arming the system</li>
+ * <li>Disarming the system</li>
+ * <li>Muting the alarm</li>
+ * </ul>
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class IntrusionDetectionHandler extends BoschSHCHandler {
+
+ private IntrusionDetectionSystemStateService intrusionDetectionSystemStateService;
+ private IntrusionDetectionControlStateService intrusionDetectionControlStateService;
+ private SurveillanceAlarmService surveillanceAlarmService;
+ private ArmActionService armActionService;
+ private DisarmActionService disarmActionService;
+ private MuteActionService muteActionService;
+
+ public IntrusionDetectionHandler(Thing thing) {
+ super(thing);
+ this.intrusionDetectionSystemStateService = new IntrusionDetectionSystemStateService();
+ this.intrusionDetectionControlStateService = new IntrusionDetectionControlStateService();
+ this.surveillanceAlarmService = new SurveillanceAlarmService();
+ this.armActionService = new ArmActionService();
+ this.disarmActionService = new DisarmActionService();
+ this.muteActionService = new MuteActionService();
+ }
+
+ @Override
+ public @Nullable String getBoschID() {
+ return BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION;
+ }
+
+ @Override
+ protected void initializeServices() throws BoschSHCException {
+ super.initializeServices();
+
+ this.registerService(intrusionDetectionSystemStateService, this::updateChannels,
+ List.of(CHANNEL_SYSTEM_AVAILABILITY, CHANNEL_ARMING_STATE, CHANNEL_ALARM_STATE,
+ CHANNEL_ACTIVE_CONFIGURATION_PROFILE),
+ true);
+ this.registerService(intrusionDetectionControlStateService, this::updateChannels,
+ List.of(CHANNEL_ARMING_STATE));
+ this.registerService(surveillanceAlarmService, this::updateChannels, List.of(CHANNEL_ALARM_STATE));
+ this.registerStatelessService(armActionService);
+ this.registerStatelessService(disarmActionService);
+ this.registerStatelessService(muteActionService);
+ }
+
+ private void updateChannels(IntrusionDetectionSystemState systemState) {
+ super.updateState(CHANNEL_SYSTEM_AVAILABILITY, OnOffType.from(systemState.systemAvailability.available));
+ super.updateState(CHANNEL_ARMING_STATE, new StringType(systemState.armingState.state.toString()));
+ super.updateState(CHANNEL_ALARM_STATE, new StringType(systemState.alarmState.value.toString()));
+ super.updateState(CHANNEL_ACTIVE_CONFIGURATION_PROFILE,
+ new StringType(systemState.activeConfigurationProfile.profileId));
+ }
+
+ private void updateChannels(IntrusionDetectionControlState controlState) {
+ super.updateState(CHANNEL_ARMING_STATE, new StringType(controlState.value.toString()));
+ }
+
+ private void updateChannels(SurveillanceAlarmState surveillanceAlarmState) {
+ super.updateState(CHANNEL_ALARM_STATE, new StringType(surveillanceAlarmState.value.toString()));
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ super.handleCommand(channelUID, command);
+
+ switch (channelUID.getId()) {
+ case CHANNEL_ARM_ACTION:
+ if (command instanceof StringType) {
+ armIntrusionDetectionSystem((StringType) command);
+ }
+ break;
+ case CHANNEL_DISARM_ACTION:
+ if (command instanceof OnOffType) {
+ disarmIntrusionDetectionSystem((OnOffType) command);
+ }
+ break;
+ case CHANNEL_MUTE_ACTION:
+ if (command instanceof OnOffType) {
+ muteIntrusionDetectionSystem((OnOffType) command);
+ }
+ break;
+ }
+ }
+
+ private void armIntrusionDetectionSystem(StringType profileIdCommand) {
+ ArmActionRequest armActionRequest = new ArmActionRequest();
+ armActionRequest.profileId = profileIdCommand.toFullString();
+ postAction(armActionService, armActionRequest);
+ }
+
+ private void disarmIntrusionDetectionSystem(OnOffType command) {
+ if (command == OnOffType.ON) {
+ postAction(disarmActionService);
+ }
+ }
+
+ private void muteIntrusionDetectionSystem(OnOffType command) {
+ if (command == OnOffType.ON) {
+ postAction(muteActionService);
+ }
+ }
+}
import javax.measure.quantity.Power;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
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;
* @author Stefan Kästle - Initial contribution
*/
@NonNullByDefault
-public class LightControlHandler extends BoschSHCHandler {
+public class LightControlHandler extends BoschSHCDeviceHandler {
private final PowerSwitchService powerSwitchService;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
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;
* @author Christian Oeing - Use service instead of custom logic
*/
@NonNullByDefault
-public class MotionDetectorHandler extends BoschSHCHandler {
+public class MotionDetectorHandler extends BoschSHCDeviceHandler {
public MotionDetectorHandler(Thing thing) {
super(thing);
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.shuttercontrol.OperationState;
import org.openhab.binding.boschshc.internal.services.shuttercontrol.ShutterControlService;
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
-public class ShutterControlHandler extends BoschSHCHandler {
+public class ShutterControlHandler extends BoschSHCDeviceHandler {
/**
* Utility functions to convert data between Bosch things and openHAB items
*/
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
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;
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
-public final class ThermostatHandler extends BoschSHCHandler {
+public final class ThermostatHandler extends BoschSHCDeviceHandler {
private ChildLockService childLockService;
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.BoschSHCDeviceHandler;
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;
* @author Christian Oeing - Use service instead of custom logic
*/
@NonNullByDefault
-public class TwinguardHandler extends BoschSHCHandler {
+public class TwinguardHandler extends BoschSHCDeviceHandler {
public TwinguardHandler(Thing thing) {
super(thing);
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
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;
* @author Christian Oeing - Initial contribution
*/
@NonNullByDefault
-public final class WallThermostatHandler extends BoschSHCHandler {
+public final class WallThermostatHandler extends BoschSHCDeviceHandler {
public WallThermostatHandler(Thing thing) {
super(thing);
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCDeviceHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactService;
import org.openhab.binding.boschshc.internal.services.shuttercontact.ShutterContactState;
* @author Stefan Kästle - Initial contribution
*/
@NonNullByDefault
-public class WindowContactHandler extends BoschSHCHandler {
+public class WindowContactHandler extends BoschSHCDeviceHandler {
public WindowContactHandler(Thing thing) {
super(thing);
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
+
+/**
+ * Base class for Bosch Smart Home services containing what all services have in common.
+ * <p>
+ * The services of the devices and their official APIs can be found
+ * <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
+ *
+ * @author Christian Oeing - Initial contribution
+ * @author David Pace - Service abstraction
+ */
+@NonNullByDefault
+public abstract class AbstractBoschSHCService {
+
+ /**
+ * Unique service name
+ */
+ private final String serviceName;
+
+ /**
+ * Bridge to use for communication from/to the device
+ */
+ private @Nullable BridgeHandler bridgeHandler;
+
+ /**
+ * Id of device the service belongs to
+ */
+ private @Nullable String deviceId;
+
+ protected AbstractBoschSHCService(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ /**
+ * Initializes the service
+ *
+ * @param bridgeHandler Bridge to use for communication from/to the device
+ * @param deviceId Id of device this service is for
+ */
+ public void initialize(BridgeHandler bridgeHandler, String deviceId) {
+ this.bridgeHandler = bridgeHandler;
+ this.deviceId = deviceId;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ protected @Nullable BridgeHandler getBridgeHandler() {
+ return bridgeHandler;
+ }
+
+ protected @Nullable String getDeviceId() {
+ return deviceId;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
+
+/**
+ * Abstract implementation for services that only allow setting states via HTTP POST requests.
+ * State-less services can not receive any states from the bridge.
+ * <p>
+ * This implementation does not support request bodies when submitting the POST request.
+ * Request bodies are supported by the subclass {@link AbstractStatelessBoschSHCServiceWithRequestBody}.
+ * <p>
+ * Examples for this kind of service are the following actions of the intrusion detection system:
+ *
+ * <pre>
+ * /intrusion/actions/arm
+ * /intrusion/actions/disarm
+ * /intrusion/actions/mute
+ * </pre>
+ * <p>
+ * The services of the devices and their official APIs can be found
+ * <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
+ *
+ * @author David Pace - Initial contribution
+ */
+@NonNullByDefault
+public abstract class AbstractStatelessBoschSHCService extends AbstractBoschSHCService {
+
+ private String endpoint;
+
+ protected AbstractStatelessBoschSHCService(String serviceName, String endpoint) {
+ super(serviceName);
+ this.endpoint = endpoint;
+ }
+
+ /**
+ * Sends a HTTP POST request without request body to the endpoint specified by {@link #endpoint}.
+ *
+ * @throws InterruptedException
+ * @throws TimeoutException
+ * @throws ExecutionException
+ */
+ public void postAction() throws InterruptedException, TimeoutException, ExecutionException {
+ BridgeHandler bridgeHandler = getBridgeHandler();
+ if (bridgeHandler == null)
+ return;
+
+ bridgeHandler.postAction(endpoint);
+ }
+
+ public String getEndpoint() {
+ return endpoint;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+
+/**
+ * Abstract implementation for services that allow setting states via HTTP POST requests containing a JSON request body.
+ * State-less services can not receive any states from the bridge.
+ * <p>
+ * An example of such a service is the <code>arm</code> action of the intrusion detection system.
+ * <p>
+ * The services of the devices and their official APIs can be found
+ * <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
+ *
+ * @param <TRequest> Type to represent JSON requests sent by this service
+ *
+ * @author David Pace - Initial contribution
+ */
+@NonNullByDefault
+public abstract class AbstractStatelessBoschSHCServiceWithRequestBody<TRequest extends BoschSHCServiceState>
+ extends AbstractStatelessBoschSHCService {
+
+ protected AbstractStatelessBoschSHCServiceWithRequestBody(String serviceName, String endpoint) {
+ super(serviceName, endpoint);
+ }
+
+ /**
+ * Sends a HTTP POST request containing the serialized request body to the endpoint specified by
+ * {@link #getEndpoint()}.
+ *
+ * @param request a JSON object representing the request body
+ * @throws InterruptedException
+ * @throws TimeoutException
+ * @throws ExecutionException
+ */
+ public void postAction(TRequest request) throws InterruptedException, TimeoutException, ExecutionException {
+ BridgeHandler bridgeHandler = getBridgeHandler();
+ if (bridgeHandler == null) {
+ return;
+ }
+
+ bridgeHandler.postAction(getEndpoint(), request);
+ }
+}
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/
+ * Abstract implementation of a service that supports reading and writing its state using the same JSON message and the
+ * same endpoint.
+ * <p>
+ * The endpoints of this service have the following URL structure:
+ *
+ * <pre>
+ * https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
+ * </pre>
+ *
+ * The HTTP client of the bridge will use <code>GET</code> requests to retrieve the state and <code>PUT</code> requests
+ * to set the state.
+ * <p>
+ * The services of the devices and their official APIs can be found
+ * <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
*
* @author Christian Oeing - Initial contribution
+ * @author David Pace - Service abstraction
*/
@NonNullByDefault
-public abstract class BoschSHCService<TState extends BoschSHCServiceState> {
+public abstract class BoschSHCService<TState extends BoschSHCServiceState> extends AbstractBoschSHCService {
protected final Logger logger = LoggerFactory.getLogger(BoschSHCService.class);
- /**
- * Unique service name
- */
- private final String serviceName;
-
/**
* Class of service state
*/
private final Class<TState> stateClass;
- /**
- * Bridge to use for communication from/to the device
- */
- private @Nullable BridgeHandler bridgeHandler;
-
- /**
- * Id of device the service belongs to
- */
- private @Nullable String deviceId;
-
/**
* Function to call after receiving state updates from the device
*/
* from/to the device.
*/
protected BoschSHCService(String serviceName, Class<TState> stateClass) {
- this.serviceName = serviceName;
+ super(serviceName);
this.stateClass = stateClass;
}
*/
public void initialize(BridgeHandler bridgeHandler, String deviceId,
@Nullable Consumer<TState> stateUpdateListener) {
- this.bridgeHandler = bridgeHandler;
- this.deviceId = deviceId;
+ super.initialize(bridgeHandler, deviceId);
this.stateUpdateListener = stateUpdateListener;
}
- /**
- * Returns the unique name of this service.
- *
- * @return Unique name of the service.
- */
- public String getServiceName() {
- return this.serviceName;
- }
-
/**
* Returns the class of the state this service provides.
*
*/
public @Nullable TState getState()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
- String deviceId = this.deviceId;
+ String deviceId = getDeviceId();
if (deviceId == null) {
return null;
}
- BridgeHandler bridgeHandler = this.bridgeHandler;
+ BridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
return null;
}
- return bridgeHandler.getState(deviceId, this.serviceName, this.stateClass);
+ return bridgeHandler.getState(deviceId, getServiceName(), getStateClass());
}
/**
* @throws TimeoutException
*/
public void setState(TState state) throws InterruptedException, TimeoutException, ExecutionException {
- String deviceId = this.deviceId;
+ String deviceId = getDeviceId();
if (deviceId == null) {
return;
}
- BridgeHandler bridgeHandler = this.bridgeHandler;
+ BridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
return;
}
- bridgeHandler.putState(deviceId, this.serviceName, state);
+ bridgeHandler.putState(deviceId, getServiceName(), state);
}
/**
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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;
+
+/**
+ * Abstract implementation of a system service that does not represent a physical device.
+ * Examples for system services are the intrusion detection system and the water detection system.
+ * <p>
+ * The endpoints to retrieve system states are different from the ones for physical devices, i.e. they do not follow the
+ * pattern
+ *
+ * <pre>
+ * https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
+ * </pre>
+ *
+ * Instead, system services have endpoints like
+ *
+ * <pre>
+ * /intrusion/states/system
+ * </pre>
+ *
+ * <p>
+ * The services of the devices and their official APIs can be found
+ * <a href="https://apidocs.bosch-smarthome.com/local/">here</a>.
+ *
+ * @param <TState> type used for representing the service state
+ *
+ * @author David Pace - Initial contribution
+ */
+@NonNullByDefault
+public abstract class BoschSHCSystemService<TState extends BoschSHCServiceState> extends BoschSHCService<TState> {
+
+ private String endpoint;
+
+ /**
+ * Constructs a system service instance.
+ *
+ * @param serviceName name of the service, such as <code>intrusionDetectionService</code>
+ * @param stateClass the class representing states of the system
+ * @param endpoint the part of the URL after <code>https://{IP}:8444/smarthome/</code>, e.g.
+ * <code>intrusion/states/system</code>
+ */
+ protected BoschSHCSystemService(String serviceName, Class<TState> stateClass, String endpoint) {
+ super(serviceName, stateClass);
+ this.endpoint = endpoint;
+ }
+
+ /**
+ * Uses the endpoint directly instead of constructing a device-specific URL.
+ */
+ @Override
+ public @Nullable TState getState()
+ throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+
+ BridgeHandler bridgeHandler = getBridgeHandler();
+ if (bridgeHandler == null) {
+ return null;
+ }
+ return bridgeHandler.getState(this.endpoint, getStateClass());
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.services.BoschSHCService;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionControlState;
+
+/**
+ * Allows to retrieve the control state of the intrusion detection system.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class IntrusionDetectionControlStateService extends BoschSHCService<IntrusionDetectionControlState> {
+
+ public IntrusionDetectionControlStateService() {
+ super("IntrusionDetectionControl", IntrusionDetectionControlState.class);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.services.BoschSHCSystemService;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionSystemState;
+
+/**
+ * Allows to retrieve the system state of the intrusion detection system.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class IntrusionDetectionSystemStateService extends BoschSHCSystemService<IntrusionDetectionSystemState> {
+
+ public IntrusionDetectionSystemStateService() {
+ super(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, IntrusionDetectionSystemState.class,
+ "intrusion/states/system");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.services.BoschSHCService;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.SurveillanceAlarmState;
+
+/**
+ * Service to handle intrusion detection events.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class SurveillanceAlarmService extends BoschSHCService<SurveillanceAlarmState> {
+
+ public SurveillanceAlarmService() {
+ super("SurveillanceAlarm", SurveillanceAlarmState.class);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.actions.arm;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCServiceWithRequestBody;
+import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
+
+/**
+ * Service to arm the intrusion detection system using a specified profile.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ArmActionService extends AbstractStatelessBoschSHCServiceWithRequestBody<ArmActionRequest> {
+
+ public ArmActionService() {
+ super(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, "intrusion/actions/arm");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.actions.arm.dto;
+
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+
+/**
+ * DTO for arming the intrusion detection system using a specified profile.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class ArmActionRequest extends BoschSHCServiceState {
+
+ public ArmActionRequest() {
+ super("armRequest");
+ }
+
+ /**
+ * The ID of the profile to be used for arming the system.
+ */
+ public String profileId;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.actions.disarm;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCService;
+
+/**
+ * Service for disarming the intrusion detection system.
+ * <p>
+ * This service does not require a DTO because it uses a simple HTTP POST request without a request body.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class DisarmActionService extends AbstractStatelessBoschSHCService {
+
+ public DisarmActionService() {
+ super(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, "intrusion/actions/disarm");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.actions.mute;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCService;
+
+/**
+ * Service to mute the intrusion detection system.
+ * <p>
+ * This service does not require a DTO because it uses a simple HTTP POST request without a request body.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class MuteActionService extends AbstractStatelessBoschSHCService {
+
+ public MuteActionService() {
+ super(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, "intrusion/actions/mute");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.dto;
+
+/**
+ * Represents the active configuration profile of the intrusion detection system.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class ActiveConfigurationProfileData {
+
+ /**
+ * The ID of the active configuration profile (default: <code>"0"</code>)
+ */
+ public String profileId;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Possible alarm states of the intrusion detection system.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public enum AlarmState {
+ ALARM_OFF,
+ PRE_ALARM,
+ ALARM_ON,
+ ALARM_MUTED,
+ UNKNOWN;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.dto;
+
+/**
+ * DTO for the alarm state of the intrusion detection system.
+ * <p>
+ * Example data:
+ *
+ * <pre>
+ * "alarmState": {
+ * "value": "ALARM_OFF",
+ * "incidents": [
+ * {
+ * "id": "string",
+ * "triggerName": "string",
+ * "type": "SYSTEM_ARMED",
+ * "time": 0,
+ * "location": "string",
+ * "locationId": "string"
+ * }
+ * ],
+ * "deleted": true,
+ * "id": "string"
+ * }
+ * </pre>
+ *
+ * <p>
+ * <b>Note:</b> Incidents are not supported yet as they do not seem to be included in the responses from the bridge.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class AlarmStateData {
+
+ public AlarmState value;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.dto;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Possible arming state values of the intrusion detection system.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+public enum ArmingState {
+ SYSTEM_ARMED,
+ SYSTEM_ARMING,
+ SYSTEM_DISARMED;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.dto;
+
+/**
+ * DTO for the arming state of the intrusion detection system.
+ * <p>
+ * Example data:
+ *
+ * <pre>
+ * "armingState": {
+ * "remainingTimeUntilArmed": 0,
+ * "state": "SYSTEM_ARMING",
+ * "deleted": true,
+ * "id": "string"
+ * }
+ * </pre>
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class ArmingStateData {
+
+ public long remainingTimeUntilArmed;
+
+ public ArmingState state;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.dto;
+
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+
+/**
+ * A state which is periodically sent by the intrusion detection control state while arming.
+ * <p>
+ * Example data:
+ *
+ * <pre>
+ * {
+ * "@type": "intrusionDetectionControlState",
+ * "activeProfile": "0",
+ * "alarmActivationDelayTime": 30,
+ * "actuators": [
+ * {
+ * "readonly": false,
+ * "active": true,
+ * "id": "intrusion:video"
+ * },
+ * {
+ * "readonly": false,
+ * "active": false,
+ * "id": "intrusion:siren"
+ * }
+ * ],
+ * "remainingTimeUntilArmed": 29559,
+ * "armActivationDelayTime": 30,
+ * "triggers": [
+ * {
+ * "readonly": false,
+ * "active": true,
+ * "id": "hdm:ZigBee:000d6f0012f02378"
+ * }
+ * ],
+ * "value": "SYSTEM_ARMING"
+ * }
+ * </pre>
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class IntrusionDetectionControlState extends BoschSHCServiceState {
+
+ public IntrusionDetectionControlState() {
+ super("intrusionDetectionControlState");
+ }
+
+ public String activeProfile;
+
+ public int alarmActivationDelayTime;
+
+ public long remainingTimeUntilArmed;
+
+ public int armActivationDelayTime;
+
+ public ArmingState value;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.dto;
+
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+
+/**
+ * Represents the state of the intrusion detection system as reported by the Bosch Smart Home Controller.
+ * <p>
+ * Example data:
+ *
+ * <pre>
+ * {
+ * "@type": "systemState",
+ * "systemAvailability": {
+ * "@type": "systemAvailabilityState",
+ * "available": true,
+ * "deleted": false
+ * },
+ * "armingState": {
+ * "@type": "armingState",
+ * "state": "SYSTEM_DISARMED",
+ * "deleted": false
+ * },
+ * "alarmState": {
+ * "@type": "alarmState",
+ * "value": "ALARM_OFF",
+ * "incidents": [],
+ * "deleted": false
+ * },
+ * "activeConfigurationProfile": {
+ * "@type": "activeConfigurationProfile",
+ * "deleted": false
+ * },
+ * "securityGapState": {
+ * "@type": "securityGapState",
+ * "securityGaps": [],
+ * "deleted": false
+ * },
+ * "deleted": false
+ * }
+ * </pre>
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class IntrusionDetectionSystemState extends BoschSHCServiceState {
+
+ public IntrusionDetectionSystemState() {
+ super("systemState");
+ }
+
+ public SystemAvailabilityStateData systemAvailability;
+
+ public ArmingStateData armingState;
+
+ public AlarmStateData alarmState;
+
+ public ActiveConfigurationProfileData activeConfigurationProfile;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.dto;
+
+import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
+
+/**
+ * A state containing information about intrusion detection events.
+ * <p>
+ * Example data:
+ *
+ * <pre>
+ * {
+ * "@type": "surveillanceAlarmState",
+ * "incidents": [
+ * {
+ * "triggerName": "Motion Detector",
+ * "locationId": "hz_5",
+ * "location": "Living Room",
+ * "id": "hdm:ZigBee:000d6f0012f02342",
+ * "time": 1652615755336,
+ * "type": "INTRUSION"
+ * }
+ * ],
+ * "value": "ALARM_ON"
+ * }
+ * </pre>
+ *
+ * <p>
+ * <b>Note:</b> This state is not documented in the official Bosch API docs.
+ * The type of the incidents seems to be very similar to <code>IncidentType</code> documented for
+ * the system state. However, the type enum seems to be slightly different (<code>INTRUSION</code> instead of
+ * <code>INTRUSION_DETECTED</code>).
+ * For this reason incidents are not modeled in this state object for now.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class SurveillanceAlarmState extends BoschSHCServiceState {
+
+ public SurveillanceAlarmState() {
+ super("surveillanceAlarmState");
+ }
+
+ public AlarmState value;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion.dto;
+
+/**
+ * DTO for the availability state of the intrusion detection system.
+ * <p>
+ * Example data:
+ *
+ * <pre>
+ * {
+ * "@type": "systemAvailabilityState",
+ * "available": true,
+ * "deleted": false
+ * }
+ * </pre>
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+public class SystemAvailabilityStateData {
+
+ public boolean available;
+}
</thing-type>
+ <thing-type id="intrusion-detection-system">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="shc"/>
+ </supported-bridge-type-refs>
+
+ <label>Intrusion Detection System</label>
+ <description>Allows to retrieve and control the state of the intrusion detection alarm system.</description>
+
+ <channels>
+ <channel id="system-availability" typeId="system-availability"/>
+ <channel id="arming-state" typeId="arming-state"/>
+ <channel id="alarm-state" typeId="alarm-state"/>
+ <channel id="active-configuration-profile" typeId="active-configuration-profile"/>
+ <channel id="arm-action" typeId="arm-action"/>
+ <channel id="disarm-action" typeId="disarm-action"/>
+ <channel id="mute-action" typeId="mute-action"/>
+ </channels>
+
+ </thing-type>
+
+ <channel-type id="system-availability">
+ <item-type>Switch</item-type>
+ <label>System Availability</label>
+ <description>Indicates whether the intrusion detection system is available.</description>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="arming-state">
+ <item-type>String</item-type>
+ <label>Arming State</label>
+ <description>The arming state of the intrusion detection system. Possible values are SYSTEM_ARMING, SYSTEM_ARMED and
+ SYSTEM_DISARMED. This channel is read-only. Use the arm-action and disarm-action channels to arm and disarm the
+ system.</description>
+ <state readOnly="true">
+ <options>
+ <option value="SYSTEM_ARMING">System is currently arming</option>
+ <option value="SYSTEM_ARMED">System is armed</option>
+ <option value="SYSTEM_DISARMED">System is disarmed</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="alarm-state">
+ <item-type>String</item-type>
+ <label>Alarm State</label>
+ <description>The alarm state of the intrusion detection system. Possible values are ALARM_OFF, PRE_ALARM, ALARM_ON,
+ ALARM_MUTED and UNKNOWN.</description>
+ <state readOnly="true">
+ <options>
+ <option value="ALARM_OFF">No alarm</option>
+ <option value="PRE_ALARM">Alarm is about to go off</option>
+ <option value="ALARM_ON">Alarm was triggered</option>
+ <option value="ALARM_MUTED">Alarm is muted</option>
+ <option value="UNKNOWN">Alarm status is unknown</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="active-configuration-profile">
+ <item-type>String</item-type>
+ <label>Active Configuration Profile</label>
+ <description>The name of the active configuration profile used for the intrusion detection system.</description>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="arm-action">
+ <item-type>String</item-type>
+ <label>Arm Action</label>
+ <description>Arms the intrusion detection system using the given profile ID.</description>
+ </channel-type>
+
+ <channel-type id="disarm-action">
+ <item-type>Switch</item-type>
+ <label>Disarm Action</label>
+ <description>Disarms the intrusion detection system when an ON command is received.</description>
+ </channel-type>
+
+ <channel-type id="mute-action">
+ <item-type>Switch</item-type>
+ <label>Mute Action</label>
+ <description>Mutes the alarm when an ON command is received.</description>
+ </channel-type>
+
<channel-type id="privacy-mode">
<item-type>Switch</item-type>
<label>Privacy Mode</label>
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.bridge;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpMethod;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
+import org.openhab.core.thing.Bridge;
+
+/**
+ * Unit tests for the {@link BridgeHandler}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@NonNullByDefault
+class BridgeHandlerTest {
+
+ @Nullable
+ private BridgeHandler fixture;
+
+ @Nullable
+ private BoschHttpClient httpClient;
+
+ @BeforeEach
+ void beforeEach() {
+ Bridge bridge = mock(Bridge.class);
+ fixture = new BridgeHandler(bridge);
+ httpClient = mock(BoschHttpClient.class);
+ fixture.httpClient = httpClient;
+ }
+
+ @Test
+ void postAction() throws InterruptedException, TimeoutException, ExecutionException {
+ String endpoint = "/intrusion/actions/arm";
+ String url = "https://127.0.0.1:8444/smarthome/intrusion/actions/arm";
+ when(httpClient.getBoschSmartHomeUrl(endpoint)).thenReturn(url);
+ Request mockRequest = mock(Request.class);
+ when(httpClient.createRequest(anyString(), any(), any())).thenReturn(mockRequest);
+ ArmActionRequest request = new ArmActionRequest();
+ request.profileId = "0";
+
+ fixture.postAction(endpoint, request);
+ verify(httpClient).createRequest(eq(url), same(HttpMethod.POST), same(request));
+ verify(mockRequest).send();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionControlState;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+
+/**
+ * Unit tests for {@link IntrusionDetectionControlStateService}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@ExtendWith(MockitoExtension.class)
+class IntrusionDetectionControlStateServiceTest {
+
+ private IntrusionDetectionControlStateService fixture;
+
+ @Mock
+ private BridgeHandler bridgeHandler;
+
+ @Mock
+ private Consumer<IntrusionDetectionControlState> consumer;
+
+ @Mock
+ private IntrusionDetectionControlState testState;
+
+ @BeforeEach
+ void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+ fixture = new IntrusionDetectionControlStateService();
+ fixture.initialize(bridgeHandler, BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, consumer);
+ }
+
+ @Test
+ void getServiceName() {
+ assertEquals("IntrusionDetectionControl", fixture.getServiceName());
+ }
+
+ @Test
+ void getStateClass() {
+ assertSame(IntrusionDetectionControlState.class, fixture.getStateClass());
+ }
+
+ @Test
+ void getState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+ when(bridgeHandler.getState(anyString(), anyString(), any())).thenReturn(testState);
+ IntrusionDetectionControlState state = fixture.getState();
+ verify(bridgeHandler).getState(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION,
+ "IntrusionDetectionControl", IntrusionDetectionControlState.class);
+ assertSame(testState, state);
+ }
+
+ @Test
+ void setState() throws InterruptedException, TimeoutException, ExecutionException {
+ fixture.setState(testState);
+ verify(bridgeHandler).putState(BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION,
+ "IntrusionDetectionControl", testState);
+ }
+
+ @Test
+ void onStateUpdate() {
+ final String json = "{\n" + "\"@type\": \"intrusionDetectionControlState\",\n" + "\"activeProfile\": \"0\",\n"
+ + "\"alarmActivationDelayTime\": 30,\n" + "\"actuators\": [\n" + "{\n" + "\"readonly\": false,\n"
+ + "\"active\": true,\n" + "\"id\": \"intrusion:video\"\n" + "},\n" + "{\n" + "\"readonly\": false,\n"
+ + "\"active\": false,\n" + "\"id\": \"intrusion:siren\"\n" + "}\n" + "],\n"
+ + "\"remainingTimeUntilArmed\": 28959,\n" + "\"armActivationDelayTime\": 30,\n" + "\"triggers\": [\n"
+ + "{\n" + "\"readonly\": false,\n" + "\"active\": true,\n" + "\"id\": \"hdm:ZigBee:000d6f0422f42378\"\n"
+ + "}\n" + "],\n" + "\"value\": \"SYSTEM_ARMING\"\n" + "}";
+ JsonElement jsonElement = JsonParser.parseString(json);
+ fixture.onStateUpdate(jsonElement);
+ verify(consumer).accept(any());
+ }
+
+ @Test
+ void refreshState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+ when(bridgeHandler.getState(anyString(), anyString(), any())).thenReturn(testState);
+ fixture.refreshState();
+ verify(consumer).accept(testState);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 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.intrusion;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
+import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
+import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
+import org.openhab.binding.boschshc.internal.services.intrusion.dto.IntrusionDetectionSystemState;
+
+/**
+ * Unit tests for {@link IntrusionDetectionSystemStateService}.
+ *
+ * @author David Pace - Initial contribution
+ *
+ */
+@ExtendWith(MockitoExtension.class)
+class IntrusionDetectionSystemStateServiceTest {
+
+ private IntrusionDetectionSystemStateService fixture;
+
+ @Mock
+ private BridgeHandler bridgeHandler;
+
+ @Mock
+ private Consumer<IntrusionDetectionSystemState> consumer;
+
+ @Mock
+ private IntrusionDetectionSystemState testState;
+
+ @BeforeEach
+ void beforeEach() {
+ fixture = new IntrusionDetectionSystemStateService();
+ fixture.initialize(bridgeHandler, BoschSHCBindingConstants.SERVICE_INTRUSION_DETECTION, consumer);
+ }
+
+ @Test
+ void getState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
+ when(bridgeHandler.getState(anyString(), any())).thenReturn(testState);
+ IntrusionDetectionSystemState state = fixture.getState();
+ verify(bridgeHandler).getState("intrusion/states/system", IntrusionDetectionSystemState.class);
+ assertSame(testState, state);
+ }
+}