Mesh And Zenoh¶
truST uses a Zenoh-backed mesh surface for explicit multi-runtime sharing.
Mesh Fits¶
- you want publish/subscribe-style data sharing between runtimes
- you need explicit control over what a runtime publishes or subscribes to
- discovery alone is not enough because you need live data exchange
Core config surface¶
Mesh is configured in [runtime.mesh] in runtime.toml.
Typical keys:
enabledrolelistenconnecttlsauth_tokenzenohd_versionpublish[runtime.mesh.subscribe]
Example:
[runtime.mesh]
enabled = true
role = "peer"
listen = "127.0.0.1:5212"
connect = []
tls = false
auth_token = ""
zenohd_version = "1.7.2"
publish = ["Status.PLCState"]
[runtime.mesh.subscribe]
"LineB:Status.PLCState" = "Local.Status.RemoteState"
When auth_token is configured, peers authenticate with that shared mesh token. Peers with
missing or different tokens cannot join the authenticated link. tls = true uses the
certificate, key and CA configured under [runtime.tls] and negotiates TLS on mesh sockets;
it does not fall back to plaintext. Certificates must cover the hostnames used to connect.
How to think about it¶
- discovery finds peers
- pairing establishes trust/access workflow
- mesh moves selected runtime values between trusted peers
For same-host deterministic transport, use Realtime T0. Mesh is not the HardRT path.
Worked tutorial¶
This tutorial runs two runtimes and walks through:
- network discovery,
- optional pairing,
- mesh data sharing.
Why this tutorial exists¶
Single-PLC workflows are only the start. Real plants often split logic across multiple runtimes. You need a repeatable method to discover peers, trust peers, and exchange selected data safely.
What you will learn¶
- how to configure two runtimes so they can be discovered
- why service names and unique ports matter
- how to enable mesh publish/subscribe with explicit mappings
Prerequisites¶
- complete Tutorial 13 first
- one machine is enough (we will run two runtimes with different ports)
Step 1: Create PLC-A and PLC-B project copies¶
Why: separate project folders simulate real independent PLC nodes.
rm -rf /tmp/trust-plc-a /tmp/trust-plc-b
cp -R /tmp/trust-tutorial-13 /tmp/trust-plc-a
cp -R /tmp/trust-tutorial-13 /tmp/trust-plc-b
Expected result: - two independent project trees
Step 2: Configure unique runtime endpoints¶
Why: two runtimes on one host need unique web and mesh ports. Each project gets its own control endpoint by default, so this step leaves the control endpoint to that default.
Set PLC-A runtime config:
cat > /tmp/trust-plc-a/runtime.toml <<'TOML'
[bundle]
version = 1
[resource]
name = "PlcA"
cycle_interval_ms = 100
[runtime.control]
mode = "production"
debug_enabled = false
[runtime.web]
enabled = true
listen = "127.0.0.1:18101"
auth = "local"
[runtime.discovery]
enabled = true
service_name = "LineA"
advertise = true
interfaces = []
[runtime.mesh]
enabled = true
listen = "127.0.0.1:5211"
auth_token = ""
publish = []
subscribe = {}
[runtime.log]
level = "info"
[runtime.retain]
mode = "none"
save_interval_ms = 1000
[runtime.watchdog]
enabled = false
timeout_ms = 5000
action = "halt"
[runtime.fault]
policy = "halt"
TOML
Set PLC-B runtime config:
cat > /tmp/trust-plc-b/runtime.toml <<'TOML'
[bundle]
version = 1
[resource]
name = "PlcB"
cycle_interval_ms = 100
[runtime.control]
mode = "production"
debug_enabled = false
[runtime.web]
enabled = true
listen = "127.0.0.1:18102"
auth = "local"
[runtime.discovery]
enabled = true
service_name = "LineB"
advertise = true
interfaces = []
[runtime.mesh]
enabled = true
listen = "127.0.0.1:5212"
auth_token = ""
publish = ["Status.PLCState"]
subscribe = {}
[runtime.log]
level = "info"
[runtime.retain]
mode = "none"
save_interval_ms = 1000
[runtime.watchdog]
enabled = false
timeout_ms = 5000
action = "halt"
[runtime.fault]
policy = "halt"
TOML
Expected result:
- both runtimes are discoverable
- PLC-B publishes one mesh point (Status.PLCState)
Step 3: Build both projects¶
Why: each node still needs its compiled program before it can run.
trust-runtime build --project /tmp/trust-plc-a --sources /tmp/trust-plc-a/src
trust-runtime build --project /tmp/trust-plc-b --sources /tmp/trust-plc-b/src
Expected result:
- program.tpi exists in both folders
Step 4: Start both runtimes¶
Why: discovery and mesh only work when both nodes are online.
Terminal A:
trust-runtime run --project /tmp/trust-plc-a
Terminal B:
trust-runtime run --project /tmp/trust-plc-b
Expected result:
- PLC-A Web UI on http://127.0.0.1:18101
- PLC-B Web UI on http://127.0.0.1:18102
Step 5: Verify discovery from Web UI¶
Why: discovery is the first proof that nodes can see each other on the network.
- Open PLC-A Web UI (
http://127.0.0.1:18101). - Go to
Network -> Discovery. - Confirm
LineBappears.
Expected result: - PLC-B is listed with reachable link
If discovery fails:
- verify both runtimes are running
- verify runtime.discovery.enabled = true
- use manual add with PLC-B URL (127.0.0.1:18102)
Step 6: Pair nodes (optional but recommended)¶
Why: pairing is a safer trust workflow than sharing static credentials manually.
- In PLC-B, open
Network -> Pairingand generate a code. - In PLC-A, claim PLC-B using that code.
Expected result: - PLC-A can access PLC-B without repeated token prompts
Step 7: Configure mesh subscription on PLC-A¶
Why: publish/subscribe must be explicit so only intended data flows between runtimes.
Edit /tmp/trust-plc-a/runtime.toml and replace subscribe = {} with:
[runtime.mesh.subscribe]
"LineB:Status.PLCState" = "Local.Status.RemoteState"
Restart PLC-A after edit.
Expected result: - PLC-A subscribes to PLC-B status
Step 8: Verify mesh connection¶
Why: operational confidence requires observing active mesh links, not just configuration files.
- Open PLC-A Web UI.
- Go to
Network -> Mesh connections. - Confirm connection to
LineBis present.
Expected result: - mesh link appears as connected/healthy
Common mistakes¶
- same web or mesh ports for both PLCs
- service name mismatch (
LineBin config vs actual runtime name) - editing
runtime.tomlwithout restarting runtime
Completion checklist¶
- [ ] PLC-A and PLC-B both running
- [ ] discovery shows peer runtime
- [ ] optional pairing completed
- [ ] mesh publish/subscription configured
- [ ] mesh connection observed in UI
Published names may also select a nested program or equipment member, such as
Main.Tank.Level, or an array element such as Main.Levels[2]. Only the named values are
included; missing members and out-of-bounds elements are omitted. Identifiers match without
case sensitivity. This does not by itself enable HMI remote writes.