mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
Script profile: Separate toHandlerScript for commands and states (#4058)
* Script profile: Separate toHandlerScript for commands and states This allows much more fine-grained control for the script profile. E.g. it is now possible to mimic the behaviour of the `system:follow` profile, but apply a script transformation to the forwarded state. Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
This commit is contained in:
+33
-11
@@ -43,6 +43,8 @@ public class ScriptProfile implements StateProfile {
|
||||
|
||||
public static final String CONFIG_TO_ITEM_SCRIPT = "toItemScript";
|
||||
public static final String CONFIG_TO_HANDLER_SCRIPT = "toHandlerScript";
|
||||
public static final String CONFIG_COMMAND_FROM_ITEM_SCRIPT = "commandFromItemScript";
|
||||
public static final String CONFIG_STATE_FROM_ITEM_SCRIPT = "stateFromItemScript";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ScriptProfile.class);
|
||||
|
||||
@@ -54,7 +56,8 @@ public class ScriptProfile implements StateProfile {
|
||||
private final List<Class<? extends Command>> handlerAcceptedCommandTypes;
|
||||
|
||||
private final String toItemScript;
|
||||
private final String toHandlerScript;
|
||||
private final String commandFromItemScript;
|
||||
private final String stateFromItemScript;
|
||||
private final ProfileTypeUID profileTypeUID;
|
||||
|
||||
private final boolean isConfigured;
|
||||
@@ -71,12 +74,22 @@ public class ScriptProfile implements StateProfile {
|
||||
|
||||
this.toItemScript = ConfigParser.valueAsOrElse(profileContext.getConfiguration().get(CONFIG_TO_ITEM_SCRIPT),
|
||||
String.class, "");
|
||||
this.toHandlerScript = ConfigParser
|
||||
String toHandlerScript = ConfigParser
|
||||
.valueAsOrElse(profileContext.getConfiguration().get(CONFIG_TO_HANDLER_SCRIPT), String.class, "");
|
||||
String localCommandFromItemScript = ConfigParser.valueAsOrElse(
|
||||
profileContext.getConfiguration().get(CONFIG_COMMAND_FROM_ITEM_SCRIPT), String.class, "");
|
||||
this.commandFromItemScript = localCommandFromItemScript.isBlank() ? toHandlerScript
|
||||
: localCommandFromItemScript;
|
||||
this.stateFromItemScript = ConfigParser
|
||||
.valueAsOrElse(profileContext.getConfiguration().get(CONFIG_STATE_FROM_ITEM_SCRIPT), String.class, "");
|
||||
|
||||
if (toItemScript.isBlank() && toHandlerScript.isBlank()) {
|
||||
if (!toHandlerScript.isBlank() && commandFromItemScript.isBlank()) {
|
||||
logger.warn("'toHandlerScript' has been deprecated! Please use 'commandFromItemScript' instead.");
|
||||
}
|
||||
|
||||
if (toItemScript.isBlank() && commandFromItemScript.isBlank() && stateFromItemScript.isBlank()) {
|
||||
logger.error(
|
||||
"Neither 'toItemScript' nor 'toHandlerScript' defined in link '{}'. Profile will discard all states and commands.",
|
||||
"Neither 'toItemScript', 'commandFromItemScript' nor 'stateFromItemScript' defined in link '{}'. Profile will discard all states and commands.",
|
||||
callback.getItemChannelLink());
|
||||
isConfigured = false;
|
||||
return;
|
||||
@@ -92,18 +105,27 @@ public class ScriptProfile implements StateProfile {
|
||||
|
||||
@Override
|
||||
public void onStateUpdateFromItem(State state) {
|
||||
if (isConfigured) {
|
||||
fromItem(stateFromItemScript, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandFromItem(Command command) {
|
||||
if (isConfigured) {
|
||||
String returnValue = executeScript(toHandlerScript, command);
|
||||
if (returnValue != null) {
|
||||
// try to parse the value
|
||||
Command newCommand = TypeParser.parseCommand(handlerAcceptedCommandTypes, returnValue);
|
||||
if (newCommand != null) {
|
||||
callback.handleCommand(newCommand);
|
||||
}
|
||||
fromItem(commandFromItemScript, command);
|
||||
}
|
||||
}
|
||||
|
||||
private void fromItem(String script, Type type) {
|
||||
String returnValue = executeScript(script, type);
|
||||
if (returnValue != null) {
|
||||
// try to parse the value
|
||||
Command newCommand = TypeParser.parseCommand(handlerAcceptedCommandTypes, returnValue);
|
||||
if (newCommand != null) {
|
||||
callback.handleCommand(newCommand);
|
||||
} else {
|
||||
logger.debug("The given type {} could not be transformed to a command", type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-2
@@ -11,12 +11,26 @@
|
||||
may return null to discard the updates/commands and not pass them through.</description>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
</parameter>
|
||||
<parameter name="toHandlerScript" type="text">
|
||||
<label>Item To Thing Transformation</label>
|
||||
<parameter name="commandFromItemScript" type="text">
|
||||
<label>Item Command To Thing Transformation</label>
|
||||
<description>The Script for transforming commands from the item to the Thing handler. The script may return null to
|
||||
discard the commands and not pass them through.</description>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
</parameter>
|
||||
<parameter name="stateFromItemScript" type="text">
|
||||
<label>Item State To Thing Transformation</label>
|
||||
<description>The Script for transforming states from the item to the Thing handler. The script may return null to
|
||||
discard the states and not pass them through.</description>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
</parameter>
|
||||
<parameter name="toHandlerScript" type="text">
|
||||
<label>Item To Thing Transformation (DEPRECATED)</label>
|
||||
<description>The Script for transforming commands from the item to the Thing handler. The script may return null to
|
||||
discard the commands and not pass them through. This is deprecated and has been replaced by
|
||||
'commandFromItemScript'.</description>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
|
||||
+8
-4
@@ -1,4 +1,8 @@
|
||||
profile.system.script.toItemScript.label = Thing To Item Transformation
|
||||
profile.system.script.toItemScript.description = The Script for transforming state updates and commands from the Thing handler to the item. The script may return null to discard the updates/commands and not pass them through.
|
||||
profile.system.script.toHandlerScript.label = Item To Thing Transformation
|
||||
profile.system.script.toHandlerScript.description = The Script for transforming commands from the item to the Thing handler. The script may return null to discard the commands and not pass them through.
|
||||
profile.config.transform.SCRIPT.commandFromItemScript.label = Item Command To Thing Transformation
|
||||
profile.config.transform.SCRIPT.commandFromItemScript.description = The Script for transforming commands from the item to the Thing handler. The script may return null to discard the commands and not pass them through.
|
||||
profile.config.transform.SCRIPT.stateFromItemScript.label = Item State To Thing Transformation
|
||||
profile.config.transform.SCRIPT.stateFromItemScript.description = The Script for transforming states from the item to the Thing handler. The script may return null to discard the states and not pass them through.
|
||||
profile.config.transform.SCRIPT.toHandlerScript.label = Item To Thing Transformation (DEPRECATED)
|
||||
profile.config.transform.SCRIPT.toHandlerScript.description = The Script for transforming commands from the item to the Thing handler. The script may return null to discard the commands and not pass them through. This is deprecated and has been replaced by 'commandFromItemScript'.
|
||||
profile.config.transform.SCRIPT.toItemScript.label = Thing To Item Transformation
|
||||
profile.config.transform.SCRIPT.toItemScript.description = The Script for transforming state updates and commands from the Thing handler to the item. The script may return null to discard the updates/commands and not pass them through.
|
||||
|
||||
+97
-35
@@ -18,6 +18,8 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_COMMAND_FROM_ITEM_SCRIPT;
|
||||
import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_STATE_FROM_ITEM_SCRIPT;
|
||||
import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_TO_HANDLER_SCRIPT;
|
||||
import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_TO_ITEM_SCRIPT;
|
||||
|
||||
@@ -90,14 +92,14 @@ public class ScriptProfileTest extends JavaTest {
|
||||
verify(profileCallback, never()).sendCommand(any());
|
||||
|
||||
assertLogMessage(ScriptProfile.class, LogLevel.ERROR,
|
||||
"Neither 'toItemScript' nor 'toHandlerScript' defined in link '" + link.toString()
|
||||
+ "'. Profile will discard all states and commands.");
|
||||
"Neither 'toItemScript', 'commandFromItemScript' nor 'stateFromItemScript' defined in link '"
|
||||
+ link.toString() + "'. Profile will discard all states and commands.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptExecutionErrorForwardsNoValueToCallback() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withToHandlerScript("outScript").build();
|
||||
.withCommandFromItemScript("outScript").withStateFromItemScript("outScript").build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any()))
|
||||
.thenThrow(new TransformationException("intentional failure"));
|
||||
@@ -108,8 +110,9 @@ public class ScriptProfileTest extends JavaTest {
|
||||
scriptProfile.onCommandFromHandler(OnOffType.ON);
|
||||
scriptProfile.onStateUpdateFromHandler(OnOffType.ON);
|
||||
scriptProfile.onCommandFromItem(OnOffType.ON);
|
||||
scriptProfile.onStateUpdateFromItem(OnOffType.ON);
|
||||
|
||||
verify(transformationServiceMock, times(3)).transform(any(), any());
|
||||
verify(transformationServiceMock, times(4)).transform(any(), any());
|
||||
verify(profileCallback, never()).handleCommand(any());
|
||||
verify(profileCallback, never()).sendUpdate(any());
|
||||
verify(profileCallback, never()).sendCommand(any());
|
||||
@@ -118,7 +121,7 @@ public class ScriptProfileTest extends JavaTest {
|
||||
@Test
|
||||
public void scriptExecutionResultNullForwardsNoValueToCallback() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withToHandlerScript("outScript").build();
|
||||
.withCommandFromItemScript("outScript").withStateFromItemScript("outScript").build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(null);
|
||||
|
||||
@@ -128,8 +131,9 @@ public class ScriptProfileTest extends JavaTest {
|
||||
scriptProfile.onCommandFromHandler(OnOffType.ON);
|
||||
scriptProfile.onStateUpdateFromHandler(OnOffType.ON);
|
||||
scriptProfile.onCommandFromItem(OnOffType.ON);
|
||||
scriptProfile.onStateUpdateFromItem(OnOffType.ON);
|
||||
|
||||
verify(transformationServiceMock, times(3)).transform(any(), any());
|
||||
verify(transformationServiceMock, times(4)).transform(any(), any());
|
||||
verify(profileCallback, never()).handleCommand(any());
|
||||
verify(profileCallback, never()).sendUpdate(any());
|
||||
verify(profileCallback, never()).sendCommand(any());
|
||||
@@ -138,8 +142,8 @@ public class ScriptProfileTest extends JavaTest {
|
||||
@Test
|
||||
public void scriptExecutionResultForwardsTransformedValueToCallback() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(OnOffType.class))
|
||||
.withAcceptedDataTypes(List.of(OnOffType.class))
|
||||
.withCommandFromItemScript("outScript").withStateFromItemScript("outScript")
|
||||
.withAcceptedCommandTypes(List.of(OnOffType.class)).withAcceptedDataTypes(List.of(OnOffType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(OnOffType.class)).build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString());
|
||||
@@ -150,9 +154,10 @@ public class ScriptProfileTest extends JavaTest {
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onCommandFromItem(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
|
||||
|
||||
verify(transformationServiceMock, times(3)).transform(any(), any());
|
||||
verify(profileCallback).handleCommand(OnOffType.OFF);
|
||||
verify(transformationServiceMock, times(4)).transform(any(), any());
|
||||
verify(profileCallback, times(2)).handleCommand(OnOffType.OFF);
|
||||
verify(profileCallback).sendUpdate(OnOffType.OFF);
|
||||
verify(profileCallback).sendCommand(OnOffType.OFF);
|
||||
}
|
||||
@@ -171,6 +176,7 @@ public class ScriptProfileTest extends JavaTest {
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onCommandFromItem(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
|
||||
|
||||
verify(transformationServiceMock, times(2)).transform(any(), any());
|
||||
verify(profileCallback, never()).handleCommand(any());
|
||||
@@ -179,7 +185,74 @@ public class ScriptProfileTest extends JavaTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlyToHandlerScriptDoesNotForwardInboundCommands() throws TransformationException {
|
||||
public void onlyToHandlerCommandScriptDoesNotForwardInboundCommands() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withCommandFromItemScript("outScript")
|
||||
.withAcceptedCommandTypes(List.of(DecimalType.class)).withAcceptedDataTypes(List.of(DecimalType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(OnOffType.class)).build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString());
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onCommandFromItem(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
|
||||
|
||||
verify(transformationServiceMock, times(1)).transform(any(), any());
|
||||
verify(profileCallback, times(1)).handleCommand(OnOffType.OFF);
|
||||
verify(profileCallback, never()).sendUpdate(any());
|
||||
verify(profileCallback, never()).sendCommand(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlyToHandlerStateScriptDoesNotForwardInboundCommands() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withStateFromItemScript("outScript")
|
||||
.withAcceptedCommandTypes(List.of(DecimalType.class)).withAcceptedDataTypes(List.of(DecimalType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(OnOffType.class)).build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString());
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onCommandFromItem(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
|
||||
|
||||
verify(transformationServiceMock, times(1)).transform(any(), any());
|
||||
verify(profileCallback, times(1)).handleCommand(OnOffType.OFF);
|
||||
verify(profileCallback, never()).sendUpdate(any());
|
||||
verify(profileCallback, never()).sendCommand(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompatibleStateOrCommandNotForwardedToCallback() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withCommandFromItemScript("outScript").withStateFromItemScript("outScript")
|
||||
.withAcceptedCommandTypes(List.of(DecimalType.class)).withAcceptedDataTypes(List.of(PercentType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(HSBType.class)).build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString());
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onCommandFromItem(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
|
||||
|
||||
verify(transformationServiceMock, times(4)).transform(any(), any());
|
||||
verify(profileCallback, never()).handleCommand(any());
|
||||
verify(profileCallback, never()).sendUpdate(any());
|
||||
verify(profileCallback, never()).sendCommand(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallbackToToHandlerScriptIfNotToHandlerCommandScript() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToHandlerScript("outScript")
|
||||
.withAcceptedCommandTypes(List.of(DecimalType.class)).withAcceptedDataTypes(List.of(DecimalType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(OnOffType.class)).build();
|
||||
@@ -192,31 +265,10 @@ public class ScriptProfileTest extends JavaTest {
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onCommandFromItem(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromItem(DecimalType.ZERO);
|
||||
|
||||
verify(transformationServiceMock).transform(any(), any());
|
||||
verify(profileCallback).handleCommand(OnOffType.OFF);
|
||||
verify(profileCallback, never()).sendUpdate(any());
|
||||
verify(profileCallback, never()).sendCommand(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompatibleStateOrCommandNotForwardedToCallback() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(DecimalType.class))
|
||||
.withAcceptedDataTypes(List.of(PercentType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(HSBType.class)).build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString());
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onCommandFromItem(DecimalType.ZERO);
|
||||
|
||||
verify(transformationServiceMock, times(3)).transform(any(), any());
|
||||
verify(profileCallback, never()).handleCommand(any());
|
||||
verify(transformationServiceMock, times(1)).transform(any(), any());
|
||||
verify(profileCallback, times(1)).handleCommand(OnOffType.OFF);
|
||||
verify(profileCallback, never()).sendUpdate(any());
|
||||
verify(profileCallback, never()).sendCommand(any());
|
||||
}
|
||||
@@ -241,6 +293,16 @@ public class ScriptProfileTest extends JavaTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProfileContextBuilder withCommandFromItemScript(String commandFromItemScript) {
|
||||
configuration.put(CONFIG_COMMAND_FROM_ITEM_SCRIPT, commandFromItemScript);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProfileContextBuilder withStateFromItemScript(String stateFromItemScript) {
|
||||
configuration.put(CONFIG_STATE_FROM_ITEM_SCRIPT, stateFromItemScript);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProfileContextBuilder withAcceptedDataTypes(List<Class<? extends State>> acceptedDataTypes) {
|
||||
this.acceptedDataTypes = acceptedDataTypes;
|
||||
return this;
|
||||
|
||||
Reference in New Issue
Block a user