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