From 0436d88a8c5b89aa00a1a86f93f90bdc82a6f004 Mon Sep 17 00:00:00 2001 From: Dennis Frommknecht Date: Fri, 16 Oct 2020 16:27:46 +0200 Subject: [PATCH] add support for linked services Some services require linked services to work, for example, the Television service which requires each input to be a linked service. With this change, support to link services is added, though it's not used in any existing service, as they don't need linking. This change also centralizes the generation of interface ids. They are now only generated in the registry. We were previously also generating them in the accessory controller, relying on an identical processing sequence. --- CHANGES.md | 1 + .../hapjava/server/impl/HomekitRegistry.java | 32 +++++++++++------ .../server/impl/json/AccessoryController.java | 36 +++++++++++++++---- .../io/github/hapjava/services/Service.java | 7 ++++ .../services/impl/AbstractServiceImpl.java | 10 ++++++ 5 files changed, 68 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7680cec66..3da9222e2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ * `services` package consists of services, which grouping characteristics. e.g. `WindowCoveringService` defines mandatory and optional characteristics for a window covering service as it is defined in HAP spec. * `server` package consists classes to run HomeKit server and handle communication * the process is following: client, e.g. openHAB bindings, extends accessory classes, e.g. `WindowCoveringAccessory` and implements all required methods. WindowCoveringAccessory is linked already to WindowCoveringService, that in turn is link to single characteristics. +* linked service support # HAP-Java 1.1.5 diff --git a/src/main/java/io/github/hapjava/server/impl/HomekitRegistry.java b/src/main/java/io/github/hapjava/server/impl/HomekitRegistry.java index f655c1879..39ec31ff9 100644 --- a/src/main/java/io/github/hapjava/server/impl/HomekitRegistry.java +++ b/src/main/java/io/github/hapjava/server/impl/HomekitRegistry.java @@ -4,7 +4,12 @@ import io.github.hapjava.characteristics.Characteristic; import io.github.hapjava.services.Service; import io.github.hapjava.services.impl.AccessoryInformationService; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +20,7 @@ public class HomekitRegistry { private final String label; private final Map accessories; - private final Map> services = new HashMap<>(); + private final Map> services = new HashMap<>(); private final Map> characteristics = new HashMap<>(); private boolean isAllowUnauthenticatedRequests = false; @@ -35,21 +40,26 @@ public synchronized void reset() { try { newServices = new ArrayList<>(2); newServices.add(new AccessoryInformationService(accessory)); - newServices.addAll(accessory.getServices()); + for (Service service : accessory.getServices()) { + newServices.add(service); + newServices.addAll(service.getLinkedServices()); + } } catch (Exception e) { logger.warn("Could not instantiate services for accessory " + accessory.getName(), e); - services.put(accessory, Collections.emptyList()); + services.put(accessory, Collections.emptyMap()); continue; } - Map newCharacteristics = new HashMap<>(); - services.put(accessory, newServices); + + Map newCharacteristicsByInterfaceId = new HashMap<>(); + Map newServicesByInterfaceId = new HashMap<>(); for (Service service : newServices) { - iid++; + newServicesByInterfaceId.put(++iid, service); for (Characteristic characteristic : service.getCharacteristics()) { - newCharacteristics.put(++iid, characteristic); + newCharacteristicsByInterfaceId.put(++iid, characteristic); } } - characteristics.put(accessory, newCharacteristics); + services.put(accessory, newServicesByInterfaceId); + characteristics.put(accessory, newCharacteristicsByInterfaceId); } } @@ -61,8 +71,8 @@ public Collection getAccessories() { return accessories.values(); } - public List getServices(Integer aid) { - return Collections.unmodifiableList(services.get(accessories.get(aid))); + public Map getServices(Integer aid) { + return Collections.unmodifiableMap(services.get(accessories.get(aid))); } public Map getCharacteristics(Integer aid) { diff --git a/src/main/java/io/github/hapjava/server/impl/json/AccessoryController.java b/src/main/java/io/github/hapjava/server/impl/json/AccessoryController.java index fb53c6315..98e6cd266 100644 --- a/src/main/java/io/github/hapjava/server/impl/json/AccessoryController.java +++ b/src/main/java/io/github/hapjava/server/impl/json/AccessoryController.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObject; @@ -31,12 +32,18 @@ public HttpResponse listing() throws Exception { Map>> accessoryServiceFutures = new HashMap<>(); for (HomekitAccessory accessory : registry.getAccessories()) { - int iid = 0; List> serviceFutures = new ArrayList<>(); - for (Service service : registry.getServices(accessory.getId())) { - serviceFutures.add(toJson(service, iid)); - iid += service.getCharacteristics().size() + 1; + + Map servicesByInterfaceId = registry.getServices(accessory.getId()); + + Map iidLookup = new HashMap<>(); + iidLookup.putAll(swapKeyAndValue(servicesByInterfaceId)); + iidLookup.putAll(swapKeyAndValue(registry.getCharacteristics(accessory.getId()))); + + for (Service service : servicesByInterfaceId.values()) { + serviceFutures.add(toJson(service, iidLookup)); } + accessoryServiceFutures.put(accessory.getId(), serviceFutures); } @@ -64,16 +71,18 @@ public HttpResponse listing() throws Exception { } } - private CompletableFuture toJson(Service service, int interfaceId) throws Exception { + private CompletableFuture toJson(Service service, Map iidLookup) + throws Exception { String shortType = service.getType().replaceAll("^0*([0-9a-fA-F]+)-0000-1000-8000-0026BB765291$", "$1"); JsonObjectBuilder builder = - Json.createObjectBuilder().add("iid", ++interfaceId).add("type", shortType); + Json.createObjectBuilder().add("iid", iidLookup.get(service)).add("type", shortType); List characteristics = service.getCharacteristics(); Collection> characteristicFutures = new ArrayList<>(characteristics.size()); for (Characteristic characteristic : characteristics) { - characteristicFutures.add(characteristic.toJson(++interfaceId)); + Integer iid = iidLookup.get(characteristic); + characteristicFutures.add(characteristic.toJson(iid)); } return CompletableFuture.allOf( @@ -85,7 +94,20 @@ private CompletableFuture toJson(Service service, int interfaceId) t .map(future -> future.join()) .forEach(c -> jsonCharacteristics.add(c)); builder.add("characteristics", jsonCharacteristics); + + if (!service.getLinkedServices().isEmpty()) { + JsonArrayBuilder jsonLinkedServices = Json.createArrayBuilder(); + service.getLinkedServices().stream() + .map(iidLookup::get) + .forEach(jsonLinkedServices::add); + builder.add("linked", jsonLinkedServices); + } + return builder.build(); }); } + + private Map swapKeyAndValue(Map map) { + return map.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey)); + } } diff --git a/src/main/java/io/github/hapjava/services/Service.java b/src/main/java/io/github/hapjava/services/Service.java index aab8ef7e4..0fa65156e 100644 --- a/src/main/java/io/github/hapjava/services/Service.java +++ b/src/main/java/io/github/hapjava/services/Service.java @@ -29,4 +29,11 @@ public interface Service { * ########-####-####-####-############. */ String getType(); + + /** + * List of all the services to which the service links + * + * @return the list of linked services. + */ + List getLinkedServices(); } diff --git a/src/main/java/io/github/hapjava/services/impl/AbstractServiceImpl.java b/src/main/java/io/github/hapjava/services/impl/AbstractServiceImpl.java index 97f99906d..9db2a2a2d 100644 --- a/src/main/java/io/github/hapjava/services/impl/AbstractServiceImpl.java +++ b/src/main/java/io/github/hapjava/services/impl/AbstractServiceImpl.java @@ -12,6 +12,7 @@ abstract class AbstractServiceImpl implements Service { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final String type; private final List characteristics = new LinkedList<>(); + private final List linkedServices = new LinkedList<>(); /** @param type unique UUID of the service according to HAP specification. */ public AbstractServiceImpl(String type) { @@ -28,7 +29,16 @@ public String getType() { return type; } + @Override + public List getLinkedServices() { + return Collections.unmodifiableList(linkedServices); + } + public void addCharacteristic(Characteristic characteristic) { this.characteristics.add(characteristic); } + + public void addLinkedService(Service service) { + this.linkedServices.add(service); + } }