Skip to content

Commit

Permalink
[boschshc] Add support for Light/Shutter Control II (openhab#16400)
Browse files Browse the repository at this point in the history
* [boschshc] Add support for Shutter Control II (openhab#14562)
* add new channel type for child protection

Signed-off-by: David Pace <dev@davidpace.de>
  • Loading branch information
david-pace authored Mar 31, 2024
1 parent afc6d94 commit b77172c
Show file tree
Hide file tree
Showing 36 changed files with 1,670 additions and 154 deletions.
34 changes: 33 additions & 1 deletion bundles/org.openhab.binding.boschshc/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Bosch Smart Home Binding

Binding for the Bosch Smart Home.
Binding for Bosch Smart Home devices.

- [Bosch Smart Home Binding](#bosch-smart-home-binding)
- [Supported Things](#supported-things)
Expand All @@ -10,8 +10,10 @@ Binding for the Bosch Smart Home.
- [Twinguard Smoke Detector](#twinguard-smoke-detector)
- [Door/Window Contact](#door-window-contact)
- [Door/Window Contact II](#door-window-contact-ii)
- [Light Control II](#light-control-ii)
- [Motion Detector](#motion-detector)
- [Shutter Control](#shutter-control)
- [Shutter Control II](#shutter-control-ii)
- [Thermostat](#thermostat)
- [Climate Control](#climate-control)
- [Wall Thermostat](#wall-thermostat)
Expand Down Expand Up @@ -114,6 +116,22 @@ Detects open windows and doors and features an additional button.
| bypass | Switch | &#9744; | Indicates whether the device is currently bypassed. Possible values are `ON`,`OFF` and `UNDEF` if the bypass state cannot be determined. |
| 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). |

### Light Control II

This thing type is used if Light/Shutter Control II devices are configured as light controls.

**Thing Type ID**: `light-control-2`

| Channel Type ID | Item Type | Writable | Description |
| ------------------ | ------------- | :------: | ------------------------------------------------------------- |
| 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). |
| power-consumption | Number:Power | &#9744; | Current power consumption (W) of the device. |
| energy-consumption | Number:Energy | &#9744; | Cumulated energy consumption (Wh) of the device. |
| power-switch-1 | Switch | &#9745; | Switches the light on or off (circuit 1). |
| child-protection-1 | Switch | &#9745; | Indicates whether the child protection is active (circuit 1). |
| power-switch-2 | Switch | &#9745; | Switches the light on or off (circuit 2). |
| child-protection-2 | Switch | &#9745; | Indicates whether the child protection is active (circuit 2). |

### Motion Detector

Detects every movement through an intelligent combination of passive infra-red technology and an additional temperature sensor.
Expand All @@ -137,6 +155,20 @@ Control of your shutter to take any position you desire.
| --------------- | ------------- | :------: | ---------------------------------------- |
| level | Rollershutter | &#9745; | Current open ratio (0 to 100, Step 0.5). |

### Shutter Control II

This thing type is used if Light/Shutter Control II devices are configured as shutter controls.

**Thing Type ID**: `shutter-control-2`

| Channel Type ID | Item Type | Writable | Description |
| ------------------ | ------------- | :------: | ------------------------------------------------- |
| level | Rollershutter | &#9745; | Current open ratio (0 to 100, Step 0.5). |
| 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). |
| child-protection | Switch | &#9745; | Indicates whether the child protection is active. |
| power-consumption | Number:Power | &#9744; | Current power consumption (W) of the device. |
| energy-consumption | Number:Energy | &#9744; | Cumulated energy consumption (Wh) of the device. |

### Thermostat

Radiator thermostat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ public BoschShcCommandExtension(final @Reference ThingRegistry thingRegistry) {
*/
List<String> getAllBoschShcServices() {
return List.of("airqualitylevel", "batterylevel", "binaryswitch", "bypass", "cameranotification", "childlock",
"communicationquality", "hsbcoloractuator", "humiditylevel", "illuminance", "intrusion", "keypad",
"latestmotion", "multilevelswitch", "powermeter", "powerswitch", "privacymode", "roomclimatecontrol",
"shuttercontact", "shuttercontrol", "silentmode", "smokedetectorcheck", "temperaturelevel", "userstate",
"valvetappet");
"childprotection", "communicationquality", "hsbcoloractuator", "humiditylevel", "illuminance",
"intrusion", "keypad", "latestmotion", "multilevelswitch", "powermeter", "powerswitch", "privacymode",
"roomclimatecontrol", "shuttercontact", "shuttercontrol", "silentmode", "smokedetectorcheck",
"temperaturelevel", "userstate", "valvetappet");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,16 @@
*/
package org.openhab.binding.boschshc.internal.devices;

import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_POWER_SWITCH;

import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.powermeter.PowerMeterService;
import org.openhab.binding.boschshc.internal.services.powermeter.dto.PowerMeterServiceState;
import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchService;
import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchState;
import org.openhab.binding.boschshc.internal.services.powerswitch.dto.PowerSwitchServiceState;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
Expand Down Expand Up @@ -61,8 +57,6 @@ protected void initializeServices() throws BoschSHCException {
super.initializeServices();

this.registerService(this.powerSwitchService, this::updateChannels, List.of(CHANNEL_POWER_SWITCH), true);
this.createService(PowerMeterService::new, this::updateChannels,
List.of(CHANNEL_POWER_CONSUMPTION, CHANNEL_ENERGY_CONSUMPTION), true);
}

@Override
Expand All @@ -79,19 +73,9 @@ public void handleCommand(ChannelUID channelUID, Command command) {
}

/**
* Updates the channels which are linked to the {@link PowerMeterService} of the device.
* Updates the power switch channel when a new state is received.
*
* @param state Current state of {@link PowerMeterService}.
*/
private void updateChannels(PowerMeterServiceState state) {
super.updateState(CHANNEL_POWER_CONSUMPTION, new QuantityType<>(state.powerConsumption, Units.WATT));
super.updateState(CHANNEL_ENERGY_CONSUMPTION, new QuantityType<>(state.energyConsumption, Units.WATT_HOUR));
}

/**
* Updates the channels which are linked to the {@link PowerSwitchService} of the device.
*
* @param state Current state of {@link PowerSwitchService}.
* @param state the new {@link PowerSwitchService} state.
*/
private void updateChannels(PowerSwitchServiceState state) {
State powerState = OnOffType.from(state.switchState.toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* 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;

import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ENERGY_CONSUMPTION;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_POWER_CONSUMPTION;

import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.powermeter.PowerMeterService;
import org.openhab.binding.boschshc.internal.services.powermeter.dto.PowerMeterServiceState;
import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchService;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Thing;

/**
* Abstract handler implementation for devices providing a {@link PowerSwitchService} and a {@link PowerMeterService}.
* <p>
* Examples for such devices are smart plugs and in-wall switches.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public abstract class AbstractPowerSwitchHandlerWithPowerMeter extends AbstractPowerSwitchHandler {

protected AbstractPowerSwitchHandlerWithPowerMeter(Thing thing) {
super(thing);
}

@Override
protected void initializeServices() throws BoschSHCException {
super.initializeServices();

this.createService(PowerMeterService::new, this::updateChannels,
List.of(CHANNEL_POWER_CONSUMPTION, CHANNEL_ENERGY_CONSUMPTION), true);
}

/**
* Updates the channels which are linked to the {@link PowerMeterService} of the device.
*
* @param state Current state of {@link PowerMeterService}.
*/
private void updateChannels(PowerMeterServiceState state) {
super.updateState(CHANNEL_POWER_CONSUMPTION, new QuantityType<>(state.powerConsumption, Units.WATT));
super.updateState(CHANNEL_ENERGY_CONSUMPTION, new QuantityType<>(state.energyConsumption, Units.WATT_HOUR));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* 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;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* Utilities for handling parent/child relations in Bosch device IDs.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public final class BoschDeviceIdUtils {

private static final String CHILD_ID_SEPARATOR = "#";

private BoschDeviceIdUtils() {
// Utility Class
}

/**
* Returns whether the given device ID is a child device ID.
* <p>
* Example for a parent device ID:
*
* <pre>
* hdm:ZigBee:70ac08fffefead2d
* </pre>
*
* Example for a child device ID:
*
* <pre>
* hdm:ZigBee:70ac08fffefead2d#2
* </pre>
*
* @param deviceId the Bosch device ID to check
* @return <code>true</code> if the device ID contains a hash character, <code>false</code> otherwise
*/
public static boolean isChildDeviceId(String deviceId) {
return deviceId.contains(CHILD_ID_SEPARATOR);
}

/**
* If the given device ID is a child device ID, the parent device ID is derived by cutting off the part starting
* from the hash character.
*
* @param deviceId a device ID
* @return the parent device ID, if derivable. Otherwise the given ID is returned.
*/
public static String getParentDeviceId(String deviceId) {
int hashIndex = deviceId.indexOf(CHILD_ID_SEPARATOR);
if (hashIndex < 0) {
return deviceId;
}

return deviceId.substring(0, hashIndex);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class BoschSHCBindingConstants {
public static final ThingTypeUID THING_TYPE_WINDOW_CONTACT_2 = new ThingTypeUID(BINDING_ID, "window-contact-2");
public static final ThingTypeUID THING_TYPE_MOTION_DETECTOR = new ThingTypeUID(BINDING_ID, "motion-detector");
public static final ThingTypeUID THING_TYPE_SHUTTER_CONTROL = new ThingTypeUID(BINDING_ID, "shutter-control");
public static final ThingTypeUID THING_TYPE_SHUTTER_CONTROL_2 = new ThingTypeUID(BINDING_ID, "shutter-control-2");
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");
Expand All @@ -51,9 +52,10 @@ public class BoschSHCBindingConstants {
public static final ThingTypeUID THING_TYPE_SMOKE_DETECTOR = new ThingTypeUID(BINDING_ID, "smoke-detector");
public static final ThingTypeUID THING_TYPE_UNIVERSAL_SWITCH = new ThingTypeUID(BINDING_ID, "universal-switch");
public static final ThingTypeUID THING_TYPE_UNIVERSAL_SWITCH_2 = new ThingTypeUID(BINDING_ID, "universal-switch-2");
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_USER_DEFINED_STATE = new ThingTypeUID(BINDING_ID, "user-defined-state");
public static final ThingTypeUID THING_TYPE_SMOKE_DETECTOR_2 = new ThingTypeUID(BINDING_ID, "smoke-detector-2");

// List of all Channel IDs
// Auto-generated from thing-types.xml via script, don't modify
Expand All @@ -76,6 +78,7 @@ 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_CHILD_PROTECTION = "child-protection";
public static final String CHANNEL_PRIVACY_MODE = "privacy-mode";
public static final String CHANNEL_CAMERA_NOTIFICATION = "camera-notification";
public static final String CHANNEL_SYSTEM_AVAILABILITY = "system-availability";
Expand All @@ -99,6 +102,14 @@ public class BoschSHCBindingConstants {
public static final String CHANNEL_KEY_EVENT_TYPE = "key-event-type";
public static final String CHANNEL_KEY_EVENT_TIMESTAMP = "key-event-timestamp";

// numbered channels
// the rationale for introducing numbered channels was discussed in
// https://github.com/openhab/openhab-addons/pull/16400
public static final String CHANNEL_POWER_SWITCH_1 = "power-switch-1";
public static final String CHANNEL_POWER_SWITCH_2 = "power-switch-2";
public static final String CHANNEL_CHILD_PROTECTION_1 = "child-protection-1";
public static final String CHANNEL_CHILD_PROTECTION_2 = "child-protection-2";

public static final String CHANNEL_USER_DEFINED_STATE = "user-state";

// static device/service names
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
Expand Down Expand Up @@ -57,30 +58,68 @@ protected BoschSHCDeviceHandler(Thing thing) {

@Override
public void initialize() {
var config = this.config = getConfigAs(BoschSHCConfiguration.class);
this.config = getConfigAs(BoschSHCConfiguration.class);

String deviceId = config.id;
@Nullable
Device deviceInfo = validateDeviceId(deviceId);
if (deviceInfo == null) {
return;
}

if (!processDeviceInfo(deviceInfo)) {
return;
}

super.initialize();
}

/**
* Allows the handler to process the device info that was obtained from a REST
* call to the Smart Home Controller at <code>/devices/{deviceId}</code>.
*
* @param deviceInfo the device info obtained from the controller, guaranteed to be non-null
* @return <code>true</code> if the device info is valid and the initialization should proceed, <code>false</code>
* otherwise
*/
protected boolean processDeviceInfo(Device deviceInfo) {
return true;
}

/**
* Attempts to obtain information about the device with the specified ID via a REST call.
* <p>
* If the REST call is successful, the device ID is considered to be valid and the resulting {@link Device} object
* is returned.
* <p>
* If the device ID is not configured/empty or the REST call is not successful, the device ID is considered invalid
* and <code>null</code> is returned.
*
* @param deviceId the device ID to check
* @return the {@link Device} info object if the REST call was successful, <code>null</code> otherwise
*/
@Nullable
protected Device validateDeviceId(@Nullable String deviceId) {
if (deviceId == null || deviceId.isBlank()) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error.empty-device-id");
return;
return null;
}

// Try to get device info to make sure the device exists
try {
var bridgeHandler = this.getBridgeHandler();
var info = bridgeHandler.getDeviceInfo(deviceId);
logger.trace("Device initialized:\n{}", info);
var deviceInfo = bridgeHandler.getDeviceInfo(deviceId);
logger.trace("Device validated and initialized:\n{}", deviceInfo);
return deviceInfo;
} catch (TimeoutException | ExecutionException | BoschSHCException e) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
return;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
return;
}

super.initialize();
return null;
}

/**
Expand Down
Loading

0 comments on commit b77172c

Please sign in to comment.