Table of Contents

Startup Trigger (Host + RecipeSend UI)

What is this script?

This is a custom StartupTrigger for TransSECS Host projects. It performs two key tasks:

The script includes a Swing-based popup window for entering a PPID and choosing a file.

Key Features

How It Works

At startup, a background thread is launched:

Once the host is found:

When the host reports status = 0 (Connected):

If the host never becomes available:

Benefits

Full Code

// === Imports ===
var TransSecsController      = Java.type("com.ergotech.transsecs.secs.TransSecsController");
var SecsFormat10             = Java.type("com.ergotech.secs.SecsFormat10");
var ConnectionStatusListener = Java.type("com.ergotech.transsecs.secs.host.ConnectionStatusListener"); 
var DataSource               = Java.type("com.ergotech.vib.servers.DataSource");
 
var UIManager          = Java.type("javax.swing.UIManager");
var SwingUtilities     = Java.type("javax.swing.SwingUtilities");
var JFrame             = Java.type("javax.swing.JFrame");
var WindowConstants    = Java.type("javax.swing.WindowConstants");
var WindowAdapter      = Java.type("java.awt.event.WindowAdapter");
var GridBagLayout      = Java.type("java.awt.GridBagLayout");
var GridBagConstraints = Java.type("java.awt.GridBagConstraints");
var Insets             = Java.type("java.awt.Insets");
var FlowLayout         = Java.type("java.awt.FlowLayout");
var JButton            = Java.type("javax.swing.JButton");
var JLabel             = Java.type("javax.swing.JLabel");
var JTextField         = Java.type("javax.swing.JTextField");
var JOptionPane        = Java.type("javax.swing.JOptionPane");
var JFileChooser       = Java.type("javax.swing.JFileChooser");
 
var Files             = Java.type("java.nio.file.Files");
var CompletableFuture = Java.type("java.util.concurrent.CompletableFuture");
var HashMap           = Java.type("java.util.HashMap");
var Thread            = Java.type("java.lang.Thread");
var RuntimeException  = Java.type("java.lang.RuntimeException");
 
// We'll store our singleton UI+state under this key.
var KEY = "TransSECS.ScriptParamWindow";
 
// --- Singleton state accessor ---
function getState() {
    var state = UIManager.get(KEY);
    if (state != null) return state;
 
    state = new HashMap(); // { frame, ppidField, fileField, fileBytes, pending }
 
    var frame = new JFrame("TransSECS – PPID + File");
    frame.setLayout(new GridBagLayout());
    frame.setSize(560, 180);
    frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
 
    var ppidField = new JTextField();
    var fileField = new JTextField();
    fileField.setEditable(false);
 
    var chooseBtn = new JButton("Choose File…");
    var okBtn     = new JButton("OK");
    var cancelBtn = new JButton("Cancel");
 
    var c = new GridBagConstraints();
    c.insets = new Insets(6,6,6,6);
    c.fill   = GridBagConstraints.HORIZONTAL;
 
    c.weightx = 0; c.gridx = 0; c.gridy = 0; frame.add(new JLabel("PPID:"), c);
    c.weightx = 1; c.gridx = 1; c.gridy = 0; frame.add(ppidField, c);
 
    c.weightx = 0; c.gridx = 0; c.gridy = 1; frame.add(new JLabel("File:"), c);
    c.weightx = 1; c.gridx = 1; c.gridy = 1; frame.add(fileField, c);
    c.weightx = 0; c.gridx = 2; c.gridy = 1; frame.add(chooseBtn, c);
 
    var JPanel = Java.type("javax.swing.JPanel");
    var buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
    buttonPanel.add(cancelBtn); buttonPanel.add(okBtn);
    c.gridwidth = 3; c.gridx = 0; c.gridy = 2; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.EAST;
    frame.add(buttonPanel, c);
 
    // --- Handlers ---
    chooseBtn.addActionListener(function(e) {
        var fc = new JFileChooser();
        if (fc.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
            try {
                var bytes = Files.readAllBytes(fc.getSelectedFile().toPath());
                state.put("fileBytes", bytes);
                fileField.setText(fc.getSelectedFile().getAbsolutePath());
            } catch (ex) {
                JOptionPane.showMessageDialog(frame, "Failed to read file: " + ex.getMessage(),
                                              "Error", JOptionPane.ERROR_MESSAGE);
            }
        }
    });
 
    okBtn.addActionListener(function(e) {
        var pending = state.get("pending");
        if (pending != null && !pending.isDone()) {
            var ppid = String(ppidField.getText() || "");
            var body = state.get("fileBytes");
            if (ppid.isEmpty()) {
                JOptionPane.showMessageDialog(frame, "Please enter a PPID.", "Missing PPID",
                                              JOptionPane.WARNING_MESSAGE);
                return;
            }
            if (body == null || body.length == 0) {
                JOptionPane.showMessageDialog(frame, "Please choose a file.", "Missing File",
                                              JOptionPane.WARNING_MESSAGE);
                return;
            }
            var selection = new HashMap();
            selection.put("ppid", ppid);
            selection.put("body", body);
            pending.complete(selection);
        }
    });
 
    function hideAndCancel(reason) {
        try { frame.setVisible(false); } catch (ignore) {}
        var pending = state.get("pending");
        if (pending != null && !pending.isDone()) {
            pending.completeExceptionally(new RuntimeException(reason || "Cancelled"));
        }
    }
 
    cancelBtn.addActionListener(function(e) { hideAndCancel("Cancelled by user"); });
    frame.addWindowListener(new WindowAdapter() {
        windowClosing: function(evt) { hideAndCancel("Window closed"); }
    });
 
    frame.setLocationByPlatform(true);
 
    state.put("frame", frame);
    state.put("ppidField", ppidField);
    state.put("fileField", fileField);
    UIManager.put(KEY, state);
    return state;
}
 
// Show non-modal popup and return a CompletableFuture that completes on OK/Cancel
function requestParams() {
    var state  = getState();
    var frame  = state.get("frame");
    var future = new CompletableFuture();
    state.put("pending", future);
 
    SwingUtilities.invokeLater(function () {
        if (!frame.isVisible()) frame.setVisible(true);
        frame.toFront();
        frame.requestFocus();
    });
 
    return future;
}
 
// --- Startup Trigger Main Loop ---
var r = new java.lang.Runnable() {    //Defines a background thread that will execute the startup logic.
    run: function() {
        var host = null;
        var count = 0;
        while (host == null) {        //Polls the system every 50 ms to see if the TransSecsController (the Host object) has been created.
            Thread.sleep(50);
            count++;                  // count and timeout check. Increments a counter each loop. After 100 iterations (≈ 5 seconds), stops waiting and logs a message that no host was found. 
            host = TransSecsController.getSingleController();
            if (host == null && count % 10 == 0) {       //Every 10 loops (≈ 0.5 seconds), logs "Still waiting for host." for debugging/monitoring purposes.
                DataSource.logMessage("Startup Trigger: Still waiting for host.");
            }
            if (count >= 100) {
                DataSource.logMessage("Startup Trigger: Host did not connect after ~5 seconds. Stopping wait.");
                break;
            }
        }
 
        if (host != null) {    //Once the host is found, it logs success and waits another 1 second to ensure the host is fully initialized.
            DataSource.logMessage("Startup Trigger: Host model is ready.");
            Thread.sleep(1000);
            host.addConnectionStatusListener(new ConnectionStatusListener() {   //Subscribes to connection status changes. When the status changes to 0 (connected), it triggers the UI builder (buildUI(host)) in the Swing UI thread.
                connectionStatusChanged: function(connectionStatus, comment) {
                    if (connectionStatus == 0) { // Connected
                        SwingUtilities.invokeLater(function() {         //Ensures that the UI is built safely in the event dispatch thread, preventing concurrency issues.
                            buildUI(host);
                        });
                    }
                }
            });
            DataSource.logMessage("Startup Trigger: Connection Status Listener added.");
        } else {
            DataSource.logMessage("Startup Trigger: No host available after timeout. UI will not be shown.");
        }
    }
};
 
new Thread(r).start();   //Starts the thread to run this logic asynchronously so it does not block other operations.