[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 29 deletions

View File

@ -424,7 +424,7 @@ Following table summarizes the optional characteristics supported by sensors.
| ActiveStatus | Switch, Contact | Accessory current working status. "ON"/"OPEN" indicates that the accessory is active and is functioning without any errors. |
| FaultStatus | Switch, Contact | Accessory fault status. "ON"/"OPEN" value indicates that the accessory has experienced a fault that may be interfering with its intended functionality. A value of "OFF"/"CLOSED" indicates that there is no fault. |
| TamperedStatus | Switch, Contact | Accessory tampered status. "ON"/"OPEN" indicates that the accessory has been tampered. Value should return to "OFF"/"CLOSED" when the accessory has been reset to a non-tampered state. |
| BatteryLowStatus | Switch, Contact | Accessory battery status. "ON"/"OPEN" indicates that the battery level of the accessory is low. Value should return to "OFF"/"CLOSED" when the battery charges to a level thats above the low threshold. |
| BatteryLowStatus | Switch, Contact, Number | Accessory battery status. "ON"/"OPEN" indicates that the battery level of the accessory is low. Value should return to "OFF"/"CLOSED" when the battery charges to a level that's above the low threshold. Alternatively, you can give a Number item that's the battery level, and if it's lower than the lowThreshold configuration, it will report low. |
Switch and Contact items support inversion of the state mapping, e.g. by default the openHAB switch state "ON" is mapped to HomeKit contact sensor state "Open", and "OFF" to "Closed".
The configuration "inverted=true" inverts this mapping, so that "ON" will be mapped to "Closed" and "OFF" to "Open".
@ -555,63 +555,63 @@ Support for this is planned for the future release of openHAB HomeKit binding.
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| LeakSensor | | | | Leak Sensor |
| | LeakDetectedState | | Switch, Contact | Leak sensor state (ON=Leak Detected, OFF=no leak) |
| | | Name | String | Name of the sensor |
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| MotionSensor | | | | Motion Sensor |
| | MotionDetectedState | | Switch, Contact | Motion sensor state (ON=motion detected, OFF=no motion) |
| | | Name | String | Name of the sensor |
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| OccupancySensor | | | | Occupancy Sensor |
| | OccupancyDetectedState | | Switch, Contact | Occupancy sensor state (ON=occupied, OFF=not occupied) |
| | | Name | String | Name of the sensor |
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| ContactSensor | | | | Contact Sensor, An accessory with on/off state that can be viewed in HomeKit but not changed such as a contact sensor for a door or window |
| | ContactSensorState | | Switch, Contact | Contact sensor state (ON=open, OFF=closed) |
| | | Name | String | Name of the sensor |
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| SmokeSensor | | | | Smoke Sensor |
| | SmokeDetectedState | | Switch, Contact | Smoke sensor state (ON=smoke detected, OFF=no smoke) |
| | | Name | String | Name of the sensor |
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| LightSensor | | | | Light sensor |
| | LightLevel | | Number | Light level in lux |
| | | Name | String | Name of the sensor |
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| HumiditySensor | | | | Relative Humidity Sensor providing read-only values |
| | RelativeHumidity | | Number | Relative humidity in % between 0 and 100. additional configuration homekitMultiplicator = <number to multiply result with>. |
| | | Name | String | Name of the sensor |
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| TemperatureSensor | | | | Temperature sensor |
| | CurrentTemperature | | Number | current temperature. supported configuration: minValue, maxValue, step. |
| | | Name | String | Name of the sensor |
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| CarbonDioxideSensor | | | | Carbon Dioxide Sensor |
| | CarbonDioxideDetectedState | | Switch, Contact | carbon dioxide sensor state (ON- abnormal level of carbon dioxide detected, OFF - level is normal) |
| | | CarbonDioxideLevel | Number | Carbon dioxide level in ppm, max 100000 |
@ -620,7 +620,7 @@ Support for this is planned for the future release of openHAB HomeKit binding.
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| CarbonMonoxideSensor | | | | Carbon monoxide Sensor |
| | CarbonMonoxideDetectedState | | Switch, Contact | Carbon monoxide sensor state (ON- abnormal level of carbon monoxide detected, OFF - level is normal) |
| | | CarbonMonoxideLevel | Number | Carbon monoxide level in ppm, max 100 |
@ -629,7 +629,7 @@ Support for this is planned for the future release of openHAB HomeKit binding.
| | | ActiveStatus | Switch, Contact | Working status |
| | | FaultStatus | Switch, Contact | Fault status |
| | | TamperedStatus | Switch, Contact | Tampered status |
| | | BatteryLowStatus | Switch, Contact | Battery status |
| | | BatteryLowStatus | Switch, Contact, Number | Battery status |
| Door | | | | Motorized door. One Rollershutter item covers all mandatory characteristics. see examples below. |
| | CurrentPosition | | Rollershutter, Dimmer, Number | Current position of motorized door |
| | TargetPosition | | Rollershutter, Dimmer, Number | Target position of motorized door |
@ -732,7 +732,7 @@ Support for this is planned for the future release of openHAB HomeKit binding.
| | | LockTargetState | Switch | target states of lock mechanism (OFF=SECURED, ON=UNSECURED) |
| Battery | | | | Accessory with battery. Battery can be chargeable (configuration chargeable:true) and non-chargeable (configuration chargeable:false) |
| | BatteryLevel | | Number | Battery level 0% to 100% |
| | BatteryLowStatus | | Switch, Contact | Battery low indicator. ON/OPEN = battery level is low. |
| | BatteryLowStatus | | Switch, Contact, Number | Battery low indicator. ON/OPEN = battery level is low; for number if the value is below the lowThreshold, then it is low. Default is 20. |
| | BatteryChargingState | | Switch, Contact | Mandatory only for chargeable battery. ON/OPEN = battery is charging |
| | | Name | String | Name of the battery accessory |
| Filter | | | | Accessory with filter maintenance indicator |

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