From 7568889ba2fdc6384a905ab86ca89089548cd082 Mon Sep 17 00:00:00 2001 From: kupazs Date: Wed, 1 Apr 2020 10:56:05 +0200 Subject: [PATCH 1/2] Add linked services support --- .../java/io/github/hapjava/HomekitRoot.java | 19 ++++++++++ .../github/hapjava/impl/HomekitRegistry.java | 35 +++++++++++++++++-- .../impl/json/AccessoryController.java | 33 +++++++++++++---- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/github/hapjava/HomekitRoot.java b/src/main/java/io/github/hapjava/HomekitRoot.java index 29ed0113c..4ebd3652a 100644 --- a/src/main/java/io/github/hapjava/HomekitRoot.java +++ b/src/main/java/io/github/hapjava/HomekitRoot.java @@ -68,6 +68,15 @@ public void addAccessory(HomekitAccessory accessory) { addAccessorySkipRangeCheck(accessory); } + public void addLinkedAccessory( + HomekitAccessory primaryAccessory, HomekitAccessory linkedAccessory) { + if (linkedAccessory.getId() <= 1 && !(linkedAccessory instanceof Bridge)) { + throw new IndexOutOfBoundsException( + "The ID of an linkedAccessory used in a bridge must be greater than 1"); + } + addLinkedAccessorySkipRangeCheck(primaryAccessory, linkedAccessory); + } + /** * Skips the range check. Used by {@link HomekitStandaloneAccessoryServer} as well as {@link * #addAccessory(HomekitAccessory)}; @@ -83,6 +92,16 @@ void addAccessorySkipRangeCheck(HomekitAccessory accessory) { } } + void addLinkedAccessorySkipRangeCheck( + HomekitAccessory primaryAccessory, HomekitAccessory linkedAccessory) { + this.registry.addLinked(primaryAccessory, linkedAccessory); + logger.info("Added linked accessory " + linkedAccessory.getLabel()); + if (started) { + registry.reset(); + webHandler.resetConnections(); + } + } + /** * Removes an accessory from being handled or advertised by this root. Any existing Homekit * connections will be terminated to allow the clients to reconnect and see the updated accessory diff --git a/src/main/java/io/github/hapjava/impl/HomekitRegistry.java b/src/main/java/io/github/hapjava/impl/HomekitRegistry.java index e3107d44e..2f975c5be 100644 --- a/src/main/java/io/github/hapjava/impl/HomekitRegistry.java +++ b/src/main/java/io/github/hapjava/impl/HomekitRegistry.java @@ -4,7 +4,12 @@ import io.github.hapjava.Service; import io.github.hapjava.characteristics.Characteristic; import io.github.hapjava.impl.services.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,9 @@ public class HomekitRegistry { private final String label; private final Map accessories; + private final Map> linkedAccessories; private final Map> services = new HashMap<>(); + private final Map> linkedServices = new HashMap<>(); private final Map> characteristics = new HashMap<>(); private boolean isAllowUnauthenticatedRequests = false; @@ -23,6 +30,7 @@ public class HomekitRegistry { public HomekitRegistry(String label) { this.label = label; this.accessories = new ConcurrentHashMap<>(); + this.linkedAccessories = new ConcurrentHashMap<>(); reset(); } @@ -35,7 +43,20 @@ public synchronized void reset() { try { newServices = new ArrayList<>(2); newServices.add(new AccessoryInformationService(accessory)); - newServices.addAll(accessory.getServices()); + Collection services = accessory.getServices(); + newServices.addAll(services); + List linkedAccessories = this.linkedAccessories.get(accessory.getId()); + if (linkedAccessories != null) { + for (HomekitAccessory linkedAccessory : linkedAccessories) { + Collection linkedServices = linkedAccessory.getServices(); + for (Service service : services) { + this.linkedServices + .computeIfAbsent(service, k -> new ArrayList<>()) + .addAll(linkedServices); + } + newServices.addAll(linkedServices); + } + } } catch (Exception e) { logger.error("Could not instantiate services for accessory " + accessory.getLabel(), e); services.put(accessory, Collections.emptyList()); @@ -77,6 +98,12 @@ public void add(HomekitAccessory accessory) { accessories.put(accessory.getId(), accessory); } + public void addLinked(HomekitAccessory primaryAccessory, HomekitAccessory linkedAccessory) { + linkedAccessories + .computeIfAbsent(primaryAccessory.getId(), k -> new ArrayList<>()) + .add(linkedAccessory); + } + public void remove(HomekitAccessory accessory) { accessories.remove(accessory.getId()); } @@ -88,4 +115,8 @@ public boolean isAllowUnauthenticatedRequests() { public void setAllowUnauthenticatedRequests(boolean allow) { this.isAllowUnauthenticatedRequests = allow; } + + public List getLinkedServices(Service primaryService) { + return linkedServices.getOrDefault(primaryService, Collections.emptyList()); + } } diff --git a/src/main/java/io/github/hapjava/impl/json/AccessoryController.java b/src/main/java/io/github/hapjava/impl/json/AccessoryController.java index 66c46bdcf..5b88189f4 100644 --- a/src/main/java/io/github/hapjava/impl/json/AccessoryController.java +++ b/src/main/java/io/github/hapjava/impl/json/AccessoryController.java @@ -12,7 +12,9 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObject; @@ -29,13 +31,23 @@ public AccessoryController(HomekitRegistry registry) { public HttpResponse listing() throws Exception { JsonArrayBuilder accessories = Json.createArrayBuilder(); - Map>> accessoryServiceFutures = new HashMap<>(); + Map> idsByAccessory = new HashMap<>(); for (HomekitAccessory accessory : registry.getAccessories()) { + Map ids = idsByAccessory.computeIfAbsent(accessory, k -> new HashMap<>()); int iid = 0; + for (Service service : registry.getServices(accessory.getId())) { + ids.put(service, ++iid); + for (Characteristic characteristic : service.getCharacteristics()) { + ids.put(characteristic, ++iid); + } + } + } + Map>> accessoryServiceFutures = new HashMap<>(); + for (HomekitAccessory accessory : registry.getAccessories()) { List> serviceFutures = new ArrayList<>(); + Map ids = idsByAccessory.get(accessory); for (Service service : registry.getServices(accessory.getId())) { - serviceFutures.add(toJson(service, iid)); - iid += service.getCharacteristics().size() + 1; + serviceFutures.add(toJson(ids, service)); } accessoryServiceFutures.put(accessory.getId(), serviceFutures); } @@ -64,17 +76,20 @@ public HttpResponse listing() throws Exception { } } - private CompletableFuture toJson(Service service, int interfaceId) throws Exception { + private CompletableFuture toJson(Map ids, Service service) + 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", ids.get(service)).add("type", shortType); List characteristics = service.getCharacteristics(); Collection> characteristicFutures = new ArrayList<>(characteristics.size()); for (Characteristic characteristic : characteristics) { - characteristicFutures.add(characteristic.toJson(++interfaceId)); + characteristicFutures.add(characteristic.toJson(ids.get(characteristic))); } + Set linkedServiceIds = + registry.getLinkedServices(service).stream().map(ids::get).collect(Collectors.toSet()); return CompletableFuture.allOf( characteristicFutures.toArray(new CompletableFuture[characteristicFutures.size()])) @@ -85,6 +100,12 @@ private CompletableFuture toJson(Service service, int interfaceId) t .map(future -> future.join()) .forEach(c -> jsonCharacteristics.add(c)); builder.add("characteristics", jsonCharacteristics); + if (!linkedServiceIds.isEmpty()) { + builder.add("primary", true); + JsonArrayBuilder jsonLinkedServices = Json.createArrayBuilder(); + linkedServiceIds.forEach(jsonLinkedServices::add); + builder.add("linked", jsonLinkedServices); + } return builder.build(); }); } From 150c574c6e16ab0c93de5be4710957146fb27659 Mon Sep 17 00:00:00 2001 From: kupazs Date: Wed, 1 Apr 2020 11:08:04 +0200 Subject: [PATCH 2/2] Add linked services support to CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 143958172..a325de721 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,3 +17,4 @@ * Occupancy sensor support [#59](https://github.com/hap-java/HAP-Java/pull/59) * Leak sensors and valve support added [#52](https://github.com/hap-java/HAP-Java/pull/52) * Notifications are batched now, when possible [#66](https://github.com/hap-java/HAP-Java/pull/66) +* Linked services support