
-- open home app on your iPhone or iPad
+- open Home app on your iPhone or iPad
- create new home


-- follow the instruction of the home app wizard
+- follow the instruction of the Home app wizard

A HomeKit accessory has mandatory and optional characteristics (listed below in the table).
The mapping between openHAB items and HomeKit accessory and characteristics is done by means of [metadata](https://www.openhab.org/docs/concepts/items.html#item-metadata)
-If the first word of the item name match the room name in home app, home app will hide it.
+If the first word of the item name match the room name in Home app, Home app will hide it.
E.g. item with the name "Kitchen Light" will be shown in "Kitchen" room as "Light". This is recommended naming convention for HomeKit items and rooms.
### UI based Configuration
Switch leaksensor_metadata "Leak Sensor" {homekit="LeakSensor"}
```
-You can link one openHAB item to one or more HomeKit accessory, e.g.
-
-```xtend
-Switch occupancy_and_motion_sensor "Occupancy and Motion Sensor Tag" {homekit="OccupancySensor,MotionSensor"}
-```
-
The tag can be:
- full qualified: i.e. with accessory type and characteristic, e.g. "LeakSensor.LeakDetectedState"
Switch leaksensor_battery "Leak Sensor Battery" (gLeakSensor) {homekit="LeakSensor.BatteryLowStatus"}
```
-You can use openHAB group to manage state of multiple items. (see [Group items](https://www.openhab.org/docs/configuration/items.html#derive-group-state-from-member-items))
-In this case, you can assign HomeKit accessory type to the group and to the group items
-Following example defines 3 HomeKit accessories of type Lighting:
+### Complex Multiple Service Accessories
+
+Alternatively, you may want to have a choice of controlling the items individually, OR as a group, from HomeKit.
+The following examples defines a single HomeKit accessory _with multiple services_ that the Home app will allow you to control together, or drill down and control individually.
+Note that `AccessoryGroup` doesn't expose any services itself, but allows you to group other services together underneath it.
+Also note that when nesting accessories, you cannot use the shorthand of naming only a characteristic, and not its accessory type, since it would be ambiguous if that item belongs to a secondary service, or to the primary service it's nested under.
+
+```java
+Group:Switch:OR(ON,OFF) gLight "Light Group" {homekit="AccessoryGroup"}
+Switch light1 "Light 1" (gLight) {homekit="Lighting"}
+Switch light2 "Light 2" (gLight) {homekit="Lighting"}
+```
+
+
-- "Light 1" and "Light 2" as independent lights
-- "Light Group" that controls "Light 1" and "Light 2" as group
+You can also group additional accessories directly under another accessory.
+In this example, HomeKit will show three separate light controls.
+As this is somewhat confusing that Home will allow controlling all members as a group, and you also have the group as a distinct switch inside the HomeKit accessory, this is not a recommended configuration.
```xtend
Group:Switch:OR(ON,OFF) gLight "Light Group" {homekit="Lighting"}
-Switch light1 "Light 1" (gLight) {homekit="Lighting.OnState"}
-Switch light2 "Light 2" (gLight) {homekit="Lighting.OnState"}
+Switch light1 "Light 1" (gLight) {homekit="Lighting"}
+Switch light2 "Light 2" (gLight) {homekit="Lighting"}
+```
+
+
+
+You can also mix and match accessories:
+
+```java
+Group gFan {homekit="Fan"}
+Switch fan1 "Fan" (gFan) {homekit="Fan.Active"}
+Switch fan1_light "Fan Light" (gFan) {homekit="Lighting"}
+```
+
+
+
+Another way to build complex accessories is to associate multiple accessory types with the root group, and then define all of the individual characteristics on group members.
+When using this style, you cannot have multiple instance of the same accessory type.
+
+```java
+Group FanWithLight "Fan with Light" {homekit = "Fan,Lighting"}
+Switch FanActiveStatus "Fan Active Status" (FanWithLight) {homekit = "Fan.ActiveStatus"}
+Number FanRotationSpeed "Fan Rotation Speed" (FanWithLight) {homekit = "Fan.RotationSpeed"}
+Switch Light "Light" (FanWithLight) {homekit = "Lighting.OnState"}
```
+or in MainUI:
+
+
+
+
+
+Finally, you can link one openHAB item to one or more HomeKit accessories, as well:
+
+```java
+Switch occupancy_and_motion_sensor "Occupancy and Motion Sensor Tag" {homekit="OccupancySensor,MotionSensor"}
+```
+
+You can even form complex sensors this way.
+Just be sure that you fully specify additional characteristics, so that the addon knows which root service to add it to.
+
+```java
+Group eBunkAirthings "Bunk Room Airthings Wave Plus" { homekit="AirQualitySensor,TemperatureSensor,HumiditySensor" }
+
+String Bunk_AirQuality "Bunk Room Air Quality" (eBunkAirthings) { homekit="AirQualitySensor.AirQuality" }
+Number:Dimensionless Bunk_Humidity "Bunk Room Relative Humidity [%d %%]" (eBunkAirthings) { homekit="HumiditySensor.RelativeHumidity" }
+Number:Temperature Bunk_AmbTemp "Bunk Room Temperature [%.1f °F]" (eBunkAirthings) { homekit="TemperatureSensor.CurrentTemperature" }
+Number:Dimensionless Bunk_tVOC "Bunk Room tVOC [%d ppb]" (eBunkAirthings) { homekit="AirQualitySensor.VOCDensity" [ maxValue=10000 ] }
+```
+
+A sensor with a battery configured in MainUI:
+
+
+
+The Home app uses the first accessory in a group as the icon for the group as a whole.
+E.g. an accessory defined as `homekit="Fan,Light"` will be shown as a fan and an accessory defined as `homekit="Light,Fan"` will be shown as a light in the Home app.
+You can also override the primary service by using adding `primary=<type>` to the HomeKit metadata configuration:
+
+```java
+Group FanWithLight "Fan with Light" {homekit = "Light,Fan" [primary = "Fan"]}
+```
+
+on in MainUI:
+
+
+
+Unusual combinations are also possible, e.g. you can combine temperature sensor with blinds and light.
+
+It will be represented by the Home app as follows:
+
+
+
+Note that for sensors that aren't interactive, the Home app will show the constituent pieces in the room and home summaries, and you'll only be able to see the combined accessory when viewing the accessories associated with a particular bridge in the home settings:
+
+
+
+
## Dummy Accessories
OpenHAB is a highly dynamic system, and prone to occasional misconfigurations where items can't be loaded for various reasons, especially if you're using something besides the UI to manage your items.
or using UI

-
-### Complex accessory
-
-Multiple HomeKit accessories can be combined to one accessory in order to group several functions provided by one or multiple physical devices.
-
-For example, ceiling fans often include lighting functionality. Such fans can be modeled as:
-
-- two separate HomeKit accessories - fan **and** light.
-
- iOS home app would show them as **two tiles** that can be controlled directly from home screen.
-
- 
-
-- one complex accessory - fan **with** light.
-
- iOS home app would show them as **one tile** that opens view with two controls
-
- 
-
- 
-
-The provided functionality is in both cases identical.
-
-In order to combine multiple accessories to one HomeKit accessory you need:
-
-- add corresponding openHAB items to one openHAB group
-- configure HomeKit metadata of both HomeKit accessories at that group.
-
-e.g. configuration for a fan with light would look as follows
-
-```xtend
-Group FanWithLight "Fan with Light" {homekit = "Fan,Lighting"}
-Switch FanActiveStatus "Fan Active Status" (FanWithLight) {homekit = "Fan.ActiveStatus"}
-Number FanRotationSpeed "Fan Rotation Speed" (FanWithLight) {homekit = "Fan.RotationSpeed"}
-Switch Light "Light" (FanWithLight) {homekit = "Lighting.OnState"}
-```
-
-or in mainUI
-
-
-
-
-
-iOS home app uses by default the type of the first accessory on the list for the tile on home screen.
-e.g. an accessory defined as homekit = "Fan,Light" will be shown as a fan and an accessory defined as homekit = "Light,Fan" as a light in iOS home app.
-
-if you want to change the tile you can either change the order of types in homekit metadata or add "primary=<type>" to HomeKit metadata configuration.
-e.g. following configuration will force "fan" to be used as tile
-
-```xtend
-Group FanWithLight "Fan with Light" {homekit = "Light,Fan" [primary = "Fan"]}
-```
-
-
-
-Similarly, you can create a sensor with battery
-
-
-
-However, home app does not support changing of tiles for already added accessory.
-If you want to change the tile after the accessory was added, you need either to rename the group, if you use textual item configuration, or to delete and to create a new group with a different name, if you use UI for configuration.
-
-You can combine more than two accessories as well as accessories linked to different physical devices.
-You can also do unusually combinations, e.g. you can combine temperature sensor with blinds and light.
-It will be represented by home app as follows
-
-
-
-
-#### Limitations
-
-Currently, it is not possible to combine multiple accessories of the same type, e.g. 2 lights.
-Support for this is planned for the future release of openHAB HomeKit binding.
## Supported accessory type
FAUCET("Faucet"),
MICROPHONE("Microphone"),
SLAT("Slat"),
+ ACCESSORY_GROUP("AccessoryGroup"),
DUMMY("Dummy");
private static final Map<String, HomekitAccessoryType> TAG_MAP = new HashMap<>();
*/
package org.openhab.io.homekit.internal;
+import java.lang.reflect.InvocationTargetException;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
import org.slf4j.LoggerFactory;
import io.github.hapjava.accessories.HomekitAccessory;
+import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
import io.github.hapjava.server.impl.HomekitRoot;
/**
private void createRootAccessories(Item item) {
final List<Entry<HomekitAccessoryType, HomekitCharacteristicType>> accessoryTypes = HomekitAccessoryFactory
.getAccessoryTypes(item, metadataRegistry);
+ if (accessoryTypes.isEmpty()) {
+ return;
+ }
+
final List<GroupItem> groups = HomekitAccessoryFactory.getAccessoryGroups(item, itemRegistry, metadataRegistry);
+ // Don't create accessories that are sub-accessories of other accessories
+ if (groups.stream().anyMatch(g -> !HomekitAccessoryFactory.getAccessoryTypes(g, metadataRegistry).isEmpty())) {
+ return;
+ }
+
final @Nullable Map<String, Object> itemConfiguration = HomekitAccessoryFactory.getItemConfiguration(item,
metadataRegistry);
- if (accessoryTypes.isEmpty() || !(groups.isEmpty() || groups.stream().noneMatch(g -> g.getBaseItem() == null))
- || !itemIsForThisBridge(item, itemConfiguration)) {
+ if (!itemIsForThisBridge(item, itemConfiguration)) {
return;
}
logger.trace("Item {} is a HomeKit accessory of types {}. Primary type is {}", item.getName(), accessoryTypes,
primaryAccessoryType);
final HomekitOHItemProxy itemProxy = new HomekitOHItemProxy(item);
- final HomekitTaggedItem taggedItem = new HomekitTaggedItem(new HomekitOHItemProxy(item), primaryAccessoryType,
- itemConfiguration);
+ final HomekitTaggedItem taggedItem = new HomekitTaggedItem(itemProxy, primaryAccessoryType, itemConfiguration);
try {
final AbstractHomekitAccessoryImpl accessory = HomekitAccessoryFactory.create(taggedItem, metadataRegistry,
updater, settings);
+ if (accessory.isLinkedServiceOnly()) {
+ logger.warn("Item '{}' is a '{}' which must be nested another another accessory.", taggedItem.getName(),
+ primaryAccessoryType);
+ return;
+ }
accessoryTypes.stream().filter(aType -> !primaryAccessoryType.equals(aType.getKey()))
.forEach(additionalAccessoryType -> {
final HomekitTaggedItem additionalTaggedItem = new HomekitTaggedItem(itemProxy,
additionalAccessoryType.getKey(), itemConfiguration);
try {
- final HomekitAccessory additionalAccessory = HomekitAccessoryFactory
+ final AbstractHomekitAccessoryImpl additionalAccessory = HomekitAccessoryFactory
.create(additionalTaggedItem, metadataRegistry, updater, settings);
+ // Secondary accessories that don't explicitly specify a name will implicitly
+ // get a name characteristic based on the item's name
+ if (!additionalAccessory.getCharacteristic(HomekitCharacteristicType.NAME).isPresent()) {
+ try {
+ additionalAccessory.addCharacteristic(
+ new NameCharacteristic(() -> additionalAccessory.getName()));
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ // This should never happen; all services should support NameCharacteristic as an
+ // optional Characteristic.
+ // If HAP-Java defined a service that doesn't support
+ // addOptionalCharacteristic(NameCharacteristic), then it's a bug there, and we're
+ // just going to ignore the exception here.
+ }
+ }
accessory.getServices().add(additionalAccessory.getPrimaryService());
} catch (HomekitException e) {
logger.warn("Cannot create additional accessory {}", additionalTaggedItem);
*/
package org.openhab.io.homekit.internal.accessories;
+import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.LoggerFactory;
import io.github.hapjava.accessories.HomekitAccessory;
+import io.github.hapjava.characteristics.Characteristic;
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
import io.github.hapjava.characteristics.impl.base.BaseCharacteristic;
import io.github.hapjava.services.Service;
private final HomekitAccessoryUpdater updater;
private final HomekitSettings settings;
private final List<Service> services;
+ private final Map<Class<? extends Characteristic>, Characteristic> rawCharacteristics;
public AbstractHomekitAccessoryImpl(HomekitTaggedItem accessory, List<HomekitTaggedItem> characteristics,
HomekitAccessoryUpdater updater, HomekitSettings settings) {
this.updater = updater;
this.services = new ArrayList<>();
this.settings = settings;
+ this.rawCharacteristics = new HashMap<>();
+ }
+
+ /**
+ * @param parentAccessory The primary service to link to.
+ * @return If this accessory should be nested as a linked service below a primary service,
+ * rather than as a sibling.
+ */
+ public boolean isLinkable(HomekitAccessory parentAccessory) {
+ return false;
+ }
+
+ /**
+ * @return If this accessory is only valid as a linked service, not as a standalone accessory.
+ */
+ public boolean isLinkedServiceOnly() {
+ return false;
}
@NonNullByDefault
- protected Optional<HomekitTaggedItem> getCharacteristic(HomekitCharacteristicType type) {
+ public Optional<HomekitTaggedItem> getCharacteristic(HomekitCharacteristicType type) {
return characteristics.stream().filter(c -> c.getCharacteristicType() == type).findAny();
}
}
@NonNullByDefault
- protected void addCharacteristic(HomekitTaggedItem characteristic) {
- characteristics.add(characteristic);
+ protected void addCharacteristic(HomekitTaggedItem item, Characteristic characteristic)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ characteristics.add(item);
+ addCharacteristic(characteristic);
+ }
+
+ /**
+ * @param type
+ * @param characteristic
+ */
+ @NonNullByDefault
+ public void addCharacteristic(Characteristic characteristic)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ if (rawCharacteristics.containsKey(characteristic.getClass())) {
+ logger.warn("Accessory {} already has a characteristic of type {}; ignoring additional definition.",
+ accessory.getName(), characteristic.getClass().getSimpleName());
+ return;
+ }
+ rawCharacteristics.put(characteristic.getClass(), characteristic);
+ var service = getPrimaryService();
+ // find the corresponding add method at service and call it.
+ service.getClass().getMethod("addOptionalCharacteristic", characteristic.getClass()).invoke(service,
+ characteristic);
+ }
+
+ @NonNullByDefault
+ public <T> Optional<T> getCharacteristic(Class<? extends T> klazz) {
+ return Optional.ofNullable((T) rawCharacteristics.get(klazz));
}
/**
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.LoggerFactory;
import io.github.hapjava.characteristics.Characteristic;
-import io.github.hapjava.services.Service;
+import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
/**
* Creates a HomekitAccessory for a given HomekitTaggedItem.
/** List of mandatory attributes for each accessory type. **/
private final static Map<HomekitAccessoryType, HomekitCharacteristicType[]> MANDATORY_CHARACTERISTICS = new HashMap<HomekitAccessoryType, HomekitCharacteristicType[]>() {
{
+ put(ACCESSORY_GROUP, new HomekitCharacteristicType[] {});
put(LEAK_SENSOR, new HomekitCharacteristicType[] { LEAK_DETECTED_STATE });
put(MOTION_SENSOR, new HomekitCharacteristicType[] { MOTION_DETECTED_STATE });
put(OCCUPANCY_SENSOR, new HomekitCharacteristicType[] { OCCUPANCY_DETECTED_STATE });
/** List of service implementation for each accessory type. **/
private final static Map<HomekitAccessoryType, Class<? extends AbstractHomekitAccessoryImpl>> SERVICE_IMPL_MAP = new HashMap<HomekitAccessoryType, Class<? extends AbstractHomekitAccessoryImpl>>() {
{
+ put(ACCESSORY_GROUP, HomekitAccessoryGroupImpl.class);
put(LEAK_SENSOR, HomekitLeakSensorImpl.class);
put(MOTION_SENSOR, HomekitMotionSensorImpl.class);
put(OCCUPANCY_SENSOR, HomekitOccupancySensorImpl.class);
* @throws HomekitException exception in case HomeKit accessory could not be created, e.g. due missing mandatory
* characteristic
*/
- @SuppressWarnings("null")
public static AbstractHomekitAccessoryImpl create(HomekitTaggedItem taggedItem, MetadataRegistry metadataRegistry,
HomekitAccessoryUpdater updater, HomekitSettings settings) throws HomekitException {
+ Set<HomekitTaggedItem> ancestorServices = new HashSet<>();
+ return create(taggedItem, metadataRegistry, updater, settings, ancestorServices);
+ }
+
+ @SuppressWarnings("null")
+ private static AbstractHomekitAccessoryImpl create(HomekitTaggedItem taggedItem, MetadataRegistry metadataRegistry,
+ HomekitAccessoryUpdater updater, HomekitSettings settings, Set<HomekitTaggedItem> ancestorServices)
+ throws HomekitException {
final HomekitAccessoryType accessoryType = taggedItem.getAccessoryType();
logger.trace("Constructing {} of accessory type {}", taggedItem.getName(), accessoryType.getTag());
final List<HomekitTaggedItem> foundCharacteristics = getMandatoryCharacteristicsFromItem(taggedItem,
final @Nullable Class<? extends AbstractHomekitAccessoryImpl> accessoryImplClass = SERVICE_IMPL_MAP
.get(accessoryType);
if (accessoryImplClass != null) {
+ if (ancestorServices.contains(taggedItem)) {
+ logger.warn("Item {} has already been created. Perhaps you have circular Homekit accessory groups?",
+ taggedItem.getName());
+ throw new HomekitException("Circular accessory references");
+ }
+ ancestorServices.add(taggedItem);
accessoryImpl = accessoryImplClass.getConstructor(HomekitTaggedItem.class, List.class,
HomekitAccessoryUpdater.class, HomekitSettings.class)
.newInstance(taggedItem, foundCharacteristics, updater, settings);
addOptionalCharacteristics(taggedItem, accessoryImpl, metadataRegistry);
+ addLinkedServices(taggedItem, accessoryImpl, metadataRegistry, updater, settings, ancestorServices);
return accessoryImpl;
} else {
logger.warn("Unsupported HomeKit type: {}", accessoryType.getTag());
*/
public static List<GroupItem> getAccessoryGroups(Item item, ItemRegistry itemRegistry,
MetadataRegistry metadataRegistry) {
- return (item instanceof GroupItem) ? Collections.emptyList() : item.getGroupNames().stream().flatMap(name -> {
+ return item.getGroupNames().stream().flatMap(name -> {
final @Nullable Item groupItem = itemRegistry.get(name);
- if ((groupItem instanceof GroupItem) && ((GroupItem) groupItem).getBaseItem() == null) {
+ if (groupItem instanceof GroupItem) {
return Stream.of((GroupItem) groupItem);
} else {
return Stream.empty();
MetadataRegistry metadataRegistry) {
Map<HomekitCharacteristicType, GenericItem> characteristics = getOptionalCharacteristics(
accessory.getRootAccessory(), metadataRegistry);
- Service service = accessory.getPrimaryService();
HashMap<String, HomekitOHItemProxy> proxyItems = new HashMap<>();
proxyItems.put(taggedItem.getItem().getUID(), taggedItem.getProxyItem());
// an accessory can have multiple optional characteristics. iterate over them.
getItemConfiguration(item, metadataRegistry));
final Characteristic characteristic = HomekitCharacteristicFactory.createCharacteristic(optionalItem,
accessory.getUpdater());
- // find the corresponding add method at service and call it.
- service.getClass().getMethod("addOptionalCharacteristic", characteristic.getClass()).invoke(service,
- characteristic);
- accessory.addCharacteristic(optionalItem);
+ accessory.addCharacteristic(optionalItem, characteristic);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | HomekitException e) {
- logger.warn("Unsupported optional HomeKit characteristic: service type {}, characteristic type {}",
- service.getType(), type.getTag());
+ logger.warn("Unsupported optional HomeKit characteristic: type {}, characteristic type {}",
+ accessory.getPrimaryService(), type.getTag());
}
});
}
/**
- * collect optional HomeKit characteristics for an OH item.
+ * creates HomeKit services for an openhab item that are members of this group item.
+ *
+ * @param taggedItem openhab item tagged as HomeKit item
+ * @param AbstractHomekitAccessoryImpl the accessory to add services to
+ * @param metadataRegistry openhab metadata registry required to get item meta information
+ * @param updater OH HomeKit update class that ensure the status sync between OH item and corresponding HomeKit
+ * characteristic.
+ * @param settings OH settings
+ * @param ancestorServices set of all accessories/services under the same root accessory, for
+ * for preventing circular references
+ * @throws HomekitException exception in case HomeKit accessory could not be created, e.g. due missing mandatory
+ * characteristic
+ */
+ private static void addLinkedServices(HomekitTaggedItem taggedItem, AbstractHomekitAccessoryImpl accessory,
+ MetadataRegistry metadataRegistry, HomekitAccessoryUpdater updater, HomekitSettings settings,
+ Set<HomekitTaggedItem> ancestorServices) throws HomekitException {
+ final var item = taggedItem.getItem();
+ if (!(item instanceof GroupItem))
+ return;
+
+ for (var groupMember : ((GroupItem) item).getMembers().stream()
+ .sorted((lhs, rhs) -> lhs.getName().compareTo(rhs.getName())).collect(Collectors.toList())) {
+ final var characteristicTypes = getAccessoryTypes(groupMember, metadataRegistry);
+ var accessoryTypes = characteristicTypes.stream().filter(c -> c.getValue() == EMPTY)
+ .collect(Collectors.toList());
+
+ logger.trace("accessory types for {} are {}", groupMember.getName(), accessoryTypes);
+ if (accessoryTypes.isEmpty())
+ continue;
+
+ if (accessoryTypes.size() > 1) {
+ logger.warn("Item {} is a HomeKit sub-accessory, but multiple accessory types are not allowed.",
+ groupMember.getName());
+ continue;
+ }
+
+ final @Nullable Map<String, Object> itemConfiguration = getItemConfiguration(groupMember, metadataRegistry);
+
+ final var accessoryType = accessoryTypes.iterator().next().getKey();
+ logger.trace("Item {} is a HomeKit sub-accessory of type {}.", groupMember.getName(), accessoryType);
+ final var itemProxy = new HomekitOHItemProxy(groupMember);
+ final var subTaggedItem = new HomekitTaggedItem(itemProxy, accessoryType, itemConfiguration);
+ final var subAccessory = create(subTaggedItem, metadataRegistry, updater, settings, ancestorServices);
+
+ try {
+ subAccessory.addCharacteristic(new NameCharacteristic(() -> subAccessory.getName()));
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ // This should never happen; all services should support NameCharacteristic as an optional
+ // Characteristic.
+ // If HAP-Java defined a service that doesn't support addOptionalCharacteristic(NameCharacteristic),
+ // Then it's a bug there, and we're just going to ignore the exception here.
+ }
+
+ if (subAccessory.isLinkable(accessory)) {
+ accessory.getPrimaryService().addLinkedService(subAccessory.getPrimaryService());
+ } else {
+ accessory.getServices().add(subAccessory.getPrimaryService());
+ }
+ }
+ }
+
+ /**
+ * collect optional HomeKit characteristics for a OH item.
*
* @param taggedItem main OH item
* @param metadataRegistry OH metadata registry
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.io.homekit.internal.accessories;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
+import org.openhab.io.homekit.internal.HomekitSettings;
+import org.openhab.io.homekit.internal.HomekitTaggedItem;
+
+/**
+ * Bare accessory (for being the root of a multi-service accessory).
+ *
+ * @author Cody Cutrer - Initial contribution
+ */
+@NonNullByDefault
+public class HomekitAccessoryGroupImpl extends AbstractHomekitAccessoryImpl {
+ public HomekitAccessoryGroupImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
+ HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
+ super(taggedItem, mandatoryCharacteristics, updater, settings);
+ }
+}