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