[boschshc] Add support for Dimmer (#16501)

Adds support for Bosch Smart Home Dimmer devices.

Signed-off-by: David Pace <dev@davidpace.de>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
David Pace 2024-04-01 12:26:03 +02:00 committed by Ciprian Pascu
parent 7deea3a8e2
commit 06bbd4d3aa
8 changed files with 291 additions and 2 deletions

View File

@ -70,6 +70,19 @@ A compact smart plug with energy monitoring capabilities.
| power-consumption | Number:Power | &#9744; | Current power consumption (W) of the device. | | power-consumption | Number:Power | &#9744; | Current power consumption (W) of the device. |
| energy-consumption | Number:Energy | &#9744; | Cumulated energy consumption (Wh) of the device. | | energy-consumption | Number:Energy | &#9744; | Cumulated energy consumption (Wh) of the device. |
### Dimmer
Smart dimmer capable of controlling any dimmable lamp.
**Thing Type ID**: `dimmer`
| Channel Type ID | Item Type | Writable | Description |
| ------------------ | ------------- | :------: | -------------------------------------------------------------- |
| power-switch | Switch | &#9745; | Current state of the switch. |
| brightness | Dimmer | &#9745; | Regulates the brightness on a percentage scale from 0 to 100%. |
| 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. |
### Twinguard Smoke Detector ### Twinguard Smoke Detector
The Twinguard smoke detector warns you in case of fire and constantly monitors the air. The Twinguard smoke detector warns you in case of fire and constantly monitors the air.

View File

@ -54,6 +54,7 @@ public class BoschSHCBindingConstants {
public static final ThingTypeUID THING_TYPE_UNIVERSAL_SWITCH_2 = new ThingTypeUID(BINDING_ID, "universal-switch-2"); 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_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_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_USER_DEFINED_STATE = new ThingTypeUID(BINDING_ID, "user-defined-state"); public static final ThingTypeUID THING_TYPE_USER_DEFINED_STATE = new ThingTypeUID(BINDING_ID, "user-defined-state");

View File

@ -15,6 +15,7 @@ package org.openhab.binding.boschshc.internal.devices;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_CAMERA_360; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_CAMERA_360;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_CAMERA_EYES; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_CAMERA_EYES;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_CLIMATE_CONTROL;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_DIMMER;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_INTRUSION_DETECTION_SYSTEM; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_INTRUSION_DETECTION_SYSTEM;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_INWALL_SWITCH;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_LIGHT_CONTROL_2; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_LIGHT_CONTROL_2;
@ -45,6 +46,7 @@ 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.camera.CameraHandler;
import org.openhab.binding.boschshc.internal.devices.climatecontrol.ClimateControlHandler; import org.openhab.binding.boschshc.internal.devices.climatecontrol.ClimateControlHandler;
import org.openhab.binding.boschshc.internal.devices.intrusion.IntrusionDetectionHandler; import org.openhab.binding.boschshc.internal.devices.intrusion.IntrusionDetectionHandler;
import org.openhab.binding.boschshc.internal.devices.lightcontrol.DimmerHandler;
import org.openhab.binding.boschshc.internal.devices.lightcontrol.LightControl2Handler; import org.openhab.binding.boschshc.internal.devices.lightcontrol.LightControl2Handler;
import org.openhab.binding.boschshc.internal.devices.lightcontrol.LightControlHandler; import org.openhab.binding.boschshc.internal.devices.lightcontrol.LightControlHandler;
import org.openhab.binding.boschshc.internal.devices.motiondetector.MotionDetectorHandler; import org.openhab.binding.boschshc.internal.devices.motiondetector.MotionDetectorHandler;
@ -129,7 +131,8 @@ public class BoschSHCHandlerFactory extends BaseThingHandlerFactory {
new ThingTypeHandlerMapping(THING_TYPE_UNIVERSAL_SWITCH_2, new ThingTypeHandlerMapping(THING_TYPE_UNIVERSAL_SWITCH_2,
thing -> new UniversalSwitch2Handler(thing, timeZoneProvider)), thing -> new UniversalSwitch2Handler(thing, timeZoneProvider)),
new ThingTypeHandlerMapping(THING_TYPE_SMOKE_DETECTOR_2, SmokeDetector2Handler::new), new ThingTypeHandlerMapping(THING_TYPE_SMOKE_DETECTOR_2, SmokeDetector2Handler::new),
new ThingTypeHandlerMapping(THING_TYPE_LIGHT_CONTROL_2, LightControl2Handler::new)); new ThingTypeHandlerMapping(THING_TYPE_LIGHT_CONTROL_2, LightControl2Handler::new),
new ThingTypeHandlerMapping(THING_TYPE_DIMMER, DimmerHandler::new));
@Override @Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) { public boolean supportsThingType(ThingTypeUID thingTypeUID) {

View File

@ -0,0 +1,98 @@
/**
* 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.lightcontrol;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_BRIGHTNESS;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_CHILD_PROTECTION;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SIGNAL_STRENGTH;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.devices.AbstractPowerSwitchHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.childprotection.ChildProtectionService;
import org.openhab.binding.boschshc.internal.services.childprotection.dto.ChildProtectionServiceState;
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.multilevelswitch.MultiLevelSwitchService;
import org.openhab.binding.boschshc.internal.services.multilevelswitch.dto.MultiLevelSwitchServiceState;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* Handler for Bosch Smart Home dimmers.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class DimmerHandler extends AbstractPowerSwitchHandler {
private MultiLevelSwitchService multiLevelSwitchService;
private ChildProtectionService childProtectionService;
public DimmerHandler(Thing thing) {
super(thing);
this.multiLevelSwitchService = new MultiLevelSwitchService();
this.childProtectionService = new ChildProtectionService();
}
@Override
protected void initializeServices() throws BoschSHCException {
super.initializeServices();
createService(CommunicationQualityService::new, this::updateChannels, List.of(CHANNEL_SIGNAL_STRENGTH), true);
registerService(multiLevelSwitchService, this::updateChannels, List.of(CHANNEL_BRIGHTNESS), true);
registerService(childProtectionService, this::updateChannels, List.of(CHANNEL_CHILD_PROTECTION), true);
}
private void updateChannels(MultiLevelSwitchServiceState serviceState) {
super.updateState(CHANNEL_BRIGHTNESS, serviceState.toPercentType());
}
private void updateChannels(CommunicationQualityServiceState communicationQualityServiceState) {
updateState(CHANNEL_SIGNAL_STRENGTH, communicationQualityServiceState.quality.toSystemSignalStrength());
}
private void updateChannels(ChildProtectionServiceState childProtectionServiceState) {
super.updateState(CHANNEL_CHILD_PROTECTION, OnOffType.from(childProtectionServiceState.childLockActive));
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
if (CHANNEL_CHILD_PROTECTION.equals(channelUID.getId()) && (command instanceof OnOffType onOffCommand)) {
updateChildProtectionState(onOffCommand);
} else if (CHANNEL_BRIGHTNESS.equals(channelUID.getId()) && command instanceof PercentType percentCommand) {
updateMultiLevelSwitchState(percentCommand);
}
}
private void updateChildProtectionState(OnOffType onOffCommand) {
ChildProtectionServiceState childProtectionServiceState = new ChildProtectionServiceState();
childProtectionServiceState.childLockActive = onOffCommand == OnOffType.ON;
updateServiceState(childProtectionService, childProtectionServiceState);
}
private void updateMultiLevelSwitchState(PercentType percentCommand) {
MultiLevelSwitchServiceState serviceState = new MultiLevelSwitchServiceState();
serviceState.level = percentCommand.intValue();
this.updateServiceState(multiLevelSwitchService, serviceState);
}
}

View File

@ -97,7 +97,8 @@ public class ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<
new AbstractMap.SimpleEntry<>("SMOKE_DETECTOR2", BoschSHCBindingConstants.THING_TYPE_SMOKE_DETECTOR_2), new AbstractMap.SimpleEntry<>("SMOKE_DETECTOR2", BoschSHCBindingConstants.THING_TYPE_SMOKE_DETECTOR_2),
new AbstractMap.SimpleEntry<>("MICROMODULE_SHUTTER", BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL_2), 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_AWNING", BoschSHCBindingConstants.THING_TYPE_SHUTTER_CONTROL_2),
new AbstractMap.SimpleEntry<>("MICROMODULE_LIGHT_CONTROL", BoschSHCBindingConstants.THING_TYPE_LIGHT_CONTROL_2) new AbstractMap.SimpleEntry<>("MICROMODULE_LIGHT_CONTROL", BoschSHCBindingConstants.THING_TYPE_LIGHT_CONTROL_2),
new AbstractMap.SimpleEntry<>("MICROMODULE_DIMMER", BoschSHCBindingConstants.THING_TYPE_DIMMER)
// Future Extension: map deviceModel names to BoschSHC Thing Types when they are supported // Future Extension: map deviceModel names to BoschSHC Thing Types when they are supported
// new AbstractMap.SimpleEntry<>("SMOKE_DETECTION_SYSTEM", BoschSHCBindingConstants.), // new AbstractMap.SimpleEntry<>("SMOKE_DETECTION_SYSTEM", BoschSHCBindingConstants.),
// new AbstractMap.SimpleEntry<>("PRESENCE_SIMULATION_SERVICE", BoschSHCBindingConstants.), // new AbstractMap.SimpleEntry<>("PRESENCE_SIMULATION_SERVICE", BoschSHCBindingConstants.),

View File

@ -7,6 +7,8 @@ addon.boschshc.description = This is the binding for Bosch Smart Home.
thing-type.boschshc.climate-control.label = Climate Control thing-type.boschshc.climate-control.label = Climate Control
thing-type.boschshc.climate-control.description = This is a virtual device which is automatically created for all rooms that have thermostats in it. thing-type.boschshc.climate-control.description = This is a virtual device which is automatically created for all rooms that have thermostats in it.
thing-type.boschshc.dimmer.label = Dimmer
thing-type.boschshc.dimmer.description = Smart dimmer capable of controlling any dimmable lamp.
thing-type.boschshc.in-wall-switch.label = In-wall Switch thing-type.boschshc.in-wall-switch.label = In-wall Switch
thing-type.boschshc.in-wall-switch.description = A simple light control. thing-type.boschshc.in-wall-switch.description = A simple light control.
thing-type.boschshc.intrusion-detection-system.label = Intrusion Detection System thing-type.boschshc.intrusion-detection-system.label = Intrusion Detection System

View File

@ -421,6 +421,24 @@
<config-description-ref uri="thing-type:boschshc:device"/> <config-description-ref uri="thing-type:boschshc:device"/>
</thing-type> </thing-type>
<thing-type id="dimmer">
<supported-bridge-type-refs>
<bridge-type-ref id="shc"/>
</supported-bridge-type-refs>
<label>Dimmer</label>
<description>Smart dimmer capable of controlling any dimmable lamp.</description>
<channels>
<channel id="power-switch" typeId="system.power"/>
<channel id="brightness" typeId="system.brightness"/>
<channel id="signal-strength" typeId="system.signal-strength"/>
<channel id="child-protection" typeId="child-protection"/>
</channels>
<config-description-ref uri="thing-type:boschshc:device"/>
</thing-type>
<!-- Channels --> <!-- Channels -->
<channel-type id="system-availability"> <channel-type id="system-availability">

View File

@ -0,0 +1,153 @@
/**
* 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.lightcontrol;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
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.AbstractPowerSwitchHandlerTest;
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.childprotection.dto.ChildProtectionServiceState;
import org.openhab.binding.boschshc.internal.services.multilevelswitch.dto.MultiLevelSwitchServiceState;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
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 DimmerHandler}.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
class DimmerHandlerTest extends AbstractPowerSwitchHandlerTest<DimmerHandler> {
private @Captor @NonNullByDefault({}) ArgumentCaptor<MultiLevelSwitchServiceState> multiLevelSwitchServiceStateCaptor;
private @Captor @NonNullByDefault({}) ArgumentCaptor<ChildProtectionServiceState> childProtectionServiceStateCaptor;
@Override
protected DimmerHandler createFixture() {
return new DimmerHandler(getThing());
}
@Override
protected ThingTypeUID getThingTypeUID() {
return BoschSHCBindingConstants.THING_TYPE_DIMMER;
}
@Override
protected String getDeviceID() {
return "hdm:ZigBee:60b647fffec5a9d8";
}
@Test
void testUpdateChannelMultiLevelSwitchState() {
JsonElement jsonObject = JsonParser.parseString("{\"@type\":\"multiLevelSwitchState\",\"level\":16}");
getFixture().processUpdate("MultiLevelSwitch", jsonObject);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BRIGHTNESS), new PercentType(16));
}
@Test
void testUpdateChannelsChildProtectionService() {
String json = """
{
"@type": "ChildProtectionState",
"childLockActive": true
}
""";
JsonElement jsonObject = JsonParser.parseString(json);
getFixture().processUpdate("ChildProtection", jsonObject);
verify(getCallback()).stateUpdated(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CHILD_PROTECTION), OnOffType.ON);
}
@Test
void testHandleCommandMultiLevelSwitch()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_BRIGHTNESS),
new PercentType(42));
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("MultiLevelSwitch"),
multiLevelSwitchServiceStateCaptor.capture());
MultiLevelSwitchServiceState state = multiLevelSwitchServiceStateCaptor.getValue();
assertEquals(42, state.level);
}
@Test
void testUpdateChannelCommunicationQualityService() {
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 testHandleCommandChildProtection()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
getFixture().handleCommand(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CHILD_PROTECTION), OnOffType.ON);
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("ChildProtection"),
childProtectionServiceStateCaptor.capture());
ChildProtectionServiceState state = childProtectionServiceStateCaptor.getValue();
assertTrue(state.childLockActive);
}
@Test
void testHandleCommandChildProtectionInvalidCommand()
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
getFixture().handleCommand(
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CHILD_PROTECTION),
DecimalType.ZERO);
verify(getBridgeHandler(), times(0)).putState(eq(getDeviceID()), eq("ChildProtection"),
childProtectionServiceStateCaptor.capture());
}
}