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();
81 if (airbaseModelInfo == null || !"OK".equals(airbaseModelInfo.ret)) {
82 airbaseModelInfo = webTargets.getAirbaseModelInfo();
83 updateChannelStateDescriptions();
86 if (controlInfo != null) {
87 updateState(DaikinBindingConstants.CHANNEL_AC_POWER, controlInfo.power ? OnOffType.ON : OnOffType.OFF);
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,
91 new StringType(controlInfo.fanSpeed.name()));
93 if (!controlInfo.power) {
94 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
95 } else if (controlInfo.mode == AirbaseMode.COLD) {
96 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
97 } else if (controlInfo.mode == AirbaseMode.HEAT) {
98 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
99 } else if (controlInfo.mode == AirbaseMode.AUTO) {
100 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
104 SensorInfo sensorInfo = webTargets.getAirbaseSensorInfo();
105 if (sensorInfo != null) {
106 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
108 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
111 AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
112 if (zoneInfo != null) {
113 IntStream.range(0, zoneInfo.zone.length)
114 .forEach(idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + idx,
115 OnOffType.from(zoneInfo.zone[idx])));
117 updateStatus(ThingStatus.ONLINE);
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 AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
182 long commonZones = 0;
183 long maxZones = zoneInfo.zone.length;
185 if (airbaseModelInfo != null) {
186 maxZones = Math.min(maxZones - 1, airbaseModelInfo.zonespresent);
187 commonZones = airbaseModelInfo.commonzone;
189 if (zone <= 0 || zone > maxZones) {
190 logger.warn("The given zone number ({}) is outside the number of zones supported by the controller ({})",
195 long openZones = IntStream.range(0, zoneInfo.zone.length).filter(idx -> zoneInfo.zone[idx]).count()
197 logger.debug("Number of open zones: \"{}\"", openZones);
199 if (openZones >= 1) {
200 zoneInfo.zone[zone] = command;
201 webTargets.setAirbaseZoneInfo(zoneInfo);
206 protected void registerUuid(@Nullable String key) {
207 // not implemented. There is currently no known Airbase adapter that requires uuid authentication
210 protected void updateChannelStateDescriptions() {
211 updateAirbaseFanSpeedChannelStateDescription();
212 updateAirbaseModeChannelStateDescription();
215 protected void updateAirbaseFanSpeedChannelStateDescription() {
216 List<StateOption> options = new ArrayList<>();
217 options.add(new StateOption(AirbaseFanSpeed.AUTO.name(), AirbaseFanSpeed.AUTO.getLabel()));
218 if (airbaseModelInfo.features.contains(AirbaseFeature.AIRSIDE)) {
219 options.add(new StateOption(AirbaseFanSpeed.AIRSIDE.name(), AirbaseFanSpeed.AIRSIDE.getLabel()));
221 for (AirbaseFanSpeed f : EnumSet.range(AirbaseFanSpeed.LEVEL_1, AirbaseFanSpeed.LEVEL_5)) {
222 options.add(new StateOption(f.name(), f.getLabel()));
224 if (airbaseModelInfo.features.contains(AirbaseFeature.FRATE_AUTO)) {
225 for (AirbaseFanSpeed f : EnumSet.range(AirbaseFanSpeed.AUTO_LEVEL_1, AirbaseFanSpeed.AUTO_LEVEL_5)) {
226 options.add(new StateOption(f.name(), f.getLabel()));
229 stateDescriptionProvider.setStateOptions(
230 new ChannelUID(thing.getUID(), DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED), options);
233 protected void updateAirbaseModeChannelStateDescription() {
234 List<StateOption> options = new ArrayList<>();
235 if (airbaseModelInfo.features.contains(AirbaseFeature.AUTO)) {
236 options.add(new StateOption(AirbaseMode.AUTO.name(), AirbaseMode.AUTO.getLabel()));
238 for (AirbaseMode f : EnumSet.complementOf(EnumSet.of(AirbaseMode.AUTO, AirbaseMode.DRY))) {
239 options.add(new StateOption(f.name(), f.getLabel()));
241 if (airbaseModelInfo.features.contains(AirbaseFeature.DRY)) {
242 options.add(new StateOption(AirbaseMode.DRY.name(), AirbaseMode.DRY.getLabel()));
244 stateDescriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), DaikinBindingConstants.CHANNEL_AC_MODE),