]> git.basschouten.com Git - openhab-addons.git/blob
a3620b51052bc2aa622c155e7509bd733350675e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.io.IOException;
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 <paul@smedley.id.au> - 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 IOException;
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         try {
97             if (handleCommandInternal(channelUID, command)) {
98                 return;
99             }
100             switch (channelUID.getId()) {
101                 case DaikinBindingConstants.CHANNEL_AC_POWER:
102                     if (command instanceof OnOffType) {
103                         changePower(((OnOffType) command).equals(OnOffType.ON));
104                         return;
105                     }
106                     break;
107                 case DaikinBindingConstants.CHANNEL_AC_TEMP:
108                     if (changeSetPoint(command)) {
109                         return;
110                     }
111                     break;
112                 case DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED:
113                 case DaikinBindingConstants.CHANNEL_AC_FAN_SPEED:
114                     if (command instanceof StringType) {
115                         changeFanSpeed(((StringType) command).toString());
116                         return;
117                     }
118                     break;
119                 case DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE:
120                     if (command instanceof StringType) {
121                         changeHomekitMode(command.toString());
122                         return;
123                     }
124                     break;
125                 case DaikinBindingConstants.CHANNEL_AC_MODE:
126                     if (command instanceof StringType) {
127                         changeMode(((StringType) command).toString());
128                         return;
129                     }
130                     break;
131             }
132             logger.debug("Received command ({}) of wrong type for thing '{}' on channel {}", command,
133                     thing.getUID().getAsString(), channelUID.getId());
134         } catch (DaikinCommunicationException ex) {
135             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage());
136         }
137     }
138
139     @Override
140     public void initialize() {
141         logger.debug("Initializing Daikin AC Unit");
142         config = getConfigAs(DaikinConfiguration.class);
143         if (config.host == null) {
144             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Host address must be set");
145         } else {
146             if (config.uuid != null) {
147                 config.uuid = config.uuid.replaceAll("\\s|-", "");
148             }
149             webTargets = new DaikinWebTargets(httpClient, config.host, config.secure, config.uuid);
150             refreshInterval = config.refresh;
151
152             schedulePoll();
153         }
154     }
155
156     @Override
157     public void handleRemoval() {
158         stopPoll();
159         super.handleRemoval();
160     }
161
162     @Override
163     public void dispose() {
164         stopPoll();
165         super.dispose();
166     }
167
168     protected void schedulePoll() {
169         if (pollFuture != null) {
170             pollFuture.cancel(false);
171         }
172         logger.debug("Scheduling poll for 1s out, then every {} s", refreshInterval);
173         pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, refreshInterval, TimeUnit.SECONDS);
174     }
175
176     protected synchronized void stopPoll() {
177         if (pollFuture != null && !pollFuture.isCancelled()) {
178             pollFuture.cancel(true);
179             pollFuture = null;
180         }
181     }
182
183     private synchronized void poll() {
184         try {
185             logger.debug("Polling for state");
186             pollStatus();
187         } catch (DaikinCommunicationForbiddenException e) {
188             if (!uuidRegistrationAttempted && config.key != null && config.uuid != null) {
189                 logger.debug("poll: Attempting to register uuid {} with key {}", config.uuid, config.key);
190                 registerUuid(config.key);
191                 uuidRegistrationAttempted = true;
192             } else {
193                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
194                         "Access denied. Check uuid/key.");
195                 logger.warn("{} access denied by adapter. Check uuid/key.", thing.getUID());
196             }
197         } catch (IOException e) {
198             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
199         } catch (RuntimeException e) {
200             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
201         }
202     }
203
204     protected void updateTemperatureChannel(String channel, Optional<Double> maybeTemperature) {
205         updateState(channel,
206                 maybeTemperature.<State> map(t -> new QuantityType<>(t, SIUnits.CELSIUS)).orElse(UnDefType.UNDEF));
207     }
208
209     /**
210      * @return true if the command was of an expected type, false otherwise
211      */
212     private boolean changeSetPoint(Command command) throws DaikinCommunicationException {
213         double newTemperature;
214         if (command instanceof DecimalType) {
215             newTemperature = ((DecimalType) command).doubleValue();
216         } else if (command instanceof QuantityType) {
217             newTemperature = ((QuantityType<Temperature>) command).toUnit(SIUnits.CELSIUS).doubleValue();
218         } else {
219             return false;
220         }
221
222         // Only half degree increments are allowed, all others are silently rejected by the A/C units
223         newTemperature = Math.round(newTemperature * 2) / 2.0;
224         changeSetPoint(newTemperature);
225         return true;
226     }
227
228     private void changeHomekitMode(String homekitmode) throws DaikinCommunicationException {
229         if (HomekitMode.OFF.getValue().equals(homekitmode)) {
230             changePower(false);
231         } else {
232             changePower(true);
233             if (HomekitMode.AUTO.getValue().equals(homekitmode)) {
234                 changeMode("AUTO");
235             } else if (HomekitMode.HEAT.getValue().equals(homekitmode)) {
236                 changeMode("HEAT");
237             } else if (HomekitMode.COOL.getValue().equals(homekitmode)) {
238                 changeMode("COLD");
239             }
240         }
241     }
242 }