2 * Copyright (c) 2010-2022 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.io.IOException;
16 import java.util.ArrayList;
17 import java.util.EnumSet;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.stream.IntStream;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.openhab.binding.daikin.internal.DaikinBindingConstants;
26 import org.openhab.binding.daikin.internal.DaikinCommunicationException;
27 import org.openhab.binding.daikin.internal.DaikinDynamicStateDescriptionProvider;
28 import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
29 import org.openhab.binding.daikin.internal.api.SensorInfo;
30 import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo;
31 import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFanSpeed;
32 import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFeature;
33 import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseMode;
34 import org.openhab.binding.daikin.internal.api.airbase.AirbaseModelInfo;
35 import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.StateOption;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * Handles communicating with a Daikin Airbase wifi adapter.
49 * @author Tim Waterhouse - Initial Contribution
50 * @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
51 * @author Jimmy Tanagra - Support Airside and auto fan levels, DynamicStateDescription
55 public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
56 private final Logger logger = LoggerFactory.getLogger(DaikinAirbaseUnitHandler.class);
57 private @Nullable AirbaseModelInfo airbaseModelInfo;
59 public DaikinAirbaseUnitHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
60 @Nullable HttpClient httpClient) {
61 super(thing, stateDescriptionProvider, httpClient);
65 protected boolean handleCommandInternal(ChannelUID channelUID, Command command)
66 throws DaikinCommunicationException {
67 if (channelUID.getId().startsWith(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE)) {
68 int zoneNumber = Integer.parseInt(channelUID.getId().substring(4));
69 if (command instanceof OnOffType) {
70 changeZone(zoneNumber, command == OnOffType.ON);
78 protected void pollStatus() throws IOException {
79 AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo();
80 updateStatus(ThingStatus.ONLINE);
82 if (airbaseModelInfo == null || !"OK".equals(airbaseModelInfo.ret)) {
83 airbaseModelInfo = webTargets.getAirbaseModelInfo();
84 updateChannelStateDescriptions();
87 if (controlInfo != null) {
88 updateState(DaikinBindingConstants.CHANNEL_AC_POWER, controlInfo.power ? OnOffType.ON : OnOffType.OFF);
89 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp);
90 updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name()));
91 updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED,
92 new StringType(controlInfo.fanSpeed.name()));
94 if (!controlInfo.power) {
95 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
96 } else if (controlInfo.mode == AirbaseMode.COLD) {
97 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
98 } else if (controlInfo.mode == AirbaseMode.HEAT) {
99 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
100 } else if (controlInfo.mode == AirbaseMode.AUTO) {
101 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
105 SensorInfo sensorInfo = webTargets.getAirbaseSensorInfo();
106 if (sensorInfo != null) {
107 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
109 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
112 AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
113 if (zoneInfo != null) {
114 IntStream.range(0, zoneInfo.zone.length)
115 .forEach(idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + idx,
116 OnOffType.from(zoneInfo.zone[idx])));
121 protected void changePower(boolean power) throws DaikinCommunicationException {
122 AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
124 webTargets.setAirbaseControlInfo(info);
128 protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
129 AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
130 info.temp = Optional.of(newTemperature);
131 webTargets.setAirbaseControlInfo(info);
135 protected void changeMode(String mode) throws DaikinCommunicationException {
138 newMode = AirbaseMode.valueOf(mode);
139 } catch (IllegalArgumentException ex) {
140 logger.warn("Invalid mode: {}. Valid values: {}", mode, AirbaseMode.values());
143 if (airbaseModelInfo != null) {
144 if ((newMode == AirbaseMode.AUTO && !airbaseModelInfo.features.contains(AirbaseFeature.AUTO))
145 || (newMode == AirbaseMode.DRY && !airbaseModelInfo.features.contains(AirbaseFeature.DRY))) {
146 logger.warn("{} mode is not supported by your controller", mode);
150 AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
152 webTargets.setAirbaseControlInfo(info);
156 protected void changeFanSpeed(String speed) throws DaikinCommunicationException {
157 AirbaseFanSpeed newFanSpeed;
159 newFanSpeed = AirbaseFanSpeed.valueOf(speed);
160 } catch (IllegalArgumentException ex) {
161 logger.warn("Invalid fan speed: {}. Valid values: {}", speed, AirbaseFanSpeed.values());
164 if (airbaseModelInfo != null) {
165 if (EnumSet.range(AirbaseFanSpeed.AUTO_LEVEL_1, AirbaseFanSpeed.AUTO_LEVEL_5).contains(newFanSpeed)
166 && !airbaseModelInfo.features.contains(AirbaseFeature.FRATE_AUTO)) {
167 logger.warn("Fan AUTO_LEVEL_X is not supported by your controller");
170 if (newFanSpeed == AirbaseFanSpeed.AIRSIDE && !airbaseModelInfo.features.contains(AirbaseFeature.AIRSIDE)) {
171 logger.warn("Airside is not supported by your controller");
175 AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
176 info.fanSpeed = newFanSpeed;
177 webTargets.setAirbaseControlInfo(info);
180 protected void changeZone(int zone, boolean command) throws DaikinCommunicationException {
181 if (zone <= 0 || zone > airbaseModelInfo.zonespresent) {
182 logger.warn("The given zone number ({}) is outside the number of zones supported by the controller ({})",
183 zone, airbaseModelInfo.zonespresent);
187 AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
188 long count = IntStream.range(0, zoneInfo.zone.length).filter(idx -> zoneInfo.zone[idx]).count()
189 + airbaseModelInfo.commonzone;
190 logger.debug("Number of open zones: \"{}\"", count);
193 zoneInfo.zone[zone] = command;
194 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),