2 * Copyright (c) 2010-2021 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.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.ThingTypeUID;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.State;
45 import org.openhab.core.types.UnDefType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * Base class that handles common tasks with a Daikin air conditioning unit.
52 * @author Tim Waterhouse - Initial Contribution
53 * @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
54 * @author Jimmy Tanagra - Split handler classes, support Airside and DynamicStateDescription
58 public abstract class DaikinBaseHandler extends BaseThingHandler {
59 private final Logger logger = LoggerFactory.getLogger(DaikinBaseHandler.class);
61 private final @Nullable HttpClient httpClient;
63 private long refreshInterval;
65 protected @Nullable DaikinWebTargets webTargets;
66 private @Nullable ScheduledFuture<?> pollFuture;
67 protected final DaikinDynamicStateDescriptionProvider stateDescriptionProvider;
68 protected @Nullable DaikinConfiguration config;
69 private boolean uuidRegistrationAttempted = false;
71 // Abstract methods to be overridden by specific Daikin implementation class
72 protected abstract void pollStatus() throws IOException;
74 protected abstract void changePower(boolean power) throws DaikinCommunicationException;
76 protected abstract void changeSetPoint(double newTemperature) throws DaikinCommunicationException;
78 protected abstract void changeMode(String mode) throws DaikinCommunicationException;
80 protected abstract void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException;
82 // Power, Temp, Fan and Mode are handled in this base class. Override this to handle additional channels.
83 protected abstract boolean handleCommandInternal(ChannelUID channelUID, Command command)
84 throws DaikinCommunicationException;
86 protected abstract void registerUuid(@Nullable String key);
88 public DaikinBaseHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
89 @Nullable HttpClient httpClient) {
91 this.stateDescriptionProvider = stateDescriptionProvider;
92 this.httpClient = httpClient;
96 public void handleCommand(ChannelUID channelUID, Command command) {
98 if (handleCommandInternal(channelUID, command)) {
101 switch (channelUID.getId()) {
102 case DaikinBindingConstants.CHANNEL_AC_POWER:
103 if (command instanceof OnOffType) {
104 changePower(((OnOffType) command).equals(OnOffType.ON));
108 case DaikinBindingConstants.CHANNEL_AC_TEMP:
109 if (changeSetPoint(command)) {
113 case DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED:
114 case DaikinBindingConstants.CHANNEL_AC_FAN_SPEED:
115 if (command instanceof StringType) {
116 changeFanSpeed(((StringType) command).toString());
120 case DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE:
121 if (command instanceof StringType) {
122 changeHomekitMode(command.toString());
126 case DaikinBindingConstants.CHANNEL_AC_MODE:
127 if (command instanceof StringType) {
128 changeMode(((StringType) command).toString());
133 logger.debug("Received command ({}) of wrong type for thing '{}' on channel {}", command,
134 thing.getUID().getAsString(), channelUID.getId());
135 } catch (DaikinCommunicationException ex) {
136 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
141 public void initialize() {
142 logger.debug("Initializing Daikin AC Unit");
143 config = getConfigAs(DaikinConfiguration.class);
144 if (config.host == null) {
145 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host address must be set");
147 if (config.uuid != null) {
148 config.uuid = config.uuid.replaceAll("\\s|-", "");
150 webTargets = new DaikinWebTargets(httpClient, config.host, config.secure, config.uuid);
151 refreshInterval = config.refresh;
158 public void handleRemoval() {
160 super.handleRemoval();
164 public void dispose() {
169 protected void schedulePoll() {
170 if (pollFuture != null) {
171 pollFuture.cancel(false);
173 logger.debug("Scheduling poll for 1s out, then every {} s", refreshInterval);
174 pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, refreshInterval, TimeUnit.SECONDS);
177 protected synchronized void stopPoll() {
178 if (pollFuture != null && !pollFuture.isCancelled()) {
179 pollFuture.cancel(true);
184 private synchronized void poll() {
186 logger.debug("Polling for state");
188 } catch (DaikinCommunicationForbiddenException e) {
189 if (!uuidRegistrationAttempted && config.key != null && config.uuid != null) {
190 logger.debug("poll: Attempting to register uuid {} with key {}", config.uuid, config.key);
191 registerUuid(config.key);
192 uuidRegistrationAttempted = true;
194 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
195 "Access denied. Check uuid/key.");
196 logger.warn("{} access denied by adapter. Check uuid/key.", thing.getUID());
198 } catch (IOException e) {
199 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
200 } catch (RuntimeException e) {
201 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
205 protected void updateTemperatureChannel(String channel, Optional<Double> maybeTemperature) {
207 maybeTemperature.<State> map(t -> new QuantityType<>(t, SIUnits.CELSIUS)).orElse(UnDefType.UNDEF));
211 * @return true if the command was of an expected type, false otherwise
213 private boolean changeSetPoint(Command command) throws DaikinCommunicationException {
214 double newTemperature;
215 if (command instanceof DecimalType) {
216 newTemperature = ((DecimalType) command).doubleValue();
217 } else if (command instanceof QuantityType) {
218 newTemperature = ((QuantityType<Temperature>) command).toUnit(SIUnits.CELSIUS).doubleValue();
223 // Only half degree increments are allowed, all others are silently rejected by the A/C units
224 newTemperature = Math.round(newTemperature * 2) / 2.0;
225 changeSetPoint(newTemperature);
229 private void changeHomekitMode(String homekitmode) throws DaikinCommunicationException {
230 ThingTypeUID thingTypeUID = thing.getThingTypeUID();
231 if (HomekitMode.OFF.getValue().equals(homekitmode)) {
235 if (HomekitMode.AUTO.getValue().equals(homekitmode)) {
237 } else if (HomekitMode.HEAT.getValue().equals(homekitmode)) {
239 } else if (HomekitMode.COOL.getValue().equals(homekitmode)) {