]> git.basschouten.com Git - openhab-addons.git/blob
3b34ad5642649cdb9c9dad5b6dbab64b817006ef
[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.math.BigDecimal;
16 import java.util.Objects;
17 import java.util.Optional;
18 import java.util.stream.IntStream;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.eclipse.jetty.client.HttpClient;
23 import org.openhab.binding.daikin.internal.DaikinBindingConstants;
24 import org.openhab.binding.daikin.internal.DaikinCommunicationException;
25 import org.openhab.binding.daikin.internal.DaikinDynamicStateDescriptionProvider;
26 import org.openhab.binding.daikin.internal.api.ControlInfo;
27 import org.openhab.binding.daikin.internal.api.DemandControl;
28 import org.openhab.binding.daikin.internal.api.EnergyInfoDayAndWeek;
29 import org.openhab.binding.daikin.internal.api.EnergyInfoYear;
30 import org.openhab.binding.daikin.internal.api.Enums.DemandControlMode;
31 import org.openhab.binding.daikin.internal.api.Enums.FanMovement;
32 import org.openhab.binding.daikin.internal.api.Enums.FanSpeed;
33 import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
34 import org.openhab.binding.daikin.internal.api.Enums.Mode;
35 import org.openhab.binding.daikin.internal.api.Enums.SpecialMode;
36 import org.openhab.binding.daikin.internal.api.SensorInfo;
37 import org.openhab.core.library.types.DecimalType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.PercentType;
40 import org.openhab.core.library.types.QuantityType;
41 import org.openhab.core.library.types.StringType;
42 import org.openhab.core.library.unit.Units;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.types.Command;
46 import org.openhab.core.types.State;
47 import org.openhab.core.types.UnDefType;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 import com.google.gson.JsonSyntaxException;
52
53 /**
54  * Handles communicating with a Daikin air conditioning unit.
55  *
56  * @author Tim Waterhouse - Initial Contribution
57  * @author Paul Smedley - Modifications to support Airbase Controllers
58  * @author Lukas Agethen - Added support for Energy Year reading, compressor frequency and powerful mode
59  * @author Wouter Denayer - Added to support for weekly and daily energy reading
60  *
61  */
62 @NonNullByDefault
63 public class DaikinAcUnitHandler extends DaikinBaseHandler {
64     private final Logger logger = LoggerFactory.getLogger(DaikinAcUnitHandler.class);
65
66     private Optional<Integer> autoModeValue = Optional.empty();
67     private boolean pollDemandControl = true;
68     private Optional<String> savedDemandControlSchedule = Optional.empty();
69     private Optional<Integer> savedDemandControlMaxPower = Optional.empty();
70
71     public DaikinAcUnitHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
72             @Nullable HttpClient httpClient) {
73         super(thing, stateDescriptionProvider, httpClient);
74     }
75
76     @Override
77     protected void pollStatus() throws DaikinCommunicationException {
78         ControlInfo controlInfo = webTargets.getControlInfo();
79         if (!"OK".equals(controlInfo.ret)) {
80             throw new DaikinCommunicationException("Invalid response from host");
81         }
82
83         updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(controlInfo.power));
84         updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp);
85
86         updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name()));
87         updateState(DaikinBindingConstants.CHANNEL_AC_FAN_SPEED, new StringType(controlInfo.fanSpeed.name()));
88         updateState(DaikinBindingConstants.CHANNEL_AC_FAN_DIR, new StringType(controlInfo.fanMovement.name()));
89
90         if (!controlInfo.power) {
91             updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
92         } else if (controlInfo.mode == Mode.COLD) {
93             updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
94         } else if (controlInfo.mode == Mode.HEAT) {
95             updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
96         } else if (controlInfo.mode == Mode.AUTO) {
97             updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
98         }
99
100         if (controlInfo.advancedMode.isUndefined()) {
101             updateState(DaikinBindingConstants.CHANNEL_AC_STREAMER, UnDefType.UNDEF);
102             updateState(DaikinBindingConstants.CHANNEL_AC_SPECIALMODE, UnDefType.UNDEF);
103         } else {
104             updateState(DaikinBindingConstants.CHANNEL_AC_STREAMER,
105                     OnOffType.from(controlInfo.advancedMode.isStreamerActive()));
106             updateState(DaikinBindingConstants.CHANNEL_AC_SPECIALMODE,
107                     new StringType(controlInfo.getSpecialMode().name()));
108         }
109
110         SensorInfo sensorInfo = webTargets.getSensorInfo();
111         updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
112
113         updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
114
115         if (sensorInfo.indoorhumidity.isPresent()) {
116             updateState(DaikinBindingConstants.CHANNEL_HUMIDITY,
117                     new QuantityType<>(sensorInfo.indoorhumidity.get(), Units.PERCENT));
118         } else {
119             updateState(DaikinBindingConstants.CHANNEL_HUMIDITY, UnDefType.UNDEF);
120         }
121
122         if (sensorInfo.compressorfrequency.isPresent()) {
123             updateState(DaikinBindingConstants.CHANNEL_CMP_FREQ,
124                     new QuantityType<>(sensorInfo.compressorfrequency.get(), Units.PERCENT));
125         } else {
126             updateState(DaikinBindingConstants.CHANNEL_CMP_FREQ, UnDefType.UNDEF);
127         }
128
129         try {
130             EnergyInfoYear energyInfoYear = webTargets.getEnergyInfoYear();
131
132             if (energyInfoYear.energyHeatingThisYear.isPresent()) {
133                 updateEnergyYearChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_CURRENTYEAR,
134                         energyInfoYear.energyHeatingThisYear);
135             }
136             if (energyInfoYear.energyCoolingThisYear.isPresent()) {
137                 updateEnergyYearChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_CURRENTYEAR,
138                         energyInfoYear.energyCoolingThisYear);
139             }
140         } catch (DaikinCommunicationException e) {
141             // Suppress any error if energy info is not supported.
142             logger.debug("getEnergyInfoYear() error: {}", e.getMessage());
143         }
144
145         try {
146             EnergyInfoDayAndWeek energyInfoDayAndWeek = webTargets.getEnergyInfoDayAndWeek();
147
148             updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_TODAY,
149                     energyInfoDayAndWeek.energyHeatingToday);
150             updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_THISWEEK,
151                     energyInfoDayAndWeek.energyHeatingThisWeek);
152             updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_HEATING_LASTWEEK,
153                     energyInfoDayAndWeek.energyHeatingLastWeek);
154             updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_TODAY,
155                     energyInfoDayAndWeek.energyCoolingToday);
156             updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_THISWEEK,
157                     energyInfoDayAndWeek.energyCoolingThisWeek);
158             updateEnergyDayAndWeekChannel(DaikinBindingConstants.CHANNEL_ENERGY_COOLING_LASTWEEK,
159                     energyInfoDayAndWeek.energyCoolingLastWeek);
160         } catch (DaikinCommunicationException e) {
161             // Suppress any error if energy info is not supported.
162             logger.debug("getEnergyInfoDayAndWeek() error: {}", e.getMessage());
163         }
164
165         if (pollDemandControl) {
166             try {
167                 DemandControl demandInfo = webTargets.getDemandControl();
168                 String schedule = demandInfo.getSchedule();
169                 int maxPower = demandInfo.maxPower;
170
171                 if (demandInfo.mode == DemandControlMode.SCHEDULED) {
172                     savedDemandControlSchedule = Optional.of(schedule);
173                     maxPower = demandInfo.getScheduledMaxPower();
174                 } else if (demandInfo.mode == DemandControlMode.MANUAL) {
175                     savedDemandControlMaxPower = Optional.of(demandInfo.maxPower);
176                 }
177
178                 updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_MODE, new StringType(demandInfo.mode.name()));
179                 updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_MAX_POWER, new PercentType(maxPower));
180                 updateState(DaikinBindingConstants.CHANNEL_AC_DEMAND_SCHEDULE, new StringType(schedule));
181             } catch (DaikinCommunicationException e) {
182                 // Suppress any error if demand control is not supported.
183                 logger.debug("getDemandControl() error: {}", e.getMessage());
184                 pollDemandControl = false;
185             }
186         }
187     }
188
189     @Override
190     protected boolean handleCommandInternal(ChannelUID channelUID, Command command)
191             throws DaikinCommunicationException {
192         switch (channelUID.getId()) {
193             case DaikinBindingConstants.CHANNEL_AC_FAN_DIR:
194                 if (command instanceof StringType stringCommand) {
195                     changeFanDir(stringCommand.toString());
196                     return true;
197                 }
198                 break;
199             case DaikinBindingConstants.CHANNEL_AC_SPECIALMODE:
200                 if (command instanceof StringType stringCommand) {
201                     changeSpecialMode(stringCommand.toString());
202                     return true;
203                 }
204                 break;
205             case DaikinBindingConstants.CHANNEL_AC_STREAMER:
206                 if (command instanceof OnOffType onOffCommand) {
207                     changeStreamer(onOffCommand.equals(OnOffType.ON));
208                     return true;
209                 }
210                 break;
211             case DaikinBindingConstants.CHANNEL_AC_DEMAND_MODE:
212                 if (command instanceof StringType stringCommand) {
213                     changeDemandMode(stringCommand.toString());
214                     return true;
215                 }
216                 break;
217             case DaikinBindingConstants.CHANNEL_AC_DEMAND_MAX_POWER:
218                 if (command instanceof PercentType percentCommand) {
219                     changeDemandMaxPower(percentCommand.intValue());
220                     return true;
221                 }
222                 break;
223             case DaikinBindingConstants.CHANNEL_AC_DEMAND_SCHEDULE:
224                 if (command instanceof StringType stringCommand) {
225                     changeDemandSchedule(stringCommand.toString());
226                     return true;
227                 }
228                 break;
229         }
230         return false;
231     }
232
233     @Override
234     protected void changePower(boolean power) throws DaikinCommunicationException {
235         ControlInfo info = webTargets.getControlInfo();
236         info.power = power;
237         webTargets.setControlInfo(info);
238     }
239
240     @Override
241     protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
242         ControlInfo info = webTargets.getControlInfo();
243         info.temp = Optional.of(newTemperature);
244         webTargets.setControlInfo(info);
245     }
246
247     @Override
248     protected void changeMode(String mode) throws DaikinCommunicationException {
249         Mode newMode;
250         try {
251             newMode = Mode.valueOf(mode);
252         } catch (IllegalArgumentException ex) {
253             logger.warn("Invalid mode: {}. Valid values: {}", mode, Mode.values());
254             return;
255         }
256         ControlInfo info = webTargets.getControlInfo();
257         info.mode = newMode;
258         if (autoModeValue.isPresent()) {
259             info.autoModeValue = autoModeValue.get();
260         }
261         boolean accepted = webTargets.setControlInfo(info);
262
263         // If mode=0 is not accepted try AUTO1 (mode=1)
264         if (!accepted && newMode == Mode.AUTO && autoModeValue.isEmpty()) {
265             info.autoModeValue = Mode.AUTO1.getValue();
266             if (webTargets.setControlInfo(info)) {
267                 autoModeValue = Optional.of(info.autoModeValue);
268                 logger.debug("AUTO uses mode={}", info.autoModeValue);
269             } else {
270                 logger.warn("AUTO mode not accepted with mode=0 or mode=1");
271             }
272         }
273     }
274
275     @Override
276     protected void changeFanSpeed(String fanSpeed) throws DaikinCommunicationException {
277         FanSpeed newSpeed;
278         try {
279             newSpeed = FanSpeed.valueOf(fanSpeed);
280         } catch (IllegalArgumentException ex) {
281             logger.warn("Invalid fan speed: {}. Valid values: {}", fanSpeed, FanSpeed.values());
282             return;
283         }
284         ControlInfo info = webTargets.getControlInfo();
285         info.fanSpeed = newSpeed;
286         webTargets.setControlInfo(info);
287     }
288
289     protected void changeFanDir(String fanDir) throws DaikinCommunicationException {
290         FanMovement newMovement;
291         try {
292             newMovement = FanMovement.valueOf(fanDir);
293         } catch (IllegalArgumentException ex) {
294             logger.warn("Invalid fan direction: {}. Valid values: {}", fanDir, FanMovement.values());
295             return;
296         }
297         ControlInfo info = webTargets.getControlInfo();
298         info.fanMovement = newMovement;
299         webTargets.setControlInfo(info);
300     }
301
302     protected void changeSpecialMode(String specialMode) throws DaikinCommunicationException {
303         SpecialMode newMode;
304         try {
305             newMode = SpecialMode.valueOf(specialMode);
306         } catch (IllegalArgumentException e) {
307             logger.warn("Invalid specialmode: {}. Valid values: {}", specialMode, SpecialMode.values());
308             return;
309         }
310         webTargets.setSpecialMode(newMode);
311     }
312
313     protected void changeStreamer(boolean streamerMode) throws DaikinCommunicationException {
314         webTargets.setStreamerMode(streamerMode);
315     }
316
317     protected void changeDemandMode(String mode) throws DaikinCommunicationException {
318         DemandControlMode newMode;
319         try {
320             newMode = DemandControlMode.valueOf(mode);
321         } catch (IllegalArgumentException e) {
322             logger.warn("Invalid demand mode: {}. Valid values: {}", mode, DemandControlMode.values());
323             return;
324         }
325         DemandControl demandInfo = webTargets.getDemandControl();
326         if (demandInfo.mode != newMode) {
327             if (newMode == DemandControlMode.SCHEDULED && savedDemandControlSchedule.isPresent()) {
328                 // restore previously saved schedule
329                 demandInfo.setSchedule(savedDemandControlSchedule.get());
330             }
331
332             if (newMode == DemandControlMode.MANUAL && savedDemandControlMaxPower.isPresent()) {
333                 // restore previously saved maxPower
334                 demandInfo.maxPower = savedDemandControlMaxPower.get();
335             }
336         }
337         demandInfo.mode = newMode;
338         webTargets.setDemandControl(demandInfo);
339     }
340
341     protected void changeDemandMaxPower(int maxPower) throws DaikinCommunicationException {
342         DemandControl demandInfo = webTargets.getDemandControl();
343         demandInfo.mode = DemandControlMode.MANUAL;
344         demandInfo.maxPower = maxPower;
345         webTargets.setDemandControl(demandInfo);
346         savedDemandControlMaxPower = Optional.of(maxPower);
347     }
348
349     protected void changeDemandSchedule(String schedule) throws DaikinCommunicationException {
350         DemandControl demandInfo = webTargets.getDemandControl();
351         try {
352             demandInfo.setSchedule(schedule);
353         } catch (JsonSyntaxException e) {
354             logger.warn("Invalid schedule: {}. {}", schedule, e.getMessage());
355             return;
356         }
357         demandInfo.mode = DemandControlMode.SCHEDULED;
358         webTargets.setDemandControl(demandInfo);
359         savedDemandControlSchedule = Optional.of(demandInfo.getSchedule());
360     }
361
362     /**
363      * Updates energy year channels. Values are provided in hundreds of Watt
364      *
365      * @param channel
366      * @param maybePower
367      */
368     protected void updateEnergyYearChannel(String channel, Optional<Integer[]> maybePower) {
369         IntStream.range(1, 13).forEach(i -> updateState(
370                 String.format(DaikinBindingConstants.CHANNEL_ENERGY_STRING_FORMAT, channel, i),
371                 Objects.requireNonNull(maybePower.<State> map(
372                         t -> new QuantityType<>(BigDecimal.valueOf(t[i - 1].longValue(), 1), Units.KILOWATT_HOUR))
373                         .orElse(UnDefType.UNDEF)))
374
375         );
376     }
377
378     /**
379      *
380      * @param channel
381      * @param maybePower
382      */
383     protected void updateEnergyDayAndWeekChannel(String channel, Optional<Double> maybePower) {
384         if (maybePower.isPresent()) {
385             updateState(channel,
386                     Objects.requireNonNull(
387                             maybePower.<State> map(t -> new QuantityType<>(new DecimalType(t), Units.KILOWATT_HOUR))
388                                     .orElse(UnDefType.UNDEF)));
389         }
390     }
391
392     @Override
393     protected void registerUuid(@Nullable String key) {
394         if (key == null) {
395             return;
396         }
397         try {
398             webTargets.registerUuid(key);
399         } catch (DaikinCommunicationException e) {
400             // suppress exceptions
401             logger.debug("registerUuid({}) error: {}", key, e.getMessage());
402         }
403     }
404 }