]> git.basschouten.com Git - openhab-addons.git/blob
d3b2464c07923227d6f34e4d9fb39d029a6090ff
[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.vesync.internal.handlers;
14
15 import static org.openhab.binding.vesync.internal.VeSyncConstants.*;
16 import static org.openhab.binding.vesync.internal.dto.requests.VeSyncProtocolConstants.*;
17
18 import java.util.Arrays;
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.Set;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.vesync.internal.VeSyncBridgeConfiguration;
25 import org.openhab.binding.vesync.internal.VeSyncConstants;
26 import org.openhab.binding.vesync.internal.dto.requests.VeSyncRequestManagedDeviceBypassV2;
27 import org.openhab.binding.vesync.internal.dto.responses.VeSyncV2BypassHumidifierStatus;
28 import org.openhab.core.cache.ExpiringCache;
29 import org.openhab.core.library.types.DecimalType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.library.unit.Units;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * The {@link VeSyncDeviceAirHumidifierHandler} is responsible for handling commands, which are
45  * sent to one of the channels.
46  *
47  * @author David Goodyear - Initial contribution
48  */
49 @NonNullByDefault
50 public class VeSyncDeviceAirHumidifierHandler extends VeSyncBaseDeviceHandler {
51
52     public static final String DEV_TYPE_FAMILY_AIR_HUMIDIFIER = "LUH";
53
54     public static final int DEFAULT_AIR_PURIFIER_POLL_RATE = 120;
55
56     public static final String DEV_FAMILY_CLASSIC_200S = "Classic 200S";
57     public static final String DEV_FAMILY_CLASSIC_300S = "Classic 300S";
58     public static final String DEV_FAMILY_DUAL_200S = "Dual 200S";
59     public static final String DEV_FAMILY_600S = "600S";
60     public static final String DEV_FAMILY_OASIS_MIST = "Oasis Mist";
61
62     public static final VeSyncDeviceMetadata CLASSIC200S = new VeSyncDeviceMetadata(DEV_FAMILY_CLASSIC_200S,
63             Collections.emptyList(), List.of("Classic200S"));
64
65     public static final VeSyncDeviceMetadata CLASSIC300S = new VeSyncDeviceMetadata(DEV_FAMILY_CLASSIC_300S,
66             Arrays.asList("A601S"), List.of("Classic300S"));
67
68     public static final VeSyncDeviceMetadata DUAL200S = new VeSyncDeviceMetadata(DEV_FAMILY_DUAL_200S,
69             Arrays.asList("D301S"), List.of("Dual200S"));
70
71     public static final VeSyncDeviceMetadata LV600S = new VeSyncDeviceMetadata(DEV_FAMILY_600S, Arrays.asList("A602S"),
72             Collections.emptyList());
73
74     public static final VeSyncDeviceMetadata OASIS_MIST = new VeSyncDeviceMetadata(DEV_FAMILY_OASIS_MIST,
75             Arrays.asList("O451S"), Collections.emptyList());
76
77     public static final List<VeSyncDeviceMetadata> SUPPORTED_MODEL_FAMILIES = Arrays.asList(LV600S, CLASSIC300S,
78             CLASSIC200S, DUAL200S, OASIS_MIST);
79
80     private static final List<String> CLASSIC_300S_600S_MODES = Arrays.asList(MODE_AUTO, MODE_MANUAL, MODE_SLEEP);
81     private static final List<String> CLASSIC_300S_NIGHT_LIGHT_MODES = Arrays.asList(MODE_ON, MODE_DIM, MODE_OFF);
82
83     private final Logger logger = LoggerFactory.getLogger(VeSyncDeviceAirHumidifierHandler.class);
84
85     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_HUMIDIFIER);
86
87     private final Object pollLock = new Object();
88
89     public VeSyncDeviceAirHumidifierHandler(Thing thing) {
90         super(thing);
91     }
92
93     @Override
94     protected String[] getChannelsToRemove() {
95         String[] toRemove = new String[] {};
96         final String deviceFamily = getThing().getProperties().get(DEVICE_PROP_DEVICE_FAMILY);
97         if (deviceFamily != null) {
98             switch (deviceFamily) {
99                 case DEV_FAMILY_CLASSIC_300S:
100                     toRemove = new String[] { DEVICE_CHANNEL_WARM_ENABLED, DEVICE_CHANNEL_WARM_LEVEL };
101                     break;
102                 case DEV_FAMILY_DUAL_200S:
103                 case DEV_FAMILY_CLASSIC_200S:
104                     toRemove = new String[] { DEVICE_CHANNEL_WARM_ENABLED, DEVICE_CHANNEL_WARM_LEVEL,
105                             DEVICE_CHANNEL_AF_NIGHT_LIGHT };
106                     break;
107                 case DEV_FAMILY_OASIS_MIST:
108                     toRemove = new String[] { DEVICE_CHANNEL_AF_NIGHT_LIGHT };
109                     break;
110             }
111         }
112         return toRemove;
113     }
114
115     @Override
116     public void initialize() {
117         super.initialize();
118         customiseChannels();
119     }
120
121     @Override
122     public void updateBridgeBasedPolls(final VeSyncBridgeConfiguration config) {
123         Integer pollRate = config.airPurifierPollInterval;
124         if (pollRate == null) {
125             pollRate = DEFAULT_AIR_PURIFIER_POLL_RATE;
126         }
127         if (ThingStatus.OFFLINE.equals(getThing().getStatus())) {
128             setBackgroundPollInterval(-1);
129         } else {
130             setBackgroundPollInterval(pollRate);
131         }
132     }
133
134     @Override
135     public void dispose() {
136         this.setBackgroundPollInterval(-1);
137     }
138
139     @Override
140     public String getDeviceFamilyProtocolPrefix() {
141         return DEV_TYPE_FAMILY_AIR_HUMIDIFIER;
142     }
143
144     @Override
145     public List<VeSyncDeviceMetadata> getSupportedDeviceMetadata() {
146         return SUPPORTED_MODEL_FAMILIES;
147     }
148
149     @Override
150     public void handleCommand(final ChannelUID channelUID, final Command command) {
151         final String deviceFamily = getThing().getProperties().get(DEVICE_PROP_DEVICE_FAMILY);
152         if (deviceFamily == null) {
153             return;
154         }
155
156         scheduler.submit(() -> {
157
158             if (command instanceof OnOffType) {
159                 switch (channelUID.getId()) {
160                     case DEVICE_CHANNEL_ENABLED:
161                         sendV2BypassControlCommand(DEVICE_SET_SWITCH,
162                                 new VeSyncRequestManagedDeviceBypassV2.SetSwitchPayload(command.equals(OnOffType.ON),
163                                         0));
164                         break;
165                     case DEVICE_CHANNEL_DISPLAY_ENABLED:
166                         sendV2BypassControlCommand(DEVICE_SET_DISPLAY,
167                                 new VeSyncRequestManagedDeviceBypassV2.SetState(command.equals(OnOffType.ON)));
168                         break;
169                     case DEVICE_CHANNEL_STOP_AT_TARGET:
170                         sendV2BypassControlCommand(DEVICE_SET_AUTOMATIC_STOP,
171                                 new VeSyncRequestManagedDeviceBypassV2.EnabledPayload(command.equals(OnOffType.ON)));
172                         break;
173                     case DEVICE_CHANNEL_WARM_ENABLED:
174                         logger.warn("Warm mode API is unknown in order to send the command");
175                         break;
176                 }
177             } else if (command instanceof QuantityType) {
178                 switch (channelUID.getId()) {
179                     case DEVICE_CHANNEL_CONFIG_TARGET_HUMIDITY:
180                         int targetHumidity = ((QuantityType<?>) command).intValue();
181                         if (targetHumidity < 30) {
182                             logger.warn("Target Humidity less than 30 - adjusting to 30 as the valid API value");
183                             targetHumidity = 30;
184                         } else if (targetHumidity > 80) {
185                             logger.warn("Target Humidity greater than 80 - adjusting to 80 as the valid API value");
186                             targetHumidity = 80;
187                         }
188
189                         sendV2BypassControlCommand(DEVICE_SET_HUMIDITY_MODE,
190                                 new VeSyncRequestManagedDeviceBypassV2.SetMode(MODE_AUTO), false);
191
192                         sendV2BypassControlCommand(DEVICE_SET_TARGET_HUMIDITY_MODE,
193                                 new VeSyncRequestManagedDeviceBypassV2.SetTargetHumidity(targetHumidity));
194                         break;
195                     case DEVICE_CHANNEL_MIST_LEVEL:
196                         int targetMistLevel = ((QuantityType<?>) command).intValue();
197                         // If more devices have this the hope is it's those with the prefix LUH so the check can
198                         // be simplified, originally devices mapped 1/5/9 to 1/2/3.
199                         if (DEV_FAMILY_DUAL_200S.equals(deviceFamily)) {
200                             if (targetMistLevel < 1) {
201                                 logger.warn("Target Mist Level less than 1 - adjusting to 1 as the valid API value");
202                                 targetMistLevel = 1;
203                             } else if (targetMistLevel > 2) {
204                                 logger.warn("Target Mist Level greater than 2 - adjusting to 2 as the valid API value");
205                                 targetMistLevel = 2;
206                             }
207                         } else {
208                             if (targetMistLevel < 1) {
209                                 logger.warn("Target Mist Level less than 1 - adjusting to 1 as the valid API value");
210                                 targetMistLevel = 1;
211                             } else if (targetMistLevel > 3) {
212                                 logger.warn("Target Mist Level greater than 3 - adjusting to 3 as the valid API value");
213                                 targetMistLevel = 3;
214                             }
215                             // Re-map to what appears to be bitwise encoding of the states
216                             switch (targetMistLevel) {
217                                 case 1:
218                                     targetMistLevel = 1;
219                                     break;
220                                 case 2:
221                                     targetMistLevel = 5;
222                                     break;
223                                 case 3:
224                                     targetMistLevel = 9;
225                                     break;
226                             }
227                         }
228
229                         sendV2BypassControlCommand(DEVICE_SET_HUMIDITY_MODE,
230                                 new VeSyncRequestManagedDeviceBypassV2.SetMode(MODE_MANUAL), false);
231
232                         sendV2BypassControlCommand(DEVICE_SET_VIRTUAL_LEVEL,
233                                 new VeSyncRequestManagedDeviceBypassV2.SetLevelPayload(0, DEVICE_LEVEL_TYPE_MIST,
234                                         targetMistLevel));
235                         break;
236                     case DEVICE_CHANNEL_WARM_LEVEL:
237                         logger.warn("Warm level API is unknown in order to send the command");
238                         break;
239                 }
240             } else if (command instanceof StringType) {
241                 final String targetMode = command.toString().toLowerCase();
242                 switch (channelUID.getId()) {
243                     case DEVICE_CHANNEL_HUMIDIFIER_MODE:
244                         if (!CLASSIC_300S_600S_MODES.contains(targetMode)) {
245                             logger.warn(
246                                     "Humidifier mode command for \"{}\" is not valid in the (Classic300S/600S) API possible options {}",
247                                     command, String.join(",", CLASSIC_300S_NIGHT_LIGHT_MODES));
248                             return;
249                         }
250                         sendV2BypassControlCommand(DEVICE_SET_HUMIDITY_MODE,
251                                 new VeSyncRequestManagedDeviceBypassV2.SetMode(targetMode));
252                         break;
253                     case DEVICE_CHANNEL_AF_NIGHT_LIGHT:
254                         if (!DEV_FAMILY_CLASSIC_300S.equals(deviceFamily) && !DEV_FAMILY_600S.equals(deviceFamily)) {
255                             logger.warn("Humidifier night light is not valid for your device ({}})", deviceFamily);
256                             return;
257                         }
258                         if (!CLASSIC_300S_NIGHT_LIGHT_MODES.contains(targetMode)) {
259                             logger.warn(
260                                     "Humidifier night light mode command for \"{}\" is not valid in the (Classic300S) API possible options {}",
261                                     command, String.join(",", CLASSIC_300S_NIGHT_LIGHT_MODES));
262                             return;
263                         }
264                         int targetValue;
265                         switch (targetMode) {
266                             case MODE_OFF:
267                                 targetValue = 0;
268                                 break;
269                             case MODE_DIM:
270                                 targetValue = 50;
271                                 break;
272                             case MODE_ON:
273                                 targetValue = 100;
274                                 break;
275                             default:
276                                 return; // should never hit
277                         }
278                         sendV2BypassControlCommand(DEVICE_SET_NIGHT_LIGHT_BRIGHTNESS,
279                                 new VeSyncRequestManagedDeviceBypassV2.SetNightLightBrightness(targetValue));
280                 }
281             } else if (command instanceof RefreshType) {
282                 pollForUpdate();
283             } else {
284                 logger.trace("UNKNOWN COMMAND: {} {}", command.getClass().toString(), channelUID);
285             }
286         });
287     }
288
289     @Override
290     protected void pollForDeviceData(final ExpiringCache<String> cachedResponse) {
291         String response;
292         VeSyncV2BypassHumidifierStatus humidifierStatus;
293         synchronized (pollLock) {
294             response = cachedResponse.getValue();
295             boolean cachedDataUsed = response != null;
296             if (response == null) {
297                 logger.trace("Requesting fresh response");
298                 response = sendV2BypassCommand(DEVICE_GET_HUMIDIFIER_STATUS,
299                         new VeSyncRequestManagedDeviceBypassV2.EmptyPayload());
300             } else {
301                 logger.trace("Using cached response {}", response);
302             }
303
304             if (response.equals(EMPTY_STRING)) {
305                 return;
306             }
307
308             humidifierStatus = VeSyncConstants.GSON.fromJson(response, VeSyncV2BypassHumidifierStatus.class);
309
310             if (humidifierStatus == null) {
311                 return;
312             }
313
314             if (!cachedDataUsed) {
315                 cachedResponse.putValue(response);
316             }
317         }
318
319         // Bail and update the status of the thing - it will be updated to online by the next search
320         // that detects it is online.
321         if (humidifierStatus.isMsgDeviceOffline()) {
322             updateStatus(ThingStatus.OFFLINE);
323             return;
324         } else if (humidifierStatus.isMsgSuccess()) {
325             updateStatus(ThingStatus.ONLINE);
326         }
327
328         if (!"0".equals(humidifierStatus.result.getCode())) {
329             logger.warn("Check correct Thing type has been set - API gave a unexpected response for an Air Humidifier");
330             return;
331         }
332
333         final String deviceFamily = getThing().getProperties().get(DEVICE_PROP_DEVICE_FAMILY);
334
335         updateState(DEVICE_CHANNEL_ENABLED, OnOffType.from(humidifierStatus.result.result.enabled));
336         updateState(DEVICE_CHANNEL_DISPLAY_ENABLED, OnOffType.from(humidifierStatus.result.result.display));
337         updateState(DEVICE_CHANNEL_WATER_LACKS, OnOffType.from(humidifierStatus.result.result.waterLacks));
338         updateState(DEVICE_CHANNEL_HUMIDITY_HIGH, OnOffType.from(humidifierStatus.result.result.humidityHigh));
339         updateState(DEVICE_CHANNEL_WATER_TANK_LIFTED, OnOffType.from(humidifierStatus.result.result.waterTankLifted));
340         updateState(DEVICE_CHANNEL_STOP_AT_TARGET,
341                 OnOffType.from(humidifierStatus.result.result.automaticStopReachTarget));
342         updateState(DEVICE_CHANNEL_HUMIDITY,
343                 new QuantityType<>(humidifierStatus.result.result.humidity, Units.PERCENT));
344         updateState(DEVICE_CHANNEL_MIST_LEVEL, new DecimalType(humidifierStatus.result.result.mistLevel));
345         updateState(DEVICE_CHANNEL_HUMIDIFIER_MODE, new StringType(humidifierStatus.result.result.mode));
346
347         // Only the 300S supports nightlight currently of tested devices.
348         if (DEV_FAMILY_CLASSIC_300S.equals(deviceFamily) || DEV_FAMILY_600S.equals(deviceFamily)) {
349             // Map the numeric that only applies to the same modes as the Air Filter 300S series.
350             if (humidifierStatus.result.result.nightLightBrightness == 0) {
351                 updateState(DEVICE_CHANNEL_AF_NIGHT_LIGHT, new StringType(MODE_OFF));
352             } else if (humidifierStatus.result.result.nightLightBrightness == 100) {
353                 updateState(DEVICE_CHANNEL_AF_NIGHT_LIGHT, new StringType(MODE_ON));
354             } else {
355                 updateState(DEVICE_CHANNEL_AF_NIGHT_LIGHT, new StringType(MODE_DIM));
356             }
357         } else if (DEV_FAMILY_600S.equals(deviceFamily) || DEV_FAMILY_OASIS_MIST.equals(deviceFamily)) {
358             updateState(DEVICE_CHANNEL_WARM_ENABLED, OnOffType.from(humidifierStatus.result.result.warnEnabled));
359             updateState(DEVICE_CHANNEL_WARM_LEVEL, new DecimalType(humidifierStatus.result.result.warmLevel));
360         }
361
362         updateState(DEVICE_CHANNEL_CONFIG_TARGET_HUMIDITY,
363                 new QuantityType<>(humidifierStatus.result.result.configuration.autoTargetHumidity, Units.PERCENT));
364     }
365 }