]> git.basschouten.com Git - openhab-addons.git/blob
62601846b05fad0ae6a535e31a8d78d600b6f771
[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.ArrayList;
16 import java.util.EnumSet;
17 import java.util.List;
18 import java.util.Optional;
19 import java.util.stream.IntStream;
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.DaikinDynamicStateDescriptionProvider;
27 import org.openhab.binding.daikin.internal.api.Enums.HomekitMode;
28 import org.openhab.binding.daikin.internal.api.SensorInfo;
29 import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo;
30 import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFanSpeed;
31 import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseFeature;
32 import org.openhab.binding.daikin.internal.api.airbase.AirbaseEnums.AirbaseMode;
33 import org.openhab.binding.daikin.internal.api.airbase.AirbaseModelInfo;
34 import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo;
35 import org.openhab.core.library.types.OnOffType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.types.Command;
40 import org.openhab.core.types.StateOption;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * Handles communicating with a Daikin Airbase wifi adapter.
46  *
47  * @author Tim Waterhouse - Initial Contribution
48  * @author Paul Smedley - Modifications to support Airbase Controllers
49  * @author Jimmy Tanagra - Support Airside and auto fan levels, DynamicStateDescription
50  *
51  */
52 @NonNullByDefault
53 public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
54     private final Logger logger = LoggerFactory.getLogger(DaikinAirbaseUnitHandler.class);
55     private @Nullable AirbaseModelInfo airbaseModelInfo;
56
57     public DaikinAirbaseUnitHandler(Thing thing, DaikinDynamicStateDescriptionProvider stateDescriptionProvider,
58             @Nullable HttpClient httpClient) {
59         super(thing, stateDescriptionProvider, httpClient);
60     }
61
62     @Override
63     protected boolean handleCommandInternal(ChannelUID channelUID, Command command)
64             throws DaikinCommunicationException {
65         if (channelUID.getId().startsWith(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE)) {
66             int zoneNumber = Integer.parseInt(channelUID.getId().substring(4));
67             if (command instanceof OnOffType) {
68                 changeZone(zoneNumber, command == OnOffType.ON);
69                 return true;
70             }
71         }
72         return false;
73     }
74
75     @Override
76     protected void pollStatus() throws DaikinCommunicationException {
77         if (airbaseModelInfo == null || !"OK".equals(airbaseModelInfo.ret)) {
78             airbaseModelInfo = webTargets.getAirbaseModelInfo();
79             updateChannelStateDescriptions();
80         }
81
82         AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo();
83         if (!"OK".equals(controlInfo.ret)) {
84             throw new DaikinCommunicationException("Invalid response from host");
85         }
86
87         updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(controlInfo.power));
88         updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp);
89         updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name()));
90         updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED, new StringType(controlInfo.fanSpeed.name()));
91
92         if (!controlInfo.power) {
93             updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
94         } else if (controlInfo.mode == AirbaseMode.COLD) {
95             updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
96         } else if (controlInfo.mode == AirbaseMode.HEAT) {
97             updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
98         } else if (controlInfo.mode == AirbaseMode.AUTO) {
99             updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
100         }
101
102         SensorInfo sensorInfo = webTargets.getAirbaseSensorInfo();
103         updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
104         updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
105
106         AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
107         IntStream.range(0, zoneInfo.zone.length)
108                 .forEach(idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + (idx + 1),
109                         OnOffType.from(zoneInfo.zone[idx])));
110     }
111
112     @Override
113     protected void changePower(boolean power) throws DaikinCommunicationException {
114         AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
115         info.power = power;
116         webTargets.setAirbaseControlInfo(info);
117     }
118
119     @Override
120     protected void changeSetPoint(double newTemperature) throws DaikinCommunicationException {
121         AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
122         info.temp = Optional.of(newTemperature);
123         webTargets.setAirbaseControlInfo(info);
124     }
125
126     @Override
127     protected void changeMode(String mode) throws DaikinCommunicationException {
128         AirbaseMode newMode;
129         try {
130             newMode = AirbaseMode.valueOf(mode);
131         } catch (IllegalArgumentException ex) {
132             logger.warn("Invalid mode: {}. Valid values: {}", mode, AirbaseMode.values());
133             return;
134         }
135         if (airbaseModelInfo != null) {
136             if ((newMode == AirbaseMode.AUTO && !airbaseModelInfo.features.contains(AirbaseFeature.AUTO))
137                     || (newMode == AirbaseMode.DRY && !airbaseModelInfo.features.contains(AirbaseFeature.DRY))) {
138                 logger.warn("{} mode is not supported by your controller", mode);
139                 return;
140             }
141         }
142         AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
143         info.mode = newMode;
144         webTargets.setAirbaseControlInfo(info);
145     }
146
147     @Override
148     protected void changeFanSpeed(String speed) throws DaikinCommunicationException {
149         AirbaseFanSpeed newFanSpeed;
150         try {
151             newFanSpeed = AirbaseFanSpeed.valueOf(speed);
152         } catch (IllegalArgumentException ex) {
153             logger.warn("Invalid fan speed: {}. Valid values: {}", speed, AirbaseFanSpeed.values());
154             return;
155         }
156         if (airbaseModelInfo != null) {
157             if (EnumSet.range(AirbaseFanSpeed.AUTO_LEVEL_1, AirbaseFanSpeed.AUTO_LEVEL_5).contains(newFanSpeed)
158                     && !airbaseModelInfo.features.contains(AirbaseFeature.FRATE_AUTO)) {
159                 logger.warn("Fan AUTO_LEVEL_X is not supported by your controller");
160                 return;
161             }
162             if (newFanSpeed == AirbaseFanSpeed.AIRSIDE && !airbaseModelInfo.features.contains(AirbaseFeature.AIRSIDE)) {
163                 logger.warn("Airside is not supported by your controller");
164                 return;
165             }
166         }
167         AirbaseControlInfo info = webTargets.getAirbaseControlInfo();
168         info.fanSpeed = newFanSpeed;
169         webTargets.setAirbaseControlInfo(info);
170     }
171
172     /**
173      * 
174      * Turn the zone on/off
175      * The Airbase controller allows turning off all zones, so we allow it here too
176      * 
177      * @param zone the zone number starting from 1
178      * @param command true to turn on the zone, false to turn it off
179      * 
180      */
181     protected void changeZone(int zone, boolean command) throws DaikinCommunicationException {
182         AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
183         long maxZones = zoneInfo.zone.length;
184
185         if (airbaseModelInfo != null) {
186             maxZones = Math.min(maxZones, airbaseModelInfo.zonespresent);
187         }
188         if (zone <= 0 || zone > maxZones) {
189             logger.warn("The given zone number ({}) is outside the number of zones supported by the controller ({})",
190                     zone, maxZones);
191             return;
192         }
193
194         zoneInfo.zone[zone - 1] = command;
195         webTargets.setAirbaseZoneInfo(zoneInfo);
196     }
197
198     @Override
199     protected void registerUuid(@Nullable String key) {
200         // not implemented. There is currently no known Airbase adapter that requires uuid authentication
201     }
202
203     protected void updateChannelStateDescriptions() {
204         updateAirbaseFanSpeedChannelStateDescription();
205         updateAirbaseModeChannelStateDescription();
206     }
207
208     protected void updateAirbaseFanSpeedChannelStateDescription() {
209         List<StateOption> options = new ArrayList<>();
210         options.add(new StateOption(AirbaseFanSpeed.AUTO.name(), AirbaseFanSpeed.AUTO.getLabel()));
211         if (airbaseModelInfo.features.contains(AirbaseFeature.AIRSIDE)) {
212             options.add(new StateOption(AirbaseFanSpeed.AIRSIDE.name(), AirbaseFanSpeed.AIRSIDE.getLabel()));
213         }
214         for (AirbaseFanSpeed f : EnumSet.range(AirbaseFanSpeed.LEVEL_1, AirbaseFanSpeed.LEVEL_5)) {
215             options.add(new StateOption(f.name(), f.getLabel()));
216         }
217         if (airbaseModelInfo.features.contains(AirbaseFeature.FRATE_AUTO)) {
218             for (AirbaseFanSpeed f : EnumSet.range(AirbaseFanSpeed.AUTO_LEVEL_1, AirbaseFanSpeed.AUTO_LEVEL_5)) {
219                 options.add(new StateOption(f.name(), f.getLabel()));
220             }
221         }
222         stateDescriptionProvider.setStateOptions(
223                 new ChannelUID(thing.getUID(), DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED), options);
224     }
225
226     protected void updateAirbaseModeChannelStateDescription() {
227         List<StateOption> options = new ArrayList<>();
228         if (airbaseModelInfo.features.contains(AirbaseFeature.AUTO)) {
229             options.add(new StateOption(AirbaseMode.AUTO.name(), AirbaseMode.AUTO.getLabel()));
230         }
231         for (AirbaseMode f : EnumSet.complementOf(EnumSet.of(AirbaseMode.AUTO, AirbaseMode.DRY))) {
232             options.add(new StateOption(f.name(), f.getLabel()));
233         }
234         if (airbaseModelInfo.features.contains(AirbaseFeature.DRY)) {
235             options.add(new StateOption(AirbaseMode.DRY.name(), AirbaseMode.DRY.getLabel()));
236         }
237         stateDescriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), DaikinBindingConstants.CHANNEL_AC_MODE),
238                 options);
239     }
240 }