Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[homekit] Support multiple values per enum mapping #17144

Merged
merged 2 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
ccutrer marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -322,9 +323,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 @@ -362,9 +363,22 @@ public static <T extends Enum<T> & CharacteristicEnum> Map<T, String> createMapp
var configuration = item.getConfiguration();
if (configuration != null) {
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 @@ -385,17 +399,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 @@ -410,7 +424,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 @@ -433,14 +447,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 @@ -465,18 +488,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 @@ -1196,7 +1232,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 @@ -1218,11 +1254,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