Skip to content

Add optional remote scripting server (Tools -> Remote Scripting...)#105

Open
thomdehoog wants to merge 2 commits into
mesoSPIM:release/candidate-py312from
thomdehoog:remote-scripting-py312
Open

Add optional remote scripting server (Tools -> Remote Scripting...)#105
thomdehoog wants to merge 2 commits into
mesoSPIM:release/candidate-py312from
thomdehoog:remote-scripting-py312

Conversation

@thomdehoog

Copy link
Copy Markdown

What this adds

mesoSPIM-control has no external control interface — everything runs in-process in the Qt event loop. The Script Window already runs Python in the live Core (self == Core); this PR makes that reachable from another process over a localhost socket, so external tools can script the microscope with the full mesoSPIM Python API. Text in, console text out — no command vocabulary, no data format.

Why it's this small

It reuses the existing, unmodified Core.execute_script. Everything opinionated (a command set, structured results) lives in the client, injected as a script — so there's nothing new for mesoSPIM to maintain.

Files (3 files, +480)

  • mesoSPIM_RemoteScripting.py (new): a signal-driven QTcpServer. For each received script it runs Core.execute_script and returns its console output, captured per-thread so it never swaps the process-wide stdout/stderr out from under other threads. Length-framed UTF-8; framing + the token gate are socket-free helpers (frame / FrameDecoder / AuthGate) with Qt-free unit tests; constant-time token compare over UTF-8 bytes. A new client preempts a stale one, and a dropped/crashed client's teardown is guarded, so a misbehaving client can neither wedge nor crash the server.
  • mesoSPIM_Core.py (+37): start_remote_scripting(host, port, token) / stop_remote_scripting() slots, invoked via a queued connection so the server lives on the Core thread (where execute_script runs), plus a sig_remote_scripting_started(ok, message) signal so a bind failure is reported instead of shown as a false "running".
  • mesoSPIM_MainWindow.py (+131): a Tools → Remote Scripting… menu entry with a Start/Stop dialog (host / port / token + generator); reflects the real start outcome; stops on app close.

Security — this is remote code execution, handled deliberately

  • Opt-in: the menu appears only when the config sets enable_remote_scripting = True — an unmodified install cannot start it.
  • Off by default even then; started by an operator from the GUI.
  • Secure by default when started: binds 127.0.0.1, the dialog pre-fills a fresh token, and warns before any network bind without a token.
  • Plain TCP — the token gates casual/accidental LAN access, it is not sniffer-proof (tunnel over SSH/VPN for untrusted networks). TLS is intentionally out of scope for this minimal PR.

Validation

Ran the real app in -D demo mode (all Demo backends, offscreen Qt on Windows) on both v1.20.0 and this release/candidate-py312 branch: a client authenticates (incl. a non-ASCII token), reads state, drives the demo stage (self.move_absolute), runs an acquisition that writes a file, gets structured output via print(json.dumps(...)), and sees a raising script's traceback returned as text (no crash). execute_script is unchanged, so there's no behavior change for existing users.

Happy to adjust the naming, add a config-file defaults block, or a CHANGELOG / docs entry as you prefer.

mesoSPIM-control has no external control interface: everything runs in-process
inside the Qt event loop. The Script Window already runs Python in the live Core
context (self == Core); this makes that reachable from another process over a
socket, so external tools can script the microscope with the full mesoSPIM Python
API. Text in, console text out -- no command vocabulary, no data format.

- mesoSPIM_RemoteScripting.py (new): a signal-driven QTcpServer that, for each
  received script, runs the EXISTING Core.execute_script and returns its captured
  console output. Length-framed UTF-8. Optional shared token (first frame,
  constant-time compare over UTF-8 bytes) gates access. A script's console is
  captured PER-THREAD, so its output is returned without swapping the process-wide
  stdout/stderr out from under the other threads. A dropped/crashed client cannot
  crash mesoSPIM (the disconnect teardown is guarded against an already-reclaimed
  socket).
- mesoSPIM_Core: start_remote_scripting(host, port, token) / stop slots, invoked
  via a queued connection so the server lives on the Core's thread (where
  execute_script runs), plus a sig_remote_scripting_started(ok, message) signal so
  a bind failure is reported rather than shown as a false "running".
- mesoSPIM_MainWindow: a "Tools -> Remote Scripting..." menu entry (added in code,
  no .ui change) with a Start/Stop dialog (host/port/token + generator); reflects
  the real start outcome; stops on app close.

OFF BY DEFAULT and secure-by-default: the server never runs until an operator
starts it from Tools -> Remote Scripting...; the dialog binds 127.0.0.1, pre-fills a
fresh token, and warns before any network bind without a token. A script is
arbitrary Python on the acquisition PC; this is plain TCP, so the token gates
casual LAN access, it is not sniffer-proof (tunnel it for untrusted networks).

Validated against -D demo mode (both v1.20.0 and release/candidate-py312): started
from the GUI signal path, a client authenticates, reads state, drives the demo
stage, gets structured output via print(json), runs an acquisition, and sees a
raising script's traceback as text (no crash). execute_script is reused unmodified.
@thomdehoog
thomdehoog force-pushed the remote-scripting-py312 branch from 8d931c1 to b8eff6c Compare July 3, 2026 12:13
thomdehoog added a commit to thomdehoog/ZMART-microscopy that referenced this pull request Jul 6, 2026
PR #105 (mesoSPIM/mesoSPIM-control) picked up a second, purely additive commit
after this branch last touched pull_request/: Add remote scripting full demo
test, adding mesoSPIM/scripts/remote_scripting_full_test.py (1121 lines). It
does not change the 3 core files from the first commit.

- Add 0002-Add-remote-scripting-full-demo-test.patch alongside the existing
  0001 patch (same git format-patch convention).
- README/TODO: the PR is open at
  mesoSPIM/mesoSPIM-control#105 -- drop the stale
  not-yet-submitted wording and note the second patch is optional.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant