[homekit] allow NumberItems for BatteryLowStatus (#13449)

* [homekit] allow NumberItems for BatteryLowStatus

use a lowThreshold metadata config to infer if it's low

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer
2022-10-03 04:13:39 -06:00
committed by GitHub
parent e9848e27e8
commit 64d97374ad
6 changed files with 97 additions and 29 deletions

View File

@@ -45,14 +45,15 @@ public class HomekitTaggedItem {
private final Logger logger = LoggerFactory.getLogger(HomekitTaggedItem.class);
/** configuration keywords at items level **/
public final static String MIN_VALUE = "minValue";
public final static String MAX_VALUE = "maxValue";
public final static String STEP = "step";
public final static String DIMMER_MODE = "dimmerMode";
public final static String DELAY = "commandDelay";
public final static String INVERTED = "inverted";
public final static String PRIMARY_SERVICE = "primary";
public final static String DIMMER_MODE = "dimmerMode";
public static final String BATTERY_LOW_THRESHOLD = "lowThreshold";
public final static String INSTANCE = "instance";
public final static String INVERTED = "inverted";
public final static String MAX_VALUE = "maxValue";
public final static String MIN_VALUE = "minValue";
public final static String PRIMARY_SERVICE = "primary";
public final static String STEP = "step";
private static final Map<Integer, String> CREATED_ACCESSORY_IDS = new ConcurrentHashMap<>();

View File

@@ -12,6 +12,7 @@
*/
package org.openhab.io.homekit.internal.accessories;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -314,6 +315,25 @@ abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
trueOnOffValue, trueOpenClosedValue);
}
/**
* create boolean reader for a number item with ON state mapped to the value of the
* item being above a given threshold
*
* @param characteristicType characteristic id
* @param trueThreshold threshold for true of number item
* @param invertThreshold result is true if item is less than threshold, instead of more
* @return boolean read
* @throws IncompleteAccessoryException
*/
@NonNullByDefault
protected BooleanItemReader createBooleanReader(HomekitCharacteristicType characteristicType,
BigDecimal trueThreshold, boolean invertThreshold) throws IncompleteAccessoryException {
final HomekitTaggedItem taggedItem = getCharacteristic(characteristicType)
.orElseThrow(() -> new IncompleteAccessoryException(characteristicType));
return new BooleanItemReader(taggedItem.getItem(), taggedItem.isInverted() ? OnOffType.OFF : OnOffType.ON,
taggedItem.isInverted() ? OpenClosedType.CLOSED : OpenClosedType.OPEN, trueThreshold, invertThreshold);
}
/**
* create boolean reader with default ON/OFF mapping considering inverted flag
*

View File

@@ -12,14 +12,20 @@
*/
package org.openhab.io.homekit.internal.accessories;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
import org.openhab.io.homekit.internal.HomekitOHItemProxy;
@@ -38,6 +44,8 @@ public class BooleanItemReader {
private final OnOffType trueOnOffValue;
private final OpenClosedType trueOpenClosedValue;
private final Logger logger = LoggerFactory.getLogger(BooleanItemReader.class);
private final @Nullable BigDecimal trueThreshold;
private final boolean invertThreshold;
/**
*
@@ -46,29 +54,58 @@ public class BooleanItemReader {
* @param trueOpenClosedValue if OpenClosedType, then consider true if this value
*/
BooleanItemReader(Item item, OnOffType trueOnOffValue, OpenClosedType trueOpenClosedValue) {
this(item, trueOnOffValue, trueOpenClosedValue, null, false);
}
/**
*
* @param item The item to read
* @param trueOnOffValue If OnOffType, then consider true if this value
* @param trueOpenClosedValue if OpenClosedType, then consider true if this value
* @param trueThreshold If the state is numeric, and this param is given, return true if the value is above this
* threshold
* @param invertThreshold Invert threshold to be true if below, not above
*/
BooleanItemReader(Item item, OnOffType trueOnOffValue, OpenClosedType trueOpenClosedValue,
@Nullable BigDecimal trueThreshold, boolean invertThreshold) {
this.item = item;
this.trueOnOffValue = trueOnOffValue;
this.trueOpenClosedValue = trueOpenClosedValue;
final Item baseItem = HomekitOHItemProxy.getBaseItem(item);
if (!(baseItem instanceof SwitchItem) && !(baseItem instanceof ContactItem)
&& !(baseItem instanceof StringItem)) {
logger.warn("Item {} is a {} instead of the expected SwitchItem, ContactItem or StringItem", item.getName(),
item.getClass().getName());
this.trueThreshold = trueThreshold;
this.invertThreshold = invertThreshold;
if (!(baseItem instanceof SwitchItem || baseItem instanceof ContactItem || baseItem instanceof StringItem
|| (trueThreshold != null && baseItem instanceof NumberItem))) {
if (trueThreshold != null) {
logger.warn("Item {} is a {} instead of the expected SwitchItem, ContactItem, NumberItem or StringItem",
item.getName(), item.getClass().getName());
} else {
logger.warn("Item {} is a {} instead of the expected SwitchItem, ContactItem or StringItem",
item.getName(), item.getClass().getName());
}
}
}
boolean getValue() {
final State state = item.getState();
final BigDecimal localTrueThresheold = trueThreshold;
if (state instanceof OnOffType) {
return state.equals(trueOnOffValue);
} else if (state instanceof OpenClosedType) {
return state.equals(trueOpenClosedValue);
} else if (state instanceof StringType) {
return state.toString().equalsIgnoreCase("Open") || state.toString().equalsIgnoreCase("Opened");
} else {
logger.debug("Unexpected item state, returning false. Item {}, State {}", item.getName(), state);
return false;
} else if (localTrueThresheold != null) {
if (state instanceof DecimalType) {
final boolean result = ((DecimalType) state).toBigDecimal().compareTo(localTrueThresheold) > 0;
return result ^ invertThreshold;
} else if (state instanceof QuantityType) {
final boolean result = ((QuantityType<?>) state).toBigDecimal().compareTo(localTrueThresheold) > 0;
return result ^ invertThreshold;
}
}
logger.debug("Unexpected item state, returning false. Item {}, State {}", item.getName(), state);
return false;
}
private OnOffType getOffValue(OnOffType onValue) {

View File

@@ -16,12 +16,14 @@ import static org.openhab.io.homekit.internal.HomekitCharacteristicType.BATTERY_
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.BATTERY_LEVEL;
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.BATTERY_LOW_STATUS;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType;
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;
@@ -42,11 +44,14 @@ public class HomekitBatteryImpl extends AbstractHomekitAccessoryImpl implements
private final BooleanItemReader lowBatteryReader;
private BooleanItemReader chargingBatteryReader;
private final boolean isChargeable;
private final BigDecimal lowThreshold;
public HomekitBatteryImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
super(taggedItem, mandatoryCharacteristics, updater, settings);
lowBatteryReader = createBooleanReader(BATTERY_LOW_STATUS);
lowThreshold = getAccessoryConfiguration(HomekitCharacteristicType.BATTERY_LOW_STATUS,
HomekitTaggedItem.BATTERY_LOW_THRESHOLD, BigDecimal.valueOf(20));
lowBatteryReader = createBooleanReader(BATTERY_LOW_STATUS, lowThreshold, true);
isChargeable = getAccessoryConfigurationAsBoolean(BATTERY_TYPE, false);
if (isChargeable) {
chargingBatteryReader = createBooleanReader(BATTERY_CHARGING_STATE);

View File

@@ -405,9 +405,14 @@ public class HomekitCharacteristicFactory {
// create method for characteristic
private static StatusLowBatteryCharacteristic createStatusLowBatteryCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
BigDecimal lowThreshold = taggedItem.getConfiguration(HomekitTaggedItem.BATTERY_LOW_THRESHOLD,
BigDecimal.valueOf(20));
BooleanItemReader lowBatteryReader = new BooleanItemReader(taggedItem.getItem(),
taggedItem.isInverted() ? OnOffType.OFF : OnOffType.ON,
taggedItem.isInverted() ? OpenClosedType.CLOSED : OpenClosedType.OPEN, lowThreshold, true);
return new StatusLowBatteryCharacteristic(
() -> getEnumFromItem(taggedItem, StatusLowBatteryEnum.NORMAL, StatusLowBatteryEnum.LOW,
StatusLowBatteryEnum.NORMAL),
() -> CompletableFuture.completedFuture(
lowBatteryReader.getValue() ? StatusLowBatteryEnum.LOW : StatusLowBatteryEnum.NORMAL),
getSubscriber(taggedItem, BATTERY_LOW_STATUS, updater),
getUnsubscriber(taggedItem, BATTERY_LOW_STATUS, updater));
}