-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[homekit] bugfix 7491 / add support for merging several updates to on…
…e command (#7825) * add support for merging several updates to one command * incorporate J-N-K feedback, adapt the logic for dimmer * add yfre to CODEOWNERS * incorporate feedback from @cpmeister * remove some blank lines Signed-off-by: Eugen Freiter <[email protected]>
- Loading branch information
Showing
11 changed files
with
563 additions
and
194 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
....openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitCommandType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
60 changes: 60 additions & 0 deletions
60
...g.openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitDimmerMode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String, HomekitDimmerMode> 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<HomekitDimmerMode> valueOfTag(String tag) { | ||
return Optional.ofNullable(TAG_MAP.get(tag.toUpperCase())); | ||
} | ||
} |
153 changes: 153 additions & 0 deletions
153
....openhab.io.homekit/src/main/java/org/openhab/io/homekit/internal/HomekitOHItemProxy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<HomekitCommandType, State> 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); | ||
} | ||
} | ||
} |
Oops, something went wrong.