Skip to content

Commit 0436d88

Browse files
committed
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.
1 parent 7145c94 commit 0436d88

File tree

5 files changed

+68
-18
lines changed

5 files changed

+68
-18
lines changed

Diff for: CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* `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.
88
* `server` package consists classes to run HomeKit server and handle communication
99
* 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.
10+
* linked service support
1011

1112
# HAP-Java 1.1.5
1213

Diff for: src/main/java/io/github/hapjava/server/impl/HomekitRegistry.java

+21-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
import io.github.hapjava.characteristics.Characteristic;
55
import io.github.hapjava.services.Service;
66
import io.github.hapjava.services.impl.AccessoryInformationService;
7-
import java.util.*;
7+
import java.util.ArrayList;
8+
import java.util.Collection;
9+
import java.util.Collections;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
813
import java.util.concurrent.ConcurrentHashMap;
914
import org.slf4j.Logger;
1015
import org.slf4j.LoggerFactory;
@@ -15,7 +20,7 @@ public class HomekitRegistry {
1520

1621
private final String label;
1722
private final Map<Integer, HomekitAccessory> accessories;
18-
private final Map<HomekitAccessory, List<Service>> services = new HashMap<>();
23+
private final Map<HomekitAccessory, Map<Integer, Service>> services = new HashMap<>();
1924
private final Map<HomekitAccessory, Map<Integer, Characteristic>> characteristics =
2025
new HashMap<>();
2126
private boolean isAllowUnauthenticatedRequests = false;
@@ -35,21 +40,26 @@ public synchronized void reset() {
3540
try {
3641
newServices = new ArrayList<>(2);
3742
newServices.add(new AccessoryInformationService(accessory));
38-
newServices.addAll(accessory.getServices());
43+
for (Service service : accessory.getServices()) {
44+
newServices.add(service);
45+
newServices.addAll(service.getLinkedServices());
46+
}
3947
} catch (Exception e) {
4048
logger.warn("Could not instantiate services for accessory " + accessory.getName(), e);
41-
services.put(accessory, Collections.emptyList());
49+
services.put(accessory, Collections.emptyMap());
4250
continue;
4351
}
44-
Map<Integer, Characteristic> newCharacteristics = new HashMap<>();
45-
services.put(accessory, newServices);
52+
53+
Map<Integer, Characteristic> newCharacteristicsByInterfaceId = new HashMap<>();
54+
Map<Integer, Service> newServicesByInterfaceId = new HashMap<>();
4655
for (Service service : newServices) {
47-
iid++;
56+
newServicesByInterfaceId.put(++iid, service);
4857
for (Characteristic characteristic : service.getCharacteristics()) {
49-
newCharacteristics.put(++iid, characteristic);
58+
newCharacteristicsByInterfaceId.put(++iid, characteristic);
5059
}
5160
}
52-
characteristics.put(accessory, newCharacteristics);
61+
services.put(accessory, newServicesByInterfaceId);
62+
characteristics.put(accessory, newCharacteristicsByInterfaceId);
5363
}
5464
}
5565

@@ -61,8 +71,8 @@ public Collection<HomekitAccessory> getAccessories() {
6171
return accessories.values();
6272
}
6373

64-
public List<Service> getServices(Integer aid) {
65-
return Collections.unmodifiableList(services.get(accessories.get(aid)));
74+
public Map<Integer, Service> getServices(Integer aid) {
75+
return Collections.unmodifiableMap(services.get(accessories.get(aid)));
6676
}
6777

6878
public Map<Integer, Characteristic> getCharacteristics(Integer aid) {

Diff for: src/main/java/io/github/hapjava/server/impl/json/AccessoryController.java

+29-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.Map;
1414
import java.util.Map.Entry;
1515
import java.util.concurrent.CompletableFuture;
16+
import java.util.stream.Collectors;
1617
import javax.json.Json;
1718
import javax.json.JsonArrayBuilder;
1819
import javax.json.JsonObject;
@@ -31,12 +32,18 @@ public HttpResponse listing() throws Exception {
3132

3233
Map<Integer, List<CompletableFuture<JsonObject>>> accessoryServiceFutures = new HashMap<>();
3334
for (HomekitAccessory accessory : registry.getAccessories()) {
34-
int iid = 0;
3535
List<CompletableFuture<JsonObject>> serviceFutures = new ArrayList<>();
36-
for (Service service : registry.getServices(accessory.getId())) {
37-
serviceFutures.add(toJson(service, iid));
38-
iid += service.getCharacteristics().size() + 1;
36+
37+
Map<Integer, Service> servicesByInterfaceId = registry.getServices(accessory.getId());
38+
39+
Map<Object, Integer> iidLookup = new HashMap<>();
40+
iidLookup.putAll(swapKeyAndValue(servicesByInterfaceId));
41+
iidLookup.putAll(swapKeyAndValue(registry.getCharacteristics(accessory.getId())));
42+
43+
for (Service service : servicesByInterfaceId.values()) {
44+
serviceFutures.add(toJson(service, iidLookup));
3945
}
46+
4047
accessoryServiceFutures.put(accessory.getId(), serviceFutures);
4148
}
4249

@@ -64,16 +71,18 @@ public HttpResponse listing() throws Exception {
6471
}
6572
}
6673

67-
private CompletableFuture<JsonObject> toJson(Service service, int interfaceId) throws Exception {
74+
private CompletableFuture<JsonObject> toJson(Service service, Map<Object, Integer> iidLookup)
75+
throws Exception {
6876
String shortType =
6977
service.getType().replaceAll("^0*([0-9a-fA-F]+)-0000-1000-8000-0026BB765291$", "$1");
7078
JsonObjectBuilder builder =
71-
Json.createObjectBuilder().add("iid", ++interfaceId).add("type", shortType);
79+
Json.createObjectBuilder().add("iid", iidLookup.get(service)).add("type", shortType);
7280
List<Characteristic> characteristics = service.getCharacteristics();
7381
Collection<CompletableFuture<JsonObject>> characteristicFutures =
7482
new ArrayList<>(characteristics.size());
7583
for (Characteristic characteristic : characteristics) {
76-
characteristicFutures.add(characteristic.toJson(++interfaceId));
84+
Integer iid = iidLookup.get(characteristic);
85+
characteristicFutures.add(characteristic.toJson(iid));
7786
}
7887

7988
return CompletableFuture.allOf(
@@ -85,7 +94,20 @@ private CompletableFuture<JsonObject> toJson(Service service, int interfaceId) t
8594
.map(future -> future.join())
8695
.forEach(c -> jsonCharacteristics.add(c));
8796
builder.add("characteristics", jsonCharacteristics);
97+
98+
if (!service.getLinkedServices().isEmpty()) {
99+
JsonArrayBuilder jsonLinkedServices = Json.createArrayBuilder();
100+
service.getLinkedServices().stream()
101+
.map(iidLookup::get)
102+
.forEach(jsonLinkedServices::add);
103+
builder.add("linked", jsonLinkedServices);
104+
}
105+
88106
return builder.build();
89107
});
90108
}
109+
110+
private <K, V> Map<V, K> swapKeyAndValue(Map<K, V> map) {
111+
return map.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey));
112+
}
91113
}

Diff for: src/main/java/io/github/hapjava/services/Service.java

+7
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,11 @@ public interface Service {
2929
* ########-####-####-####-############.
3030
*/
3131
String getType();
32+
33+
/**
34+
* List of all the services to which the service links
35+
*
36+
* @return the list of linked services.
37+
*/
38+
List<Service> getLinkedServices();
3239
}

Diff for: src/main/java/io/github/hapjava/services/impl/AbstractServiceImpl.java

+10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ abstract class AbstractServiceImpl implements Service {
1212
private final Logger logger = LoggerFactory.getLogger(this.getClass());
1313
private final String type;
1414
private final List<Characteristic> characteristics = new LinkedList<>();
15+
private final List<Service> linkedServices = new LinkedList<>();
1516

1617
/** @param type unique UUID of the service according to HAP specification. */
1718
public AbstractServiceImpl(String type) {
@@ -28,7 +29,16 @@ public String getType() {
2829
return type;
2930
}
3031

32+
@Override
33+
public List<Service> getLinkedServices() {
34+
return Collections.unmodifiableList(linkedServices);
35+
}
36+
3137
public void addCharacteristic(Characteristic characteristic) {
3238
this.characteristics.add(characteristic);
3339
}
40+
41+
public void addLinkedService(Service service) {
42+
this.linkedServices.add(service);
43+
}
3444
}

0 commit comments

Comments
 (0)