Event scripts (#36)

* Begin scripting work

* More scripting work

* Finalize scripting system

* Begin implementing script events

* Finalize script system

Co-authored-by: Banks T <btrout.dhrs@gmail.com>
This commit is contained in:
oriagranat9
2020-01-04 06:20:28 -08:00
committed by GitHub
parent 2ce8a8dad9
commit 159dea1e25
10 changed files with 278 additions and 9 deletions

View File

@@ -2,6 +2,8 @@ package com.chameleonvision;
import com.chameleonvision.config.ConfigManager;
import com.chameleonvision.network.NetworkManager;
import com.chameleonvision.scripting.ScriptEventType;
import com.chameleonvision.scripting.ScriptManager;
import com.chameleonvision.util.Platform;
import com.chameleonvision.util.ShellExec;
import com.chameleonvision.util.Utilities;
@@ -42,6 +44,8 @@ public class Main {
if (!hasReportedConnectionFailure && logMessage.message.contains("timed out")) {
System.err.println("NT Connection has failed!");
hasReportedConnectionFailure = true;
} else if (logMessage.message.contains("connected")) {
ScriptManager.queueEvent(ScriptEventType.kNTConnected);
}
}
}
@@ -102,6 +106,9 @@ public class Main {
}
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> ScriptManager.queueEvent(ScriptEventType.kProgramExit)));
if (CurrentPlatform.equals(Platform.UNSUPPORTED)) {
System.err.printf("Sorry, this platform is not supported. Give these details to the developers.\n%s\n", CurrentPlatform.toString());
return;
@@ -133,6 +140,14 @@ public class Main {
}
ConfigManager.initializeSettings();
if (!CurrentPlatform.isWindows()) {
ScriptManager.initialize();
} else {
System.out.println("Scripts not yet supported on Windows. ScriptEvents will be ignored.");
}
NetworkManager.initialize(manageNetwork);
if (ntServerMode) {
@@ -148,6 +163,8 @@ public class Main {
// NetworkTableInstance.getDefault().startClient("localhost");
}
ScriptManager.queueEvent(ScriptEventType.kProgramInit);
boolean visionSourcesOk = VisionManager.initializeSources();
if (!visionSourcesOk) {
System.out.println("No cameras connected!");
@@ -156,13 +173,13 @@ public class Main {
boolean visionProcessesOk = VisionManager.initializeProcesses();
if (!visionProcessesOk) {
System.err.println("shit");
System.err.println("Failed to start threads!");
return;
}
VisionManager.startProcesses();
System.out.printf("Starting Webserver at port %d\n", DEFAULT_PORT);
System.out.printf("Starting Web server at port %d\n", DEFAULT_PORT);
Server.main(DEFAULT_PORT);
}
}

View File

@@ -0,0 +1,14 @@
package com.chameleonvision.scripting;
public enum ScriptCommandType {
kDefault(""),
kBashScript("bash"),
kPythonScript("python"),
kPython3Script("python3");
public final String value;
ScriptCommandType(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,23 @@
package com.chameleonvision.scripting;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ScriptConfig {
public final ScriptEventType eventType;
public final String command;
public ScriptConfig(ScriptEventType eventType) {
this.eventType = eventType;
this.command = "";
}
@JsonCreator
public ScriptConfig(
@JsonProperty("eventType") ScriptEventType eventType,
@JsonProperty("command") String command
) {
this.eventType = eventType;
this.command = command;
}
}

View File

@@ -0,0 +1,35 @@
package com.chameleonvision.scripting;
import com.chameleonvision.Debug;
import com.chameleonvision.util.ShellExec;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class ScriptEvent {
private static final ShellExec executor = new ShellExec(true, true);
public final ScriptConfig config;
public ScriptEvent(ScriptConfig config) {
this.config = config;
}
public int run() throws IOException {
int retVal = executor.executeBashCommand(config.command);
String output = executor.getOutput();
String error = executor.getError();
if (!error.isEmpty()) {
System.err.printf("Error when running \"%s\" script: %s\n", config.eventType.name(), error);
} else if (!output.isEmpty()) {
Debug.printInfo(String.format("Output from \"%s\" script: %s\n", config.eventType.name(), output));
}
Debug.printInfo(String.format("Script for %s ran with command line: \"%s\", exit code: %d, output: %s, error: %s\n", config.eventType.name(), config.command, retVal, output, error));
return retVal;
}
}

View File

@@ -0,0 +1,21 @@
package com.chameleonvision.scripting;
public enum ScriptEventType {
kProgramInit("Program Init"),
kProgramExit("Program Exit"),
kNTConnected("NT Connected"),
kLEDOn("LED On"),
kLEDOff("LED Off"),
kEnterDriverMode("Enter Driver Mode"),
kExitDriverMode("Exit Driver Mode"),
kFoundTarget("Found Target"),
kFoundMultipleTarget("Found Multiple Target"),
kLostTarget("Lost Target"),
kPipelineLag("Pipeline Lag");
public final String value;
ScriptEventType(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,126 @@
package com.chameleonvision.scripting;
import com.chameleonvision.Debug;
import com.chameleonvision.Main;
import com.chameleonvision.config.ConfigManager;
import com.chameleonvision.util.JacksonHelper;
import com.chameleonvision.util.LoopingRunnable;
import com.chameleonvision.util.Platform;
import com.chameleonvision.util.ProgramDirectoryUtilities;
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.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
public class ScriptManager {
private ScriptManager() {}
private static final List<ScriptEvent> events = new ArrayList<>();
private static final LinkedBlockingDeque<ScriptEventType> queuedEvents = new LinkedBlockingDeque<>(25);
public static void initialize() {
ScriptConfigManager.initialize();
if (ScriptConfigManager.fileExists()) {
for (ScriptConfig scriptConfig : ScriptConfigManager.loadConfig()) {
ScriptEvent scriptEvent = new ScriptEvent(scriptConfig);
events.add(scriptEvent);
}
new Thread(new ScriptRunner(10L)).start();
} else {
System.err.println("Something went wrong initializing scripts! Events will not run.");
}
}
private static class ScriptRunner extends LoopingRunnable {
ScriptRunner(Long loopTimeMs) {
super(loopTimeMs);
}
@Override
protected void process() {
try {
handleEvent(queuedEvents.takeFirst());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void handleEvent(ScriptEventType eventType) {
var toRun = events.parallelStream().filter(e -> e.config.eventType == eventType).findFirst().orElse(null);
if (toRun != null) {
try {
toRun.run();
} catch (IOException e) {
System.err.printf("Failed to run script for event: %s, exception below.\n%s\n", eventType.name(), e.getMessage());
}
}
}
}
protected static class ScriptConfigManager {
protected static final Path scriptConfigPath = Paths.get(ConfigManager.SettingsPath.toString(), "scripts.json");
private ScriptConfigManager() {}
static boolean fileExists() { return Files.exists(scriptConfigPath); }
public static void initialize() {
if (!fileExists()) {
List<ScriptConfig> eventsConfig = new ArrayList<>();
for (var eventType : ScriptEventType.values()) {
eventsConfig.add(new ScriptConfig(eventType));
}
try {
JacksonHelper.serializer(scriptConfigPath, eventsConfig.toArray(new ScriptConfig[0]));
} catch (IOException e) {
e.printStackTrace();
}
}
}
static List<ScriptConfig> loadConfig() {
try {
var raw = JacksonHelper.deserializer(scriptConfigPath, ScriptConfig[].class);
if (raw != null) {
return List.of(raw);
}
} catch (IOException e) {
e.printStackTrace();
}
return new ArrayList<>();
}
protected static void deleteConfig() {
try {
Files.delete(scriptConfigPath);
} catch (IOException e) {
//
}
}
}
public static void queueEvent(ScriptEventType eventType) {
if (!Platform.getCurrentPlatform().isWindows()) {
try {
queuedEvents.putLast(eventType);
Debug.printInfo("Queued event: " + eventType.name());
} catch (InterruptedException e) {
System.err.println("Failed to add event to queue: " + eventType.name());
}
}
}
}

View File

@@ -23,7 +23,7 @@ public enum Platform {
private static final String OS_ARCH = System.getProperty("os.arch");
public static final Platform CurrentPlatform = getCurrentPlatform();
public boolean isWindows() {
public boolean isWindows() {
return this == WINDOWS_64;
}
@@ -35,6 +35,10 @@ public enum Platform {
return this == MACOS_64;
}
public static boolean isRaspberryPi() {
return CurrentPlatform.equals(LINUX_RASPBIAN);
}
private static ShellExec shell = new ShellExec(true, false);
public boolean isRoot() {

View File

@@ -24,19 +24,16 @@ public class ProgramDirectoryUtilities
{
if (runningFromJAR())
{
if (Platform.isRaspberryPi()) {
return "/boot/chameleon-vision";
}
return getCurrentJARDirectory();
} else
{
return System.getProperty("user.dir");
// return getCurrentProjectDirectory();
}
}
private static String getCurrentProjectDirectory()
{
return new File("").getAbsolutePath();
}
private static String getCurrentJARDirectory()
{
try

View File

@@ -4,6 +4,8 @@ import com.chameleonvision.Debug;
import com.chameleonvision.config.CameraCalibrationConfig;
import com.chameleonvision.config.CameraConfig;
import com.chameleonvision.config.ConfigManager;
import com.chameleonvision.scripting.ScriptEventType;
import com.chameleonvision.scripting.ScriptManager;
import com.chameleonvision.config.FullCameraConfiguration;
import com.chameleonvision.util.LoopingRunnable;
import com.chameleonvision.util.MathHandler;
@@ -137,6 +139,7 @@ public class VisionProcess {
public void setDriverMode(boolean driverMode) {
pipelineManager.setDriverMode(driverMode);
ScriptManager.queueEvent(driverMode ? ScriptEventType.kEnterDriverMode : ScriptEventType.kExitDriverMode);
SocketHandler.sendFullSettings();
}