Skip to content

Commit

Permalink
[homekit] Support multiple values per enum mapping (openhab#17144)
Browse files Browse the repository at this point in the history
* [homekit] Support multiple values per enum mapping

Signed-off-by: Cody Cutrer <cody@cutrer.us>
  • Loading branch information
ccutrer authored and matchews committed Oct 18, 2024
1 parent c202549 commit 991e6f8
Show file tree
Hide file tree
Showing 18 changed files with 105 additions and 50 deletions.
19 changes: 19 additions & 0 deletions bundles/org.openhab.io.homekit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,25 @@ All enum values can be customized via item metadata. I.e. `HEAT="heating", COOL=
They are appropriately marked.
Enums that are linked to Switches or Contacts have an `inverted` param that will reverse the sense of `ON`/`OFF` or `OPEN`/`CLOSED`.

Enum mappings can have multiple values for a single key.
These must be an array, not a comma separated string.
If the characteristic can be set by HomeKit, the first value will be used when sending the command to the linked item.
Such a mapping can be configured manually in MainUI on HomeKit metadata in the Code editor:
```yaml
value: "Lock"
config:
SECURE:
- LOCK
- LOCKED
UNSECURE:
- UNLOCK
- UNLOCKED
```
Or in a `.items` file:
```java
String MyLock "My Lock" { homekit="Lock"[SECURE="LOCK","LOCKED", UNSECURE="UNLOCK","UNLOCKED"] }
```

All accessories support the following characteristics that can be set via metadata or linked to a String item:
* Name (defaults to item's label)
* Manufacturer (defaults to "none")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,19 +372,19 @@ protected <T> T getAccessoryConfiguration(HomekitCharacteristicType characterist
}

@NonNullByDefault
protected <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(
protected <T extends Enum<T> & CharacteristicEnum> Map<T, Object> createMapping(
HomekitCharacteristicType characteristicType, Class<T> klazz) {
return createMapping(characteristicType, klazz, null, false);
}

@NonNullByDefault
protected <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(
protected <T extends Enum<T> & CharacteristicEnum> Map<T, Object> createMapping(
HomekitCharacteristicType characteristicType, Class<T> klazz, boolean inverted) {
return createMapping(characteristicType, klazz, null, inverted);
}

@NonNullByDefault
protected <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(
protected <T extends Enum<T> & CharacteristicEnum> Map<T, Object> createMapping(
HomekitCharacteristicType characteristicType, Class<T> klazz, @Nullable List<T> customEnumList) {
return createMapping(characteristicType, klazz, customEnumList, false);
}
Expand All @@ -398,7 +398,7 @@ protected <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(
* @return mapping of enum values to custom string values
*/
@NonNullByDefault
protected <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(
protected <T extends Enum<T> & CharacteristicEnum> Map<T, Object> createMapping(
HomekitCharacteristicType characteristicType, Class<T> klazz, @Nullable List<T> customEnumList,
boolean inverted) {
HomekitTaggedItem item = getCharacteristic(characteristicType).get();
Expand All @@ -416,7 +416,7 @@ protected <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(
* @return key for the value
*/
@NonNullByDefault
public <T> T getKeyFromMapping(HomekitCharacteristicType characteristicType, Map<T, String> mapping,
public <T> T getKeyFromMapping(HomekitCharacteristicType characteristicType, Map<T, Object> mapping,
T defaultValue) {
final Optional<HomekitTaggedItem> c = getCharacteristic(characteristicType);
if (c.isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ abstract class AbstractHomekitPositionAccessoryImpl extends AbstractHomekitAcces
private final Logger logger = LoggerFactory.getLogger(AbstractHomekitPositionAccessoryImpl.class);
protected int closedPosition;
protected int openPosition;
private final Map<PositionStateEnum, String> positionStateMapping;
private final Map<PositionStateEnum, Object> positionStateMapping;
protected boolean emulateState;
protected boolean emulateStopSameDirection;
protected boolean sendUpDownForExtents;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* @author Eugen Freiter - Initial contribution
*/
public class HomekitAirQualitySensorImpl extends AbstractHomekitAccessoryImpl implements AirQualityAccessory {
private final Map<AirQualityEnum, String> qualityStateMapping;
private final Map<AirQualityEnum, Object> qualityStateMapping;

public HomekitAirQualitySensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
List<Characteristic> mandatoryRawCharacteristics, HomekitAccessoryUpdater updater, HomekitSettings settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
*/
public class HomekitCarbonDioxideSensorImpl extends AbstractHomekitAccessoryImpl
implements CarbonDioxideSensorAccessory {
private final Map<CarbonDioxideDetectedEnum, String> mapping;
private final Map<CarbonDioxideDetectedEnum, Object> mapping;

public HomekitCarbonDioxideSensorImpl(HomekitTaggedItem taggedItem,
List<HomekitTaggedItem> mandatoryCharacteristics, List<Characteristic> mandatoryRawCharacteristics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
*/
public class HomekitCarbonMonoxideSensorImpl extends AbstractHomekitAccessoryImpl
implements CarbonMonoxideSensorAccessory {
private final Map<CarbonMonoxideDetectedEnum, String> mapping;
private final Map<CarbonMonoxideDetectedEnum, Object> mapping;

public HomekitCarbonMonoxideSensorImpl(HomekitTaggedItem taggedItem,
List<HomekitTaggedItem> mandatoryCharacteristics, List<Characteristic> mandatoryRawCharacteristics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.measure.Quantity;
import javax.measure.Unit;
Expand Down Expand Up @@ -329,9 +330,9 @@ public static Characteristic createCharacteristic(HomekitTaggedItem item, Homeki
* associated with, which has already been set.
* @return
*/
public static <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(HomekitTaggedItem item,
public static <T extends Enum<T> & CharacteristicEnum> Map<T, Object> createMapping(HomekitTaggedItem item,
Class<T> klazz, @Nullable List<T> customEnumList, boolean inverted) {
EnumMap<T, String> map = new EnumMap(klazz);
EnumMap<T, Object> map = new EnumMap(klazz);
var dataTypes = item.getBaseItem().getAcceptedDataTypes();
boolean switchType = dataTypes.contains(OnOffType.class);
boolean contactType = dataTypes.contains(OpenClosedType.class);
Expand Down Expand Up @@ -379,9 +380,22 @@ public static <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapp
}
if (configuration != null && !configuration.isEmpty()) {
map.forEach((k, current_value) -> {
final Object newValue = configuration.get(k.toString());
if (newValue instanceof String || newValue instanceof Number) {
map.put(k, newValue.toString());
Object newValue = configuration.get(k.toString());
if (newValue instanceof String || newValue instanceof Number || newValue instanceof List) {
if (newValue instanceof Number) {
newValue = newValue.toString();
} else if (newValue instanceof List listValue) {
newValue = listValue.stream().map(v -> {
// they probably put "NULL" in the YAML in MainUI;
// and they meant it as a string to match the UnDefType.NULL
if (v == null) {
return "NULL";
} else {
return v.toString();
}
}).collect(Collectors.toList());
}
map.put(k, Objects.requireNonNull(newValue));
if (customEnumList != null) {
customEnumList.add(k);
}
Expand All @@ -402,17 +416,17 @@ public static <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapp
return map;
}

public static <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(HomekitTaggedItem item,
public static <T extends Enum<T> & CharacteristicEnum> Map<T, Object> createMapping(HomekitTaggedItem item,
Class<T> klazz) {
return createMapping(item, klazz, null, false);
}

public static <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(HomekitTaggedItem item,
public static <T extends Enum<T> & CharacteristicEnum> Map<T, Object> createMapping(HomekitTaggedItem item,
Class<T> klazz, @Nullable List<T> customEnumList) {
return createMapping(item, klazz, customEnumList, false);
}

public static <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapping(HomekitTaggedItem item,
public static <T extends Enum<T> & CharacteristicEnum> Map<T, Object> createMapping(HomekitTaggedItem item,
Class<T> klazz, boolean inverted) {
return createMapping(item, klazz, null, inverted);
}
Expand All @@ -427,7 +441,7 @@ public static <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapp
* @param <T> type of the result derived from
* @return key for the value
*/
public static <T> T getKeyFromMapping(HomekitTaggedItem item, State state, Map<T, String> mapping, T defaultValue) {
public static <T> T getKeyFromMapping(HomekitTaggedItem item, State state, Map<T, Object> mapping, T defaultValue) {
LOGGER.trace("getKeyFromMapping: characteristic {}, state {}, mapping {}", item.getAccessoryType().getTag(),
state, mapping);

Expand All @@ -450,14 +464,23 @@ public static <T> T getKeyFromMapping(HomekitTaggedItem item, State state, Map<T
return defaultValue;
}

return mapping.entrySet().stream().filter(entry -> value.equalsIgnoreCase(entry.getValue())).findAny()
.map(Map.Entry::getKey).orElseGet(() -> {
LOGGER.warn(
"Wrong value {} for {} characteristic of the item {}. Expected one of following {}. Returning {}.",
state.toString(), item.getAccessoryType().getTag(), item.getName(), mapping.values(),
defaultValue);
return defaultValue;
});
return mapping.entrySet().stream().filter(entry -> {
Object mappingValue = entry.getValue();
if (mappingValue instanceof String stringValue) {
return value.equalsIgnoreCase(stringValue);
} else if (mappingValue instanceof List listValue) {
return listValue.stream().filter(listEntry -> value.equalsIgnoreCase(listEntry.toString())).findAny()
.isPresent();
} else {
LOGGER.warn("Found unexpected enum value type {}; this is a bug.", mappingValue.getClass());
return false;
}
}).findAny().map(Map.Entry::getKey).orElseGet(() -> {
LOGGER.warn(
"Wrong value {} for {} characteristic of the item {}. Expected one of following {}. Returning {}.",
state.toString(), item.getAccessoryType().getTag(), item.getName(), mapping.values(), defaultValue);
return defaultValue;
});
}

// supporting methods
Expand All @@ -482,18 +505,31 @@ public static Unit<Temperature> getSystemTemperatureUnit() {
}

private static <T extends CharacteristicEnum> CompletableFuture<T> getEnumFromItem(HomekitTaggedItem item,
Map<T, String> mapping, T defaultValue) {
Map<T, Object> mapping, T defaultValue) {
return CompletableFuture
.completedFuture(getKeyFromMapping(item, item.getItem().getState(), mapping, defaultValue));
}

public static <T extends Enum<T>> void setValueFromEnum(HomekitTaggedItem taggedItem, T value, Map<T, String> map) {
public static <T extends Enum<T>> void setValueFromEnum(HomekitTaggedItem taggedItem, T value, Map<T, Object> map) {
Object mapValue = map.get(value);
// if the mapping has multiple values for this enum, just use the first one for the command sent to the item
if (mapValue instanceof List listValue) {
if (listValue.isEmpty()) {
mapValue = null;
} else {
mapValue = listValue.get(0);
}
}
if (mapValue == null) {
LOGGER.warn("Unable to find mapping value for {} for item {}", value, taggedItem.getName());
return;
}
if (taggedItem.getBaseItem() instanceof NumberItem) {
taggedItem.send(new DecimalType(Objects.requireNonNull(map.get(value))));
taggedItem.send(new DecimalType(mapValue.toString()));
} else if (taggedItem.getBaseItem() instanceof SwitchItem) {
taggedItem.send(OnOffType.from(Objects.requireNonNull(map.get(value))));
taggedItem.send(OnOffType.from(mapValue.toString()));
} else {
taggedItem.send(new StringType(map.get(value)));
taggedItem.send(new StringType(mapValue.toString()));
}
}

Expand Down Expand Up @@ -1242,7 +1278,7 @@ private static PowerModeCharacteristic createPowerModeCharacteristic(HomekitTagg
private static ProgrammableSwitchEventCharacteristic createProgrammableSwitchEventCharacteristic(
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
// have to build the map custom, since SINGLE_PRESS starts at 0
Map<ProgrammableSwitchEnum, String> map = new EnumMap(ProgrammableSwitchEnum.class);
Map<ProgrammableSwitchEnum, Object> map = new EnumMap(ProgrammableSwitchEnum.class);
List<ProgrammableSwitchEnum> validValues = new ArrayList<>();

if (taggedItem.getBaseItem().getAcceptedDataTypes().contains(OnOffType.class)) {
Expand All @@ -1264,11 +1300,11 @@ private static ProgrammableSwitchEventCharacteristic createProgrammableSwitchEve
private static class ProgrammableSwitchEventCharacteristicHelper {
private @Nullable ProgrammableSwitchEnum lastValue = null;
private final HomekitTaggedItem taggedItem;
private final Map<ProgrammableSwitchEnum, String> map;
private final Map<ProgrammableSwitchEnum, Object> map;
private final HomekitAccessoryUpdater updater;

ProgrammableSwitchEventCharacteristicHelper(HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater,
Map<ProgrammableSwitchEnum, String> map) {
Map<ProgrammableSwitchEnum, Object> map) {
this.taggedItem = taggedItem;
this.map = map;
this.updater = updater;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
* @author Philipp Arndt - Initial contribution
*/
public class HomekitContactSensorImpl extends AbstractHomekitAccessoryImpl implements ContactSensorAccessory {
private final Map<ContactStateEnum, String> mapping;
private final Map<ContactStateEnum, Object> mapping;

public HomekitContactSensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
List<Characteristic> mandatoryRawCharacteristics, HomekitAccessoryUpdater updater, HomekitSettings settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* @author Eugen Freiter - Initial contribution
*/
public class HomekitFilterMaintenanceImpl extends AbstractHomekitAccessoryImpl implements FilterMaintenanceAccessory {
private final Map<FilterChangeIndicationEnum, String> mapping;
private final Map<FilterChangeIndicationEnum, Object> mapping;

public HomekitFilterMaintenanceImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
List<Characteristic> mandatoryRawCharacteristics, HomekitAccessoryUpdater updater, HomekitSettings settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
public class HomekitHeaterCoolerImpl extends AbstractHomekitAccessoryImpl implements HeaterCoolerAccessory {
private final Logger logger = LoggerFactory.getLogger(HomekitHeaterCoolerImpl.class);
private final BooleanItemReader activeReader;
private final Map<CurrentHeaterCoolerStateEnum, String> currentStateMapping;
private final Map<TargetHeaterCoolerStateEnum, String> targetStateMapping;
private final Map<CurrentHeaterCoolerStateEnum, Object> currentStateMapping;
private final Map<TargetHeaterCoolerStateEnum, Object> targetStateMapping;

private final List<CurrentHeaterCoolerStateEnum> customCurrentStateList = new ArrayList<>();
private final List<TargetHeaterCoolerStateEnum> customTargetStateList = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
*/
@NonNullByDefault({})
public class HomekitIrrigationSystemImpl extends AbstractHomekitAccessoryImpl implements IrrigationSystemAccessory {
private Map<InUseEnum, String> inUseMapping;
private Map<ProgramModeEnum, String> programModeMap;
private Map<InUseEnum, Object> inUseMapping;
private Map<ProgramModeEnum, Object> programModeMap;

public HomekitIrrigationSystemImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
List<Characteristic> mandatoryRawCharacteristics, HomekitAccessoryUpdater updater, HomekitSettings settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
* @author Tim Harper - Initial contribution
*/
public class HomekitLeakSensorImpl extends AbstractHomekitAccessoryImpl implements LeakSensorAccessory {
private final Map<LeakDetectedStateEnum, String> mapping;
private final Map<LeakDetectedStateEnum, Object> mapping;

public HomekitLeakSensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
List<Characteristic> mandatoryRawCharacteristics, HomekitAccessoryUpdater updater, HomekitSettings settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
*
*/
public class HomekitLockImpl extends AbstractHomekitAccessoryImpl implements LockMechanismAccessory {
final Map<LockCurrentStateEnum, String> currentStateMapping;
final Map<LockTargetStateEnum, String> targetStateMapping;
final Map<LockCurrentStateEnum, Object> currentStateMapping;
final Map<LockTargetStateEnum, Object> targetStateMapping;

public HomekitLockImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
List<Characteristic> mandatoryRawCharacteristics, HomekitAccessoryUpdater updater,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
* @author Tim Harper - Initial contribution
*/
public class HomekitOccupancySensorImpl extends AbstractHomekitAccessoryImpl implements OccupancySensorAccessory {
private final Map<OccupancyDetectedEnum, String> mapping;
private final Map<OccupancyDetectedEnum, Object> mapping;

public HomekitOccupancySensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
List<Characteristic> mandatoryRawCharacteristics, HomekitAccessoryUpdater updater, HomekitSettings settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
* @author Cody Cutrer - Initial contribution
*/
public class HomekitSecuritySystemImpl extends AbstractHomekitAccessoryImpl implements SecuritySystemAccessory {
private final Map<CurrentSecuritySystemStateEnum, String> currentStateMapping;
private final Map<TargetSecuritySystemStateEnum, String> targetStateMapping;
private final Map<CurrentSecuritySystemStateEnum, Object> currentStateMapping;
private final Map<TargetSecuritySystemStateEnum, Object> targetStateMapping;
private final List<CurrentSecuritySystemStateEnum> customCurrentStateList = new ArrayList<>();
private final List<TargetSecuritySystemStateEnum> customTargetStateList = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
*/
public class HomekitSlatImpl extends AbstractHomekitAccessoryImpl implements SlatAccessory {
private static final String CONFIG_TYPE = "type";
private final Map<CurrentSlatStateEnum, String> currentSlatStateMapping;
private final Map<CurrentSlatStateEnum, Object> currentSlatStateMapping;
private final SlatTypeEnum slatType;

public HomekitSlatImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
Expand Down
Loading

0 comments on commit 991e6f8

Please sign in to comment.