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