]> git.basschouten.com Git - openhab-addons.git/blob
8a3fb18efdcde740d17179f402e03b2c158f4d16
[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.io.homekit.internal.accessories;
14
15 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.*;
16
17 import java.math.BigDecimal;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.concurrent.CompletableFuture;
21 import java.util.function.BiFunction;
22 import java.util.function.Consumer;
23 import java.util.function.Supplier;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.core.items.GenericItem;
28 import org.openhab.core.items.Item;
29 import org.openhab.core.library.items.ColorItem;
30 import org.openhab.core.library.items.DimmerItem;
31 import org.openhab.core.library.items.NumberItem;
32 import org.openhab.core.library.items.SwitchItem;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.HSBType;
35 import org.openhab.core.library.types.OnOffType;
36 import org.openhab.core.library.types.OpenClosedType;
37 import org.openhab.core.library.types.PercentType;
38 import org.openhab.core.types.State;
39 import org.openhab.core.types.UnDefType;
40 import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
41 import org.openhab.io.homekit.internal.HomekitCharacteristicType;
42 import org.openhab.io.homekit.internal.HomekitCommandType;
43 import org.openhab.io.homekit.internal.HomekitException;
44 import org.openhab.io.homekit.internal.HomekitTaggedItem;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import io.github.hapjava.characteristics.Characteristic;
49 import io.github.hapjava.characteristics.CharacteristicEnum;
50 import io.github.hapjava.characteristics.ExceptionalConsumer;
51 import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
52 import io.github.hapjava.characteristics.impl.airquality.NitrogenDioxideDensityCharacteristic;
53 import io.github.hapjava.characteristics.impl.airquality.OzoneDensityCharacteristic;
54 import io.github.hapjava.characteristics.impl.airquality.PM10DensityCharacteristic;
55 import io.github.hapjava.characteristics.impl.airquality.PM25DensityCharacteristic;
56 import io.github.hapjava.characteristics.impl.airquality.SulphurDioxideDensityCharacteristic;
57 import io.github.hapjava.characteristics.impl.airquality.VOCDensityCharacteristic;
58 import io.github.hapjava.characteristics.impl.audio.VolumeCharacteristic;
59 import io.github.hapjava.characteristics.impl.battery.StatusLowBatteryCharacteristic;
60 import io.github.hapjava.characteristics.impl.battery.StatusLowBatteryEnum;
61 import io.github.hapjava.characteristics.impl.carbondioxidesensor.CarbonDioxideLevelCharacteristic;
62 import io.github.hapjava.characteristics.impl.carbondioxidesensor.CarbonDioxidePeakLevelCharacteristic;
63 import io.github.hapjava.characteristics.impl.carbonmonoxidesensor.CarbonMonoxideLevelCharacteristic;
64 import io.github.hapjava.characteristics.impl.carbonmonoxidesensor.CarbonMonoxidePeakLevelCharacteristic;
65 import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
66 import io.github.hapjava.characteristics.impl.common.ObstructionDetectedCharacteristic;
67 import io.github.hapjava.characteristics.impl.common.StatusActiveCharacteristic;
68 import io.github.hapjava.characteristics.impl.common.StatusFaultCharacteristic;
69 import io.github.hapjava.characteristics.impl.common.StatusFaultEnum;
70 import io.github.hapjava.characteristics.impl.common.StatusTamperedCharacteristic;
71 import io.github.hapjava.characteristics.impl.common.StatusTamperedEnum;
72 import io.github.hapjava.characteristics.impl.fan.CurrentFanStateCharacteristic;
73 import io.github.hapjava.characteristics.impl.fan.CurrentFanStateEnum;
74 import io.github.hapjava.characteristics.impl.fan.LockPhysicalControlsCharacteristic;
75 import io.github.hapjava.characteristics.impl.fan.LockPhysicalControlsEnum;
76 import io.github.hapjava.characteristics.impl.fan.RotationDirectionCharacteristic;
77 import io.github.hapjava.characteristics.impl.fan.RotationDirectionEnum;
78 import io.github.hapjava.characteristics.impl.fan.RotationSpeedCharacteristic;
79 import io.github.hapjava.characteristics.impl.fan.SwingModeCharacteristic;
80 import io.github.hapjava.characteristics.impl.fan.SwingModeEnum;
81 import io.github.hapjava.characteristics.impl.fan.TargetFanStateCharacteristic;
82 import io.github.hapjava.characteristics.impl.fan.TargetFanStateEnum;
83 import io.github.hapjava.characteristics.impl.lightbulb.BrightnessCharacteristic;
84 import io.github.hapjava.characteristics.impl.lightbulb.ColorTemperatureCharacteristic;
85 import io.github.hapjava.characteristics.impl.lightbulb.HueCharacteristic;
86 import io.github.hapjava.characteristics.impl.lightbulb.SaturationCharacteristic;
87 import io.github.hapjava.characteristics.impl.thermostat.CoolingThresholdTemperatureCharacteristic;
88 import io.github.hapjava.characteristics.impl.thermostat.HeatingThresholdTemperatureCharacteristic;
89 import io.github.hapjava.characteristics.impl.valve.RemainingDurationCharacteristic;
90 import io.github.hapjava.characteristics.impl.valve.SetDurationCharacteristic;
91 import io.github.hapjava.characteristics.impl.windowcovering.CurrentHorizontalTiltAngleCharacteristic;
92 import io.github.hapjava.characteristics.impl.windowcovering.CurrentVerticalTiltAngleCharacteristic;
93 import io.github.hapjava.characteristics.impl.windowcovering.HoldPositionCharacteristic;
94 import io.github.hapjava.characteristics.impl.windowcovering.TargetHorizontalTiltAngleCharacteristic;
95 import io.github.hapjava.characteristics.impl.windowcovering.TargetVerticalTiltAngleCharacteristic;
96
97 /**
98  * Creates a optional characteristics .
99  *
100  * @author Eugen Freiter - Initial contribution
101  */
102 @NonNullByDefault
103 public class HomekitCharacteristicFactory {
104     private static final Logger logger = LoggerFactory.getLogger(HomekitCharacteristicFactory.class);
105
106     // List of optional characteristics and corresponding method to create them.
107     private final static Map<HomekitCharacteristicType, BiFunction<HomekitTaggedItem, HomekitAccessoryUpdater, Characteristic>> optional = new HashMap<HomekitCharacteristicType, BiFunction<HomekitTaggedItem, HomekitAccessoryUpdater, Characteristic>>() {
108         {
109             put(NAME, HomekitCharacteristicFactory::createNameCharacteristic);
110             put(BATTERY_LOW_STATUS, HomekitCharacteristicFactory::createStatusLowBatteryCharacteristic);
111             put(FAULT_STATUS, HomekitCharacteristicFactory::createStatusFaultCharacteristic);
112             put(TAMPERED_STATUS, HomekitCharacteristicFactory::createStatusTamperedCharacteristic);
113             put(ACTIVE_STATUS, HomekitCharacteristicFactory::createStatusActiveCharacteristic);
114             put(CARBON_MONOXIDE_LEVEL, HomekitCharacteristicFactory::createCarbonMonoxideLevelCharacteristic);
115             put(CARBON_MONOXIDE_PEAK_LEVEL, HomekitCharacteristicFactory::createCarbonMonoxidePeakLevelCharacteristic);
116             put(CARBON_DIOXIDE_LEVEL, HomekitCharacteristicFactory::createCarbonDioxideLevelCharacteristic);
117             put(CARBON_DIOXIDE_PEAK_LEVEL, HomekitCharacteristicFactory::createCarbonDioxidePeakLevelCharacteristic);
118             put(HOLD_POSITION, HomekitCharacteristicFactory::createHoldPositionCharacteristic);
119             put(OBSTRUCTION_STATUS, HomekitCharacteristicFactory::createObstructionDetectedCharacteristic);
120             put(CURRENT_HORIZONTAL_TILT_ANGLE,
121                     HomekitCharacteristicFactory::createCurrentHorizontalTiltAngleCharacteristic);
122             put(CURRENT_VERTICAL_TILT_ANGLE,
123                     HomekitCharacteristicFactory::createCurrentVerticalTiltAngleCharacteristic);
124             put(TARGET_HORIZONTAL_TILT_ANGLE,
125                     HomekitCharacteristicFactory::createTargetHorizontalTiltAngleCharacteristic);
126             put(TARGET_VERTICAL_TILT_ANGLE, HomekitCharacteristicFactory::createTargetVerticalTiltAngleCharacteristic);
127             put(HUE, HomekitCharacteristicFactory::createHueCharacteristic);
128             put(BRIGHTNESS, HomekitCharacteristicFactory::createBrightnessCharacteristic);
129             put(SATURATION, HomekitCharacteristicFactory::createSaturationCharacteristic);
130             put(COLOR_TEMPERATURE, HomekitCharacteristicFactory::createColorTemperatureCharacteristic);
131             put(CURRENT_FAN_STATE, HomekitCharacteristicFactory::createCurrentFanStateCharacteristic);
132             put(TARGET_FAN_STATE, HomekitCharacteristicFactory::createTargetFanStateCharacteristic);
133             put(ROTATION_DIRECTION, HomekitCharacteristicFactory::createRotationDirectionCharacteristic);
134             put(ROTATION_SPEED, HomekitCharacteristicFactory::createRotationSpeedCharacteristic);
135             put(SWING_MODE, HomekitCharacteristicFactory::createSwingModeCharacteristic);
136             put(LOCK_CONTROL, HomekitCharacteristicFactory::createLockPhysicalControlsCharacteristic);
137             put(DURATION, HomekitCharacteristicFactory::createDurationCharacteristic);
138             put(VOLUME, HomekitCharacteristicFactory::createVolumeCharacteristic);
139             put(COOLING_THRESHOLD_TEMPERATURE, HomekitCharacteristicFactory::createCoolingThresholdCharacteristic);
140             put(HEATING_THRESHOLD_TEMPERATURE, HomekitCharacteristicFactory::createHeatingThresholdCharacteristic);
141             put(REMAINING_DURATION, HomekitCharacteristicFactory::createRemainingDurationCharacteristic);
142             put(OZONE_DENSITY, HomekitCharacteristicFactory::createOzoneDensityCharacteristic);
143             put(NITROGEN_DIOXIDE_DENSITY, HomekitCharacteristicFactory::createNitrogenDioxideDensityCharacteristic);
144             put(SULPHUR_DIOXIDE_DENSITY, HomekitCharacteristicFactory::createSulphurDioxideDensityCharacteristic);
145             put(PM25_DENSITY, HomekitCharacteristicFactory::createPM25DensityCharacteristic);
146             put(PM10_DENSITY, HomekitCharacteristicFactory::createPM10DensityCharacteristic);
147             put(VOC_DENSITY, HomekitCharacteristicFactory::createVOCDensityCharacteristic);
148         }
149     };
150
151     /**
152      * create optional HomeKit characteristic
153      *
154      * @param item corresponding OH item
155      * @param updater update to keep OH item and HomeKit characteristic in sync
156      * @return HomeKit characteristic
157      */
158     public static Characteristic createCharacteristic(HomekitTaggedItem item, HomekitAccessoryUpdater updater)
159             throws HomekitException {
160         final @Nullable HomekitCharacteristicType type = item.getCharacteristicType();
161         logger.trace("CreateCharacteristic, type {} item {}", type, item);
162         if (optional.containsKey(type)) {
163             return optional.get(type).apply(item, updater);
164         }
165         logger.warn("Unsupported optional characteristic. Accessory type {}, characteristic type {}",
166                 item.getAccessoryType(), type);
167         throw new HomekitException("Unsupported optional characteristic. Characteristic type \"" + type + "\"");
168     }
169
170     // METHODS TO CREATE SINGLE CHARACTERISTIC FROM OH ITEM
171
172     // supporting methods
173     private static <T extends CharacteristicEnum> CompletableFuture<T> getEnumFromItem(HomekitTaggedItem item,
174             T offEnum, T onEnum, T defaultEnum) {
175         final State state = item.getItem().getState();
176         if (state instanceof OnOffType) {
177             return CompletableFuture
178                     .completedFuture(state.equals(item.isInverted() ? OnOffType.ON : OnOffType.OFF) ? offEnum : onEnum);
179         } else if (state instanceof OpenClosedType) {
180             return CompletableFuture.completedFuture(
181                     state.equals(item.isInverted() ? OpenClosedType.OPEN : OpenClosedType.CLOSED) ? offEnum : onEnum);
182         } else if (state instanceof DecimalType) {
183             return CompletableFuture.completedFuture(((DecimalType) state).intValue() == 0 ? offEnum : onEnum);
184         } else if (state instanceof UnDefType) {
185             return CompletableFuture.completedFuture(defaultEnum);
186         }
187         logger.warn(
188                 "Item state {} is not supported. Only OnOffType,OpenClosedType and Decimal (0/1) are supported. Ignore item {}",
189                 state, item.getName());
190         return CompletableFuture.completedFuture(defaultEnum);
191     }
192
193     private static void setValueFromEnum(HomekitTaggedItem taggedItem, CharacteristicEnum value,
194             CharacteristicEnum offEnum, CharacteristicEnum onEnum) {
195         if (taggedItem.getItem() instanceof SwitchItem) {
196             if (value.equals(offEnum)) {
197                 ((SwitchItem) taggedItem.getItem()).send(taggedItem.isInverted() ? OnOffType.ON : OnOffType.OFF);
198             } else if (value.equals(onEnum)) {
199                 ((SwitchItem) taggedItem.getItem()).send(taggedItem.isInverted() ? OnOffType.OFF : OnOffType.ON);
200             } else {
201                 logger.warn("Enum value {} is not supported. Only following values are supported: {},{}", value,
202                         offEnum, onEnum);
203             }
204         } else if (taggedItem.getItem() instanceof NumberItem) {
205             ((NumberItem) taggedItem.getItem()).send(new DecimalType(value.getCode()));
206         } else {
207             logger.warn("Item type {} is not supported. Only Switch and Number item types are supported.",
208                     taggedItem.getItem().getType());
209         }
210     }
211
212     private static int getIntFromItem(HomekitTaggedItem taggedItem, int defaultValue) {
213         int value = defaultValue;
214         final State state = taggedItem.getItem().getState();
215         if (state instanceof PercentType) {
216             value = ((PercentType) state).intValue();
217         } else if (state instanceof DecimalType) {
218             value = ((DecimalType) state).intValue();
219         } else if (state instanceof UnDefType) {
220             logger.debug("Item state {} is UNDEF {}. Returning default value {}", state, taggedItem.getName(),
221                     defaultValue);
222         } else {
223             logger.warn(
224                     "Item state {} is not supported for {}. Only PercentType and DecimalType (0/100) are supported.",
225                     state, taggedItem.getName());
226         }
227         return value;
228     }
229
230     /** special method for tilts. it converts percentage to angle */
231     private static int getAngleFromItem(HomekitTaggedItem taggedItem, int defaultValue) {
232         int value = defaultValue;
233         final State state = taggedItem.getItem().getState();
234         if (state instanceof PercentType) {
235             value = (int) ((((PercentType) state).intValue() * 90.0) / 50.0 - 90.0);
236         } else {
237             value = getIntFromItem(taggedItem, defaultValue);
238         }
239         return value;
240     }
241
242     private static Supplier<CompletableFuture<Integer>> getAngleSupplier(HomekitTaggedItem taggedItem,
243             int defaultValue) {
244         return () -> CompletableFuture.completedFuture(getAngleFromItem(taggedItem, defaultValue));
245     }
246
247     private static Supplier<CompletableFuture<Integer>> getIntSupplier(HomekitTaggedItem taggedItem, int defaultValue) {
248         return () -> CompletableFuture.completedFuture(getIntFromItem(taggedItem, defaultValue));
249     }
250
251     private static ExceptionalConsumer<Integer> setIntConsumer(HomekitTaggedItem taggedItem) {
252         return (value) -> {
253             if (taggedItem.getItem() instanceof NumberItem) {
254                 ((NumberItem) taggedItem.getItem()).send(new DecimalType(value));
255             } else {
256                 logger.warn("Item type {} is not supported for {}. Only NumberItem is supported.",
257                         taggedItem.getItem().getType(), taggedItem.getName());
258             }
259         };
260     }
261
262     private static ExceptionalConsumer<Integer> setPercentConsumer(HomekitTaggedItem taggedItem) {
263         return (value) -> {
264             if (taggedItem.getItem() instanceof NumberItem) {
265                 ((NumberItem) taggedItem.getItem()).send(new DecimalType(value));
266             } else if (taggedItem.getItem() instanceof DimmerItem) {
267                 ((DimmerItem) taggedItem.getItem()).send(new PercentType(value));
268             } else {
269                 logger.warn("Item type {} is not supported for {}. Only DimmerItem and NumberItem are supported.",
270                         taggedItem.getItem().getType(), taggedItem.getName());
271             }
272         };
273     }
274
275     private static ExceptionalConsumer<Integer> setAngleConsumer(HomekitTaggedItem taggedItem) {
276         return (value) -> {
277             if (taggedItem.getItem() instanceof NumberItem) {
278                 ((NumberItem) taggedItem.getItem()).send(new DecimalType(value));
279             } else if (taggedItem.getItem() instanceof DimmerItem) {
280                 value = (int) (value * 50.0 / 90.0 + 50.0);
281                 ((DimmerItem) taggedItem.getItem()).send(new PercentType(value));
282             } else {
283                 logger.warn("Item type {} is not supported for {}. Only DimmerItem and NumberItem are supported.",
284                         taggedItem.getItem().getType(), taggedItem.getName());
285             }
286         };
287     }
288
289     private static Supplier<CompletableFuture<Double>> getDoubleSupplier(HomekitTaggedItem taggedItem,
290             double defaultValue) {
291         return () -> {
292             final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
293             return CompletableFuture.completedFuture(value != null ? value.doubleValue() : defaultValue);
294         };
295     }
296
297     private static ExceptionalConsumer<Double> setDoubleConsumer(HomekitTaggedItem taggedItem) {
298         return (value) -> {
299             if (taggedItem.getItem() instanceof NumberItem) {
300                 ((NumberItem) taggedItem.getItem()).send(new DecimalType(value));
301             } else {
302                 logger.warn("Item type {} is not supported for {}. Only Number type is supported.",
303                         taggedItem.getItem().getType(), taggedItem.getName());
304             }
305         };
306     }
307
308     protected static Consumer<HomekitCharacteristicChangeCallback> getSubscriber(HomekitTaggedItem taggedItem,
309             HomekitCharacteristicType key, HomekitAccessoryUpdater updater) {
310         return (callback) -> updater.subscribe((GenericItem) taggedItem.getItem(), key.getTag(), callback);
311     }
312
313     protected static Runnable getUnsubscriber(HomekitTaggedItem taggedItem, HomekitCharacteristicType key,
314             HomekitAccessoryUpdater updater) {
315         return () -> updater.unsubscribe((GenericItem) taggedItem.getItem(), key.getTag());
316     }
317
318     // create method for characteristic
319     private static StatusLowBatteryCharacteristic createStatusLowBatteryCharacteristic(HomekitTaggedItem taggedItem,
320             HomekitAccessoryUpdater updater) {
321         return new StatusLowBatteryCharacteristic(
322                 () -> getEnumFromItem(taggedItem, StatusLowBatteryEnum.NORMAL, StatusLowBatteryEnum.LOW,
323                         StatusLowBatteryEnum.NORMAL),
324                 getSubscriber(taggedItem, BATTERY_LOW_STATUS, updater),
325                 getUnsubscriber(taggedItem, BATTERY_LOW_STATUS, updater));
326     }
327
328     private static StatusFaultCharacteristic createStatusFaultCharacteristic(HomekitTaggedItem taggedItem,
329             HomekitAccessoryUpdater updater) {
330         return new StatusFaultCharacteristic(
331                 () -> getEnumFromItem(taggedItem, StatusFaultEnum.NO_FAULT, StatusFaultEnum.GENERAL_FAULT,
332                         StatusFaultEnum.NO_FAULT),
333                 getSubscriber(taggedItem, FAULT_STATUS, updater), getUnsubscriber(taggedItem, FAULT_STATUS, updater));
334     }
335
336     private static StatusTamperedCharacteristic createStatusTamperedCharacteristic(HomekitTaggedItem taggedItem,
337             HomekitAccessoryUpdater updater) {
338         return new StatusTamperedCharacteristic(
339                 () -> getEnumFromItem(taggedItem, StatusTamperedEnum.NOT_TAMPERED, StatusTamperedEnum.TAMPERED,
340                         StatusTamperedEnum.NOT_TAMPERED),
341                 getSubscriber(taggedItem, TAMPERED_STATUS, updater),
342                 getUnsubscriber(taggedItem, TAMPERED_STATUS, updater));
343     }
344
345     private static ObstructionDetectedCharacteristic createObstructionDetectedCharacteristic(
346             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
347         return new ObstructionDetectedCharacteristic(
348                 () -> CompletableFuture.completedFuture(taggedItem.getItem().getState() == OnOffType.ON
349                         || taggedItem.getItem().getState() == OpenClosedType.OPEN),
350                 getSubscriber(taggedItem, OBSTRUCTION_STATUS, updater),
351                 getUnsubscriber(taggedItem, OBSTRUCTION_STATUS, updater));
352     }
353
354     private static StatusActiveCharacteristic createStatusActiveCharacteristic(HomekitTaggedItem taggedItem,
355             HomekitAccessoryUpdater updater) {
356         return new StatusActiveCharacteristic(
357                 () -> CompletableFuture.completedFuture(taggedItem.getItem().getState() == OnOffType.ON
358                         || taggedItem.getItem().getState() == OpenClosedType.OPEN),
359                 getSubscriber(taggedItem, ACTIVE_STATUS, updater), getUnsubscriber(taggedItem, ACTIVE_STATUS, updater));
360     }
361
362     private static NameCharacteristic createNameCharacteristic(HomekitTaggedItem taggedItem,
363             HomekitAccessoryUpdater updater) {
364         return new NameCharacteristic(() -> {
365             final State state = taggedItem.getItem().getState();
366             return CompletableFuture.completedFuture(state instanceof UnDefType ? "" : state.toString());
367         });
368     }
369
370     private static HoldPositionCharacteristic createHoldPositionCharacteristic(HomekitTaggedItem taggedItem,
371             HomekitAccessoryUpdater updater) {
372         return new HoldPositionCharacteristic(value -> ((SwitchItem) taggedItem.getItem()).send(OnOffType.from(value)));
373     }
374
375     private static CarbonMonoxideLevelCharacteristic createCarbonMonoxideLevelCharacteristic(
376             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
377         return new CarbonMonoxideLevelCharacteristic(
378                 getDoubleSupplier(taggedItem,
379                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
380                                 CarbonMonoxideLevelCharacteristic.DEFAULT_MIN_VALUE)),
381                 getSubscriber(taggedItem, CARBON_DIOXIDE_LEVEL, updater),
382                 getUnsubscriber(taggedItem, CARBON_DIOXIDE_LEVEL, updater));
383     }
384
385     private static CarbonMonoxidePeakLevelCharacteristic createCarbonMonoxidePeakLevelCharacteristic(
386             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
387         return new CarbonMonoxidePeakLevelCharacteristic(
388                 getDoubleSupplier(taggedItem,
389                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
390                                 CarbonMonoxidePeakLevelCharacteristic.DEFAULT_MIN_VALUE)),
391                 getSubscriber(taggedItem, CARBON_DIOXIDE_PEAK_LEVEL, updater),
392                 getUnsubscriber(taggedItem, CARBON_DIOXIDE_PEAK_LEVEL, updater));
393     }
394
395     private static CarbonDioxideLevelCharacteristic createCarbonDioxideLevelCharacteristic(HomekitTaggedItem taggedItem,
396             HomekitAccessoryUpdater updater) {
397         return new CarbonDioxideLevelCharacteristic(
398                 getDoubleSupplier(taggedItem,
399                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
400                                 CarbonDioxideLevelCharacteristic.DEFAULT_MIN_VALUE)),
401                 getSubscriber(taggedItem, CARBON_MONOXIDE_LEVEL, updater),
402                 getUnsubscriber(taggedItem, CARBON_MONOXIDE_LEVEL, updater));
403     }
404
405     private static CarbonDioxidePeakLevelCharacteristic createCarbonDioxidePeakLevelCharacteristic(
406             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
407         return new CarbonDioxidePeakLevelCharacteristic(
408                 getDoubleSupplier(taggedItem,
409                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
410                                 CarbonDioxidePeakLevelCharacteristic.DEFAULT_MIN_VALUE)),
411                 getSubscriber(taggedItem, CARBON_MONOXIDE_PEAK_LEVEL, updater),
412                 getUnsubscriber(taggedItem, CARBON_MONOXIDE_PEAK_LEVEL, updater));
413     }
414
415     private static CurrentHorizontalTiltAngleCharacteristic createCurrentHorizontalTiltAngleCharacteristic(
416             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
417         return new CurrentHorizontalTiltAngleCharacteristic(getAngleSupplier(taggedItem, 0),
418                 getSubscriber(taggedItem, CURRENT_HORIZONTAL_TILT_ANGLE, updater),
419                 getUnsubscriber(taggedItem, CURRENT_HORIZONTAL_TILT_ANGLE, updater));
420     }
421
422     private static CurrentVerticalTiltAngleCharacteristic createCurrentVerticalTiltAngleCharacteristic(
423             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
424         return new CurrentVerticalTiltAngleCharacteristic(getAngleSupplier(taggedItem, 0),
425                 getSubscriber(taggedItem, CURRENT_VERTICAL_TILT_ANGLE, updater),
426                 getUnsubscriber(taggedItem, CURRENT_VERTICAL_TILT_ANGLE, updater));
427     }
428
429     private static TargetHorizontalTiltAngleCharacteristic createTargetHorizontalTiltAngleCharacteristic(
430             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
431         return new TargetHorizontalTiltAngleCharacteristic(getAngleSupplier(taggedItem, 0),
432                 setAngleConsumer(taggedItem), getSubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater),
433                 getUnsubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater));
434     }
435
436     private static TargetVerticalTiltAngleCharacteristic createTargetVerticalTiltAngleCharacteristic(
437             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
438         return new TargetVerticalTiltAngleCharacteristic(getAngleSupplier(taggedItem, 0), setAngleConsumer(taggedItem),
439                 getSubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater),
440                 getUnsubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater));
441     }
442
443     private static HueCharacteristic createHueCharacteristic(HomekitTaggedItem taggedItem,
444             HomekitAccessoryUpdater updater) {
445         return new HueCharacteristic(() -> {
446             double value = 0.0;
447             State state = taggedItem.getItem().getState();
448             if (state instanceof HSBType) {
449                 value = ((HSBType) state).getHue().doubleValue();
450             }
451             return CompletableFuture.completedFuture(value);
452         }, (hue) -> {
453             if (taggedItem.getItem() instanceof ColorItem) {
454                 taggedItem.sendCommandProxy(HomekitCommandType.HUE_COMMAND, new DecimalType(hue));
455             } else {
456                 logger.warn("Item type {} is not supported for {}. Only Color type is supported.",
457                         taggedItem.getItem().getType(), taggedItem.getName());
458             }
459         }, getSubscriber(taggedItem, HUE, updater), getUnsubscriber(taggedItem, HUE, updater));
460     }
461
462     private static BrightnessCharacteristic createBrightnessCharacteristic(HomekitTaggedItem taggedItem,
463             HomekitAccessoryUpdater updater) {
464         return new BrightnessCharacteristic(() -> {
465             int value = 0;
466             final State state = taggedItem.getItem().getState();
467             if (state instanceof HSBType) {
468                 value = ((HSBType) state).getBrightness().intValue();
469             } else if (state instanceof PercentType) {
470                 value = ((PercentType) state).intValue();
471             }
472             return CompletableFuture.completedFuture(value);
473         }, (brightness) -> {
474             final Item item = taggedItem.getItem();
475             if (item instanceof DimmerItem) {
476                 taggedItem.sendCommandProxy(HomekitCommandType.BRIGHTNESS_COMMAND, new PercentType(brightness));
477             } else {
478                 logger.warn("Item type {} is not supported for {}. Only ColorItem and DimmerItem are supported.",
479                         item.getType(), taggedItem.getName());
480             }
481         }, getSubscriber(taggedItem, BRIGHTNESS, updater), getUnsubscriber(taggedItem, BRIGHTNESS, updater));
482     }
483
484     private static SaturationCharacteristic createSaturationCharacteristic(HomekitTaggedItem taggedItem,
485             HomekitAccessoryUpdater updater) {
486         return new SaturationCharacteristic(() -> {
487             double value = 0.0;
488             State state = taggedItem.getItem().getState();
489             if (state instanceof HSBType) {
490                 value = ((HSBType) state).getSaturation().doubleValue();
491             } else if (state instanceof PercentType) {
492                 value = ((PercentType) state).doubleValue();
493             }
494             return CompletableFuture.completedFuture(value);
495         }, (saturation) -> {
496             if (taggedItem.getItem() instanceof ColorItem) {
497                 taggedItem.sendCommandProxy(HomekitCommandType.SATURATION_COMMAND,
498                         new PercentType(saturation.intValue()));
499             } else {
500                 logger.warn("Item type {} is not supported for {}. Only Color type is supported.",
501                         taggedItem.getItem().getType(), taggedItem.getName());
502             }
503         }, getSubscriber(taggedItem, SATURATION, updater), getUnsubscriber(taggedItem, SATURATION, updater));
504     }
505
506     private static ColorTemperatureCharacteristic createColorTemperatureCharacteristic(HomekitTaggedItem taggedItem,
507             HomekitAccessoryUpdater updater) {
508         int minValue = taggedItem.getConfigurationAsInt(HomekitTaggedItem.MIN_VALUE,
509                 ColorTemperatureCharacteristic.DEFAULT_MIN_VALUE);
510         return new ColorTemperatureCharacteristic(minValue,
511                 taggedItem.getConfigurationAsInt(HomekitTaggedItem.MAX_VALUE,
512                         ColorTemperatureCharacteristic.DEFAULT_MAX_VALUE),
513                 getIntSupplier(taggedItem, minValue), setIntConsumer(taggedItem),
514                 getSubscriber(taggedItem, COLOR_TEMPERATURE, updater),
515                 getUnsubscriber(taggedItem, COLOR_TEMPERATURE, updater));
516     }
517
518     private static CurrentFanStateCharacteristic createCurrentFanStateCharacteristic(HomekitTaggedItem taggedItem,
519             HomekitAccessoryUpdater updater) {
520         return new CurrentFanStateCharacteristic(() -> {
521             final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
522             @Nullable
523             CurrentFanStateEnum currentFanStateEnum = value != null ? CurrentFanStateEnum.fromCode(value.intValue())
524                     : null;
525             if (currentFanStateEnum == null) {
526                 currentFanStateEnum = CurrentFanStateEnum.INACTIVE;
527             }
528             return CompletableFuture.completedFuture(currentFanStateEnum);
529         }, getSubscriber(taggedItem, CURRENT_FAN_STATE, updater),
530                 getUnsubscriber(taggedItem, CURRENT_FAN_STATE, updater));
531     }
532
533     private static TargetFanStateCharacteristic createTargetFanStateCharacteristic(HomekitTaggedItem taggedItem,
534             HomekitAccessoryUpdater updater) {
535         return new TargetFanStateCharacteristic(() -> {
536             final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
537             @Nullable
538             TargetFanStateEnum targetFanStateEnum = value != null ? TargetFanStateEnum.fromCode(value.intValue())
539                     : null;
540             if (targetFanStateEnum == null) {
541                 targetFanStateEnum = TargetFanStateEnum.AUTO;
542             }
543             return CompletableFuture.completedFuture(targetFanStateEnum);
544         }, (targetState) -> {
545             if (taggedItem.getItem() instanceof NumberItem) {
546                 ((NumberItem) taggedItem.getItem()).send(new DecimalType(targetState.getCode()));
547             } else {
548                 logger.warn("Item type {} is not supported for {}. Only Number type is supported.",
549                         taggedItem.getItem().getType(), taggedItem.getName());
550             }
551         }, getSubscriber(taggedItem, TARGET_FAN_STATE, updater),
552                 getUnsubscriber(taggedItem, TARGET_FAN_STATE, updater));
553     }
554
555     private static RotationDirectionCharacteristic createRotationDirectionCharacteristic(HomekitTaggedItem taggedItem,
556             HomekitAccessoryUpdater updater) {
557         return new RotationDirectionCharacteristic(
558                 () -> getEnumFromItem(taggedItem, RotationDirectionEnum.CLOCKWISE,
559                         RotationDirectionEnum.COUNTER_CLOCKWISE, RotationDirectionEnum.CLOCKWISE),
560                 (value) -> setValueFromEnum(taggedItem, value, RotationDirectionEnum.CLOCKWISE,
561                         RotationDirectionEnum.COUNTER_CLOCKWISE),
562                 getSubscriber(taggedItem, ROTATION_DIRECTION, updater),
563                 getUnsubscriber(taggedItem, ROTATION_DIRECTION, updater));
564     }
565
566     private static SwingModeCharacteristic createSwingModeCharacteristic(HomekitTaggedItem taggedItem,
567             HomekitAccessoryUpdater updater) {
568         return new SwingModeCharacteristic(
569                 () -> getEnumFromItem(taggedItem, SwingModeEnum.SWING_DISABLED, SwingModeEnum.SWING_ENABLED,
570                         SwingModeEnum.SWING_DISABLED),
571                 (value) -> setValueFromEnum(taggedItem, value, SwingModeEnum.SWING_DISABLED,
572                         SwingModeEnum.SWING_ENABLED),
573                 getSubscriber(taggedItem, SWING_MODE, updater), getUnsubscriber(taggedItem, SWING_MODE, updater));
574     }
575
576     private static LockPhysicalControlsCharacteristic createLockPhysicalControlsCharacteristic(
577             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
578         return new LockPhysicalControlsCharacteristic(
579                 () -> getEnumFromItem(taggedItem, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED,
580                         LockPhysicalControlsEnum.CONTROL_LOCK_ENABLED, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED),
581                 (value) -> setValueFromEnum(taggedItem, value, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED,
582                         LockPhysicalControlsEnum.CONTROL_LOCK_ENABLED),
583                 getSubscriber(taggedItem, LOCK_CONTROL, updater), getUnsubscriber(taggedItem, LOCK_CONTROL, updater));
584     }
585
586     private static RotationSpeedCharacteristic createRotationSpeedCharacteristic(HomekitTaggedItem item,
587             HomekitAccessoryUpdater updater) {
588         return new RotationSpeedCharacteristic(getIntSupplier(item, 0), setPercentConsumer(item),
589                 getSubscriber(item, ROTATION_SPEED, updater), getUnsubscriber(item, ROTATION_SPEED, updater));
590     }
591
592     private static SetDurationCharacteristic createDurationCharacteristic(HomekitTaggedItem taggedItem,
593             HomekitAccessoryUpdater updater) {
594         return new SetDurationCharacteristic(() -> {
595             int value = getIntFromItem(taggedItem, 0);
596             final @Nullable Map<String, Object> itemConfiguration = taggedItem.getConfiguration();
597             if ((value == 0) && (itemConfiguration != null)) { // check for default duration
598                 final Object duration = itemConfiguration.get(HomekitValveImpl.CONFIG_DEFAULT_DURATION);
599                 if (duration instanceof BigDecimal) {
600                     value = ((BigDecimal) duration).intValue();
601                     if (taggedItem.getItem() instanceof NumberItem) {
602                         ((NumberItem) taggedItem.getItem()).setState(new DecimalType(value));
603                     }
604                 }
605             }
606             return CompletableFuture.completedFuture(value);
607         }, setIntConsumer(taggedItem), getSubscriber(taggedItem, DURATION, updater),
608                 getUnsubscriber(taggedItem, DURATION, updater));
609     }
610
611     private static RemainingDurationCharacteristic createRemainingDurationCharacteristic(HomekitTaggedItem taggedItem,
612             HomekitAccessoryUpdater updater) {
613         return new RemainingDurationCharacteristic(getIntSupplier(taggedItem, 0),
614                 getSubscriber(taggedItem, REMAINING_DURATION, updater),
615                 getUnsubscriber(taggedItem, REMAINING_DURATION, updater));
616     }
617
618     private static VolumeCharacteristic createVolumeCharacteristic(HomekitTaggedItem taggedItem,
619             HomekitAccessoryUpdater updater) {
620         return new VolumeCharacteristic(getIntSupplier(taggedItem, 0),
621                 (volume) -> ((NumberItem) taggedItem.getItem()).send(new DecimalType(volume)),
622                 getSubscriber(taggedItem, DURATION, updater), getUnsubscriber(taggedItem, DURATION, updater));
623     }
624
625     private static CoolingThresholdTemperatureCharacteristic createCoolingThresholdCharacteristic(
626             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
627         return new CoolingThresholdTemperatureCharacteristic(
628                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
629                         CoolingThresholdTemperatureCharacteristic.DEFAULT_MIN_VALUE),
630                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MAX_VALUE,
631                         CoolingThresholdTemperatureCharacteristic.DEFAULT_MAX_VALUE),
632                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.STEP,
633                         CoolingThresholdTemperatureCharacteristic.DEFAULT_STEP),
634                 getDoubleSupplier(taggedItem,
635                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
636                                 CoolingThresholdTemperatureCharacteristic.DEFAULT_MIN_VALUE)),
637                 setDoubleConsumer(taggedItem), getSubscriber(taggedItem, COOLING_THRESHOLD_TEMPERATURE, updater),
638                 getUnsubscriber(taggedItem, COOLING_THRESHOLD_TEMPERATURE, updater));
639     }
640
641     private static HeatingThresholdTemperatureCharacteristic createHeatingThresholdCharacteristic(
642             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
643         return new HeatingThresholdTemperatureCharacteristic(
644                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
645                         HeatingThresholdTemperatureCharacteristic.DEFAULT_MIN_VALUE),
646                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MAX_VALUE,
647                         HeatingThresholdTemperatureCharacteristic.DEFAULT_MAX_VALUE),
648                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.STEP,
649                         HeatingThresholdTemperatureCharacteristic.DEFAULT_STEP),
650                 getDoubleSupplier(taggedItem,
651                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
652                                 HeatingThresholdTemperatureCharacteristic.DEFAULT_MIN_VALUE)),
653                 setDoubleConsumer(taggedItem), getSubscriber(taggedItem, HEATING_THRESHOLD_TEMPERATURE, updater),
654                 getUnsubscriber(taggedItem, HEATING_THRESHOLD_TEMPERATURE, updater));
655     }
656
657     private static OzoneDensityCharacteristic createOzoneDensityCharacteristic(final HomekitTaggedItem taggedItem,
658             HomekitAccessoryUpdater updater) {
659         return new OzoneDensityCharacteristic(
660                 getDoubleSupplier(taggedItem,
661                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
662                                 OzoneDensityCharacteristic.DEFAULT_MIN_VALUE)),
663                 getSubscriber(taggedItem, OZONE_DENSITY, updater), getUnsubscriber(taggedItem, OZONE_DENSITY, updater));
664     }
665
666     private static NitrogenDioxideDensityCharacteristic createNitrogenDioxideDensityCharacteristic(
667             final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
668         return new NitrogenDioxideDensityCharacteristic(
669                 getDoubleSupplier(taggedItem,
670                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
671                                 NitrogenDioxideDensityCharacteristic.DEFAULT_MIN_VALUE)),
672                 getSubscriber(taggedItem, NITROGEN_DIOXIDE_DENSITY, updater),
673                 getUnsubscriber(taggedItem, NITROGEN_DIOXIDE_DENSITY, updater));
674     }
675
676     private static SulphurDioxideDensityCharacteristic createSulphurDioxideDensityCharacteristic(
677             final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
678         return new SulphurDioxideDensityCharacteristic(
679                 getDoubleSupplier(taggedItem,
680                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
681                                 SulphurDioxideDensityCharacteristic.DEFAULT_MIN_VALUE)),
682                 getSubscriber(taggedItem, SULPHUR_DIOXIDE_DENSITY, updater),
683                 getUnsubscriber(taggedItem, SULPHUR_DIOXIDE_DENSITY, updater));
684     }
685
686     private static PM25DensityCharacteristic createPM25DensityCharacteristic(final HomekitTaggedItem taggedItem,
687             HomekitAccessoryUpdater updater) {
688         return new PM25DensityCharacteristic(
689                 getDoubleSupplier(taggedItem,
690                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
691                                 PM25DensityCharacteristic.DEFAULT_MIN_VALUE)),
692                 getSubscriber(taggedItem, PM25_DENSITY, updater), getUnsubscriber(taggedItem, PM25_DENSITY, updater));
693     }
694
695     private static PM10DensityCharacteristic createPM10DensityCharacteristic(final HomekitTaggedItem taggedItem,
696             HomekitAccessoryUpdater updater) {
697         return new PM10DensityCharacteristic(
698                 getDoubleSupplier(taggedItem,
699                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
700                                 PM10DensityCharacteristic.DEFAULT_MIN_VALUE)),
701                 getSubscriber(taggedItem, PM10_DENSITY, updater), getUnsubscriber(taggedItem, PM10_DENSITY, updater));
702     }
703
704     private static VOCDensityCharacteristic createVOCDensityCharacteristic(final HomekitTaggedItem taggedItem,
705             HomekitAccessoryUpdater updater) {
706         return new VOCDensityCharacteristic(
707                 getDoubleSupplier(taggedItem,
708                         taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
709                                 VOCDensityCharacteristic.DEFAULT_MIN_VALUE)),
710                 getSubscriber(taggedItem, VOC_DENSITY, updater), getUnsubscriber(taggedItem, VOC_DENSITY, updater));
711     }
712 }