]> git.basschouten.com Git - openhab-addons.git/blob
ef2976131c353c5b5a0a9a7d82038ae2df530142
[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 boolean changePower(boolean power) throws DaikinCommunicationException;
74
75     protected abstract boolean changeSetPoint(double newTemperature) throws DaikinCommunicationException;
76
77     protected abstract boolean changeMode(String mode) throws DaikinCommunicationException;
78
79     protected abstract boolean 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                         if (changePower(onOffCommand.equals(OnOffType.ON))) {
108                             updateState(channelUID, onOffCommand);
109                         }
110                         return;
111                     }
112                     break;
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;
123                     } else {
124                         break; // Exit switch statement but proceed to log about unsupported command type
125                     }
126
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);
131                     }
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);
138                         }
139                         return;
140                     }
141                     break;
142                 case DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE:
143                     if (command instanceof StringType stringCommand) {
144                         if (changeHomekitMode(stringCommand.toString())) {
145                             updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, stringCommand);
146                         }
147                         return;
148                     }
149                     break;
150                 case DaikinBindingConstants.CHANNEL_AC_MODE:
151                     if (command instanceof StringType stringCommand) {
152                         if (changeMode(stringCommand.toString())) {
153                             updateState(channelUID, stringCommand);
154                         }
155                         return;
156                     }
157                     break;
158             }
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());
163         }
164     }
165
166     @Override
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");
172         } else {
173             if (config.uuid != null) {
174                 config.uuid = config.uuid.replaceAll("\\s|-", "");
175             }
176             webTargets = new DaikinWebTargets(httpClient, config.host, config.secure, config.uuid);
177             refreshInterval = config.refresh;
178             schedulePoll();
179         }
180     }
181
182     @Override
183     public void handleRemoval() {
184         stopPoll();
185         super.handleRemoval();
186     }
187
188     @Override
189     public void dispose() {
190         stopPoll();
191         super.dispose();
192     }
193
194     protected void schedulePoll() {
195         if (pollFuture != null) {
196             pollFuture.cancel(false);
197         }
198         logger.debug("Scheduling poll for 1s out, then every {} s", refreshInterval);
199         pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, refreshInterval, TimeUnit.SECONDS);
200     }
201
202     protected synchronized void stopPoll() {
203         if (pollFuture != null && !pollFuture.isCancelled()) {
204             pollFuture.cancel(true);
205             pollFuture = null;
206         }
207     }
208
209     private synchronized void poll() {
210         try {
211             logger.trace("Polling for state");
212             pollStatus();
213             if (getThing().getStatus() != ThingStatus.ONLINE) {
214                 updateStatus(ThingStatus.ONLINE);
215             }
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;
221             } else {
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());
225             }
226         } catch (DaikinCommunicationException e) {
227             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
228         }
229     }
230
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)));
234     }
235
236     private boolean changeHomekitMode(String homekitmode) throws DaikinCommunicationException {
237         try {
238             HomekitMode mode = HomekitMode.valueOf(homekitmode.toUpperCase());
239             boolean power = mode != HomekitMode.OFF;
240             if (!changePower(power)) {
241                 return false;
242             }
243
244             boolean changeModeSuccess = false;
245             updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(power));
246
247             String newMode = switch (mode) {
248                 case AUTO -> "AUTO";
249                 case HEAT -> "HEAT";
250                 case COOL -> "COLD";
251                 case OFF -> null;
252             };
253
254             if (newMode == null) {
255                 return true;
256             }
257
258             if (changeMode(newMode)) {
259                 updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(newMode));
260                 return true;
261             }
262
263             return false;
264         } catch (IllegalArgumentException e) {
265             logger.warn("Invalid homekit mode: {}", homekitmode);
266             return false;
267         }
268     }
269 }