]> git.basschouten.com Git - openhab-addons.git/commitdiff
[boschshc] Add support for Dimmer (#16501)
authorDavid Pace <dev@davidpace.de>
Mon, 1 Apr 2024 10:26:03 +0000 (12:26 +0200)
committerGitHub <noreply@github.com>
Mon, 1 Apr 2024 10:26:03 +0000 (12:26 +0200)
Adds support for Bosch Smart Home Dimmer devices.

Signed-off-by: David Pace <dev@davidpace.de>
bundles/org.openhab.binding.boschshc/README.md
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCBindingConstants.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandlerFactory.java
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/DimmerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java
bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/i18n/boschshc.properties
bundles/org.openhab.binding.boschshc/src/main/resources/OH-INF/thing/thing-types.xml
bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/DimmerHandlerTest.java [new file with mode: 0644]

index 68a0ff5a2464952467f65c023314ead220453eb3..1cb21d4b4842960c9cdd97d3a44249266d157804 100644 (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.     |
 | 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.
index ed849aff4e8251da13caf64815b8f3f665da0081..eb7f4dc685b1459fddc9c5f4b9ff6237c1c52e2e 100644 (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_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");
 
index 94c9bfad5301f4ee1f24b0ad24fe7079cb1ce225..21e0b0c87f3316ddabad7254a1a90c11f4f9ab4e 100644 (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_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;
@@ -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.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;
@@ -129,7 +131,8 @@ public class BoschSHCHandlerFactory extends BaseThingHandlerFactory {
             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) {
diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/DimmerHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/DimmerHandler.java
new file mode 100644 (file)
index 0000000..13eeb30
--- /dev/null
@@ -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);
+    }
+}
index e912cbd07da180f66e74cfeccc3317d9a274a5d7..423e3acee752bf670a5adfe378d589ada0084618 100644 (file)
@@ -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.),
index 8ce04446766ca959562a0cd0b094869351b56e41..ba8473f1f852549fae154bd80d24ef4815a9ac05 100644 (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.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
index 05fb5e87f72a6473eddbdf959b1bc67c8320d340..ce106bdbba320246ee9e70540130fd5cd00b9f88 100644 (file)
                <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">
diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/DimmerHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/lightcontrol/DimmerHandlerTest.java
new file mode 100644 (file)
index 0000000..96b8249
--- /dev/null
@@ -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());
+    }
+}