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.math.BigDecimal;
16 import java.util.Objects;
17 import java.util.Optional;
18 import java.util.stream.IntStream;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.eclipse.jetty.client.HttpClient;
23 import org.openhab.binding.daikin.internal.DaikinBindingConstants;
24 import org.openhab.binding.daikin.internal.DaikinCommunicationException;
25 import org.openhab.binding.daikin.internal.DaikinDynamicStateDescriptionProvider;
26 import org.openhab.binding.daikin.internal.api.ControlInfo;
27 import org.openhab.binding.daikin.internal.api.DemandControl;
28 import org.openhab.binding.daikin.internal.api.EnergyInfoDayAndWeek;
29 import org.openhab.binding.daikin.internal.api.EnergyInfoYear;
30 import org.openhab.binding.daikin.internal.api.Enums.DemandControlMode;
31 import org.openhab.binding.daikin.internal.api.Enums.FanMovement;
32 import org.openhab.binding.daikin.internal.api.Enums.FanSpeed;
33 import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
34 import org.openhab.binding.daikin.internal.api.Enums.Mode;
35 import org.openhab.binding.daikin.internal.api.Enums.SpecialMode;
36 import org.openhab.binding.daikin.internal.api.SensorInfo;
37 import org.openhab.core.library.types.DecimalType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.PercentType;
40 import org.openhab.core.library.types.QuantityType;
41 import org.openhab.core.library.types.StringType;
42 import org.openhab.core.library.unit.Units;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.types.Command;
46 import org.openhab.core.types.State;
47 import org.openhab.core.types.UnDefType;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
51 import com.google.gson.JsonSyntaxException;
54 * Handles communicating with a Daikin air conditioning unit.
56 * @author Tim Waterhouse - Initial Contribution
57 * @author Paul Smedley - Modifications to support Airbase Controllers
58 * @author Lukas Agethen - Added support for Energy Year reading, compressor frequency and powerful mode
59 * @author Wouter Denayer - Added to support for weekly and daily energy reading
63 public class DaikinAcUnitHandler extends DaikinBaseHandler {
64 private final Logger logger = LoggerFactory.getLogger(DaikinAcUnitHandler.class);
66 private Optional<Integer> autoModeValue = Optional.empty();
67 private boolean pollDemandControl = true;
68 private Optional<String> savedDemandControlSchedule = Optional.empty();
69 private Optional<Integer> savedDemandControlMaxPower = Optional.empty();
71 public DaikinAcUnitHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
72 @Nullable HttpClient httpClient) {
73 super(thing, stateDescriptionProvider, httpClient);
77 protected void pollStatus() throws DaikinCommunicationException {
78 ControlInfo controlInfo = webTargets.getControlInfo();
79 if (!"OK".equals(controlInfo.ret)) {
80 throw new DaikinCommunicationException("Invalid response from host");
83 updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(controlInfo.power));
84 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp);
86 updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name()));
87 updateState(DaikinBindingConstants.CHANNEL_AC_FAN_SPEED, new StringType(controlInfo.fanSpeed.name()));
88 updateState(DaikinBindingConstants.CHANNEL_AC_FAN_DIR, new StringType(controlInfo.fanMovement.name()));
90 if (!controlInfo.power) {
91 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
92 } else if (controlInfo.mode == Mode.COLD) {
93 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
94 } else if (controlInfo.mode == Mode.HEAT) {
95 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
96 } else if (controlInfo.mode == Mode.AUTO) {
97 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
100 if (controlInfo.advancedMode.isUndefined()) {
101 updateState(DaikinBindingConstants.CHANNEL_AC_STREAMER, UnDefType.UNDEF);
102 updateState(DaikinBindingConstants.CHANNEL_AC_SPECIALMODE, UnDefType.UNDEF);
104 updateState(DaikinBindingConstants.CHANNEL_AC_STREAMER,
105 OnOffType.from(controlInfo.advancedMode.isStreamerActive()));
106 updateState(DaikinBindingConstants.CHANNEL_AC_SPECIALMODE,
107 new StringType(controlInfo.getSpecialMode().name()));
110 SensorInfo sensorInfo = webTargets.getSensorInfo();
111 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
113 updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
115 if (sensorInfo.indoorhumidity.isPresent()) {
116 updateState(DaikinBindingConstants.CHANNEL_HUMIDITY,
117 new QuantityType<>(sensorInfo.indoorhumidity.get(), Units.PERCENT));
119 updateState(DaikinBindingConstants.CHANNEL_HUMIDITY, UnDefType.UNDEF);
122 if (sensorInfo.compressorfrequency.isPresent()) {
123 updateState(DaikinBindingConstants.CHANNEL_CMP_FREQ,
124 new QuantityType<>(sensorInfo.compressorfrequency.get(), Units.PERCENT));
126 updateState(DaikinBindingConstants.CHANNEL_CMP_FREQ, UnDefType.UNDEF);
130 EnergyInfoYear energyInfoYear = webTargets.getEnergyInfoYear();
132 if (energyInfoYear.energyHeatingThisYear.isPresent()) {
133 updateEnergyYearChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_CURRENTYEAR,
134 energyInfoYear.energyHeatingThisYear);
136 if (energyInfoYear.energyCoolingThisYear.isPresent()) {
137 updateEnergyYearChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_CURRENTYEAR,
138 energyInfoYear.energyCoolingThisYear);
140 } catch (DaikinCommunicationException e) {
141 // Suppress any error if energy info is not supported.
142 logger.debug("getEnergyInfoYear() error: {}", e.getMessage());
146 EnergyInfoDayAndWeek energyInfoDayAndWeek = webTargets.getEnergyInfoDayAndWeek();
148 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_TODAY,
149 energyInfoDayAndWeek.energyHeatingToday);
150 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_THISWEEK,
151 energyInfoDayAndWeek.energyHeatingThisWeek);
152 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_LASTWEEK,
153 energyInfoDayAndWeek.energyHeatingLastWeek);
154 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_TODAY,
155 energyInfoDayAndWeek.energyCoolingToday);
156 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_THISWEEK,
157 energyInfoDayAndWeek.energyCoolingThisWeek);
158 updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_LASTWEEK,
159 energyInfoDayAndWeek.energyCoolingLastWeek);
160 } catch (DaikinCommunicationException e) {
161 // Suppress any error if energy info is not supported.
162 logger.debug("getEnergyInfoDayAndWeek() error: {}", e.getMessage());
165 if (pollDemandControl) {
167 DemandControl demandInfo = webTargets.getDemandControl();
168 String schedule = demandInfo.getSchedule();
169 int maxPower = demandInfo.maxPower;
171 if (demandInfo.mode == DemandControlMode.SCHEDULED) {
172 savedDemandControlSchedule = Optional.of(schedule);
173 maxPower = demandInfo.getScheduledMaxPower();
174 } else if (demandInfo.mode == DemandControlMode.MANUAL) {
175 savedDemandControlMaxPower = Optional.of(demandInfo.maxPower);
178 updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_MODE, new StringType(demandInfo.mode.name()));
179 updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_MAX_POWER, new PercentType(maxPower));
180 updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_SCHEDULE, new StringType(schedule));
181 } catch (DaikinCommunicationException e) {
182 // Suppress any error if demand control is not supported.
183 logger.debug("getDemandControl() error: {}", e.getMessage());
184 pollDemandControl = false;
190 protected boolean handleCommandInternal(ChannelUID channelUID, Command command)
191 throws DaikinCommunicationException {
192 switch (channelUID.getId()) {
193 case DaikinBindingConstants.CHANNEL_AC_FAN_DIR:
194 if (command instanceof StringType stringCommand) {
195 changeFanDir(stringCommand.toString());
199 case DaikinBindingConstants.CHANNEL_AC_SPECIALMODE:
200 if (command instanceof StringType stringCommand) {
201 changeSpecialMode(stringCommand.toString());
205 case DaikinBindingConstants.CHANNEL_AC_STREAMER:
206 if (command instanceof OnOffType onOffCommand) {
207 changeStreamer(onOffCommand.equals(OnOffType.ON));
211 case DaikinBindingConstants.CHANNEL_AC_DEMAND_MODE:
212 if (command instanceof StringType stringCommand) {
213 changeDemandMode(stringCommand.toString());
217 case DaikinBindingConstants.CHANNEL_AC_DEMAND_MAX_POWER:
218 if (command instanceof PercentType percentCommand) {
219 changeDemandMaxPower(percentCommand.intValue());
223 case DaikinBindingConstants.CHANNEL_AC_DEMAND_SCHEDULE:
224 if (command instanceof StringType stringCommand) {
225 changeDemandSchedule(stringCommand.toString());
234 protected void changePower(boolean power) throws DaikinCommunicationException {
235 ControlInfo info = webTargets.getControlInfo();
237 webTargets.setControlInfo(info);
241 protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
242 ControlInfo info = webTargets.getControlInfo();
243 info.temp = Optional.of(newTemperature);
244 webTargets.setControlInfo(info);
248 protected void changeMode(String mode) throws DaikinCommunicationException {
251 newMode = Mode.valueOf(mode);
252 } catch (IllegalArgumentException ex) {
253 logger.warn("Invalid mode: {}. Valid values: {}", mode, Mode.values());
256 ControlInfo info = webTargets.getControlInfo();
258 if (autoModeValue.isPresent()) {
259 info.autoModeValue = autoModeValue.get();
261 boolean accepted = webTargets.setControlInfo(info);
263 // If mode=0 is not accepted try AUTO1 (mode=1)
264 if (!accepted && newMode == Mode.AUTO && autoModeValue.isEmpty()) {
265 info.autoModeValue = Mode.AUTO1.getValue();
266 if (webTargets.setControlInfo(info)) {
267 autoModeValue = Optional.of(info.autoModeValue);
268 logger.debug("AUTO uses mode={}", info.autoModeValue);
270 logger.warn("AUTO mode not accepted with mode=0 or mode=1");
276 protected void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException {
279 newSpeed = FanSpeed.valueOf(fanSpeed);
280 } catch (IllegalArgumentException ex) {
281 logger.warn("Invalid fan speed: {}. Valid values: {}", fanSpeed, FanSpeed.values());
284 ControlInfo info = webTargets.getControlInfo();
285 info.fanSpeed = newSpeed;
286 webTargets.setControlInfo(info);
289 protected void changeFanDir(String fanDir) throws DaikinCommunicationException {
290 FanMovement newMovement;
292 newMovement = FanMovement.valueOf(fanDir);
293 } catch (IllegalArgumentException ex) {
294 logger.warn("Invalid fan direction: {}. Valid values: {}", fanDir, FanMovement.values());
297 ControlInfo info = webTargets.getControlInfo();
298 info.fanMovement = newMovement;
299 webTargets.setControlInfo(info);
302 protected void changeSpecialMode(String specialMode) throws DaikinCommunicationException {
305 newMode = SpecialMode.valueOf(specialMode);
306 } catch (IllegalArgumentException e) {
307 logger.warn("Invalid specialmode: {}. Valid values: {}", specialMode, SpecialMode.values());
310 webTargets.setSpecialMode(newMode);
313 protected void changeStreamer(boolean streamerMode) throws DaikinCommunicationException {
314 webTargets.setStreamerMode(streamerMode);
317 protected void changeDemandMode(String mode) throws DaikinCommunicationException {
318 DemandControlMode newMode;
320 newMode = DemandControlMode.valueOf(mode);
321 } catch (IllegalArgumentException e) {
322 logger.warn("Invalid demand mode: {}. Valid values: {}", mode, DemandControlMode.values());
325 DemandControl demandInfo = webTargets.getDemandControl();
326 if (demandInfo.mode != newMode) {
327 if (newMode == DemandControlMode.SCHEDULED && savedDemandControlSchedule.isPresent()) {
328 // restore previously saved schedule
329 demandInfo.setSchedule(savedDemandControlSchedule.get());
332 if (newMode == DemandControlMode.MANUAL && savedDemandControlMaxPower.isPresent()) {
333 // restore previously saved maxPower
334 demandInfo.maxPower = savedDemandControlMaxPower.get();
337 demandInfo.mode = newMode;
338 webTargets.setDemandControl(demandInfo);
341 protected void changeDemandMaxPower(int maxPower) throws DaikinCommunicationException {
342 DemandControl demandInfo = webTargets.getDemandControl();
343 demandInfo.mode = DemandControlMode.MANUAL;
344 demandInfo.maxPower = maxPower;
345 webTargets.setDemandControl(demandInfo);
346 savedDemandControlMaxPower = Optional.of(maxPower);
349 protected void changeDemandSchedule(String schedule) throws DaikinCommunicationException {
350 DemandControl demandInfo = webTargets.getDemandControl();
352 demandInfo.setSchedule(schedule);
353 } catch (JsonSyntaxException e) {
354 logger.warn("Invalid schedule: {}. {}", schedule, e.getMessage());
357 demandInfo.mode = DemandControlMode.SCHEDULED;
358 webTargets.setDemandControl(demandInfo);
359 savedDemandControlSchedule = Optional.of(demandInfo.getSchedule());
363 * Updates energy year channels. Values are provided in hundreds of Watt
368 protected void updateEnergyYearChannel(String channel, Optional<Integer[]> maybePower) {
369 IntStream.range(1, 13).forEach(i -> updateState(
370 String.format(DaikinBindingConstants.CHANNEL_ENERGY_STRING_FORMAT, channel, i),
371 Objects.requireNonNull(maybePower.<State> map(
372 t -> new QuantityType<>(BigDecimal.valueOf(t[i - 1].longValue(), 1), Units.KILOWATT_HOUR))
373 .orElse(UnDefType.UNDEF)))
383 protected void updateEnergyDayAndWeekChannel(String channel, Optional<Double> maybePower) {
384 if (maybePower.isPresent()) {
386 Objects.requireNonNull(
387 maybePower.<State> map(t -> new QuantityType<>(new DecimalType(t), Units.KILOWATT_HOUR))
388 .orElse(UnDefType.UNDEF)));
393 protected void registerUuid(@Nullable String key) {
398 webTargets.registerUuid(key);
399 } catch (DaikinCommunicationException e) {
400 // suppress exceptions
401 logger.debug("registerUuid({}) error: {}", key, e.getMessage());