diff --git a/CODEOWNERS b/CODEOWNERS index 9b9bf59650c17..1b63d9f9c7f4c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -224,7 +224,7 @@ /bundles/org.openhab.binding.zway/ @pathec /bundles/org.openhab.extensionservice.marketplace/ @kaikreuzer /bundles/org.openhab.extensionservice.marketplace.automation/ @kaikreuzer -/bundles/org.openhab.io.homekit/ @beowulfe +/bundles/org.openhab.io.homekit/ @beowulfe @yfre /bundles/org.openhab.io.hueemulation/ @davidgraeff @digitaldan /bundles/org.openhab.io.imperihome/ @pdegeus /bundles/org.openhab.io.javasound/ @kaikreuzer diff --git a/bundles/org.openhab.io.homekit/README.md b/bundles/org.openhab.io.homekit/README.md index 20e159dc9c4b9..222b7ca0a9f79 100644 --- a/bundles/org.openhab.io.homekit/README.md +++ b/bundles/org.openhab.io.homekit/README.md @@ -218,7 +218,7 @@ A full list of supported accessory types can be found in the table *below*. | | | Name | String | Name of the light | | | | Hue | Dimmer, Color | Hue | | | | Saturation | Dimmer, Color | Saturation in % (1-100) | -| | | Brightness | Dimmer, Color | Brightness in % (1-100) | +| | | Brightness | Dimmer, Color | Brightness in % (1-100). See "Usage of dimmer modes" for configuration details. | | | | ColorTemperature | Number | Color temperature which is represented in reciprocal megaKelvin, values - 50 to 400. should not be used in combination with hue, saturation and brightness | | Fan | | | | Fan | | | ActiveStatus | | Switch | accessory current working status. A value of "ON"/"OPEN" indicate that the accessory is active and is functioning without any errors. | @@ -380,6 +380,36 @@ String security_current_state "Security Current State" String security_target_state "Security Target State" (gSecuritySystem) {homekit="SecuritySystem.TargetSecuritySystemState"} ``` +## Usage of dimmer modes + +The way HomeKit handles dimmer devices can be different to the actual dimmers' way of working. +HomeKit home app sends following commands/update: + +- On brightness change home app sends "ON" event along with target brightness, e.g. "Brightness = 50%" + "State = ON". +- On "ON" event home app sends "ON" along with brightness 100%, i.e. "Brightness = 100%" + "State = ON" +- On "OFF" event home app sends "OFF" without brightness information. + +However, some dimmer devices for example do not expect brightness on "ON" event, some others do not expect "ON" upon brightness change. +In order to support different devices HomeKit binding can filter some events. Which events should be filtered is defined via dimmerMode configuration. + +```xtend +Dimmer dimmer_light "Dimmer Light" {homekit="Lighting, Lighting.Brightness" [dimmerMode=""]} +``` + +Following modes are supported: + +- "normal" - no filtering. The commands will be sent to device as received from HomeKit. This is default mode. +- "filterOn" - ON events are filtered out. only OFF events and brightness information are sent +- "filterBrightness100" - only Brightness=100% is filtered out. everything else sent unchanged. This allows custom logic for soft launch in devices. +- "filterOnExceptBrightness100" - ON events are filtered out in all cases except of brightness = 100%. + + Examples: + + ```xtend + Dimmer dimmer_light_1 "Dimmer Light 1" {homekit="Lighting, Lighting.Brightness" [dimmerMode="filterOn"]} + Dimmer dimmer_light_2 "Dimmer Light 2" {homekit="Lighting, Lighting.Brightness" [dimmerMode="filterBrightness100"]} + Dimmer dimmer_light_3 "Dimmer Light 3" {homekit="Lighting, Lighting.Brightness" [dimmerMode="filterOnExceptBrightness100"]} + ``` ## Usage of valve timer diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitChangeListener.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitChangeListener.java index d55626b36e77a..bae3bda5ab352 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitChangeListener.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitChangeListener.java @@ -227,7 +227,8 @@ private void createRootAccessories(Item item) { if (!accessoryTypes.isEmpty() && groups.isEmpty()) { // it has homekit accessory type and is not part of bigger // homekit group item logger.trace("Item {} is a HomeKit accessory of types {}", item.getName(), accessoryTypes); - accessoryTypes.stream().forEach(rootAccessory -> createRootAccessory(new HomekitTaggedItem(item, + final HomekitOHItemProxy itemProxy = new HomekitOHItemProxy(item); + accessoryTypes.stream().forEach(rootAccessory -> createRootAccessory(new HomekitTaggedItem(itemProxy, rootAccessory.getKey(), HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry)))); } } diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitCommandType.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitCommandType.java new file mode 100644 index 0000000000000..0c48aad8908a2 --- /dev/null +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitCommandType.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2020 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; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * + * Different command types supported by HomekitOHItemProxy. + * + * @author Eugen Freiter - Initial contribution + */ + +@NonNullByDefault +public enum HomekitCommandType { + HUE_COMMAND, + SATURATION_COMMAND, + BRIGHTNESS_COMMAND, + ON_COMMAND; +} diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitDimmerMode.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitDimmerMode.java new file mode 100644 index 0000000000000..9026b8bead607 --- /dev/null +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitDimmerMode.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2020 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; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Dimmer commands are handled differently by different devices. + * Some devices expect only the brightness updates, some other expect brightness as well as "On/Off" commands. + * This enum describes different modes of dimmer handling in the context of HomeKit binding. + * + * Following modes are supported: + * DIMMER_MODE_NORMAL - no filtering. The commands will be sent to device as received from HomeKit. + * DIMMER_MODE_FILTER_ON - ON events are filtered out. only OFF and brightness information are sent + * DIMMER_MODE_FILTER_BRIGHTNESS_100 - only Brightness=100% is filtered out. everything else unchanged. This allows + * custom logic for soft launch in devices. + * DIMMER_MODE_FILTER_ON_EXCEPT_BRIGHTNESS_100 - ON events are filtered out in all cases except of Brightness = 100%. + * + * @author Eugen Freiter - Initial contribution + */ + +@NonNullByDefault +public enum HomekitDimmerMode { + DIMMER_MODE_NORMAL("normal"), + DIMMER_MODE_FILTER_ON("filterOn"), + DIMMER_MODE_FILTER_BRIGHTNESS_100("filterBrightness100"), + DIMMER_MODE_FILTER_ON_EXCEPT_BRIGHTNESS_100("filterOnExceptBrightness100"); + + private static final Map TAG_MAP = Arrays.stream(HomekitDimmerMode.values()) + .collect(Collectors.toMap(type -> type.tag.toUpperCase(), type -> type)); + + private final String tag; + + private HomekitDimmerMode(String tag) { + this.tag = tag; + } + + public String getTag() { + return tag; + } + + public static Optional valueOfTag(String tag) { + return Optional.ofNullable(TAG_MAP.get(tag.toUpperCase())); + } +} diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitOHItemProxy.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitOHItemProxy.java new file mode 100644 index 0000000000000..d1ef1a0ae4c97 --- /dev/null +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitOHItemProxy.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2010-2020 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; + +import static org.openhab.io.homekit.internal.HomekitCommandType.*; +import static org.openhab.io.homekit.internal.HomekitDimmerMode.*; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.common.ThreadPoolManager; +import org.eclipse.smarthome.core.items.Item; +import org.eclipse.smarthome.core.library.items.ColorItem; +import org.eclipse.smarthome.core.library.items.DimmerItem; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.HSBType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.types.State; +import org.eclipse.smarthome.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * Proxy class that can collect multiple commands for the same openHAB item and merge them to one command. + * e.g. Hue and Saturation update for Color Item + * + * @author Eugen Freiter - Initial contribution + * + */ +@NonNullByDefault +public class HomekitOHItemProxy { + private final Logger logger = LoggerFactory.getLogger(HomekitOHItemProxy.class); + private static final int DEFAULT_DELAY = 50; // in ms + private final Item item; + private final Map commandCache = new ConcurrentHashMap<>(); + private final ScheduledExecutorService scheduler = ThreadPoolManager + .getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON); + private @Nullable ScheduledFuture future; + private HomekitDimmerMode dimmerMode = DIMMER_MODE_NORMAL; + // delay, how long wait for further commands. in ms. + private int delay = DEFAULT_DELAY; + + public HomekitOHItemProxy(final Item item) { + this.item = item; + } + + public Item getItem() { + return item; + } + + public void setDimmerMode(HomekitDimmerMode mode) { + dimmerMode = mode; + } + + public void setDelay(int delay) { + this.delay = delay; + } + + @SuppressWarnings("null") + private void sendCommand() { + if (!(item instanceof DimmerItem)) { + // currently supports only DimmerItem and ColorItem (which extends DimmerItem) + logger.debug("unexpected item type {}. Only DimmerItem and ColorItem are supported.", item); + return; + } + final OnOffType on = (OnOffType) commandCache.remove(ON_COMMAND); + final PercentType brightness = (PercentType) commandCache.remove(BRIGHTNESS_COMMAND); + final DecimalType hue = (DecimalType) commandCache.remove(HUE_COMMAND); + final PercentType saturation = (PercentType) commandCache.remove(SATURATION_COMMAND); + if (on != null) { + // always sends OFF. + // sends ON only if + // - DIMMER_MODE_NONE is enabled OR + // - DIMMER_MODE_FILTER_BRIGHTNESS_100 is enabled OR + // - DIMMER_MODE_FILTER_ON_EXCEPT100 is not enabled and brightness is null or below 100 + if ((on == OnOffType.OFF) || (dimmerMode == DIMMER_MODE_NORMAL) + || (dimmerMode == DIMMER_MODE_FILTER_BRIGHTNESS_100) + || ((dimmerMode == DIMMER_MODE_FILTER_ON_EXCEPT_BRIGHTNESS_100) + && ((brightness == null) || (brightness.intValue() == 100)))) { + logger.trace("send OnOff command for item {} with value {}", item, on); + ((DimmerItem) item).send(on); + } + } + + // if hue or saturation present, send an HSBType state update. no filter applied for HUE & Saturation + if ((hue != null) || (saturation != null)) { + if (item instanceof ColorItem) { + // logic for ColorItem = combine hue, saturation and brightness update to one command + final HSBType currentState = item.getState() instanceof UnDefType ? HSBType.BLACK + : (HSBType) item.getState(); + ((ColorItem) item).send(new HSBType(hue != null ? hue : currentState.getHue(), + saturation != null ? saturation : currentState.getSaturation(), + brightness != null ? brightness : currentState.getBrightness())); + logger.trace("send HSB command for item {} with following values hue={} saturation={} brightness={}", + item, hue, saturation, brightness); + } + } else if ((brightness != null) && (item instanceof DimmerItem)) { + // sends brightness: + // - DIMMER_MODE_NONE + // - DIMMER_MODE_FILTER_ON + // - other modes (DIMMER_MODE_FILTER_BRIGHTNESS_100 or DIMMER_MODE_FILTER_ON_EXCEPT_BRIGHTNESS_100) and + // <100%. + if ((dimmerMode == DIMMER_MODE_NORMAL) || (dimmerMode == DIMMER_MODE_FILTER_ON) + || (brightness.intValue() < 100)) { + logger.trace("send Brightness command for item {} with value {}", item, brightness); + ((DimmerItem) item).send(brightness); + } + } + commandCache.clear(); + } + + public synchronized void sendCommandProxy(final HomekitCommandType commandType, final State state) { + commandCache.put(commandType, state); + logger.trace("add command to command cache: item {}, command type {}, command state {}. cache state after: {}", + this, commandType, state, commandCache); + // if cache has already HUE+SATURATION or BRIGHTNESS+ON then we don't expect any further relevant command + if (((item instanceof ColorItem) && commandCache.containsKey(HUE_COMMAND) + && commandCache.containsKey(SATURATION_COMMAND)) + || (commandCache.containsKey(BRIGHTNESS_COMMAND) && commandCache.containsKey(ON_COMMAND))) { + if (future != null) { + future.cancel(false); + } + sendCommand(); + return; + } + // if timer is not already set, create a new one to ensure that the command command is send even if no follow up + // commands are received. + if (future == null || future.isDone()) { + future = scheduler.schedule(() -> { + logger.trace("timer of {} ms is over, sending the command", delay); + sendCommand(); + }, delay, TimeUnit.MILLISECONDS); + } + } +} diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitTaggedItem.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitTaggedItem.java index 6cc57f78e38fa..52bd734dc39be 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitTaggedItem.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitTaggedItem.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.items.GroupItem; import org.eclipse.smarthome.core.items.Item; +import org.eclipse.smarthome.core.types.State; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,41 +38,52 @@ public class HomekitTaggedItem { public final static String MIN_VALUE = "minValue"; public final static String MAX_VALUE = "maxValue"; public final static String STEP = "step"; + public final static String DIMMER_MODE = "dimmerMode"; + public final static String DELAY = "commandDelay"; private static final Map CREATED_ACCESSORY_IDS = new ConcurrentHashMap<>(); - /** - * The type of HomekitDevice we've decided this was. If the item is question is the member of a group which is a - * HomekitDevice, then this is null. - */ - private final Item item; + + // proxy item used to group commands for complex item types like Color or Dimmer + private final HomekitOHItemProxy proxyItem; + + // type of HomeKit accessory/service, e.g. TemperatureSensor private final HomekitAccessoryType homekitAccessoryType; + + // type of HomeKit characteristic, e.g. CurrentTemperature private @Nullable HomekitCharacteristicType homekitCharacteristicType; + + // configuration attached to the openHAB Item, e.g. minValue, maxValue, valveType private @Nullable Map configuration; + + // link to the groupItem if item is part of a group private @Nullable GroupItem parentGroupItem; + + // HomeKit accessory id (aid) which is generated from item name private final int id; - public HomekitTaggedItem(Item item, HomekitAccessoryType homekitAccessoryType, + public HomekitTaggedItem(HomekitOHItemProxy item, HomekitAccessoryType homekitAccessoryType, @Nullable Map configuration) { - this.item = item; + this.proxyItem = item; this.parentGroupItem = null; this.configuration = configuration; this.homekitAccessoryType = homekitAccessoryType; this.homekitCharacteristicType = HomekitCharacteristicType.EMPTY; if (homekitAccessoryType != DUMMY) { - this.id = calculateId(item); + this.id = calculateId(item.getItem()); } else { this.id = 0; } + parseConfiguration(); } - public HomekitTaggedItem(Item item, HomekitAccessoryType homekitAccessoryType, + public HomekitTaggedItem(HomekitOHItemProxy item, HomekitAccessoryType homekitAccessoryType, @Nullable HomekitCharacteristicType homekitCharacteristicType, @Nullable Map configuration) { this(item, homekitAccessoryType, configuration); this.homekitCharacteristicType = homekitCharacteristicType; } - public HomekitTaggedItem(Item item, HomekitAccessoryType homekitAccessoryType, + public HomekitTaggedItem(HomekitOHItemProxy item, HomekitAccessoryType homekitAccessoryType, @Nullable HomekitCharacteristicType homekitCharacteristicType, @Nullable GroupItem parentGroup, @Nullable Map configuration) { this(item, homekitAccessoryType, homekitCharacteristicType, configuration); @@ -79,7 +91,7 @@ public HomekitTaggedItem(Item item, HomekitAccessoryType homekitAccessoryType, } public boolean isGroup() { - return (isAccessory() && (this.item instanceof GroupItem)); + return (isAccessory() && (proxyItem.getItem() instanceof GroupItem)); } public HomekitAccessoryType getAccessoryType() { @@ -112,8 +124,34 @@ public boolean isCharacteristic() { return homekitCharacteristicType != null && homekitCharacteristicType != HomekitCharacteristicType.EMPTY; } + /** + * return openHAB item responsible for the HomeKit item + * + * @return openHAB item + */ public Item getItem() { - return item; + return proxyItem.getItem(); + } + + /** + * return proxy item which is used to group commands. + * + * @return proxy item + */ + public HomekitOHItemProxy getProxyItem() { + return proxyItem; + } + + /** + * send openHAB item command via proxy item, which allows to group commands. + * e.g. sendCommandProxy(hue), sendCommandProxy(brightness) would lead to one openHAB command that updates hue and + * brightness at once + * + * @param commandType type of the command, e.g. HomekitCommandType.HUE_COMMAND + * @param command command/state + */ + public void sendCommandProxy(HomekitCommandType commandType, State command) { + proxyItem.sendCommandProxy(commandType, command); } public int getId() { @@ -121,7 +159,7 @@ public int getId() { } public String getName() { - return item.getName(); + return proxyItem.getItem().getName(); } /** @@ -141,6 +179,19 @@ public boolean isMemberOfAccessoryGroup() { return parentGroupItem != null; } + private void parseConfiguration() { + if (configuration != null) { + Object dimmerModeConfig = configuration.get(DIMMER_MODE); + if (dimmerModeConfig instanceof String) { + HomekitDimmerMode.valueOfTag((String) dimmerModeConfig).ifPresent(proxyItem::setDimmerMode); + } + Object delayConfig = configuration.get(DELAY); + if (delayConfig instanceof Number) { + proxyItem.setDelay(((Number) delayConfig).intValue()); + } + } + } + private int calculateId(Item item) { // magic number 629 is the legacy from apache HashCodeBuilder (17*37) int id = 629 + item.getName().hashCode(); @@ -164,7 +215,7 @@ private int calculateId(Item item) { } public String toString() { - return "Item:" + item + " HomeKit type:" + homekitAccessoryType + " HomeKit characteristic:" + return "Item:" + proxyItem + " HomeKit type:" + homekitAccessoryType + " HomeKit characteristic:" + homekitCharacteristicType; } } diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/AbstractHomekitAccessoryImpl.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/AbstractHomekitAccessoryImpl.java index 0a66dcd95c202..70458100a0144 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/AbstractHomekitAccessoryImpl.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/AbstractHomekitAccessoryImpl.java @@ -12,14 +12,14 @@ */ package org.openhab.io.homekit.internal.accessories; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.math.BigDecimal; -import java.math.RoundingMode; import javax.measure.Quantity; import javax.measure.Unit; @@ -28,9 +28,9 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.items.GenericItem; import org.eclipse.smarthome.core.items.Item; -import org.eclipse.smarthome.core.types.State; -import org.eclipse.smarthome.core.library.unit.SIUnits; import org.eclipse.smarthome.core.library.unit.ImperialUnits; +import org.eclipse.smarthome.core.library.unit.SIUnits; +import org.eclipse.smarthome.core.types.State; import org.openhab.io.homekit.internal.HomekitAccessoryUpdater; import org.openhab.io.homekit.internal.HomekitCharacteristicType; import org.openhab.io.homekit.internal.HomekitSettings; @@ -218,15 +218,18 @@ protected void addCharacteristic(HomekitTaggedItem characteristic) { } private > double convertAndRound(double value, Unit from, Unit to) { - double rawValue = from == to ? value : from.getConverterTo(to).convert(value); - return new BigDecimal(rawValue).setScale(1, RoundingMode.HALF_UP).doubleValue(); + double rawValue = from == to ? value : from.getConverterTo(to).convert(value); + return new BigDecimal(rawValue).setScale(1, RoundingMode.HALF_UP).doubleValue(); } - protected double convertToCelsius(double degrees){ - return convertAndRound(degrees, getSettings().useFahrenheitTemperature ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS, SIUnits.CELSIUS); + protected double convertToCelsius(double degrees) { + return convertAndRound(degrees, + getSettings().useFahrenheitTemperature ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS, SIUnits.CELSIUS); } - protected double convertFromCelsius(double degrees){ - return convertAndRound(degrees, getSettings().useFahrenheitTemperature ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT, ImperialUnits.FAHRENHEIT); + protected double convertFromCelsius(double degrees) { + return convertAndRound(degrees, + getSettings().useFahrenheitTemperature ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT, + ImperialUnits.FAHRENHEIT); } } diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitAccessoryFactory.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitAccessoryFactory.java index 929b9ffa81ed7..de7effdb6ccaa 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitAccessoryFactory.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitAccessoryFactory.java @@ -43,6 +43,7 @@ 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.HomekitOHItemProxy; import org.openhab.io.homekit.internal.HomekitSettings; import org.openhab.io.homekit.internal.HomekitTaggedItem; import org.slf4j.Logger; @@ -185,7 +186,7 @@ public static HomekitAccessory create(HomekitTaggedItem taggedItem, MetadataRegi .getConstructor(HomekitTaggedItem.class, List.class, HomekitAccessoryUpdater.class, HomekitSettings.class) .newInstance(taggedItem, requiredCharacteristics, updater, settings); - addOptionalCharacteristics(accessoryImpl, metadataRegistry); + addOptionalCharacteristics(taggedItem, accessoryImpl, metadataRegistry); return accessoryImpl; } else { logger.warn("Unsupported HomeKit type: {}", accessoryType); @@ -291,8 +292,12 @@ private static List getMandatoryCharacteristics(HomekitTagged } /** - * add mandatory HomeKit items for a given main item to a list of characteristics - * + * add mandatory HomeKit items for a given main item to a list of characteristics. + * Main item is use only to determine, which characteristics are mandatory. + * The characteristics are added to item. + * e.g. mainItem could be a group tagged as "thermostat" and item could be item linked to the group and marked as + * TargetTemperature + * * @param mainItem main item * @param characteristics list of characteristics * @param item current item @@ -301,41 +306,66 @@ private static List getMandatoryCharacteristics(HomekitTagged @SuppressWarnings("null") private static void addMandatoryCharacteristics(HomekitTaggedItem mainItem, List characteristics, Item item, MetadataRegistry metadataRegistry) { + // get list of mandatory characteristics HomekitCharacteristicType[] mandatoryCharacteristics = MANDATORY_CHARACTERISTICS .get(mainItem.getAccessoryType()); + if ((mandatoryCharacteristics == null) || (mandatoryCharacteristics.length == 0)) { + // no mandatory characteristics linked to accessory type of mainItem. we are done + return; + } + // check whether we adding characteristic to the main item, and if yes, use existing item proxy. + // if we adding no to the main item (typical for groups), create new proxy item. + final HomekitOHItemProxy itemProxy = mainItem.getItem().equals(item) ? mainItem.getProxyItem() + : new HomekitOHItemProxy(item); + // an item can have several tags, e.g. "ActiveStatus, InUse". we iterate here over all his tags for (Entry accessory : getAccessoryTypes(item, metadataRegistry)) { - if (isRootAccessory(accessory) && (mandatoryCharacteristics != null)) { + // if the item has only accessory tag, e.g. TemperatureSensor, + // the we will link all mandatory characteristic to this item, + // e.g. we will link CurrentTemperature in case of TemperatureSensor. + if (isRootAccessory(accessory)) { Arrays.stream(mandatoryCharacteristics) - .forEach(c -> characteristics.add(new HomekitTaggedItem(item, accessory.getKey(), c, + .forEach(c -> characteristics.add(new HomekitTaggedItem(itemProxy, accessory.getKey(), c, mainItem.isGroup() ? (GroupItem) mainItem.getItem() : null, HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry)))); } else { - if (isMandatoryCharacteristic(mainItem.getAccessoryType(), legacyCheck(accessory.getValue()))) - characteristics - .add(new HomekitTaggedItem(item, accessory.getKey(), legacyCheck(accessory.getValue()), - mainItem.isGroup() ? (GroupItem) mainItem.getItem() : null, - HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry))); + // item has characteristic tag on it, so, adding it as that characteristic. + + // check whether it is a legacy characteristic, i.e. old tag was used, and replaced by a new one + final HomekitCharacteristicType characteristic = legacyCheck(accessory.getValue()); + + // check whether it is a mandatory characteristic. optional will be added later by another method. + if (isMandatoryCharacteristic(mainItem.getAccessoryType(), characteristic)) { + characteristics.add(new HomekitTaggedItem(itemProxy, accessory.getKey(), characteristic, + mainItem.isGroup() ? (GroupItem) mainItem.getItem() : null, + HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry))); + } } } } /** * add optional characteristic for given accessory. - * + * + * @param taggedItem main item * @param accessory accessory * @param metadataRegistry metadata registry */ - private static void addOptionalCharacteristics(AbstractHomekitAccessoryImpl accessory, - MetadataRegistry metadataRegistry) { + private static void addOptionalCharacteristics(final HomekitTaggedItem taggedItem, + AbstractHomekitAccessoryImpl accessory, MetadataRegistry metadataRegistry) { Map characteristics = getOptionalCharacteristics( accessory.getRootAccessory(), metadataRegistry); Service service = accessory.getPrimaryService(); - + HashMap proxyItems = new HashMap<>(); + proxyItems.put(taggedItem.getItem().getUID(), taggedItem.getProxyItem()); + // an accessory can have multiple optional characteristics. iterate over them. characteristics.forEach((type, item) -> { try { logger.trace("adding optional characteristic: {} for item {}", type, item.getName()); - final HomekitTaggedItem optionalItem = new HomekitTaggedItem(item, + // check whether a proxyItem already exists, if not create one. + final HomekitOHItemProxy proxyItem = proxyItems.computeIfAbsent(item.getUID(), + k -> new HomekitOHItemProxy(item)); + final HomekitTaggedItem optionalItem = new HomekitTaggedItem(proxyItem, accessory.getRootAccessory().getAccessoryType(), type, accessory.getRootAccessory().getRootDeviceGroupItem(), getItemConfiguration(item, metadataRegistry)); diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitCharacteristicFactory.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitCharacteristicFactory.java index 0334aa898d35b..9ed2fdca54c7c 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitCharacteristicFactory.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitCharacteristicFactory.java @@ -39,6 +39,7 @@ import org.eclipse.smarthome.core.types.UnDefType; import org.openhab.io.homekit.internal.HomekitAccessoryUpdater; import org.openhab.io.homekit.internal.HomekitCharacteristicType; +import org.openhab.io.homekit.internal.HomekitCommandType; import org.openhab.io.homekit.internal.HomekitException; import org.openhab.io.homekit.internal.HomekitTaggedItem; import org.slf4j.Logger; @@ -166,211 +167,212 @@ private static CompletableFuture getEnumFromIt return CompletableFuture.completedFuture(defaultEnum); } - private static void setValueFromEnum(final HomekitTaggedItem item, CharacteristicEnum value, + private static void setValueFromEnum(final HomekitTaggedItem taggedItem, CharacteristicEnum value, CharacteristicEnum offEnum, CharacteristicEnum onEnum) { - if (item.getItem() instanceof SwitchItem) { + if (taggedItem.getItem() instanceof SwitchItem) { if (value.equals(offEnum)) { - ((SwitchItem) item.getItem()).send(OnOffType.OFF); + ((SwitchItem) taggedItem.getItem()).send(OnOffType.OFF); } else if (value.equals(onEnum)) { - ((SwitchItem) item.getItem()).send(OnOffType.ON); + ((SwitchItem) taggedItem.getItem()).send(OnOffType.ON); } else { logger.warn("Enum value {} is not supported. Only following values are supported: {},{}", value, offEnum, onEnum); } - } else if (item.getItem() instanceof NumberItem) { - ((NumberItem) item.getItem()).send(new DecimalType(value.getCode())); + } else if (taggedItem.getItem() instanceof NumberItem) { + ((NumberItem) taggedItem.getItem()).send(new DecimalType(value.getCode())); } else { logger.warn("Item type {} is not supported. Only Switch and Number item types are supported.", - item.getItem().getType()); + taggedItem.getItem().getType()); } } @SuppressWarnings("null") - private static int getIntFromItem(final HomekitTaggedItem item) { + private static int getIntFromItem(final HomekitTaggedItem taggedItem) { int value = 0; - final State state = item.getItem().getState(); + final State state = taggedItem.getItem().getState(); if (state instanceof PercentType) { value = state.as(PercentType.class).intValue(); } else if (state instanceof DecimalType) { value = state.as(DecimalType.class).intValue(); } else if (state instanceof UnDefType) { - logger.debug("Item state {} is UNDEF {}.", state, item.getName()); + logger.debug("Item state {} is UNDEF {}.", state, taggedItem.getName()); } else { logger.warn( "Item state {} is not supported for {}. Only PercentType and DecimalType (0/100) are supported.", - state, item.getName()); + state, taggedItem.getName()); } return value; } - private static Supplier> getIntSupplier(final HomekitTaggedItem item) { - return () -> CompletableFuture.completedFuture(getIntFromItem(item)); + private static Supplier> getIntSupplier(final HomekitTaggedItem taggedItem) { + return () -> CompletableFuture.completedFuture(getIntFromItem(taggedItem)); } - private static ExceptionalConsumer setIntConsumer(final HomekitTaggedItem item) { + private static ExceptionalConsumer setIntConsumer(final HomekitTaggedItem taggedItem) { return (value) -> { - if (item.getItem() instanceof NumberItem) { - ((NumberItem) item.getItem()).send(new DecimalType(value)); + if (taggedItem.getItem() instanceof NumberItem) { + ((NumberItem) taggedItem.getItem()).send(new DecimalType(value)); } else { logger.warn("Item type {} is not supported for {}. Only Number type is supported.", - item.getItem().getType(), item.getName()); + taggedItem.getItem().getType(), taggedItem.getName()); } }; } - private static Supplier> getDoubleSupplier(final HomekitTaggedItem item) { + private static Supplier> getDoubleSupplier(final HomekitTaggedItem taggedItem) { return () -> { - final DecimalType value = item.getItem().getStateAs(DecimalType.class); + final DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class); return CompletableFuture.completedFuture(value != null ? value.doubleValue() : 0.0); }; } - protected static Consumer getSubscriber(final HomekitTaggedItem item, + protected static Consumer getSubscriber(final HomekitTaggedItem taggedItem, final HomekitCharacteristicType key, final HomekitAccessoryUpdater updater) { - return (callback) -> updater.subscribe((GenericItem) item.getItem(), key.getTag(), callback); + return (callback) -> updater.subscribe((GenericItem) taggedItem.getItem(), key.getTag(), callback); } - protected static Runnable getUnsubscriber(final HomekitTaggedItem item, final HomekitCharacteristicType key, + protected static Runnable getUnsubscriber(final HomekitTaggedItem taggedItem, final HomekitCharacteristicType key, final HomekitAccessoryUpdater updater) { - return () -> updater.unsubscribe((GenericItem) item.getItem(), key.getTag()); + return () -> updater.unsubscribe((GenericItem) taggedItem.getItem(), key.getTag()); } // create method for characteristic - private static StatusLowBatteryCharacteristic createStatusLowBatteryCharacteristic(final HomekitTaggedItem item, - final HomekitAccessoryUpdater updater) { + private static StatusLowBatteryCharacteristic createStatusLowBatteryCharacteristic( + final HomekitTaggedItem taggedItem, final HomekitAccessoryUpdater updater) { return new StatusLowBatteryCharacteristic( - () -> getEnumFromItem(item, StatusLowBatteryEnum.NORMAL, StatusLowBatteryEnum.LOW, + () -> getEnumFromItem(taggedItem, StatusLowBatteryEnum.NORMAL, StatusLowBatteryEnum.LOW, StatusLowBatteryEnum.NORMAL), - getSubscriber(item, BATTERY_LOW_STATUS, updater), getUnsubscriber(item, BATTERY_LOW_STATUS, updater)); + getSubscriber(taggedItem, BATTERY_LOW_STATUS, updater), + getUnsubscriber(taggedItem, BATTERY_LOW_STATUS, updater)); } - private static StatusFaultCharacteristic createStatusFaultCharacteristic(final HomekitTaggedItem item, + private static StatusFaultCharacteristic createStatusFaultCharacteristic(final HomekitTaggedItem taggedItem, final HomekitAccessoryUpdater updater) { return new StatusFaultCharacteristic( - () -> getEnumFromItem(item, StatusFaultEnum.NO_FAULT, StatusFaultEnum.GENERAL_FAULT, + () -> getEnumFromItem(taggedItem, StatusFaultEnum.NO_FAULT, StatusFaultEnum.GENERAL_FAULT, StatusFaultEnum.NO_FAULT), - getSubscriber(item, FAULT_STATUS, updater), getUnsubscriber(item, FAULT_STATUS, updater)); + getSubscriber(taggedItem, FAULT_STATUS, updater), getUnsubscriber(taggedItem, FAULT_STATUS, updater)); } - private static StatusTamperedCharacteristic createStatusTamperedCharacteristic(final HomekitTaggedItem item, + private static StatusTamperedCharacteristic createStatusTamperedCharacteristic(final HomekitTaggedItem taggedItem, final HomekitAccessoryUpdater updater) { return new StatusTamperedCharacteristic( - () -> getEnumFromItem(item, StatusTamperedEnum.NOT_TAMPERED, StatusTamperedEnum.TAMPERED, + () -> getEnumFromItem(taggedItem, StatusTamperedEnum.NOT_TAMPERED, StatusTamperedEnum.TAMPERED, StatusTamperedEnum.NOT_TAMPERED), - getSubscriber(item, TAMPERED_STATUS, updater), getUnsubscriber(item, TAMPERED_STATUS, updater)); + getSubscriber(taggedItem, TAMPERED_STATUS, updater), + getUnsubscriber(taggedItem, TAMPERED_STATUS, updater)); } private static ObstructionDetectedCharacteristic createObstructionDetectedCharacteristic( - final HomekitTaggedItem item, HomekitAccessoryUpdater updater) { + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new ObstructionDetectedCharacteristic( - () -> CompletableFuture.completedFuture( - item.getItem().getState() == OnOffType.ON || item.getItem().getState() == OpenClosedType.OPEN), - getSubscriber(item, OBSTRUCTION_STATUS, updater), getUnsubscriber(item, OBSTRUCTION_STATUS, updater)); + () -> CompletableFuture.completedFuture(taggedItem.getItem().getState() == OnOffType.ON + || taggedItem.getItem().getState() == OpenClosedType.OPEN), + getSubscriber(taggedItem, OBSTRUCTION_STATUS, updater), + getUnsubscriber(taggedItem, OBSTRUCTION_STATUS, updater)); } - private static StatusActiveCharacteristic createStatusActiveCharacteristic(final HomekitTaggedItem item, + private static StatusActiveCharacteristic createStatusActiveCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new StatusActiveCharacteristic( - () -> CompletableFuture.completedFuture( - item.getItem().getState() == OnOffType.ON || item.getItem().getState() == OpenClosedType.OPEN), - getSubscriber(item, ACTIVE_STATUS, updater), getUnsubscriber(item, ACTIVE_STATUS, updater)); + () -> CompletableFuture.completedFuture(taggedItem.getItem().getState() == OnOffType.ON + || taggedItem.getItem().getState() == OpenClosedType.OPEN), + getSubscriber(taggedItem, ACTIVE_STATUS, updater), getUnsubscriber(taggedItem, ACTIVE_STATUS, updater)); } - private static NameCharacteristic createNameCharacteristic(final HomekitTaggedItem item, + private static NameCharacteristic createNameCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new NameCharacteristic(() -> { - final State state = item.getItem().getState(); + final State state = taggedItem.getItem().getState(); return CompletableFuture.completedFuture(state instanceof UnDefType ? "" : state.toString()); }); } - private static HoldPositionCharacteristic createHoldPositionCharacteristic(final HomekitTaggedItem item, + private static HoldPositionCharacteristic createHoldPositionCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new HoldPositionCharacteristic(OnOffType::from); } private static CarbonMonoxideLevelCharacteristic createCarbonMonoxideLevelCharacteristic( - final HomekitTaggedItem item, HomekitAccessoryUpdater updater) { - return new CarbonMonoxideLevelCharacteristic(getDoubleSupplier(item), - getSubscriber(item, CARBON_DIOXIDE_LEVEL, updater), - getUnsubscriber(item, CARBON_DIOXIDE_LEVEL, updater)); + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new CarbonMonoxideLevelCharacteristic(getDoubleSupplier(taggedItem), + getSubscriber(taggedItem, CARBON_DIOXIDE_LEVEL, updater), + getUnsubscriber(taggedItem, CARBON_DIOXIDE_LEVEL, updater)); } private static CarbonMonoxidePeakLevelCharacteristic createCarbonMonoxidePeakLevelCharacteristic( - final HomekitTaggedItem item, HomekitAccessoryUpdater updater) { - return new CarbonMonoxidePeakLevelCharacteristic(getDoubleSupplier(item), - getSubscriber(item, CARBON_DIOXIDE_PEAK_LEVEL, updater), - getUnsubscriber(item, CARBON_DIOXIDE_PEAK_LEVEL, updater)); + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new CarbonMonoxidePeakLevelCharacteristic(getDoubleSupplier(taggedItem), + getSubscriber(taggedItem, CARBON_DIOXIDE_PEAK_LEVEL, updater), + getUnsubscriber(taggedItem, CARBON_DIOXIDE_PEAK_LEVEL, updater)); } - private static CarbonDioxideLevelCharacteristic createCarbonDioxideLevelCharacteristic(final HomekitTaggedItem item, - HomekitAccessoryUpdater updater) { - return new CarbonDioxideLevelCharacteristic(getDoubleSupplier(item), - getSubscriber(item, CARBON_MONOXIDE_LEVEL, updater), - getUnsubscriber(item, CARBON_MONOXIDE_LEVEL, updater)); + private static CarbonDioxideLevelCharacteristic createCarbonDioxideLevelCharacteristic( + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new CarbonDioxideLevelCharacteristic(getDoubleSupplier(taggedItem), + getSubscriber(taggedItem, CARBON_MONOXIDE_LEVEL, updater), + getUnsubscriber(taggedItem, CARBON_MONOXIDE_LEVEL, updater)); } private static CarbonDioxidePeakLevelCharacteristic createCarbonDioxidePeakLevelCharacteristic( - final HomekitTaggedItem item, HomekitAccessoryUpdater updater) { - return new CarbonDioxidePeakLevelCharacteristic(getDoubleSupplier(item), - getSubscriber(item, CARBON_MONOXIDE_PEAK_LEVEL, updater), - getUnsubscriber(item, CARBON_MONOXIDE_PEAK_LEVEL, updater)); + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new CarbonDioxidePeakLevelCharacteristic(getDoubleSupplier(taggedItem), + getSubscriber(taggedItem, CARBON_MONOXIDE_PEAK_LEVEL, updater), + getUnsubscriber(taggedItem, CARBON_MONOXIDE_PEAK_LEVEL, updater)); } private static CurrentHorizontalTiltAngleCharacteristic createCurrentHorizontalTiltAngleCharacteristic( - final HomekitTaggedItem item, HomekitAccessoryUpdater updater) { - return new CurrentHorizontalTiltAngleCharacteristic(getIntSupplier(item), - getSubscriber(item, CURRENT_HORIZONTAL_TILT_ANGLE, updater), - getUnsubscriber(item, CURRENT_HORIZONTAL_TILT_ANGLE, updater)); + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new CurrentHorizontalTiltAngleCharacteristic(getIntSupplier(taggedItem), + getSubscriber(taggedItem, CURRENT_HORIZONTAL_TILT_ANGLE, updater), + getUnsubscriber(taggedItem, CURRENT_HORIZONTAL_TILT_ANGLE, updater)); } private static CurrentVerticalTiltAngleCharacteristic createCurrentVerticalTiltAngleCharacteristic( - final HomekitTaggedItem item, HomekitAccessoryUpdater updater) { - return new CurrentVerticalTiltAngleCharacteristic(getIntSupplier(item), - getSubscriber(item, CURRENT_VERTICAL_TILT_ANGLE, updater), - getUnsubscriber(item, CURRENT_VERTICAL_TILT_ANGLE, updater)); + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new CurrentVerticalTiltAngleCharacteristic(getIntSupplier(taggedItem), + getSubscriber(taggedItem, CURRENT_VERTICAL_TILT_ANGLE, updater), + getUnsubscriber(taggedItem, CURRENT_VERTICAL_TILT_ANGLE, updater)); } private static TargetHorizontalTiltAngleCharacteristic createTargetHorizontalTiltAngleCharacteristic( - final HomekitTaggedItem item, HomekitAccessoryUpdater updater) { - return new TargetHorizontalTiltAngleCharacteristic(getIntSupplier(item), setIntConsumer(item), - getSubscriber(item, TARGET_HORIZONTAL_TILT_ANGLE, updater), - getUnsubscriber(item, TARGET_HORIZONTAL_TILT_ANGLE, updater)); + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new TargetHorizontalTiltAngleCharacteristic(getIntSupplier(taggedItem), setIntConsumer(taggedItem), + getSubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater), + getUnsubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater)); } private static TargetVerticalTiltAngleCharacteristic createTargetVerticalTiltAngleCharacteristic( - final HomekitTaggedItem item, HomekitAccessoryUpdater updater) { - return new TargetVerticalTiltAngleCharacteristic(getIntSupplier(item), setIntConsumer(item), - getSubscriber(item, TARGET_HORIZONTAL_TILT_ANGLE, updater), - getUnsubscriber(item, TARGET_HORIZONTAL_TILT_ANGLE, updater)); + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new TargetVerticalTiltAngleCharacteristic(getIntSupplier(taggedItem), setIntConsumer(taggedItem), + getSubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater), + getUnsubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater)); } - private static HueCharacteristic createHueCharacteristic(final HomekitTaggedItem item, + private static HueCharacteristic createHueCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new HueCharacteristic(() -> { Double value = 0.0; - State state = item.getItem().getState(); + State state = taggedItem.getItem().getState(); if (state instanceof HSBType) { value = ((HSBType) state).getHue().doubleValue(); } return CompletableFuture.completedFuture(value); }, (hue) -> { - State state = item.getItem().getState(); - if (item.getItem() instanceof ColorItem) { - ((ColorItem) item.getItem()).send(new HSBType(new DecimalType(hue), ((HSBType) state).getSaturation(), - ((HSBType) state).getBrightness())); + if (taggedItem.getItem() instanceof ColorItem) { + taggedItem.sendCommandProxy(HomekitCommandType.HUE_COMMAND, new DecimalType(hue)); } else { logger.warn("Item type {} is not supported for {}. Only Color type is supported.", - item.getItem().getType(), item.getName()); + taggedItem.getItem().getType(), taggedItem.getName()); } - }, getSubscriber(item, HUE, updater), getUnsubscriber(item, HUE, updater)); + }, getSubscriber(taggedItem, HUE, updater), getUnsubscriber(taggedItem, HUE, updater)); } - private static BrightnessCharacteristic createBrightnessCharacteristic(final HomekitTaggedItem item, + private static BrightnessCharacteristic createBrightnessCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new BrightnessCharacteristic(() -> { int value = 0; - final State state = item.getItem().getState(); + final State state = taggedItem.getItem().getState(); if (state instanceof HSBType) { value = ((HSBType) state).getBrightness().intValue(); } else if (state instanceof PercentType) { @@ -378,25 +380,21 @@ private static BrightnessCharacteristic createBrightnessCharacteristic(final Hom } return CompletableFuture.completedFuture(value); }, (brightness) -> { - final Item oItem = item.getItem(); - final State state = oItem.getState(); - if (oItem instanceof ColorItem) { - ((ColorItem) oItem).send(new HSBType(((HSBType) state).getHue(), ((HSBType) state).getSaturation(), - new PercentType(brightness))); - } else if (oItem instanceof DimmerItem) { - ((DimmerItem) oItem).send(new PercentType(brightness)); + final Item item = taggedItem.getItem(); + if (item instanceof DimmerItem) { + taggedItem.sendCommandProxy(HomekitCommandType.BRIGHTNESS_COMMAND, new PercentType(brightness)); } else { logger.warn("Item type {} is not supported for {}. Only ColorItem and DimmerItem are supported.", - oItem.getType(), item.getName()); + item.getType(), taggedItem.getName()); } - }, getSubscriber(item, BRIGHTNESS, updater), getUnsubscriber(item, BRIGHTNESS, updater)); + }, getSubscriber(taggedItem, BRIGHTNESS, updater), getUnsubscriber(taggedItem, BRIGHTNESS, updater)); } - private static SaturationCharacteristic createSaturationCharacteristic(final HomekitTaggedItem item, + private static SaturationCharacteristic createSaturationCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new SaturationCharacteristic(() -> { Double value = 0.0; - State state = item.getItem().getState(); + State state = taggedItem.getItem().getState(); if (state instanceof HSBType) { value = ((HSBType) state).getSaturation().doubleValue(); } else if (state instanceof PercentType) { @@ -404,40 +402,41 @@ private static SaturationCharacteristic createSaturationCharacteristic(final Hom } return CompletableFuture.completedFuture(value); }, (saturation) -> { - final State state = item.getItem().getState(); - if (item.getItem() instanceof ColorItem) { - ((ColorItem) item.getItem()).send(new HSBType(((HSBType) state).getHue(), - new PercentType(saturation.intValue()), ((HSBType) state).getBrightness())); + if (taggedItem.getItem() instanceof ColorItem) { + taggedItem.sendCommandProxy(HomekitCommandType.SATURATION_COMMAND, + new PercentType(saturation.intValue())); } else { logger.warn("Item type {} is not supported for {}. Only Color type is supported.", - item.getItem().getType(), item.getName()); + taggedItem.getItem().getType(), taggedItem.getName()); } - }, getSubscriber(item, SATURATION, updater), getUnsubscriber(item, SATURATION, updater)); + }, getSubscriber(taggedItem, SATURATION, updater), getUnsubscriber(taggedItem, SATURATION, updater)); } - private static ColorTemperatureCharacteristic createColorTemperatureCharacteristic(final HomekitTaggedItem item, - HomekitAccessoryUpdater updater) { - return new ColorTemperatureCharacteristic(getIntSupplier(item), setIntConsumer(item), - getSubscriber(item, COLOR_TEMPERATURE, updater), getUnsubscriber(item, COLOR_TEMPERATURE, updater)); + private static ColorTemperatureCharacteristic createColorTemperatureCharacteristic( + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new ColorTemperatureCharacteristic(getIntSupplier(taggedItem), setIntConsumer(taggedItem), + getSubscriber(taggedItem, COLOR_TEMPERATURE, updater), + getUnsubscriber(taggedItem, COLOR_TEMPERATURE, updater)); } - private static CurrentFanStateCharacteristic createCurrentFanStateCharacteristic(final HomekitTaggedItem item, + private static CurrentFanStateCharacteristic createCurrentFanStateCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new CurrentFanStateCharacteristic(() -> { - final DecimalType value = item.getItem().getStateAs(DecimalType.class); + final DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class); CurrentFanStateEnum currentFanStateEnum = value != null ? CurrentFanStateEnum.fromCode(value.intValue()) : null; if (currentFanStateEnum == null) { currentFanStateEnum = CurrentFanStateEnum.INACTIVE; } return CompletableFuture.completedFuture(currentFanStateEnum); - }, getSubscriber(item, CURRENT_FAN_STATE, updater), getUnsubscriber(item, CURRENT_FAN_STATE, updater)); + }, getSubscriber(taggedItem, CURRENT_FAN_STATE, updater), + getUnsubscriber(taggedItem, CURRENT_FAN_STATE, updater)); } - private static TargetFanStateCharacteristic createTargetFanStateCharacteristic(final HomekitTaggedItem item, + private static TargetFanStateCharacteristic createTargetFanStateCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new TargetFanStateCharacteristic(() -> { - final DecimalType value = item.getItem().getStateAs(DecimalType.class); + final DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class); TargetFanStateEnum targetFanStateEnum = value != null ? TargetFanStateEnum.fromCode(value.intValue()) : null; if (targetFanStateEnum == null) { @@ -445,42 +444,45 @@ private static TargetFanStateCharacteristic createTargetFanStateCharacteristic(f } return CompletableFuture.completedFuture(targetFanStateEnum); }, (targetState) -> { - if (item.getItem() instanceof NumberItem) { - ((NumberItem) item.getItem()).send(new DecimalType(targetState.getCode())); + if (taggedItem.getItem() instanceof NumberItem) { + ((NumberItem) taggedItem.getItem()).send(new DecimalType(targetState.getCode())); } else { logger.warn("Item type {} is not supported for {}. Only Number type is supported.", - item.getItem().getType(), item.getName()); + taggedItem.getItem().getType(), taggedItem.getName()); } - }, getSubscriber(item, TARGET_FAN_STATE, updater), getUnsubscriber(item, TARGET_FAN_STATE, updater)); + }, getSubscriber(taggedItem, TARGET_FAN_STATE, updater), + getUnsubscriber(taggedItem, TARGET_FAN_STATE, updater)); } - private static RotationDirectionCharacteristic createRotationDirectionCharacteristic(final HomekitTaggedItem item, - HomekitAccessoryUpdater updater) { + private static RotationDirectionCharacteristic createRotationDirectionCharacteristic( + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new RotationDirectionCharacteristic( - () -> getEnumFromItem(item, RotationDirectionEnum.CLOCKWISE, RotationDirectionEnum.COUNTER_CLOCKWISE, - RotationDirectionEnum.CLOCKWISE), - (value) -> setValueFromEnum(item, value, RotationDirectionEnum.CLOCKWISE, + () -> getEnumFromItem(taggedItem, RotationDirectionEnum.CLOCKWISE, + RotationDirectionEnum.COUNTER_CLOCKWISE, RotationDirectionEnum.CLOCKWISE), + (value) -> setValueFromEnum(taggedItem, value, RotationDirectionEnum.CLOCKWISE, RotationDirectionEnum.COUNTER_CLOCKWISE), - getSubscriber(item, ROTATION_DIRECTION, updater), getUnsubscriber(item, ROTATION_DIRECTION, updater)); + getSubscriber(taggedItem, ROTATION_DIRECTION, updater), + getUnsubscriber(taggedItem, ROTATION_DIRECTION, updater)); } - private static SwingModeCharacteristic createSwingModeCharacteristic(final HomekitTaggedItem item, + private static SwingModeCharacteristic createSwingModeCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new SwingModeCharacteristic( - () -> getEnumFromItem(item, SwingModeEnum.SWING_DISABLED, SwingModeEnum.SWING_ENABLED, + () -> getEnumFromItem(taggedItem, SwingModeEnum.SWING_DISABLED, SwingModeEnum.SWING_ENABLED, SwingModeEnum.SWING_DISABLED), - (value) -> setValueFromEnum(item, value, SwingModeEnum.SWING_DISABLED, SwingModeEnum.SWING_ENABLED), - getSubscriber(item, SWING_MODE, updater), getUnsubscriber(item, SWING_MODE, updater)); + (value) -> setValueFromEnum(taggedItem, value, SwingModeEnum.SWING_DISABLED, + SwingModeEnum.SWING_ENABLED), + getSubscriber(taggedItem, SWING_MODE, updater), getUnsubscriber(taggedItem, SWING_MODE, updater)); } private static LockPhysicalControlsCharacteristic createLockPhysicalControlsCharacteristic( - final HomekitTaggedItem item, HomekitAccessoryUpdater updater) { + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new LockPhysicalControlsCharacteristic( - () -> getEnumFromItem(item, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED, + () -> getEnumFromItem(taggedItem, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED, LockPhysicalControlsEnum.CONTROL_LOCK_ENABLED, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED), - (value) -> setValueFromEnum(item, value, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED, + (value) -> setValueFromEnum(taggedItem, value, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED, LockPhysicalControlsEnum.CONTROL_LOCK_ENABLED), - getSubscriber(item, LOCK_CONTROL, updater), getUnsubscriber(item, LOCK_CONTROL, updater)); + getSubscriber(taggedItem, LOCK_CONTROL, updater), getUnsubscriber(taggedItem, LOCK_CONTROL, updater)); } private static RotationSpeedCharacteristic createRotationSpeedCharacteristic(final HomekitTaggedItem item, @@ -489,33 +491,35 @@ private static RotationSpeedCharacteristic createRotationSpeedCharacteristic(fin getSubscriber(item, ROTATION_SPEED, updater), getUnsubscriber(item, ROTATION_SPEED, updater)); } - private static SetDurationCharacteristic createDurationCharacteristic(final HomekitTaggedItem item, + private static SetDurationCharacteristic createDurationCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { return new SetDurationCharacteristic(() -> { - int value = getIntFromItem(item); + int value = getIntFromItem(taggedItem); if (value == 0) { // check for default duration - final Object duration = item.getConfiguration().get(HomekitValveImpl.CONFIG_DEFAULT_DURATION); - if ((duration != null) && (duration instanceof BigDecimal)) { + final Object duration = taggedItem.getConfiguration().get(HomekitValveImpl.CONFIG_DEFAULT_DURATION); + if (duration instanceof BigDecimal) { value = ((BigDecimal) duration).intValue(); - if (item.getItem() instanceof NumberItem) { - ((NumberItem) item.getItem()).setState(new DecimalType(value)); + if (taggedItem.getItem() instanceof NumberItem) { + ((NumberItem) taggedItem.getItem()).setState(new DecimalType(value)); } } } return CompletableFuture.completedFuture(value); - }, setIntConsumer(item), getSubscriber(item, DURATION, updater), getUnsubscriber(item, DURATION, updater)); + }, setIntConsumer(taggedItem), getSubscriber(taggedItem, DURATION, updater), + getUnsubscriber(taggedItem, DURATION, updater)); } - private static RemainingDurationCharacteristic createRemainingDurationCharacteristic(final HomekitTaggedItem item, - HomekitAccessoryUpdater updater) { - return new RemainingDurationCharacteristic(getIntSupplier(item), - getSubscriber(item, REMAINING_DURATION, updater), getUnsubscriber(item, REMAINING_DURATION, updater)); + private static RemainingDurationCharacteristic createRemainingDurationCharacteristic( + final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { + return new RemainingDurationCharacteristic(getIntSupplier(taggedItem), + getSubscriber(taggedItem, REMAINING_DURATION, updater), + getUnsubscriber(taggedItem, REMAINING_DURATION, updater)); } - private static VolumeCharacteristic createVolumeCharacteristic(final HomekitTaggedItem item, + private static VolumeCharacteristic createVolumeCharacteristic(final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) { - return new VolumeCharacteristic(getIntSupplier(item), - (volume) -> ((NumberItem) item.getItem()).send(new DecimalType(volume)), - getSubscriber(item, DURATION, updater), getUnsubscriber(item, DURATION, updater)); + return new VolumeCharacteristic(getIntSupplier(taggedItem), + (volume) -> ((NumberItem) taggedItem.getItem()).send(new DecimalType(volume)), + getSubscriber(taggedItem, DURATION, updater), getUnsubscriber(taggedItem, DURATION, updater)); } } diff --git a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitLightbulbImpl.java b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitLightbulbImpl.java index af5bc1872dd64..5bd1782ee5ad5 100644 --- a/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitLightbulbImpl.java +++ b/bundles/org.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/accessories/HomekitLightbulbImpl.java @@ -17,10 +17,12 @@ import org.eclipse.smarthome.core.items.GenericItem; import org.eclipse.smarthome.core.items.GroupItem; +import org.eclipse.smarthome.core.library.items.DimmerItem; import org.eclipse.smarthome.core.library.items.SwitchItem; import org.eclipse.smarthome.core.library.types.OnOffType; import org.openhab.io.homekit.internal.HomekitAccessoryUpdater; import org.openhab.io.homekit.internal.HomekitCharacteristicType; +import org.openhab.io.homekit.internal.HomekitCommandType; import org.openhab.io.homekit.internal.HomekitSettings; import org.openhab.io.homekit.internal.HomekitTaggedItem; @@ -49,12 +51,17 @@ public CompletableFuture getLightbulbPowerState() { @Override public CompletableFuture setLightbulbPowerState(boolean value) { - GenericItem item = getItem(HomekitCharacteristicType.ON_STATE, GenericItem.class); - if (item instanceof SwitchItem) { - ((SwitchItem) item).send(OnOffType.from(value)); - } else if (item instanceof GroupItem) { - ((GroupItem) item).send(OnOffType.from(value)); - } + getCharacteristic(HomekitCharacteristicType.ON_STATE).ifPresent(tItem -> { + final OnOffType onOffState = OnOffType.from(value); + final GenericItem item = (GenericItem) tItem.getItem(); + if (item instanceof DimmerItem) { + tItem.sendCommandProxy(HomekitCommandType.ON_COMMAND, onOffState); + } else if (item instanceof SwitchItem) { + ((SwitchItem) item).send(onOffState); + } else if (item instanceof GroupItem) { + ((GroupItem) item).send(onOffState); + } + }); return CompletableFuture.completedFuture(null); }