io.toml¶
io.toml defines which I/O backend a project uses and which outputs must be
driven to a safe value on fault/watchdog handling.
Unknown fields are rejected.
Two Supported Shapes¶
[io]¶
| Key | Type | Required | Notes |
|---|---|---|---|
driver |
string | single-driver form | Use one built-in driver name plus [io.params]. |
params |
table | single-driver form | Driver-specific configuration for driver. |
drivers |
array of tables | multi-driver form | Compose multiple named drivers in one runtime. |
Single-driver form¶
[io]
driver = "loopback"
params = {}
Multi-driver form¶
[io]
drivers = [
{ name = "modbus-tcp", params = { address = "127.0.0.1:1502", unit_id = 1, input_start = 0, output_start = 0, timeout_ms = 500, on_error = "fault" } },
{ name = "mqtt", params = { broker = "127.0.0.1:1883", topic_in = "trust/examples/in", topic_out = "trust/examples/out", reconnect_ms = 500, keep_alive_s = 5, allow_insecure_remote = false } }
]
Selection rules:
| Condition | Requirement |
|---|---|
| single-driver file | use io.driver plus io.params |
| multi-driver file | use io.drivers only |
| mixed forms | invalid |
| empty config | invalid unless you intentionally choose driver = "none" |
Removing the final project driver through communication authoring retains
io.toml as an explicit no-driver project configuration:
[io]
driver = "none"
params = {}
safe_state = [{ address = "%QX0.0", value = "FALSE" }]
This preserves safe-state policy and prevents the project from implicitly
falling back to machine-wide system I/O. Adding a driver later replaces the
none sentinel while retaining safe-state entries and project comments outside
rewritten driver/safe-state items.
Built-in Driver Names¶
| Driver | Purpose |
|---|---|
analog |
analog inputs and outputs in millivolts (a chip's ADC, Linux IIO) |
can |
CAN frames to and from the process image (a chip's CAN controller, Linux SocketCAN) |
counter |
pulse and encoder counters (a chip's counters, the Linux counter subsystem) |
ethercat |
EtherCAT master / mock adapter workflows |
gpio |
direct GPIO-backed I/O |
loopback |
feed outputs back into inputs locally |
modbus-tcp |
Modbus TCP client I/O |
mqtt |
MQTT topic-backed I/O |
pwm |
PWM outputs with a duty in hundredths of a percent (a chip's PWM, Linux sysfs PWM) |
simulated |
runtime without physical hardware |
Accepted aliases also include:
| Alias | Canonical driver |
|---|---|
sim, noop |
simulated |
modbus_tcp |
modbus-tcp |
mqtt-tcp |
mqtt |
ether-cat, ecat |
ethercat |
Driver Patterns¶
simulated¶
[io]
driver = "simulated"
params = {}
Choose this driver when you want runtime execution without physical hardware.
loopback¶
[io]
driver = "loopback"
params = {}
Use this for first-project validation when you want outputs reflected back locally.
gpio¶
[io]
driver = "gpio"
[io.params]
backend = "sysfs"
inputs = [{ address = "%IX0.0", line = 17, debounce_ms = 5 }]
outputs = [{ address = "%QX0.0", line = 27, initial = false }]
modbus-tcp¶
[io]
driver = "modbus-tcp"
[io.params]
address = "127.0.0.1:1502"
unit_id = 1
input_start = 0
output_start = 0
timeout_ms = 500
on_error = "fault"
mqtt¶
[io]
driver = "mqtt"
[io.params]
broker = "127.0.0.1:1883"
topic_in = "trust/examples/mqtt/in"
topic_out = "trust/examples/mqtt/out"
reconnect_ms = 500
keep_alive_s = 5
allow_insecure_remote = false
Remote plaintext brokers are rejected unless allow_insecure_remote = true is
set explicitly for a test or development exception. Production remote brokers
should use TLS:
[io]
driver = "mqtt"
[io.params]
broker = "mqtts://mqtt.example.test:8883"
topic_in = "trust/site-a/in"
topic_out = "trust/site-a/out"
reconnect_ms = 500
keep_alive_s = 5
tls = true
tls_ca_path = "/etc/trust/certs/mqtt-ca.pem"
tls_client_cert_path = "/etc/trust/certs/runtime-client.pem"
tls_client_key_path = "/etc/trust/private/runtime-client-key.pem"
tls_alpn = ["mqtt"]
MQTT TLS uses the broker host name as the TLS server name/SNI value, so the
broker's certificate must name that host: a DNS name, or an IP address among its
IP addresses. The runtime trusts only the certificates in tls_ca_path, every
one the file holds, not the computer's trusted certificates, and it speaks TLS
1.2 and 1.3, the same on Windows, macOS and Linux. Over TLS 1.2 the broker must
offer an ECDHE cipher suite with AES-GCM or ChaCha20-Poly1305; CBC suites and RSA
key exchange are not offered.
tls_client_cert_path and tls_client_key_path must be provided together when
mutual TLS is required. The certificate file holds the client's certificate in
PEM, then any intermediate certificates; the key file holds its private key in
PEM, as PKCS#8, PKCS#1 (RSA) or SEC1 (EC). The runtime refuses the configuration
when the key does not belong to the certificate, and keeps the key in its own
memory.
ethercat¶
[io]
driver = "ethercat"
[io.params]
adapter = "mock"
timeout_ms = 250
cycle_warn_ms = 5
on_error = "fault"
mock_inputs = ["01", "00"]
[[io.params.modules]]
model = "EK1100"
slot = 0
[[io.params.modules]]
model = "EL2008"
slot = 1
channels = 8
Use adapter = "mock" for deterministic local validation. Real adapters need
the appropriate wire feature and supported host platform.
Safe-state Outputs¶
Safe-state entries are optional but strongly recommended:
[[io.safe_state]]
address = "%QX0.0"
value = "FALSE"
[[io.safe_state]]¶
| Key | Type | Required | Notes |
|---|---|---|---|
address |
IEC output address | yes | Uses %Q... addressing. |
value |
string | yes | Parsed as a typed value for the target output. |
Validation Rules¶
| Condition | Requirement |
|---|---|
io.drivers[*].name |
must not be empty |
driver params |
must be a TOML table |
io.safe_state[*].value |
must match the output type |
Numeric safe-state values must fit the addressed output width exactly: BYTE is
0..255, WORD is 0..65535, DWORD is 0..4294967295, and LWORD is the full
unsigned 64-bit range. Out-of-range values are rejected rather than wrapped or
clamped.