Skip to content

Commit

Permalink
[boschshc] Support for Smart Water Alarm
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
david-pace committed May 18, 2024
1 parent 3e3558f commit 10541f7
Show file tree
Hide file tree
Showing 30 changed files with 956 additions and 131 deletions.
18 changes: 18 additions & 0 deletions bundles/org.openhab.binding.boschshc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 | Switch | &#9744; | Triggered when the sensor is moved. |

## Limitations

No major limitation known.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ List<String> getAllBoschShcServices() {
"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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
@NonNullByDefault
public class BoschSHCBindingConstants {

private BoschSHCBindingConstants() {
// Class containing constants only
}

public static final String BINDING_ID = "boschshc";

// List of all Thing Type UIDs
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -175,6 +176,15 @@ public void processChildUpdate(String childDeviceId, String serviceName, @Nullab
// 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)}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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;

Expand Down Expand Up @@ -62,6 +63,7 @@
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;
Expand Down Expand Up @@ -132,7 +134,8 @@ public ThingTypeHandlerMapping(ThingTypeUID thingTypeUID, Function<Thing, BaseTh
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -40,6 +42,7 @@
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;
Expand Down Expand Up @@ -488,10 +491,44 @@ void handleLongPollResult(LongPollResult result) {
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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@
*/
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;

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;
Expand Down Expand Up @@ -91,13 +91,13 @@ public void handleCommand(ChannelUID channelUID, Command command) {

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);
}

Expand Down
Loading

0 comments on commit 10541f7

Please sign in to comment.