]> git.basschouten.com Git - openhab-addons.git/blob
a427459337e3575dc4450f2af49f74f3809a17f6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.daikin.internal.handler;
14
15 import java.util.Objects;
16 import java.util.Optional;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19
20 import javax.measure.quantity.Temperature;
21
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;
47
48 /**
49  * Base class that handles common tasks with a Daikin air conditioning unit.
50  *
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
54  *
55  */
56 @NonNullByDefault
57 public abstract class DaikinBaseHandler extends BaseThingHandler {
58     private final Logger logger = LoggerFactory.getLogger(DaikinBaseHandler.class);
59
60     private final @Nullable HttpClient httpClient;
61
62     private long refreshInterval;
63
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;
69
70     // Abstract methods to be overridden by specific Daikin implementation class
71     protected abstract void pollStatus() throws DaikinCommunicationException;
72
73     protected abstract void changePower(boolean power) throws DaikinCommunicationException;
74
75     protected abstract void changeSetPoint(double newTemperature) throws DaikinCommunicationException;
76
77     protected abstract void changeMode(String mode) throws DaikinCommunicationException;
78
79     protected abstract void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException;
80
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;
84
85     protected abstract void registerUuid(@Nullable String key);
86
87     public DaikinBaseHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
88             @Nullable HttpClient httpClient) {
89         super(thing);
90         this.stateDescriptionProvider = stateDescriptionProvider;
91         this.httpClient = httpClient;
92     }
93
94     @Override
95     public void handleCommand(ChannelUID channelUID, Command command) {
96         if (webTargets == null) {
97             logger.warn("webTargets is null. This is possibly a bug.");
98             return;
99         }
100         try {
101             if (handleCommandInternal(channelUID, command)) {
102                 return;
103             }
104             switch (channelUID.getId()) {
105                 case DaikinBindingConstants.CHANNEL_AC_POWER:
106                     if (command instanceof OnOffType onOffCommand) {
107                         changePower(onOffCommand.equals(OnOffType.ON));
108                         return;
109                     }
110                     break;
111                 case DaikinBindingConstants.CHANNEL_AC_TEMP:
112                     if (changeSetPoint(command)) {
113                         return;
114                     }
115                     break;
116                 case DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED:
117                 case DaikinBindingConstants.CHANNEL_AC_FAN_SPEED:
118                     if (command instanceof StringType stringCommand) {
119                         changeFanSpeed(stringCommand.toString());
120                         return;
121                     }
122                     break;
123                 case DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE:
124                     if (command instanceof StringType) {
125                         changeHomekitMode(command.toString());
126                         return;
127                     }
128                     break;
129                 case DaikinBindingConstants.CHANNEL_AC_MODE:
130                     if (command instanceof StringType stringCommand) {
131                         changeMode(stringCommand.toString());
132                         return;
133                     }
134                     break;
135             }
136             logger.debug("Received command ({}) of wrong type for thing '{}' on channel {}", command,
137                     thing.getUID().getAsString(), channelUID.getId());
138         } catch (DaikinCommunicationException e) {
139             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
140         }
141     }
142
143     @Override
144     public void initialize() {
145         logger.debug("Initializing Daikin AC Unit");
146         config = getConfigAs(DaikinConfiguration.class);
147         if (config.host == null) {
148             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host address must be set");
149         } else {
150             if (config.uuid != null) {
151                 config.uuid = config.uuid.replaceAll("\\s|-", "");
152             }
153             webTargets = new DaikinWebTargets(httpClient, config.host, config.secure, config.uuid);
154             refreshInterval = config.refresh;
155             schedulePoll();
156         }
157     }
158
159     @Override
160     public void handleRemoval() {
161         stopPoll();
162         super.handleRemoval();
163     }
164
165     @Override
166     public void dispose() {
167         stopPoll();
168         super.dispose();
169     }
170
171     protected void schedulePoll() {
172         if (pollFuture != null) {
173             pollFuture.cancel(false);
174         }
175         logger.debug("Scheduling poll for 1s out, then every {} s", refreshInterval);
176         pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, refreshInterval, TimeUnit.SECONDS);
177     }
178
179     protected synchronized void stopPoll() {
180         if (pollFuture != null && !pollFuture.isCancelled()) {
181             pollFuture.cancel(true);
182             pollFuture = null;
183         }
184     }
185
186     private synchronized void poll() {
187         try {
188             logger.trace("Polling for state");
189             pollStatus();
190             if (getThing().getStatus() != ThingStatus.ONLINE) {
191                 updateStatus(ThingStatus.ONLINE);
192             }
193         } catch (DaikinCommunicationForbiddenException e) {
194             if (!uuidRegistrationAttempted && config.key != null && config.uuid != null) {
195                 logger.debug("poll: Attempting to register uuid {} with key {}", config.uuid, config.key);
196                 registerUuid(config.key);
197                 uuidRegistrationAttempted = true;
198             } else {
199                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
200                         "Access denied. Check uuid/key.");
201                 logger.warn("{} access denied by adapter. Check uuid/key.", thing.getUID());
202             }
203         } catch (DaikinCommunicationException e) {
204             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
205         }
206     }
207
208     protected void updateTemperatureChannel(String channel, Optional<Double> maybeTemperature) {
209         updateState(channel, Objects.requireNonNull(
210                 maybeTemperature.<State> map(t -> new QuantityType<>(t, SIUnits.CELSIUS)).orElse(UnDefType.UNDEF)));
211     }
212
213     /**
214      * @return true if the command was of an expected type, false otherwise
215      */
216     private boolean changeSetPoint(Command command) throws DaikinCommunicationException {
217         double newTemperature;
218         if (command instanceof DecimalType decimalCommand) {
219             newTemperature = decimalCommand.doubleValue();
220         } else if (command instanceof QuantityType) {
221             newTemperature = ((QuantityType<Temperature>) command).toUnit(SIUnits.CELSIUS).doubleValue();
222         } else {
223             return false;
224         }
225
226         // Only half degree increments are allowed, all others are silently rejected by the A/C units
227         newTemperature = Math.round(newTemperature * 2) / 2.0;
228         changeSetPoint(newTemperature);
229         return true;
230     }
231
232     private void changeHomekitMode(String homekitmode) throws DaikinCommunicationException {
233         if (HomekitMode.OFF.getValue().equals(homekitmode)) {
234             changePower(false);
235         } else {
236             changePower(true);
237             if (HomekitMode.AUTO.getValue().equals(homekitmode)) {
238                 changeMode("AUTO");
239             } else if (HomekitMode.HEAT.getValue().equals(homekitmode)) {
240                 changeMode("HEAT");
241             } else if (HomekitMode.COOL.getValue().equals(homekitmode)) {
242                 changeMode("COLD");
243             }
244         }
245     }
246 }