diff --git a/bundles/org.openhab.binding.loxone/README.md b/bundles/org.openhab.binding.loxone/README.md index d651859d5b615..29fd9e540e90b 100644 --- a/bundles/org.openhab.binding.loxone/README.md +++ b/bundles/org.openhab.binding.loxone/README.md @@ -88,7 +88,7 @@ The binding supports the following authentication methods, which are selected au | Method | Miniserver Firmware | Authentication | Encryption | Requirements | |-------------|---------------------|--------------------------------------------------------------------------------|------------|-------------------------------------------------------| | Hash-based | 8.x | HMAC-SHA1 hash on user and password | None | None | -| Token-based | 9.x | Token acquired on the first connection and used later instead of the password. | AES-256 | JRE must have unrestricted security policy configured | +| Token-based | From 9.x | Token acquired on the first connection and used later instead of the password. | AES-256 | JRE must have unrestricted security policy configured | For the token-based authentication, the password is required only for the first login and acquiring the token. After the token is acquired, the password is cleared in the binding configuration. @@ -121,7 +121,8 @@ Currently supported controls are presented in the table below. | | | `String` - list of alarm sensors separated with `|` | Read-only channel | | | | `Switch` - acknowledge the alarm - pushbutton | `OnOffType.ON` - acknowledge alarm | | ColorPickerV2 | [RGBW 24v Dimmer Tree](https://www.loxone.com/enen/kb/rgbw-24v-dimmer-tree/) | `Color` | `HSBType` - sets the color of the light, `DecimalType` and `PercentType` - sets the brightness, `IncreaseDecreaseType.*` - increases/decreases the brightness, `OnOffType.*` - switches light on/off | -| Dimmer | [Dimmer](https://www.loxone.com/enen/kb/dimmer/) | `Dimmer` | `OnOffType.*`, `PercentType` | +| Dimmer | [Dimmer](https://www.loxone.com/enen/kb/dimmer/) | `Dimmer` | `OnOffType.*`, `PercentType`, `IncreaseDecreaseType.*` | +| EIBDimmer | EIB Dimmer (undocumented) | `Dimmer` | `OnOffType.*`, `PercentType`, `IncreaseDecreaseType.*` | | InfoOnlyAnalog | Analog [virtual inputs](https://www.loxone.com/enen/kb/virtual-inputs-outputs/) (virtual state) | `Number` | Read-only channel | | InfoOnlyDigital | Digital [virtual inputs](https://www.loxone.com/enen/kb/virtual-inputs-outputs/) (virtual state) | `String` | Read-only channel | | IRoomControllerV2 | [Intelligent Room Controller V2](https://www.loxone.com/enen/kb/irc-v2/) | `Number` - active mode | Read-only channel | diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlDimmer.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlDimmer.java index c6aaaa72635f9..700f23e10dfdb 100644 --- a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlDimmer.java +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlDimmer.java @@ -12,18 +12,7 @@ */ package org.openhab.binding.loxone.internal.controls; -import static org.openhab.binding.loxone.internal.LxBindingConstants.*; - -import java.io.IOException; - -import org.openhab.binding.loxone.internal.types.LxCategory; -import org.openhab.binding.loxone.internal.types.LxTags; import org.openhab.binding.loxone.internal.types.LxUuid; -import org.openhab.core.library.types.IncreaseDecreaseType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.PercentType; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openhab.core.types.Command; /** * A dimmer type of control on Loxone Miniserver. @@ -36,7 +25,7 @@ * @author Stephan Brunner - initial contribution * */ -class LxControlDimmer extends LxControl { +class LxControlDimmer extends LxControlEIBDimmer { static class Factory extends LxControlInstance { @Override @@ -51,118 +40,28 @@ String getType() { } /** - * States + * States additionally to EIBDimmer */ - private static final String STATE_POSITION = "position"; private static final String STATE_MIN = "min"; private static final String STATE_MAX = "max"; private static final String STATE_STEP = "step"; - /** - * Command string used to set the dimmer ON - */ - private static final String CMD_ON = "On"; - /** - * Command string used to set the dimmer to OFF - */ - private static final String CMD_OFF = "Off"; - private LxControlDimmer(LxUuid uuid) { super(uuid); } @Override - public void initialize(LxControlConfig config) { - super.initialize(config); - LxCategory category = getCategory(); - if (category != null && category.getType() == LxCategory.CategoryType.LIGHTS) { - tags.addAll(LxTags.LIGHTING); - } - addChannel("Dimmer", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_DIMMER), defaultChannelLabel, - "Dimmer", tags, this::handleCommands, this::getChannelState); - } - - private void handleCommands(Command command) throws IOException { - if (command instanceof OnOffType) { - if (command == OnOffType.ON) { - sendAction(CMD_ON); - } else { - sendAction(CMD_OFF); - } - } else if (command instanceof PercentType) { - PercentType percentCmd = (PercentType) command; - setPosition(percentCmd.doubleValue()); - } else if (command instanceof IncreaseDecreaseType) { - Double value = getStateDoubleValue(STATE_POSITION); - Double min = getStateDoubleValue(STATE_MIN); - Double max = getStateDoubleValue(STATE_MAX); - Double step = getStateDoubleValue(STATE_STEP); - if (value != null && max != null && min != null && step != null && min >= 0 && max >= 0 && max > min) { - if ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE) { - value += step; - if (value > max) { - value = max; - } - } else { - value -= step; - if (value < min) { - value = min; - } - } - sendAction(value.toString()); - } - } + Double getMin() { + return getStateDoubleValue(STATE_MIN); } - private PercentType getChannelState() { - Double value = mapLoxoneToOH(getStateDoubleValue(STATE_POSITION)); - if (value != null && value >= 0 && value <= 100) { - return new PercentType(value.intValue()); - } - return null; - } - - /** - * Sets the current position of the dimmer - * - * @param position position to move to (0-100, 0 - full off, 100 - full on) - * @throws IOException error communicating with the Miniserver - */ - private void setPosition(Double position) throws IOException { - Double loxonePosition = mapOHToLoxone(position); - if (loxonePosition != null) { - sendAction(loxonePosition.toString()); - } - } - - private Double mapLoxoneToOH(Double loxoneValue) { - if (loxoneValue != null) { - // 0 means turn dimmer off, any value above zero should be mapped from min-max range - if (Double.compare(loxoneValue, 0.0) == 0) { - return 0.0; - } - Double max = getStateDoubleValue(STATE_MAX); - Double min = getStateDoubleValue(STATE_MIN); - if (max != null && min != null && max > min && min >= 0 && max >= 0) { - return 100 * (loxoneValue - min) / (max - min); - } - } - return null; + @Override + Double getMax() { + return getStateDoubleValue(STATE_MAX); } - private Double mapOHToLoxone(Double ohValue) { - if (ohValue != null) { - // 0 means turn dimmer off, any value above zero should be mapped to min-max range - if (Double.compare(ohValue, 0.0) == 0) { - return 0.0; - } - Double max = getStateDoubleValue(STATE_MAX); - Double min = getStateDoubleValue(STATE_MIN); - if (max != null && min != null) { - double value = min + ohValue * (max - min) / 100; - return value; // no rounding to integer value is needed as loxone is accepting floating point values - } - } - return null; + @Override + Double getStep() { + return getStateDoubleValue(STATE_STEP); } } diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlEIBDimmer.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlEIBDimmer.java new file mode 100644 index 0000000000000..d3601dbf6ac1d --- /dev/null +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlEIBDimmer.java @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2010-2021 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.loxone.internal.controls; + +import static org.openhab.binding.loxone.internal.LxBindingConstants.*; + +import java.io.IOException; + +import org.openhab.binding.loxone.internal.types.LxCategory; +import org.openhab.binding.loxone.internal.types.LxTags; +import org.openhab.binding.loxone.internal.types.LxUuid; +import org.openhab.core.library.types.IncreaseDecreaseType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.Command; + +/** + * An EIB dimmer type of control on Loxone Miniserver. + *

+ * This control is absent in the API documentation. It looks like it behaves like a normal Dimmer, but it is missing the + * information about min, max and step values. + * + * @author Pawel Pieczul - initial contribution + * + */ +class LxControlEIBDimmer extends LxControl { + + static class Factory extends LxControlInstance { + @Override + LxControl create(LxUuid uuid) { + return new LxControlEIBDimmer(uuid); + } + + @Override + String getType() { + return "eibdimmer"; + } + } + + /** + * States + */ + private static final String STATE_POSITION = "position"; + private static final Double DEFAULT_MIN = 0.0; + private static final Double DEFAULT_MAX = 100.0; + private static final Double DEFAULT_STEP = 5.0; + + /** + * Command string used to set the dimmer ON + */ + private static final String CMD_ON = "On"; + /** + * Command string used to set the dimmer to OFF + */ + private static final String CMD_OFF = "Off"; + + LxControlEIBDimmer(LxUuid uuid) { + super(uuid); + } + + @Override + public void initialize(LxControlConfig config) { + super.initialize(config); + LxCategory category = getCategory(); + if (category != null && category.getType() == LxCategory.CategoryType.LIGHTS) { + tags.addAll(LxTags.LIGHTING); + } + addChannel("Dimmer", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_DIMMER), defaultChannelLabel, + "Dimmer", tags, this::handleCommands, this::getChannelState); + } + + Double getMin() { + return DEFAULT_MIN; + } + + Double getMax() { + return DEFAULT_MAX; + } + + Double getStep() { + return DEFAULT_STEP; + } + + private void handleCommands(Command command) throws IOException { + if (command instanceof OnOffType) { + if (command == OnOffType.ON) { + sendAction(CMD_ON); + } else { + sendAction(CMD_OFF); + } + } else if (command instanceof PercentType) { + PercentType percentCmd = (PercentType) command; + setPosition(percentCmd.doubleValue()); + } else if (command instanceof IncreaseDecreaseType) { + Double value = getStateDoubleValue(STATE_POSITION); + Double min = getMin(); + Double max = getMax(); + Double step = getStep(); + if (value != null && max != null && min != null && step != null && min >= 0 && max >= 0 && max > min) { + if ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE) { + value += step; + if (value > max) { + value = max; + } + } else { + value -= step; + if (value < min) { + value = min; + } + } + sendAction(value.toString()); + } + } + } + + private PercentType getChannelState() { + Double value = mapLoxoneToOH(getStateDoubleValue(STATE_POSITION)); + if (value != null && value >= 0 && value <= 100) { + return new PercentType(value.intValue()); + } + return null; + } + + /** + * Sets the current position of the dimmer + * + * @param position position to move to (0-100, 0 - full off, 100 - full on) + * @throws IOException error communicating with the Miniserver + */ + private void setPosition(Double position) throws IOException { + Double loxonePosition = mapOHToLoxone(position); + if (loxonePosition != null) { + sendAction(loxonePosition.toString()); + } + } + + private Double mapLoxoneToOH(Double loxoneValue) { + if (loxoneValue != null) { + // 0 means turn dimmer off, any value above zero should be mapped from min-max range + if (Double.compare(loxoneValue, 0.0) == 0) { + return 0.0; + } + Double max = getMax(); + Double min = getMin(); + if (max != null && min != null && max > min && min >= 0 && max >= 0) { + return 100 * (loxoneValue - min) / (max - min); + } + } + return null; + } + + private Double mapOHToLoxone(Double ohValue) { + if (ohValue != null) { + // 0 means turn dimmer off, any value above zero should be mapped to min-max range + if (Double.compare(ohValue, 0.0) == 0) { + return 0.0; + } + Double max = getMax(); + Double min = getMin(); + if (max != null && min != null) { + double value = min + ohValue * (max - min) / 100; + return value; // no rounding to integer value is needed as loxone is accepting floating point values + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlFactory.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlFactory.java index a83b1ae9fa1f7..8348945406f67 100644 --- a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlFactory.java +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlFactory.java @@ -31,6 +31,7 @@ class LxControlFactory { add(new LxControlAlarm.Factory()); add(new LxControlColorPickerV2.Factory()); add(new LxControlDimmer.Factory()); + add(new LxControlEIBDimmer.Factory()); add(new LxControlInfoOnlyAnalog.Factory()); add(new LxControlInfoOnlyDigital.Factory()); add(new LxControlIRoomControllerV2.Factory()); diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlIRoomControllerV2.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlIRoomControllerV2.java index 7035ec9af53ee..853c1ad3b387a 100644 --- a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlIRoomControllerV2.java +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControlIRoomControllerV2.java @@ -27,8 +27,6 @@ import org.openhab.core.types.State; import org.openhab.core.types.StateDescriptionFragment; import org.openhab.core.types.StateDescriptionFragmentBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * An Intelligent Room Controller V2. @@ -73,8 +71,6 @@ String getType() { private static final String CMD_SET_ABSENT_MAX_TEMPERATURE = "setAbsentMaxTemperature/"; private static final String CMD_SET_MANUAL_TEMPERATURE = "setManualTemperature/"; - private final Logger logger = LoggerFactory.getLogger(LxControlIRoomControllerV2.class); - private LxControlIRoomControllerV2(LxUuid uuid) { super(uuid); } diff --git a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/types/LxCategory.java b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/types/LxCategory.java index 5bfb8ec8c827f..3950fd61c9dc1 100644 --- a/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/types/LxCategory.java +++ b/bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/types/LxCategory.java @@ -57,11 +57,11 @@ public enum CategoryType { public CategoryType getType() { if (catType == null && type != null) { String tl = type.toLowerCase(); - if (tl.equals("lights")) { + if ("lights".equals(tl)) { catType = CategoryType.LIGHTS; - } else if (tl.equals("shading")) { + } else if ("shading".equals(tl)) { catType = CategoryType.SHADING; - } else if (tl.equals("indoortemperature")) { + } else if ("indoortemperature".equals(tl)) { catType = CategoryType.TEMPERATURE; } else { catType = CategoryType.UNDEFINED; diff --git a/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxControlAlarmNoPresenceTest.java b/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxControlAlarmNoPresenceTest.java index c3296d4b8255a..30c15a74054c9 100644 --- a/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxControlAlarmNoPresenceTest.java +++ b/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxControlAlarmNoPresenceTest.java @@ -47,7 +47,7 @@ public class LxControlAlarmNoPresenceTest extends LxControlTest { static final String SENSORS_CHANNEL = " / Sensors"; static final String QUIT_CHANNEL = " / Acknowledge"; - private static final String numberChannels[] = { NEXT_LEVEL_CHANNEL, NEXT_LEVEL_DELAY_CHANNEL, + private static final String NUMBER_CHANNELS[] = { NEXT_LEVEL_CHANNEL, NEXT_LEVEL_DELAY_CHANNEL, NEXT_LEVEL_DELAY_TOTAL_CHANNEL, LEVEL_CHANNEL, ARMED_DELAY_CHANNEL, ARMED_TOTAL_DELAY_CHANNEL }; @BeforeEach @@ -175,7 +175,7 @@ public void testLevelAndAcknowledge() { private void testNumberChannel(String channel, String state) { Map states = new HashMap<>(); - for (String s : numberChannels) { + for (String s : NUMBER_CHANNELS) { states.put(s, getChannelState(s)); } for (Double i = -100.0; i <= 100.0; i += 2.341) { diff --git a/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxControlEIBDimmerTest.java b/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxControlEIBDimmerTest.java new file mode 100644 index 0000000000000..e3b7e082eb499 --- /dev/null +++ b/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxControlEIBDimmerTest.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2021 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.loxone.internal.controls; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openhab.core.library.types.IncreaseDecreaseType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.StopMoveType; + +/** + * Test class for (@link LxControlDimmer} + * + * @author Pawel Pieczul - initial contribution + * + */ +public class LxControlEIBDimmerTest extends LxControlTest { + @BeforeEach + public void setup() { + setupControl("faa30f5c-4b4f-11e2-8928b8ba17ef51ee", "0b734138-037d-034e-ffff403fb0c34b9e", + "0fe650c2-0004-d446-ffff504f9410790f", "Kitchen Dimmer"); + } + + @Test + public void testControlCreation() { + testControlCreation(LxControlDimmer.class, 1, 0, 1, 1, 1); + } + + @Test + public void testChannels() { + testChannel("Dimmer"); + } + + @Test + public void testLoxonePositionChanges() { + // filling in missing state values + testChannelState(null); + for (Double i = 0.0; i <= 100.0; i += 1.0) { + changeLoxoneState("position", i); + testChannelState(new PercentType(i.intValue())); + } + // out of range + changeLoxoneState("position", 199.9); + testChannelState(null); + changeLoxoneState("position", 400.1); + testChannelState(null); + } + + @Test + public void testOnOffPercentCommands() { + executeCommand(OnOffType.ON); + testAction("On"); + executeCommand(OnOffType.OFF); + testAction("Off"); + for (Double i = 0.0; i <= 100.0; i += 1.0) { + executeCommand(new PercentType(i.intValue())); + testAction(i.toString()); + } + executeCommand(StopMoveType.MOVE); + testAction(null); + } + + @Test + public void testIncreaseDecreaseCommands() { + for (Double i = 0.0; i <= 95.0; i += 1.0) { + changeLoxoneState("position", i); + testChannelState(new PercentType(i.intValue())); + testAction(null); + executeCommand(IncreaseDecreaseType.INCREASE); + Double j = i + 5.0; + testAction(j.toString()); + } + for (Double i = 100.0; i >= 5.0; i -= 1.0) { + changeLoxoneState("position", i); + testChannelState(new PercentType(i.intValue())); + testAction(null); + executeCommand(IncreaseDecreaseType.DECREASE); + Double j = i - 5.0; + testAction(j.toString()); + } + // test not exceeding range + changeLoxoneState("position", 100.0); + testChannelState(PercentType.HUNDRED); + testAction(null); + executeCommand(IncreaseDecreaseType.INCREASE); + testAction("100.0"); + + changeLoxoneState("position", 0.0); + testChannelState(PercentType.ZERO); + testAction(null); + executeCommand(IncreaseDecreaseType.DECREASE); + testAction("0.0"); + } +} diff --git a/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxServerHandlerDummy.java b/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxServerHandlerDummy.java index a07b58b54c528..bff2d82f9efe3 100644 --- a/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxServerHandlerDummy.java +++ b/bundles/org.openhab.binding.loxone/src/test/java/org/openhab/binding/loxone/internal/controls/LxServerHandlerDummy.java @@ -96,7 +96,6 @@ public void removeControl(LxControl control) { @Override public void setChannelState(ChannelUID channelId, State state) { - // TODO Auto-generated method stub } @Override @@ -108,13 +107,11 @@ public void setChannelStateDescription(ChannelUID channelId, StateDescription de @Override public String getSetting(String name) { - // TODO Auto-generated method stub return null; } @Override public void setSettings(Map properties) { - // TODO Auto-generated method stub } @Override diff --git a/bundles/org.openhab.binding.loxone/src/test/resources/org/openhab/binding/loxone/internal/controls/LoxAPP3.json b/bundles/org.openhab.binding.loxone/src/test/resources/org/openhab/binding/loxone/internal/controls/LoxAPP3.json index 48cfd4e86375e..a8ca82f37e219 100644 --- a/bundles/org.openhab.binding.loxone/src/test/resources/org/openhab/binding/loxone/internal/controls/LoxAPP3.json +++ b/bundles/org.openhab.binding.loxone/src/test/resources/org/openhab/binding/loxone/internal/controls/LoxAPP3.json @@ -313,7 +313,20 @@ "step": "131b19cd-03c0-6407-ffffd2fd15b703b6" } }, - "0e367c09-0161-e2c1-ffff403fb0c34b9e": { + "faa30f5c-4b4f-11e2-8928b8ba17ef51ee": { + "name": "Kitchen Dimmer", + "type": "EIBDimmer", + "uuidAction": "faa30f5c-4b4f-11e2-8928b8ba17ef51ee", + "room": "0b734138-037d-034e-ffff403fb0c34b9e", + "cat": "0fe650c2-0004-d446-ffff504f9410790f", + "defaultRating": 0, + "isFavorite": false, + "isSecured": false, + "states": { + "position": "faa30f5d-4b4f-11e2-892eb8ba17ef51ee" + } + }, + "0e367c09-0161-e2c1-ffff403fb0c34b9e": { "name": "Window Blinds", "type": "Jalousie", "uuidAction": "0e367c09-0161-e2c1-ffff403fb0c34b9e",