[loxone] Sauna controller implementation (#11270)
Signed-off-by: Pawel Pieczul <pieczul@gmail.com>
This commit is contained in:
parent
0165ea7659
commit
5ffac34c7a
|
@ -30,6 +30,7 @@ import org.openhab.binding.loxone.internal.types.LxState;
|
||||||
import org.openhab.binding.loxone.internal.types.LxUuid;
|
import org.openhab.binding.loxone.internal.types.LxUuid;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.PercentType;
|
||||||
import org.openhab.core.library.types.StringType;
|
import org.openhab.core.library.types.StringType;
|
||||||
import org.openhab.core.thing.Channel;
|
import org.openhab.core.thing.Channel;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
@ -130,6 +131,8 @@ public class LxControl {
|
||||||
Map<String, String> outputs;
|
Map<String, String> outputs;
|
||||||
Boolean presenceConnected;
|
Boolean presenceConnected;
|
||||||
Integer connectedInputs;
|
Integer connectedInputs;
|
||||||
|
Boolean hasVaporizer;
|
||||||
|
Boolean hasDoorSensor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -586,6 +589,24 @@ public class LxControl {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets value of a state object of given name, if exists, and converts it to percent type value.
|
||||||
|
* Assumes the state value is between 0.0-100.0 which corresponds directly to 0-100 percent.
|
||||||
|
*
|
||||||
|
* @param name state name
|
||||||
|
* @return state value
|
||||||
|
*/
|
||||||
|
State getStatePercentValue(String name) {
|
||||||
|
Double value = getStateDoubleValue(name);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value >= 0.0 && value <= 100.0) {
|
||||||
|
return new PercentType(value.intValue());
|
||||||
|
}
|
||||||
|
return UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets text value of a state object of given name, if exists
|
* Gets text value of a state object of given name, if exists
|
||||||
*
|
*
|
||||||
|
|
|
@ -43,6 +43,7 @@ class LxControlFactory {
|
||||||
add(new LxControlMeter.Factory());
|
add(new LxControlMeter.Factory());
|
||||||
add(new LxControlPushbutton.Factory());
|
add(new LxControlPushbutton.Factory());
|
||||||
add(new LxControlRadio.Factory());
|
add(new LxControlRadio.Factory());
|
||||||
|
add(new LxControlSauna.Factory());
|
||||||
add(new LxControlSlider.Factory());
|
add(new LxControlSlider.Factory());
|
||||||
add(new LxControlSwitch.Factory());
|
add(new LxControlSwitch.Factory());
|
||||||
add(new LxControlTextState.Factory());
|
add(new LxControlTextState.Factory());
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
/**
|
||||||
|
* 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.LxUuid;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loxone Miniserver's Sauna
|
||||||
|
*
|
||||||
|
* @author Pawel Pieczul - initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class LxControlSauna extends LxControl {
|
||||||
|
|
||||||
|
static class Factory extends LxControlInstance {
|
||||||
|
@Override
|
||||||
|
LxControl create(LxUuid uuid) {
|
||||||
|
return new LxControlSauna(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String getType() {
|
||||||
|
return "sauna";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String STATE_ACTIVE = "active";
|
||||||
|
private static final String STATE_POWER_LEVEL = "power";
|
||||||
|
private static final String STATE_TEMP_ACTUAL = "tempactual";
|
||||||
|
private static final String STATE_TEMP_BENCH = "tempbench";
|
||||||
|
private static final String STATE_TEMP_TARGET = "temptarget";
|
||||||
|
private static final String STATE_FAN = "fan";
|
||||||
|
private static final String STATE_DRYING = "drying";
|
||||||
|
private static final String STATE_DOOR_CLOSED = "doorclosed";
|
||||||
|
private static final String STATE_ERROR = "error";
|
||||||
|
private static final String STATE_VAPOR_POWER_LEVEL = "vaporpower";
|
||||||
|
private static final String STATE_SAUNA_ERROR = "saunaerror";
|
||||||
|
private static final String STATE_TIMER = "timer";
|
||||||
|
private static final String STATE_TIMER_TOTAL = "timertotal";
|
||||||
|
private static final String STATE_OUT_OF_WATER = "lesswater";
|
||||||
|
private static final String STATE_HUMIDITY_ACTUAL = "humidityactual";
|
||||||
|
private static final String STATE_HUMIDITY_TARGET = "humiditytarget";
|
||||||
|
private static final String STATE_EVAPORATOR_MODE = "mode";
|
||||||
|
|
||||||
|
private static final String CMD_ON = "on";
|
||||||
|
private static final String CMD_OFF = "off";
|
||||||
|
private static final String CMD_FAN_ON = "fanon";
|
||||||
|
private static final String CMD_FAN_OFF = "fanoff";
|
||||||
|
private static final String CMD_SET_TEMP_TARGET = "temp/";
|
||||||
|
private static final String CMD_SET_HUMIDITY_TARGET = "humidity/";
|
||||||
|
private static final String CMD_SET_EVAPORATOR_MODE = "mode/";
|
||||||
|
private static final String CMD_NEXT_STATE = "pulse";
|
||||||
|
private static final String CMD_START_TIMER = "starttimer";
|
||||||
|
|
||||||
|
LxControlSauna(LxUuid uuid) {
|
||||||
|
super(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(LxControlConfig config) {
|
||||||
|
super.initialize(config);
|
||||||
|
addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
|
||||||
|
defaultChannelLabel + " / Active", "Sauna Active", tags, this::handleSaunaActivateCommands,
|
||||||
|
() -> getStateOnOffValue(STATE_ACTIVE));
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
|
||||||
|
defaultChannelLabel + " / Power", "Sauna Power Level", tags, null,
|
||||||
|
() -> getStatePercentValue(STATE_POWER_LEVEL));
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
|
||||||
|
defaultChannelLabel + " / Temperature / Actual", "Actual Temperature", tags, null,
|
||||||
|
() -> getStateDecimalValue(STATE_TEMP_ACTUAL));
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
|
||||||
|
defaultChannelLabel + " / Temperature / Bench", "Bench Temperature", tags, null,
|
||||||
|
() -> getStateDecimalValue(STATE_TEMP_BENCH));
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_NUMBER),
|
||||||
|
defaultChannelLabel + " / Temperature / Target", "Target Temperature", tags,
|
||||||
|
(cmd) -> handleSetNumberCommands(cmd, CMD_SET_TEMP_TARGET),
|
||||||
|
() -> getStateDecimalValue(STATE_TEMP_TARGET));
|
||||||
|
addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
|
||||||
|
defaultChannelLabel + " / Fan", "Fan", tags, this::handleFanCommands,
|
||||||
|
() -> getStateOnOffValue(STATE_FAN));
|
||||||
|
addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_SWITCH),
|
||||||
|
defaultChannelLabel + " / Drying", "Drying", tags, null, () -> getStateOnOffValue(STATE_DRYING));
|
||||||
|
if (details != null && details.hasDoorSensor != null && details.hasDoorSensor) {
|
||||||
|
addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_SWITCH),
|
||||||
|
defaultChannelLabel + " / Door Closed", "Door Closed", tags, null,
|
||||||
|
() -> getStateOnOffValue(STATE_DOOR_CLOSED));
|
||||||
|
}
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
|
||||||
|
defaultChannelLabel + " / Error Code", "Error Code", tags, null, () -> getStateErrorValue());
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
|
||||||
|
defaultChannelLabel + " / Timer / Current", "Current Timer Value", tags, null,
|
||||||
|
() -> getStateDecimalValue(STATE_TIMER));
|
||||||
|
addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
|
||||||
|
defaultChannelLabel + " / Timer / Trigger", "Start Timer", tags,
|
||||||
|
(cmd) -> handleTriggerCommands(cmd, CMD_START_TIMER), () -> OnOffType.OFF);
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
|
||||||
|
defaultChannelLabel + " / Timer / Total", "Total Timer Value", tags, null,
|
||||||
|
() -> getStateDecimalValue(STATE_TIMER_TOTAL));
|
||||||
|
if (details != null && details.hasVaporizer != null && details.hasVaporizer) {
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
|
||||||
|
defaultChannelLabel + " / Evaporator / Power", "Evaporator Power Level", tags, null,
|
||||||
|
() -> getStatePercentValue(STATE_VAPOR_POWER_LEVEL));
|
||||||
|
addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_SWITCH),
|
||||||
|
defaultChannelLabel + " / Evaporator / Out Of Water", "Evaporator Out Of Water", tags, null,
|
||||||
|
() -> getStateOnOffValue(STATE_OUT_OF_WATER));
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
|
||||||
|
defaultChannelLabel + " / Evaporator / Humidity / Actual", "Actual Humidity", tags, null,
|
||||||
|
() -> getStateDecimalValue(STATE_HUMIDITY_ACTUAL));
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_NUMBER),
|
||||||
|
defaultChannelLabel + " / Evaporator / Humidity / Target", "Target Humidity", tags,
|
||||||
|
(cmd) -> handleSetNumberCommands(cmd, CMD_SET_HUMIDITY_TARGET),
|
||||||
|
() -> getStateDecimalValue(STATE_HUMIDITY_TARGET));
|
||||||
|
addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_NUMBER),
|
||||||
|
defaultChannelLabel + " / Evaporator / Mode", "Evaporator Mode", tags, this::handleModeCommands,
|
||||||
|
() -> getStateDecimalValue(STATE_EVAPORATOR_MODE));
|
||||||
|
}
|
||||||
|
addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
|
||||||
|
defaultChannelLabel + " / Next State", "Trigger Next State", tags,
|
||||||
|
(cmd) -> handleTriggerCommands(cmd, CMD_NEXT_STATE), () -> OnOffType.OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSaunaActivateCommands(Command command) throws IOException {
|
||||||
|
if (command instanceof OnOffType) {
|
||||||
|
if ((OnOffType) command == OnOffType.ON) {
|
||||||
|
sendAction(CMD_ON);
|
||||||
|
} else {
|
||||||
|
sendAction(CMD_OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetNumberCommands(Command command, String prefix) throws IOException {
|
||||||
|
if (command instanceof DecimalType) {
|
||||||
|
Double value = ((DecimalType) command).doubleValue();
|
||||||
|
sendAction(prefix + value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFanCommands(Command command) throws IOException {
|
||||||
|
if (command instanceof OnOffType) {
|
||||||
|
if ((OnOffType) command == OnOffType.ON) {
|
||||||
|
sendAction(CMD_FAN_ON);
|
||||||
|
} else {
|
||||||
|
sendAction(CMD_FAN_OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTriggerCommands(Command command, String prefix) throws IOException {
|
||||||
|
if (command instanceof OnOffType && (OnOffType) command == OnOffType.ON) {
|
||||||
|
sendAction(prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleModeCommands(Command command) throws IOException {
|
||||||
|
if (command instanceof DecimalType) {
|
||||||
|
Double value = ((DecimalType) command).doubleValue();
|
||||||
|
// per API there are 7 evaporator modes selected with number 0-6
|
||||||
|
if (value % 1 == 0 && value >= 0.0 && value <= 6.0) {
|
||||||
|
sendAction(CMD_SET_EVAPORATOR_MODE + value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private State getStateErrorValue() {
|
||||||
|
Double val = getStateDoubleValue(STATE_ERROR);
|
||||||
|
if (val != null && val != 0.0) {
|
||||||
|
return getStateDecimalValue(STATE_SAUNA_ERROR);
|
||||||
|
}
|
||||||
|
return DecimalType.ZERO;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* 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.OnOffType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class for (@link LxControlSauna} - version with door sensor no vaporizer
|
||||||
|
*
|
||||||
|
* @author Pawel Pieczul - initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class LxControlSaunaDoorTest extends LxControlSaunaTest {
|
||||||
|
@Override
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
setupControl("17452951-02ae-1b6e-ffff266cf17271dc", "0b734138-037d-034e-ffff403fb0c34b9e",
|
||||||
|
"0fe650c2-0004-d446-ffff504f9410790f", "Sauna Controller No Vaporizer With Door Sensor");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testControlCreation() {
|
||||||
|
testControlCreation(LxControlSauna.class, 3, 0, 13, 13, 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testChannels() {
|
||||||
|
super.testChannels();
|
||||||
|
testChannel("Switch", DOOR_CLOSED_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testDoorClosedChannel() {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
changeLoxoneState("doorclosed", 0.0);
|
||||||
|
testChannelState(DOOR_CLOSED_CHANNEL, OnOffType.OFF);
|
||||||
|
changeLoxoneState("doorclosed", 1.0);
|
||||||
|
testChannelState(DOOR_CLOSED_CHANNEL, OnOffType.ON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/**
|
||||||
|
* 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.DecimalType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.PercentType;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class for (@link LxControlSauna} - version with vaporizer and door sensor
|
||||||
|
*
|
||||||
|
* @author Pawel Pieczul - initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class LxControlSaunaDoorVaporizerTest extends LxControlSaunaDoorTest {
|
||||||
|
@Override
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
setupControl("17452951-02ae-1b6e-ffff266cf17271dd", "0b734138-037d-034e-ffff403fb0c34b9e",
|
||||||
|
"0fe650c2-0004-d446-ffff504f9410790f", "Sauna Controller With Vaporizer With Door Sensor");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testControlCreation() {
|
||||||
|
testControlCreation(LxControlSauna.class, 3, 0, 18, 18, 21);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testChannels() {
|
||||||
|
super.testChannels();
|
||||||
|
testChannel("Number", VAPOR_POWER_CHANNEL);
|
||||||
|
testChannel("Switch", OUT_OF_WATER_CHANNEL);
|
||||||
|
testChannel("Number", ACTUAL_HUMIDITY_CHANNEL);
|
||||||
|
testChannel("Number", TARGET_HUMIDITY_CHANNEL);
|
||||||
|
testChannel("Number", EVAPORATOR_MODE_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void vaporPowerChannel() {
|
||||||
|
for (Double i = 0.0; i <= 100.0; i += 1.0) {
|
||||||
|
changeLoxoneState("vaporpower", i);
|
||||||
|
testChannelState(VAPOR_POWER_CHANNEL, new PercentType(i.intValue()));
|
||||||
|
}
|
||||||
|
changeLoxoneState("vaporpower", -1.0);
|
||||||
|
testChannelState(VAPOR_POWER_CHANNEL, UnDefType.UNDEF);
|
||||||
|
changeLoxoneState("vaporpower", 100.1);
|
||||||
|
testChannelState(VAPOR_POWER_CHANNEL, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testOutOfWaterChannel() {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
changeLoxoneState("lesswater", 0.0);
|
||||||
|
testChannelState(OUT_OF_WATER_CHANNEL, OnOffType.OFF);
|
||||||
|
changeLoxoneState("lesswater", 1.0);
|
||||||
|
testChannelState(OUT_OF_WATER_CHANNEL, OnOffType.ON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testActualHumidityChannel() {
|
||||||
|
for (Double i = 0.0; i <= 100.0; i += 0.17) {
|
||||||
|
changeLoxoneState("humidityactual", i);
|
||||||
|
testChannelState(ACTUAL_HUMIDITY_CHANNEL, new DecimalType(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testTargetHumidityChannel() {
|
||||||
|
for (Double i = 0.0; i <= 100.0; i += 0.17) {
|
||||||
|
changeLoxoneState("humiditytarget", i);
|
||||||
|
testChannelState(TARGET_HUMIDITY_CHANNEL, new DecimalType(i));
|
||||||
|
}
|
||||||
|
for (Double i = 0.0; i <= 100.0; i += 0.13) {
|
||||||
|
executeCommand(TARGET_HUMIDITY_CHANNEL, new DecimalType(i));
|
||||||
|
testAction("humidity/" + i.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testEvaporatorModelChannel() {
|
||||||
|
for (Double i = 0.0; i <= 6.0; i += 1.0) {
|
||||||
|
changeLoxoneState("mode", i);
|
||||||
|
testChannelState(EVAPORATOR_MODE_CHANNEL, new DecimalType(i));
|
||||||
|
}
|
||||||
|
for (Double i = -10.0; i < 0.0; i += 0.4) {
|
||||||
|
executeCommand(EVAPORATOR_MODE_CHANNEL, new DecimalType(i));
|
||||||
|
testAction(null);
|
||||||
|
}
|
||||||
|
for (Double i = 0.0; i < 6.0; i += 1.0) {
|
||||||
|
executeCommand(EVAPORATOR_MODE_CHANNEL, new DecimalType(i));
|
||||||
|
testAction("mode/" + i.toString());
|
||||||
|
}
|
||||||
|
for (Double i = 6.1; i < 15.0; i += 0.1) {
|
||||||
|
executeCommand(EVAPORATOR_MODE_CHANNEL, new DecimalType(i));
|
||||||
|
testAction(null);
|
||||||
|
}
|
||||||
|
for (Double i = 0.3; i < 6.0; i += 1.0) {
|
||||||
|
executeCommand(EVAPORATOR_MODE_CHANNEL, new DecimalType(i));
|
||||||
|
testAction(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
/**
|
||||||
|
* 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.DecimalType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.PercentType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class for (@link LxControlSauna} - version with no door sensor and no vaporizer
|
||||||
|
*
|
||||||
|
* @author Pawel Pieczul - initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class LxControlSaunaTest extends LxControlTest {
|
||||||
|
private static final String ACTIVE_CHANNEL = " / Active";
|
||||||
|
private static final String POWER_CHANNEL = " / Power";
|
||||||
|
private static final String TEMP_ACTUAL_CHANNEL = " / Temperature / Actual";
|
||||||
|
private static final String TEMP_BENCH_CHANNEL = " / Temperature / Bench";
|
||||||
|
private static final String TEMP_TARGET_CHANNEL = " / Temperature / Target";
|
||||||
|
private static final String FAN_CHANNEL = " / Fan";
|
||||||
|
private static final String DRYING_CHANNEL = " / Drying";
|
||||||
|
static final String DOOR_CLOSED_CHANNEL = " / Door Closed";
|
||||||
|
private static final String ERROR_CODE_CHANNEL = " / Error Code";
|
||||||
|
static final String VAPOR_POWER_CHANNEL = " / Evaporator / Power";
|
||||||
|
private static final String TIMER_CURRENT_CHANNEL = " / Timer / Current";
|
||||||
|
private static final String TIMER_TRIGGER_CHANNEL = " / Timer / Trigger";
|
||||||
|
private static final String TIMER_TOTAL_CHANNEL = " / Timer / Total";
|
||||||
|
static final String OUT_OF_WATER_CHANNEL = " / Evaporator / Out Of Water";
|
||||||
|
static final String ACTUAL_HUMIDITY_CHANNEL = " / Evaporator / Humidity / Actual";
|
||||||
|
static final String TARGET_HUMIDITY_CHANNEL = " / Evaporator / Humidity / Target";
|
||||||
|
static final String EVAPORATOR_MODE_CHANNEL = " / Evaporator / Mode";
|
||||||
|
private static final String NEXT_STATE_CHANNEL = " / Next State";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
setupControl("17452951-02ae-1b6e-ffff266cf17271db", "0b734138-037d-034e-ffff403fb0c34b9e",
|
||||||
|
"0fe650c2-0004-d446-ffff504f9410790f", "Sauna Controller No Vaporizer No Door Sensor");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testControlCreation() {
|
||||||
|
testControlCreation(LxControlSauna.class, 3, 0, 12, 12, 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChannels() {
|
||||||
|
testChannel("Switch", ACTIVE_CHANNEL);
|
||||||
|
testChannel("Number", POWER_CHANNEL);
|
||||||
|
testChannel("Number", TEMP_ACTUAL_CHANNEL);
|
||||||
|
testChannel("Number", TEMP_BENCH_CHANNEL);
|
||||||
|
testChannel("Number", TEMP_TARGET_CHANNEL);
|
||||||
|
testChannel("Switch", FAN_CHANNEL);
|
||||||
|
testChannel("Switch", DRYING_CHANNEL);
|
||||||
|
testChannel("Number", ERROR_CODE_CHANNEL);
|
||||||
|
testChannel("Number", TIMER_CURRENT_CHANNEL);
|
||||||
|
testChannel("Switch", TIMER_TRIGGER_CHANNEL);
|
||||||
|
testChannel("Number", TIMER_TOTAL_CHANNEL);
|
||||||
|
testChannel("Switch", NEXT_STATE_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActiveChannel() {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
changeLoxoneState("active", 0.0);
|
||||||
|
testChannelState(ACTIVE_CHANNEL, OnOffType.OFF);
|
||||||
|
changeLoxoneState("active", 1.0);
|
||||||
|
testChannelState(ACTIVE_CHANNEL, OnOffType.ON);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
executeCommand(ACTIVE_CHANNEL, OnOffType.ON);
|
||||||
|
testAction("on");
|
||||||
|
executeCommand(ACTIVE_CHANNEL, DecimalType.ZERO);
|
||||||
|
testAction(null);
|
||||||
|
executeCommand(ACTIVE_CHANNEL, OnOffType.OFF);
|
||||||
|
testAction("off");
|
||||||
|
executeCommand(ACTIVE_CHANNEL, StringType.EMPTY);
|
||||||
|
testAction(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPowerChannel() {
|
||||||
|
for (Double i = 0.0; i <= 100.0; i += 1.0) {
|
||||||
|
changeLoxoneState("power", i);
|
||||||
|
testChannelState(POWER_CHANNEL, new PercentType(i.intValue()));
|
||||||
|
}
|
||||||
|
changeLoxoneState("power", -1.0);
|
||||||
|
testChannelState(POWER_CHANNEL, UnDefType.UNDEF);
|
||||||
|
changeLoxoneState("power", 100.1);
|
||||||
|
testChannelState(POWER_CHANNEL, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTempActualBenchChannels() {
|
||||||
|
for (Double i = -20.0; i <= 150.0; i += 0.37) {
|
||||||
|
changeLoxoneState("tempactual", i);
|
||||||
|
testChannelState(TEMP_ACTUAL_CHANNEL, new DecimalType(i));
|
||||||
|
changeLoxoneState("tempbench", i * 1.1);
|
||||||
|
testChannelState(TEMP_BENCH_CHANNEL, new DecimalType(i * 1.1));
|
||||||
|
changeLoxoneState("temptarget", i * 1.2);
|
||||||
|
testChannelState(TEMP_TARGET_CHANNEL, new DecimalType(i * 1.2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTempTargetSetCommand() {
|
||||||
|
for (Double i = 0.0; i <= 150.0; i += 0.37) {
|
||||||
|
executeCommand(TEMP_TARGET_CHANNEL, new DecimalType(i));
|
||||||
|
testAction("temp/" + i.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFanChannel() {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
changeLoxoneState("fan", 0.0);
|
||||||
|
testChannelState(FAN_CHANNEL, OnOffType.OFF);
|
||||||
|
changeLoxoneState("fan", 1.0);
|
||||||
|
testChannelState(FAN_CHANNEL, OnOffType.ON);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
executeCommand(FAN_CHANNEL, OnOffType.ON);
|
||||||
|
testAction("fanon");
|
||||||
|
executeCommand(FAN_CHANNEL, DecimalType.ZERO);
|
||||||
|
testAction(null);
|
||||||
|
executeCommand(FAN_CHANNEL, OnOffType.OFF);
|
||||||
|
testAction("fanoff");
|
||||||
|
executeCommand(FAN_CHANNEL, StringType.EMPTY);
|
||||||
|
testAction(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDryingChannel() {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
changeLoxoneState("drying", 0.0);
|
||||||
|
testChannelState(DRYING_CHANNEL, OnOffType.OFF);
|
||||||
|
changeLoxoneState("drying", 1.0);
|
||||||
|
testChannelState(DRYING_CHANNEL, OnOffType.ON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoorClosedChannel() {
|
||||||
|
testNoChannel(DOOR_CLOSED_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testErrorCodeChannel() {
|
||||||
|
for (Double i = 0.0; i < 10.0; i += 1.0) {
|
||||||
|
changeLoxoneState("saunaerror", i);
|
||||||
|
changeLoxoneState("error", 0.0);
|
||||||
|
testChannelState(ERROR_CODE_CHANNEL, DecimalType.ZERO);
|
||||||
|
changeLoxoneState("error", 1.0);
|
||||||
|
testChannelState(ERROR_CODE_CHANNEL, new DecimalType(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTimerCurrentTotalChannels() {
|
||||||
|
for (Double i = 0.0; i <= 150.0; i += 0.21) {
|
||||||
|
changeLoxoneState("timer", i);
|
||||||
|
testChannelState(TIMER_CURRENT_CHANNEL, new DecimalType(i));
|
||||||
|
changeLoxoneState("timertotal", i * 1.3);
|
||||||
|
testChannelState(TIMER_TOTAL_CHANNEL, new DecimalType(i * 1.3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTimerTriggerChannel() {
|
||||||
|
for (int i = 0; i <= 10; i++) {
|
||||||
|
executeCommand(TIMER_TRIGGER_CHANNEL, DecimalType.ZERO);
|
||||||
|
testAction(null);
|
||||||
|
testChannelState(TIMER_TRIGGER_CHANNEL, OnOffType.OFF);
|
||||||
|
executeCommand(TIMER_TRIGGER_CHANNEL, OnOffType.ON);
|
||||||
|
testAction("starttimer");
|
||||||
|
testChannelState(TIMER_TRIGGER_CHANNEL, OnOffType.OFF);
|
||||||
|
executeCommand(TIMER_TRIGGER_CHANNEL, OnOffType.OFF);
|
||||||
|
testAction(null);
|
||||||
|
testChannelState(TIMER_TRIGGER_CHANNEL, OnOffType.OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void vaporPowerChannel() {
|
||||||
|
testNoChannel(VAPOR_POWER_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOutOfWaterChannel() {
|
||||||
|
testNoChannel(OUT_OF_WATER_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActualHumidityChannel() {
|
||||||
|
testNoChannel(ACTUAL_HUMIDITY_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTargetHumidityChannel() {
|
||||||
|
testNoChannel(TARGET_HUMIDITY_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEvaporatorModelChannel() {
|
||||||
|
testNoChannel(EVAPORATOR_MODE_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNextStateTriggerChannel() {
|
||||||
|
for (int i = 0; i <= 10; i++) {
|
||||||
|
executeCommand(NEXT_STATE_CHANNEL, DecimalType.ZERO);
|
||||||
|
testAction(null);
|
||||||
|
testChannelState(NEXT_STATE_CHANNEL, OnOffType.OFF);
|
||||||
|
executeCommand(NEXT_STATE_CHANNEL, OnOffType.ON);
|
||||||
|
testAction("pulse");
|
||||||
|
testChannelState(NEXT_STATE_CHANNEL, OnOffType.OFF);
|
||||||
|
executeCommand(NEXT_STATE_CHANNEL, OnOffType.OFF);
|
||||||
|
testAction(null);
|
||||||
|
testChannelState(NEXT_STATE_CHANNEL, OnOffType.OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -147,6 +147,13 @@ class LxControlTest {
|
||||||
testChannel(itemType, namePostFix, null, null, null, null, null, null, null);
|
testChannel(itemType, namePostFix, null, null, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testNoChannel(String namePostFix) {
|
||||||
|
LxControl ctrl = getControl(controlUuid);
|
||||||
|
assertNotNull(ctrl);
|
||||||
|
Channel c = getChannel(getExpectedName(ctrl.getLabel(), ctrl.getRoom().getName(), namePostFix), ctrl);
|
||||||
|
assertNull(c);
|
||||||
|
}
|
||||||
|
|
||||||
void testChannel(String itemType, String namePostFix, Set<String> tags) {
|
void testChannel(String itemType, String namePostFix, Set<String> tags) {
|
||||||
testChannel(itemType, namePostFix, null, null, null, null, null, null, tags);
|
testChannel(itemType, namePostFix, null, null, null, null, null, null, tags);
|
||||||
}
|
}
|
||||||
|
@ -249,9 +256,11 @@ class LxControlTest {
|
||||||
private Channel getChannel(String name, LxControl c) {
|
private Channel getChannel(String name, LxControl c) {
|
||||||
List<Channel> channels = c.getChannels();
|
List<Channel> channels = c.getChannels();
|
||||||
List<Channel> filtered = channels.stream().filter(a -> name.equals(a.getLabel())).collect(Collectors.toList());
|
List<Channel> filtered = channels.stream().filter(a -> name.equals(a.getLabel())).collect(Collectors.toList());
|
||||||
assertEquals(1, filtered.size());
|
if (filtered.size() == 1) {
|
||||||
return filtered.get(0);
|
return filtered.get(0);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private <T> long numberOfControls(Class<T> c) {
|
private <T> long numberOfControls(Class<T> c) {
|
||||||
Collection<LxControl> v = handler.controls.values();
|
Collection<LxControl> v = handler.controls.values();
|
||||||
|
|
|
@ -851,6 +851,106 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"17452951-02ae-1b6e-ffff266cf17271db": {
|
||||||
|
"name": "Sauna Controller No Vaporizer No Door Sensor",
|
||||||
|
"type": "Sauna",
|
||||||
|
"uuidAction": "17452951-02ae-1b6e-ffff266cf17271db",
|
||||||
|
"room": "0b734138-037d-034e-ffff403fb0c34b9e",
|
||||||
|
"cat": "0fe650c2-0004-d446-ffff504f9410790f",
|
||||||
|
"defaultRating": 0,
|
||||||
|
"isFavorite": false,
|
||||||
|
"isSecured": false,
|
||||||
|
"details": {
|
||||||
|
"jLockable": true,
|
||||||
|
"hasVaporizer": false,
|
||||||
|
"hasDoorSensor": false
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"jLocked": "97452951-02ae-1b57-ffffe29abab51e83",
|
||||||
|
"power": "17452951-02ae-1b62-ffffe29abab51e83",
|
||||||
|
"tempActual": "17452951-02ae-1b51-ffffe29abab51e83",
|
||||||
|
"tempBench": "17452951-02ae-1b55-ffffe29abab51e83",
|
||||||
|
"tempTarget": "17452951-02ae-1b6b-ffffe29abab51e83",
|
||||||
|
"fan": "17452951-02ae-1b67-ffffe29abab51e83",
|
||||||
|
"drying": "17452951-02ae-1b69-ffffe29abab51e83",
|
||||||
|
"doorClosed": "17452951-02ae-1b54-ffffe29abab51e83",
|
||||||
|
"presence": "17452951-02ae-1b56-ffffe29abab51e83",
|
||||||
|
"error": "17452951-02ae-1b6a-ffffe29abab51e83",
|
||||||
|
"saunaError": "17452951-02ae-1b4e-ffffe29abab51e83",
|
||||||
|
"timer": "17452951-02ae-1b68-ffffe29abab51e83",
|
||||||
|
"active": "17452951-02ae-1b66-ffffe29abab51e83",
|
||||||
|
"timerTotal": "17452951-02ae-1b5c-ffffe29abab51e83"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"17452951-02ae-1b6e-ffff266cf17271dc": {
|
||||||
|
"name": "Sauna Controller No Vaporizer With Door Sensor",
|
||||||
|
"type": "Sauna",
|
||||||
|
"uuidAction": "17452951-02ae-1b6e-ffff266cf17271dc",
|
||||||
|
"room": "0b734138-037d-034e-ffff403fb0c34b9e",
|
||||||
|
"cat": "0fe650c2-0004-d446-ffff504f9410790f",
|
||||||
|
"defaultRating": 0,
|
||||||
|
"isFavorite": false,
|
||||||
|
"isSecured": false,
|
||||||
|
"details": {
|
||||||
|
"jLockable": true,
|
||||||
|
"hasVaporizer": false,
|
||||||
|
"hasDoorSensor": true
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"jLocked": "97452951-02ae-1b57-ffffe29abab51e84",
|
||||||
|
"power": "17452951-02ae-1b62-ffffe29abab51e84",
|
||||||
|
"tempActual": "17452951-02ae-1b51-ffffe29abab51e84",
|
||||||
|
"tempBench": "17452951-02ae-1b55-ffffe29abab51e84",
|
||||||
|
"tempTarget": "17452951-02ae-1b6b-ffffe29abab51e84",
|
||||||
|
"fan": "17452951-02ae-1b67-ffffe29abab51e84",
|
||||||
|
"drying": "17452951-02ae-1b69-ffffe29abab51e84",
|
||||||
|
"doorClosed": "17452951-02ae-1b54-ffffe29abab51e84",
|
||||||
|
"presence": "17452951-02ae-1b56-ffffe29abab51e84",
|
||||||
|
"error": "17452951-02ae-1b6a-ffffe29abab51e84",
|
||||||
|
"saunaError": "17452951-02ae-1b4e-ffffe29abab51e84",
|
||||||
|
"timer": "17452951-02ae-1b68-ffffe29abab51e84",
|
||||||
|
"active": "17452951-02ae-1b66-ffffe29abab51e84",
|
||||||
|
"timerTotal": "17452951-02ae-1b5c-ffffe29abab51e84"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"17452951-02ae-1b6e-ffff266cf17271dd": {
|
||||||
|
"name": "Sauna Controller With Vaporizer With Door Sensor",
|
||||||
|
"type": "Sauna",
|
||||||
|
"uuidAction": "17452951-02ae-1b6e-ffff266cf17271dd",
|
||||||
|
"room": "0b734138-037d-034e-ffff403fb0c34b9e",
|
||||||
|
"cat": "0fe650c2-0004-d446-ffff504f9410790f",
|
||||||
|
"defaultRating": 0,
|
||||||
|
"isFavorite": false,
|
||||||
|
"isSecured": false,
|
||||||
|
"details": {
|
||||||
|
"jLockable": true,
|
||||||
|
"hasVaporizer": true,
|
||||||
|
"hasDoorSensor": true
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"jLocked": "97452951-02ae-1b57-ffffe29abab51e85",
|
||||||
|
"power": "17452951-02ae-1b62-ffffe29abab51e85",
|
||||||
|
"tempActual": "17452951-02ae-1b51-ffffe29abab51e85",
|
||||||
|
"tempBench": "17452951-02ae-1b55-ffffe29abab51e85",
|
||||||
|
"tempTarget": "17452951-02ae-1b6b-ffffe29abab51e85",
|
||||||
|
"fan": "17452951-02ae-1b67-ffffe29abab51e85",
|
||||||
|
"drying": "17452951-02ae-1b69-ffffe29abab51e85",
|
||||||
|
"doorClosed": "17452951-02ae-1b54-ffffe29abab51e85",
|
||||||
|
"presence": "17452951-02ae-1b56-ffffe29abab51e85",
|
||||||
|
"error": "17452951-02ae-1b6a-ffffe29abab51e85",
|
||||||
|
"vaporPower": "17eb161f-0350-6d90-ffff292cf0ed07b9",
|
||||||
|
"saunaError": "17452951-02ae-1b4e-ffffe29abab51e85",
|
||||||
|
"tempAndHumidity": "17eb161f-0350-6d6d-ffff292cf0ed07b9",
|
||||||
|
"ready": "17eb161f-0350-6d6e-ffff292cf0ed07b9",
|
||||||
|
"timer": "17452951-02ae-1b68-ffffe29abab51e85",
|
||||||
|
"active": "17452951-02ae-1b66-ffffe29abab51e85",
|
||||||
|
"lessWater": "17eb161f-0350-6d80-ffff292cf0ed07b9",
|
||||||
|
"humidityActual": "17eb161f-0350-6d7a-ffff292cf0ed07b9",
|
||||||
|
"humidityTarget": "17eb161f-0350-6d99-ffff292cf0ed07b9",
|
||||||
|
"mode": "17eb161f-0350-6d96-ffff292cf0ed07b9",
|
||||||
|
"timerTotal": "17452951-02ae-1b5c-ffffe29abab51e85"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"weatherServer": {
|
"weatherServer": {
|
||||||
|
|
Loading…
Reference in New Issue