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); + } }