2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.io.homekit.internal.accessories;
15 import static org.openhab.io.homekit.internal.HomekitAccessoryType.*;
16 import static org.openhab.io.homekit.internal.HomekitCharacteristicType.*;
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.AbstractMap.SimpleEntry;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
27 import java.util.Map.Entry;
28 import java.util.Objects;
29 import java.util.Optional;
31 import java.util.TreeMap;
32 import java.util.stream.Collectors;
33 import java.util.stream.Stream;
35 import org.eclipse.jdt.annotation.NonNullByDefault;
36 import org.eclipse.jdt.annotation.Nullable;
37 import org.openhab.core.items.GenericItem;
38 import org.openhab.core.items.GroupItem;
39 import org.openhab.core.items.Item;
40 import org.openhab.core.items.ItemRegistry;
41 import org.openhab.core.items.Metadata;
42 import org.openhab.core.items.MetadataKey;
43 import org.openhab.core.items.MetadataRegistry;
44 import org.openhab.io.homekit.internal.HomekitAccessoryType;
45 import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
46 import org.openhab.io.homekit.internal.HomekitCharacteristicType;
47 import org.openhab.io.homekit.internal.HomekitException;
48 import org.openhab.io.homekit.internal.HomekitOHItemProxy;
49 import org.openhab.io.homekit.internal.HomekitSettings;
50 import org.openhab.io.homekit.internal.HomekitTaggedItem;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
54 import io.github.hapjava.characteristics.Characteristic;
55 import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
58 * Creates a HomekitAccessory for a given HomekitTaggedItem.
60 * @author Andy Lintner - Initial contribution
61 * @author Eugen Freiter - refactoring for optional characteristics
64 public class HomekitAccessoryFactory {
65 private static final Logger logger = LoggerFactory.getLogger(HomekitAccessoryFactory.class);
66 public final static String METADATA_KEY = "homekit"; // prefix for HomeKit meta information in items.xml
68 /** List of mandatory attributes for each accessory type. **/
69 private final static Map<HomekitAccessoryType, HomekitCharacteristicType[]> MANDATORY_CHARACTERISTICS = new HashMap<HomekitAccessoryType, HomekitCharacteristicType[]>() {
71 put(ACCESSORY_GROUP, new HomekitCharacteristicType[] {});
72 put(LEAK_SENSOR, new HomekitCharacteristicType[] { LEAK_DETECTED_STATE });
73 put(MOTION_SENSOR, new HomekitCharacteristicType[] { MOTION_DETECTED_STATE });
74 put(OCCUPANCY_SENSOR, new HomekitCharacteristicType[] { OCCUPANCY_DETECTED_STATE });
75 put(CONTACT_SENSOR, new HomekitCharacteristicType[] { CONTACT_SENSOR_STATE });
76 put(SMOKE_SENSOR, new HomekitCharacteristicType[] { SMOKE_DETECTED_STATE });
77 put(HUMIDITY_SENSOR, new HomekitCharacteristicType[] { RELATIVE_HUMIDITY });
78 put(AIR_QUALITY_SENSOR, new HomekitCharacteristicType[] { AIR_QUALITY });
79 put(SWITCH, new HomekitCharacteristicType[] { ON_STATE });
80 put(CARBON_DIOXIDE_SENSOR, new HomekitCharacteristicType[] { CARBON_DIOXIDE_DETECTED_STATE });
81 put(CARBON_MONOXIDE_SENSOR, new HomekitCharacteristicType[] { CARBON_MONOXIDE_DETECTED_STATE });
82 put(WINDOW_COVERING, new HomekitCharacteristicType[] { TARGET_POSITION, CURRENT_POSITION, POSITION_STATE });
83 put(LIGHTBULB, new HomekitCharacteristicType[] { ON_STATE });
84 put(BASIC_FAN, new HomekitCharacteristicType[] { ON_STATE });
85 put(FAN, new HomekitCharacteristicType[] { ACTIVE_STATUS });
86 put(LIGHT_SENSOR, new HomekitCharacteristicType[] { LIGHT_LEVEL });
87 put(TEMPERATURE_SENSOR, new HomekitCharacteristicType[] { CURRENT_TEMPERATURE });
88 put(THERMOSTAT, new HomekitCharacteristicType[] { CURRENT_HEATING_COOLING_STATE,
89 TARGET_HEATING_COOLING_STATE, CURRENT_TEMPERATURE, TARGET_TEMPERATURE });
90 put(LOCK, new HomekitCharacteristicType[] { LOCK_CURRENT_STATE, LOCK_TARGET_STATE });
91 put(VALVE, new HomekitCharacteristicType[] { ACTIVE_STATUS, INUSE_STATUS });
93 new HomekitCharacteristicType[] { SECURITY_SYSTEM_CURRENT_STATE, SECURITY_SYSTEM_TARGET_STATE });
94 put(OUTLET, new HomekitCharacteristicType[] { ON_STATE, INUSE_STATUS });
95 put(SPEAKER, new HomekitCharacteristicType[] { MUTE });
96 put(SMART_SPEAKER, new HomekitCharacteristicType[] { CURRENT_MEDIA_STATE, TARGET_MEDIA_STATE });
97 put(GARAGE_DOOR_OPENER,
98 new HomekitCharacteristicType[] { CURRENT_DOOR_STATE, TARGET_DOOR_STATE, OBSTRUCTION_STATUS });
99 put(HEATER_COOLER, new HomekitCharacteristicType[] { ACTIVE_STATUS, CURRENT_HEATER_COOLER_STATE,
100 TARGET_HEATER_COOLER_STATE, CURRENT_TEMPERATURE });
101 put(WINDOW, new HomekitCharacteristicType[] { CURRENT_POSITION, TARGET_POSITION, POSITION_STATE });
102 put(DOOR, new HomekitCharacteristicType[] { CURRENT_POSITION, TARGET_POSITION, POSITION_STATE });
103 put(BATTERY, new HomekitCharacteristicType[] { BATTERY_LEVEL, BATTERY_LOW_STATUS });
104 put(FILTER_MAINTENANCE, new HomekitCharacteristicType[] { FILTER_CHANGE_INDICATION });
105 put(SLAT, new HomekitCharacteristicType[] { CURRENT_SLAT_STATE });
106 put(FAUCET, new HomekitCharacteristicType[] { ACTIVE_STATUS });
107 put(MICROPHONE, new HomekitCharacteristicType[] { MUTE });
111 /** List of service implementation for each accessory type. **/
112 private final static Map<HomekitAccessoryType, Class<? extends AbstractHomekitAccessoryImpl>> SERVICE_IMPL_MAP = new HashMap<HomekitAccessoryType, Class<? extends AbstractHomekitAccessoryImpl>>() {
114 put(ACCESSORY_GROUP, HomekitAccessoryGroupImpl.class);
115 put(LEAK_SENSOR, HomekitLeakSensorImpl.class);
116 put(MOTION_SENSOR, HomekitMotionSensorImpl.class);
117 put(OCCUPANCY_SENSOR, HomekitOccupancySensorImpl.class);
118 put(CONTACT_SENSOR, HomekitContactSensorImpl.class);
119 put(SMOKE_SENSOR, HomekitSmokeSensorImpl.class);
120 put(HUMIDITY_SENSOR, HomekitHumiditySensorImpl.class);
121 put(AIR_QUALITY_SENSOR, HomekitAirQualitySensorImpl.class);
122 put(SWITCH, HomekitSwitchImpl.class);
123 put(CARBON_DIOXIDE_SENSOR, HomekitCarbonDioxideSensorImpl.class);
124 put(CARBON_MONOXIDE_SENSOR, HomekitCarbonMonoxideSensorImpl.class);
125 put(WINDOW_COVERING, HomekitWindowCoveringImpl.class);
126 put(LIGHTBULB, HomekitLightbulbImpl.class);
127 put(BASIC_FAN, HomekitBasicFanImpl.class);
128 put(FAN, HomekitFanImpl.class);
129 put(LIGHT_SENSOR, HomekitLightSensorImpl.class);
130 put(TEMPERATURE_SENSOR, HomekitTemperatureSensorImpl.class);
131 put(THERMOSTAT, HomekitThermostatImpl.class);
132 put(LOCK, HomekitLockImpl.class);
133 put(VALVE, HomekitValveImpl.class);
134 put(SECURITY_SYSTEM, HomekitSecuritySystemImpl.class);
135 put(OUTLET, HomekitOutletImpl.class);
136 put(SPEAKER, HomekitSpeakerImpl.class);
137 put(SMART_SPEAKER, HomekitSmartSpeakerImpl.class);
138 put(GARAGE_DOOR_OPENER, HomekitGarageDoorOpenerImpl.class);
139 put(DOOR, HomekitDoorImpl.class);
140 put(WINDOW, HomekitWindowImpl.class);
141 put(HEATER_COOLER, HomekitHeaterCoolerImpl.class);
142 put(BATTERY, HomekitBatteryImpl.class);
143 put(FILTER_MAINTENANCE, HomekitFilterMaintenanceImpl.class);
144 put(SLAT, HomekitSlatImpl.class);
145 put(FAUCET, HomekitFaucetImpl.class);
146 put(MICROPHONE, HomekitMicrophoneImpl.class);
150 private static List<HomekitCharacteristicType> getRequiredCharacteristics(HomekitTaggedItem taggedItem) {
151 final List<HomekitCharacteristicType> characteristics = new ArrayList<>();
152 if (MANDATORY_CHARACTERISTICS.containsKey(taggedItem.getAccessoryType())) {
153 characteristics.addAll(Arrays.asList(MANDATORY_CHARACTERISTICS.get(taggedItem.getAccessoryType())));
155 if (taggedItem.getAccessoryType() == BATTERY) {
156 final boolean isChargeable = taggedItem.getConfigurationAsBoolean(HomekitBatteryImpl.BATTERY_TYPE, false);
158 characteristics.add(BATTERY_CHARGING_STATE);
161 return characteristics;
165 * creates HomeKit accessory for an openhab item.
167 * @param taggedItem openhab item tagged as HomeKit item
168 * @param metadataRegistry openhab metadata registry required to get item meta information
169 * @param updater OH HomeKit update class that ensure the status sync between OH item and corresponding HomeKit
171 * @param settings OH settings
172 * @return HomeKit accessory
173 * @throws HomekitException exception in case HomeKit accessory could not be created, e.g. due missing mandatory
176 public static AbstractHomekitAccessoryImpl create(HomekitTaggedItem taggedItem, MetadataRegistry metadataRegistry,
177 HomekitAccessoryUpdater updater, HomekitSettings settings) throws HomekitException {
178 Set<HomekitTaggedItem> ancestorServices = new HashSet<>();
179 return create(taggedItem, metadataRegistry, updater, settings, ancestorServices);
182 @SuppressWarnings("null")
183 private static AbstractHomekitAccessoryImpl create(HomekitTaggedItem taggedItem, MetadataRegistry metadataRegistry,
184 HomekitAccessoryUpdater updater, HomekitSettings settings, Set<HomekitTaggedItem> ancestorServices)
185 throws HomekitException {
186 final HomekitAccessoryType accessoryType = taggedItem.getAccessoryType();
187 logger.trace("Constructing {} of accessory type {}", taggedItem.getName(), accessoryType.getTag());
188 final List<HomekitTaggedItem> foundCharacteristics = getMandatoryCharacteristicsFromItem(taggedItem,
190 final List<HomekitCharacteristicType> mandatoryCharacteristics = getRequiredCharacteristics(taggedItem);
191 if (foundCharacteristics.size() < mandatoryCharacteristics.size()) {
192 logger.warn("Accessory of type {} must have following characteristics {}. Found only {}",
193 accessoryType.getTag(), mandatoryCharacteristics, foundCharacteristics);
194 throw new HomekitException("Missing mandatory characteristics");
196 AbstractHomekitAccessoryImpl accessoryImpl;
198 final @Nullable Class<? extends AbstractHomekitAccessoryImpl> accessoryImplClass = SERVICE_IMPL_MAP
200 if (accessoryImplClass != null) {
201 if (ancestorServices.contains(taggedItem)) {
202 logger.warn("Item {} has already been created. Perhaps you have circular Homekit accessory groups?",
203 taggedItem.getName());
204 throw new HomekitException("Circular accessory references");
206 ancestorServices.add(taggedItem);
207 accessoryImpl = accessoryImplClass.getConstructor(HomekitTaggedItem.class, List.class,
208 HomekitAccessoryUpdater.class, HomekitSettings.class)
209 .newInstance(taggedItem, foundCharacteristics, updater, settings);
210 addOptionalCharacteristics(taggedItem, accessoryImpl, metadataRegistry);
211 addLinkedServices(taggedItem, accessoryImpl, metadataRegistry, updater, settings, ancestorServices);
212 return accessoryImpl;
214 logger.warn("Unsupported HomeKit type: {}", accessoryType.getTag());
215 throw new HomekitException("Unsupported HomeKit type: " + accessoryType);
217 } catch (NoSuchMethodException | IllegalAccessException | InstantiationException
218 | InvocationTargetException e) {
219 logger.warn("Cannot instantiate accessory implementation for accessory {}", accessoryType.getTag(), e);
220 throw new HomekitException("Cannot instantiate accessory implementation for accessory " + accessoryType);
225 * return HomeKit accessory types for an OH item based on meta data
227 * @param item OH item
228 * @param metadataRegistry meta data registry
229 * @return list of HomeKit accessory types and characteristics.
231 public static List<Entry<HomekitAccessoryType, HomekitCharacteristicType>> getAccessoryTypes(Item item,
232 MetadataRegistry metadataRegistry) {
233 final List<Entry<HomekitAccessoryType, HomekitCharacteristicType>> accessories = new ArrayList<>();
234 final @Nullable Metadata metadata = metadataRegistry.get(new MetadataKey(METADATA_KEY, item.getUID()));
235 if (metadata != null) {
236 String[] tags = metadata.getValue().split(",");
237 for (String tag : tags) {
238 final String[] meta = tag.split("\\.");
239 Optional<HomekitAccessoryType> accessoryType = HomekitAccessoryType.valueOfTag(meta[0].trim());
240 if (accessoryType.isPresent()) { // it accessory, check for characteristic
241 HomekitAccessoryType type = accessoryType.get();
242 if (meta.length > 1) {
243 // it has characteristic as well
244 accessories.add(new SimpleEntry<>(type,
245 HomekitCharacteristicType.valueOfTag(meta[1].trim()).orElse(EMPTY)));
246 } else {// it has no characteristic
247 accessories.add(new SimpleEntry<>(type, EMPTY));
249 } else { // it is no accessory, so, maybe it is a characteristic
250 HomekitCharacteristicType.valueOfTag(meta[0].trim())
251 .ifPresent(c -> accessories.add(new SimpleEntry<>(DUMMY, c)));
258 public static @Nullable Map<String, Object> getItemConfiguration(Item item, MetadataRegistry metadataRegistry) {
259 final @Nullable Metadata metadata = metadataRegistry.get(new MetadataKey(METADATA_KEY, item.getUID()));
260 return metadata != null ? metadata.getConfiguration() : null;
264 * return list of HomeKit relevant groups linked to an accessory
266 * @param item OH item
267 * @param itemRegistry item registry
268 * @param metadataRegistry metadata registry
269 * @return list of relevant group items
271 public static List<GroupItem> getAccessoryGroups(Item item, ItemRegistry itemRegistry,
272 MetadataRegistry metadataRegistry) {
273 return item.getGroupNames().stream().flatMap(name -> {
274 final @Nullable Item groupItem = itemRegistry.get(name);
275 if (groupItem instanceof GroupItem) {
276 return Stream.of((GroupItem) groupItem);
278 return Stream.empty();
280 }).filter(groupItem -> !getAccessoryTypes(groupItem, metadataRegistry).isEmpty()).collect(Collectors.toList());
284 * collect all mandatory characteristics for a given tagged item, e.g. collect all mandatory HomeKit items from a
287 * @param taggedItem HomeKit tagged item
288 * @param metadataRegistry meta data registry
289 * @return list of mandatory
291 private static List<HomekitTaggedItem> getMandatoryCharacteristicsFromItem(HomekitTaggedItem taggedItem,
292 MetadataRegistry metadataRegistry) {
293 List<HomekitTaggedItem> collectedCharacteristics = new ArrayList<>();
294 if (taggedItem.isGroup()) {
295 for (Item item : ((GroupItem) taggedItem.getItem()).getMembers()) {
296 addMandatoryCharacteristics(taggedItem, collectedCharacteristics, item, metadataRegistry);
299 addMandatoryCharacteristics(taggedItem, collectedCharacteristics, taggedItem.getItem(), metadataRegistry);
301 logger.trace("Mandatory characteristics: {}", collectedCharacteristics);
302 return collectedCharacteristics;
306 * add mandatory HomeKit items for a given main item to a list of characteristics.
307 * Main item is use only to determine, which characteristics are mandatory.
308 * The characteristics are added to item.
309 * e.g. mainItem could be a group tagged as "thermostat" and item could be item linked to the group and marked as
312 * @param mainItem main item
313 * @param characteristics list of characteristics
314 * @param item current item
315 * @param metadataRegistry meta date registry
317 private static void addMandatoryCharacteristics(HomekitTaggedItem mainItem, List<HomekitTaggedItem> characteristics,
318 Item item, MetadataRegistry metadataRegistry) {
319 // get list of mandatory characteristics
320 List<HomekitCharacteristicType> mandatoryCharacteristics = getRequiredCharacteristics(mainItem);
321 if (mandatoryCharacteristics.isEmpty()) {
322 // no mandatory characteristics linked to accessory type of mainItem. we are done
325 // check whether we are adding characteristic to the main item, and if yes, use existing item proxy.
326 // if we are adding not to the main item (typical for groups), create new proxy item.
327 final HomekitOHItemProxy itemProxy = mainItem.getItem().equals(item) ? mainItem.getProxyItem()
328 : new HomekitOHItemProxy(item);
329 // an item can have several tags, e.g. "ActiveStatus, InUse". we iterate here over all his tags
330 for (Entry<HomekitAccessoryType, HomekitCharacteristicType> accessory : getAccessoryTypes(item,
332 // if the item has only accessory tag, e.g. TemperatureSensor,
333 // then we will link all mandatory characteristic to this item,
334 // e.g. we will link CurrentTemperature in case of TemperatureSensor.
335 // Note that accessories that are members of other accessories do _not_
336 // count - we're already constructing another root accessory.
337 if (isRootAccessory(accessory) && mainItem.getItem().equals(item)) {
338 mandatoryCharacteristics.forEach(c -> characteristics.add(new HomekitTaggedItem(itemProxy,
339 accessory.getKey(), c, mainItem.isGroup() ? (GroupItem) mainItem.getItem() : null,
340 HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry))));
342 // item has characteristic tag on it, so, adding it as that characteristic.
344 final HomekitCharacteristicType characteristic = accessory.getValue();
346 // check whether it is a mandatory characteristic. optional will be added later by another method.
347 if (belongsToType(mainItem.getAccessoryType(), accessory)
348 && isMandatoryCharacteristic(mainItem, characteristic)) {
349 characteristics.add(new HomekitTaggedItem(itemProxy, accessory.getKey(), characteristic,
350 mainItem.isGroup() ? (GroupItem) mainItem.getItem() : null,
351 HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry)));
358 * add optional characteristics for given accessory.
360 * @param taggedItem main item
361 * @param accessory accessory
362 * @param metadataRegistry metadata registry
364 private static void addOptionalCharacteristics(HomekitTaggedItem taggedItem, AbstractHomekitAccessoryImpl accessory,
365 MetadataRegistry metadataRegistry) {
366 Map<HomekitCharacteristicType, GenericItem> characteristics = getOptionalCharacteristics(
367 accessory.getRootAccessory(), metadataRegistry);
368 HashMap<String, HomekitOHItemProxy> proxyItems = new HashMap<>();
369 proxyItems.put(taggedItem.getItem().getUID(), taggedItem.getProxyItem());
370 // an accessory can have multiple optional characteristics. iterate over them.
371 characteristics.forEach((type, item) -> {
373 // check whether a proxyItem already exists, if not create one.
374 final HomekitOHItemProxy proxyItem = Objects
375 .requireNonNull(proxyItems.computeIfAbsent(item.getUID(), k -> new HomekitOHItemProxy(item)));
376 final HomekitTaggedItem optionalItem = new HomekitTaggedItem(proxyItem,
377 accessory.getRootAccessory().getAccessoryType(), type,
378 accessory.getRootAccessory().getRootDeviceGroupItem(),
379 getItemConfiguration(item, metadataRegistry));
380 final Characteristic characteristic = HomekitCharacteristicFactory.createCharacteristic(optionalItem,
381 accessory.getUpdater());
382 accessory.addCharacteristic(optionalItem, characteristic);
383 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | HomekitException e) {
384 logger.warn("Unsupported optional HomeKit characteristic: type {}, characteristic type {}",
385 accessory.getPrimaryService(), type.getTag());
391 * creates HomeKit services for an openhab item that are members of this group item.
393 * @param taggedItem openhab item tagged as HomeKit item
394 * @param AbstractHomekitAccessoryImpl the accessory to add services to
395 * @param metadataRegistry openhab metadata registry required to get item meta information
396 * @param updater OH HomeKit update class that ensure the status sync between OH item and corresponding HomeKit
398 * @param settings OH settings
399 * @param ancestorServices set of all accessories/services under the same root accessory, for
400 * for preventing circular references
401 * @throws HomekitException exception in case HomeKit accessory could not be created, e.g. due missing mandatory
404 private static void addLinkedServices(HomekitTaggedItem taggedItem, AbstractHomekitAccessoryImpl accessory,
405 MetadataRegistry metadataRegistry, HomekitAccessoryUpdater updater, HomekitSettings settings,
406 Set<HomekitTaggedItem> ancestorServices) throws HomekitException {
407 final var item = taggedItem.getItem();
408 if (!(item instanceof GroupItem))
411 for (var groupMember : ((GroupItem) item).getMembers().stream()
412 .sorted((lhs, rhs) -> lhs.getName().compareTo(rhs.getName())).collect(Collectors.toList())) {
413 final var characteristicTypes = getAccessoryTypes(groupMember, metadataRegistry);
414 var accessoryTypes = characteristicTypes.stream().filter(c -> c.getValue() == EMPTY)
415 .collect(Collectors.toList());
417 logger.trace("accessory types for {} are {}", groupMember.getName(), accessoryTypes);
418 if (accessoryTypes.isEmpty())
421 if (accessoryTypes.size() > 1) {
422 logger.warn("Item {} is a HomeKit sub-accessory, but multiple accessory types are not allowed.",
423 groupMember.getName());
427 final @Nullable Map<String, Object> itemConfiguration = getItemConfiguration(groupMember, metadataRegistry);
429 final var accessoryType = accessoryTypes.iterator().next().getKey();
430 logger.trace("Item {} is a HomeKit sub-accessory of type {}.", groupMember.getName(), accessoryType);
431 final var itemProxy = new HomekitOHItemProxy(groupMember);
432 final var subTaggedItem = new HomekitTaggedItem(itemProxy, accessoryType, itemConfiguration);
433 final var subAccessory = create(subTaggedItem, metadataRegistry, updater, settings, ancestorServices);
436 subAccessory.addCharacteristic(new NameCharacteristic(() -> subAccessory.getName()));
437 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
438 // This should never happen; all services should support NameCharacteristic as an optional
440 // If HAP-Java defined a service that doesn't support addOptionalCharacteristic(NameCharacteristic),
441 // Then it's a bug there, and we're just going to ignore the exception here.
444 if (subAccessory.isLinkable(accessory)) {
445 accessory.getPrimaryService().addLinkedService(subAccessory.getPrimaryService());
447 accessory.getServices().add(subAccessory.getPrimaryService());
453 * collect optional HomeKit characteristics for a OH item.
455 * @param taggedItem main OH item
456 * @param metadataRegistry OH metadata registry
457 * @return a map with characteristics and corresponding OH items
459 private static Map<HomekitCharacteristicType, GenericItem> getOptionalCharacteristics(HomekitTaggedItem taggedItem,
460 MetadataRegistry metadataRegistry) {
461 Map<HomekitCharacteristicType, GenericItem> characteristicItems = new TreeMap<>();
462 if (taggedItem.isGroup()) {
463 GroupItem groupItem = (GroupItem) taggedItem.getItem();
464 groupItem.getMembers().forEach(item -> getAccessoryTypes(item, metadataRegistry).stream()
465 .filter(c -> !isRootAccessory(c)).filter(c -> belongsToType(taggedItem.getAccessoryType(), c))
466 .filter(c -> !isMandatoryCharacteristic(taggedItem, c.getValue()))
467 .forEach(characteristic -> characteristicItems.put(characteristic.getValue(), (GenericItem) item)));
469 getAccessoryTypes(taggedItem.getItem(), metadataRegistry).stream().filter(c -> !isRootAccessory(c))
470 .filter(c -> !isMandatoryCharacteristic(taggedItem, c.getValue()))
471 .forEach(characteristic -> characteristicItems.put(characteristic.getValue(),
472 (GenericItem) taggedItem.getItem()));
474 logger.trace("Optional characteristics for item {}: {}", taggedItem.getName(), characteristicItems.values());
475 return Collections.unmodifiableMap(characteristicItems);
479 * return true is characteristic is a mandatory characteristic for the accessory.
482 * @param characteristic characteristic
483 * @return true if characteristic is mandatory, false if not mandatory
485 private static boolean isMandatoryCharacteristic(HomekitTaggedItem item, HomekitCharacteristicType characteristic) {
486 return MANDATORY_CHARACTERISTICS.containsKey(item.getAccessoryType())
487 && getRequiredCharacteristics(item).contains(characteristic);
491 * check whether accessory is root accessory, i.e. without characteristic tag.
493 * @param accessory accessory
494 * @return true if accessory has not characteristic.
496 private static boolean isRootAccessory(Entry<HomekitAccessoryType, HomekitCharacteristicType> accessory) {
497 return ((accessory.getValue() == null) || (accessory.getValue() == EMPTY));
501 * check whether characteristic belongs to the specific accessory type.
502 * characteristic with no accessory type mentioned in metadata are considered as candidates for all types.
504 * @param accessoryType accessory type
505 * @param characteristic characteristic
506 * @return true if characteristic belongs to the accessory type.
508 private static boolean belongsToType(HomekitAccessoryType accessoryType,
509 Entry<HomekitAccessoryType, HomekitCharacteristicType> characteristic) {
510 return ((characteristic.getKey() == accessoryType) || (characteristic.getKey() == DUMMY));