SpecMan4EPR can be driven from any external program over a plain TCP/IP text connection. This is the recommended way to integrate SpecMan4EPR into an automation framework, a measurement robot, or a higher-level orchestration script. The interface is deterministic: every command produces a correlated, machine-parsable response, and long-running operations report both when they start and when they finish — without blocking the connection.

This interface received a major update in version 3.8.0.

1. Enabling remote control

In Options:

  • Select Network → Communication port (e.g. 8023)
  • Add the remote host IP to Network → List of allowed hosts (e.g. localhost)
  • Specify the buffer directory in Directories and files → Data buffer directory

    Use any TCP/IP terminal software. For Windows users the TCPCheck app is provided on request. The connection is line-oriented: terminate every command with LF (0x0A). CR (0x0D) and CRLF are also accepted.

    2. Command syntax

    Commands address a tree of objects, written as a dotted path that starts with the top-level object and drills down to a property or action:

    .<object>.<sub-object>.<property> [= value]

    Reading omits the assignment; writing supplies = value. String values are single-quoted. For each command the response carries the current value of the addressed property, named by its full path, e.g.

    .d.state            ! read  →  .daemon.state=4
    .s.echo = 0         ! write →  .server.echo=0
    

    The top-level objects are:

  • .s — .server : connection, files, data transfer, JSON engine
  • .d — .daemon : execution control (run / tune / stop), progress
  • .e — .experiment : the loaded experiment, its axes and processing
  • .spec — .spectrometer : device configuration

    Append .help (or .h) to any object to list its commands, e.g. .s.help, .d.help, .e.help. Append it to a command instead to describe that one command, e.g. .spec.ADC.state.helpSTate (ro, int): device state: 0 disabled, 1 comError, .... Commands accept the usual abbreviation (the capitalised letters of the listed name): STate takes ST or STATE, so .spec.ADC.st and .spec.ADC.st.help work too. For a device, each configuration key carries its own text: .spec.ADC.cfg.<key>.help.

    Help and text replies (help, info, description, history) are returned on a single line: multi-line content is collapsed with the literal separator \n, which the client splits to display. When the content will not fit the single-quoted ='...' form (it contains a '), the reply is delivered instead as JSON on a .json leaf — a command listing becomes .<path>.help.json='[{"name":..,"access":..,"type":..,"help":..}, ...]', and free text becomes a JSON string on .<path>.info.json (any single quote inside is unicode-escaped, so the value never breaks the wire framing). Clients should parse the value as JSON whenever the leaf ends in .json.

    3. Request correlation with .id

    Any command may be prefixed with .id<number>. The response then echoes the same id, so an automation client can match replies to requests even when responses arrive out of order or asynchronously (see the deterministic protocol below). The id is reflected in upper case:

    .id55.d.state       →   .ID55.daemon.state=4
    .id7.d.error        →   .ID7.daemon.error='...'
    

    Using a unique id per command is strongly recommended for unattended automation: it is the only reliable way to associate a delayed completion event (below) with the command that started it.

    4. The deterministic JSON command interface

    Structured operations are issued through a single entry point that accepts a JSON payload. Because JSON uses double quotes, wrap the payload in single quotes:

    .s.json = '{"cmd":"<name>", ...}'

    Every JSON command issued with an .id prefix produces a correlated reply whose leaf token encodes the outcome:

  • ack — command received and started (long-running operations only; immediate)
  • nack — rejected: malformed, busy, or a missing/invalid file (immediate)
  • ok — completed successfully
  • error — failed during or after execution (delayed)

    Short commands resolve in one cycle and reply with ok or nack. Long-running commands (run, tune, batch) reply ack immediately, leaving the connection free, then later emit ok or error when the daemon settles. A rejected detail rides in the value:

    .id1.s.json = '{"cmd":"loadTemplate","file":"\\Demo\\Demo 2\\test_WURST.tpl"}'
       →   .ID1.server.ok
    
    .id2.s.json = '{"cmd":"loadTemplate","file":"missing.tpl"}'
       →   .ID2.server.nack='experiment template does not exist'
    
    .id3.s.json = '{"cmd":"run"}'
       →   .ID3.server.ack             ! accepted, executing
       ...                                  ! connection stays free
       →   .ID3.server.ok              ! daemon returned to idle
    

    Important — escape backslashes inside JSON. Backslash is the JSON escape character, so a Windows path must double each one ("\\Demo\\x.tpl") or use forward slashes ("/Demo/x.tpl"). A bare "\Demo" is invalid JSON (\D is not a valid escape) and the command is rejected with nack='JSON parse failed'. This applies only to the JSON interface; the plain .s.open='\Demo\x.tpl' form takes the path verbatim and needs no escaping.

    JSON commands:

  • loadTemplate {"file":"<path>"[,"fld":<n>]} — load a template/experiment. Missing or unparseable files reply nack and put SpecMan into the Repair state. "fld" is an optional directory selector (default: template folder).
  • revertTemplate {} — reload the daemon's stored experiment copy together with its data.
  • saveTemplate {"file":"<name>"} — save the current experiment as a template.
  • setParArray {"par":"<name>","str":"1 MHz to 10 MHz"} — set a parameter from an ASCII formula; or {"par":"...","b64":"<base64 float array>"} to set it directly from a binary array.
  • getParArray {"par":"<name>"} — return a parameter's array as base64 (reply cmd "parArrayData").
  • run | tune | batch {} — start the corresponding long-running mode (ack → ok/error).
  • getError {} — return the cause of the last Repair transition in the reply value.
  • status {} — return a consolidated status snapshot as a single JSON object in the reply value (see below). One poll replaces separate state/error queries.

    The status reply is a JSON object covering every top-level subsystem, so a single poll answers "is it busy, is it valid, did anything fail":

    .id7.s.json = '{"cmd":"status"}'
       →   .ID7.server.ok='{"cmd":"status","ok":true,
                  "spectrometer":{"state":"ready"},
                  "daemon":{"id":"...","class":"...","state":"idle","action":"idle","busy":false,"error":""},
                  "experiment":{"name":"test_WURST","valid":true,"error":""},
                  "saveQueue":{"busy":false},
                  "lastConsoleError":"",
                  "plugins":[{"id":"...","state":"idle","error":""}],
                  "errors":[]}'
    

    Top-level ok is false whenever any subsystem reports an error; errors is the flattened list of every non-empty error message across the spectrometer, daemon, experiment, console, and plugins — one place to look for a health check.

    Unlike most JSON commands, status always answers even without an .id prefix: a bare .s.json = '{"cmd":"status"}' returns the same object on an uncorrelated .server.status='{...}' leaf, so an interactive poll is never silently dropped. Network clients receive it as a tcpJSONCommand reply (cmd "status").

    5. The Repair state and error retrieval

    Errors are reported on two independent fields:

  • .d.error (reply leaf .daemon.error) — the daemon error: the cause of the last error ("Repair") transition. Set whenever an operation invalidates the experiment — a missing or unparseable template, a hardware fault during execution, a critical stop — which also drops the daemon into the error state (daemon.state=0).
  • .s.error (reply leaf .server.error) — the server error: the most recent console/parser error (e.g. a malformed command line). It is independent of the daemon state and is empty when the last command parsed cleanly.

    The daemon (Repair) cause can be retrieved at any time, by console or JSON:

    .id9.d.error                                   ! console getter (daemon error)
       →   .ID9.daemon.error='experiment template does not exist: \tpl\x.tpl'
    
    .id9.s.json = '{"cmd":"getError"}'             ! JSON equivalent
       →   .ID9.server.ok='experiment template does not exist: \tpl\x.tpl'
    

    A run that fails delivers the same reason in its delayed reply:

    .id3.s.json = '{"cmd":"run"}'
       →   .ID3.server.ack
       →   .ID3.server.error='Loop service code: device timeout'
    

    The stored reason persists as the "last cause" until the next failure, so it can be read after the fact. To leave the Repair state, load a valid experiment or issue .d.idle.

    6. Command reference

    .server (.s)

  • .s.echo = 0|1 — mirror each received command line to the server log (debug; IN:/OUT: lines stay local and are never sent to the CLI).
  • .s.show = 0|1 — suppress / enable unrequested responses (alias of showlog).
  • .s.message = 'text' — post a message to the SpecMan log and connected clients.
  • .s.open = '<path>' — load a template (shorthand for the loadTemplate JSON command).
  • .s.json = '{...}' — the JSON command interface (section 4).
  • .s.error / .s.err — read the last server-level error (the most recent console/parser error; empty when the last command parsed cleanly).
  • .s.opmode [= 'mode'] — read or set the spectrometer operational mode.
  • .s.tpllib — return the template library as JSON.
  • .s.fileready — 0 = no file for current .d.fguid, 1 = ready, 2 = save in progress.
  • .s.fg = '<name>' / .s.fileget — fetch a buffer file (binary; see section 8).
  • .s.traceget — fetch the current trace (binary; see section 8).
  • .s.clrbuf — clear the data buffer directory.

    .daemon (.d)

  • .d.state — execution state (see codes below).
  • .d.error — read the cause of the last error ("Repair") transition (daemon error reason; same value as the JSON getError command).
  • .d.run / .d.tune / .d.pause / .d.stop (= .d.idle) — control execution.
  • .d.v / .d.version — daemon version.
  • .d.exptime — elapsed run time (seconds; 0 when not running).
  • .d.expdone — fraction of the experiment completed (0..1).
  • .d.fguid = '<id>' — tag the next acquired file so .s.fg can fetch it (one-shot: cleared after save).
  • .d.qs.<name> [= value] — quick-set variable.
  • .d.hwreset — clear the command counter.

    .experiment (.e)

  • .e.state — experiment validity (0 = error, 1 = OK).
  • .e.name — experiment name.
  • .e.filename / .e.fname — source file.
  • .e.datastate / .e.dstate — data status.
  • .e.eax.<axis>.<field> / .e.expaxes... — experiment axes (e.g. .e.eax.P.reps = 1000).
  • .e.ppars... — stream processing parameters.

    .spectrometer (.spec)

  • .spec.dev.list / .spec.list — list configured device names.
  • .spec.suspend = '<DEV1,DEV2,...>' — user-suspend a comma-separated list of devices. Suspended devices are excluded from the "spectrometer not ready" check. The reply names the devices acted on and, after a semicolon, any names that were not found, e.g. .spec.suspend='DEV1,DEV2 suspended; not found: DEVX'.
  • .spec.resume = '<DEV1,DEV2,...>' — resume (re-initialise) a comma-separated list of devices, the equivalent of the configuration dialog's Activate button.
  • .spec.suspend — with no value, read back the devices currently user-suspended.
  • .spec.json = '{...}' — the device-configuration JSON engine (add/remove devices, etc.).

    Daemon state codes (.d.state):

    0  error (Repair)      3  idle (finished)     6  run        9  test
    1  error (terminating) 4  idle (default)      7  batch
    2  idle (terminating)  5  tune                8  pause
    

    States 0..3 are transient/error; SpecMan is "busy" in 5,6,7,8; ready in 4.

    7. Worked examples

    Symbol ! marks comments below. DO NOT POST comments to the console — it does not support them. Requests are in bold.

    Run a template (classic console control)

    .s.echo = 0      ! disable echo
    .s.show = 0      ! disable unrequested responces
    .d.v             ! get daemon version
    .s.message='SpecMan4EPR is under control'   ! here we come
    .s.open='\Demo\Demo 2\test_WURST.tpl'   ! load tpl script
    .e.eax.P.reps=1000    ! let experiment to run very long
    .e.state         ! request experiment state 0 - error / 1 - OK
    .d.state         ! request daemon state 4 - idle / 6 - run
    .d.run           ! run
    
    .d.exptime       ! request how long experiment is running
    .d.expdone       ! request fraction of experiment done
    .d.state         ! request daemon state 4 - idle / 6 - run
    
    .d.stop          ! stop SpecMan4EPR
    
    .d.state         ! request daemon state 4 - idle / 6 - run
    

    Run a template deterministically (recommended for automation)

    .s.echo = 0
    .s.show = 0
    .id1.s.json='{"cmd":"loadTemplate","file":"\\Demo\\Demo 2\\test_WURST.tpl"}'
                     ! wait for .ID1.server.ok ; .ID1.server.nack='...' means it failed
    .id2.s.json='{"cmd":"run"}'
                     ! .ID2.server.ack  = started
                     ! .ID2.server.ok   = finished      (then fetch data)
                     ! .ID2.server.error = failed; read .d.error for the cause
    

    Run a template and get data

    .s.open='\Demo\Demo 2\test_WURST.tpl'   ! load tpl script
    .d.fguid='123'   ! name the experiment we want to acquire (one-shot: cleared after save)
    .d.run           ! run
    .d.state         ! wait until .daemon.state=4
    .s.traceget      ! get current trace
    
    .s.fileready     ! 0 = no file for current .d.fguid, 1 = ready, 2 = save in progress
                     ! poll until =1 before requesting the file
    .s.fg='123.exp'  ! get file
    .s.fg='123.d01'  ! get file
    

    Handle a failure / Repair state

    .id5.s.json='{"cmd":"loadTemplate","file":"\\tpl\\does_not_exist.tpl"}'
       →   .ID5.server.nack='experiment template does not exist'
    .id6.d.error      ! read why SpecMan is in Repair
       →   .ID6.daemon.error='experiment template does not exist: \tpl\does_not_exist.tpl'
    .id7.d.idle      ! leave Repair (or load a valid template)
    

    8. Binary response formats

    Trace data (response to .s.traceget):

    .server.traceget=[i],[j],[k],[l]#[start, 4 char hex][length, 4 char hex][body binary data, length]

    File transfer (response to .s.fg / .s.fileget):

    .server.fileget=[file name]#[length, 8 char hex][body binary data, length]

    When no matching file exists the server replies .server.fileget=nullptr#00000000.