[daikin] Update channels immediately after a successful API command (#17149)

* [daikin] Update channels immediately after a successful API command

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
jimtng 2024-08-28 19:43:16 +10:00 committed by Ciprian Pascu
parent c40312fa2d
commit 493994d97f
6 changed files with 185 additions and 106 deletions

View File

@ -15,7 +15,6 @@ package org.openhab.binding.daikin.internal;
import java.io.EOFException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -33,7 +32,6 @@ import org.openhab.binding.daikin.internal.api.DemandControl;
import org.openhab.binding.daikin.internal.api.EnergyInfoDayAndWeek;
import org.openhab.binding.daikin.internal.api.EnergyInfoYear;
import org.openhab.binding.daikin.internal.api.Enums.SpecialMode;
import org.openhab.binding.daikin.internal.api.InfoParser;
import org.openhab.binding.daikin.internal.api.SensorInfo;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseBasicInfo;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo;
@ -119,9 +117,7 @@ public class DaikinWebTargets {
public boolean setControlInfo(ControlInfo info) throws DaikinCommunicationException {
Map<String, String> queryParams = info.getParamString();
String result = invoke(setControlInfoUri, queryParams);
Map<String, String> responseMap = InfoParser.parse(result);
return Optional.ofNullable(responseMap.get("ret")).orElse("").equals("OK");
return isSuccessful(invoke(setControlInfoUri, queryParams));
}
public SensorInfo getSensorInfo() throws DaikinCommunicationException {
@ -146,7 +142,7 @@ public class DaikinWebTargets {
return EnergyInfoDayAndWeek.parse(response);
}
public void setSpecialMode(SpecialMode newMode) throws DaikinCommunicationException {
public boolean setSpecialMode(SpecialMode newMode) throws DaikinCommunicationException {
Map<String, String> queryParams = new HashMap<>();
if (newMode == SpecialMode.NORMAL) {
queryParams.put("set_spmode", "0");
@ -160,17 +156,23 @@ public class DaikinWebTargets {
queryParams.put("spmode_kind", newMode.getValue());
}
String response = invoke(setSpecialModeUri, queryParams);
if (!response.contains("ret=OK")) {
if (isSuccessful(response)) {
return true;
} else {
logger.warn("Error setting special mode. Response: '{}'", response);
return false;
}
}
public void setStreamerMode(boolean state) throws DaikinCommunicationException {
public boolean setStreamerMode(boolean state) throws DaikinCommunicationException {
Map<String, String> queryParams = new HashMap<>();
queryParams.put("en_streamer", state ? "1" : "0");
String response = invoke(setSpecialModeUri, queryParams);
if (!response.contains("ret=OK")) {
if (isSuccessful(response)) {
return true;
} else {
logger.warn("Error setting streamer mode. Response: '{}'", response);
return false;
}
}
@ -181,9 +183,7 @@ public class DaikinWebTargets {
public boolean setDemandControl(DemandControl info) throws DaikinCommunicationException {
Map<String, String> queryParams = info.getParamString();
String result = invoke(setDemandControlUri, queryParams);
Map<String, String> responseMap = InfoParser.parse(result);
return Optional.ofNullable(responseMap.get("ret")).orElse("").equals("OK");
return isSuccessful(invoke(setDemandControlUri, queryParams));
}
// Daikin Airbase API
@ -192,9 +192,9 @@ public class DaikinWebTargets {
return AirbaseControlInfo.parse(response);
}
public void setAirbaseControlInfo(AirbaseControlInfo info) throws DaikinCommunicationException {
public boolean setAirbaseControlInfo(AirbaseControlInfo info) throws DaikinCommunicationException {
Map<String, String> queryParams = info.getParamString();
invoke(setAirbaseControlInfoUri, queryParams);
return isSuccessful(invoke(setAirbaseControlInfoUri, queryParams));
}
public SensorInfo getAirbaseSensorInfo() throws DaikinCommunicationException {
@ -217,9 +217,13 @@ public class DaikinWebTargets {
return AirbaseZoneInfo.parse(response);
}
public void setAirbaseZoneInfo(AirbaseZoneInfo zoneinfo) throws DaikinCommunicationException {
public boolean setAirbaseZoneInfo(AirbaseZoneInfo zoneinfo) throws DaikinCommunicationException {
Map<String, String> queryParams = zoneinfo.getParamString();
invoke(setAirbaseZoneInfoUri, queryParams);
return isSuccessful(invoke(setAirbaseZoneInfoUri, queryParams));
}
private boolean isSuccessful(String response) {
return response.contains("ret=OK");
}
private String invoke(String uri) throws DaikinCommunicationException {

View File

@ -129,7 +129,6 @@ public class Enums {
HEAT("heat"),
OFF("off");
private static final Logger LOGGER = LoggerFactory.getLogger(HomekitMode.class);
private final String value;
HomekitMode(String value) {

View File

@ -192,19 +192,25 @@ public class DaikinAcUnitHandler extends DaikinBaseHandler {
switch (channelUID.getId()) {
case DaikinBindingConstants.CHANNEL_AC_FAN_DIR:
if (command instanceof StringType stringCommand) {
changeFanDir(stringCommand.toString());
if (changeFanDir(stringCommand.toString())) {
updateState(channelUID, stringCommand);
}
return true;
}
break;
case DaikinBindingConstants.CHANNEL_AC_SPECIALMODE:
if (command instanceof StringType stringCommand) {
changeSpecialMode(stringCommand.toString());
if (changeSpecialMode(stringCommand.toString())) {
updateState(channelUID, stringCommand);
}
return true;
}
break;
case DaikinBindingConstants.CHANNEL_AC_STREAMER:
if (command instanceof OnOffType onOffCommand) {
changeStreamer(onOffCommand.equals(OnOffType.ON));
if (changeStreamer(onOffCommand.equals(OnOffType.ON))) {
updateState(channelUID, onOffCommand);
}
return true;
}
break;
@ -216,13 +222,21 @@ public class DaikinAcUnitHandler extends DaikinBaseHandler {
break;
case DaikinBindingConstants.CHANNEL_AC_DEMAND_MAX_POWER:
if (command instanceof PercentType percentCommand) {
changeDemandMaxPower(percentCommand.intValue());
if (changeDemandMaxPower(percentCommand.intValue())) {
updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_MODE,
new StringType(DemandControlMode.MANUAL.name()));
updateState(channelUID, percentCommand);
}
return true;
}
break;
case DaikinBindingConstants.CHANNEL_AC_DEMAND_SCHEDULE:
if (command instanceof StringType stringCommand) {
changeDemandSchedule(stringCommand.toString());
if (changeDemandSchedule(stringCommand.toString())) {
updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_MODE,
new StringType(DemandControlMode.SCHEDULED.name()));
updateState(channelUID, stringCommand);
}
return true;
}
break;
@ -231,27 +245,27 @@ public class DaikinAcUnitHandler extends DaikinBaseHandler {
}
@Override
protected void changePower(boolean power) throws DaikinCommunicationException {
protected boolean changePower(boolean power) throws DaikinCommunicationException {
ControlInfo info = webTargets.getControlInfo();
info.power = power;
webTargets.setControlInfo(info);
return webTargets.setControlInfo(info);
}
@Override
protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
protected boolean changeSetPoint(double newTemperature) throws DaikinCommunicationException {
ControlInfo info = webTargets.getControlInfo();
info.temp = Optional.of(newTemperature);
webTargets.setControlInfo(info);
return webTargets.setControlInfo(info);
}
@Override
protected void changeMode(String mode) throws DaikinCommunicationException {
protected boolean changeMode(String mode) throws DaikinCommunicationException {
Mode newMode;
try {
newMode = Mode.valueOf(mode);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid mode: {}. Valid values: {}", mode, Mode.values());
return;
return false;
}
ControlInfo info = webTargets.getControlInfo();
info.mode = newMode;
@ -263,55 +277,58 @@ public class DaikinAcUnitHandler extends DaikinBaseHandler {
// If mode=0 is not accepted try AUTO1 (mode=1)
if (!accepted && newMode == Mode.AUTO && autoModeValue.isEmpty()) {
info.autoModeValue = Mode.AUTO1.getValue();
if (webTargets.setControlInfo(info)) {
accepted = webTargets.setControlInfo(info);
if (accepted) {
autoModeValue = Optional.of(info.autoModeValue);
logger.debug("AUTO uses mode={}", info.autoModeValue);
} else {
logger.warn("AUTO mode not accepted with mode=0 or mode=1");
}
}
return accepted;
}
@Override
protected void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException {
protected boolean changeFanSpeed(String fanSpeed) throws DaikinCommunicationException {
FanSpeed newSpeed;
try {
newSpeed = FanSpeed.valueOf(fanSpeed);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid fan speed: {}. Valid values: {}", fanSpeed, FanSpeed.values());
return;
return false;
}
ControlInfo info = webTargets.getControlInfo();
info.fanSpeed = newSpeed;
webTargets.setControlInfo(info);
return webTargets.setControlInfo(info);
}
protected void changeFanDir(String fanDir) throws DaikinCommunicationException {
protected boolean changeFanDir(String fanDir) throws DaikinCommunicationException {
FanMovement newMovement;
try {
newMovement = FanMovement.valueOf(fanDir);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid fan direction: {}. Valid values: {}", fanDir, FanMovement.values());
return;
return false;
}
ControlInfo info = webTargets.getControlInfo();
info.fanMovement = newMovement;
webTargets.setControlInfo(info);
return webTargets.setControlInfo(info);
}
protected void changeSpecialMode(String specialMode) throws DaikinCommunicationException {
protected boolean changeSpecialMode(String specialMode) throws DaikinCommunicationException {
SpecialMode newMode;
try {
newMode = SpecialMode.valueOf(specialMode);
} catch (IllegalArgumentException e) {
logger.warn("Invalid specialmode: {}. Valid values: {}", specialMode, SpecialMode.values());
return;
return false;
}
webTargets.setSpecialMode(newMode);
return webTargets.setSpecialMode(newMode);
}
protected void changeStreamer(boolean streamerMode) throws DaikinCommunicationException {
webTargets.setStreamerMode(streamerMode);
protected boolean changeStreamer(boolean streamerMode) throws DaikinCommunicationException {
return webTargets.setStreamerMode(streamerMode);
}
protected void changeDemandMode(String mode) throws DaikinCommunicationException {
@ -323,40 +340,54 @@ public class DaikinAcUnitHandler extends DaikinBaseHandler {
return;
}
DemandControl demandInfo = webTargets.getDemandControl();
boolean scheduleChanged = false;
boolean maxPowerChanged = false;
if (demandInfo.mode != newMode) {
if (newMode == DemandControlMode.SCHEDULED && savedDemandControlSchedule.isPresent()) {
// restore previously saved schedule
demandInfo.setSchedule(savedDemandControlSchedule.get());
scheduleChanged = true;
}
if (newMode == DemandControlMode.MANUAL && savedDemandControlMaxPower.isPresent()) {
// restore previously saved maxPower
demandInfo.maxPower = savedDemandControlMaxPower.get();
maxPowerChanged = true;
}
}
demandInfo.mode = newMode;
webTargets.setDemandControl(demandInfo);
if (webTargets.setDemandControl(demandInfo)) {
updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_MODE, new StringType(newMode.name()));
if (scheduleChanged) {
updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_SCHEDULE,
new StringType(savedDemandControlSchedule.get()));
}
if (maxPowerChanged) {
updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_MAX_POWER,
new PercentType(savedDemandControlMaxPower.get()));
}
}
}
protected void changeDemandMaxPower(int maxPower) throws DaikinCommunicationException {
protected boolean changeDemandMaxPower(int maxPower) throws DaikinCommunicationException {
DemandControl demandInfo = webTargets.getDemandControl();
demandInfo.mode = DemandControlMode.MANUAL;
demandInfo.maxPower = maxPower;
webTargets.setDemandControl(demandInfo);
savedDemandControlMaxPower = Optional.of(maxPower);
return webTargets.setDemandControl(demandInfo);
}
protected void changeDemandSchedule(String schedule) throws DaikinCommunicationException {
protected boolean changeDemandSchedule(String schedule) throws DaikinCommunicationException {
DemandControl demandInfo = webTargets.getDemandControl();
try {
demandInfo.setSchedule(schedule);
} catch (JsonSyntaxException e) {
logger.warn("Invalid schedule: {}. {}", schedule, e.getMessage());
return;
return false;
}
demandInfo.mode = DemandControlMode.SCHEDULED;
webTargets.setDemandControl(demandInfo);
savedDemandControlSchedule = Optional.of(demandInfo.getSchedule());
return webTargets.setDemandControl(demandInfo);
}
/**

View File

@ -64,8 +64,10 @@ public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
throws DaikinCommunicationException {
if (channelUID.getId().startsWith(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE)) {
int zoneNumber = Integer.parseInt(channelUID.getId().substring(4));
if (command instanceof OnOffType) {
changeZone(zoneNumber, command == OnOffType.ON);
if (command instanceof OnOffType onOffCommand) {
if (changeZone(zoneNumber, onOffCommand == OnOffType.ON)) {
updateState(channelUID, onOffCommand);
}
return true;
}
}
@ -110,63 +112,63 @@ public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
}
@Override
protected void changePower(boolean power) throws DaikinCommunicationException {
protected boolean changePower(boolean power) throws DaikinCommunicationException {
AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
info.power = power;
webTargets.setAirbaseControlInfo(info);
return webTargets.setAirbaseControlInfo(info);
}
@Override
protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
protected boolean changeSetPoint(double newTemperature) throws DaikinCommunicationException {
AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
info.temp = Optional.of(newTemperature);
webTargets.setAirbaseControlInfo(info);
return webTargets.setAirbaseControlInfo(info);
}
@Override
protected void changeMode(String mode) throws DaikinCommunicationException {
protected boolean changeMode(String mode) throws DaikinCommunicationException {
AirbaseMode newMode;
try {
newMode = AirbaseMode.valueOf(mode);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid mode: {}. Valid values: {}", mode, AirbaseMode.values());
return;
return false;
}
if (airbaseModelInfo != null) {
if ((newMode == AirbaseMode.AUTO && !airbaseModelInfo.features.contains(AirbaseFeature.AUTO))
|| (newMode == AirbaseMode.DRY && !airbaseModelInfo.features.contains(AirbaseFeature.DRY))) {
logger.warn("{} mode is not supported by your controller", mode);
return;
return false;
}
}
AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
info.mode = newMode;
webTargets.setAirbaseControlInfo(info);
return webTargets.setAirbaseControlInfo(info);
}
@Override
protected void changeFanSpeed(String speed) throws DaikinCommunicationException {
protected boolean changeFanSpeed(String speed) throws DaikinCommunicationException {
AirbaseFanSpeed newFanSpeed;
try {
newFanSpeed = AirbaseFanSpeed.valueOf(speed);
} catch (IllegalArgumentException ex) {
logger.warn("Invalid fan speed: {}. Valid values: {}", speed, AirbaseFanSpeed.values());
return;
return false;
}
if (airbaseModelInfo != null) {
if (EnumSet.range(AirbaseFanSpeed.AUTO_LEVEL_1, AirbaseFanSpeed.AUTO_LEVEL_5).contains(newFanSpeed)
&& !airbaseModelInfo.features.contains(AirbaseFeature.FRATE_AUTO)) {
logger.warn("Fan AUTO_LEVEL_X is not supported by your controller");
return;
return false;
}
if (newFanSpeed == AirbaseFanSpeed.AIRSIDE && !airbaseModelInfo.features.contains(AirbaseFeature.AIRSIDE)) {
logger.warn("Airside is not supported by your controller");
return;
return false;
}
}
AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
info.fanSpeed = newFanSpeed;
webTargets.setAirbaseControlInfo(info);
return webTargets.setAirbaseControlInfo(info);
}
/**
@ -178,7 +180,7 @@ public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
* @param command true to turn on the zone, false to turn it off
*
*/
protected void changeZone(int zone, boolean command) throws DaikinCommunicationException {
protected boolean changeZone(int zone, boolean command) throws DaikinCommunicationException {
AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
long maxZones = zoneInfo.zone.length;
@ -188,11 +190,11 @@ public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
if (zone <= 0 || zone > maxZones) {
logger.warn("The given zone number ({}) is outside the number of zones supported by the controller ({})",
zone, maxZones);
return;
return false;
}
zoneInfo.zone[zone - 1] = command;
webTargets.setAirbaseZoneInfo(zoneInfo);
return webTargets.setAirbaseZoneInfo(zoneInfo);
}
@Override

View File

@ -70,13 +70,13 @@ public abstract class DaikinBaseHandler extends BaseThingHandler {
// Abstract methods to be overridden by specific Daikin implementation class
protected abstract void pollStatus() throws DaikinCommunicationException;
protected abstract void changePower(boolean power) throws DaikinCommunicationException;
protected abstract boolean changePower(boolean power) throws DaikinCommunicationException;
protected abstract void changeSetPoint(double newTemperature) throws DaikinCommunicationException;
protected abstract boolean changeSetPoint(double newTemperature) throws DaikinCommunicationException;
protected abstract void changeMode(String mode) throws DaikinCommunicationException;
protected abstract boolean changeMode(String mode) throws DaikinCommunicationException;
protected abstract void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException;
protected abstract boolean changeFanSpeed(String fanSpeed) throws DaikinCommunicationException;
// Power, Temp, Fan and Mode are handled in this base class. Override this to handle additional channels.
protected abstract boolean handleCommandInternal(ChannelUID channelUID, Command command)
@ -104,31 +104,54 @@ public abstract class DaikinBaseHandler extends BaseThingHandler {
switch (channelUID.getId()) {
case DaikinBindingConstants.CHANNEL_AC_POWER:
if (command instanceof OnOffType onOffCommand) {
changePower(onOffCommand.equals(OnOffType.ON));
if (changePower(onOffCommand.equals(OnOffType.ON))) {
updateState(channelUID, onOffCommand);
}
return;
}
break;
case DaikinBindingConstants.CHANNEL_AC_TEMP:
if (changeSetPoint(command)) {
return;
double newTemperature;
State newState = UnDefType.UNDEF;
if (command instanceof DecimalType decimalCommand) {
newTemperature = decimalCommand.doubleValue();
newState = decimalCommand;
} else if (command instanceof QuantityType) {
QuantityType<Temperature> quantityCommand = (QuantityType<Temperature>) command;
newTemperature = quantityCommand.toUnit(SIUnits.CELSIUS).doubleValue();
newState = quantityCommand;
} else {
break; // Exit switch statement but proceed to log about unsupported command type
}
break;
// Only half degree increments are allowed, all others are silently rejected by the A/C units
newTemperature = Math.round(newTemperature * 2) / 2.0;
if (changeSetPoint(newTemperature)) {
updateState(channelUID, newState);
}
return; // return here and don't log about wrong type below
case DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED:
case DaikinBindingConstants.CHANNEL_AC_FAN_SPEED:
if (command instanceof StringType stringCommand) {
changeFanSpeed(stringCommand.toString());
if (changeFanSpeed(stringCommand.toString())) {
updateState(channelUID, stringCommand);
}
return;
}
break;
case DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE:
if (command instanceof StringType) {
changeHomekitMode(command.toString());
if (command instanceof StringType stringCommand) {
if (changeHomekitMode(stringCommand.toString())) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, stringCommand);
}
return;
}
break;
case DaikinBindingConstants.CHANNEL_AC_MODE:
if (command instanceof StringType stringCommand) {
changeMode(stringCommand.toString());
if (changeMode(stringCommand.toString())) {
updateState(channelUID, stringCommand);
}
return;
}
break;
@ -210,37 +233,37 @@ public abstract class DaikinBaseHandler extends BaseThingHandler {
maybeTemperature.<State> map(t -> new QuantityType<>(t, SIUnits.CELSIUS)).orElse(UnDefType.UNDEF)));
}
/**
* @return true if the command was of an expected type, false otherwise
*/
private boolean changeSetPoint(Command command) throws DaikinCommunicationException {
double newTemperature;
if (command instanceof DecimalType decimalCommand) {
newTemperature = decimalCommand.doubleValue();
} else if (command instanceof QuantityType) {
newTemperature = ((QuantityType<Temperature>) command).toUnit(SIUnits.CELSIUS).doubleValue();
} else {
return false;
}
// Only half degree increments are allowed, all others are silently rejected by the A/C units
newTemperature = Math.round(newTemperature * 2) / 2.0;
changeSetPoint(newTemperature);
return true;
}
private void changeHomekitMode(String homekitmode) throws DaikinCommunicationException {
if (HomekitMode.OFF.getValue().equals(homekitmode)) {
changePower(false);
} else {
changePower(true);
if (HomekitMode.AUTO.getValue().equals(homekitmode)) {
changeMode("AUTO");
} else if (HomekitMode.HEAT.getValue().equals(homekitmode)) {
changeMode("HEAT");
} else if (HomekitMode.COOL.getValue().equals(homekitmode)) {
changeMode("COLD");
private boolean changeHomekitMode(String homekitmode) throws DaikinCommunicationException {
try {
HomekitMode mode = HomekitMode.valueOf(homekitmode.toUpperCase());
boolean power = mode != HomekitMode.OFF;
if (!changePower(power)) {
return false;
}
boolean changeModeSuccess = false;
updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(power));
String newMode = switch (mode) {
case AUTO -> "AUTO";
case HEAT -> "HEAT";
case COOL -> "COLD";
case OFF -> null;
};
if (newMode == null) {
return true;
}
if (changeMode(newMode)) {
updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(newMode));
return true;
}
return false;
} catch (IllegalArgumentException e) {
logger.warn("Invalid homekit mode: {}", homekitmode);
return false;
}
}
}

View File

@ -94,6 +94,7 @@
<label>Power</label>
<description>Power for the AC unit</description>
<category>Switch</category>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-settemp">
@ -102,6 +103,7 @@
<description>The set point temperature</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" step="0.5"/>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-indoortemp">
@ -141,6 +143,7 @@
<option value="FAN">fan</option>
</options>
</state>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-homekitmode">
@ -155,6 +158,7 @@
<option value="off">Off</option>
</options>
</state>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-fan">
@ -172,6 +176,7 @@
<option value="LEVEL_5">level 5</option>
</options>
</state>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-fandir">
@ -186,6 +191,7 @@
<option value="VERTICAL_AND_HORIZONTAL">vertical and horizontal</option>
</options>
</state>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-cmpfrequency" advanced="true">
@ -205,12 +211,14 @@
<option value="POWERFUL">Powerful</option>
</options>
</state>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-streamer" advanced="true">
<item-type>Switch</item-type>
<label>Streamer</label>
<description>Streamer Mode</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-energyheatingtoday" advanced="true">
@ -442,6 +450,7 @@
<option value="MANUAL">Manual</option>
</options>
</state>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-demandcontrolmaxpower" advanced="true">
<item-type>Dimmer</item-type>
@ -449,65 +458,76 @@
<description>The maximum power for demand control in percent. Allowed range is between 40% and 100% in increments of
5%.</description>
<state pattern="%d %%" min="40" max="100" step="5"></state>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="acunit-demandcontrolschedule" advanced="true">
<item-type>String</item-type>
<label>Demand Control Schedule</label>
<description>The demand control schedule in JSON format.</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="airbase-acunit-fan">
<item-type>String</item-type>
<label>Fan</label>
<description>Current fan speed setting of the AC unit</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="airbase-acunit-zone1">
<item-type>Switch</item-type>
<label>Zone 1</label>
<description>Zone 1 for the AC unit</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="airbase-acunit-zone2">
<item-type>Switch</item-type>
<label>Zone 2</label>
<description>Zone 2 for the AC unit</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="airbase-acunit-zone3">
<item-type>Switch</item-type>
<label>Zone 3</label>
<description>Zone 3 for the AC unit</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="airbase-acunit-zone4">
<item-type>Switch</item-type>
<label>Zone 4</label>
<description>Zone 4 for the AC unit</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="airbase-acunit-zone5">
<item-type>Switch</item-type>
<label>Zone 5</label>
<description>Zone 5 for the AC unit</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="airbase-acunit-zone6">
<item-type>Switch</item-type>
<label>Zone 6</label>
<description>Zone 6 for the AC unit</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="airbase-acunit-zone7">
<item-type>Switch</item-type>
<label>Zone 7</label>
<description>Zone 7 for the AC unit</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="airbase-acunit-zone8">
<item-type>Switch</item-type>
<label>Zone 8</label>
<description>Zone 8 for the AC unit</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
</thing:thing-descriptions>