2 * Copyright (c) 2010-2023 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.math.BigDecimal;
16 import java.util.Optional;
17 import java.util.stream.IntStream;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.eclipse.jetty.client.HttpClient;
22 import org.openhab.binding.daikin.internal.DaikinBindingConstants;
23 import org.openhab.binding.daikin.internal.DaikinCommunicationException;
24 import org.openhab.binding.daikin.internal.DaikinDynamicStateDescriptionProvider;
25 import org.openhab.binding.daikin.internal.api.ControlInfo;
26 import org.openhab.binding.daikin.internal.api.EnergyInfoDayAndWeek;
27 import org.openhab.binding.daikin.internal.api.EnergyInfoYear;
28 import org.openhab.binding.daikin.internal.api.Enums.FanMovement;
29 import org.openhab.binding.daikin.internal.api.Enums.FanSpeed;
30 import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
31 import org.openhab.binding.daikin.internal.api.Enums.Mode;
32 import org.openhab.binding.daikin.internal.api.Enums.SpecialMode;
33 import org.openhab.binding.daikin.internal.api.SensorInfo;
34 import org.openhab.core.library.types.DecimalType;
35 import org.openhab.core.library.types.OnOffType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.library.unit.Units;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.State;
43 import org.openhab.core.types.UnDefType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * Handles communicating with a Daikin air conditioning unit.
50 * @author Tim Waterhouse - Initial Contribution
51 * @author Paul Smedley - Modifications to support Airbase Controllers
52 * @author Lukas Agethen - Added support for Energy Year reading, compressor frequency and powerful mode
53 * @author Wouter Denayer - Added to support for weekly & daily energy reading
57 public class DaikinAcUnitHandler extends DaikinBaseHandler {
58 private final Logger logger = LoggerFactory.getLogger(DaikinAcUnitHandler.class);
60 private Optional<Integer> autoModeValue = Optional.empty();
62 public DaikinAcUnitHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
63 @Nullable HttpClient httpClient) {
64 super(thing, stateDescriptionProvider, httpClient);
68 protected void pollStatus() throws DaikinCommunicationException {
69 ControlInfo controlInfo = webTargets.getControlInfo();
70 if (!"OK".equals(controlInfo.ret)) {
71 throw new DaikinCommunicationException("Invalid response from host");
74 updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(controlInfo.power));
75 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp);
77 updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name()));
78 updateState(DaikinBindingConstants.CHANNEL_AC_FAN_SPEED, new StringType(controlInfo.fanSpeed.name()));
79 updateState(DaikinBindingConstants.CHANNEL_AC_FAN_DIR, new StringType(controlInfo.fanMovement.name()));
81 if (!controlInfo.power) {
82 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
83 } else if (controlInfo.mode == Mode.COLD) {
84 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
85 } else if (controlInfo.mode == Mode.HEAT) {
86 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
87 } else if (controlInfo.mode == Mode.AUTO) {
88 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
91 if (controlInfo.advancedMode.isUndefined()) {
92 updateState(DaikinBindingConstants.CHANNEL_AC_STREAMER, UnDefType.UNDEF);
93 updateState(DaikinBindingConstants.CHANNEL_AC_SPECIALMODE, UnDefType.UNDEF);
95 updateState(DaikinBindingConstants.CHANNEL_AC_STREAMER,
96 OnOffType.from(controlInfo.advancedMode.isStreamerActive()));
97 updateState(DaikinBindingConstants.CHANNEL_AC_SPECIALMODE,
98 new StringType(controlInfo.getSpecialMode().name()));
101 SensorInfo sensorInfo = webTargets.getSensorInfo();
102 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
104 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
106 if (sensorInfo.indoorhumidity.isPresent()) {
107 updateState(DaikinBindingConstants.CHANNEL_HUMIDITY,
108 new QuantityType<>(sensorInfo.indoorhumidity.get(), Units.PERCENT));
110 updateState(DaikinBindingConstants.CHANNEL_HUMIDITY, UnDefType.UNDEF);
113 if (sensorInfo.compressorfrequency.isPresent()) {
114 updateState(DaikinBindingConstants.CHANNEL_CMP_FREQ,
115 new QuantityType<>(sensorInfo.compressorfrequency.get(), Units.PERCENT));
117 updateState(DaikinBindingConstants.CHANNEL_CMP_FREQ, UnDefType.UNDEF);
121 EnergyInfoYear energyInfoYear = webTargets.getEnergyInfoYear();
123 if (energyInfoYear.energyHeatingThisYear.isPresent()) {
124 updateEnergyYearChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_CURRENTYEAR,
125 energyInfoYear.energyHeatingThisYear);
127 if (energyInfoYear.energyCoolingThisYear.isPresent()) {
128 updateEnergyYearChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_CURRENTYEAR,
129 energyInfoYear.energyCoolingThisYear);
131 } catch (DaikinCommunicationException e) {
132 // Suppress any error if energy info is not supported.
133 logger.debug("getEnergyInfoYear() error: {}", e.getMessage());
137 EnergyInfoDayAndWeek energyInfoDayAndWeek = webTargets.getEnergyInfoDayAndWeek();
139 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_TODAY,
140 energyInfoDayAndWeek.energyHeatingToday);
141 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_THISWEEK,
142 energyInfoDayAndWeek.energyHeatingThisWeek);
143 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_LASTWEEK,
144 energyInfoDayAndWeek.energyHeatingLastWeek);
145 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_TODAY,
146 energyInfoDayAndWeek.energyCoolingToday);
147 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_THISWEEK,
148 energyInfoDayAndWeek.energyCoolingThisWeek);
149 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_LASTWEEK,
150 energyInfoDayAndWeek.energyCoolingLastWeek);
151 } catch (DaikinCommunicationException e) {
152 // Suppress any error if energy info is not supported.
153 logger.debug("getEnergyInfoDayAndWeek() error: {}", e.getMessage());
158 protected boolean handleCommandInternal(ChannelUID channelUID, Command command)
159 throws DaikinCommunicationException {
160 switch (channelUID.getId()) {
161 case DaikinBindingConstants.CHANNEL_AC_FAN_DIR:
162 if (command instanceof StringType stringCommand) {
163 changeFanDir(stringCommand.toString());
167 case DaikinBindingConstants.CHANNEL_AC_SPECIALMODE:
168 if (command instanceof StringType stringCommand) {
169 changeSpecialMode(stringCommand.toString());
173 case DaikinBindingConstants.CHANNEL_AC_STREAMER:
174 if (command instanceof OnOffType onOffCommand) {
175 changeStreamer(onOffCommand.equals(OnOffType.ON));
184 protected void changePower(boolean power) throws DaikinCommunicationException {
185 ControlInfo info = webTargets.getControlInfo();
187 webTargets.setControlInfo(info);
191 protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
192 ControlInfo info = webTargets.getControlInfo();
193 info.temp = Optional.of(newTemperature);
194 webTargets.setControlInfo(info);
198 protected void changeMode(String mode) throws DaikinCommunicationException {
201 newMode = Mode.valueOf(mode);
202 } catch (IllegalArgumentException ex) {
203 logger.warn("Invalid mode: {}. Valid values: {}", mode, Mode.values());
206 ControlInfo info = webTargets.getControlInfo();
208 if (autoModeValue.isPresent()) {
209 info.autoModeValue = autoModeValue.get();
211 boolean accepted = webTargets.setControlInfo(info);
213 // If mode=0 is not accepted try AUTO1 (mode=1)
214 if (!accepted && newMode == Mode.AUTO && autoModeValue.isEmpty()) {
215 info.autoModeValue = Mode.AUTO1.getValue();
216 if (webTargets.setControlInfo(info)) {
217 autoModeValue = Optional.of(info.autoModeValue);
218 logger.debug("AUTO uses mode={}", info.autoModeValue);
220 logger.warn("AUTO mode not accepted with mode=0 or mode=1");
226 protected void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException {
229 newSpeed = FanSpeed.valueOf(fanSpeed);
230 } catch (IllegalArgumentException ex) {
231 logger.warn("Invalid fan speed: {}. Valid values: {}", fanSpeed, FanSpeed.values());
234 ControlInfo info = webTargets.getControlInfo();
235 info.fanSpeed = newSpeed;
236 webTargets.setControlInfo(info);
239 protected void changeFanDir(String fanDir) throws DaikinCommunicationException {
240 FanMovement newMovement;
242 newMovement = FanMovement.valueOf(fanDir);
243 } catch (IllegalArgumentException ex) {
244 logger.warn("Invalid fan direction: {}. Valid values: {}", fanDir, FanMovement.values());
247 ControlInfo info = webTargets.getControlInfo();
248 info.fanMovement = newMovement;
249 webTargets.setControlInfo(info);
252 protected void changeSpecialMode(String specialMode) throws DaikinCommunicationException {
255 newMode = SpecialMode.valueOf(specialMode);
256 } catch (IllegalArgumentException e) {
257 logger.warn("Invalid specialmode: {}. Valid values: {}", specialMode, SpecialMode.values());
260 webTargets.setSpecialMode(newMode);
263 protected void changeStreamer(boolean streamerMode) throws DaikinCommunicationException {
264 webTargets.setStreamerMode(streamerMode);
268 * Updates energy year channels. Values are provided in hundreds of Watt
273 protected void updateEnergyYearChannel(String channel, Optional<Integer[]> maybePower) {
274 IntStream.range(1, 13).forEach(i -> updateState(
275 String.format(DaikinBindingConstants.CHANNEL_ENERGY_STRING_FORMAT, channel, i),
276 maybePower.<State> map(
277 t -> new QuantityType<>(BigDecimal.valueOf(t[i - 1].longValue(), 1), Units.KILOWATT_HOUR))
278 .orElse(UnDefType.UNDEF))
288 protected void updateEnergyDayAndWeekChannel(String channel, Optional<Double> maybePower) {
289 if (maybePower.isPresent()) {
291 maybePower.<State> map(t -> new QuantityType<>(new DecimalType(t), Units.KILOWATT_HOUR))
292 .orElse(UnDefType.UNDEF));
297 protected void registerUuid(@Nullable String key) {
302 webTargets.registerUuid(key);
303 } catch (DaikinCommunicationException e) {
304 // suppress exceptions
305 logger.debug("registerUuid({}) error: {}", key, e.getMessage());