diff --git a/src/main/java/com/beowulfe/hap/accessories/SecuritySystem.java b/src/main/java/com/beowulfe/hap/accessories/SecuritySystem.java
new file mode 100644
index 000000000..096937b25
--- /dev/null
+++ b/src/main/java/com/beowulfe/hap/accessories/SecuritySystem.java
@@ -0,0 +1,96 @@
+package com.beowulfe.hap.accessories;
+
+import com.beowulfe.hap.HomekitAccessory;
+import com.beowulfe.hap.HomekitCharacteristicChangeCallback;
+import com.beowulfe.hap.Service;
+import com.beowulfe.hap.accessories.properties.CurrentSecuritySystemState;
+import com.beowulfe.hap.accessories.properties.SecuritySystemAlarmType;
+import com.beowulfe.hap.accessories.properties.TargetSecuritySystemState;
+import com.beowulfe.hap.impl.services.SecuritySystemService;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ *
A security system that can be armed so that when a contact sensor is opened or a motion
+ * sensor detects movement, then a siren could be fired off. There are different modes for arming
+ * the system. See {@link TargetSecuritySystemState} for more information.
+ *
+ * @author Gaston Dombiak
+ */
+public interface SecuritySystem extends HomekitAccessory {
+
+ /**
+ * Retrieves the current state of the security system. The state describes if the system
+ * is armed in any of its variations; or if the alarm has been triggered; or if the system
+ * is disarmed.
+ *
+ * @return current state of the security system.
+ */
+ CompletableFuture getCurrentSecuritySystemState();
+
+ /**
+ * Subscribes to changes to the state of the security system.
+ *
+ * @param callback the function to call when the state changes.
+ */
+ void subscribeCurrentSecuritySystemState(HomekitCharacteristicChangeCallback callback);
+
+ /**
+ * Unsubscribes from changes in the state of the security system.
+ */
+ void unsubscribeCurrentSecuritySystemState();
+
+ /**
+ * Sets the state of the security system. The security system could be armed in any
+ * of its variations or disarmed.
+ *
+ * @param state target state of the security system.
+ * @throws Exception when the change cannot be made.
+ */
+ void setTargetSecuritySystemState(TargetSecuritySystemState state) throws Exception;
+
+ /**
+ * Retrieves the pending, but not yet completed, state of the security system.
+ *
+ * @return target state of the security system.
+ */
+ CompletableFuture getTargetSecuritySystemState();
+
+ /**
+ * Subscribes to changes in the pending, but not yet completed, state of the security system.
+ *
+ * @param callback the function to call when the state changes.
+ */
+ void subscribeTargetSecuritySystemState(HomekitCharacteristicChangeCallback callback);
+
+ /**
+ * Unsubscribes from changes in the pending, but not yet completed, state of the security system.
+ */
+ void unsubscribeTargetSecuritySystemState();
+
+ /**
+ * Retrieves the alarm type of the security system.
+ *
+ * @return alarm type of the security system.
+ */
+ CompletableFuture getAlarmTypeState();
+
+ /**
+ * Subscribes to changes to the alarm type of the security system.
+ *
+ * @param callback the function to call when the alarm type changes.
+ */
+ void subscribeAlarmTypeState(HomekitCharacteristicChangeCallback callback);
+
+ /**
+ * Unsubscribes from changes in the alarm type of the security system.
+ */
+ void unsubscribeAlarmTypeState();
+
+ @Override
+ default Collection getServices() {
+ return Collections.singleton(new SecuritySystemService(this));
+ }
+}
diff --git a/src/main/java/com/beowulfe/hap/accessories/properties/CurrentSecuritySystemState.java b/src/main/java/com/beowulfe/hap/accessories/properties/CurrentSecuritySystemState.java
new file mode 100644
index 000000000..a9666c946
--- /dev/null
+++ b/src/main/java/com/beowulfe/hap/accessories/properties/CurrentSecuritySystemState.java
@@ -0,0 +1,57 @@
+package com.beowulfe.hap.accessories.properties;
+
+import com.beowulfe.hap.accessories.SecuritySystem;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * The current state of a {@link SecuritySystem}. Unlike {@link TargetSecuritySystemState}, this enum
+ * includes a triggered state.
+ *
+ * @author Gaston Dombiak
+ */
+public enum CurrentSecuritySystemState {
+
+ /**
+ * The home is occupied and residents are active.
+ */
+ STAY_ARM(0),
+ /**
+ * The home is unoccupied.
+ */
+ AWAY_ARM(1),
+ /**
+ * The home is occupied and residents are sleeping.
+ */
+ NIGHT_ARM(2),
+ /**
+ * The security system is disarmed.
+ */
+ DISARMED(3),
+ /**
+ * The security system is triggered.
+ */
+ TRIGGERED(4);
+
+ private final static Map reverse;
+ static {
+ reverse = Arrays.stream(CurrentSecuritySystemState.values()).collect(Collectors
+ .toMap(CurrentSecuritySystemState::getCode, t -> t));
+ }
+
+ public static CurrentSecuritySystemState fromCode(Integer code) {
+ return reverse.get(code);
+ }
+
+ private final int code;
+
+ CurrentSecuritySystemState(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/src/main/java/com/beowulfe/hap/accessories/properties/SecuritySystemAlarmType.java b/src/main/java/com/beowulfe/hap/accessories/properties/SecuritySystemAlarmType.java
new file mode 100644
index 000000000..01a275b5c
--- /dev/null
+++ b/src/main/java/com/beowulfe/hap/accessories/properties/SecuritySystemAlarmType.java
@@ -0,0 +1,44 @@
+package com.beowulfe.hap.accessories.properties;
+
+import com.beowulfe.hap.accessories.SecuritySystem;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Type of alarm of a {@link SecuritySystem}.
+ *
+ * @author Gaston Dombiak
+ */
+public enum SecuritySystemAlarmType {
+
+ /**
+ * Alarm conditions are cleared
+ */
+ CLEARED(0),
+ /**
+ * Alarm type is not known
+ */
+ UNKNOWN(1);
+
+ private final static Map reverse;
+ static {
+ reverse = Arrays.stream(SecuritySystemAlarmType.values()).collect(Collectors
+ .toMap(SecuritySystemAlarmType::getCode, t -> t));
+ }
+
+ public static SecuritySystemAlarmType fromCode(Integer code) {
+ return reverse.get(code);
+ }
+
+ private final int code;
+
+ SecuritySystemAlarmType(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/src/main/java/com/beowulfe/hap/accessories/properties/TargetSecuritySystemState.java b/src/main/java/com/beowulfe/hap/accessories/properties/TargetSecuritySystemState.java
new file mode 100644
index 000000000..afd095e80
--- /dev/null
+++ b/src/main/java/com/beowulfe/hap/accessories/properties/TargetSecuritySystemState.java
@@ -0,0 +1,52 @@
+package com.beowulfe.hap.accessories.properties;
+
+import com.beowulfe.hap.accessories.SecuritySystem;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * The target state of a {@link SecuritySystem}.
+ *
+ * @author Gaston Dombiak
+ */
+public enum TargetSecuritySystemState {
+
+ /**
+ * The home is occupied and residents are active.
+ */
+ STAY_ARM(0),
+ /**
+ * The home is unoccupied.
+ */
+ AWAY_ARM(1),
+ /**
+ * The home is occupied and residents are sleeping.
+ */
+ NIGHT_ARM(2),
+ /**
+ * The security system is disarmed.
+ */
+ DISARMED(3);
+
+ private final static Map reverse;
+ static {
+ reverse = Arrays.stream(TargetSecuritySystemState.values()).collect(Collectors
+ .toMap(TargetSecuritySystemState::getCode, t -> t));
+ }
+
+ public static TargetSecuritySystemState fromCode(Integer code) {
+ return reverse.get(code);
+ }
+
+ private final int code;
+
+ TargetSecuritySystemState(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/src/main/java/com/beowulfe/hap/impl/characteristics/securitysystem/CurrentSecuritySystemStateCharacteristic.java b/src/main/java/com/beowulfe/hap/impl/characteristics/securitysystem/CurrentSecuritySystemStateCharacteristic.java
new file mode 100644
index 000000000..327a45a49
--- /dev/null
+++ b/src/main/java/com/beowulfe/hap/impl/characteristics/securitysystem/CurrentSecuritySystemStateCharacteristic.java
@@ -0,0 +1,39 @@
+package com.beowulfe.hap.impl.characteristics.securitysystem;
+
+import com.beowulfe.hap.HomekitCharacteristicChangeCallback;
+import com.beowulfe.hap.accessories.SecuritySystem;
+import com.beowulfe.hap.accessories.properties.CurrentSecuritySystemState;
+import com.beowulfe.hap.characteristics.EnumCharacteristic;
+import com.beowulfe.hap.characteristics.EventableCharacteristic;
+
+import java.util.concurrent.CompletableFuture;
+
+public class CurrentSecuritySystemStateCharacteristic extends EnumCharacteristic implements EventableCharacteristic {
+
+ private final SecuritySystem securitySystem;
+
+ public CurrentSecuritySystemStateCharacteristic(SecuritySystem securitySystem) {
+ super("00000066-0000-1000-8000-0026BB765291", false, true, "Current security system state", 4);
+ this.securitySystem = securitySystem;
+ }
+
+ @Override
+ protected CompletableFuture getValue() {
+ return securitySystem.getCurrentSecuritySystemState().thenApply(CurrentSecuritySystemState::getCode);
+ }
+
+ @Override
+ protected void setValue(Integer value) throws Exception {
+ //Not writable
+ }
+
+ @Override
+ public void subscribe(HomekitCharacteristicChangeCallback callback) {
+ securitySystem.subscribeCurrentSecuritySystemState(callback);
+ }
+
+ @Override
+ public void unsubscribe() {
+ securitySystem.unsubscribeCurrentSecuritySystemState();
+ }
+}
diff --git a/src/main/java/com/beowulfe/hap/impl/characteristics/securitysystem/SecuritySystemAlarmTypeCharacteristic.java b/src/main/java/com/beowulfe/hap/impl/characteristics/securitysystem/SecuritySystemAlarmTypeCharacteristic.java
new file mode 100644
index 000000000..680089b61
--- /dev/null
+++ b/src/main/java/com/beowulfe/hap/impl/characteristics/securitysystem/SecuritySystemAlarmTypeCharacteristic.java
@@ -0,0 +1,39 @@
+package com.beowulfe.hap.impl.characteristics.securitysystem;
+
+import com.beowulfe.hap.HomekitCharacteristicChangeCallback;
+import com.beowulfe.hap.accessories.SecuritySystem;
+import com.beowulfe.hap.accessories.properties.SecuritySystemAlarmType;
+import com.beowulfe.hap.characteristics.EnumCharacteristic;
+import com.beowulfe.hap.characteristics.EventableCharacteristic;
+
+import java.util.concurrent.CompletableFuture;
+
+public class SecuritySystemAlarmTypeCharacteristic extends EnumCharacteristic implements EventableCharacteristic {
+
+ private final SecuritySystem securitySystem;
+
+ public SecuritySystemAlarmTypeCharacteristic(SecuritySystem securitySystem) {
+ super("0000008E-0000-1000-8000-0026BB765291", false, true, "Security system alarm type", 1);
+ this.securitySystem = securitySystem;
+ }
+
+ @Override
+ protected CompletableFuture getValue() {
+ return securitySystem.getAlarmTypeState().thenApply(SecuritySystemAlarmType::getCode);
+ }
+
+ @Override
+ protected void setValue(Integer value) throws Exception {
+ //Not writable
+ }
+
+ @Override
+ public void subscribe(HomekitCharacteristicChangeCallback callback) {
+ securitySystem.subscribeAlarmTypeState(callback);
+ }
+
+ @Override
+ public void unsubscribe() {
+ securitySystem.unsubscribeAlarmTypeState();
+ }
+}
diff --git a/src/main/java/com/beowulfe/hap/impl/characteristics/securitysystem/TargetSecuritySystemStateCharacteristic.java b/src/main/java/com/beowulfe/hap/impl/characteristics/securitysystem/TargetSecuritySystemStateCharacteristic.java
new file mode 100644
index 000000000..7fc53fa72
--- /dev/null
+++ b/src/main/java/com/beowulfe/hap/impl/characteristics/securitysystem/TargetSecuritySystemStateCharacteristic.java
@@ -0,0 +1,39 @@
+package com.beowulfe.hap.impl.characteristics.securitysystem;
+
+import com.beowulfe.hap.HomekitCharacteristicChangeCallback;
+import com.beowulfe.hap.accessories.SecuritySystem;
+import com.beowulfe.hap.accessories.properties.TargetSecuritySystemState;
+import com.beowulfe.hap.characteristics.EnumCharacteristic;
+import com.beowulfe.hap.characteristics.EventableCharacteristic;
+
+import java.util.concurrent.CompletableFuture;
+
+public class TargetSecuritySystemStateCharacteristic extends EnumCharacteristic implements EventableCharacteristic {
+
+ private final SecuritySystem securitySystem;
+
+ public TargetSecuritySystemStateCharacteristic(SecuritySystem securitySystem) {
+ super("00000067-0000-1000-8000-0026BB765291", true, true, "Target security system state", 3);
+ this.securitySystem = securitySystem;
+ }
+
+ @Override
+ protected CompletableFuture getValue() {
+ return securitySystem.getTargetSecuritySystemState().thenApply(TargetSecuritySystemState::getCode);
+ }
+
+ @Override
+ protected void setValue(Integer value) throws Exception {
+ securitySystem.setTargetSecuritySystemState(TargetSecuritySystemState.fromCode(value));
+ }
+
+ @Override
+ public void subscribe(HomekitCharacteristicChangeCallback callback) {
+ securitySystem.subscribeTargetSecuritySystemState(callback);
+ }
+
+ @Override
+ public void unsubscribe() {
+ securitySystem.unsubscribeTargetSecuritySystemState();
+ }
+}
diff --git a/src/main/java/com/beowulfe/hap/impl/http/impl/AccessoryHandler.java b/src/main/java/com/beowulfe/hap/impl/http/impl/AccessoryHandler.java
index 9a86f98e0..fc1aa813e 100644
--- a/src/main/java/com/beowulfe/hap/impl/http/impl/AccessoryHandler.java
+++ b/src/main/java/com/beowulfe/hap/impl/http/impl/AccessoryHandler.java
@@ -1,16 +1,19 @@
package com.beowulfe.hap.impl.http.impl;
+import com.beowulfe.hap.impl.http.HomekitClientConnection;
+import com.beowulfe.hap.impl.http.HomekitClientConnectionFactory;
+import com.beowulfe.hap.impl.http.HttpResponse;
import io.netty.buffer.Unpooled;
-import io.netty.channel.*;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
-
-import java.nio.charset.StandardCharsets;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.beowulfe.hap.impl.http.*;
-import com.beowulfe.hap.impl.http.HttpResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
class AccessoryHandler extends SimpleChannelInboundHandler {
@@ -84,7 +87,16 @@ public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
- LOGGER.error("Exception caught in web handler", cause);
+ boolean errorLevel = true;
+ if (cause instanceof IOException) {
+ // Decide level of logging based on exception
+ errorLevel = !"Connection timed out".equals(cause.getMessage());
+ }
+ if (errorLevel) {
+ LOGGER.error("Exception caught in web handler", cause);
+ } else {
+ LOGGER.debug("Exception caught in web handler", cause);
+ }
ctx.close();
}
}
diff --git a/src/main/java/com/beowulfe/hap/impl/http/impl/BinaryHandler.java b/src/main/java/com/beowulfe/hap/impl/http/impl/BinaryHandler.java
index 73e6a7f03..225576bee 100644
--- a/src/main/java/com/beowulfe/hap/impl/http/impl/BinaryHandler.java
+++ b/src/main/java/com/beowulfe/hap/impl/http/impl/BinaryHandler.java
@@ -1,19 +1,18 @@
package com.beowulfe.hap.impl.http.impl;
+import com.beowulfe.hap.impl.http.HomekitClientConnection;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
import org.apache.commons.io.HexDump;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.beowulfe.hap.impl.http.HomekitClientConnection;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
public class BinaryHandler extends ByteToMessageCodec {
@@ -53,7 +52,16 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in,
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
- logger.error("Exception in binary handler", cause);
+ boolean errorLevel = true;
+ if (cause instanceof IOException) {
+ // Decide level of logging based on exception
+ errorLevel = !"Connection timed out".equals(cause.getMessage());
+ }
+ if (errorLevel) {
+ logger.error("Exception in binary handler", cause);
+ } else {
+ logger.debug("Exception in binary handler", cause);
+ }
super.exceptionCaught(ctx, cause);
}
diff --git a/src/main/java/com/beowulfe/hap/impl/services/SecuritySystemService.java b/src/main/java/com/beowulfe/hap/impl/services/SecuritySystemService.java
new file mode 100644
index 000000000..5fe1da6ff
--- /dev/null
+++ b/src/main/java/com/beowulfe/hap/impl/services/SecuritySystemService.java
@@ -0,0 +1,16 @@
+package com.beowulfe.hap.impl.services;
+
+import com.beowulfe.hap.accessories.SecuritySystem;
+import com.beowulfe.hap.impl.characteristics.securitysystem.CurrentSecuritySystemStateCharacteristic;
+import com.beowulfe.hap.impl.characteristics.securitysystem.SecuritySystemAlarmTypeCharacteristic;
+import com.beowulfe.hap.impl.characteristics.securitysystem.TargetSecuritySystemStateCharacteristic;
+
+public class SecuritySystemService extends AbstractServiceImpl {
+
+ public SecuritySystemService(SecuritySystem securitySystem) {
+ super("0000007E-0000-1000-8000-0026BB765291", securitySystem);
+ addCharacteristic(new CurrentSecuritySystemStateCharacteristic(securitySystem));
+ addCharacteristic(new TargetSecuritySystemStateCharacteristic(securitySystem));
+ addCharacteristic(new SecuritySystemAlarmTypeCharacteristic(securitySystem));
+ }
+}