Hardware in the loop
A PLC program, or any controller with an OPC UA server, is tested against a simulated plant before it meets the real one: taktwerk runs the plant model in real time and exchanges the signals the program would get from its I/O.
flowchart LR
subgraph tw[taktwerk, real time]
PM[plant model] <--> PI[(image)]
end
PI -- "sensor values<br/>written" --> PLC[PLC program under test]
PLC -- "actuator values<br/>read" --> PI
PI -- "recorder @io" --> R[records]
[engine]
tick_ms = 50.0
input_max_age_ms = 500.0
[engine.realtime]
policy = "fifo"
priority = 70
lock_memory = true
[models.plant]
kind = "fmi"
path = "packages/plant.fmu"
[[instance]]
id = "plant"
model = "plant"
inputs = { valve = "plc.valve", pump = "plc.pump" }
[[connector]]
id = "plc"
kind = "opcua-client"
endpoint = "opc.tcp://10.0.0.10:4840"
writes = "on"
sync_period_ms = 50
[connector.map]
"plc.valve" = "ns=4;s=Line.valve_cmd" # read: what the program commands
"plc.pump" = "ns=4;s=Line.pump_cmd"
"plant.level" = "ns=4;s=Line.level_sim" # written: what its sensor would read
"plant.temp" = "ns=4;s=Line.temp_sim"
"taktwerk.heartbeat" = "ns=4;s=Line.sim_heartbeat"
[[recorder]]
id = "hil"
signals = ["@io"]
- Timing: the plant steps on absolute deadlines;
[engine.realtime]with a dedicated CPU keeps the jitter low (Real time).taktwerk.overrunscounts missed deadlines, the run summary prints the longest tick. Anopcua-clientreads everysync_period_msand writes once per published tick. - Lost connection: the actuator inputs age; with
input_max_age_msthe engine skips ticks and holds the plant until reads resume, instead of simulating on stale commands. - Test cases: a scenario file runs in real time too:
set,addandrampdrive the plant model’s tunables and external inputs (a disturbance, a load change), timed to the run. Faults change what models read, not what a connector writes, so a sensor fault the PLC should see is a tunable or input of the plant model. - Evidence:
@iorecords every input as the PLC wrote it and every output each tick, with the scenario inrun.json.taktwerk plc-mirrorgenerates a PLC program and the matching map when the program should see more of the engine’s signals. - Commissioning the test bench:
check --liveverifies every mapped node before the first tick (Commissioning).