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 controlIn Options: 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 syntaxCommands 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: 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.help → STate (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 .idAny 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 interfaceStructured 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: 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: 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 retrievalErrors are reported on two independent fields: 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) .daemon (.d) .experiment (.e) .spectrometer (.spec) 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 examplesSymbol ! 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 formatsTrace 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. |