diff --git a/CHANGES.md b/CHANGES.md
index 2f8e24550..ab4bb1202 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,8 @@
# HAP-Java 2.0.10
+* Add constructors with custom min/max to the window covering tilt angle characteristics [#162](https://github.com/hap-java/HAP-Java/issues/162)
* Add optional Mute characteristic to Doorbell service
+* Fix leak of mDNS instances and shutdown hooks when stopping `HomekitRoot`
+* Updated netty to 4.1.139.Final
# HAP-Java 2.0.9
* Fix Set Duration and Remaining Duration characteristics to advertise format `uint32` instead of `int` [#202](https://github.com/hap-java/HAP-Java/issues/202)
diff --git a/pom.xml b/pom.xml
index a1a952779..7bbbb9021 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
UTF-8
- 4.1.126.Final
+ 4.1.139.Final
diff --git a/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/CurrentHorizontalTiltAngleCharacteristic.java b/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/CurrentHorizontalTiltAngleCharacteristic.java
index 1255d7eff..272a822f5 100644
--- a/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/CurrentHorizontalTiltAngleCharacteristic.java
+++ b/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/CurrentHorizontalTiltAngleCharacteristic.java
@@ -11,20 +11,31 @@
/** This characteristic describes the current angle of horizontal slats. */
public class CurrentHorizontalTiltAngleCharacteristic extends IntegerCharacteristic
implements EventableCharacteristic {
+ public static final int DEFAULT_MIN_VALUE = -90;
+ public static final int DEFAULT_MAX_VALUE = 90;
public CurrentHorizontalTiltAngleCharacteristic(
+ int minValue,
+ int maxValue,
Supplier> getter,
Consumer subscriber,
Runnable unsubscriber) {
super(
"0000006C-0000-1000-8000-0026BB765291",
"current horizontal tilt angle",
- -90,
- 90,
+ minValue,
+ maxValue,
"arcdegrees",
Optional.of(getter),
Optional.empty(),
Optional.of(subscriber),
Optional.of(unsubscriber));
}
+
+ public CurrentHorizontalTiltAngleCharacteristic(
+ Supplier> getter,
+ Consumer subscriber,
+ Runnable unsubscriber) {
+ this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, getter, subscriber, unsubscriber);
+ }
}
diff --git a/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/CurrentVerticalTiltAngleCharacteristic.java b/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/CurrentVerticalTiltAngleCharacteristic.java
index c6258a2db..7d7168973 100644
--- a/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/CurrentVerticalTiltAngleCharacteristic.java
+++ b/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/CurrentVerticalTiltAngleCharacteristic.java
@@ -11,20 +11,31 @@
/** This characteristic describes the current angle of vertical slats for accessory. */
public class CurrentVerticalTiltAngleCharacteristic extends IntegerCharacteristic
implements EventableCharacteristic {
+ public static final int DEFAULT_MIN_VALUE = -90;
+ public static final int DEFAULT_MAX_VALUE = 90;
public CurrentVerticalTiltAngleCharacteristic(
+ int minValue,
+ int maxValue,
Supplier> getter,
Consumer subscriber,
Runnable unsubscriber) {
super(
"0000006E-0000-1000-8000-0026BB765291",
"current vertical tilt angle",
- -90,
- 90,
+ minValue,
+ maxValue,
"arcdegrees",
Optional.of(getter),
Optional.empty(),
Optional.of(subscriber),
Optional.of(unsubscriber));
}
+
+ public CurrentVerticalTiltAngleCharacteristic(
+ Supplier> getter,
+ Consumer subscriber,
+ Runnable unsubscriber) {
+ this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, getter, subscriber, unsubscriber);
+ }
}
diff --git a/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/TargetHorizontalTiltAngleCharacteristic.java b/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/TargetHorizontalTiltAngleCharacteristic.java
index 38c075760..17639ba6e 100644
--- a/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/TargetHorizontalTiltAngleCharacteristic.java
+++ b/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/TargetHorizontalTiltAngleCharacteristic.java
@@ -12,8 +12,12 @@
/** This characteristic describes the target angle of horizontal slats for accessories. */
public class TargetHorizontalTiltAngleCharacteristic extends IntegerCharacteristic
implements EventableCharacteristic {
+ public static final int DEFAULT_MIN_VALUE = -90;
+ public static final int DEFAULT_MAX_VALUE = 90;
public TargetHorizontalTiltAngleCharacteristic(
+ int minValue,
+ int maxValue,
Supplier> getter,
ExceptionalConsumer setter,
Consumer subscriber,
@@ -21,12 +25,20 @@ public TargetHorizontalTiltAngleCharacteristic(
super(
"0000007B-0000-1000-8000-0026BB765291",
"target horizontal tilt angle",
- -90,
- 90,
+ minValue,
+ maxValue,
"arcdegrees",
Optional.of(getter),
Optional.of(setter),
Optional.of(subscriber),
Optional.of(unsubscriber));
}
+
+ public TargetHorizontalTiltAngleCharacteristic(
+ Supplier> getter,
+ ExceptionalConsumer setter,
+ Consumer subscriber,
+ Runnable unsubscriber) {
+ this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, getter, setter, subscriber, unsubscriber);
+ }
}
diff --git a/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/TargetVerticalTiltAngleCharacteristic.java b/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/TargetVerticalTiltAngleCharacteristic.java
index 4d7c3ad71..03fd7a3f5 100644
--- a/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/TargetVerticalTiltAngleCharacteristic.java
+++ b/src/main/java/io/github/hapjava/characteristics/impl/windowcovering/TargetVerticalTiltAngleCharacteristic.java
@@ -12,8 +12,12 @@
/** This characteristic describes the target angle of vertical slats. */
public class TargetVerticalTiltAngleCharacteristic extends IntegerCharacteristic
implements EventableCharacteristic {
+ public static final int DEFAULT_MIN_VALUE = -90;
+ public static final int DEFAULT_MAX_VALUE = 90;
public TargetVerticalTiltAngleCharacteristic(
+ int minValue,
+ int maxValue,
Supplier> getter,
ExceptionalConsumer setter,
Consumer subscriber,
@@ -21,12 +25,20 @@ public TargetVerticalTiltAngleCharacteristic(
super(
"0000007D-0000-1000-8000-0026BB765291",
"target vertical tilt angle",
- -90,
- 90,
+ minValue,
+ maxValue,
"arcdegrees",
Optional.of(getter),
Optional.of(setter),
Optional.of(subscriber),
Optional.of(unsubscriber));
}
+
+ public TargetVerticalTiltAngleCharacteristic(
+ Supplier> getter,
+ ExceptionalConsumer setter,
+ Consumer subscriber,
+ Runnable unsubscriber) {
+ this(DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, getter, setter, subscriber, unsubscriber);
+ }
}
diff --git a/src/main/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiser.java b/src/main/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiser.java
index 9086acb3e..1073f9b86 100644
--- a/src/main/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiser.java
+++ b/src/main/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiser.java
@@ -17,6 +17,8 @@ public class JmdnsHomekitAdvertiser {
private static final String SERVICE_TYPE = "_hap._tcp.local.";
private final JmDNS jmdns;
+ private final boolean ownsJmdns;
+ private Thread shutdownHook;
private boolean discoverable = true;
private static final Logger logger = LoggerFactory.getLogger(JmdnsHomekitAdvertiser.class);
private boolean isAdvertising = false;
@@ -30,11 +32,16 @@ public class JmdnsHomekitAdvertiser {
private int category;
public JmdnsHomekitAdvertiser(JmDNS jmdns) {
- this.jmdns = jmdns;
+ this(jmdns, false);
}
public JmdnsHomekitAdvertiser(InetAddress localAddress) throws UnknownHostException, IOException {
- jmdns = JmDNS.create(localAddress);
+ this(JmDNS.create(localAddress), true);
+ }
+
+ JmdnsHomekitAdvertiser(JmDNS jmdns, boolean ownsJmdns) {
+ this.jmdns = jmdns;
+ this.ownsJmdns = ownsJmdns;
}
public synchronized void advertise(
@@ -54,18 +61,33 @@ public synchronized void advertise(
registerService();
- Runtime.getRuntime()
- .addShutdownHook(
- new Thread(
- () -> {
- logger.trace("Stopping advertising in response to shutdown.");
- jmdns.unregisterAllServices();
- }));
+ shutdownHook =
+ new Thread(
+ () -> {
+ logger.trace("Stopping advertising in response to shutdown.");
+ jmdns.unregisterAllServices();
+ });
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
isAdvertising = true;
}
public synchronized void stop() {
unregisterService();
+ isAdvertising = false;
+ if (shutdownHook != null) {
+ try {
+ Runtime.getRuntime().removeShutdownHook(shutdownHook);
+ } catch (IllegalStateException e) {
+ // already shutting down
+ }
+ }
+ if (ownsJmdns) {
+ try {
+ jmdns.close();
+ } catch (IOException e) {
+ logger.warn("Failed to close mDNS", e);
+ }
+ }
}
public synchronized void setDiscoverable(boolean discoverable) throws IOException {
diff --git a/src/test/java/io/github/hapjava/characteristics/impl/CustomTiltAngleRangeTest.java b/src/test/java/io/github/hapjava/characteristics/impl/CustomTiltAngleRangeTest.java
new file mode 100644
index 000000000..d3cd51756
--- /dev/null
+++ b/src/test/java/io/github/hapjava/characteristics/impl/CustomTiltAngleRangeTest.java
@@ -0,0 +1,78 @@
+package io.github.hapjava.characteristics.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.github.hapjava.characteristics.Characteristic;
+import io.github.hapjava.characteristics.impl.windowcovering.CurrentHorizontalTiltAngleCharacteristic;
+import io.github.hapjava.characteristics.impl.windowcovering.CurrentVerticalTiltAngleCharacteristic;
+import io.github.hapjava.characteristics.impl.windowcovering.TargetHorizontalTiltAngleCharacteristic;
+import io.github.hapjava.characteristics.impl.windowcovering.TargetVerticalTiltAngleCharacteristic;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import javax.json.JsonObject;
+import org.junit.Test;
+
+/** Custom tilt angle ranges, see https://github.com/hap-java/HAP-Java/issues/162 */
+public class CustomTiltAngleRangeTest {
+
+ @Test
+ public void defaultConstructorsKeepTheirSerializedOutput() {
+ // Serialized output captured before custom ranges were added.
+ for (Case c :
+ Arrays.asList(
+ Case.of(
+ new CurrentHorizontalTiltAngleCharacteristic(
+ () -> CompletableFuture.completedFuture(50), cb -> {}, () -> {}),
+ "{\"iid\":7,\"type\":\"6C\",\"perms\":[\"pr\",\"ev\"],\"format\":\"int\",\"value\":50,\"minValue\":-90,\"maxValue\":90,\"minStep\":1,\"unit\":\"arcdegrees\"}"),
+ Case.of(
+ new TargetHorizontalTiltAngleCharacteristic(
+ () -> CompletableFuture.completedFuture(50), v -> {}, cb -> {}, () -> {}),
+ "{\"iid\":7,\"type\":\"7B\",\"perms\":[\"pr\",\"pw\",\"ev\"],\"format\":\"int\",\"value\":50,\"minValue\":-90,\"maxValue\":90,\"minStep\":1,\"unit\":\"arcdegrees\"}"),
+ Case.of(
+ new CurrentVerticalTiltAngleCharacteristic(
+ () -> CompletableFuture.completedFuture(50), cb -> {}, () -> {}),
+ "{\"iid\":7,\"type\":\"6E\",\"perms\":[\"pr\",\"ev\"],\"format\":\"int\",\"value\":50,\"minValue\":-90,\"maxValue\":90,\"minStep\":1,\"unit\":\"arcdegrees\"}"),
+ Case.of(
+ new TargetVerticalTiltAngleCharacteristic(
+ () -> CompletableFuture.completedFuture(50), v -> {}, cb -> {}, () -> {}),
+ "{\"iid\":7,\"type\":\"7D\",\"perms\":[\"pr\",\"pw\",\"ev\"],\"format\":\"int\",\"value\":50,\"minValue\":-90,\"maxValue\":90,\"minStep\":1,\"unit\":\"arcdegrees\"}"))) {
+ assertThat(c.characteristic.toJson(7).join().toString())
+ .as(c.characteristic.getClass().getSimpleName())
+ .isEqualTo(c.expected);
+ }
+ }
+
+ @Test
+ public void customRangeIsAdvertised() {
+ for (Characteristic c :
+ Arrays.asList(
+ new CurrentHorizontalTiltAngleCharacteristic(
+ 0, 90, () -> CompletableFuture.completedFuture(50), cb -> {}, () -> {}),
+ new TargetHorizontalTiltAngleCharacteristic(
+ 0, 90, () -> CompletableFuture.completedFuture(50), v -> {}, cb -> {}, () -> {}),
+ new CurrentVerticalTiltAngleCharacteristic(
+ 0, 90, () -> CompletableFuture.completedFuture(50), cb -> {}, () -> {}),
+ new TargetVerticalTiltAngleCharacteristic(
+ 0, 90, () -> CompletableFuture.completedFuture(50), v -> {}, cb -> {}, () -> {}))) {
+ JsonObject json = c.toJson(7).join();
+ String name = c.getClass().getSimpleName();
+ assertThat(json.getInt("minValue")).as(name).isEqualTo(0);
+ assertThat(json.getInt("maxValue")).as(name).isEqualTo(90);
+ assertThat(json.getInt("minStep")).as(name).isEqualTo(1);
+ }
+ }
+
+ private static class Case {
+ final Characteristic characteristic;
+ final String expected;
+
+ private Case(Characteristic characteristic, String expected) {
+ this.characteristic = characteristic;
+ this.expected = expected;
+ }
+
+ static Case of(Characteristic characteristic, String expected) {
+ return new Case(characteristic, expected);
+ }
+ }
+}
diff --git a/src/test/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiserTest.java b/src/test/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiserTest.java
index 870bd739b..731be498f 100644
--- a/src/test/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiserTest.java
+++ b/src/test/java/io/github/hapjava/server/impl/jmdns/JmdnsHomekitAdvertiserTest.java
@@ -2,7 +2,9 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import java.io.IOException;
@@ -54,6 +56,29 @@ public void testSetConfigurationIndex() throws Exception {
assertThat(getArgumentFromUnregister().getPropertyString("c#")).isEqualTo("1");
}
+ @Test
+ public void testStopClosesOwnedJmdns() throws Exception {
+ subject = new JmdnsHomekitAdvertiser(jmdns, true);
+ advertise();
+ subject.stop();
+ verify(jmdns).close();
+ }
+
+ @Test
+ public void testStopDoesNotCloseSuppliedJmdns() throws Exception {
+ advertise();
+ subject.stop();
+ verify(jmdns, never()).close();
+ }
+
+ @Test
+ public void testSetDiscoverableAfterStopDoesNotRegister() throws Exception {
+ advertise();
+ subject.stop();
+ subject.setDiscoverable(false);
+ verify(jmdns).registerService(any(ServiceInfo.class));
+ }
+
private ServiceInfo getArgumentFromUnregister() {
ArgumentCaptor serviceInfoCaptor = ArgumentCaptor.forClass(ServiceInfo.class);
verify(jmdns).unregisterService(serviceInfoCaptor.capture());