Operating a running engine
The engine commands operate a running engine through its http connector and print JSON, so
a checklist or a plant-side agent needs no scripts of its own. Three ways to the same
operations:
taktwerk engine status --url 127.0.0.1:8480 # any engine with --http
taktwerk agent engine plant status --root /opt/taktwerk # on the device: the bundle's env
taktwerk device engine line-1 plant status # from a computer, over SSH
agent engine reads the bundle’s address and token from its env file; device engine runs it
over SSH, so neither needs a port forward or the token on the computer. taktwerk engine takes
--url and, for writes, --token-file, TAKTWERK_HTTP_TOKEN or the token file the engine
wrote. Reads need no token.
Status for a checklist. status reads every signal once and watches the heartbeat for up
to --heartbeat-timeout seconds. It passes when the status is running (1), the heartbeat
advances and every --expect holds; it exits 1 otherwise, and the report’s failed says why.
taktwerk device engine line-1 plant status \
--expect 'taktwerk.overruns == 0' --expect 'ctl.engaged == 1' --expect 'est.status & 514 == 0'
{
"pass": true, "failed": [], "state": "running", "status": 1, "cycle": 452,
"heartbeat": 451, "heartbeat_advancing": true, "overruns": 0, "stale": 3,
"stale_inputs": [],
"recorders": {"culture": {"rows_written": 45, "rows_dropped": 0, "gaps": 0, "buffer_fill": 0.0, "...": 0}},
"connectors": {"plc": {"gate_open": true, "gate_opens": 1, "gate_closes": 0, "gate_held": 0}},
"checks": [{"check": "taktwerk.overruns == 0", "value": 0.0, "pass": true}],
"recording": {"engine_run": "…", "tick_s": 1.0, "start_time": 0.0, "recorders": ["culture"]},
"taktwerk": "0.9.0", "t_ms": 1761735600000
}
A check is <signal>[<i>] [& <mask>] <op> <number> with >, >=, <, <=, ==, !=, or a
bare signal for “not zero”; & <mask> (decimal or 0x…) tests bits of an integer. stale is
the engine’s count of skipped ticks, stale_inputs the inputs older than their max_age now.
recorders and connectors hold every <prefix>.recorder.<id>.* and
<prefix>.connector.<id>.* system signal.
Read, statistics, watch. read [<signal or glob>…] prints one sample: cycle, t_ms and
the values and age_s by name. read --for <seconds> samples over a window, one sample per
new cycle polled every --every seconds (default: the connector’s sample period), and prints
per signal count, mean, sd (sample standard deviation), min and max, arrays per
element, with the window’s first and last cycle and wall time: a calibration capture.
watch [<signal or glob>…] prints one JSON line per new cycle until --for, --count or
SIGINT (on the device or with taktwerk engine; device engine refuses it, use read --for).
Writes read back. set <signal> <value> [--index i] writes an input or tunable; the value
is JSON (0.5, [1, 2], true, "text"), else text, and --index changes one element of an
array. Without --yes it prints the plan with the current value. With --yes it writes, waits
until the engine published the tick that sees the value, and reports read_back, matches
and, for a tunable of an engine that records, the event and its events.csv line:
{"signal": "ctl.x_ref", "direction": "tunable", "before": 0.004, "value": 0.005,
"applied": true, "read_back": 0.005, "matches": true, "cycle": 384,
"event": {"seq": 96, "cycle": 383, "kind": "tunable", "source": "ctl.x_ref", "detail": "0.004 -> 0.005 by http"},
"events_csv": "383,383.0,tunable,ctl.x_ref,0.004 -> 0.005 by http", "plan": ["…"]}
A refused write reports the error and, for a tunable, its refused … event. set exits 1
unless the value is read back; --yes is refused when CI is set.
Start at a set time. device start <device> <bundle> --at 11:15 --yes (or --in 15m)
arms taktwerk-start@<bundle>.timer on the device, a transient systemd timer that starts the
engine unit once. The computer turns --at into a delay by its own clock, and the device
counts that delay on its monotonic clock, so a wrong device clock does not move the start.
Arming again replaces the armed start; --cancel removes it; --project selects the project
at once. It is refused while the engine runs, and a transient timer does not survive a reboot.
On the device the same is agent start-at <bundle> --in 15m --yes (or --at, by the device’s
clock), through root.