mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[exec] Support transformation chaining and refactor using ChannelTransformation (#17292)
* [exec] Support transformation chaining and refactor using ChannelTransformation Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
1924034ee2
commit
d276000d4a
@ -36,13 +36,20 @@ It is not advised to run the virtual machine as superuser/root.
|
|||||||
The "command" Thing requires the command to execute on the shell.
|
The "command" Thing requires the command to execute on the shell.
|
||||||
Optionally one can specify:
|
Optionally one can specify:
|
||||||
|
|
||||||
- `transform` - A [transformation](https://www.openhab.org/docs/configuration/transformations.html) to apply on the execution result string.
|
- `transform` - [Transformations](/docs/configuration/transformations.html) to apply on the execution result string.
|
||||||
- `interval` - An interval, in seconds, the command will be repeatedly executed. Default is 60 seconds, set to 0 to avoid automatic repetition.
|
- `interval` - An interval, in seconds, the command will be repeatedly executed. Default is 60 seconds, set to 0 to avoid automatic repetition.
|
||||||
- `timeout` - A time-out, in seconds, the execution of the command will time out, and lastly,
|
- `timeout` - A time-out, in seconds, the execution of the command will time out, and lastly,
|
||||||
- `autorun` - A boolean parameter to make the command execute immediately every time the input channel is sent a different openHAB command. If choosing autorun, you may wish to also set `interval=0`. Note that sending the same command a second time will not trigger execution.
|
- `autorun` - A boolean parameter to make the command execute immediately every time the input channel is sent a different openHAB command. If choosing autorun, you may wish to also set `interval=0`. Note that sending the same command a second time will not trigger execution.
|
||||||
|
|
||||||
For each shell command, a separate Thing has to be defined.
|
For each shell command, a separate Thing has to be defined.
|
||||||
|
|
||||||
|
### Transformations
|
||||||
|
|
||||||
|
Transformations can be chained in the UI by listing each transformation on a separate line, or by separating them with the mathematical intersection character "∩".
|
||||||
|
Transformations are defined using this syntax: `TYPE(FUNCTION)`, e.g.: `JSONPATH($.path)`.
|
||||||
|
The syntax: `TYPE:FUNCTION` is also supported, e.g.: `JSONPATH:$.path`.
|
||||||
|
Please note that if the transformation failed or returned `null`, the original data will be passed through.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
Thing exec:command:uniquename [command="/command/to/execute here", interval=15, timeout=5, autorun=false]
|
Thing exec:command:uniquename [command="/command/to/execute here", interval=15, timeout=5, autorun=false]
|
||||||
```
|
```
|
||||||
|
@ -23,11 +23,10 @@ import java.util.Arrays;
|
|||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.IllegalFormatException;
|
import java.util.IllegalFormatException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.regex.PatternSyntaxException;
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
@ -41,9 +40,7 @@ import org.openhab.core.thing.ChannelUID;
|
|||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
import org.openhab.core.transform.TransformationException;
|
import org.openhab.core.thing.binding.generic.ChannelTransformation;
|
||||||
import org.openhab.core.transform.TransformationHelper;
|
|
||||||
import org.openhab.core.transform.TransformationService;
|
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
import org.openhab.core.util.StringUtils;
|
import org.openhab.core.util.StringUtils;
|
||||||
@ -85,14 +82,13 @@ public class ExecHandler extends BaseThingHandler {
|
|||||||
public static final String TRANSFORM = "transform";
|
public static final String TRANSFORM = "transform";
|
||||||
public static final String AUTORUN = "autorun";
|
public static final String AUTORUN = "autorun";
|
||||||
|
|
||||||
// RegEx to extract a parse a function String <code>'(.*?)\((.*)\)'</code>
|
|
||||||
private static final Pattern EXTRACT_FUNCTION_PATTERN = Pattern.compile("(.*?)\\((.*)\\)");
|
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> executionJob;
|
private @Nullable ScheduledFuture<?> executionJob;
|
||||||
private @Nullable String lastInput;
|
private @Nullable String lastInput;
|
||||||
|
|
||||||
private static Runtime rt = Runtime.getRuntime();
|
private static Runtime rt = Runtime.getRuntime();
|
||||||
|
|
||||||
|
private @Nullable ChannelTransformation channelTransformation;
|
||||||
|
|
||||||
public ExecHandler(Thing thing, ExecWhitelistWatchService execWhitelistWatchService) {
|
public ExecHandler(Thing thing, ExecWhitelistWatchService execWhitelistWatchService) {
|
||||||
super(thing);
|
super(thing);
|
||||||
this.bundleContext = FrameworkUtil.getBundle(ExecHandler.class).getBundleContext();
|
this.bundleContext = FrameworkUtil.getBundle(ExecHandler.class).getBundleContext();
|
||||||
@ -128,6 +124,8 @@ public class ExecHandler extends BaseThingHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
channelTransformation = new ChannelTransformation((List<String>) getConfig().get(TRANSFORM));
|
||||||
|
|
||||||
if (executionJob == null || executionJob.isCancelled()) {
|
if (executionJob == null || executionJob.isCancelled()) {
|
||||||
if ((getConfig().get(INTERVAL)) != null && ((BigDecimal) getConfig().get(INTERVAL)).intValue() > 0) {
|
if ((getConfig().get(INTERVAL)) != null && ((BigDecimal) getConfig().get(INTERVAL)).intValue() > 0) {
|
||||||
int pollingInterval = ((BigDecimal) getConfig().get(INTERVAL)).intValue();
|
int pollingInterval = ((BigDecimal) getConfig().get(INTERVAL)).intValue();
|
||||||
@ -144,6 +142,7 @@ public class ExecHandler extends BaseThingHandler {
|
|||||||
executionJob.cancel(true);
|
executionJob.cancel(true);
|
||||||
executionJob = null;
|
executionJob = null;
|
||||||
}
|
}
|
||||||
|
channelTransformation = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void execute() {
|
public void execute() {
|
||||||
@ -291,10 +290,9 @@ public class ExecHandler extends BaseThingHandler {
|
|||||||
outputBuilder.append(errorBuilder.toString());
|
outputBuilder.append(errorBuilder.toString());
|
||||||
|
|
||||||
String transformedResponse = Objects.requireNonNull(StringUtils.chomp(outputBuilder.toString()));
|
String transformedResponse = Objects.requireNonNull(StringUtils.chomp(outputBuilder.toString()));
|
||||||
String transformation = (String) getConfig().get(TRANSFORM);
|
|
||||||
|
|
||||||
if (transformation != null && transformation.length() > 0) {
|
if (channelTransformation != null) {
|
||||||
transformedResponse = transformResponse(transformedResponse, transformation);
|
transformedResponse = channelTransformation.apply(transformedResponse).orElse(transformedResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateState(OUTPUT, new StringType(transformedResponse));
|
updateState(OUTPUT, new StringType(transformedResponse));
|
||||||
@ -304,58 +302,6 @@ public class ExecHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected @Nullable String transformResponse(String response, String transformation) {
|
|
||||||
String transformedResponse;
|
|
||||||
|
|
||||||
try {
|
|
||||||
String[] parts = splitTransformationConfig(transformation);
|
|
||||||
String transformationType = parts[0];
|
|
||||||
String transformationFunction = parts[1];
|
|
||||||
|
|
||||||
TransformationService transformationService = TransformationHelper.getTransformationService(bundleContext,
|
|
||||||
transformationType);
|
|
||||||
if (transformationService != null) {
|
|
||||||
transformedResponse = transformationService.transform(transformationFunction, response);
|
|
||||||
} else {
|
|
||||||
transformedResponse = response;
|
|
||||||
logger.warn("Couldn't transform response because transformationService of type '{}' is unavailable",
|
|
||||||
transformationType);
|
|
||||||
}
|
|
||||||
} catch (TransformationException te) {
|
|
||||||
logger.warn("An exception occurred while transforming '{}' with '{}' : '{}'", response, transformation,
|
|
||||||
te.getMessage());
|
|
||||||
|
|
||||||
// in case of an error we return the response without any transformation
|
|
||||||
transformedResponse = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Transformed response is '{}'", transformedResponse);
|
|
||||||
return transformedResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Splits a transformation configuration string into its two parts - the
|
|
||||||
* transformation type and the function/pattern to apply.
|
|
||||||
*
|
|
||||||
* @param transformation the string to split
|
|
||||||
* @return a string array with exactly two entries for the type and the function
|
|
||||||
*/
|
|
||||||
protected String[] splitTransformationConfig(String transformation) {
|
|
||||||
Matcher matcher = EXTRACT_FUNCTION_PATTERN.matcher(transformation);
|
|
||||||
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
throw new IllegalArgumentException("given transformation function '" + transformation
|
|
||||||
+ "' does not follow the expected pattern '<function>(<pattern>)'");
|
|
||||||
}
|
|
||||||
matcher.reset();
|
|
||||||
|
|
||||||
matcher.find();
|
|
||||||
String type = matcher.group(1);
|
|
||||||
String pattern = matcher.group(2);
|
|
||||||
|
|
||||||
return new String[] { type, pattern };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the command string into an array.
|
* Transforms the command string into an array.
|
||||||
* Either invokes the shell and passes using the "c" option
|
* Either invokes the shell and passes using the "c" option
|
||||||
|
@ -19,7 +19,7 @@ thing-type.config.exec.command.interval.description = Interval, in seconds, the
|
|||||||
thing-type.config.exec.command.timeout.label = Timeout
|
thing-type.config.exec.command.timeout.label = Timeout
|
||||||
thing-type.config.exec.command.timeout.description = Time out, in seconds, the execution of the command will time out
|
thing-type.config.exec.command.timeout.description = Time out, in seconds, the execution of the command will time out
|
||||||
thing-type.config.exec.command.transform.label = Transform
|
thing-type.config.exec.command.transform.label = Transform
|
||||||
thing-type.config.exec.command.transform.description = The transformation to apply on the execution result, e.g. REGEX((.*))
|
thing-type.config.exec.command.transform.description = The transformation to apply on the execution result, e.g. REGEX((.*)). You can chain transformations by listing each transformation on a separate line, or by separating them with the intersection character ∩.
|
||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
||||||
|
@ -21,10 +21,12 @@
|
|||||||
<label>Command</label>
|
<label>Command</label>
|
||||||
<description>The command to execute</description>
|
<description>The command to execute</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="transform" type="text" required="false">
|
<parameter name="transform" type="text" required="false" multiple="true">
|
||||||
<label>Transform</label>
|
<label>Transform</label>
|
||||||
<description>The transformation to apply on the execution result, e.g. REGEX((.*))</description>
|
<description>The transformation to apply on the execution result, e.g. REGEX((.*)).
|
||||||
<default>REGEX((.*))</default>
|
You can chain transformations by
|
||||||
|
listing each transformation on a separate line, or
|
||||||
|
by separating them with the intersection character ∩.</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="interval" type="integer" required="false">
|
<parameter name="interval" type="integer" required="false">
|
||||||
<label>Interval</label>
|
<label>Interval</label>
|
||||||
|
Loading…
Reference in New Issue
Block a user