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

Status and the write gate

The engine knows whether it runs: taktwerk.status, the heartbeat, stale inputs. It does not know whether a model’s outputs make sense. A package says so in a status output, and the project’s write gate holds the outputs a PLC or a plant data system would otherwise take.

A status output

One integer scalar output, conventionally status: 0 when every output is valid, else one bit per reason. An integer output of any width works for a gate; u32 maps to an OPC UA UInt32 and a PLC’s UDINT. The engine interprets none of the bits; the project picks the ones that hold writes.

The recommended layout keeps the bits that make outputs unusable apart from the ones that only inform:

bitsvaluemeaningoutputs
01not ready: initialising, too few measurements so farnot valid
12an input not plausible: out of the model’s range, not finitenot valid
24the computation failed this step (no convergence, singular data); outputs heldnot valid
3 to 78 to 128further reasons of the package’s own why outputs are not validnot valid
8256an output limited to its boundsvalid
9512degraded: running on a fallback, a reduced model, fewer measurementsvalid
10 to 151024 to 32768further warnings of the package’s ownvalid

So status & 255 is zero when the outputs may be written, and a gate closes on mask = 255; a project that also refuses degraded results adds 512. Document every bit the package uses in its variable’s description or the package’s own documentation.

A step that sets a bit returns success. A non-success return is a fault: it stops the engine, or with on_error = "hold" or "safe" takes the instance down and holds the heartbeat (Faults), which is right for a bug and wrong for a bad measurement.

The gate

[[connector]]
id = "plc"
kind = "opcua-client"
endpoint = "opc.tcp://10.0.0.10:4840"
writes = "on"

[connector.gate]
open_after = 5                               # good ticks in a row before writes resume
conditions = [{ signal = "est.status", mask = 255 }]

[connector.map]
"est.x_hat" = { node = "ns=4;s=Line.x_hat", min = 0.0, max = 50.0 }
"est.status" = { node = "ns=4;s=Line.est_status", gated = false }
"taktwerk.heartbeat" = "ns=4;s=Line.heartbeat"
"taktwerk.status" = "ns=4;s=Line.taktwerk_status"
  • The gate is closed while the engine is not running or the heartbeat did not advance, while a condition holds, or while a gated output is not finite or outside its mapping’s min and max. It opens after open_after good ticks in a row.
  • A closed gate writes none of the gated outputs, so the target keeps their last values. The heartbeat and taktwerk.status are always written; map the status output with gated = false too, so the PLC sees why.
  • Every change is a gate event (open, closed: <reason>) in events.csv, and <prefix>.connector.<id>.gate_open, gate_opens, gate_closes and gate_held count it.
  • The rest connector takes the same gate table.

The PLC still owns the safe state: it watches the heartbeat and taktwerk.status, and decides what a held value means for the plant (see the safety contract).

Checks from outside

taktwerk engine status --expect 'est.status & 255 == 0' passes only while the outputs are valid, so a checklist or a monitoring job can test the same bits (Operating a running engine).