transsecs_host:startup_trigger_host_recipesend_ui
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:
- Waits for the TransSECS Host controller to become available.
- Sets up a Connection Status Listener that, once communications are established, launches a Swing-based PPID + File selection UI for recipe sending using S7F3 (RecipeSend).
The script includes a Swing-based popup window for entering a PPID and choosing a file.
Key Features
- Swing UI: Lets the user enter a PPID and select a file to send as a recipe.
- Background host discovery: Uses a separate thread to wait until the host is ready.
- In SIM mode: waits until the user toggles from SIM → LIVE.
- In deployment mode: usually connects quickly, but can be delayed if:
- The tool is still initializing.
- The tool is not reachable on the network.
- There are temporary communication issues.
- Lightweight polling: Checks every 50 ms, with logging every 500 ms during the wait.
- Connection-safe listener setup: Adds a 1-second delay before registering the listener to avoid race conditions during initialization.
- Robust logging: Provides clear feedback during wait, connection, timeout, and UI launch.
- Thread-safe UI handling: UI actions are executed via `SwingUtilities.invokeLater` to prevent Swing threading issues.
How It Works
At startup, a background thread is launched:
- Repeatedly checks `TransSecsController.getSingleController()` until it returns a valid controller, or until a 5-second timeout is reached.
- Logs progress every 10 polling cycles (~500 ms) while waiting.
- Stops waiting and logs an error message after the timeout if no host is detected.
Once the host is found:
- Waits 1 second for the host and project to stabilize.
- Registers a Connection Status Listener.
When the host reports status = 0 (Connected):
- Launches the Swing PPID + File popup UI.
- Collects the user's PPID and reads the file into a byte array.
- Sends an S7F3 RecipeSend message with the selected PPID and file contents.
If the host never becomes available:
- Logs a timeout message.
- Skips UI initialization to avoid errors.
Benefits
- Resilient in all environments: Handles delays, network issues, and tool readiness problems gracefully.
- Lightweight and efficient: Uses non-blocking polling with minimal CPU usage.
- Safe initialization: Prevents null references and avoids premature listener setup.
- User-friendly:
- Clear log messages for troubleshooting.
- Safe and simple UI for recipe selection and transmission.
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.
transsecs_host/startup_trigger_host_recipesend_ui.txt · Last modified: by amyw

