[boschshc] Support Bosch Smart Home Cameras (#12666) (#12680)

* [boschshc] Support Bosch Smart Home Cameras (#12666)

- add thing definitions for indoor security camera 360 and outdoor
security camera Eyes
- implement services for privacy mode and camera notifications
- implement camera handler
- add unit tests
- update documentation

* [boschshc] Enhance logging and comments (#12666)

* only log on debug level when initial state can not be retrieved
* do not log stack traces
* minor Javadoc fixes

closes #12666

Signed-off-by: David Pace <dev@davidpace.de>
pull/12642/head
Dave 2022-05-06 23:13:04 +02:00 committed by GitHub
parent e9d9149c35
commit 8460054c9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 582 additions and 4 deletions

View File

@ -3,16 +3,17 @@
Binding for the Bosch Smart Home.
- [Bosch Smart Home Binding](#bosch-smart-home-binding)
- [Changelog](#changelog)
- [Supported Things](#supported-things)
- [In-Wall switches & Smart Plugs](#in-wall-switches--smart-plugs)
- [In-Wall switches & Smart Plugs](#in-wall-switches-smart-plugs)
- [TwinGuard smoke detector](#twinguard-smoke-detector)
- [Door/Window contact](#doorwindow-contact)
- [Door/Window contact](#door-window-contact)
- [Motion Detector](#motion-detector)
- [Shutter Control](#shutter-control)
- [Thermostat](#thermostat)
- [Climate Control](#climate-control)
- [Wall Thermostat](#wall-thermostat)
- [Security Camera 360](#security-camera-360)
- [Security Camera Eyes](#security-camera-eyes)
- [Limitations](#limitations)
- [Discovery](#discovery)
- [Bridge Configuration](#bridge-configuration)
@ -115,6 +116,28 @@ Display of the current room temperature as well as the relative humidity in the
| temperature | Number:Temperature | &#9744; | Current measured temperature. |
| humidity | Number:Dimensionless | &#9744; | Current measured humidity (0 to 100). |
### Security Camera 360
Indoor security camera with 360° view and motion detection.
**Thing Type ID**: `security-camera-360`
| Channel Type ID | Item Type | Writable | Description |
| --------------------- | -------------------- | :------: | ------------------------------------------------------------------ |
| privacy-mode | Switch | &#9745; | If privacy mode is enabled, the camera is disabled and vice versa. |
| camera-notification | Switch | &#9745; | Enables or disables notifications for the camera. |
### Security Camera Eyes
Outdoor security camera with motion detection and light.
**Thing Type ID**: `security-camera-eyes`
| Channel Type ID | Item Type | Writable | Description |
| --------------------- | -------------------- | :------: | ------------------------------------------------------------------ |
| privacy-mode | Switch | &#9745; | If privacy mode is enabled, the camera is disabled and vice versa. |
| camera-notification | Switch | &#9745; | Enables or disables notifications for the camera. |
## Limitations
- Discovery of Things

View File

@ -39,6 +39,8 @@ public class BoschSHCBindingConstants {
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");
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");
// List of all Channel IDs
// Auto-generated from thing-types.xml via script, don't modify
@ -59,4 +61,6 @@ public class BoschSHCBindingConstants {
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";
public static final String CHANNEL_PRIVACY_MODE = "privacy-mode";
public static final String CHANNEL_CAMERA_NOTIFICATION = "camera-notification";
}

View File

@ -21,6 +21,7 @@ import java.util.function.Function;
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.devices.camera.CameraHandler;
import org.openhab.binding.boschshc.internal.devices.climatecontrol.ClimateControlHandler;
import org.openhab.binding.boschshc.internal.devices.lightcontrol.LightControlHandler;
import org.openhab.binding.boschshc.internal.devices.motiondetector.MotionDetectorHandler;
@ -69,7 +70,9 @@ public class BoschSHCHandlerFactory extends BaseThingHandlerFactory {
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_WALL_THERMOSTAT, WallThermostatHandler::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));
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {

View File

@ -0,0 +1,166 @@
/**
* 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.camera;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_CAMERA_NOTIFICATION;
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.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationService;
import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationState;
import org.openhab.binding.boschshc.internal.services.cameranotification.dto.CameraNotificationServiceState;
import org.openhab.binding.boschshc.internal.services.privacymode.PrivacyModeService;
import org.openhab.binding.boschshc.internal.services.privacymode.PrivacyModeState;
import org.openhab.binding.boschshc.internal.services.privacymode.dto.PrivacyModeServiceState;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* Handler for security cameras.
* <p>
* This implementation handles services and commands that are common to all cameras, which are currently:
*
* <ul>
* <li><code>PrivacyMode</code> - Controls whether the camera records images</li>
* <li><code>CameraNotification</code> - Enables or disables notifications for the camera</li>
* </ul>
*
* <p>
* The Eyes outdoor camera advertises a <code>CameraLight</code> service, which unfortunately does not work properly.
* Valid states are <code>ON</code> and <code>OFF</code>.
* One of my two cameras returns <code>HTTP 204 (No Content)</code> when requesting the state.
* Once Bosch supports this service properly, a new subclass may be introduced for the Eyes outdoor camera.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class CameraHandler extends BoschSHCHandler {
private PrivacyModeService privacyModeService;
private CameraNotificationService cameraNotificationService;
public CameraHandler(Thing thing) {
super(thing);
this.privacyModeService = new PrivacyModeService();
this.cameraNotificationService = new CameraNotificationService();
}
@Override
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());
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
switch (channelUID.getId()) {
case CHANNEL_PRIVACY_MODE:
if (command instanceof OnOffType) {
updatePrivacyModeState((OnOffType) command);
}
break;
case CHANNEL_CAMERA_NOTIFICATION:
if (command instanceof OnOffType) {
updateCameraNotificationState((OnOffType) command);
}
break;
}
}
private void updatePrivacyModeState(OnOffType command) {
PrivacyModeServiceState serviceState = new PrivacyModeServiceState();
serviceState.value = PrivacyModeState.from(command);
this.updateServiceState(this.privacyModeService, serviceState);
}
private void updateCameraNotificationState(OnOffType command) {
CameraNotificationServiceState serviceState = new CameraNotificationServiceState();
serviceState.value = CameraNotificationState.from(command);
this.updateServiceState(this.cameraNotificationService, serviceState);
}
private void updateChannels(PrivacyModeServiceState state) {
super.updateState(CHANNEL_PRIVACY_MODE, state.value.toOnOffType());
}
private void updateChannels(CameraNotificationServiceState state) {
super.updateState(CHANNEL_CAMERA_NOTIFICATION, state.value.toOnOffType());
}
}

View File

@ -0,0 +1,31 @@
/**
* 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.cameranotification;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.cameranotification.dto.CameraNotificationServiceState;
/**
* Service to enable or disable notifications for security cameras.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class CameraNotificationService extends BoschSHCService<CameraNotificationServiceState> {
public CameraNotificationService() {
super("CameraNotification", CameraNotificationServiceState.class);
}
}

View File

@ -0,0 +1,47 @@
/**
* 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.cameranotification;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.OnOffType;
/**
* Possible states for camera notifications.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public enum CameraNotificationState {
ENABLED,
DISABLED;
/**
* Converts an {@link OnOffType} state into a {@link CameraNotificationState}.
*
* @param onOff the on/off state
* @return the corresponding notification state
*/
public static CameraNotificationState from(OnOffType onOff) {
return onOff == OnOffType.ON ? ENABLED : DISABLED;
}
/**
* Converts this {@link CameraNotificationState} into an {@link OnOffType}.
*
* @return the on/off state corresponding to the notification state of this enumeration literal
*/
public OnOffType toOnOffType() {
return this == ENABLED ? OnOffType.ON : OnOffType.OFF;
}
}

View File

@ -0,0 +1,39 @@
/**
* 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.cameranotification.dto;
import org.openhab.binding.boschshc.internal.services.cameranotification.CameraNotificationState;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* Represents the state of camera notifications as reported by the Smart Home Controller.
*
* @author David Pace - Initial contribution
*
*/
public class CameraNotificationServiceState extends BoschSHCServiceState {
public CameraNotificationServiceState() {
super("cameraNotificationState");
}
/**
* The name of this member has to be <code>value</code>, otherwise JSON requests and responses can not be
* serialized/deserialized. The JSON message looks like this:
*
* <pre>
* {"@type":"cameraNotificationState","value":"ENABLED"}
* </pre>
*/
public CameraNotificationState value;
}

View File

@ -0,0 +1,31 @@
/**
* 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.privacymode;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.privacymode.dto.PrivacyModeServiceState;
/**
* Service to get and set the privacy mode of security cameras.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class PrivacyModeService extends BoschSHCService<PrivacyModeServiceState> {
public PrivacyModeService() {
super("PrivacyMode", PrivacyModeServiceState.class);
}
}

View File

@ -0,0 +1,55 @@
/**
* 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.privacymode;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.OnOffType;
/**
* Possible privacy mode states of security cameras.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public enum PrivacyModeState {
/**
* Privacy mode enabled / camera disabled
*/
ENABLED,
/**
* Privacy mode disabled / camera enabled
*/
DISABLED;
/**
* Converts an {@link OnOffType} state into a {@link PrivacyModeState}.
*
* @param onOff the on/off state
* @return the corresponding privacy mode state
*/
public static PrivacyModeState from(OnOffType onOff) {
return onOff == OnOffType.ON ? ENABLED : DISABLED;
}
/**
* Converts this {@link PrivacyModeState} into an {@link OnOffType}.
*
* @return the on/off state corresponding to the privacy mode state of this enumeration literal
*/
public OnOffType toOnOffType() {
return this == ENABLED ? OnOffType.ON : OnOffType.OFF;
}
}

View File

@ -0,0 +1,39 @@
/**
* 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.privacymode.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
import org.openhab.binding.boschshc.internal.services.privacymode.PrivacyModeState;
/**
* Represents the privacy mode of cameras as reported by the Smart Home Controller.
*
* @author David Pace - Initial contribution
*
*/
public class PrivacyModeServiceState extends BoschSHCServiceState {
public PrivacyModeServiceState() {
super("privacyModeState");
}
/**
* The name of this member has to be <code>value</code>, otherwise JSON requests and responses can not be
* serialized/deserialized. The JSON message looks like this:
*
* <pre>
* {"@type":"privacyModeState","value":"ENABLED"}
* </pre>
*/
public PrivacyModeState value;
}

View File

@ -155,6 +155,64 @@
</thing-type>
<thing-type id="security-camera-360">
<supported-bridge-type-refs>
<bridge-type-ref id="shc"/>
</supported-bridge-type-refs>
<label>Security Camera 360</label>
<description>Indoor security camera with 360° view and motion detection.</description>
<channels>
<channel id="privacy-mode" typeId="privacy-mode"/>
<channel id="camera-notification" typeId="camera-notification"/>
</channels>
<config-description-ref uri="thing-type:boschshc:device"/>
</thing-type>
<thing-type id="security-camera-eyes">
<supported-bridge-type-refs>
<bridge-type-ref id="shc"/>
</supported-bridge-type-refs>
<label>Security Camera Eyes</label>
<description>Outdoor security camera with motion detection and light.</description>
<channels>
<channel id="privacy-mode" typeId="privacy-mode"/>
<channel id="camera-notification" typeId="camera-notification"/>
</channels>
<config-description-ref uri="thing-type:boschshc:device"/>
</thing-type>
<channel-type id="privacy-mode">
<item-type>Switch</item-type>
<label>Privacy Mode</label>
<description>If privacy mode is enabled, the camera is disabled and vice versa.</description>
<state>
<options>
<option value="ENABLED">Privacy mode enabled (camera disabled)</option>
<option value="DISABLED">Privacy mode disabled (camera enabled)</option>
</options>
</state>
</channel-type>
<channel-type id="camera-notification">
<item-type>Switch</item-type>
<label>Camera Notifications</label>
<description>Enables or disables notifications for the camera.</description>
<state>
<options>
<option value="ENABLED">Enable notifications</option>
<option value="DISABLED">Disable notifications</option>
</options>
</state>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>

View File

@ -0,0 +1,41 @@
/**
* 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.cameranotification;
import static org.junit.jupiter.api.Assertions.assertSame;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.core.library.types.OnOffType;
/**
* Unit tests for {@link CameraNotificationState}.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
class CameraNotificationStateTest {
@Test
void testFromOnOffType() {
assertSame(CameraNotificationState.ENABLED, CameraNotificationState.from(OnOffType.ON));
assertSame(CameraNotificationState.DISABLED, CameraNotificationState.from(OnOffType.OFF));
}
@Test
void testToOnOffType() {
assertSame(OnOffType.ON, CameraNotificationState.ENABLED.toOnOffType());
assertSame(OnOffType.OFF, CameraNotificationState.DISABLED.toOnOffType());
}
}

View File

@ -0,0 +1,41 @@
/**
* 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.privacymode;
import static org.junit.jupiter.api.Assertions.assertSame;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.core.library.types.OnOffType;
/**
* Unit tests for {@link PrivacyModeState}.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
class PrivacyModeStateTest {
@Test
void testFromOnOffType() {
assertSame(PrivacyModeState.ENABLED, PrivacyModeState.from(OnOffType.ON));
assertSame(PrivacyModeState.DISABLED, PrivacyModeState.from(OnOffType.OFF));
}
@Test
void testToOnOffType() {
assertSame(OnOffType.ON, PrivacyModeState.ENABLED.toOnOffType());
assertSame(OnOffType.OFF, PrivacyModeState.DISABLED.toOnOffType());
}
}