Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exec 2.0 Enhancements #1502

Merged
merged 2 commits into from
Nov 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<channels>
<channel id="output" typeId="output"/>
<channel id="input" typeId="input"/>
<channel id="exit" typeId="exit"/>
<channel id="run" typeId="running"/>
<channel id="lastexecution" typeId="lastexecution" />
Expand All @@ -23,15 +24,23 @@
<parameter name="transform" type="text" required="false">
<label>Transform</label>
<description>The transformation to apply on the execution result, e.g. REGEX((.*))</description>
<default>REGEX((.*))</default>
</parameter>
<parameter name="interval" type="integer" required="false">
<label>Interval</label>
<description>Interval, in seconds, the command will be repeatedly executed</description>
<default>60</default>
</parameter>
<parameter name="timeout" type="integer" required="false">
<label>Timeout</label>
<description>Time out, in seconds, the execution of the command will time out</description>
</parameter>
<default>15</default>
</parameter>
<parameter name="autorun" type="boolean" required="false">
<label>Autorun</label>
<description>When true, the command will execute each time the state of the input channel changes</description>
<default>false</default>
</parameter>
</config-description>

</thing-type>
Expand All @@ -42,7 +51,13 @@
<description>Output of the last execution of the command</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="exit">
<channel-type id="input">
<item-type>String</item-type>
<label>Input</label>
<description>Input that will be passed as second parameter to the command</description>
<state readOnly="false"></state>
</channel-type>
<channel-type id="exit">
<item-type>Number</item-type>
<label>Exit Value</label>
<description>The exit value of the last execution of the command</description>
Expand Down
22 changes: 15 additions & 7 deletions addons/binding/org.openhab.binding.exec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,34 @@ This binding integrates the possibility to execute arbitrary shell commands.

## Supported Things

Currently, the binding supports a single Thing, being the 'command' thing.
Currently, the binding supports a single type of Thing, being the ```command``` Thing.

## Binding Configuration

The binding does not require any specific configuration

Note that the commands are executed in the context and with the privileges of the process running the java virtual machine. It is not advised to run the virtual machine as superuser/root

## Thing Configuration

The command Thing requires the command to execute on the shell, and optionally one can specify a transformation to apply on the execution result, an interval, in seconds, the command will be repeatedly executed and lastly a time-out, in seconds, the execution of the command will time out
The command Thing requires the command to execute on the shell, and optionally one can specify a transformation to apply on the execution result, an interval, in seconds, the command will be repeatedly executed, a time-out, in seconds, the execution of the command will time out, and lastly, a boolean parameter to make the command execute immediately every time the state of the input channel has changed. For each command a separate Thing has to be defined

```
Thing exec:command:apc [ command="/usr/local/bin/apcaccess status", interval=15, timeout=5]
Thing exec:command:apc [command="/usr/local/bin/apcaccess status", interval=15, timeout=5, autorun=false]
```

```command``` itseld can be enhanced using the well known syntax of the java.util.Formatter class. The following parameters are automatically added:

the current date (as java.util.Date, example: %1$tY-%1$tm-%1$td)
the current State of the input channel (see below, example: %2$s)

## Channels

All devices support the following channels:
All Things support the following channels:

| Channel Type ID | Item Type | Description |
|-----------------|------------------------|--------------|----------------- |------------- |
| input | String | Input parameter to provide to the command |
| output | String | Output of the last execution of the command |
| exit | Number | The exit value of the last execution of the command |
| run | Switch | Send ON to execute the command and the current state tells whether it is running or not |
Expand All @@ -34,15 +42,15 @@ All devices support the following channels:
demo.Things:

```
Thing exec:command:apc [ command="/usr/local/bin/apcaccess status", interval=15, timeout=5]
Thing exec:command:apc [command="/usr/local/bin/apcaccess status", interval=15, timeout=5]
Thing exec:command:myscript [command="php ./configurations/scripts/script.php %2$s", transform="REGEX((.*?))"]
```

demo.items:

```
String APCRaw "[%s]" (All) {channel="exec:command:apc:output"}
Switch APCRunning { channel="exec:command:apc:running"}
Switch APCRunning { channel="exec:command:apc:run"}
Number APCExitValue {channel="exec:command:apc:exit"}
Switch APCExecute {channel="exec:command:apc:execute"}
DateTime APCLastExecution {channel="exec:command:apc:lastexecution"}
```
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class ExecBindingConstants {

// List of all Channel ids
public final static String OUTPUT = "output";
public final static String INPUT = "input";
public final static String EXIT = "exit";
public final static String RUN = "run";
public final static String LAST_EXECUTION = "lastexecution";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
*/
package org.openhab.binding.exec.handler;

import static org.openhab.binding.exec.ExecBindingConstants.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.IllegalFormatException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
Expand All @@ -32,7 +35,6 @@
import org.eclipse.smarthome.core.transform.TransformationService;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.RefreshType;
import org.openhab.binding.exec.ExecBindingConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -51,11 +53,13 @@ public class ExecHandler extends BaseThingHandler {
public static final String TIME_OUT = "timeout";
public static final String COMMAND = "command";
public static final String TRANSFORM = "transform";
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 ScheduledFuture<?> executionJob;
private String lastInput;

private static Runtime rt = Runtime.getRuntime();

Expand All @@ -69,12 +73,25 @@ public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
// Placeholder for later refinement
} else {
if (channelUID.getId().equals(ExecBindingConstants.RUN)) {
if (channelUID.getId().equals(RUN)) {
if (command instanceof OnOffType) {
if (command == OnOffType.ON) {
scheduler.schedule(periodicExecutionRunnable, 0, TimeUnit.SECONDS);
}
}
} else if (channelUID.getId().equals(INPUT)) {
if (command instanceof StringType) {
String previousInput = lastInput;
lastInput = command.toString();
if (lastInput != null && !lastInput.equals(previousInput)) {
if (getConfig().get(AUTORUN) != null && ((Boolean) getConfig().get(AUTORUN)).booleanValue()) {
lastInput = command.toString();
logger.trace("Executing command '{}' after a change of the input channel to '{}'",
getConfig().get(COMMAND), command.toString());
scheduler.schedule(periodicExecutionRunnable, 0, TimeUnit.SECONDS);
}
}
}
}
}
}
Expand Down Expand Up @@ -115,7 +132,7 @@ public void run() {

if (commandLine != null && !commandLine.isEmpty()) {

updateState(ExecBindingConstants.RUN, OnOffType.ON);
updateState(RUN, OnOffType.ON);

// For some obscure reason, when using Apache Common Exec, or using a straight implementation of
// Runtime.Exec(), on Mac OS X (Yosemite and El Capitan), there seems to be a lock race condition
Expand All @@ -126,21 +143,37 @@ public void run() {
// problem for external commands that generate a lot of output, but this will be dependent on the limits
// of the underlying operating system.

try {
if (lastInput != null) {
commandLine = String.format(commandLine, Calendar.getInstance().getTime(), lastInput);
} else {
commandLine = String.format(commandLine, Calendar.getInstance().getTime());
}
} catch (IllegalFormatException e) {
logger.error(
"An exception occurred while formatting the command line with the current time and input values : '{}'",
e.getMessage());
updateState(RUN, OnOffType.OFF);
return;
}

logger.trace("The command to be executed will be '{}'", commandLine);

Process proc = null;
try {
proc = rt.exec(commandLine.toString());
} catch (Exception e) {
logger.error("An exception occured while executing '{}' : '{}'",
new Object[] { commandLine.toString(), e.getMessage() });
updateState(ExecBindingConstants.RUN, OnOffType.OFF);
updateState(ExecBindingConstants.OUTPUT, new StringType(e.getMessage()));
updateState(RUN, OnOffType.OFF);
updateState(OUTPUT, new StringType(e.getMessage()));
return;
}

StringBuilder outputBuilder = new StringBuilder();
StringBuilder errorBuilder = new StringBuilder();

try (InputStreamReader isr = new InputStreamReader(proc.getErrorStream());
try (InputStreamReader isr = new InputStreamReader(proc.getInputStream());
BufferedReader br = new BufferedReader(isr);) {
String line = null;
while ((line = br.readLine()) != null) {
Expand Down Expand Up @@ -180,8 +213,10 @@ public void run() {
proc.destroyForcibly();
}

updateState(ExecBindingConstants.RUN, OnOffType.OFF);
updateState(ExecBindingConstants.EXIT, new DecimalType(proc.exitValue()));
updateState(RUN, OnOffType.OFF);
updateState(EXIT, new DecimalType(proc.exitValue()));

outputBuilder.append(errorBuilder.toString());

String transformedResponse = StringUtils.chomp(outputBuilder.toString());
String transformation = (String) getConfig().get(TRANSFORM);
Expand All @@ -190,10 +225,10 @@ public void run() {
transformedResponse = transformResponse(transformedResponse, transformation);
}

updateState(ExecBindingConstants.OUTPUT, new StringType(transformedResponse));
updateState(OUTPUT, new StringType(transformedResponse));

DateTimeType stampType = new DateTimeType(Calendar.getInstance());
updateState(ExecBindingConstants.LAST_EXECUTION, stampType);
updateState(LAST_EXECUTION, stampType);

}
}
Expand Down