added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.exec-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-exec" description="Exec Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<configfile finalname="${openhab.conf}/misc/exec.whitelist" override="false">mvn:${project.groupId}/openhab-addons-external/${project.version}/cfg/exec.whitelist</configfile>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.exec/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,37 @@
/**
* 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.binding.exec.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link ExecBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public class ExecBindingConstants {
public static final String BINDING_ID = "exec";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_COMMAND = new ThingTypeUID(BINDING_ID, "command");
// List of all Channel ids
public static final String OUTPUT = "output";
public static final String INPUT = "input";
public static final String EXIT = "exit";
public static final String RUN = "run";
public static final String LAST_EXECUTION = "lastexecution";
}

View File

@@ -0,0 +1,67 @@
/**
* 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.binding.exec.internal;
import static org.openhab.binding.exec.internal.ExecBindingConstants.THING_COMMAND;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.exec.internal.handler.ExecHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ExecHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.exec")
public class ExecHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_COMMAND);
private final Logger logger = LoggerFactory.getLogger(ExecHandlerFactory.class);
private final ExecWhitelistWatchService execWhitelistWatchService;
@Activate
public ExecHandlerFactory(@Reference ExecWhitelistWatchService execWhitelistWatchService) {
this.execWhitelistWatchService = execWhitelistWatchService;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_COMMAND)) {
return new ExecHandler(thing, execWhitelistWatchService);
}
return null;
}
}

View File

@@ -0,0 +1,88 @@
/**
* 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.binding.exec.internal;
import static java.nio.file.StandardWatchEventKinds.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigConstants;
import org.openhab.core.service.AbstractWatchService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ExecWhitelistWatchService} provides a whitelist check for exec commands
*
* @author Jan N. Klug - Initial contribution
*/
@Component(service = ExecWhitelistWatchService.class)
@NonNullByDefault
public class ExecWhitelistWatchService extends AbstractWatchService {
private static final String COMMAND_WHITELIST_PATH = ConfigConstants.getConfigFolder() + File.separator + "misc";
private static final String COMMAND_WHITELIST_FILE = "exec.whitelist";
private final Logger logger = LoggerFactory.getLogger(ExecWhitelistWatchService.class);
private final Set<String> commandWhitelist = new HashSet<>();
@Activate
public ExecWhitelistWatchService() {
super(COMMAND_WHITELIST_PATH);
processWatchEvent(null, null, Paths.get(COMMAND_WHITELIST_PATH, COMMAND_WHITELIST_FILE));
}
@Override
protected boolean watchSubDirectories() {
return false;
}
@Override
protected Kind<?>[] getWatchEventKinds(@Nullable Path directory) {
return new Kind<?>[] { ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY };
}
@Override
protected void processWatchEvent(@Nullable WatchEvent<?> event, @Nullable Kind<?> kind, @Nullable Path path) {
if (path != null && path.endsWith(COMMAND_WHITELIST_FILE)) {
commandWhitelist.clear();
try {
Files.lines(path).filter(line -> !line.trim().startsWith("#")).forEach(commandWhitelist::add);
logger.debug("Updated command whitelist: {}", commandWhitelist);
} catch (IOException e) {
logger.warn("Cannot read whitelist file, exec binding commands won't be processed: {}", e.getMessage());
}
}
}
/**
* Check if a command is whitelisted
*
* @param command the command to check alias
* @return true if whitelisted, false if not
*/
public boolean isWhitelisted(String command) {
return commandWhitelist.contains(command);
}
}

View File

@@ -0,0 +1,427 @@
/**
* 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.binding.exec.internal.handler;
import static org.openhab.binding.exec.internal.ExecBindingConstants.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.IllegalFormatException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.exec.internal.ExecWhitelistWatchService;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationHelper;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ExecHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Karel Goderis - Initial contribution
* @author Constantin Piber - Added better argument support (delimiter and pass to shell)
* @author Jan N. Klug - Add command whitelist check
*/
@NonNullByDefault
public class ExecHandler extends BaseThingHandler {
/**
* Use this to separate between command and parameter, and also between parameters.
*/
public static final String CMD_LINE_DELIMITER = "@@";
/**
* Shell executables
*/
public static final String[] SHELL_WINDOWS = new String[] { "cmd" };
public static final String[] SHELL_NIX = new String[] { "sh", "bash", "zsh", "csh" };
private final ExecWhitelistWatchService execWhitelistWatchService;
private Logger logger = LoggerFactory.getLogger(ExecHandler.class);
private final BundleContext bundleContext;
// List of Configurations constants
public static final String INTERVAL = "interval";
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 @Nullable ScheduledFuture<?> executionJob;
private @Nullable String lastInput;
private static Runtime rt = Runtime.getRuntime();
public ExecHandler(Thing thing, ExecWhitelistWatchService execWhitelistWatchService) {
super(thing);
this.bundleContext = FrameworkUtil.getBundle(ExecHandler.class).getBundleContext();
this.execWhitelistWatchService = execWhitelistWatchService;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
// Placeholder for later refinement
} else {
if (channelUID.getId().equals(RUN)) {
if (command instanceof OnOffType) {
if (command == OnOffType.ON) {
scheduler.schedule(this::execute, 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))) {
logger.trace("Executing command '{}' after a change of the input channel to '{}'",
getConfig().get(COMMAND), lastInput);
scheduler.schedule(this::execute, 0, TimeUnit.SECONDS);
}
}
}
}
}
}
@Override
public void initialize() {
if (executionJob == null || executionJob.isCancelled()) {
if ((getConfig().get(INTERVAL)) != null && ((BigDecimal) getConfig().get(INTERVAL)).intValue() > 0) {
int pollingInterval = ((BigDecimal) getConfig().get(INTERVAL)).intValue();
executionJob = scheduler.scheduleWithFixedDelay(this::execute, 0, pollingInterval, TimeUnit.SECONDS);
}
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void dispose() {
if (executionJob != null && !executionJob.isCancelled()) {
executionJob.cancel(true);
executionJob = null;
}
}
public void execute() {
String commandLine = (String) getConfig().get(COMMAND);
if (!execWhitelistWatchService.isWhitelisted(commandLine)) {
logger.warn("Tried to execute '{}', but it is not contained in whitelist.", commandLine);
return;
}
int timeOut = 60000;
if ((getConfig().get(TIME_OUT)) != null) {
timeOut = ((BigDecimal) getConfig().get(TIME_OUT)).intValue() * 1000;
}
if (commandLine != null && !commandLine.isEmpty()) {
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
// randomly appearing (on UNIXProcess) *when* one tries to gobble up the stdout and sterr output of the
// subprocess in separate threads. It seems to be common "wisdom" to do that in separate threads, but
// only when keeping everything between .exec() and .waitfor() in the same thread, this lock race
// condition seems to go away. This approach of not reading the outputs in separate threads *might* be a
// 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.warn(
"An exception occurred while formatting the command line with the current time and input values : '{}'",
e.getMessage());
updateState(RUN, OnOffType.OFF);
updateState(OUTPUT, new StringType(e.getMessage()));
return;
}
String[] cmdArray;
String[] shell;
if (commandLine.contains(CMD_LINE_DELIMITER)) {
logger.debug("Splitting by '{}'", CMD_LINE_DELIMITER);
try {
cmdArray = commandLine.split(CMD_LINE_DELIMITER);
} catch (PatternSyntaxException e) {
logger.warn("An exception occurred while splitting '{}' : '{}'", commandLine, e.getMessage());
updateState(RUN, OnOffType.OFF);
updateState(OUTPUT, new StringType(e.getMessage()));
return;
}
} else {
// Invoke shell with 'c' option and pass string
logger.debug("Passing to shell for parsing command.");
switch (getOperatingSystemType()) {
case WINDOWS:
shell = SHELL_WINDOWS;
logger.debug("OS: WINDOWS ({})", getOperatingSystemName());
cmdArray = createCmdArray(shell, "/c", commandLine);
break;
case LINUX:
case MAC:
case SOLARIS:
// assume sh is present, should all be POSIX-compliant
shell = SHELL_NIX;
logger.debug("OS: *NIX ({})", getOperatingSystemName());
cmdArray = createCmdArray(shell, "-c", commandLine);
break;
default:
logger.debug("OS: Unknown ({})", getOperatingSystemName());
logger.warn("OS {} not supported, please manually split commands!", getOperatingSystemName());
updateState(RUN, OnOffType.OFF);
updateState(OUTPUT, new StringType("OS not supported, please manually split commands!"));
return;
}
}
if (cmdArray.length == 0) {
logger.trace("Empty command received, not executing");
return;
}
logger.trace("The command to be executed will be '{}'", Arrays.asList(cmdArray));
Process proc;
try {
proc = rt.exec(cmdArray);
} catch (Exception e) {
logger.warn("An exception occurred while executing '{}' : '{}'", Arrays.asList(cmdArray),
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.getInputStream());
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.readLine()) != null) {
outputBuilder.append(line).append("\n");
logger.debug("Exec [{}]: '{}'", "OUTPUT", line);
}
} catch (IOException e) {
logger.warn("An exception occurred while reading the stdout when executing '{}' : '{}'", commandLine,
e.getMessage());
}
try (InputStreamReader isr = new InputStreamReader(proc.getErrorStream());
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.readLine()) != null) {
errorBuilder.append(line).append("\n");
logger.debug("Exec [{}]: '{}'", "ERROR", line);
}
} catch (IOException e) {
logger.warn("An exception occurred while reading the stderr when executing '{}' : '{}'", commandLine,
e.getMessage());
}
boolean exitVal = false;
try {
exitVal = proc.waitFor(timeOut, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.warn("An exception occurred while waiting for the process ('{}') to finish : '{}'", commandLine,
e.getMessage());
}
if (!exitVal) {
logger.warn("Forcibly termininating the process ('{}') after a timeout of {} ms", commandLine, timeOut);
proc.destroyForcibly();
}
updateState(RUN, OnOffType.OFF);
updateState(EXIT, new DecimalType(proc.exitValue()));
outputBuilder.append(errorBuilder.toString());
outputBuilder.append(errorBuilder.toString());
String transformedResponse = StringUtils.chomp(outputBuilder.toString());
String transformation = (String) getConfig().get(TRANSFORM);
if (transformation != null && transformation.length() > 0) {
transformedResponse = transformResponse(transformedResponse, transformation);
}
updateState(OUTPUT, new StringType(transformedResponse));
DateTimeType stampType = new DateTimeType(ZonedDateTime.now());
updateState(LAST_EXECUTION, stampType);
}
}
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.
* Either invokes the shell and passes using the "c" option
* or (if command already starts with one of the shells) splits by space.
*
* @param shell (path), picks to first one to execute the command
* @param cOption "c"-option string
* @param commandLine to execute
* @return command array
*/
protected String[] createCmdArray(String[] shell, String cOption, String commandLine) {
boolean startsWithShell = false;
for (String sh : shell) {
if (commandLine.startsWith(sh + " ")) {
startsWithShell = true;
break;
}
}
if (!startsWithShell) {
return new String[] { shell[0], cOption, commandLine };
} else {
logger.debug("Splitting by spaces");
try {
return commandLine.split(" ");
} catch (PatternSyntaxException e) {
logger.warn("An exception occurred while splitting '{}' : '{}'", commandLine, e.getMessage());
updateState(RUN, OnOffType.OFF);
updateState(OUTPUT, new StringType(e.getMessage()));
return new String[] {};
}
}
}
/**
* Contains information about which operating system openHAB is running on.
* Found on https://stackoverflow.com/a/31547504/7508309, slightly modified
*
* @author Constantin Piber (for Memin) - Initial contribution
*/
public enum OS {
WINDOWS,
LINUX,
MAC,
SOLARIS,
UNKNOWN,
NOT_SET
}
private static OS os = OS.NOT_SET;
public static OS getOperatingSystemType() {
if (os == OS.NOT_SET) {
String operSys = System.getProperty("os.name").toLowerCase();
if (operSys.contains("win")) {
os = OS.WINDOWS;
} else if (operSys.contains("nix") || operSys.contains("nux") || operSys.contains("aix")) {
os = OS.LINUX;
} else if (operSys.contains("mac")) {
os = OS.MAC;
} else if (operSys.contains("sunos")) {
os = OS.SOLARIS;
} else {
os = OS.UNKNOWN;
}
}
return os;
}
public static String getOperatingSystemName() {
return System.getProperty("os.name");
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="exec" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Exec Binding</name>
<description>This is the binding to execute arbitrary shell commands</description>
<author>Karel Goderis</author>
</binding:binding>

View File

@@ -0,0 +1,33 @@
###############
# binding
binding.exec.name = Exec Binding
binding.exec.description = Binding zur Ausführung von Befehlen und zur Verarbeitung des Rückgabewerts
###############
# thing types
thing-type.exec.command.label = Befehl
thing-type.exec.command.description = Befehl zur Ausführung und Verarbeitung des Rückgabewertes
# thing type configuration
thing-type.config.exec.command.command.label = Befehl
thing-type.config.exec.command.command.description = Der auszuführende Befehl
thing-type.config.exec.command.transform.label = Transformation
thing-type.config.exec.command.transform.description = Transformation des Rückgabewertes des Befehls, z.B. REGEX((.*)) liefert den Rückgabewert unverändert
thing-type.config.exec.command.interval.label = Intervall
thing-type.config.exec.command.interval.description = Intervall in Sekunden, in welchem der Befehl ausgeführt wird
thing-type.config.exec.command.timeout.label = Timeout
thing-type.config.exec.command.timeout.description = Timeout in Sekunden, nach dem die Ausführung des Befehls abgebrochen wird
thing-type.config.exec.command.autorun.label = Autorun
thing-type.config.exec.command.autorun.description = Wenn aktiv, dann wird der Befehl jedes Mal ausgeführt, wenn sich der Eingabewert ändert
# channel type
channel-type.exec.output.label = Rückgabewert
channel-type.exec.output.description = Rückgabewert der Befehlsausführung
channel-type.exec.input.label = Eingabewert
channel-type.exec.input.description = Eingabewert, der als zweiter Parameter an den Befehl übergeben wird
channel-type.exec.exit.label = Rückgabestatus
channel-type.exec.exit.description = Dokumentiert die erfolgreiche Ausführung
channel-type.exec.run.label = Ausführung
channel-type.exec.run.description = Steht während der Befehlsausführung auf ON; durch Setzen auf ON wird der Befehl sofort ausgeführt
channel-type.exec.lastexecution.label = Zeitpunkt der letzten Ausführung
channel-type.exec.lastexecution.description = Datum und Uhrzeit der letzten Ausführung des Befehls im Format yyyy-MM-dd'T'HH:mm:ss.SSSZ

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="exec"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="command">
<label>Command</label>
<description>The Command encapsulates a shell command to be executed</description>
<channels>
<channel id="output" typeId="output"/>
<channel id="input" typeId="input"/>
<channel id="exit" typeId="exit"/>
<channel id="run" typeId="run"/>
<channel id="lastexecution" typeId="lastexecution"/>
</channels>
<config-description>
<parameter name="command" type="text" required="true">
<label>Command</label>
<description>The command to execute</description>
</parameter>
<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>0</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>
<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>
<channel-type id="output">
<item-type>String</item-type>
<label>Output</label>
<description>Output of the last execution of the command</description>
<state readOnly="true"></state>
</channel-type>
<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>
<state readOnly="true"></state>
</channel-type>
<channel-type id="run">
<item-type>Switch</item-type>
<label>Running</label>
<description>Send ON to execute the command and the current state tells whether it is running or not</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="lastexecution">
<item-type>DateTime</item-type>
<label>Last Execution</label>
<description>Time/Date the command was last executed, in yyyy-MM-dd'T'HH:mm:ss.SSSZ format</description>
<state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>