mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-22 01:11:40 +00:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user