]> git.basschouten.com Git - openhab-addons.git/blob
39f6b9f2ffd8d9fc1339bc8bdb658b23a09ae585
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.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;
48
49 /**
50  * Base class that handles common tasks with a Daikin air conditioning unit.
51  *
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
55  *
56  */
57 @NonNullByDefault
58 public abstract class DaikinBaseHandler extends BaseThingHandler {
59     private final Logger logger = LoggerFactory.getLogger(DaikinBaseHandler.class);
60
61     private final @Nullable HttpClient httpClient;
62
63     private long refreshInterval;
64
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;
70
71     // Abstract methods to be overridden by specific Daikin implementation class
72     protected abstract void pollStatus() throws IOException;
73
74     protected abstract void changePower(boolean power) throws DaikinCommunicationException;
75
76     protected abstract void changeSetPoint(double newTemperature) throws DaikinCommunicationException;
77
78     protected abstract void changeMode(String mode) throws DaikinCommunicationException;
79
80     protected abstract void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException;
81
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;
85
86     protected abstract void registerUuid(@Nullable String key);
87
88     public DaikinBaseHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
89             @Nullable HttpClient httpClient) {
90         super(thing);
91         this.stateDescriptionProvider = stateDescriptionProvider;
92         this.httpClient = httpClient;
93     }
94
95     @Override
96     public void handleCommand(ChannelUID channelUID, Command command) {
97         try {
98             if (handleCommandInternal(channelUID, command)) {
99                 return;
100             }
101             switch (channelUID.getId()) {
102                 case DaikinBindingConstants.CHANNEL_AC_POWER:
103                     if (command instanceof OnOffType) {
104                         changePower(((OnOffType) command).equals(OnOffType.ON));
105                         return;
106                     }
107                     break;
108                 case DaikinBindingConstants.CHANNEL_AC_TEMP:
109                     if (changeSetPoint(command)) {
110                         return;
111                     }
112                     break;
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());
117                         return;
118                     }
119                     break;
120                 case DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE:
121                     if (command instanceof StringType) {
122                         changeHomekitMode(command.toString());
123                         return;
124                     }
125                     break;
126                 case DaikinBindingConstants.CHANNEL_AC_MODE:
127                     if (command instanceof StringType) {
128                         changeMode(((StringType) command).toString());
129                         return;
130                     }
131                     break;
132             }
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());
137         }
138     }
139
140     @Override
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");
146         } else {
147             if (config.uuid != null) {
148                 config.uuid = config.uuid.replaceAll("\\s|-", "");
149             }
150             webTargets = new DaikinWebTargets(httpClient, config.host, config.secure, config.uuid);
151             refreshInterval = config.refresh;
152
153             schedulePoll();
154         }
155     }
156
157     @Override
158     public void handleRemoval() {
159         stopPoll();
160         super.handleRemoval();
161     }
162
163     @Override
164     public void dispose() {
165         stopPoll();
166         super.dispose();
167     }
168
169     protected void schedulePoll() {
170         if (pollFuture != null) {
171             pollFuture.cancel(false);
172         }
173         logger.debug("Scheduling poll for 1s out, then every {} s", refreshInterval);
174         pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, refreshInterval, TimeUnit.SECONDS);
175     }
176
177     protected synchronized void stopPoll() {
178         if (pollFuture != null && !pollFuture.isCancelled()) {
179             pollFuture.cancel(true);
180             pollFuture = null;
181         }
182     }
183
184     private synchronized void poll() {
185         try {
186             logger.debug("Polling for state");
187             pollStatus();
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;
193             } else {
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());
197             }
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());
202         }
203     }
204
205     protected void updateTemperatureChannel(String channel, Optional<Double> maybeTemperature) {
206         updateState(channel,
207                 maybeTemperature.<State> map(t -> new QuantityType<>(t, SIUnits.CELSIUS)).orElse(UnDefType.UNDEF));
208     }
209
210     /**
211      * @return true if the command was of an expected type, false otherwise
212      */
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();
219         } else {
220             return false;
221         }
222
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);
226         return true;
227     }
228
229     private void changeHomekitMode(String homekitmode) throws DaikinCommunicationException {
230         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
231         if (HomekitMode.OFF.getValue().equals(homekitmode)) {
232             changePower(false);
233         } else {
234             changePower(true);
235             if (HomekitMode.AUTO.getValue().equals(homekitmode)) {
236                 changeMode("AUTO");
237             } else if (HomekitMode.HEAT.getValue().equals(homekitmode)) {
238                 changeMode("HEAT");
239             } else if (HomekitMode.COOL.getValue().equals(homekitmode)) {
240                 changeMode("COLD");
241             }
242         }
243     }
244 }