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