Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.