added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user