Skip to content

Commit

Permalink
[homekit] implement all AccessoryInformationService characteristics (o…
Browse files Browse the repository at this point in the history
…penhab#17031)

mostly as metadata static characteristics

fixes openhab#9595

Signed-off-by: Cody Cutrer <[email protected]>
  • Loading branch information
ccutrer authored and joni1993 committed Oct 15, 2024
1 parent ec448e8 commit f912948
Show file tree
Hide file tree
Showing 43 changed files with 416 additions and 75 deletions.
45 changes: 13 additions & 32 deletions bundles/org.openhab.io.homekit/README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bundles/org.openhab.io.homekit/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<dependency>
<groupId>io.github.hap-java</groupId>
<artifactId>hap</artifactId>
<version>2.0.5</version>
<version>2.0.6</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
*/
package org.openhab.io.homekit.internal;

import java.lang.reflect.InvocationTargetException;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
Expand Down Expand Up @@ -48,7 +47,6 @@
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;

/**
Expand Down Expand Up @@ -476,20 +474,7 @@ private void createRootAccessories(Item item) {
try {
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.
}
}
additionalAccessory.promoteNameCharacteristic();
accessory.getServices().add(additionalAccessory.getPrimaryService());
} catch (HomekitException e) {
logger.warn("Cannot create additional accessory {}", additionalTaggedItem);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ public enum HomekitCharacteristicType {
* It is illegal to have a characteristic type also be a device type
*/
EMPTY("Empty"), // used in case only accessory type but no characteristic provided

NAME("Name"),
MANUFACTURER("Manufacturer"),
MODEL("Model"),
SERIAL_NUMBER("SerialNumber"),
FIRMWARE_REVISION("FirmwareRevision"),
HARDWARE_REVISION("HardwareRevision"),
IDENTIFY("Identify"),

BATTERY_LOW_STATUS("BatteryLowStatus"),
ACTIVE_STATUS("ActiveStatus"),
ISCONFIGURED("IsConfigured"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,16 @@
import io.github.hapjava.characteristics.Characteristic;
import io.github.hapjava.characteristics.CharacteristicEnum;
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
import io.github.hapjava.characteristics.impl.accessoryinformation.FirmwareRevisionCharacteristic;
import io.github.hapjava.characteristics.impl.accessoryinformation.HardwareRevisionCharacteristic;
import io.github.hapjava.characteristics.impl.accessoryinformation.IdentifyCharacteristic;
import io.github.hapjava.characteristics.impl.accessoryinformation.ManufacturerCharacteristic;
import io.github.hapjava.characteristics.impl.accessoryinformation.ModelCharacteristic;
import io.github.hapjava.characteristics.impl.accessoryinformation.SerialNumberCharacteristic;
import io.github.hapjava.characteristics.impl.base.BaseCharacteristic;
import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
import io.github.hapjava.services.Service;
import io.github.hapjava.services.impl.AccessoryInformationService;

/**
* Abstract class for Homekit Accessory implementations, this provides the
Expand All @@ -62,6 +70,7 @@ public abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
private final HomekitSettings settings;
private final List<Service> services;
private final Map<Class<? extends Characteristic>, Characteristic> rawCharacteristics;
private boolean isLinkedService = false;

public AbstractHomekitAccessoryImpl(HomekitTaggedItem accessory, List<HomekitTaggedItem> characteristics,
HomekitAccessoryUpdater updater, HomekitSettings settings) {
Expand All @@ -88,6 +97,52 @@ public AbstractHomekitAccessoryImpl(HomekitTaggedItem accessory, List<HomekitTag
* @throws HomekitException
*/
public void init() throws HomekitException {
// initialize the AccessoryInformation Service with defaults if not specified
if (!rawCharacteristics.containsKey(NameCharacteristic.class)) {
rawCharacteristics.put(NameCharacteristic.class, new NameCharacteristic(() -> {
return CompletableFuture.completedFuture(accessory.getItem().getLabel());
}));
}

if (!isLinkedService()) {
if (!rawCharacteristics.containsKey(IdentifyCharacteristic.class)) {
rawCharacteristics.put(IdentifyCharacteristic.class, new IdentifyCharacteristic(v -> {
}));
}
if (!rawCharacteristics.containsKey(ManufacturerCharacteristic.class)) {
rawCharacteristics.put(ManufacturerCharacteristic.class, new ManufacturerCharacteristic(() -> {
return CompletableFuture.completedFuture("none");
}));
}
if (!rawCharacteristics.containsKey(ModelCharacteristic.class)) {
rawCharacteristics.put(ModelCharacteristic.class, new ModelCharacteristic(() -> {
return CompletableFuture.completedFuture("none");
}));
}
if (!rawCharacteristics.containsKey(SerialNumberCharacteristic.class)) {
rawCharacteristics.put(SerialNumberCharacteristic.class, new SerialNumberCharacteristic(() -> {
return CompletableFuture.completedFuture(accessory.getItem().getName());
}));
}
if (!rawCharacteristics.containsKey(FirmwareRevisionCharacteristic.class)) {
rawCharacteristics.put(FirmwareRevisionCharacteristic.class, new FirmwareRevisionCharacteristic(() -> {
return CompletableFuture.completedFuture("none");
}));
}

var service = new AccessoryInformationService(getCharacteristic(IdentifyCharacteristic.class).get(),
getCharacteristic(ManufacturerCharacteristic.class).get(),
getCharacteristic(ModelCharacteristic.class).get(),
getCharacteristic(NameCharacteristic.class).get(),
getCharacteristic(SerialNumberCharacteristic.class).get(),
getCharacteristic(FirmwareRevisionCharacteristic.class).get());

getCharacteristic(HardwareRevisionCharacteristic.class)
.ifPresent(c -> service.addOptionalCharacteristic(c));

// make sure this is the first service
services.add(0, service);
}
}

/**
Expand All @@ -99,6 +154,20 @@ public boolean isLinkable(HomekitAccessory parentAccessory) {
return false;
}

/**
* Sets if this accessory is being used as a linked service.
*/
public void setIsLinkedService(boolean value) {
isLinkedService = value;
}

/**
* @return If this accessory is being used as a linked service.
*/
public boolean isLinkedService() {
return isLinkedService;
}

/**
* @return If this accessory is only valid as a linked service, not as a standalone accessory.
*/
Expand All @@ -118,32 +187,36 @@ public int getId() {

@Override
public CompletableFuture<String> getName() {
return CompletableFuture.completedFuture(accessory.getItem().getLabel());
return getCharacteristic(NameCharacteristic.class).get().getValue();
}

@Override
public CompletableFuture<String> getManufacturer() {
return CompletableFuture.completedFuture("none");
return getCharacteristic(ManufacturerCharacteristic.class).get().getValue();
}

@Override
public CompletableFuture<String> getModel() {
return CompletableFuture.completedFuture("none");
return getCharacteristic(ModelCharacteristic.class).get().getValue();
}

@Override
public CompletableFuture<String> getSerialNumber() {
return CompletableFuture.completedFuture(accessory.getItem().getName());
return getCharacteristic(SerialNumberCharacteristic.class).get().getValue();
}

@Override
public CompletableFuture<String> getFirmwareRevision() {
return CompletableFuture.completedFuture("none");
return getCharacteristic(FirmwareRevisionCharacteristic.class).get().getValue();
}

@Override
public void identify() {
// We're not going to support this for now
try {
getCharacteristic(IdentifyCharacteristic.class).get().setValue(true);
} catch (Exception e) {
// ignore
}
}

public HomekitTaggedItem getRootAccessory() {
Expand Down Expand Up @@ -356,6 +429,31 @@ public void addCharacteristic(Characteristic characteristic)
}
}

/**
* Takes the NameCharacteristic that normally exists on the AccessoryInformationService,
* and puts it on the primary service.
*/
public void promoteNameCharacteristic() {
var characteristic = getCharacteristic(NameCharacteristic.class);
if (!characteristic.isPresent()) {
return;
}

var service = getPrimaryService();
if (service != null) {
try {
// find the corresponding add method at service and call it.
service.getClass().getMethod("addOptionalCharacteristic", NameCharacteristic.class).invoke(service,
characteristic.get());
} 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.
}
}
}

@NonNullByDefault
public <T> Optional<T> getCharacteristic(Class<? extends T> klazz) {
return Optional.ofNullable((T) rawCharacteristics.get(klazz));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
import org.slf4j.LoggerFactory;

import io.github.hapjava.characteristics.Characteristic;
import io.github.hapjava.characteristics.impl.common.NameCharacteristic;

/**
* Creates a HomekitAccessory for a given HomekitTaggedItem.
Expand Down Expand Up @@ -211,13 +210,14 @@ private static AbstractHomekitAccessoryImpl create(HomekitTaggedItem taggedItem,
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);
addOptionalMetadataCharacteristics(taggedItem, accessoryImpl);
accessoryImpl.setIsLinkedService(!ancestorServices.isEmpty());
accessoryImpl.init();
ancestorServices.add(taggedItem);
addLinkedServices(taggedItem, accessoryImpl, metadataRegistry, updater, settings, ancestorServices);
return accessoryImpl;
} else {
Expand Down Expand Up @@ -467,15 +467,7 @@ private static void addLinkedServices(HomekitTaggedItem taggedItem, AbstractHome
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.
}
subAccessory.promoteNameCharacteristic();

if (subAccessory.isLinkable(accessory)) {
accessory.getPrimaryService().addLinkedService(subAccessory.getPrimaryService());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.concurrent.CompletableFuture;

import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitException;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;

Expand All @@ -39,6 +40,11 @@ public HomekitAirQualitySensorImpl(HomekitTaggedItem taggedItem, List<HomekitTag
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
super(taggedItem, mandatoryCharacteristics, updater, settings);
qualityStateMapping = createMapping(AIR_QUALITY, AirQualityEnum.class);
}

@Override
public void init() throws HomekitException {
super.init();
getServices().add(new AirQualityService(this));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitException;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;

Expand All @@ -39,6 +40,11 @@ public HomekitBasicFanImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem>
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
super(taggedItem, mandatoryCharacteristics, updater, settings);
onReader = createBooleanReader(ON_STATE);
}

@Override
public void init() throws HomekitException {
super.init();
this.getServices().add(new BasicFanService(this));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.openhab.core.library.types.DecimalType;
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
import org.openhab.io.homekit.internal.HomekitException;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;

Expand Down Expand Up @@ -56,6 +57,11 @@ public HomekitBatteryImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem>
if (isChargeable) {
chargingBatteryReader = createBooleanReader(BATTERY_CHARGING_STATE);
}
}

@Override
public void init() throws HomekitException {
super.init();
getServices().add(new BatteryService(this));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.concurrent.CompletableFuture;

import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitException;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;

Expand All @@ -40,6 +41,11 @@ public HomekitCarbonDioxideSensorImpl(HomekitTaggedItem taggedItem,
throws IncompleteAccessoryException {
super(taggedItem, mandatoryCharacteristics, updater, settings);
mapping = createMapping(CARBON_DIOXIDE_DETECTED_STATE, CarbonDioxideDetectedEnum.class);
}

@Override
public void init() throws HomekitException {
super.init();
getServices().add(new CarbonDioxideSensorService(this));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.concurrent.CompletableFuture;

import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitException;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;

Expand All @@ -40,6 +41,11 @@ public HomekitCarbonMonoxideSensorImpl(HomekitTaggedItem taggedItem,
throws IncompleteAccessoryException {
super(taggedItem, mandatoryCharacteristics, updater, settings);
mapping = createMapping(CARBON_MONOXIDE_DETECTED_STATE, CarbonMonoxideDetectedEnum.class);
}

@Override
public void init() throws HomekitException {
super.init();
getServices().add(new CarbonMonoxideSensorService(this));
}

Expand Down
Loading

0 comments on commit f912948

Please sign in to comment.