diff --git a/src/main/java/io/github/hapjava/accessories/CarbonDioxideSensor.java b/src/main/java/io/github/hapjava/accessories/CarbonDioxideSensor.java
new file mode 100644
index 000000000..27c332471
--- /dev/null
+++ b/src/main/java/io/github/hapjava/accessories/CarbonDioxideSensor.java
@@ -0,0 +1,60 @@
+package io.github.hapjava.accessories;
+
+import io.github.hapjava.HomekitAccessory;
+import io.github.hapjava.HomekitCharacteristicChangeCallback;
+import io.github.hapjava.Service;
+import io.github.hapjava.accessories.properties.CarbonDioxideDetectedState;
+import io.github.hapjava.impl.services.CarbonDioxideSensorService;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A carbon dioxide sensor reports whether carbon dioxide has been detected or not.
+ *
+ *
Carbon dioxide sensors that run on batteries will need to implement this interface and also
+ * implement {@link BatteryStatusAccessory}.
+ *
+ * @author Eugen Freiter
+ */
+public interface CarbonDioxideSensor extends HomekitAccessory {
+
+ /**
+ * Retrieves the state of the sensor that indicates if carbon dioxide has been detected.
+ *
+ * @return a future that will contain the carbon dioxide sensor's state
+ */
+ CompletableFuture getCarbonDioxideDetectedState();
+
+ @Override
+ default Collection getServices() {
+ return Collections.singleton(new CarbonDioxideSensorService(this));
+ }
+
+ /**
+ * Subscribes to changes in the carbon dioxide's state.
+ *
+ * @param callback the function to call when the state changes.
+ */
+ void subscribeCarbonDioxideDetectedState(HomekitCharacteristicChangeCallback callback);
+
+ /**
+ * Retrieves the carbon dioxide level
+ *
+ * @return a future that will contain the carbon dioxide level as a value between 0 and 100000
+ */
+ CompletableFuture getCarbonDioxideLevel();
+
+ /** Unsubscribes from changes in the carbon dioxide's state. */
+ void unsubscribeCarbonDioxideDetectedState();
+
+ /**
+ * Subscribes to changes in the carbon dioxide level.
+ *
+ * @param callback the function to call when the state changes.
+ */
+ void subscribeCarbonDioxideLevel(HomekitCharacteristicChangeCallback callback);
+
+ /** Unsubscribes from changes in the carbon dioxide level. */
+ void unsubscribeCarbonDioxideLevel();
+}
diff --git a/src/main/java/io/github/hapjava/accessories/properties/CarbonDioxideDetectedState.java b/src/main/java/io/github/hapjava/accessories/properties/CarbonDioxideDetectedState.java
new file mode 100644
index 000000000..66dabf5b6
--- /dev/null
+++ b/src/main/java/io/github/hapjava/accessories/properties/CarbonDioxideDetectedState.java
@@ -0,0 +1,32 @@
+package io.github.hapjava.accessories.properties;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public enum CarbonDioxideDetectedState {
+ NORMAL(0),
+ ABNORMAL(1);
+
+ private static final Map reverse;
+
+ static {
+ reverse =
+ Arrays.stream(CarbonDioxideDetectedState.values())
+ .collect(Collectors.toMap(CarbonDioxideDetectedState::getCode, t -> t));
+ }
+
+ public static CarbonDioxideDetectedState fromCode(Integer code) {
+ return reverse.get(code);
+ }
+
+ private final int code;
+
+ CarbonDioxideDetectedState(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/src/main/java/io/github/hapjava/impl/characteristics/carbondioxide/CarbonDioxideDetectedCharacteristic.java b/src/main/java/io/github/hapjava/impl/characteristics/carbondioxide/CarbonDioxideDetectedCharacteristic.java
new file mode 100644
index 000000000..5ce03856a
--- /dev/null
+++ b/src/main/java/io/github/hapjava/impl/characteristics/carbondioxide/CarbonDioxideDetectedCharacteristic.java
@@ -0,0 +1,41 @@
+package io.github.hapjava.impl.characteristics.carbondioxide;
+
+import io.github.hapjava.HomekitCharacteristicChangeCallback;
+import io.github.hapjava.accessories.CarbonDioxideSensor;
+import io.github.hapjava.accessories.properties.CarbonDioxideDetectedState;
+import io.github.hapjava.characteristics.EnumCharacteristic;
+import io.github.hapjava.characteristics.EventableCharacteristic;
+import java.util.concurrent.CompletableFuture;
+
+public class CarbonDioxideDetectedCharacteristic extends EnumCharacteristic
+ implements EventableCharacteristic {
+
+ private final CarbonDioxideSensor carbonDioxideSensor;
+
+ public CarbonDioxideDetectedCharacteristic(CarbonDioxideSensor carbonDioxideSensor) {
+ super("00000092-0000-1000-8000-0026BB765291", false, true, "Carbon Dioxide Detected", 1);
+ this.carbonDioxideSensor = carbonDioxideSensor;
+ }
+
+ @Override
+ protected CompletableFuture getValue() {
+ return carbonDioxideSensor
+ .getCarbonDioxideDetectedState()
+ .thenApply(CarbonDioxideDetectedState::getCode);
+ }
+
+ @Override
+ protected void setValue(Integer value) throws Exception {
+ // Read Only
+ }
+
+ @Override
+ public void subscribe(HomekitCharacteristicChangeCallback callback) {
+ carbonDioxideSensor.subscribeCarbonDioxideDetectedState(callback);
+ }
+
+ @Override
+ public void unsubscribe() {
+ carbonDioxideSensor.unsubscribeCarbonDioxideDetectedState();
+ }
+}
diff --git a/src/main/java/io/github/hapjava/impl/characteristics/carbondioxide/CarbonDioxideLevelCharacteristic.java b/src/main/java/io/github/hapjava/impl/characteristics/carbondioxide/CarbonDioxideLevelCharacteristic.java
new file mode 100644
index 000000000..f6f6ecc9e
--- /dev/null
+++ b/src/main/java/io/github/hapjava/impl/characteristics/carbondioxide/CarbonDioxideLevelCharacteristic.java
@@ -0,0 +1,54 @@
+package io.github.hapjava.impl.characteristics.carbondioxide;
+
+import io.github.hapjava.HomekitCharacteristicChangeCallback;
+import io.github.hapjava.accessories.CarbonDioxideSensor;
+import io.github.hapjava.characteristics.EventableCharacteristic;
+import io.github.hapjava.characteristics.FloatCharacteristic;
+import java.util.concurrent.CompletableFuture;
+
+public class CarbonDioxideLevelCharacteristic extends FloatCharacteristic
+ implements EventableCharacteristic {
+
+ private final CarbonDioxideSensor sensor;
+
+ public CarbonDioxideLevelCharacteristic(CarbonDioxideSensor sensor) {
+ super(
+ "00000093-0000-1000-8000-0026BB765291",
+ false,
+ true,
+ "Carbon Dioxide level",
+ 0,
+ 100000,
+ 0.1,
+ "%");
+ this.sensor = sensor;
+ }
+
+ @Override
+ public void subscribe(HomekitCharacteristicChangeCallback callback) {
+ sensor.subscribeCarbonDioxideLevel(callback);
+ }
+
+ @Override
+ public void unsubscribe() {
+ sensor.unsubscribeCarbonDioxideLevel();
+ }
+
+ @Override
+ protected void setValue(Double value) throws Exception {
+ // Read Only
+ }
+
+ @Override
+ protected CompletableFuture getDoubleValue() {
+ return sensor.getCarbonDioxideLevel();
+ }
+
+ @Override
+ public String toString() {
+ return "CarbonDioxideLevelCharacteristic{"
+ + "sensor level ="
+ + sensor.getCarbonDioxideLevel()
+ + '}';
+ }
+}
diff --git a/src/main/java/io/github/hapjava/impl/connections/HttpSession.java b/src/main/java/io/github/hapjava/impl/connections/HttpSession.java
index 3ec22c42f..4a7efe7d9 100644
--- a/src/main/java/io/github/hapjava/impl/connections/HttpSession.java
+++ b/src/main/java/io/github/hapjava/impl/connections/HttpSession.java
@@ -66,6 +66,8 @@ public HttpResponse handleRequest(HttpRequest request) throws IOException {
}
public HttpResponse handleAuthenticatedRequest(HttpRequest request) throws IOException {
+ advertiser.setDiscoverable(
+ false); // brigde is already bound and should not be discoverable anymore
try {
switch (request.getUri()) {
case "/accessories":
@@ -101,7 +103,7 @@ private HttpResponse handlePairSetup(HttpRequest request) {
if (pairingManager == null) {
synchronized (HttpSession.class) {
if (pairingManager == null) {
- pairingManager = new PairingManager(authInfo, registry, advertiser);
+ pairingManager = new PairingManager(authInfo, registry);
}
}
}
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 9962fd58a..66c46bdcf 100644
--- a/src/main/java/io/github/hapjava/impl/json/AccessoryController.java
+++ b/src/main/java/io/github/hapjava/impl/json/AccessoryController.java
@@ -81,8 +81,7 @@ private CompletableFuture toJson(Service service, int interfaceId) t
.thenApply(
v -> {
JsonArrayBuilder jsonCharacteristics = Json.createArrayBuilder();
- characteristicFutures
- .stream()
+ characteristicFutures.stream()
.map(future -> future.join())
.forEach(c -> jsonCharacteristics.add(c));
builder.add("characteristics", jsonCharacteristics);
diff --git a/src/main/java/io/github/hapjava/impl/pairing/FinalPairHandler.java b/src/main/java/io/github/hapjava/impl/pairing/FinalPairHandler.java
index 9ba8d1443..29b8b88a3 100644
--- a/src/main/java/io/github/hapjava/impl/pairing/FinalPairHandler.java
+++ b/src/main/java/io/github/hapjava/impl/pairing/FinalPairHandler.java
@@ -3,7 +3,6 @@
import io.github.hapjava.HomekitAuthInfo;
import io.github.hapjava.impl.crypto.*;
import io.github.hapjava.impl.http.HttpResponse;
-import io.github.hapjava.impl.jmdns.JmdnsHomekitAdvertiser;
import io.github.hapjava.impl.pairing.PairSetupRequest.Stage3Request;
import io.github.hapjava.impl.pairing.TypeLengthValueUtils.DecodeResult;
import io.github.hapjava.impl.pairing.TypeLengthValueUtils.Encoder;
@@ -16,14 +15,12 @@ class FinalPairHandler {
private final byte[] k;
private final HomekitAuthInfo authInfo;
- private final JmdnsHomekitAdvertiser advertiser;
private byte[] hkdf_enc_key;
- public FinalPairHandler(byte[] k, HomekitAuthInfo authInfo, JmdnsHomekitAdvertiser advertiser) {
+ public FinalPairHandler(byte[] k, HomekitAuthInfo authInfo) {
this.k = k;
this.authInfo = authInfo;
- this.advertiser = advertiser;
}
public HttpResponse handle(PairSetupRequest req) throws Exception {
@@ -66,7 +63,6 @@ private HttpResponse createUser(byte[] username, byte[] ltpk, byte[] proof) thro
throw new Exception("Invalid signature");
}
authInfo.createUser(authInfo.getMac() + new String(username, StandardCharsets.UTF_8), ltpk);
- advertiser.setDiscoverable(false);
return createResponse();
}
diff --git a/src/main/java/io/github/hapjava/impl/pairing/PairingManager.java b/src/main/java/io/github/hapjava/impl/pairing/PairingManager.java
index c415114b9..934181309 100644
--- a/src/main/java/io/github/hapjava/impl/pairing/PairingManager.java
+++ b/src/main/java/io/github/hapjava/impl/pairing/PairingManager.java
@@ -4,7 +4,6 @@
import io.github.hapjava.impl.HomekitRegistry;
import io.github.hapjava.impl.http.HttpRequest;
import io.github.hapjava.impl.http.HttpResponse;
-import io.github.hapjava.impl.jmdns.JmdnsHomekitAdvertiser;
import io.github.hapjava.impl.responses.NotFoundResponse;
import io.github.hapjava.impl.responses.UnauthorizedResponse;
import org.slf4j.Logger;
@@ -16,15 +15,12 @@ public class PairingManager {
private final HomekitAuthInfo authInfo;
private final HomekitRegistry registry;
- private final JmdnsHomekitAdvertiser advertiser;
private SrpHandler srpHandler;
- public PairingManager(
- HomekitAuthInfo authInfo, HomekitRegistry registry, JmdnsHomekitAdvertiser advertiser) {
+ public PairingManager(HomekitAuthInfo authInfo, HomekitRegistry registry) {
this.authInfo = authInfo;
this.registry = registry;
- this.advertiser = advertiser;
}
public HttpResponse handle(HttpRequest httpRequest) throws Exception {
@@ -54,7 +50,7 @@ public HttpResponse handle(HttpRequest httpRequest) throws Exception {
logger.warn("Received unexpected stage 3 request for " + registry.getLabel());
return new UnauthorizedResponse();
} else {
- FinalPairHandler handler = new FinalPairHandler(srpHandler.getK(), authInfo, advertiser);
+ FinalPairHandler handler = new FinalPairHandler(srpHandler.getK(), authInfo);
try {
return handler.handle(req);
} catch (Exception e) {
diff --git a/src/main/java/io/github/hapjava/impl/services/CarbonDioxideSensorService.java b/src/main/java/io/github/hapjava/impl/services/CarbonDioxideSensorService.java
new file mode 100644
index 000000000..f00f04996
--- /dev/null
+++ b/src/main/java/io/github/hapjava/impl/services/CarbonDioxideSensorService.java
@@ -0,0 +1,18 @@
+package io.github.hapjava.impl.services;
+
+import io.github.hapjava.accessories.CarbonDioxideSensor;
+import io.github.hapjava.impl.characteristics.carbondioxide.CarbonDioxideDetectedCharacteristic;
+import io.github.hapjava.impl.characteristics.carbondioxide.CarbonDioxideLevelCharacteristic;
+
+public class CarbonDioxideSensorService extends AbstractServiceImpl {
+
+ public CarbonDioxideSensorService(CarbonDioxideSensor carbonDioxideSensor) {
+ this(carbonDioxideSensor, carbonDioxideSensor.getLabel());
+ }
+
+ public CarbonDioxideSensorService(CarbonDioxideSensor carbonDioxideSensor, String serviceName) {
+ super("00000097-0000-1000-8000-0026BB765291", carbonDioxideSensor, serviceName);
+ addCharacteristic(new CarbonDioxideDetectedCharacteristic(carbonDioxideSensor));
+ addCharacteristic(new CarbonDioxideLevelCharacteristic(carbonDioxideSensor));
+ }
+}
diff --git a/src/test/java/com/github/hapjava/HomekitRootTest.java b/src/test/java/io/github/hapjava/HomekitRootTest.java
similarity index 91%
rename from src/test/java/com/github/hapjava/HomekitRootTest.java
rename to src/test/java/io/github/hapjava/HomekitRootTest.java
index 226e80d4a..33eb51b10 100644
--- a/src/test/java/com/github/hapjava/HomekitRootTest.java
+++ b/src/test/java/io/github/hapjava/HomekitRootTest.java
@@ -2,13 +2,18 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import io.github.hapjava.impl.HomekitWebHandler;
import io.github.hapjava.impl.http.HomekitClientConnectionFactory;
import io.github.hapjava.impl.jmdns.JmdnsHomekitAdvertiser;
import java.util.concurrent.CompletableFuture;
-import org.junit.*;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
public class HomekitRootTest {
@@ -64,7 +69,7 @@ public void testWebHandlerStops() throws Exception {
@Test
public void testAdvertiserStarts() throws Exception {
- String mac = "00:00:00:00:00:00";
+ final String mac = "00:00:00:00:00:00";
when(authInfo.getMac()).thenReturn(mac);
root.start();
verify(advertiser).advertise(eq(LABEL), eq(mac), eq(PORT), eq(1));