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