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.Objects;
16 import java.util.Optional;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
20 import javax.measure.quantity.Temperature;
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.DaikinCommunicationForbiddenException;
28 import org.openhab.binding.daikin.internal.DaikinDynamicStateDescriptionProvider;
29 import org.openhab.binding.daikin.internal.DaikinWebTargets;
30 import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
31 import org.openhab.binding.daikin.internal.config.DaikinConfiguration;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.library.unit.SIUnits;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.binding.BaseThingHandler;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.State;
44 import org.openhab.core.types.UnDefType;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * Base class that handles common tasks with a Daikin air conditioning unit.
51 * @author Tim Waterhouse - Initial Contribution
52 * @author Paul Smedley - Modifications to support Airbase Controllers
53 * @author Jimmy Tanagra - Split handler classes, support Airside and DynamicStateDescription
57 public abstract class DaikinBaseHandler extends BaseThingHandler {
58 private final Logger logger = LoggerFactory.getLogger(DaikinBaseHandler.class);
60 private final @Nullable HttpClient httpClient;
62 private long refreshInterval;
64 protected @Nullable DaikinWebTargets webTargets;
65 private @Nullable ScheduledFuture<?> pollFuture;
66 protected final DaikinDynamicStateDescriptionProvider stateDescriptionProvider;
67 protected @Nullable DaikinConfiguration config;
68 private boolean uuidRegistrationAttempted = false;
70 // Abstract methods to be overridden by specific Daikin implementation class
71 protected abstract void pollStatus() throws DaikinCommunicationException;
73 protected abstract boolean changePower(boolean power) throws DaikinCommunicationException;
75 protected abstract boolean changeSetPoint(double newTemperature) throws DaikinCommunicationException;
77 protected abstract boolean changeMode(String mode) throws DaikinCommunicationException;
79 protected abstract boolean changeFanSpeed(String fanSpeed) throws DaikinCommunicationException;
81 // Power, Temp, Fan and Mode are handled in this base class. Override this to handle additional channels.
82 protected abstract boolean handleCommandInternal(ChannelUID channelUID, Command command)
83 throws DaikinCommunicationException;
85 protected abstract void registerUuid(@Nullable String key);
87 public DaikinBaseHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
88 @Nullable HttpClient httpClient) {
90 this.stateDescriptionProvider = stateDescriptionProvider;
91 this.httpClient = httpClient;
95 public void handleCommand(ChannelUID channelUID, Command command) {
96 if (webTargets == null) {
97 logger.warn("webTargets is null. This is possibly a bug.");
101 if (handleCommandInternal(channelUID, command)) {
104 switch (channelUID.getId()) {
105 case DaikinBindingConstants.CHANNEL_AC_POWER:
106 if (command instanceof OnOffType onOffCommand) {
107 if (changePower(onOffCommand.equals(OnOffType.ON))) {
108 updateState(channelUID, onOffCommand);
113 case DaikinBindingConstants.CHANNEL_AC_TEMP:
114 double newTemperature;
115 State newState = UnDefType.UNDEF;
116 if (command instanceof DecimalType decimalCommand) {
117 newTemperature = decimalCommand.doubleValue();
118 newState = decimalCommand;
119 } else if (command instanceof QuantityType) {
120 QuantityType<Temperature> quantityCommand = (QuantityType<Temperature>) command;
121 newTemperature = quantityCommand.toUnit(SIUnits.CELSIUS).doubleValue();
122 newState = quantityCommand;
124 break; // Exit switch statement but proceed to log about unsupported command type
127 // Only half degree increments are allowed, all others are silently rejected by the A/C units
128 newTemperature = Math.round(newTemperature * 2) / 2.0;
129 if (changeSetPoint(newTemperature)) {
130 updateState(channelUID, newState);
132 return; // return here and don't log about wrong type below
133 case DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED:
134 case DaikinBindingConstants.CHANNEL_AC_FAN_SPEED:
135 if (command instanceof StringType stringCommand) {
136 if (changeFanSpeed(stringCommand.toString())) {
137 updateState(channelUID, stringCommand);
142 case DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE:
143 if (command instanceof StringType stringCommand) {
144 if (changeHomekitMode(stringCommand.toString())) {
145 updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, stringCommand);
150 case DaikinBindingConstants.CHANNEL_AC_MODE:
151 if (command instanceof StringType stringCommand) {
152 if (changeMode(stringCommand.toString())) {
153 updateState(channelUID, stringCommand);
159 logger.debug("Received command ({}) of wrong type for thing '{}' on channel {}", command,
160 thing.getUID().getAsString(), channelUID.getId());
161 } catch (DaikinCommunicationException e) {
162 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
167 public void initialize() {
168 logger.debug("Initializing Daikin AC Unit");
169 config = getConfigAs(DaikinConfiguration.class);
170 if (config.host == null) {
171 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host address must be set");
173 if (config.uuid != null) {
174 config.uuid = config.uuid.replaceAll("\\s|-", "");
176 webTargets = new DaikinWebTargets(httpClient, config.host, config.secure, config.uuid);
177 refreshInterval = config.refresh;
183 public void handleRemoval() {
185 super.handleRemoval();
189 public void dispose() {
194 protected void schedulePoll() {
195 if (pollFuture != null) {
196 pollFuture.cancel(false);
198 logger.debug("Scheduling poll for 1s out, then every {} s", refreshInterval);
199 pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, refreshInterval, TimeUnit.SECONDS);
202 protected synchronized void stopPoll() {
203 if (pollFuture != null && !pollFuture.isCancelled()) {
204 pollFuture.cancel(true);
209 private synchronized void poll() {
211 logger.trace("Polling for state");
213 if (getThing().getStatus() != ThingStatus.ONLINE) {
214 updateStatus(ThingStatus.ONLINE);
216 } catch (DaikinCommunicationForbiddenException e) {
217 if (!uuidRegistrationAttempted && config.key != null && config.uuid != null) {
218 logger.debug("poll: Attempting to register uuid {} with key {}", config.uuid, config.key);
219 registerUuid(config.key);
220 uuidRegistrationAttempted = true;
222 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
223 "Access denied. Check uuid/key.");
224 logger.warn("{} access denied by adapter. Check uuid/key.", thing.getUID());
226 } catch (DaikinCommunicationException e) {
227 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
231 protected void updateTemperatureChannel(String channel, Optional<Double> maybeTemperature) {
232 updateState(channel, Objects.requireNonNull(
233 maybeTemperature.<State> map(t -> new QuantityType<>(t, SIUnits.CELSIUS)).orElse(UnDefType.UNDEF)));
236 private boolean changeHomekitMode(String homekitmode) throws DaikinCommunicationException {
238 HomekitMode mode = HomekitMode.valueOf(homekitmode.toUpperCase());
239 boolean power = mode != HomekitMode.OFF;
240 if (!changePower(power)) {
244 boolean changeModeSuccess = false;
245 updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(power));
247 String newMode = switch (mode) {
254 if (newMode == null) {
258 if (changeMode(newMode)) {
259 updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(newMode));
264 } catch (IllegalArgumentException e) {
265 logger.warn("Invalid homekit mode: {}", homekitmode);