[boschshc] Support for Smart Water Alarm (#16770)

Adds support for Bosch Smart Water Alarm devices.

* add new thing type and new channel types
* add new services for water detector
* refactor CameraNotificationState and PrivacyModeState to a common enum
EnabledDisabledState that can be re-used in the water detector tilt
service states
* implement handler for new device
* register new device in handler factory and discovery
* add unit tests
* add documentation

Signed-off-by: David Pace <dev@davidpace.de>
This commit is contained in:
David Pace 2024-05-29 16:10:48 +02:00 committed by GitHub
parent 706f9ca6b5
commit 170d81b4b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 962 additions and 131 deletions

View File

@ -26,6 +26,7 @@ Binding for Bosch Smart Home devices.
- [User-defined States](#user-defined-states)
- [Universal Switch](#universal-switch)
- [Universal Switch II](#universal-switch-ii)
- [Water Detector](#water-detector)
- [Limitations](#limitations)
- [Discovery](#discovery)
- [Bridge Configuration](#bridge-configuration)
@ -333,6 +334,23 @@ A universally configurable switch with four buttons.
| key-event-type | String | &#9744; | Indicates how the key was pressed. Possible values are `PRESS_SHORT`, `PRESS_LONG` and `PRESS_LONG_RELEASED`. |
| key-event-timestamp | DateTime | &#9744; | Timestamp indicating when the key was pressed. |
### Water Detector
Smart water leakage detector.
**Thing Type ID**: `water-detector`
| Channel Type ID | Item Type | Writable | Description |
| -------------------------- | --------- | :------: | ------------------------------------------------- |
| battery-level | Number | &#9744; | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. |
| low-battery | Switch | &#9744; | Indicates whether the battery is low (`ON`) or OK (`OFF`). |
| signal-strength | Number | &#9744; | Communication quality between the device and the Smart Home Controller. Possible values range between 0 (unknown) and 4 (best signal strength). |
| water-leakage | Switch | &#9744; | Indicates whether a water leakage was detected. |
| push-notifications | Switch | &#9745; | Indicates whether push notifications are enabled. |
| acoustic-signals | Switch | &#9745; | Indicates whether acoustic signals are enabled. |
| water-leakage-sensor-check | String | &#9744; | Provides the result of the last water leakage sensor check. |
| sensor-moved | Trigger | &#9744; | Triggered when the sensor is moved. |
## Limitations
No major limitation known.

View File

@ -82,7 +82,8 @@ public class BoschShcCommandExtension extends AbstractConsoleCommandExtension im
"childprotection", "communicationquality", "hsbcoloractuator", "humiditylevel", "illuminance",
"intrusion", "keypad", "latestmotion", "multilevelswitch", "powermeter", "powerswitch", "privacymode",
"roomclimatecontrol", "shuttercontact", "shuttercontrol", "silentmode", "smokedetectorcheck",
"temperaturelevel", "userstate", "valvetappet");
"temperaturelevel", "userstate", "valvetappet", "waterleakagesensor", "waterleakagesensorcheck",
"waterleakagesensortilt");
}
@Override

View File

@ -28,6 +28,10 @@ import org.openhab.core.thing.ThingTypeUID;
@NonNullByDefault
public class BoschSHCBindingConstants {
private BoschSHCBindingConstants() {
// Class containing constants only
}
public static final String BINDING_ID = "boschshc";
// List of all Thing Type UIDs
@ -55,6 +59,7 @@ public class BoschSHCBindingConstants {
public static final ThingTypeUID THING_TYPE_SMOKE_DETECTOR_2 = new ThingTypeUID(BINDING_ID, "smoke-detector-2");
public static final ThingTypeUID THING_TYPE_LIGHT_CONTROL_2 = new ThingTypeUID(BINDING_ID, "light-control-2");
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
public static final ThingTypeUID THING_TYPE_WATER_DETECTOR = new ThingTypeUID(BINDING_ID, "water-detector");
public static final ThingTypeUID THING_TYPE_USER_DEFINED_STATE = new ThingTypeUID(BINDING_ID, "user-defined-state");
@ -102,6 +107,11 @@ public class BoschSHCBindingConstants {
public static final String CHANNEL_KEY_NAME = "key-name";
public static final String CHANNEL_KEY_EVENT_TYPE = "key-event-type";
public static final String CHANNEL_KEY_EVENT_TIMESTAMP = "key-event-timestamp";
public static final String CHANNEL_WATER_LEAKAGE = "water-leakage";
public static final String CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE = "push-notifications-on-move";
public static final String CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE = "acoustic-signals-on-move";
public static final String CHANNEL_WATER_LEAKAGE_SENSOR_CHECK = "water-leakage-sensor-check";
public static final String CHANNEL_SENSOR_MOVED = "sensor-moved";
// numbered channels
// the rationale for introducing numbered channels was discussed in

View File

@ -23,6 +23,7 @@ import java.util.function.Supplier;
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.bridge.dto.Message;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.AbstractBoschSHCService;
import org.openhab.binding.boschshc.internal.services.AbstractStatelessBoschSHCService;
@ -175,6 +176,15 @@ public abstract class BoschSHCHandler extends BaseThingHandler {
// default implementation is empty, subclasses may override
}
/**
* Processes a device-specific message from the Bosch Smart Home Controller.
*
* @param message the message published by the controller
*/
public void processMessage(Message message) {
// default implementation is empty, subclasses may override
}
/**
* Use this method to register all services of the device with
* {@link #registerService(BoschSHCService, Consumer, Collection, boolean)}.

View File

@ -33,6 +33,7 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_UNIVERSAL_SWITCH_2;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_USER_DEFINED_STATE;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WALL_THERMOSTAT;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WATER_DETECTOR;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_WINDOW_CONTACT_2;
@ -62,6 +63,7 @@ import org.openhab.binding.boschshc.internal.devices.universalswitch.UniversalSw
import org.openhab.binding.boschshc.internal.devices.universalswitch.UniversalSwitchHandler;
import org.openhab.binding.boschshc.internal.devices.userdefinedstate.UserStateHandler;
import org.openhab.binding.boschshc.internal.devices.wallthermostat.WallThermostatHandler;
import org.openhab.binding.boschshc.internal.devices.waterleakage.WaterLeakageSensorHandler;
import org.openhab.binding.boschshc.internal.devices.windowcontact.WindowContact2Handler;
import org.openhab.binding.boschshc.internal.devices.windowcontact.WindowContactHandler;
import org.openhab.core.i18n.TimeZoneProvider;
@ -132,7 +134,8 @@ public class BoschSHCHandlerFactory extends BaseThingHandlerFactory {
thing -> new UniversalSwitch2Handler(thing, timeZoneProvider)),
new ThingTypeHandlerMapping(THING_TYPE_SMOKE_DETECTOR_2, SmokeDetector2Handler::new),
new ThingTypeHandlerMapping(THING_TYPE_LIGHT_CONTROL_2, LightControl2Handler::new),
new ThingTypeHandlerMapping(THING_TYPE_DIMMER, DimmerHandler::new));
new ThingTypeHandlerMapping(THING_TYPE_DIMMER, DimmerHandler::new),
new ThingTypeHandlerMapping(THING_TYPE_WATER_DETECTOR, WaterLeakageSensorHandler::new));
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {

View File

@ -12,7 +12,9 @@
*/
package org.openhab.binding.boschshc.internal.devices.bridge;
import static org.eclipse.jetty.http.HttpMethod.*;
import static org.eclipse.jetty.http.HttpMethod.GET;
import static org.eclipse.jetty.http.HttpMethod.POST;
import static org.eclipse.jetty.http.HttpMethod.PUT;
import java.lang.reflect.Type;
import java.util.ArrayList;
@ -40,6 +42,7 @@ import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
@ -488,10 +491,44 @@ public class BridgeHandler extends BaseBridgeHandler {
if (channel != null && isLinked(channel.getUID())) {
updateState(channel.getUID(), new StringType(scenario.name));
}
} else if (serviceState instanceof Message message) {
handleMessage(message);
}
}
}
private void handleMessage(Message message) {
if (Message.SOURCE_TYPE_DEVICE.equals(message.sourceType) && message.sourceId != null) {
forwardMessageToDevice(message, message.sourceId);
}
}
private void forwardMessageToDevice(Message message, String deviceId) {
BoschSHCHandler deviceHandler = findDeviceHandler(deviceId);
if (deviceHandler == null) {
return;
}
deviceHandler.processMessage(message);
}
@Nullable
private BoschSHCHandler findDeviceHandler(String deviceIdToFind) {
for (Thing childThing : getThing().getThings()) {
@Nullable
ThingHandler baseHandler = childThing.getHandler();
if (baseHandler instanceof BoschSHCHandler handler) {
@Nullable
String deviceId = handler.getBoschID();
if (deviceIdToFind.equals(deviceId)) {
return handler;
}
}
}
return null;
}
/**
* Processes a single long poll result.
*

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2024 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.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* DTO for messages sent by the Smart Home Controller.
* <p>
* JSON Example:
*
* <pre>
* {
* "result": [{
* "sourceId": "hdm:ZigBee:5cc7c1f6fe11fc23",
* "sourceType": "DEVICE",
* "@type": "message",
* "flags": [],
* "messageCode": {
* "name": "TILT_DETECTED",
* "category": "WARNING"
* },
* "location": "Kitchen",
* "arguments": {
* "deviceModel": "WLS"
* },
* "id": "3499a60e-45b5-4c29-ae1a-202c2182970c",
* "sourceName": "Bosch_water_detector_1",
* "timestamp": 1714375556426
* }],
* "jsonrpc": "2.0"
* }
* </pre>
*
* @author David Pace - Initial contribution
*/
public class Message extends BoschSHCServiceState {
/**
* Source type indicating that a message is device-specific
*/
public static final String SOURCE_TYPE_DEVICE = "DEVICE";
public Message() {
super("message");
}
public String id;
public String sourceId;
public String sourceName;
public String sourceType;
public String location;
public long timestamp;
public MessageCode messageCode;
}

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2024 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.dto;
/**
* DTO for message codes sent by the Smart Home Controller.
* <p>
* JSON Example:
*
* <pre>
* {
* "name": "TILT_DETECTED",
* "category": "WARNING"
* }
* </pre>
*
* @author David Pace - Initial contribution
*/
public class MessageCode {
public String name;
public String category;
}

View File

@ -12,7 +12,8 @@
*/
package org.openhab.binding.boschshc.internal.devices.camera;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*;
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;
@ -20,10 +21,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
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;
import org.openhab.binding.boschshc.internal.services.cameranotification.dto.CameraNotificationServiceState;
import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
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;
@ -91,13 +91,13 @@ public class CameraHandler extends BoschSHCDeviceHandler {
private void updatePrivacyModeState(OnOffType command) {
PrivacyModeServiceState serviceState = new PrivacyModeServiceState();
serviceState.value = PrivacyModeState.from(command);
serviceState.value = EnabledDisabledState.from(command);
this.updateServiceState(this.privacyModeService, serviceState);
}
private void updateCameraNotificationState(OnOffType command) {
CameraNotificationServiceState serviceState = new CameraNotificationServiceState();
serviceState.value = CameraNotificationState.from(command);
serviceState.value = EnabledDisabledState.from(command);
this.updateServiceState(this.cameraNotificationService, serviceState);
}

View File

@ -0,0 +1,160 @@
/**
* Copyright (c) 2010-2024 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.waterleakage;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SENSOR_MOVED;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SIGNAL_STRENGTH;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE_SENSOR_CHECK;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandler;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.communicationquality.CommunicationQualityService;
import org.openhab.binding.boschshc.internal.services.communicationquality.dto.CommunicationQualityServiceState;
import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
import org.openhab.binding.boschshc.internal.services.waterleakagesensor.WaterLeakageSensorService;
import org.openhab.binding.boschshc.internal.services.waterleakagesensor.dto.WaterLeakageSensorServiceState;
import org.openhab.binding.boschshc.internal.services.waterleakagesensorcheck.WaterLeakageSensorCheckService;
import org.openhab.binding.boschshc.internal.services.waterleakagesensorcheck.dto.WaterLeakageSensorCheckServiceState;
import org.openhab.binding.boschshc.internal.services.waterleakagesensortilt.WaterLeakageSensorTiltService;
import org.openhab.binding.boschshc.internal.services.waterleakagesensortilt.dto.WaterLeakageSensorTiltServiceState;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for water leakage sensors.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class WaterLeakageSensorHandler extends AbstractBatteryPoweredDeviceHandler {
private final Logger logger = LoggerFactory.getLogger(WaterLeakageSensorHandler.class);
/**
* Message code indicating that the water leakage sensor was moved
*/
public static final String MESSAGE_CODE_TILT_DETECTED = "TILT_DETECTED";
private WaterLeakageSensorTiltService waterLeakageSensorTiltService;
private WaterLeakageSensorCheckService waterLeakageSensorCheckService;
@Nullable
private WaterLeakageSensorTiltServiceState currentWaterSensorTiltServiceState;
public WaterLeakageSensorHandler(Thing thing) {
super(thing);
this.waterLeakageSensorTiltService = new WaterLeakageSensorTiltService();
this.waterLeakageSensorCheckService = new WaterLeakageSensorCheckService();
}
@Override
protected void initializeServices() throws BoschSHCException {
super.initializeServices();
this.createService(WaterLeakageSensorService::new, this::updateChannels, List.of(CHANNEL_WATER_LEAKAGE), true);
this.registerService(waterLeakageSensorTiltService, this::updateChannels,
List.of(CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE, CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE), true);
this.registerService(waterLeakageSensorCheckService, this::updateChannels, List.of());
this.createService(CommunicationQualityService::new, this::updateChannels, List.of(CHANNEL_SIGNAL_STRENGTH),
true);
}
private void updateChannels(WaterLeakageSensorServiceState waterLeakageSensorServiceState) {
updateState(CHANNEL_WATER_LEAKAGE, waterLeakageSensorServiceState.state.toOnOffType());
}
private void updateChannels(WaterLeakageSensorTiltServiceState waterLeakageSensorTiltServiceState) {
currentWaterSensorTiltServiceState = waterLeakageSensorTiltServiceState;
updateState(CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE,
waterLeakageSensorTiltServiceState.pushNotificationState.toOnOffType());
updateState(CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE,
waterLeakageSensorTiltServiceState.acousticSignalState.toOnOffType());
}
private void updateChannels(WaterLeakageSensorCheckServiceState waterLeakageSensorCheckServiceState) {
updateState(CHANNEL_WATER_LEAKAGE_SENSOR_CHECK, new StringType(waterLeakageSensorCheckServiceState.result));
}
private void updateChannels(CommunicationQualityServiceState communicationQualityServiceState) {
updateState(CHANNEL_SIGNAL_STRENGTH, communicationQualityServiceState.quality.toSystemSignalStrength());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
if (CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE.equals(channelUID.getId())
&& command instanceof OnOffType onOffCommand) {
updatePushNotificationState(onOffCommand);
} else if (CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE.equals(channelUID.getId())
&& command instanceof OnOffType onOffCommand) {
updateAcousticSignalState(onOffCommand);
}
}
private void updatePushNotificationState(OnOffType onOffCommand) {
WaterLeakageSensorTiltServiceState newState = cloneCurrentWaterLeakageSensorTiltServiceState();
if (newState != null) {
newState.pushNotificationState = EnabledDisabledState.from(onOffCommand);
this.currentWaterSensorTiltServiceState = newState;
updateServiceState(waterLeakageSensorTiltService, newState);
}
}
private void updateAcousticSignalState(OnOffType onOffCommand) {
WaterLeakageSensorTiltServiceState newState = cloneCurrentWaterLeakageSensorTiltServiceState();
if (newState != null) {
newState.acousticSignalState = EnabledDisabledState.from(onOffCommand);
this.currentWaterSensorTiltServiceState = newState;
updateServiceState(waterLeakageSensorTiltService, newState);
}
}
@Nullable
private WaterLeakageSensorTiltServiceState cloneCurrentWaterLeakageSensorTiltServiceState() {
if (currentWaterSensorTiltServiceState != null) {
WaterLeakageSensorTiltServiceState clonedState = new WaterLeakageSensorTiltServiceState();
clonedState.acousticSignalState = currentWaterSensorTiltServiceState.acousticSignalState;
clonedState.pushNotificationState = currentWaterSensorTiltServiceState.pushNotificationState;
return clonedState;
} else {
logger.warn("Could not obtain current water leakage detector tilt state, command will not be processed.");
}
return null;
}
@Override
public void processMessage(Message message) {
super.processMessage(message);
if (message.messageCode != null && MESSAGE_CODE_TILT_DETECTED.equals(message.messageCode.name)) {
triggerChannel(CHANNEL_SENSOR_MOVED);
}
}
}

View File

@ -98,7 +98,8 @@ public class ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<
new AbstractMap.SimpleEntry<>("MICROMODULE_SHUTTER", BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL_2),
new AbstractMap.SimpleEntry<>("MICROMODULE_AWNING", BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL_2),
new AbstractMap.SimpleEntry<>("MICROMODULE_LIGHT_CONTROL", BoschSHCBindingConstants.THING_TYPE_LIGHT_CONTROL_2),
new AbstractMap.SimpleEntry<>("MICROMODULE_DIMMER", BoschSHCBindingConstants.THING_TYPE_DIMMER)
new AbstractMap.SimpleEntry<>("MICROMODULE_DIMMER", BoschSHCBindingConstants.THING_TYPE_DIMMER),
new AbstractMap.SimpleEntry<>("WLS", BoschSHCBindingConstants.THING_TYPE_WATER_DETECTOR)
// Future Extension: map deviceModel names to BoschSHC Thing Types when they are supported
// new AbstractMap.SimpleEntry<>("SMOKE_DETECTION_SYSTEM", BoschSHCBindingConstants.),
// new AbstractMap.SimpleEntry<>("PRESENCE_SIMULATION_SERVICE", BoschSHCBindingConstants.),

View File

@ -17,6 +17,7 @@ import java.lang.reflect.Type;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
@ -65,6 +66,9 @@ public class BoschServiceDataDeserializer implements JsonDeserializer<BoschSHCSe
state.setState(jsonObject.get("state").getAsBoolean());
return state;
}
case "message" -> {
return GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(jsonElement, Message.class);
}
default -> {
return new BoschSHCServiceState(dataType.getAsString());
}

View File

@ -12,8 +12,8 @@
*/
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;
import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
/**
* Represents the state of camera notifications as reported by the Smart Home Controller.
@ -35,5 +35,5 @@ public class CameraNotificationServiceState extends BoschSHCServiceState {
* {"@type":"cameraNotificationState","value":"ENABLED"}
* </pre>
*/
public CameraNotificationState value;
public EnabledDisabledState value;
}

View File

@ -10,36 +10,36 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.boschshc.internal.services.cameranotification;
package org.openhab.binding.boschshc.internal.services.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.OnOffType;
/**
* Possible states for camera notifications.
* State that is serialized as either <code>ENABLED</code> or <code>DISABLED</code>.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public enum CameraNotificationState {
public enum EnabledDisabledState {
ENABLED,
DISABLED;
/**
* Converts an {@link OnOffType} state into a {@link CameraNotificationState}.
* Converts an {@link OnOffType} state into a {@link EnabledDisabledState}.
*
* @param onOff the on/off state
* @return the corresponding notification state
* @return the corresponding enabled/disabled state
*/
public static CameraNotificationState from(OnOffType onOff) {
public static EnabledDisabledState from(OnOffType onOff) {
return onOff == OnOffType.ON ? ENABLED : DISABLED;
}
/**
* Converts this {@link CameraNotificationState} into an {@link OnOffType}.
* Converts this {@link EnabledDisabledState} into an {@link OnOffType}.
*
* @return the on/off state corresponding to the notification state of this enumeration literal
* @return the on/off state corresponding to the enabled/disabled state of this enumeration literal
*/
public OnOffType toOnOffType() {
return OnOffType.from(this == ENABLED);

View File

@ -1,55 +0,0 @@
/**
* Copyright (c) 2010-2024 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 OnOffType.from(this == ENABLED);
}
}

View File

@ -13,7 +13,7 @@
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;
import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
/**
* Represents the privacy mode of cameras as reported by the Smart Home Controller.
@ -35,5 +35,5 @@ public class PrivacyModeServiceState extends BoschSHCServiceState {
* {"@type":"privacyModeState","value":"ENABLED"}
* </pre>
*/
public PrivacyModeState value;
public EnabledDisabledState value;
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2024 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.waterleakagesensor;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.waterleakagesensor.dto.WaterLeakageSensorServiceState;
/**
* Service for the water leakage sensor.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class WaterLeakageSensorService extends BoschSHCService<WaterLeakageSensorServiceState> {
public WaterLeakageSensorService() {
super("WaterLeakageSensor", WaterLeakageSensorServiceState.class);
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2024 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.waterleakagesensor.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* Water leakage detection state of water detectors.
*
* @author David Pace - Initial contribution
*
*/
public class WaterLeakageSensorServiceState extends BoschSHCServiceState {
public WaterLeakageSensorServiceState() {
super("waterLeakageSensorState");
}
public WaterLeakageState state;
}

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2024 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.waterleakagesensor.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.OnOffType;
/**
* Possible states of the water leakage sensor.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public enum WaterLeakageState {
NO_LEAKAGE,
LEAKAGE_DETECTED;
public OnOffType toOnOffType() {
return OnOffType.from(this == LEAKAGE_DETECTED);
}
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2024 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.waterleakagesensorcheck;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.waterleakagesensorcheck.dto.WaterLeakageSensorCheckServiceState;
/**
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class WaterLeakageSensorCheckService extends BoschSHCService<WaterLeakageSensorCheckServiceState> {
public WaterLeakageSensorCheckService() {
super("WaterLeakageSensorCheck", WaterLeakageSensorCheckServiceState.class);
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2024 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.waterleakagesensorcheck.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* DTO for the check state of water leakage sensors.
*
* @author David Pace - Initial contribution
*
*/
public class WaterLeakageSensorCheckServiceState extends BoschSHCServiceState {
public WaterLeakageSensorCheckServiceState() {
super("waterLeakageSensorCheckState");
}
public String result;
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2024 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.waterleakagesensortilt;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.waterleakagesensortilt.dto.WaterLeakageSensorTiltServiceState;
/**
* Service for notifications relating to the water detection sensor.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class WaterLeakageSensorTiltService extends BoschSHCService<WaterLeakageSensorTiltServiceState> {
public WaterLeakageSensorTiltService() {
super("WaterLeakageSensorTilt", WaterLeakageSensorTiltServiceState.class);
}
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2024 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.waterleakagesensortilt.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
/**
* Service for notifications relating to the water detection sensor.
*
* @author David Pace - Initial contribution
*
*/
public class WaterLeakageSensorTiltServiceState extends BoschSHCServiceState {
public WaterLeakageSensorTiltServiceState() {
super("waterLeakageSensorTiltState");
}
public EnabledDisabledState pushNotificationState;
public EnabledDisabledState acousticSignalState;
}

View File

@ -47,6 +47,8 @@ thing-type.boschshc.user-defined-state.label = User-defined State
thing-type.boschshc.user-defined-state.description = A User-defined state.
thing-type.boschshc.wall-thermostat.label = Wall Thermostat
thing-type.boschshc.wall-thermostat.description = Display of the current room temperature as well as the relative humidity in the room.
thing-type.boschshc.water-detector.label = Water Detector
thing-type.boschshc.water-detector.description = Smart sensor to detect water leakage.
thing-type.boschshc.window-contact-2.label = Door/Window Contact II
thing-type.boschshc.window-contact-2.description = Detects open windows and doors and features an additional button.
thing-type.boschshc.window-contact.label = Door/Window Contact
@ -65,6 +67,10 @@ thing-type.config.boschshc.user-defined-state.id.description = Unique ID of the
# channel types
channel-type.boschshc.acoustic-signals-on-move.label = Acoustic Signals When Moved
channel-type.boschshc.acoustic-signals-on-move.description = Indicates whether acoustic signals are enabled when the sensor is moved.
channel-type.boschshc.acoustic-signals-on-move.state.option.ON = Enabled
channel-type.boschshc.acoustic-signals-on-move.state.option.OFF = Disabled
channel-type.boschshc.active-configuration-profile.label = Active Configuration Profile
channel-type.boschshc.active-configuration-profile.description = The name of the active configuration profile used for the intrusion detection system.
channel-type.boschshc.air-description.label = Description
@ -148,8 +154,14 @@ channel-type.boschshc.purity-rating.label = Purity Rating
channel-type.boschshc.purity-rating.description = Rating of the air purity.
channel-type.boschshc.purity.label = Purity
channel-type.boschshc.purity.description = Purity of the air. A higher value indicates a higher pollution.
channel-type.boschshc.push-notifications-on-move.label = Push Notifications When Moved
channel-type.boschshc.push-notifications-on-move.description = Indicates whether push notifications are enabled when the sensor is moved.
channel-type.boschshc.push-notifications-on-move.state.option.ON = Enabled
channel-type.boschshc.push-notifications-on-move.state.option.OFF = Disabled
channel-type.boschshc.scenario-triggered.label = Scenario Triggered
channel-type.boschshc.scenario-triggered.description = Name of the triggered scenario
channel-type.boschshc.sensor-moved.label = Sensor Moved
channel-type.boschshc.sensor-moved.description = Triggered when the sensor is moved.
channel-type.boschshc.setpoint-temperature.label = Setpoint Temperature
channel-type.boschshc.setpoint-temperature.description = Desired temperature.
channel-type.boschshc.silent-mode.label = Silent Mode
@ -177,6 +189,12 @@ channel-type.boschshc.user-state.label = State
channel-type.boschshc.user-state.description = State of user-defined state
channel-type.boschshc.valve-tappet-position.label = Valve Tappet Position
channel-type.boschshc.valve-tappet-position.description = Current open ratio (0 to 100).
channel-type.boschshc.water-leakage-sensor-check.label = Water Leakage Sensor Check
channel-type.boschshc.water-leakage-sensor-check.description = Provides the result of the last water leakage sensor check.
channel-type.boschshc.water-leakage.label = Water Leakage
channel-type.boschshc.water-leakage.description = Indicates whether a water leakage was detected.
channel-type.boschshc.water-leakage.state.option.ON = Leakage detected
channel-type.boschshc.water-leakage.state.option.OFF = No leakage detected
# thing status offline descriptions

View File

@ -439,6 +439,28 @@
<config-description-ref uri="thing-type:boschshc:device"/>
</thing-type>
<thing-type id="water-detector">
<supported-bridge-type-refs>
<bridge-type-ref id="shc"/>
</supported-bridge-type-refs>
<label>Water Detector</label>
<description>Smart sensor to detect water leakage.</description>
<channels>
<channel id="water-leakage" typeId="water-leakage"/>
<channel id="push-notifications-on-move" typeId="push-notifications-on-move"/>
<channel id="acoustic-signals-on-move" typeId="acoustic-signals-on-move"/>
<channel id="water-leakage-sensor-check" typeId="water-leakage-sensor-check"/>
<channel id="sensor-moved" typeId="sensor-moved"/>
<channel id="battery-level" typeId="system.battery-level"/>
<channel id="low-battery" typeId="system.low-battery"/>
<channel id="signal-strength" typeId="system.signal-strength"/>
</channels>
<config-description-ref uri="thing-type:boschshc:device"/>
</thing-type>
<!-- Channels -->
<channel-type id="system-availability">
@ -769,4 +791,58 @@
<description>Enables or disables the child protection on the device.</description>
</channel-type>
<channel-type id="water-leakage">
<item-type>Switch</item-type>
<label>Water Leakage</label>
<description>Indicates whether a water leakage was detected.</description>
<state readOnly="true">
<options>
<option value="ON">Leakage detected</option>
<option value="OFF">No leakage detected</option>
</options>
</state>
</channel-type>
<channel-type id="push-notifications-on-move">
<item-type>Switch</item-type>
<label>Push Notifications When Moved</label>
<description>Indicates whether push notifications are enabled when the sensor is moved.</description>
<state>
<options>
<option value="ON">Enabled</option>
<option value="OFF">Disabled</option>
</options>
</state>
</channel-type>
<channel-type id="acoustic-signals-on-move">
<item-type>Switch</item-type>
<label>Acoustic Signals When Moved</label>
<description>Indicates whether acoustic signals are enabled when the sensor is moved.</description>
<state>
<options>
<option value="ON">Enabled</option>
<option value="OFF">Disabled</option>
</options>
</state>
</channel-type>
<channel-type id="water-leakage-sensor-check">
<item-type>String</item-type>
<label>Water Leakage Sensor Check</label>
<description>Provides the result of the last water leakage sensor check.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="sensor-moved">
<kind>trigger</kind>
<label>Sensor Moved</label>
<description>Triggered when the sensor is moved.</description>
<category>Alarm</category>
<tags>
<tag>Alarm</tag>
<tag>Tilt</tag>
</tags>
</channel-type>
</thing:thing-descriptions>

View File

@ -65,6 +65,7 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceDat
import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceTest;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Faults;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.PublicInformation;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
@ -738,6 +739,51 @@ class BridgeHandlerTest {
verify(thingHandler).processChildUpdate("hdm:ZigBee:70ac08fffefead2d#3", "PowerSwitch", expectedState);
}
@Test
void handleLongPollResultHandleMessage() {
List<Thing> things = new ArrayList<Thing>();
when(thing.getThings()).thenReturn(things);
Thing thing = mock(Thing.class);
things.add(thing);
BoschSHCHandler thingHandler = mock(BoschSHCHandler.class);
when(thing.getHandler()).thenReturn(thingHandler);
when(thingHandler.getBoschID()).thenReturn("hdm:ZigBee:5cc7c1fffe1f7967");
String json = """
{
"result": [{
"sourceId": "hdm:ZigBee:5cc7c1fffe1f7967",
"sourceType": "DEVICE",
"@type": "message",
"flags": [],
"messageCode": {
"name": "TILT_DETECTED",
"category": "WARNING"
},
"location": "Kitchen",
"arguments": {
"deviceModel": "WLS"
},
"id": "3499a60e-45b5-4c29-ae1a-202c2182970c",
"sourceName": "Bosch_water_detector_1",
"timestamp": 1714375556426
}],
"jsonrpc": "2.0"
}
""";
LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, LongPollResult.class);
assertNotNull(longPollResult);
fixture.handleLongPollResult(longPollResult);
Message expectedMessage = (Message) longPollResult.result.get(0);
verify(thingHandler).processMessage(expectedMessage);
}
@Test
void handleLongPollResultScenarioTriggered() {
Channel channel = mock(Channel.class);

View File

@ -14,7 +14,8 @@ package org.openhab.binding.boschshc.internal.devices.camera;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
@ -26,9 +27,8 @@ import org.mockito.Captor;
import org.openhab.binding.boschshc.internal.devices.AbstractBoschSHCDeviceHandlerTest;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
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.PrivacyModeState;
import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
import org.openhab.binding.boschshc.internal.services.privacymode.dto.PrivacyModeServiceState;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -73,14 +73,14 @@ class CameraHandlerTest extends AbstractBoschSHCDeviceHandlerTest<CameraHandler>
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("PrivacyMode"),
privacyModeServiceStateCaptor.capture());
PrivacyModeServiceState state = privacyModeServiceStateCaptor.getValue();
assertSame(PrivacyModeState.ENABLED, state.value);
assertSame(EnabledDisabledState.ENABLED, state.value);
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PRIVACY_MODE),
OnOffType.OFF);
verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("PrivacyMode"),
privacyModeServiceStateCaptor.capture());
state = privacyModeServiceStateCaptor.getValue();
assertSame(PrivacyModeState.DISABLED, state.value);
assertSame(EnabledDisabledState.DISABLED, state.value);
}
@Test
@ -92,7 +92,7 @@ class CameraHandlerTest extends AbstractBoschSHCDeviceHandlerTest<CameraHandler>
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("CameraNotification"),
cameraNotificationServiceStateCaptor.capture());
CameraNotificationServiceState state = cameraNotificationServiceStateCaptor.getValue();
assertSame(CameraNotificationState.ENABLED, state.value);
assertSame(EnabledDisabledState.ENABLED, state.value);
getFixture().handleCommand(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CAMERA_NOTIFICATION),
@ -100,7 +100,7 @@ class CameraHandlerTest extends AbstractBoschSHCDeviceHandlerTest<CameraHandler>
verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("CameraNotification"),
cameraNotificationServiceStateCaptor.capture());
state = cameraNotificationServiceStateCaptor.getValue();
assertSame(CameraNotificationState.DISABLED, state.value);
assertSame(EnabledDisabledState.DISABLED, state.value);
}
@Test

View File

@ -0,0 +1,229 @@
/**
* Copyright (c) 2010-2024 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.waterleakage;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.openhab.binding.boschshc.internal.devices.AbstractBatteryPoweredDeviceHandlerTest;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Message;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.MessageCode;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.dto.EnabledDisabledState;
import org.openhab.binding.boschshc.internal.services.waterleakagesensortilt.dto.WaterLeakageSensorTiltServiceState;
import org.openhab.core.library.types.DecimalType;
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.ThingTypeUID;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
/**
* Unit tests for {@link WaterLeakageSensorHandler}.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
class WaterLeakageSensorHandlerTest extends AbstractBatteryPoweredDeviceHandlerTest<WaterLeakageSensorHandler> {
private @Captor @NonNullByDefault({}) ArgumentCaptor<WaterLeakageSensorTiltServiceState> waterLeakageTiltServiceStateCaptor;
@Override
protected WaterLeakageSensorHandler createFixture() {
return new WaterLeakageSensorHandler(getThing());
}
@Override
protected ThingTypeUID getThingTypeUID() {
return BoschSHCBindingConstants.THING_TYPE_WATER_DETECTOR;
}
@Override
protected String getDeviceID() {
return "hdm:ZigBee:f0d1b80001d639d5";
}
@Test
void updateChannelsWaterLeakageSensorServiceState() {
JsonElement jsonObject = JsonParser
.parseString("{\"@type\":\"waterLeakageSensorState\",\"state\": \"NO_LEAKAGE\"}");
getFixture().processUpdate("WaterLeakageSensor", jsonObject);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE), OnOffType.OFF);
jsonObject = JsonParser.parseString("{\"@type\":\"waterLeakageSensorState\",\"state\": \"LEAKAGE_DETECTED\"}");
getFixture().processUpdate("WaterLeakageSensor", jsonObject);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE), OnOffType.ON);
}
@Test
void updateChannelsWaterLeakageSensorTiltServiceState() {
JsonElement jsonObject = JsonParser.parseString(
"{\"@type\":\"waterLeakageSensorTiltState\",\"pushNotificationState\": \"DISABLED\",\"acousticSignalState\": \"DISABLED\"}");
getFixture().processUpdate("WaterLeakageSensorTilt", jsonObject);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
OnOffType.OFF);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE),
OnOffType.OFF);
jsonObject = JsonParser.parseString(
"{\"@type\":\"waterLeakageSensorTiltState\",\"pushNotificationState\": \"ENABLED\",\"acousticSignalState\": \"ENABLED\"}");
getFixture().processUpdate("WaterLeakageSensorTilt", jsonObject);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
OnOffType.ON);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE),
OnOffType.ON);
}
@Test
void testUpdateChannelsCommunicationQualityService() {
String json = """
{
"@type": "communicationQualityState",
"quality": "UNKNOWN"
}
""";
JsonElement jsonObject = JsonParser.parseString(json);
getFixture().processUpdate("CommunicationQuality", jsonObject);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SIGNAL_STRENGTH),
new DecimalType(0));
json = """
{
"@type": "communicationQualityState",
"quality": "GOOD"
}
""";
jsonObject = JsonParser.parseString(json);
getFixture().processUpdate("CommunicationQuality", jsonObject);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SIGNAL_STRENGTH),
new DecimalType(4));
}
@Test
void updateChannelsWaterLeakageSensorCheckServiceState() {
JsonElement jsonObject = JsonParser
.parseString("{\"@type\":\"waterLeakageSensorCheckState\",\"result\": \"OK\"}");
getFixture().processUpdate("WaterLeakageSensorCheck", jsonObject);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_WATER_LEAKAGE_SENSOR_CHECK),
new StringType("OK"));
}
@Test
void testHandleCommandPushNotificationsNoPreviousState()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
getFixture().handleCommand(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
OnOffType.ON);
verify(getBridgeHandler(), times(0)).putState(any(), any(), any());
}
@Test
void testHandleCommandPushNotifications()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
JsonElement jsonObject = JsonParser.parseString(
"{\"@type\":\"waterLeakageSensorTiltState\",\"pushNotificationState\": \"DISABLED\",\"acousticSignalState\": \"DISABLED\"}");
getFixture().processUpdate("WaterLeakageSensorTilt", jsonObject);
getFixture().handleCommand(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
OnOffType.ON);
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("WaterLeakageSensorTilt"),
waterLeakageTiltServiceStateCaptor.capture());
WaterLeakageSensorTiltServiceState state = waterLeakageTiltServiceStateCaptor.getValue();
assertSame(EnabledDisabledState.ENABLED, state.pushNotificationState);
assertSame(EnabledDisabledState.DISABLED, state.acousticSignalState);
}
@Test
void testHandleCommandAcousticSignals()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
JsonElement jsonObject = JsonParser.parseString(
"{\"@type\":\"waterLeakageSensorTiltState\",\"pushNotificationState\": \"DISABLED\",\"acousticSignalState\": \"DISABLED\"}");
getFixture().processUpdate("WaterLeakageSensorTilt", jsonObject);
getFixture().handleCommand(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE),
OnOffType.ON);
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("WaterLeakageSensorTilt"),
waterLeakageTiltServiceStateCaptor.capture());
WaterLeakageSensorTiltServiceState state = waterLeakageTiltServiceStateCaptor.getValue();
assertSame(EnabledDisabledState.DISABLED, state.pushNotificationState);
assertSame(EnabledDisabledState.ENABLED, state.acousticSignalState);
}
@Test
void testHandleCommandPushNotificationsInvalidCommandType()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
getFixture().handleCommand(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_PUSH_NOTIFICATIONS_ON_MOVE),
new StringType("test"));
verify(getBridgeHandler(), times(0)).putState(any(), any(), any());
}
@Test
void testHandleCommandAcousticSignalsInvalidCommandType()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
getFixture().handleCommand(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_ACOUSTIC_SIGNALS_ON_MOVE),
new StringType("test"));
verify(getBridgeHandler(), times(0)).putState(any(), any(), any());
}
@Test
void testHandleCommandPushNotificationsInvalidChannel()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_POWER_SWITCH),
OnOffType.ON);
verify(getBridgeHandler(), times(0)).putState(any(), any(), any());
}
@Test
void processMessageTiltDetected() {
Message message = new Message();
message.sourceType = Message.SOURCE_TYPE_DEVICE;
MessageCode messageCode = new MessageCode();
messageCode.name = WaterLeakageSensorHandler.MESSAGE_CODE_TILT_DETECTED;
messageCode.category = "WARNING";
message.messageCode = messageCode;
getFixture().processMessage(message);
verify(getCallback()).channelTriggered(getThing(),
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SENSOR_MOVED), "");
}
}

View File

@ -1,41 +0,0 @@
/**
* Copyright (c) 2010-2024 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

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.boschshc.internal.services.privacymode;
package org.openhab.binding.boschshc.internal.services.dto;
import static org.junit.jupiter.api.Assertions.assertSame;
@ -19,23 +19,23 @@ import org.junit.jupiter.api.Test;
import org.openhab.core.library.types.OnOffType;
/**
* Unit tests for {@link PrivacyModeState}.
* Unit tests for {@link EnabledDisabledState}.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
class PrivacyModeStateTest {
class EnabledDisabledStateTest {
@Test
void testFromOnOffType() {
assertSame(PrivacyModeState.ENABLED, PrivacyModeState.from(OnOffType.ON));
assertSame(PrivacyModeState.DISABLED, PrivacyModeState.from(OnOffType.OFF));
assertSame(EnabledDisabledState.ENABLED, EnabledDisabledState.from(OnOffType.ON));
assertSame(EnabledDisabledState.DISABLED, EnabledDisabledState.from(OnOffType.OFF));
}
@Test
void testToOnOffType() {
assertSame(OnOffType.ON, PrivacyModeState.ENABLED.toOnOffType());
assertSame(OnOffType.OFF, PrivacyModeState.DISABLED.toOnOffType());
assertSame(OnOffType.ON, EnabledDisabledState.ENABLED.toOnOffType());
assertSame(OnOffType.OFF, EnabledDisabledState.DISABLED.toOnOffType());
}
}