Skip to content

Commit

Permalink
[boschshc] Add support for Dimmer (openhab#16501)
Browse files Browse the repository at this point in the history
Adds support for Bosch Smart Home Dimmer devices.

Signed-off-by: David Pace <dev@davidpace.de>
Signed-off-by: Patrik Gfeller <patrik.gfeller@proton.me>
  • Loading branch information
david-pace authored and pgfeller committed Sep 29, 2024
1 parent 09e93a6 commit df0bd2e
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 2 deletions.
13 changes: 13 additions & 0 deletions bundles/org.openhab.binding.boschshc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ A compact smart plug with energy monitoring capabilities.
| power-consumption | Number:Power | &#9744; | Current power consumption (W) 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

The Twinguard smoke detector warns you in case of fire and constantly monitors the air.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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_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_USER_DEFINED_STATE = new ThingTypeUID(BINDING_ID, "user-defined-state");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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_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_INWALL_SWITCH;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.THING_TYPE_LIGHT_CONTROL_2;
Expand Down Expand Up @@ -45,6 +46,7 @@
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.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.LightControlHandler;
import org.openhab.binding.boschshc.internal.devices.motiondetector.MotionDetectorHandler;
Expand Down Expand Up @@ -129,7 +131,8 @@ public ThingTypeHandlerMapping(ThingTypeUID thingTypeUID, Function<Thing, BaseTh
new ThingTypeHandlerMapping(THING_TYPE_UNIVERSAL_SWITCH_2,
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_LIGHT_CONTROL_2, LightControl2Handler::new),
new ThingTypeHandlerMapping(THING_TYPE_DIMMER, DimmerHandler::new));

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ public class ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<
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_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
// new AbstractMap.SimpleEntry<>("SMOKE_DETECTION_SYSTEM", BoschSHCBindingConstants.),
// new AbstractMap.SimpleEntry<>("PRESENCE_SIMULATION_SERVICE", BoschSHCBindingConstants.),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.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.description = A simple light control.
thing-type.boschshc.intrusion-detection-system.label = Intrusion Detection System
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,24 @@
<config-description-ref uri="thing-type:boschshc:device"/>
</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 -->

<channel-type id="system-availability">
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit df0bd2e

Please sign in to comment.