Pass event source to profiles (#4990)

So that a user can define a profile that takes different
actions depending on what's generating the command.

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer
2025-09-14 16:39:21 +02:00
committed by GitHub
parent 31b43bc566
commit 5f72352a59
3 changed files with 23 additions and 11 deletions
@@ -346,10 +346,10 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
@FunctionalInterface
private interface ProfileAction<T extends Type> {
void applyProfile(Profile profile, Thing thing, T type);
void applyProfile(Profile profile, Thing thing, T type, @Nullable String source);
}
private void applyProfileForUpdate(Profile profile, Thing thing, State convertedState) {
private void applyProfileForUpdate(Profile profile, Thing thing, State convertedState, @Nullable String source) {
CacheKey key = new CacheKey("UPDATE", profile, thing);
Profile p = profileSafeCallCache.computeIfAbsent(key, (k) -> safeCaller.create(k.profile, Profile.class) //
.withAsync() //
@@ -363,7 +363,8 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
}
}
private void applyProfileForCommand(Profile profile, Thing thing, Command convertedCommand) {
private void applyProfileForCommand(Profile profile, Thing thing, Command convertedCommand,
@Nullable String source) {
if (profile instanceof StateProfile) {
CacheKey key = new CacheKey("COMMAND", profile, thing);
Profile p = profileSafeCallCache.computeIfAbsent(key,
@@ -373,7 +374,7 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
.withTimeout(THINGHANDLER_EVENT_TIMEOUT) //
.build());
if (p instanceof StateProfile profileP) {
profileP.onCommandFromItem(convertedCommand);
profileP.onCommandFromItem(convertedCommand, source);
} else {
throw new IllegalStateException("ExpiringCache didn't provide a StateProfile instance!");
}
@@ -404,7 +405,7 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
@Nullable
T uomType = fixUoM(type, channel, item);
Profile profile = getProfile(link, item, thing);
action.applyProfile(profile, thing, uomType != null ? uomType : type);
action.applyProfile(profile, thing, uomType != null ? uomType : type, source);
}
} else {
logger.debug("Received event '{}' for non-existing channel '{}', not forwarding it to the handler",
@@ -13,6 +13,7 @@
package org.openhab.core.thing.profiles;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
@@ -31,6 +32,16 @@ public interface StateProfile extends Profile {
*/
void onCommandFromItem(Command command);
/**
* Will be called if a command should be forwarded to the binding.
*
* @param command
* @param source the source of the command event
*/
default void onCommandFromItem(Command command, @Nullable String source) {
onCommandFromItem(command);
}
/**
* If a binding issued a command to a channel, this method will be called for each linked item.
*
@@ -297,9 +297,9 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
@Test
public void testItemCommandEventSingleLink() {
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_2, OnOffType.ON));
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_2, OnOffType.ON, "mysource"));
waitForAssert(() -> {
verify(stateProfileMock).onCommandFromItem(eq(OnOffType.ON));
verify(stateProfileMock).onCommandFromItem(eq(OnOffType.ON), eq("mysource"));
});
verifyNoMoreInteractions(stateProfileMock);
verifyNoMoreInteractions(triggerProfileMock);
@@ -311,7 +311,7 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
// Take unit from accepted item type (see channel built from STATE_CHANNEL_UID_3)
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_5, DecimalType.valueOf("20")));
waitForAssert(() -> {
verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °C")));
verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °C")), isNull());
});
verifyNoMoreInteractions(stateProfileMock);
verifyNoMoreInteractions(triggerProfileMock);
@@ -325,7 +325,7 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_5, DecimalType.valueOf("20")));
waitForAssert(() -> {
verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °F")));
verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °F")), isNull());
});
verifyNoMoreInteractions(stateProfileMock);
verifyNoMoreInteractions(triggerProfileMock);
@@ -335,7 +335,7 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
public void testItemCommandEventMultiLink() {
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_1, OnOffType.ON));
waitForAssert(() -> {
verify(stateProfileMock, times(2)).onCommandFromItem(eq(OnOffType.ON));
verify(stateProfileMock, times(2)).onCommandFromItem(eq(OnOffType.ON), isNull());
});
verifyNoMoreInteractions(stateProfileMock);
verifyNoMoreInteractions(triggerProfileMock);
@@ -347,7 +347,7 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
manager.receive(
ItemEventFactory.createCommandEvent(ITEM_NAME_1, OnOffType.ON, STATE_CHANNEL_UID_2.getAsString()));
waitForAssert(() -> {
verify(stateProfileMock).onCommandFromItem(eq(OnOffType.ON));
verify(stateProfileMock).onCommandFromItem(eq(OnOffType.ON), eq(STATE_CHANNEL_UID_2.getAsString()));
});
verifyNoMoreInteractions(stateProfileMock);
verifyNoMoreInteractions(triggerProfileMock);