2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.daikin.internal.handler;
15 import java.util.ArrayList;
16 import java.util.EnumSet;
17 import java.util.List;
18 import java.util.Optional;
19 import java.util.stream.IntStream;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.eclipse.jetty.client.HttpClient;
24 import org.openhab.binding.daikin.internal.DaikinBindingConstants;
25 import org.openhab.binding.daikin.internal.DaikinCommunicationException;
26 import org.openhab.binding.daikin.internal.DaikinDynamicStateDescriptionProvider;
27 import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
28 import org.openhab.binding.daikin.internal.api.SensorInfo;
29 import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo;
30 import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFanSpeed;
31 import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFeature;
32 import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseMode;
33 import org.openhab.binding.daikin.internal.api.airbase.AirbaseModelInfo;
34 import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo;
35 import org.openhab.core.library.types.OnOffType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.types.Command;
40 import org.openhab.core.types.StateOption;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * Handles communicating with a Daikin Airbase wifi adapter.
47 * @author Tim Waterhouse - Initial Contribution
48 * @author Paul Smedley - Modifications to support Airbase Controllers
49 * @author Jimmy Tanagra - Support Airside and auto fan levels, DynamicStateDescription
53 public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
54 private final Logger logger = LoggerFactory.getLogger(DaikinAirbaseUnitHandler.class);
55 private @Nullable AirbaseModelInfo airbaseModelInfo;
57 public DaikinAirbaseUnitHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
58 @Nullable HttpClient httpClient) {
59 super(thing, stateDescriptionProvider, httpClient);
63 protected boolean handleCommandInternal(ChannelUID channelUID, Command command)
64 throws DaikinCommunicationException {
65 if (channelUID.getId().startsWith(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE)) {
66 int zoneNumber = Integer.parseInt(channelUID.getId().substring(4));
67 if (command instanceof OnOffType) {
68 changeZone(zoneNumber, command == OnOffType.ON);
76 protected void pollStatus() throws DaikinCommunicationException {
77 if (airbaseModelInfo == null || !"OK".equals(airbaseModelInfo.ret)) {
78 airbaseModelInfo = webTargets.getAirbaseModelInfo();
79 updateChannelStateDescriptions();
82 AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo();
83 if (!"OK".equals(controlInfo.ret)) {
84 throw new DaikinCommunicationException("Invalid response from host");
87 updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(controlInfo.power));
88 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp);
89 updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name()));
90 updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED, new StringType(controlInfo.fanSpeed.name()));
92 if (!controlInfo.power) {
93 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
94 } else if (controlInfo.mode == AirbaseMode.COLD) {
95 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
96 } else if (controlInfo.mode == AirbaseMode.HEAT) {
97 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
98 } else if (controlInfo.mode == AirbaseMode.AUTO) {
99 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
102 SensorInfo sensorInfo = webTargets.getAirbaseSensorInfo();
103 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
104 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
106 AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
107 IntStream.range(0, zoneInfo.zone.length)
108 .forEach(idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + (idx + 1),
109 OnOffType.from(zoneInfo.zone[idx])));
113 protected void changePower(boolean power) throws DaikinCommunicationException {
114 AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
116 webTargets.setAirbaseControlInfo(info);
120 protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
121 AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
122 info.temp = Optional.of(newTemperature);
123 webTargets.setAirbaseControlInfo(info);
127 protected void changeMode(String mode) throws DaikinCommunicationException {
130 newMode = AirbaseMode.valueOf(mode);
131 } catch (IllegalArgumentException ex) {
132 logger.warn("Invalid mode: {}. Valid values: {}", mode, AirbaseMode.values());
135 if (airbaseModelInfo != null) {
136 if ((newMode == AirbaseMode.AUTO && !airbaseModelInfo.features.contains(AirbaseFeature.AUTO))
137 || (newMode == AirbaseMode.DRY && !airbaseModelInfo.features.contains(AirbaseFeature.DRY))) {
138 logger.warn("{} mode is not supported by your controller", mode);
142 AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
144 webTargets.setAirbaseControlInfo(info);
148 protected void changeFanSpeed(String speed) throws DaikinCommunicationException {
149 AirbaseFanSpeed newFanSpeed;
151 newFanSpeed = AirbaseFanSpeed.valueOf(speed);
152 } catch (IllegalArgumentException ex) {
153 logger.warn("Invalid fan speed: {}. Valid values: {}", speed, AirbaseFanSpeed.values());
156 if (airbaseModelInfo != null) {
157 if (EnumSet.range(AirbaseFanSpeed.AUTO_LEVEL_1, AirbaseFanSpeed.AUTO_LEVEL_5).contains(newFanSpeed)
158 && !airbaseModelInfo.features.contains(AirbaseFeature.FRATE_AUTO)) {
159 logger.warn("Fan AUTO_LEVEL_X is not supported by your controller");
162 if (newFanSpeed == AirbaseFanSpeed.AIRSIDE && !airbaseModelInfo.features.contains(AirbaseFeature.AIRSIDE)) {
163 logger.warn("Airside is not supported by your controller");
167 AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
168 info.fanSpeed = newFanSpeed;
169 webTargets.setAirbaseControlInfo(info);
174 * Turn the zone on/off
175 * The Airbase controller allows turning off all zones, so we allow it here too
177 * @param zone the zone number starting from 1
178 * @param command true to turn on the zone, false to turn it off
181 protected void changeZone(int zone, boolean command) throws DaikinCommunicationException {
182 AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
183 long maxZones = zoneInfo.zone.length;
185 if (airbaseModelInfo != null) {
186 maxZones = Math.min(maxZones, airbaseModelInfo.zonespresent);
188 if (zone <= 0 || zone > maxZones) {
189 logger.warn("The given zone number ({}) is outside the number of zones supported by the controller ({})",
194 zoneInfo.zone[zone - 1] = command;
195 webTargets.setAirbaseZoneInfo(zoneInfo);
199 protected void registerUuid(@Nullable String key) {
200 // not implemented. There is currently no known Airbase adapter that requires uuid authentication
203 protected void updateChannelStateDescriptions() {
204 updateAirbaseFanSpeedChannelStateDescription();
205 updateAirbaseModeChannelStateDescription();
208 protected void updateAirbaseFanSpeedChannelStateDescription() {
209 List<StateOption> options = new ArrayList<>();
210 options.add(new StateOption(AirbaseFanSpeed.AUTO.name(), AirbaseFanSpeed.AUTO.getLabel()));
211 if (airbaseModelInfo.features.contains(AirbaseFeature.AIRSIDE)) {
212 options.add(new StateOption(AirbaseFanSpeed.AIRSIDE.name(), AirbaseFanSpeed.AIRSIDE.getLabel()));
214 for (AirbaseFanSpeed f : EnumSet.range(AirbaseFanSpeed.LEVEL_1, AirbaseFanSpeed.LEVEL_5)) {
215 options.add(new StateOption(f.name(), f.getLabel()));
217 if (airbaseModelInfo.features.contains(AirbaseFeature.FRATE_AUTO)) {
218 for (AirbaseFanSpeed f : EnumSet.range(AirbaseFanSpeed.AUTO_LEVEL_1, AirbaseFanSpeed.AUTO_LEVEL_5)) {
219 options.add(new StateOption(f.name(), f.getLabel()));
222 stateDescriptionProvider.setStateOptions(
223 new ChannelUID(thing.getUID(), DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED), options);
226 protected void updateAirbaseModeChannelStateDescription() {
227 List<StateOption> options = new ArrayList<>();
228 if (airbaseModelInfo.features.contains(AirbaseFeature.AUTO)) {
229 options.add(new StateOption(AirbaseMode.AUTO.name(), AirbaseMode.AUTO.getLabel()));
231 for (AirbaseMode f : EnumSet.complementOf(EnumSet.of(AirbaseMode.AUTO, AirbaseMode.DRY))) {
232 options.add(new StateOption(f.name(), f.getLabel()));
234 if (airbaseModelInfo.features.contains(AirbaseFeature.DRY)) {
235 options.add(new StateOption(AirbaseMode.DRY.name(), AirbaseMode.DRY.getLabel()));
237 stateDescriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), DaikinBindingConstants.CHANNEL_AC_MODE),