| 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.
| | | `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 |
*/
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.
* @author Stephan Brunner - initial contribution
*
*/
-class LxControlDimmer extends LxControl {
+class LxControlDimmer extends LxControlEIBDimmer {
static class Factory extends LxControlInstance {
@Override
}
/**
- * 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);
}
}
--- /dev/null
+/**
+ * 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.
+ * <p>
+ * 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;
+ }
+}
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());
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.
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);
}
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;
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
private void testNumberChannel(String channel, String state) {
Map<String, State> 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) {
--- /dev/null
+/**
+ * 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");
+ }
+}
@Override
public void setChannelState(ChannelUID channelId, State state) {
- // TODO Auto-generated method stub
}
@Override
@Override
public String getSetting(String name) {
- // TODO Auto-generated method stub
return null;
}
@Override
public void setSettings(Map<String, String> properties) {
- // TODO Auto-generated method stub
}
@Override
"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",