From b8a6110763a69c4268af48f5b32e5afd5ea46a65 Mon Sep 17 00:00:00 2001 From: unshrawal Date: Sun, 21 Sep 2025 15:26:03 -0500 Subject: [PATCH] Integrate RTL_FM with QSSTV, and disallow race conditions between APRS and SSTV --- .../org/ariss/star/MissionController.java | 77 ++++++++++++++++++- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/MissionControllerUI/src/main/java/org/ariss/star/MissionController.java b/MissionControllerUI/src/main/java/org/ariss/star/MissionController.java index 0b46b9c..71ec9f1 100644 --- a/MissionControllerUI/src/main/java/org/ariss/star/MissionController.java +++ b/MissionControllerUI/src/main/java/org/ariss/star/MissionController.java @@ -124,22 +124,81 @@ public class MissionController { static ConfigManager configManager; private WritableImage sstvWritable; private Process qsstv; + private Process rtlProcess; // process for rtl_fm -> aplay pipeline + private Thread qsstvWatcherThread; @FXML protected void qsstvCheckboxClicked(MouseEvent event) { try{ - if (qsstv_checkbox.isSelected()) { + boolean enable = qsstv_checkbox.isSelected(); + + if (enable) { + // If APRS receive is running, refuse to start QSSTV and ask the user to stop APRS first + if (recAPRSCheckBox.isSelected()){ + AlertBox.display("APRS receive is running — stop it before starting QSSTV"); + Platform.runLater(() -> qsstv_checkbox.setSelected(false)); + return; + } + + // QSSTV is Linux-only; refuse on Windows + if (System.getProperty("os.name").toLowerCase().contains("win")){ + AlertBox.display("QSSTV is only available on Linux"); + Platform.runLater(() -> qsstv_checkbox.setSelected(false)); + return; + } + + // disable the APRS checkbox while QSSTV runs to prevent races + Platform.runLater(() -> recAPRSCheckBox.setDisable(true)); + qsstv = new ProcessBuilder("./qsstv").start(); + + // start rtl_fm -> aplay pipeline via shell so piping works + rtlProcess = new ProcessBuilder("sh", "-c", + "rtl_fm -M fm -f 434.9M -s 48k | aplay -r 48000 -f S16_LE -D pulse") + .start(); + + // Watch the qsstv process and cleanup when it exits + qsstvWatcherThread = new Thread(() -> { + try { + if (qsstv != null) qsstv.waitFor(); + } catch (InterruptedException ignored) {} + + // ensure rtl process is stopped + if (rtlProcess != null && rtlProcess.isAlive()){ + rtlProcess.destroy(); + } + + Platform.runLater(() -> { + qsstv_checkbox.setSelected(false); + recAPRSCheckBox.setDisable(false); + }); + + qsstv = null; + rtlProcess = null; + qsstvWatcherThread = null; + }); + qsstvWatcherThread.setDaemon(true); + qsstvWatcherThread.start(); } else { - if (qsstv != null && qsstv.isAlive()) { + // stop QSSTV and rtl pipeline + if (qsstv != null && qsstv.isAlive()){ qsstv.destroy(); qsstv = null; } + + if (rtlProcess != null && rtlProcess.isAlive()){ + rtlProcess.destroy(); + rtlProcess = null; + } + + // Re-enable APRS checkbox so user can restart receive + Platform.runLater(() -> recAPRSCheckBox.setDisable(false)); } } catch (IOException e) { AlertBox.display("Failed to start or stop QSSTV"); + Platform.runLater(() -> recAPRSCheckBox.setDisable(false)); } } @@ -616,12 +675,22 @@ protected Task createTask() { }); recAPRSCheckBox.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { - if(!pairingStatus && !recAPRSCheckBox.isSelected()){ + boolean willEnable = !recAPRSCheckBox.isSelected(); + + // Prevent starting APRS receive while QSSTV (and rtl pipeline) is running + if (willEnable && qsstv != null && qsstv.isAlive()){ + event.consume(); + AlertBox.display("QSSTV is running — stop QSSTV before starting APRS receive"); + return; + } + + if(!pairingStatus && willEnable){ event.consume(); AlertBox.display("Robot must be paired"); return; } - recAPRSCheckBox.setSelected(!recAPRSCheckBox.isSelected()); + + recAPRSCheckBox.setSelected(willEnable); receive(); event.consume(); });