From 5c5b3c29c95b00a0255aa667fc84814bc7b0bd78 Mon Sep 17 00:00:00 2001 From: eugen Date: Sun, 27 Nov 2022 23:39:50 +0100 Subject: [PATCH] [homekit] support stop for blinds (#13788) * implement new blinds logic * fix NPE, improve emulated state Signed-off-by: Eugen --- bundles/org.openhab.io.homekit/README.md | 14 +++++++- .../io/homekit/internal/HomekitImpl.java | 3 +- .../homekit/internal/HomekitTaggedItem.java | 2 ++ .../AbstractHomekitPositionAccessoryImpl.java | 36 ++++++++++++++++--- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.io.homekit/README.md b/bundles/org.openhab.io.homekit/README.md index 685ce7fe0..71d48f186 100644 --- a/bundles/org.openhab.io.homekit/README.md +++ b/bundles/org.openhab.io.homekit/README.md @@ -312,7 +312,19 @@ In case you need to disable this logic you can do it with configuration paramete Rollershutter window_covering "Window Rollershutter" {homekit = "WindowCovering" [inverted=false]} Rollershutter window "Window" {homekit = "Window" [inverted=false]} Rollershutter door "Door" {homekit = "Door" [inverted=false]} + ``` +HomeKit home app never sends "STOP" but only the target position. +If you add configuration parameter "stop=true", openHAB will emulate stop and send "STOP" command to rollershutter item if you click on the blind icon in the iOS home app while the blind is moving. + +```xtend +Rollershutter window_covering "Window Rollershutter" {homekit = "WindowCovering" [stop=true]} + ``` + +Some blinds devices do support "STOP" command but would stop if they receive UP/DOWN while moving om the same direction. In order to support such devices add "stopSameDirection" parameter. + +```xtend +Rollershutter window_covering "Window Rollershutter" {homekit = "WindowCovering" [stop=true, stopSameDirection=true]} ``` Window covering can have a number of optional characteristics like horizontal & vertical tilt, obstruction status and hold position trigger. @@ -530,7 +542,7 @@ In order to combine multiple accessories to one HomeKit accessory you need: e.g. configuration for a fan with light would look as follows ```xtend -Group FanWithLight "Fan with Light" {homekit = "Fan,Light"} +Group FanWithLight "Fan with Light" {homekit = "Fan,Lighting"} Switch FanActiveStatus "Fan Active Status" (FanWithLight) {homekit = "Fan.ActiveStatus"} Number FanRotationSpeed "Fan Rotation Speed" (FanWithLight) {homekit = "Fan.RotationSpeed"} Switch Light "Light" (FanWithLight) {homekit = "Lighting.OnState"} diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java index 59525fbf5..5fbb3f3bc 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitImpl.java @@ -154,7 +154,8 @@ public class HomekitImpl implements Homekit, NetworkAddressChangeListener { return; if (!oldSettings.name.equals(settings.name) || !oldSettings.pin.equals(settings.pin) || !oldSettings.setupId.equals(settings.setupId) - || !oldSettings.networkInterface.equals(settings.networkInterface) + || (oldSettings.networkInterface != null + && !oldSettings.networkInterface.equals(settings.networkInterface)) || oldSettings.port != settings.port || oldSettings.useOHmDNS != settings.useOHmDNS || oldSettings.instances != settings.instances) { // the HomeKit server settings changed. we do a complete re-init diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitTaggedItem.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitTaggedItem.java index 747c76d0c..72c668d33 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitTaggedItem.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitTaggedItem.java @@ -57,6 +57,8 @@ public class HomekitTaggedItem { public final static String PRIMARY_SERVICE = "primary"; public final static String STEP = "step"; public final static String UNIT = "unit"; + public final static String EMULATE_STOP_STATE = "stop"; + public final static String EMULATE_STOP_SAME_DIRECTION = "stopSameDirection"; private static final Map CREATED_ACCESSORY_IDS = new ConcurrentHashMap<>(); diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/AbstractHomekitPositionAccessoryImpl.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/AbstractHomekitPositionAccessoryImpl.java index 45eb02e5e..300f23837 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/AbstractHomekitPositionAccessoryImpl.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/AbstractHomekitPositionAccessoryImpl.java @@ -31,6 +31,8 @@ import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.RollershutterItem; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.StopMoveType; +import org.openhab.core.library.types.UpDownType; import org.openhab.io.homekit.internal.HomekitAccessoryUpdater; import org.openhab.io.homekit.internal.HomekitCharacteristicType; import org.openhab.io.homekit.internal.HomekitSettings; @@ -52,12 +54,18 @@ abstract class AbstractHomekitPositionAccessoryImpl extends AbstractHomekitAcces protected int closedPosition; protected int openPosition; private final Map positionStateMapping; + protected boolean emulateState; + protected boolean emulateStopSameDirection; + protected PositionStateEnum emulatedState = PositionStateEnum.STOPPED; public AbstractHomekitPositionAccessoryImpl(HomekitTaggedItem taggedItem, List mandatoryCharacteristics, HomekitAccessoryUpdater updater, HomekitSettings settings) { super(taggedItem, mandatoryCharacteristics, updater, settings); final boolean inverted = getAccessoryConfigurationAsBoolean(HomekitTaggedItem.INVERTED, true); + emulateState = getAccessoryConfigurationAsBoolean(HomekitTaggedItem.EMULATE_STOP_STATE, false); + emulateStopSameDirection = getAccessoryConfigurationAsBoolean(HomekitTaggedItem.EMULATE_STOP_SAME_DIRECTION, + false); closedPosition = inverted ? 0 : 100; openPosition = inverted ? 100 : 0; positionStateMapping = new EnumMap<>(PositionStateEnum.class); @@ -72,8 +80,8 @@ abstract class AbstractHomekitPositionAccessoryImpl extends AbstractHomekitAcces } public CompletableFuture getPositionState() { - return CompletableFuture - .completedFuture(getKeyFromMapping(POSITION_STATE, positionStateMapping, PositionStateEnum.STOPPED)); + return CompletableFuture.completedFuture(emulateState ? emulatedState + : getKeyFromMapping(POSITION_STATE, positionStateMapping, PositionStateEnum.STOPPED)); } public CompletableFuture getTargetPosition() { @@ -84,9 +92,29 @@ abstract class AbstractHomekitPositionAccessoryImpl extends AbstractHomekitAcces getCharacteristic(TARGET_POSITION).ifPresentOrElse(taggedItem -> { final Item item = taggedItem.getItem(); final int targetPosition = convertPosition(value, openPosition); - if (item instanceof RollershutterItem) { - ((RollershutterItem) item).send(new PercentType(targetPosition)); + // HomeKit home app never sends STOP. we emulate stop if we receive 100% or 0% while the blind is moving + if (emulateState && (targetPosition == 100 && emulatedState == PositionStateEnum.DECREASING) + || ((targetPosition == 0 && emulatedState == PositionStateEnum.INCREASING))) { + if (emulateStopSameDirection) { + // some blinds devices do not support "STOP" but would stop if receive UP/DOWN while moving + ((RollershutterItem) item) + .send(emulatedState == PositionStateEnum.INCREASING ? UpDownType.UP : UpDownType.DOWN); + } else { + ((RollershutterItem) item).send(StopMoveType.STOP); + } + emulatedState = PositionStateEnum.STOPPED; + } else { + ((RollershutterItem) item).send(new PercentType(targetPosition)); + if (emulateState) { + @Nullable + PercentType currentPosition = item.getStateAs(PercentType.class); + emulatedState = currentPosition == null || currentPosition.intValue() == targetPosition + ? PositionStateEnum.STOPPED + : currentPosition.intValue() < targetPosition ? PositionStateEnum.INCREASING + : PositionStateEnum.DECREASING; + } + } } else if (item instanceof DimmerItem) { ((DimmerItem) item).send(new PercentType(targetPosition)); } else if (item instanceof NumberItem) {