]> git.basschouten.com Git - openhab-addons.git/blob
1315cda84c10f6a913578036e667efbf5a2b45c7
[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.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         return () -> {
291             final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
292             return CompletableFuture.completedFuture(value != null ? value.doubleValue() : 0.0);
293         };
294     }
295
296     private static ExceptionalConsumer<Double> setDoubleConsumer(HomekitTaggedItem taggedItem) {
297         return (value) -> {
298             if (taggedItem.getItem() instanceof NumberItem) {
299                 ((NumberItem) taggedItem.getItem()).send(new DecimalType(value));
300             } else {
301                 logger.warn("Item type {} is not supported for {}. Only Number type is supported.",
302                         taggedItem.getItem().getType(), taggedItem.getName());
303             }
304         };
305     }
306
307     protected static Consumer<HomekitCharacteristicChangeCallback> getSubscriber(HomekitTaggedItem taggedItem,
308             HomekitCharacteristicType key, HomekitAccessoryUpdater updater) {
309         return (callback) -> updater.subscribe((GenericItem) taggedItem.getItem(), key.getTag(), callback);
310     }
311
312     protected static Runnable getUnsubscriber(HomekitTaggedItem taggedItem, HomekitCharacteristicType key,
313             HomekitAccessoryUpdater updater) {
314         return () -> updater.unsubscribe((GenericItem) taggedItem.getItem(), key.getTag());
315     }
316
317     // create method for characteristic
318     private static StatusLowBatteryCharacteristic createStatusLowBatteryCharacteristic(HomekitTaggedItem taggedItem,
319             HomekitAccessoryUpdater updater) {
320         return new StatusLowBatteryCharacteristic(
321                 () -> getEnumFromItem(taggedItem, StatusLowBatteryEnum.NORMAL, StatusLowBatteryEnum.LOW,
322                         StatusLowBatteryEnum.NORMAL),
323                 getSubscriber(taggedItem, BATTERY_LOW_STATUS, updater),
324                 getUnsubscriber(taggedItem, BATTERY_LOW_STATUS, updater));
325     }
326
327     private static StatusFaultCharacteristic createStatusFaultCharacteristic(HomekitTaggedItem taggedItem,
328             HomekitAccessoryUpdater updater) {
329         return new StatusFaultCharacteristic(
330                 () -> getEnumFromItem(taggedItem, StatusFaultEnum.NO_FAULT, StatusFaultEnum.GENERAL_FAULT,
331                         StatusFaultEnum.NO_FAULT),
332                 getSubscriber(taggedItem, FAULT_STATUS, updater), getUnsubscriber(taggedItem, FAULT_STATUS, updater));
333     }
334
335     private static StatusTamperedCharacteristic createStatusTamperedCharacteristic(HomekitTaggedItem taggedItem,
336             HomekitAccessoryUpdater updater) {
337         return new StatusTamperedCharacteristic(
338                 () -> getEnumFromItem(taggedItem, StatusTamperedEnum.NOT_TAMPERED, StatusTamperedEnum.TAMPERED,
339                         StatusTamperedEnum.NOT_TAMPERED),
340                 getSubscriber(taggedItem, TAMPERED_STATUS, updater),
341                 getUnsubscriber(taggedItem, TAMPERED_STATUS, updater));
342     }
343
344     private static ObstructionDetectedCharacteristic createObstructionDetectedCharacteristic(
345             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
346         return new ObstructionDetectedCharacteristic(
347                 () -> CompletableFuture.completedFuture(taggedItem.getItem().getState() == OnOffType.ON
348                         || taggedItem.getItem().getState() == OpenClosedType.OPEN),
349                 getSubscriber(taggedItem, OBSTRUCTION_STATUS, updater),
350                 getUnsubscriber(taggedItem, OBSTRUCTION_STATUS, updater));
351     }
352
353     private static StatusActiveCharacteristic createStatusActiveCharacteristic(HomekitTaggedItem taggedItem,
354             HomekitAccessoryUpdater updater) {
355         return new StatusActiveCharacteristic(
356                 () -> CompletableFuture.completedFuture(taggedItem.getItem().getState() == OnOffType.ON
357                         || taggedItem.getItem().getState() == OpenClosedType.OPEN),
358                 getSubscriber(taggedItem, ACTIVE_STATUS, updater), getUnsubscriber(taggedItem, ACTIVE_STATUS, updater));
359     }
360
361     private static NameCharacteristic createNameCharacteristic(HomekitTaggedItem taggedItem,
362             HomekitAccessoryUpdater updater) {
363         return new NameCharacteristic(() -> {
364             final State state = taggedItem.getItem().getState();
365             return CompletableFuture.completedFuture(state instanceof UnDefType ? "" : state.toString());
366         });
367     }
368
369     private static HoldPositionCharacteristic createHoldPositionCharacteristic(HomekitTaggedItem taggedItem,
370             HomekitAccessoryUpdater updater) {
371         return new HoldPositionCharacteristic(value -> ((SwitchItem) taggedItem.getItem()).send(OnOffType.from(value)));
372     }
373
374     private static CarbonMonoxideLevelCharacteristic createCarbonMonoxideLevelCharacteristic(
375             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
376         return new CarbonMonoxideLevelCharacteristic(getDoubleSupplier(taggedItem),
377                 getSubscriber(taggedItem, CARBON_DIOXIDE_LEVEL, updater),
378                 getUnsubscriber(taggedItem, CARBON_DIOXIDE_LEVEL, updater));
379     }
380
381     private static CarbonMonoxidePeakLevelCharacteristic createCarbonMonoxidePeakLevelCharacteristic(
382             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
383         return new CarbonMonoxidePeakLevelCharacteristic(getDoubleSupplier(taggedItem),
384                 getSubscriber(taggedItem, CARBON_DIOXIDE_PEAK_LEVEL, updater),
385                 getUnsubscriber(taggedItem, CARBON_DIOXIDE_PEAK_LEVEL, updater));
386     }
387
388     private static CarbonDioxideLevelCharacteristic createCarbonDioxideLevelCharacteristic(HomekitTaggedItem taggedItem,
389             HomekitAccessoryUpdater updater) {
390         return new CarbonDioxideLevelCharacteristic(getDoubleSupplier(taggedItem),
391                 getSubscriber(taggedItem, CARBON_MONOXIDE_LEVEL, updater),
392                 getUnsubscriber(taggedItem, CARBON_MONOXIDE_LEVEL, updater));
393     }
394
395     private static CarbonDioxidePeakLevelCharacteristic createCarbonDioxidePeakLevelCharacteristic(
396             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
397         return new CarbonDioxidePeakLevelCharacteristic(getDoubleSupplier(taggedItem),
398                 getSubscriber(taggedItem, CARBON_MONOXIDE_PEAK_LEVEL, updater),
399                 getUnsubscriber(taggedItem, CARBON_MONOXIDE_PEAK_LEVEL, updater));
400     }
401
402     private static CurrentHorizontalTiltAngleCharacteristic createCurrentHorizontalTiltAngleCharacteristic(
403             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
404         return new CurrentHorizontalTiltAngleCharacteristic(getAngleSupplier(taggedItem, 0),
405                 getSubscriber(taggedItem, CURRENT_HORIZONTAL_TILT_ANGLE, updater),
406                 getUnsubscriber(taggedItem, CURRENT_HORIZONTAL_TILT_ANGLE, updater));
407     }
408
409     private static CurrentVerticalTiltAngleCharacteristic createCurrentVerticalTiltAngleCharacteristic(
410             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
411         return new CurrentVerticalTiltAngleCharacteristic(getAngleSupplier(taggedItem, 0),
412                 getSubscriber(taggedItem, CURRENT_VERTICAL_TILT_ANGLE, updater),
413                 getUnsubscriber(taggedItem, CURRENT_VERTICAL_TILT_ANGLE, updater));
414     }
415
416     private static TargetHorizontalTiltAngleCharacteristic createTargetHorizontalTiltAngleCharacteristic(
417             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
418         return new TargetHorizontalTiltAngleCharacteristic(getAngleSupplier(taggedItem, 0),
419                 setAngleConsumer(taggedItem), getSubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater),
420                 getUnsubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater));
421     }
422
423     private static TargetVerticalTiltAngleCharacteristic createTargetVerticalTiltAngleCharacteristic(
424             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
425         return new TargetVerticalTiltAngleCharacteristic(getAngleSupplier(taggedItem, 0), setAngleConsumer(taggedItem),
426                 getSubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater),
427                 getUnsubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater));
428     }
429
430     private static HueCharacteristic createHueCharacteristic(HomekitTaggedItem taggedItem,
431             HomekitAccessoryUpdater updater) {
432         return new HueCharacteristic(() -> {
433             double value = 0.0;
434             State state = taggedItem.getItem().getState();
435             if (state instanceof HSBType) {
436                 value = ((HSBType) state).getHue().doubleValue();
437             }
438             return CompletableFuture.completedFuture(value);
439         }, (hue) -> {
440             if (taggedItem.getItem() instanceof ColorItem) {
441                 taggedItem.sendCommandProxy(HomekitCommandType.HUE_COMMAND, new DecimalType(hue));
442             } else {
443                 logger.warn("Item type {} is not supported for {}. Only Color type is supported.",
444                         taggedItem.getItem().getType(), taggedItem.getName());
445             }
446         }, getSubscriber(taggedItem, HUE, updater), getUnsubscriber(taggedItem, HUE, updater));
447     }
448
449     private static BrightnessCharacteristic createBrightnessCharacteristic(HomekitTaggedItem taggedItem,
450             HomekitAccessoryUpdater updater) {
451         return new BrightnessCharacteristic(() -> {
452             int value = 0;
453             final State state = taggedItem.getItem().getState();
454             if (state instanceof HSBType) {
455                 value = ((HSBType) state).getBrightness().intValue();
456             } else if (state instanceof PercentType) {
457                 value = ((PercentType) state).intValue();
458             }
459             return CompletableFuture.completedFuture(value);
460         }, (brightness) -> {
461             final Item item = taggedItem.getItem();
462             if (item instanceof DimmerItem) {
463                 taggedItem.sendCommandProxy(HomekitCommandType.BRIGHTNESS_COMMAND, new PercentType(brightness));
464             } else {
465                 logger.warn("Item type {} is not supported for {}. Only ColorItem and DimmerItem are supported.",
466                         item.getType(), taggedItem.getName());
467             }
468         }, getSubscriber(taggedItem, BRIGHTNESS, updater), getUnsubscriber(taggedItem, BRIGHTNESS, updater));
469     }
470
471     private static SaturationCharacteristic createSaturationCharacteristic(HomekitTaggedItem taggedItem,
472             HomekitAccessoryUpdater updater) {
473         return new SaturationCharacteristic(() -> {
474             double value = 0.0;
475             State state = taggedItem.getItem().getState();
476             if (state instanceof HSBType) {
477                 value = ((HSBType) state).getSaturation().doubleValue();
478             } else if (state instanceof PercentType) {
479                 value = ((PercentType) state).doubleValue();
480             }
481             return CompletableFuture.completedFuture(value);
482         }, (saturation) -> {
483             if (taggedItem.getItem() instanceof ColorItem) {
484                 taggedItem.sendCommandProxy(HomekitCommandType.SATURATION_COMMAND,
485                         new PercentType(saturation.intValue()));
486             } else {
487                 logger.warn("Item type {} is not supported for {}. Only Color type is supported.",
488                         taggedItem.getItem().getType(), taggedItem.getName());
489             }
490         }, getSubscriber(taggedItem, SATURATION, updater), getUnsubscriber(taggedItem, SATURATION, updater));
491     }
492
493     private static ColorTemperatureCharacteristic createColorTemperatureCharacteristic(HomekitTaggedItem taggedItem,
494             HomekitAccessoryUpdater updater) {
495         int minValue = taggedItem.getConfigurationAsInt(HomekitTaggedItem.MIN_VALUE,
496                 ColorTemperatureCharacteristic.DEFAULT_MIN_VALUE);
497         return new ColorTemperatureCharacteristic(minValue,
498                 taggedItem.getConfigurationAsInt(HomekitTaggedItem.MAX_VALUE,
499                         ColorTemperatureCharacteristic.DEFAULT_MAX_VALUE),
500                 getIntSupplier(taggedItem, minValue), setIntConsumer(taggedItem),
501                 getSubscriber(taggedItem, COLOR_TEMPERATURE, updater),
502                 getUnsubscriber(taggedItem, COLOR_TEMPERATURE, updater));
503     }
504
505     private static CurrentFanStateCharacteristic createCurrentFanStateCharacteristic(HomekitTaggedItem taggedItem,
506             HomekitAccessoryUpdater updater) {
507         return new CurrentFanStateCharacteristic(() -> {
508             final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
509             @Nullable
510             CurrentFanStateEnum currentFanStateEnum = value != null ? CurrentFanStateEnum.fromCode(value.intValue())
511                     : null;
512             if (currentFanStateEnum == null) {
513                 currentFanStateEnum = CurrentFanStateEnum.INACTIVE;
514             }
515             return CompletableFuture.completedFuture(currentFanStateEnum);
516         }, getSubscriber(taggedItem, CURRENT_FAN_STATE, updater),
517                 getUnsubscriber(taggedItem, CURRENT_FAN_STATE, updater));
518     }
519
520     private static TargetFanStateCharacteristic createTargetFanStateCharacteristic(HomekitTaggedItem taggedItem,
521             HomekitAccessoryUpdater updater) {
522         return new TargetFanStateCharacteristic(() -> {
523             final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
524             @Nullable
525             TargetFanStateEnum targetFanStateEnum = value != null ? TargetFanStateEnum.fromCode(value.intValue())
526                     : null;
527             if (targetFanStateEnum == null) {
528                 targetFanStateEnum = TargetFanStateEnum.AUTO;
529             }
530             return CompletableFuture.completedFuture(targetFanStateEnum);
531         }, (targetState) -> {
532             if (taggedItem.getItem() instanceof NumberItem) {
533                 ((NumberItem) taggedItem.getItem()).send(new DecimalType(targetState.getCode()));
534             } else {
535                 logger.warn("Item type {} is not supported for {}. Only Number type is supported.",
536                         taggedItem.getItem().getType(), taggedItem.getName());
537             }
538         }, getSubscriber(taggedItem, TARGET_FAN_STATE, updater),
539                 getUnsubscriber(taggedItem, TARGET_FAN_STATE, updater));
540     }
541
542     private static RotationDirectionCharacteristic createRotationDirectionCharacteristic(HomekitTaggedItem taggedItem,
543             HomekitAccessoryUpdater updater) {
544         return new RotationDirectionCharacteristic(
545                 () -> getEnumFromItem(taggedItem, RotationDirectionEnum.CLOCKWISE,
546                         RotationDirectionEnum.COUNTER_CLOCKWISE, RotationDirectionEnum.CLOCKWISE),
547                 (value) -> setValueFromEnum(taggedItem, value, RotationDirectionEnum.CLOCKWISE,
548                         RotationDirectionEnum.COUNTER_CLOCKWISE),
549                 getSubscriber(taggedItem, ROTATION_DIRECTION, updater),
550                 getUnsubscriber(taggedItem, ROTATION_DIRECTION, updater));
551     }
552
553     private static SwingModeCharacteristic createSwingModeCharacteristic(HomekitTaggedItem taggedItem,
554             HomekitAccessoryUpdater updater) {
555         return new SwingModeCharacteristic(
556                 () -> getEnumFromItem(taggedItem, SwingModeEnum.SWING_DISABLED, SwingModeEnum.SWING_ENABLED,
557                         SwingModeEnum.SWING_DISABLED),
558                 (value) -> setValueFromEnum(taggedItem, value, SwingModeEnum.SWING_DISABLED,
559                         SwingModeEnum.SWING_ENABLED),
560                 getSubscriber(taggedItem, SWING_MODE, updater), getUnsubscriber(taggedItem, SWING_MODE, updater));
561     }
562
563     private static LockPhysicalControlsCharacteristic createLockPhysicalControlsCharacteristic(
564             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
565         return new LockPhysicalControlsCharacteristic(
566                 () -> getEnumFromItem(taggedItem, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED,
567                         LockPhysicalControlsEnum.CONTROL_LOCK_ENABLED, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED),
568                 (value) -> setValueFromEnum(taggedItem, value, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED,
569                         LockPhysicalControlsEnum.CONTROL_LOCK_ENABLED),
570                 getSubscriber(taggedItem, LOCK_CONTROL, updater), getUnsubscriber(taggedItem, LOCK_CONTROL, updater));
571     }
572
573     private static RotationSpeedCharacteristic createRotationSpeedCharacteristic(HomekitTaggedItem item,
574             HomekitAccessoryUpdater updater) {
575         return new RotationSpeedCharacteristic(getIntSupplier(item, 0), setPercentConsumer(item),
576                 getSubscriber(item, ROTATION_SPEED, updater), getUnsubscriber(item, ROTATION_SPEED, updater));
577     }
578
579     private static SetDurationCharacteristic createDurationCharacteristic(HomekitTaggedItem taggedItem,
580             HomekitAccessoryUpdater updater) {
581         return new SetDurationCharacteristic(() -> {
582             int value = getIntFromItem(taggedItem, 0);
583             final @Nullable Map<String, Object> itemConfiguration = taggedItem.getConfiguration();
584             if ((value == 0) && (itemConfiguration != null)) { // check for default duration
585                 final Object duration = itemConfiguration.get(HomekitValveImpl.CONFIG_DEFAULT_DURATION);
586                 if (duration instanceof BigDecimal) {
587                     value = ((BigDecimal) duration).intValue();
588                     if (taggedItem.getItem() instanceof NumberItem) {
589                         ((NumberItem) taggedItem.getItem()).setState(new DecimalType(value));
590                     }
591                 }
592             }
593             return CompletableFuture.completedFuture(value);
594         }, setIntConsumer(taggedItem), getSubscriber(taggedItem, DURATION, updater),
595                 getUnsubscriber(taggedItem, DURATION, updater));
596     }
597
598     private static RemainingDurationCharacteristic createRemainingDurationCharacteristic(HomekitTaggedItem taggedItem,
599             HomekitAccessoryUpdater updater) {
600         return new RemainingDurationCharacteristic(getIntSupplier(taggedItem, 0),
601                 getSubscriber(taggedItem, REMAINING_DURATION, updater),
602                 getUnsubscriber(taggedItem, REMAINING_DURATION, updater));
603     }
604
605     private static VolumeCharacteristic createVolumeCharacteristic(HomekitTaggedItem taggedItem,
606             HomekitAccessoryUpdater updater) {
607         return new VolumeCharacteristic(getIntSupplier(taggedItem, 0),
608                 (volume) -> ((NumberItem) taggedItem.getItem()).send(new DecimalType(volume)),
609                 getSubscriber(taggedItem, DURATION, updater), getUnsubscriber(taggedItem, DURATION, updater));
610     }
611
612     private static CoolingThresholdTemperatureCharacteristic createCoolingThresholdCharacteristic(
613             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
614         return new CoolingThresholdTemperatureCharacteristic(
615                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
616                         CoolingThresholdTemperatureCharacteristic.DEFAULT_MIN_VALUE),
617                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MAX_VALUE,
618                         CoolingThresholdTemperatureCharacteristic.DEFAULT_MAX_VALUE),
619                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.STEP,
620                         CoolingThresholdTemperatureCharacteristic.DEFAULT_STEP),
621                 getDoubleSupplier(taggedItem), setDoubleConsumer(taggedItem),
622                 getSubscriber(taggedItem, COOLING_THRESHOLD_TEMPERATURE, updater),
623                 getUnsubscriber(taggedItem, COOLING_THRESHOLD_TEMPERATURE, updater));
624     }
625
626     private static HeatingThresholdTemperatureCharacteristic createHeatingThresholdCharacteristic(
627             HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
628         return new HeatingThresholdTemperatureCharacteristic(
629                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
630                         HeatingThresholdTemperatureCharacteristic.DEFAULT_MIN_VALUE),
631                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MAX_VALUE,
632                         HeatingThresholdTemperatureCharacteristic.DEFAULT_MAX_VALUE),
633                 taggedItem.getConfigurationAsDouble(HomekitTaggedItem.STEP,
634                         HeatingThresholdTemperatureCharacteristic.DEFAULT_STEP),
635                 getDoubleSupplier(taggedItem), setDoubleConsumer(taggedItem),
636                 getSubscriber(taggedItem, HEATING_THRESHOLD_TEMPERATURE, updater),
637                 getUnsubscriber(taggedItem, HEATING_THRESHOLD_TEMPERATURE, updater));
638     }
639
640     private static OzoneDensityCharacteristic createOzoneDensityCharacteristic(final HomekitTaggedItem taggedItem,
641             HomekitAccessoryUpdater updater) {
642         return new OzoneDensityCharacteristic(getDoubleSupplier(taggedItem),
643                 getSubscriber(taggedItem, OZONE_DENSITY, updater), getUnsubscriber(taggedItem, OZONE_DENSITY, updater));
644     }
645
646     private static NitrogenDioxideDensityCharacteristic createNitrogenDioxideDensityCharacteristic(
647             final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
648         return new NitrogenDioxideDensityCharacteristic(getDoubleSupplier(taggedItem),
649                 getSubscriber(taggedItem, NITROGEN_DIOXIDE_DENSITY, updater),
650                 getUnsubscriber(taggedItem, NITROGEN_DIOXIDE_DENSITY, updater));
651     }
652
653     private static SulphurDioxideDensityCharacteristic createSulphurDioxideDensityCharacteristic(
654             final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
655         return new SulphurDioxideDensityCharacteristic(getDoubleSupplier(taggedItem),
656                 getSubscriber(taggedItem, SULPHUR_DIOXIDE_DENSITY, updater),
657                 getUnsubscriber(taggedItem, SULPHUR_DIOXIDE_DENSITY, updater));
658     }
659
660     private static PM25DensityCharacteristic createPM25DensityCharacteristic(final HomekitTaggedItem taggedItem,
661             HomekitAccessoryUpdater updater) {
662         return new PM25DensityCharacteristic(getDoubleSupplier(taggedItem),
663                 getSubscriber(taggedItem, PM25_DENSITY, updater), getUnsubscriber(taggedItem, PM25_DENSITY, updater));
664     }
665
666     private static PM10DensityCharacteristic createPM10DensityCharacteristic(final HomekitTaggedItem taggedItem,
667             HomekitAccessoryUpdater updater) {
668         return new PM10DensityCharacteristic(getDoubleSupplier(taggedItem),
669                 getSubscriber(taggedItem, PM10_DENSITY, updater), getUnsubscriber(taggedItem, PM10_DENSITY, updater));
670     }
671
672     private static VOCDensityCharacteristic createVOCDensityCharacteristic(final HomekitTaggedItem taggedItem,
673             HomekitAccessoryUpdater updater) {
674         return new VOCDensityCharacteristic(getDoubleSupplier(taggedItem),
675                 getSubscriber(taggedItem, VOC_DENSITY, updater), getUnsubscriber(taggedItem, VOC_DENSITY, updater));
676     }
677 }