[homekit] Add support for TV accessory (#14055)

* [homekit] TV accessory

Now possible since we support multiple secondary services. Just need to explicitly
declare that InputSource is a linked service to a Television, not just a secondary
service.

Note also that since TV and related services have so many mandatary characteristics
that are often static, I introduced a new way to declare characteristics -
via metadata on the service's item. Honestly, I feel like it's a lot cleaner to
have a factory create the mandatory characteristics the same way as the optional
characteristics, and then construct the service ourselves instead of basing the
service on the specific accessory interface. But this commit is already big enough,
I didn't want to go refactoring _all_ of the accessories to do it that way just
yet. This is why I have "unused" metadata characteristic factory methods for
AirQuality, HeaterCooler, and Thermostat - I started to make those configurable
via metadata, then realized they were mandatory characteristics that couldn't
be found from metadata via the current infrastructure.

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer
2023-01-12 07:29:50 -07:00
committed by GitHub
parent 03a67d25b1
commit 32909fa237
11 changed files with 1102 additions and 98 deletions

View File

@@ -55,6 +55,9 @@ public enum HomekitAccessoryType {
FAUCET("Faucet"),
MICROPHONE("Microphone"),
SLAT("Slat"),
TELEVISION("Television"),
INPUT_SOURCE("InputSource"),
TELEVISION_SPEAKER("TelevisionSpeaker"),
ACCESSORY_GROUP("AccessoryGroup"),
DUMMY("Dummy");

View File

@@ -124,7 +124,24 @@ public enum HomekitCharacteristicType {
FILTER_CHANGE_INDICATION("FilterChangeIndication"),
FILTER_LIFE_LEVEL("FilterLifeLevel"),
FILTER_RESET_INDICATION("FilterResetIndication");
FILTER_RESET_INDICATION("FilterResetIndication"),
ACTIVE_IDENTIFIER("ActiveIdentifier"),
REMOTE_KEY("RemoteKey"),
SLEEP_DISCOVERY_MODE("SleepDiscoveryMode"),
POWER_MODE("PowerMode"),
CLOSED_CAPTIONS("ClosedCaptions"),
PICTURE_MODE("PictureMode"),
CONFIGURED("Configured"),
INPUT_SOURCE_TYPE("InputSourceType"),
CURRENT_VISIBILITY("CurrentVisibility"),
IDENTIFIER("Identifier"),
INPUT_DEVICE_TYPE("InputDeviceType"),
TARGET_VISIBILITY_STATE("TargetVisibilityState"),
VOLUME_SELECTOR("VolumeSelector"),
VOLUME_CONTROL_TYPE("VolumeControlType");
private static final Map<String, HomekitCharacteristicType> TAG_MAP = new HashMap<>();

View File

@@ -30,6 +30,7 @@ import org.openhab.core.library.items.RollershutterItem;
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.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
@@ -221,6 +222,22 @@ public class HomekitTaggedItem {
getName());
}
/**
* Send IncreaseDecreaseType command to a DimmerItem (or a Group:Dimmer)
*/
public void send(IncreaseDecreaseType command) {
if (getItem() instanceof GroupItem && getBaseItem() instanceof DimmerItem) {
((GroupItem) getItem()).send(command);
return;
} else if (getItem() instanceof DimmerItem) {
((DimmerItem) getItem()).send(command);
return;
}
logger.warn(
"Received IncreaseDecreaseType command for item {} that doesn't support it. This is probably a bug.",
getName());
}
/**
* Send PercentType command to a DimmerItem or RollershutterItem (or a Group:Dimmer/Group:Rollershutter)
*

View File

@@ -19,7 +19,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -33,10 +32,10 @@ import org.openhab.core.items.GenericItem;
import org.openhab.core.items.Item;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
import org.openhab.io.homekit.internal.HomekitException;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;
import org.slf4j.Logger;
@@ -71,6 +70,21 @@ public abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
this.services = new ArrayList<>();
this.settings = settings;
this.rawCharacteristics = new HashMap<>();
// create raw characteristics for mandatory characteristics
characteristics.forEach(c -> {
var rawCharacteristic = HomekitCharacteristicFactory.createNullableCharacteristic(c, updater);
// not all mandatory characteristics are creatable via HomekitCharacteristicFactory (yet)
if (rawCharacteristic != null) {
rawCharacteristics.put(rawCharacteristic.getClass(), rawCharacteristic);
}
});
}
/**
* Gives an accessory an opportunity to populate additional characteristics after all optional
* charactericteristics have been added.
*/
public void init() throws HomekitException {
}
/**
@@ -263,26 +277,18 @@ public abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
* @param customEnumList list to store custom state enumeration
*/
@NonNullByDefault
protected <T> void updateMapping(HomekitCharacteristicType characteristicType, Map<T, String> map,
public <T> void updateMapping(HomekitCharacteristicType characteristicType, Map<T, String> map,
@Nullable List<T> customEnumList) {
getCharacteristic(characteristicType).ifPresent(c -> {
final Map<String, Object> configuration = c.getConfiguration();
if (configuration != null) {
map.forEach((k, current_value) -> {
final Object new_value = configuration.get(k.toString());
if (new_value instanceof String) {
map.put(k, (String) new_value);
if (customEnumList != null) {
customEnumList.add(k);
}
}
});
HomekitCharacteristicFactory.updateMapping(configuration, map, customEnumList);
}
});
}
@NonNullByDefault
protected <T> void updateMapping(HomekitCharacteristicType characteristicType, Map<T, String> map) {
public <T> void updateMapping(HomekitCharacteristicType characteristicType, Map<T, String> map) {
updateMapping(characteristicType, map, null);
}
@@ -297,23 +303,11 @@ public abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
* @return key for the value
*/
@NonNullByDefault
protected <T> T getKeyFromMapping(HomekitCharacteristicType characteristicType, Map<T, String> mapping,
public <T> T getKeyFromMapping(HomekitCharacteristicType characteristicType, Map<T, String> mapping,
T defaultValue) {
final Optional<HomekitTaggedItem> c = getCharacteristic(characteristicType);
if (c.isPresent()) {
final State state = c.get().getItem().getState();
logger.trace("getKeyFromMapping: characteristic {}, state {}, mapping {}", characteristicType.getTag(),
state, mapping);
if (state instanceof StringType) {
return mapping.entrySet().stream().filter(entry -> state.toString().equalsIgnoreCase(entry.getValue()))
.findAny().map(Entry::getKey).orElseGet(() -> {
logger.warn(
"Wrong value {} for {} characteristic of the item {}. Expected one of following {}. Returning {}.",
state.toString(), characteristicType.getTag(), c.get().getName(), mapping.values(),
defaultValue);
return defaultValue;
});
}
return HomekitCharacteristicFactory.getKeyFromMapping(c.get(), mapping, defaultValue);
}
return defaultValue;
}
@@ -326,6 +320,9 @@ public abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
}
/**
* If the primary service does not yet exist, it won't be added to it. It's the resposibility
* of the caller to add characteristics when the primary service is created.
*
* @param type
* @param characteristic
*/
@@ -339,9 +336,11 @@ public abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
}
rawCharacteristics.put(characteristic.getClass(), characteristic);
var service = getPrimaryService();
// find the corresponding add method at service and call it.
service.getClass().getMethod("addOptionalCharacteristic", characteristic.getClass()).invoke(service,
characteristic);
if (service != null) {
// find the corresponding add method at service and call it.
service.getClass().getMethod("addOptionalCharacteristic", characteristic.getClass()).invoke(service,
characteristic);
}
}
@NonNullByDefault

View File

@@ -105,6 +105,9 @@ public class HomekitAccessoryFactory {
put(SLAT, new HomekitCharacteristicType[] { CURRENT_SLAT_STATE });
put(FAUCET, new HomekitCharacteristicType[] { ACTIVE_STATUS });
put(MICROPHONE, new HomekitCharacteristicType[] { MUTE });
put(TELEVISION, new HomekitCharacteristicType[] { ACTIVE });
put(INPUT_SOURCE, new HomekitCharacteristicType[] {});
put(TELEVISION_SPEAKER, new HomekitCharacteristicType[] { MUTE });
}
};
@@ -144,6 +147,9 @@ public class HomekitAccessoryFactory {
put(SLAT, HomekitSlatImpl.class);
put(FAUCET, HomekitFaucetImpl.class);
put(MICROPHONE, HomekitMicrophoneImpl.class);
put(TELEVISION, HomekitTelevisionImpl.class);
put(INPUT_SOURCE, HomekitInputSourceImpl.class);
put(TELEVISION_SPEAKER, HomekitTelevisionSpeakerImpl.class);
}
};
@@ -208,6 +214,8 @@ public class HomekitAccessoryFactory {
HomekitAccessoryUpdater.class, HomekitSettings.class)
.newInstance(taggedItem, foundCharacteristics, updater, settings);
addOptionalCharacteristics(taggedItem, accessoryImpl, metadataRegistry);
addOptionalMetadataCharacteristics(taggedItem, accessoryImpl);
accessoryImpl.init();
addLinkedServices(taggedItem, accessoryImpl, metadataRegistry, updater, settings, ancestorServices);
return accessoryImpl;
} else {
@@ -387,6 +395,29 @@ public class HomekitAccessoryFactory {
});
}
/**
* add optional characteristics for given accessory from metadata
*
* @param taggedItem main item
* @param accessory accessory
*/
private static void addOptionalMetadataCharacteristics(HomekitTaggedItem taggedItem,
AbstractHomekitAccessoryImpl accessory)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, HomekitException {
// Check every metadata key looking for a characteristics we can create
var config = taggedItem.getConfiguration();
if (config == null) {
return;
}
for (var entry : config.entrySet().stream().sorted((lhs, rhs) -> lhs.getKey().compareTo(rhs.getKey()))
.collect(Collectors.toList())) {
var characteristic = HomekitMetadataCharacteristicFactory.createCharacteristic(entry.getKey(),
entry.getValue());
if (characteristic.isPresent())
accessory.addCharacteristic(characteristic.get());
}
}
/**
* creates HomeKit services for an openhab item that are members of this group item.
*
@@ -411,7 +442,7 @@ public class HomekitAccessoryFactory {
for (var groupMember : ((GroupItem) item).getMembers().stream()
.sorted((lhs, rhs) -> lhs.getName().compareTo(rhs.getName())).collect(Collectors.toList())) {
final var characteristicTypes = getAccessoryTypes(groupMember, metadataRegistry);
var accessoryTypes = characteristicTypes.stream().filter(c -> c.getValue() == EMPTY)
var accessoryTypes = characteristicTypes.stream().filter(HomekitAccessoryFactory::isRootAccessory)
.collect(Collectors.toList());
logger.trace("accessory types for {} are {}", groupMember.getName(), accessoryTypes);

View File

@@ -16,7 +16,9 @@ import static org.openhab.io.homekit.internal.HomekitCharacteristicType.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
@@ -38,6 +40,7 @@ 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.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
@@ -70,6 +73,7 @@ import io.github.hapjava.characteristics.impl.airquality.PM10DensityCharacterist
import io.github.hapjava.characteristics.impl.airquality.PM25DensityCharacteristic;
import io.github.hapjava.characteristics.impl.airquality.SulphurDioxideDensityCharacteristic;
import io.github.hapjava.characteristics.impl.airquality.VOCDensityCharacteristic;
import io.github.hapjava.characteristics.impl.audio.MuteCharacteristic;
import io.github.hapjava.characteristics.impl.audio.VolumeCharacteristic;
import io.github.hapjava.characteristics.impl.battery.StatusLowBatteryCharacteristic;
import io.github.hapjava.characteristics.impl.battery.StatusLowBatteryEnum;
@@ -79,7 +83,11 @@ import io.github.hapjava.characteristics.impl.carbonmonoxidesensor.CarbonMonoxid
import io.github.hapjava.characteristics.impl.carbonmonoxidesensor.CarbonMonoxidePeakLevelCharacteristic;
import io.github.hapjava.characteristics.impl.common.ActiveCharacteristic;
import io.github.hapjava.characteristics.impl.common.ActiveEnum;
import io.github.hapjava.characteristics.impl.common.ActiveIdentifierCharacteristic;
import io.github.hapjava.characteristics.impl.common.ConfiguredNameCharacteristic;
import io.github.hapjava.characteristics.impl.common.IdentifierCharacteristic;
import io.github.hapjava.characteristics.impl.common.IsConfiguredCharacteristic;
import io.github.hapjava.characteristics.impl.common.IsConfiguredEnum;
import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
import io.github.hapjava.characteristics.impl.common.ObstructionDetectedCharacteristic;
import io.github.hapjava.characteristics.impl.common.StatusActiveCharacteristic;
@@ -101,12 +109,38 @@ import io.github.hapjava.characteristics.impl.fan.TargetFanStateEnum;
import io.github.hapjava.characteristics.impl.filtermaintenance.FilterLifeLevelCharacteristic;
import io.github.hapjava.characteristics.impl.filtermaintenance.ResetFilterIndicationCharacteristic;
import io.github.hapjava.characteristics.impl.humiditysensor.CurrentRelativeHumidityCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.CurrentVisibilityStateCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.CurrentVisibilityStateEnum;
import io.github.hapjava.characteristics.impl.inputsource.InputDeviceTypeCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.InputDeviceTypeEnum;
import io.github.hapjava.characteristics.impl.inputsource.InputSourceTypeCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.InputSourceTypeEnum;
import io.github.hapjava.characteristics.impl.inputsource.TargetVisibilityStateCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.TargetVisibilityStateEnum;
import io.github.hapjava.characteristics.impl.lightbulb.BrightnessCharacteristic;
import io.github.hapjava.characteristics.impl.lightbulb.ColorTemperatureCharacteristic;
import io.github.hapjava.characteristics.impl.lightbulb.HueCharacteristic;
import io.github.hapjava.characteristics.impl.lightbulb.SaturationCharacteristic;
import io.github.hapjava.characteristics.impl.slat.CurrentTiltAngleCharacteristic;
import io.github.hapjava.characteristics.impl.slat.TargetTiltAngleCharacteristic;
import io.github.hapjava.characteristics.impl.television.ClosedCaptionsCharacteristic;
import io.github.hapjava.characteristics.impl.television.ClosedCaptionsEnum;
import io.github.hapjava.characteristics.impl.television.CurrentMediaStateCharacteristic;
import io.github.hapjava.characteristics.impl.television.CurrentMediaStateEnum;
import io.github.hapjava.characteristics.impl.television.PictureModeCharacteristic;
import io.github.hapjava.characteristics.impl.television.PictureModeEnum;
import io.github.hapjava.characteristics.impl.television.PowerModeCharacteristic;
import io.github.hapjava.characteristics.impl.television.PowerModeEnum;
import io.github.hapjava.characteristics.impl.television.RemoteKeyCharacteristic;
import io.github.hapjava.characteristics.impl.television.RemoteKeyEnum;
import io.github.hapjava.characteristics.impl.television.SleepDiscoveryModeCharacteristic;
import io.github.hapjava.characteristics.impl.television.SleepDiscoveryModeEnum;
import io.github.hapjava.characteristics.impl.television.TargetMediaStateCharacteristic;
import io.github.hapjava.characteristics.impl.television.TargetMediaStateEnum;
import io.github.hapjava.characteristics.impl.televisionspeaker.VolumeControlTypeCharacteristic;
import io.github.hapjava.characteristics.impl.televisionspeaker.VolumeControlTypeEnum;
import io.github.hapjava.characteristics.impl.televisionspeaker.VolumeSelectorCharacteristic;
import io.github.hapjava.characteristics.impl.televisionspeaker.VolumeSelectorEnum;
import io.github.hapjava.characteristics.impl.thermostat.CoolingThresholdTemperatureCharacteristic;
import io.github.hapjava.characteristics.impl.thermostat.HeatingThresholdTemperatureCharacteristic;
import io.github.hapjava.characteristics.impl.valve.RemainingDurationCharacteristic;
@@ -176,11 +210,38 @@ public class HomekitCharacteristicFactory {
put(FILTER_RESET_INDICATION, HomekitCharacteristicFactory::createFilterResetCharacteristic);
put(ACTIVE, HomekitCharacteristicFactory::createActiveCharacteristic);
put(CONFIGURED_NAME, HomekitCharacteristicFactory::createConfiguredNameCharacteristic);
put(ACTIVE_IDENTIFIER, HomekitCharacteristicFactory::createActiveIdentifierCharacteristic);
put(REMOTE_KEY, HomekitCharacteristicFactory::createRemoteKeyCharacteristic);
put(SLEEP_DISCOVERY_MODE, HomekitCharacteristicFactory::createSleepDiscoveryModeCharacteristic);
put(POWER_MODE, HomekitCharacteristicFactory::createPowerModeCharacteristic);
put(CLOSED_CAPTIONS, HomekitCharacteristicFactory::createClosedCaptionsCharacteristic);
put(PICTURE_MODE, HomekitCharacteristicFactory::createPictureModeCharacteristic);
put(CONFIGURED, HomekitCharacteristicFactory::createIsConfiguredCharacteristic);
put(INPUT_SOURCE_TYPE, HomekitCharacteristicFactory::createInputSourceTypeCharacteristic);
put(CURRENT_VISIBILITY, HomekitCharacteristicFactory::createCurrentVisibilityStateCharacteristic);
put(IDENTIFIER, HomekitCharacteristicFactory::createIdentifierCharacteristic);
put(INPUT_DEVICE_TYPE, HomekitCharacteristicFactory::createInputDeviceTypeCharacteristic);
put(TARGET_VISIBILITY_STATE, HomekitCharacteristicFactory::createTargetVisibilityStateCharacteristic);
put(VOLUME_SELECTOR, HomekitCharacteristicFactory::createVolumeSelectorCharacteristic);
put(VOLUME_CONTROL_TYPE, HomekitCharacteristicFactory::createVolumeControlTypeCharacteristic);
put(CURRENT_MEDIA_STATE, HomekitCharacteristicFactory::createCurrentMediaStateCharacteristic);
put(TARGET_MEDIA_STATE, HomekitCharacteristicFactory::createTargetMediaStateCharacteristic);
put(MUTE, HomekitCharacteristicFactory::createMuteCharacteristic);
}
};
public static @Nullable Characteristic createNullableCharacteristic(HomekitTaggedItem item,
HomekitAccessoryUpdater updater) {
final @Nullable HomekitCharacteristicType type = item.getCharacteristicType();
logger.trace("Create characteristic {}", item);
if (optional.containsKey(type)) {
return optional.get(type).apply(item, updater);
}
return null;
}
/**
* create optional HomeKit characteristic
* Create HomeKit characteristic
*
* @param item corresponding OH item
* @param updater update to keep OH item and HomeKit characteristic in sync
@@ -188,17 +249,82 @@ public class HomekitCharacteristicFactory {
*/
public static Characteristic createCharacteristic(HomekitTaggedItem item, HomekitAccessoryUpdater updater)
throws HomekitException {
final @Nullable HomekitCharacteristicType type = item.getCharacteristicType();
logger.trace("Create characteristic {}", item);
if (optional.containsKey(type)) {
return optional.get(type).apply(item, updater);
Characteristic characteristic = createNullableCharacteristic(item, updater);
if (characteristic != null) {
return characteristic;
}
final @Nullable HomekitCharacteristicType type = item.getCharacteristicType();
logger.warn("Unsupported optional characteristic from item {}. Accessory type {}, characteristic type {}",
item.getName(), item.getAccessoryType(), type.getTag());
throw new HomekitException(
"Unsupported optional characteristic. Characteristic type \"" + type.getTag() + "\"");
}
public static <T extends Enum<T>> Map<T, String> createMapping(HomekitTaggedItem item, Class<T> klazz) {
EnumMap<T, String> map = new EnumMap(klazz);
for (var k : klazz.getEnumConstants()) {
map.put(k, k.toString());
}
var configuration = item.getConfiguration();
if (configuration != null) {
updateMapping(configuration, map);
}
return map;
}
/**
* Update mapping with values from item configuration.
* It checks for all keys from the mapping whether there is configuration at item with the same key and if yes,
* replace the value.
*
* @param configuration tagged item configuration
* @param map mapping to update
* @param customEnumList list to store custom state enumeration
*/
public static <T> void updateMapping(Map<String, Object> configuration, Map<T, String> map,
@Nullable List<T> customEnumList) {
map.forEach((k, current_value) -> {
final Object new_value = configuration.get(k.toString());
if (new_value instanceof String) {
map.put(k, (String) new_value);
if (customEnumList != null) {
customEnumList.add(k);
}
}
});
}
public static <T> void updateMapping(Map<String, Object> configuration, Map<T, String> map) {
updateMapping(configuration, map, null);
}
/**
* Takes item state as value and retrieves the key for that value from mapping.
* E.g. used to map StringItem value to HomeKit Enum
*
* @param characteristicType characteristicType to identify item
* @param mapping mapping
* @param defaultValue default value if nothing found in mapping
* @param <T> type of the result derived from
* @return key for the value
*/
public static <T> T getKeyFromMapping(HomekitTaggedItem item, Map<T, String> mapping, T defaultValue) {
final State state = item.getItem().getState();
logger.trace("getKeyFromMapping: characteristic {}, state {}, mapping {}", item.getAccessoryType().getTag(),
state, mapping);
if (state instanceof StringType) {
return mapping.entrySet().stream().filter(entry -> state.toString().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 defaultValue;
}
// METHODS TO CREATE SINGLE CHARACTERISTIC FROM OH ITEM
// supporting methods
@@ -208,6 +334,11 @@ public class HomekitCharacteristicFactory {
.getServiceReference(Homekit.class.getName()).getProperty("useFahrenheitTemperature") == Boolean.TRUE;
}
private static <T extends CharacteristicEnum> CompletableFuture<T> getEnumFromItem(HomekitTaggedItem item,
Map<T, String> mapping, T defaultValue) {
return CompletableFuture.completedFuture(getKeyFromMapping(item, mapping, defaultValue));
}
private static <T extends CharacteristicEnum> CompletableFuture<T> getEnumFromItem(HomekitTaggedItem item,
T offEnum, T onEnum, T defaultEnum) {
final State state = item.getItem().getState();
@@ -228,6 +359,11 @@ public class HomekitCharacteristicFactory {
return CompletableFuture.completedFuture(defaultEnum);
}
private static <T extends Enum<T>> void setValueFromEnum(HomekitTaggedItem taggedItem, T value,
Map<T, String> map) {
taggedItem.send(new StringType(map.get(value)));
}
private static void setValueFromEnum(HomekitTaggedItem taggedItem, CharacteristicEnum value,
CharacteristicEnum offEnum, CharacteristicEnum onEnum) {
if (taggedItem.getBaseItem() instanceof SwitchItem) {
@@ -914,8 +1050,8 @@ public class HomekitCharacteristicFactory {
private static ActiveCharacteristic createActiveCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
return new ActiveCharacteristic(
() -> getEnumFromItem(taggedItem, ActiveEnum.ACTIVE, ActiveEnum.INACTIVE, ActiveEnum.INACTIVE),
(value) -> setValueFromEnum(taggedItem, value, ActiveEnum.ACTIVE, ActiveEnum.INACTIVE),
() -> getEnumFromItem(taggedItem, ActiveEnum.INACTIVE, ActiveEnum.ACTIVE, ActiveEnum.INACTIVE),
(value) -> setValueFromEnum(taggedItem, value, ActiveEnum.INACTIVE, ActiveEnum.ACTIVE),
getSubscriber(taggedItem, ACTIVE, updater), getUnsubscriber(taggedItem, ACTIVE, updater));
}
@@ -929,4 +1065,149 @@ public class HomekitCharacteristicFactory {
getSubscriber(taggedItem, CONFIGURED_NAME, updater),
getUnsubscriber(taggedItem, CONFIGURED_NAME, updater));
}
private static ActiveIdentifierCharacteristic createActiveIdentifierCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
return new ActiveIdentifierCharacteristic(getIntSupplier(taggedItem, 1), setIntConsumer(taggedItem),
getSubscriber(taggedItem, ACTIVE_IDENTIFIER, updater),
getUnsubscriber(taggedItem, ACTIVE_IDENTIFIER, updater));
}
private static RemoteKeyCharacteristic createRemoteKeyCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
var map = createMapping(taggedItem, RemoteKeyEnum.class);
return new RemoteKeyCharacteristic((value) -> setValueFromEnum(taggedItem, value, map));
}
private static SleepDiscoveryModeCharacteristic createSleepDiscoveryModeCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
return new SleepDiscoveryModeCharacteristic(
() -> getEnumFromItem(taggedItem, SleepDiscoveryModeEnum.NOT_DISCOVERABLE,
SleepDiscoveryModeEnum.ALWAYS_DISCOVERABLE, SleepDiscoveryModeEnum.ALWAYS_DISCOVERABLE),
getSubscriber(taggedItem, SLEEP_DISCOVERY_MODE, updater),
getUnsubscriber(taggedItem, SLEEP_DISCOVERY_MODE, updater));
}
private static PowerModeCharacteristic createPowerModeCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
return new PowerModeCharacteristic(
(value) -> setValueFromEnum(taggedItem, value, PowerModeEnum.HIDE, PowerModeEnum.SHOW));
}
private static ClosedCaptionsCharacteristic createClosedCaptionsCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
return new ClosedCaptionsCharacteristic(
() -> getEnumFromItem(taggedItem, ClosedCaptionsEnum.DISABLED, ClosedCaptionsEnum.ENABLED,
ClosedCaptionsEnum.DISABLED),
(value) -> setValueFromEnum(taggedItem, value, ClosedCaptionsEnum.DISABLED, ClosedCaptionsEnum.ENABLED),
getSubscriber(taggedItem, CLOSED_CAPTIONS, updater),
getUnsubscriber(taggedItem, CLOSED_CAPTIONS, updater));
}
private static PictureModeCharacteristic createPictureModeCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
var map = createMapping(taggedItem, PictureModeEnum.class);
return new PictureModeCharacteristic(() -> getEnumFromItem(taggedItem, map, PictureModeEnum.OTHER),
(value) -> setValueFromEnum(taggedItem, value, map), getSubscriber(taggedItem, PICTURE_MODE, updater),
getUnsubscriber(taggedItem, PICTURE_MODE, updater));
}
private static IsConfiguredCharacteristic createIsConfiguredCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
return new IsConfiguredCharacteristic(
() -> getEnumFromItem(taggedItem, IsConfiguredEnum.NOT_CONFIGURED, IsConfiguredEnum.CONFIGURED,
IsConfiguredEnum.NOT_CONFIGURED),
(value) -> setValueFromEnum(taggedItem, value, IsConfiguredEnum.NOT_CONFIGURED,
IsConfiguredEnum.CONFIGURED),
getSubscriber(taggedItem, CONFIGURED, updater), getUnsubscriber(taggedItem, CONFIGURED, updater));
}
private static InputSourceTypeCharacteristic createInputSourceTypeCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
var map = createMapping(taggedItem, InputSourceTypeEnum.class);
return new InputSourceTypeCharacteristic(() -> getEnumFromItem(taggedItem, map, InputSourceTypeEnum.OTHER),
getSubscriber(taggedItem, INPUT_SOURCE_TYPE, updater),
getUnsubscriber(taggedItem, INPUT_SOURCE_TYPE, updater));
}
private static CurrentVisibilityStateCharacteristic createCurrentVisibilityStateCharacteristic(
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
return new CurrentVisibilityStateCharacteristic(
() -> getEnumFromItem(taggedItem, CurrentVisibilityStateEnum.HIDDEN, CurrentVisibilityStateEnum.SHOWN,
CurrentVisibilityStateEnum.HIDDEN),
getSubscriber(taggedItem, CURRENT_VISIBILITY, updater),
getUnsubscriber(taggedItem, CURRENT_VISIBILITY, updater));
}
private static IdentifierCharacteristic createIdentifierCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
return new IdentifierCharacteristic(getIntSupplier(taggedItem, 1));
}
private static InputDeviceTypeCharacteristic createInputDeviceTypeCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
var mapping = createMapping(taggedItem, InputDeviceTypeEnum.class);
return new InputDeviceTypeCharacteristic(() -> getEnumFromItem(taggedItem, mapping, InputDeviceTypeEnum.OTHER),
getSubscriber(taggedItem, INPUT_DEVICE_TYPE, updater),
getUnsubscriber(taggedItem, INPUT_DEVICE_TYPE, updater));
}
private static TargetVisibilityStateCharacteristic createTargetVisibilityStateCharacteristic(
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
return new TargetVisibilityStateCharacteristic(
() -> getEnumFromItem(taggedItem, TargetVisibilityStateEnum.HIDDEN, TargetVisibilityStateEnum.SHOWN,
TargetVisibilityStateEnum.HIDDEN),
(value) -> setValueFromEnum(taggedItem, value, TargetVisibilityStateEnum.HIDDEN,
TargetVisibilityStateEnum.SHOWN),
getSubscriber(taggedItem, TARGET_VISIBILITY_STATE, updater),
getUnsubscriber(taggedItem, TARGET_VISIBILITY_STATE, updater));
}
private static VolumeSelectorCharacteristic createVolumeSelectorCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
if (taggedItem.getItem() instanceof DimmerItem) {
return new VolumeSelectorCharacteristic((value) -> taggedItem
.send(value.equals(VolumeSelectorEnum.INCREMENT) ? IncreaseDecreaseType.INCREASE
: IncreaseDecreaseType.DECREASE));
} else {
var map = createMapping(taggedItem, VolumeSelectorEnum.class);
return new VolumeSelectorCharacteristic((value) -> setValueFromEnum(taggedItem, value, map));
}
}
private static VolumeControlTypeCharacteristic createVolumeControlTypeCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
var map = createMapping(taggedItem, VolumeControlTypeEnum.class);
return new VolumeControlTypeCharacteristic(() -> getEnumFromItem(taggedItem, map, VolumeControlTypeEnum.NONE),
getSubscriber(taggedItem, VOLUME_CONTROL_TYPE, updater),
getUnsubscriber(taggedItem, VOLUME_CONTROL_TYPE, updater));
}
private static CurrentMediaStateCharacteristic createCurrentMediaStateCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
var map = createMapping(taggedItem, CurrentMediaStateEnum.class);
return new CurrentMediaStateCharacteristic(
() -> getEnumFromItem(taggedItem, map, CurrentMediaStateEnum.UNKNOWN),
getSubscriber(taggedItem, CURRENT_MEDIA_STATE, updater),
getUnsubscriber(taggedItem, CURRENT_MEDIA_STATE, updater));
}
private static TargetMediaStateCharacteristic createTargetMediaStateCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
var map = createMapping(taggedItem, TargetMediaStateEnum.class);
return new TargetMediaStateCharacteristic(() -> getEnumFromItem(taggedItem, map, TargetMediaStateEnum.STOP),
(value) -> setValueFromEnum(taggedItem, value, map),
getSubscriber(taggedItem, TARGET_MEDIA_STATE, updater),
getUnsubscriber(taggedItem, TARGET_MEDIA_STATE, updater));
}
private static MuteCharacteristic createMuteCharacteristic(HomekitTaggedItem taggedItem,
HomekitAccessoryUpdater updater) {
BooleanItemReader muteReader = new BooleanItemReader(taggedItem.getItem(),
taggedItem.isInverted() ? OnOffType.OFF : OnOffType.ON,
taggedItem.isInverted() ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
return new MuteCharacteristic(() -> CompletableFuture.completedFuture(muteReader.getValue()),
(value) -> taggedItem.send(value ? OnOffType.ON : OnOffType.OFF),
getSubscriber(taggedItem, MUTE, updater), getUnsubscriber(taggedItem, MUTE, updater));
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.io.homekit.internal.accessories;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitException;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;
import io.github.hapjava.accessories.HomekitAccessory;
import io.github.hapjava.characteristics.impl.common.ConfiguredNameCharacteristic;
import io.github.hapjava.characteristics.impl.common.IdentifierCharacteristic;
import io.github.hapjava.characteristics.impl.common.IsConfiguredCharacteristic;
import io.github.hapjava.characteristics.impl.common.IsConfiguredEnum;
import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.CurrentVisibilityStateCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.CurrentVisibilityStateEnum;
import io.github.hapjava.characteristics.impl.inputsource.InputDeviceTypeCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.InputSourceTypeCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.InputSourceTypeEnum;
import io.github.hapjava.characteristics.impl.inputsource.TargetVisibilityStateCharacteristic;
import io.github.hapjava.services.impl.InputSourceService;
/**
* Implements Input Source
*
* This is a little different in that we don't implement the accessory interface.
* This is because several of the "mandatory" characteristics we don't require,
* and wait until all optional attributes are added and if they don't exist
* it will create "default" values for them.
*
* @author Cody Cutrer - Initial contribution
*/
@NonNullByDefault({})
public class HomekitInputSourceImpl extends AbstractHomekitAccessoryImpl {
public HomekitInputSourceImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
super(taggedItem, mandatoryCharacteristics, updater, settings);
}
@Override
public void init() throws HomekitException {
super.init();
// these charactereristics are technically mandatory, but we provide defaults if they're not provided
var configuredNameCharacteristic = getCharacteristic(ConfiguredNameCharacteristic.class)
.orElseGet(() -> new ConfiguredNameCharacteristic(() -> getName(), v -> {
}, v -> {
}, () -> {
}));
var inputSourceTypeCharacteristic = getCharacteristic(InputSourceTypeCharacteristic.class)
.orElseGet(() -> new InputSourceTypeCharacteristic(
() -> CompletableFuture.completedFuture(InputSourceTypeEnum.OTHER), v -> {
}, () -> {
}));
var isConfiguredCharacteristic = getCharacteristic(IsConfiguredCharacteristic.class)
.orElseGet(() -> new IsConfiguredCharacteristic(
() -> CompletableFuture.completedFuture(IsConfiguredEnum.CONFIGURED), v -> {
}, v -> {
}, () -> {
}));
var currentVisibilityStateCharacteristic = getCharacteristic(CurrentVisibilityStateCharacteristic.class)
.orElseGet(() -> new CurrentVisibilityStateCharacteristic(
() -> CompletableFuture.completedFuture(CurrentVisibilityStateEnum.SHOWN), v -> {
}, () -> {
}));
var identifierCharacteristic = getCharacteristic(IdentifierCharacteristic.class)
.orElseGet(() -> new IdentifierCharacteristic(() -> CompletableFuture.completedFuture(1)));
var service = new InputSourceService(configuredNameCharacteristic, inputSourceTypeCharacteristic,
isConfiguredCharacteristic, currentVisibilityStateCharacteristic);
getCharacteristic(NameCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
service.addOptionalCharacteristic(identifierCharacteristic);
getCharacteristic(InputDeviceTypeCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
getCharacteristic(TargetVisibilityStateCharacteristic.class)
.ifPresent(c -> service.addOptionalCharacteristic(c));
getServices().add(service);
}
@Override
public boolean isLinkable(HomekitAccessory parentAccessory) {
return parentAccessory instanceof HomekitTelevisionImpl;
}
}

View File

@@ -0,0 +1,295 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.io.homekit.internal.accessories;
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.*;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.hapjava.characteristics.Characteristic;
import io.github.hapjava.characteristics.impl.airquality.AirQualityCharacteristic;
import io.github.hapjava.characteristics.impl.airquality.AirQualityEnum;
import io.github.hapjava.characteristics.impl.common.ActiveCharacteristic;
import io.github.hapjava.characteristics.impl.common.ActiveEnum;
import io.github.hapjava.characteristics.impl.common.ActiveIdentifierCharacteristic;
import io.github.hapjava.characteristics.impl.common.ConfiguredNameCharacteristic;
import io.github.hapjava.characteristics.impl.common.IdentifierCharacteristic;
import io.github.hapjava.characteristics.impl.common.IsConfiguredCharacteristic;
import io.github.hapjava.characteristics.impl.common.IsConfiguredEnum;
import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
import io.github.hapjava.characteristics.impl.heatercooler.CurrentHeaterCoolerStateCharacteristic;
import io.github.hapjava.characteristics.impl.heatercooler.CurrentHeaterCoolerStateEnum;
import io.github.hapjava.characteristics.impl.heatercooler.TargetHeaterCoolerStateCharacteristic;
import io.github.hapjava.characteristics.impl.heatercooler.TargetHeaterCoolerStateEnum;
import io.github.hapjava.characteristics.impl.inputsource.CurrentVisibilityStateCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.CurrentVisibilityStateEnum;
import io.github.hapjava.characteristics.impl.inputsource.InputDeviceTypeCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.InputDeviceTypeEnum;
import io.github.hapjava.characteristics.impl.inputsource.InputSourceTypeCharacteristic;
import io.github.hapjava.characteristics.impl.inputsource.InputSourceTypeEnum;
import io.github.hapjava.characteristics.impl.television.ClosedCaptionsCharacteristic;
import io.github.hapjava.characteristics.impl.television.ClosedCaptionsEnum;
import io.github.hapjava.characteristics.impl.television.PictureModeCharacteristic;
import io.github.hapjava.characteristics.impl.television.PictureModeEnum;
import io.github.hapjava.characteristics.impl.television.SleepDiscoveryModeCharacteristic;
import io.github.hapjava.characteristics.impl.television.SleepDiscoveryModeEnum;
import io.github.hapjava.characteristics.impl.televisionspeaker.VolumeControlTypeCharacteristic;
import io.github.hapjava.characteristics.impl.televisionspeaker.VolumeControlTypeEnum;
import io.github.hapjava.characteristics.impl.thermostat.CurrentHeatingCoolingStateCharacteristic;
import io.github.hapjava.characteristics.impl.thermostat.CurrentHeatingCoolingStateEnum;
import io.github.hapjava.characteristics.impl.thermostat.TargetHeatingCoolingStateCharacteristic;
import io.github.hapjava.characteristics.impl.thermostat.TargetHeatingCoolingStateEnum;
/**
* Creates an optional characteristics from metadata
*
* @author Cody Cutrer - Initial contribution
*/
@NonNullByDefault
public class HomekitMetadataCharacteristicFactory {
private static final Logger logger = LoggerFactory.getLogger(HomekitMetadataCharacteristicFactory.class);
// List of optional characteristics that can be set via metadata, and the corresponding method to create them.
private final static Map<HomekitCharacteristicType, Function<Object, Characteristic>> optional = new HashMap<>() {
{
put(ACTIVE_IDENTIFIER, HomekitMetadataCharacteristicFactory::createActiveIdentifierCharacteristic);
put(ACTIVE_STATUS, HomekitMetadataCharacteristicFactory::createActiveStatusCharacteristic);
put(AIR_QUALITY, HomekitMetadataCharacteristicFactory::createAirQualityCharacteristic);
put(CLOSED_CAPTIONS, HomekitMetadataCharacteristicFactory::createClosedCaptionsCharacteristic);
put(CONFIGURED, HomekitMetadataCharacteristicFactory::createIsConfiguredCharacteristic);
put(CONFIGURED_NAME, HomekitMetadataCharacteristicFactory::createConfiguredNameCharacteristic);
put(CURRENT_HEATER_COOLER_STATE,
HomekitMetadataCharacteristicFactory::createCurrentHeaterCoolerStateCharacteristic);
put(CURRENT_HEATING_COOLING_STATE,
HomekitMetadataCharacteristicFactory::createCurrentHeatingCoolingStateCharacteristic);
put(CURRENT_VISIBILITY, HomekitMetadataCharacteristicFactory::createCurrentVisibilityCharacteristic);
put(IDENTIFIER, HomekitMetadataCharacteristicFactory::createIdentifierCharacteristic);
put(INPUT_DEVICE_TYPE, HomekitMetadataCharacteristicFactory::createInputDeviceTypeCharacteristic);
put(INPUT_SOURCE_TYPE, HomekitMetadataCharacteristicFactory::createInputSourceTypeCharacteristic);
put(NAME, HomekitMetadataCharacteristicFactory::createNameCharacteristic);
put(PICTURE_MODE, HomekitMetadataCharacteristicFactory::createPictureModeCharacteristic);
put(SLEEP_DISCOVERY_MODE, HomekitMetadataCharacteristicFactory::createSleepDiscoveryModeCharacteristic);
put(TARGET_HEATER_COOLER_STATE,
HomekitMetadataCharacteristicFactory::createTargetHeaterCoolerStateCharacteristic);
put(TARGET_HEATING_COOLING_STATE,
HomekitMetadataCharacteristicFactory::createTargetHeatingCoolingStateCharacteristic);
put(VOLUME_CONTROL_TYPE, HomekitMetadataCharacteristicFactory::createVolumeControlTypeCharacteristic);
}
};
public static Optional<Characteristic> createCharacteristic(String characteristic, Object value) {
var type = HomekitCharacteristicType.valueOfTag(characteristic);
if (type.isEmpty() || !optional.containsKey(type.get()))
return Optional.empty();
return Optional.of(optional.get(type.get()).apply(value));
}
private static Supplier<CompletableFuture<Integer>> getInteger(Object value) {
int intValue;
if (value instanceof BigDecimal) {
intValue = ((BigDecimal) value).intValue();
} else if (value instanceof Float) {
intValue = ((Float) value).intValue();
} else if (value instanceof Integer) {
intValue = (Integer) value;
} else if (value instanceof Long) {
intValue = ((Long) value).intValue();
} else {
intValue = Integer.valueOf(value.toString());
}
return () -> CompletableFuture.completedFuture(intValue);
}
private static Supplier<CompletableFuture<String>> getString(Object value) {
return () -> CompletableFuture.completedFuture(value.toString());
}
private static <T extends Enum<T>> Supplier<CompletableFuture<T>> getEnum(Object value, Class<T> klazz) {
T enumValue = Enum.valueOf(klazz, value.toString());
return () -> CompletableFuture.completedFuture(enumValue);
}
private static <T extends Enum<T>> Supplier<CompletableFuture<T>> getEnum(Object value, Class<T> klazz, T trueValue,
T falseValue) {
if (value.equals(true) || value.equals("true")) {
return () -> CompletableFuture.completedFuture(trueValue);
} else if (value.equals(false) || value.equals("false")) {
return () -> CompletableFuture.completedFuture(falseValue);
}
return getEnum(value, klazz);
}
private static Characteristic createActiveIdentifierCharacteristic(Object value) {
return new ActiveIdentifierCharacteristic(getInteger(value), v -> {
}, v -> {
}, () -> {
});
}
private static Characteristic createActiveStatusCharacteristic(Object value) {
return new ActiveCharacteristic(getEnum(value, ActiveEnum.class, ActiveEnum.ACTIVE, ActiveEnum.INACTIVE), v -> {
}, v -> {
}, () -> {
});
}
private static Characteristic createAirQualityCharacteristic(Object value) {
return new AirQualityCharacteristic(getEnum(value, AirQualityEnum.class), v -> {
}, () -> {
});
}
private static Characteristic createClosedCaptionsCharacteristic(Object value) {
return new ClosedCaptionsCharacteristic(
getEnum(value, ClosedCaptionsEnum.class, ClosedCaptionsEnum.ENABLED, ClosedCaptionsEnum.DISABLED),
v -> {
}, v -> {
}, () -> {
});
}
private static Characteristic createIsConfiguredCharacteristic(Object value) {
return new IsConfiguredCharacteristic(
getEnum(value, IsConfiguredEnum.class, IsConfiguredEnum.CONFIGURED, IsConfiguredEnum.NOT_CONFIGURED),
v -> {
}, v -> {
}, () -> {
});
}
private static Characteristic createConfiguredNameCharacteristic(Object value) {
return new ConfiguredNameCharacteristic(getString(value), v -> {
}, v -> {
}, () -> {
});
}
private static Characteristic createCurrentVisibilityCharacteristic(Object value) {
return new CurrentVisibilityStateCharacteristic(getEnum(value, CurrentVisibilityStateEnum.class,
CurrentVisibilityStateEnum.SHOWN, CurrentVisibilityStateEnum.HIDDEN), v -> {
}, () -> {
});
}
private static Characteristic createCurrentHeaterCoolerStateCharacteristic(Object value) {
var enumSupplier = getEnum(value, CurrentHeaterCoolerStateEnum.class);
CurrentHeaterCoolerStateEnum enumValue;
try {
enumValue = enumSupplier.get().get();
} catch (InterruptedException | ExecutionException e) {
enumValue = CurrentHeaterCoolerStateEnum.INACTIVE;
}
return new CurrentHeaterCoolerStateCharacteristic(new CurrentHeaterCoolerStateEnum[] { enumValue },
enumSupplier, v -> {
}, () -> {
});
}
private static Characteristic createCurrentHeatingCoolingStateCharacteristic(Object value) {
var enumSupplier = getEnum(value, CurrentHeatingCoolingStateEnum.class);
CurrentHeatingCoolingStateEnum enumValue;
try {
enumValue = enumSupplier.get().get();
} catch (InterruptedException | ExecutionException e) {
enumValue = CurrentHeatingCoolingStateEnum.OFF;
}
return new CurrentHeatingCoolingStateCharacteristic(new CurrentHeatingCoolingStateEnum[] { enumValue },
enumSupplier, v -> {
}, () -> {
});
}
private static Characteristic createIdentifierCharacteristic(Object value) {
return new IdentifierCharacteristic(getInteger(value));
}
private static Characteristic createInputDeviceTypeCharacteristic(Object value) {
return new InputDeviceTypeCharacteristic(getEnum(value, InputDeviceTypeEnum.class), v -> {
}, () -> {
});
}
private static Characteristic createInputSourceTypeCharacteristic(Object value) {
return new InputSourceTypeCharacteristic(getEnum(value, InputSourceTypeEnum.class), v -> {
}, () -> {
});
}
private static Characteristic createNameCharacteristic(Object value) {
return new NameCharacteristic(getString(value));
}
private static Characteristic createPictureModeCharacteristic(Object value) {
return new PictureModeCharacteristic(getEnum(value, PictureModeEnum.class), v -> {
}, v -> {
}, () -> {
});
}
private static Characteristic createSleepDiscoveryModeCharacteristic(Object value) {
return new SleepDiscoveryModeCharacteristic(getEnum(value, SleepDiscoveryModeEnum.class,
SleepDiscoveryModeEnum.ALWAYS_DISCOVERABLE, SleepDiscoveryModeEnum.NOT_DISCOVERABLE), v -> {
}, () -> {
});
}
private static Characteristic createTargetHeaterCoolerStateCharacteristic(Object value) {
var enumSupplier = getEnum(value, TargetHeaterCoolerStateEnum.class);
TargetHeaterCoolerStateEnum enumValue;
try {
enumValue = enumSupplier.get().get();
} catch (InterruptedException | ExecutionException e) {
enumValue = TargetHeaterCoolerStateEnum.AUTO;
}
return new TargetHeaterCoolerStateCharacteristic(new TargetHeaterCoolerStateEnum[] { enumValue }, enumSupplier,
v -> {
}, v -> {
}, () -> {
});
}
private static Characteristic createTargetHeatingCoolingStateCharacteristic(Object value) {
var enumSupplier = getEnum(value, TargetHeatingCoolingStateEnum.class);
TargetHeatingCoolingStateEnum enumValue;
try {
enumValue = enumSupplier.get().get();
} catch (InterruptedException | ExecutionException e) {
enumValue = TargetHeatingCoolingStateEnum.OFF;
}
return new TargetHeatingCoolingStateCharacteristic(new TargetHeatingCoolingStateEnum[] { enumValue },
enumSupplier, v -> {
}, v -> {
}, () -> {
});
}
private static Characteristic createVolumeControlTypeCharacteristic(Object value) {
return new VolumeControlTypeCharacteristic(getEnum(value, VolumeControlTypeEnum.class), v -> {
}, () -> {
});
}
}

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.io.homekit.internal.accessories;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitException;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;
import io.github.hapjava.characteristics.impl.common.ActiveCharacteristic;
import io.github.hapjava.characteristics.impl.common.ActiveIdentifierCharacteristic;
import io.github.hapjava.characteristics.impl.common.ConfiguredNameCharacteristic;
import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
import io.github.hapjava.characteristics.impl.lightbulb.BrightnessCharacteristic;
import io.github.hapjava.characteristics.impl.television.ClosedCaptionsCharacteristic;
import io.github.hapjava.characteristics.impl.television.CurrentMediaStateCharacteristic;
import io.github.hapjava.characteristics.impl.television.PictureModeCharacteristic;
import io.github.hapjava.characteristics.impl.television.PowerModeCharacteristic;
import io.github.hapjava.characteristics.impl.television.RemoteKeyCharacteristic;
import io.github.hapjava.characteristics.impl.television.SleepDiscoveryModeCharacteristic;
import io.github.hapjava.characteristics.impl.television.SleepDiscoveryModeEnum;
import io.github.hapjava.characteristics.impl.television.TargetMediaStateCharacteristic;
import io.github.hapjava.services.impl.TelevisionService;
/**
* Implements Television
*
* This is a little different in that we don't implement the accessory interface.
* This is because several of the "mandatory" characteristics we don't require,
* and wait until all optional attributes are added and if they don't exist
* it will create "default" values for them.
*
* @author Cody Cutrer - Initial contribution
*/
@NonNullByDefault({})
public class HomekitTelevisionImpl extends AbstractHomekitAccessoryImpl {
public HomekitTelevisionImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
super(taggedItem, mandatoryCharacteristics, updater, settings);
}
@Override
public void init() throws HomekitException {
super.init();
// these charactereristics are technically mandatory, but we provide defaults if they're not provided
var activeIdentifierCharacteristic = getCharacteristic(ActiveIdentifierCharacteristic.class)
.orElseGet(() -> new ActiveIdentifierCharacteristic(() -> CompletableFuture.completedFuture(1), v -> {
}, v -> {
}, () -> {
}));
var configuredNameCharacteristic = getCharacteristic(ConfiguredNameCharacteristic.class)
.orElseGet(() -> new ConfiguredNameCharacteristic(() -> getName(), v -> {
}, v -> {
}, () -> {
}));
var remoteKeyCharacteristic = getCharacteristic(RemoteKeyCharacteristic.class)
.orElseGet(() -> new RemoteKeyCharacteristic((v) -> {
}));
var sleepDiscoveryModeCharacteristic = getCharacteristic(SleepDiscoveryModeCharacteristic.class)
.orElseGet(() -> new SleepDiscoveryModeCharacteristic(
() -> CompletableFuture.completedFuture(SleepDiscoveryModeEnum.ALWAYS_DISCOVERABLE), v -> {
}, () -> {
}));
var service = new TelevisionService(getCharacteristic(ActiveCharacteristic.class).get(),
activeIdentifierCharacteristic, configuredNameCharacteristic, remoteKeyCharacteristic,
sleepDiscoveryModeCharacteristic);
getCharacteristic(NameCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
getCharacteristic(BrightnessCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
getCharacteristic(PowerModeCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
getCharacteristic(ClosedCaptionsCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
getCharacteristic(CurrentMediaStateCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
getCharacteristic(TargetMediaStateCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
getCharacteristic(PictureModeCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
getServices().add(service);
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.io.homekit.internal.accessories;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
import org.openhab.io.homekit.internal.HomekitException;
import org.openhab.io.homekit.internal.HomekitSettings;
import org.openhab.io.homekit.internal.HomekitTaggedItem;
import io.github.hapjava.characteristics.impl.audio.MuteCharacteristic;
import io.github.hapjava.characteristics.impl.audio.VolumeCharacteristic;
import io.github.hapjava.characteristics.impl.common.ActiveCharacteristic;
import io.github.hapjava.characteristics.impl.televisionspeaker.VolumeControlTypeCharacteristic;
import io.github.hapjava.characteristics.impl.televisionspeaker.VolumeControlTypeEnum;
import io.github.hapjava.characteristics.impl.televisionspeaker.VolumeSelectorCharacteristic;
import io.github.hapjava.services.impl.TelevisionSpeakerService;
/**
* Implements Television Speaker
*
* This is a little different in that we don't implement the accessory interface.
* This is because several of the "mandatory" characteristics we don't require,
* and wait until all optional attributes are added and if they don't exist
* it will create "default" values for them.
*
* @author Cody Cutrer - Initial contribution
*/
@NonNullByDefault({})
public class HomekitTelevisionSpeakerImpl extends AbstractHomekitAccessoryImpl {
public HomekitTelevisionSpeakerImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
super(taggedItem, mandatoryCharacteristics, updater, settings);
}
@Override
public void init() throws HomekitException {
super.init();
var muteCharacteristic = (MuteCharacteristic) HomekitCharacteristicFactory
.createCharacteristic(getCharacteristic(HomekitCharacteristicType.MUTE).get(), getUpdater());
// this characteristic is technically optional, but we provide a default implementation if it's not provided
var volumeControlTypeCharacteristic = getCharacteristic(VolumeControlTypeCharacteristic.class);
// optional characteristics
var volumeCharacteristic = getCharacteristic(VolumeCharacteristic.class);
var volumeSelectorCharacteristic = getCharacteristic(VolumeSelectorCharacteristic.class);
if (!volumeControlTypeCharacteristic.isPresent()) {
VolumeControlTypeEnum type;
if (volumeCharacteristic.isPresent()) {
type = VolumeControlTypeEnum.ABSOLUTE;
} else if (volumeSelectorCharacteristic.isPresent()) {
type = VolumeControlTypeEnum.RELATIVE;
} else {
type = VolumeControlTypeEnum.NONE;
}
volumeControlTypeCharacteristic = Optional
.of(new VolumeControlTypeCharacteristic(() -> CompletableFuture.completedFuture(type), v -> {
}, () -> {
}));
}
var service = new TelevisionSpeakerService(muteCharacteristic);
getCharacteristic(ActiveCharacteristic.class).ifPresent(c -> service.addOptionalCharacteristic(c));
volumeCharacteristic.ifPresent(c -> service.addOptionalCharacteristic(c));
service.addOptionalCharacteristic(volumeControlTypeCharacteristic.get());
getServices().add(service);
}
}