[homekit] support stop for blinds (#13788)

* implement new blinds logic
* fix NPE, improve emulated state

Signed-off-by: Eugen <eugen@relotrust.com>
This commit is contained in:
eugen 2022-11-27 23:39:50 +01:00 committed by GitHub
parent 1d9bf63d5e
commit 5c5b3c29c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 6 deletions

View File

@ -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"}

View File

@ -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

View File

@ -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<Integer, String> CREATED_ACCESSORY_IDS = new ConcurrentHashMap<>();

View File

@ -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<PositionStateEnum, String> positionStateMapping;
protected boolean emulateState;
protected boolean emulateStopSameDirection;
protected PositionStateEnum emulatedState = PositionStateEnum.STOPPED;
public AbstractHomekitPositionAccessoryImpl(HomekitTaggedItem taggedItem,
List<HomekitTaggedItem> 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<PositionStateEnum> getPositionState() {
return CompletableFuture
.completedFuture(getKeyFromMapping(POSITION_STATE, positionStateMapping, PositionStateEnum.STOPPED));
return CompletableFuture.completedFuture(emulateState ? emulatedState
: getKeyFromMapping(POSITION_STATE, positionStateMapping, PositionStateEnum.STOPPED));
}
public CompletableFuture<Integer> 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) {