Project Layout¶
Project tree¶
my-plc/
runtime.toml
io.toml
trust-lsp.toml
program.stbc
src/
Main.st
Configuration.st
What Each File Answers¶
| File or folder | Answers which question? |
|---|---|
src/ |
what logic do I want to run? |
runtime.toml |
how should the runtime behave? |
io.toml |
what backend or device plane touches %I/%Q? |
trust-lsp.toml |
what editor/project/dependency rules apply? |
program.stbc |
what executable bytecode did the build produce? |
Where You Usually Edit It¶
| File | Typical editor surface | Why you touch it |
|---|---|---|
src/Main.st |
VS Code, Browser IDE | main ST logic |
src/Configuration.st |
VS Code, Browser IDE | tasks and %I/%Q mapping |
runtime.toml |
VS Code, Browser IDE, config workflow | runtime timing, control, web, retain, fault behavior |
io.toml |
VS Code, Browser IDE, setup workflow | hardware/backend mapping |
hmi/ |
VS Code, Browser IDE | HMI page config, SVG, operator layout |
Reusable library shape¶
Keep project-owned ST in the project src/. Put reusable packages in their own
package root and reference them through [dependencies].
workspace/
my-plc/
runtime.toml
io.toml
trust-lsp.toml
src/
libraries/
my_motion_lib/
trust-lsp.toml
src/
Common Wrong Layouts¶
- putting runtime config inside
src/ - mixing generated bytecode with reusable library source
- leaving HMI files outside
hmi/and expecting the runtime to discover them - scattering
%I/%Qmapping rules across comments instead ofConfiguration.st
Use Maintain An Existing Project when a colleague handed you an existing system. Use Create A New Project when you are starting from an empty folder.
Developer Guide¶
truST PLC Developer Guide¶
This guide is for automation engineers and developers building and deploying PLC project folders. It assumes you already have the runtime installed.
Project Layout (Structure)¶
A PLC project folder typically contains:
runtime.toml
io.toml
program.stbc
trust-lsp.toml
src/
runtime.toml: runtime configuration (tasks, control, web, watchdog, retain).io.toml: I/O driver config and safe-state outputs.program.stbc: compiled bytecode.trust-lsp.toml: optional project config for include paths, package dependencies, vendor profile, and runtime-assisted editor features.src/: project-owned Structured Text sources.
Reusable Libraries¶
Project-owned Structured Text belongs in <project>/src/.
Reusable truST libraries should live in their own package directory. In this
repo the convention is libraries/<name>/; in user projects any separate
package directory is fine as long as consuming projects point to it through
[dependencies].
Example layout:
my-project/
runtime.toml
io.toml
trust-lsp.toml
src/
libraries/
my_motion_lib/
trust-lsp.toml
src/
A reusable library package should contain:
trust-lsp.tomlwith[package].versionand[project].include_paths = ["src"]src/with the library Structured Text sources
Consumers reference reusable packages from their own trust-lsp.toml:
[dependencies]
MyMotionLib = { path = "../libraries/my_motion_lib", version = "0.1.0" }
trust-runtime build --project ... and trust-runtime test --project ...
compile the project's own src/ plus any local [dependencies] packages.
Use [[libraries]] for external/index-only library trees, vendor stub packs,
or attached documentation packs. Do not place reusable libraries under
crates/.../tests/fixtures/; that path is test-only repo infrastructure.
Config Paths + Apply Semantics¶
Runtime reads configuration from these canonical paths:
- Project runtime config:
<project-folder>/runtime.toml(required). - Project I/O config:
<project-folder>/io.toml(optional). - System I/O fallback if project
io.tomlis missing: - Linux/macOS:
/etc/trust/io.toml - Windows:
C:\ProgramData\truST\io.toml
Apply/restart behavior:
- Offline edits to
runtime.tomlandio.tomlare loaded on next runtime start/restart. trust-runtime validate --project <project-folder>validates both files against the canonical schema (required keys, types/ranges, unknown-key policy).- Browser UI and deploy preflight use the same schema checks before writing/applying config.
config.setupdates running settings in memory and returnsrestart_requiredkeys when a restart is needed to apply the change surface (web/discovery/mesh/control mode/retain mode).
Build Flow¶
Compile sources into bytecode:
trust-runtime build --project <project-folder>
Validate a project folder (config + bytecode):
trust-runtime validate --project <project-folder>
Host-Specific Performance Builds¶
Use the portable release build when the binary needs to run across different CPU families or when you are preparing a shared/distributed artifact:
cargo build --release -p trust-runtime --bin trust-runtime
If you control the deployment hardware and want maximum performance on that machine class, build a host-native binary:
RUSTFLAGS="-C target-cpu=native" cargo build --release -p trust-runtime --bin trust-runtime
That host-native binary may not run on other CPUs. It is appropriate for self-built deployments to a homogeneous hardware target and for local performance tuning, but not for portable/shared release artifacts.
The benchmark scripts support the same build-mode policy so you can validate the exact build style you intend to ship:
TRUST_RUNTIME_HOST_CODEGEN=auto: default; picksnativeon Raspberry Pi benchmark hosts andgenericelsewhereTRUST_RUNTIME_HOST_CODEGEN=generic: force portable benchmark buildsTRUST_RUNTIME_HOST_CODEGEN=native: force host-native benchmark builds
For a host-native validation run on the current machine, use one of the shipped benchmark surfaces:
TRUST_RUNTIME_HOST_CODEGEN=native ./scripts/runtime_motion_example_bench_gate.sh
For the broader shipped motion breakdown:
TRUST_RUNTIME_HOST_CODEGEN=native ./scripts/runtime_motion_benchmark_breakdown.sh
Profile-Guided Optimization (PGO)¶
When you want the fastest self-built runtime for one machine class, use PGO on top of the host-native build. This is not a portable release workflow.
- Install the LLVM merge tool bundled for Rust:
rustup component add llvm-tools-preview
- Collect training data with representative workloads on the target machine:
PGO_ROOT="$PWD/target/pgo/runtime-motion-native"
rm -rf "$PGO_ROOT" target/gate-artifacts/runtime-motion-pgo-gen*
mkdir -p "$PGO_ROOT/raw"
OUT_DIR=target/gate-artifacts/runtime-motion-pgo-gen-gate \
TRUST_RUNTIME_HOST_CODEGEN=native \
RUSTFLAGS="-Cprofile-generate=$PGO_ROOT/raw" \
./scripts/runtime_motion_example_bench_gate.sh
OUT_DIR=target/gate-artifacts/runtime-motion-pgo-gen-breakdown \
TRUST_RUNTIME_HOST_CODEGEN=native \
RUSTFLAGS="-Cprofile-generate=$PGO_ROOT/raw" \
./scripts/runtime_motion_benchmark_breakdown.sh
- Merge the raw profiles:
SYSROOT=$(rustc --print sysroot)
LLVM_PROFDATA=$(find "$SYSROOT" -path '*/bin/llvm-profdata' -type f | head -n 1)
"$LLVM_PROFDATA" merge -output="$PGO_ROOT/merged.profdata" "$PGO_ROOT"/raw/*.profraw
- Rebuild and rerun the same validation surfaces with the merged profile:
OUT_DIR=target/gate-artifacts/runtime-motion-pgo-gate \
TRUST_RUNTIME_HOST_CODEGEN=native \
RUSTFLAGS="-Cprofile-use=$PGO_ROOT/merged.profdata" \
./scripts/runtime_motion_example_bench_gate.sh
OUT_DIR=target/gate-artifacts/runtime-motion-pgo-breakdown \
TRUST_RUNTIME_HOST_CODEGEN=native \
RUSTFLAGS="-Cprofile-use=$PGO_ROOT/merged.profdata" \
./scripts/runtime_motion_benchmark_breakdown.sh
Current Raspberry Pi 5 evidence on the full_demo motion path:
- portable baseline
full_demo p50:433.501 us - host-native
full_demo p50:404.353 us - host-native + PGO
full_demo p50:292.298 us
Generate API docs from tagged ST comments (@brief, @param, @return):
trust-runtime docs --project <project-folder> --format both --out-dir <project-folder>/docs/api
PLCopen XML interchange (strict ST subset profile):
trust-runtime plcopen profile
trust-runtime plcopen export --project <project-folder> --output <project-folder>/interop/plcopen.xml
trust-runtime plcopen export --project <project-folder> --output <project-folder>/interop/plcopen.xml --json
trust-runtime plcopen export --project <project-folder> --target ab --json
trust-runtime plcopen export --project <project-folder> --target siemens --json
trust-runtime plcopen export --project <project-folder> --target schneider --json
trust-runtime plcopen import --input <plcopen.xml> --project <target-project-folder>
trust-runtime plcopen import --input <plcopen.xml> --project <target-project-folder> --json
Import writes migrated sources to src/ and a migration report to:
<project-folder>/interop/plcopen-migration-report.json
The report includes detected vendor ecosystem, discovered/imported/skipped POU counts, source coverage, semantic-loss score, compatibility coverage summary, structured unsupported-node diagnostics, applied vendor-library shims, and per-POU skip reasons.
For compatibility matrix, round-trip limits, and known gaps, see:
docs/guides/PLCOPEN_INTEROP_COMPATIBILITY.md
For multi-vendor export adapter manual steps/limitations, see:
docs/guides/PLCOPEN_EXPORT_ADAPTERS_V1.md
For direct Siemens .scl export/import tutorial (TIA External source files path), see:
docs/guides/SIEMENS_TIA_SCL_IMPORT_TUTORIAL.md
For OpenPLC-specific migration expectations and sample flow, see:
docs/guides/OPENPLC_INTEROP_V1.md
Start runtime:
trust-runtime --project <project-folder>
Runtime Configuration (runtime.toml)¶
Key sections:
[resource]: name + cycle time.[runtime.control]: control endpoint + debug gating.[runtime.web]: browser UI.[runtime.discovery]: local mDNS.[runtime.mesh]: runtime-to-runtime sharing.[runtime.observability]: historian sampling + Prometheus export.[runtime.retain]: retain store.[runtime.watchdog]: fault policy + safe halt.simulation.toml: simulation couplings, delays, and scripted disturbances/fault injection.
I/O Configuration (io.toml)¶
See docs/guides/PLC_IO_BINDING_GUIDE.md for full examples.
Supported I/O backends are loopback, simulated, gpio, modbus-tcp, mqtt, and ethercat.
io.toml supports:
- single-driver form: io.driver + io.params
- multi-driver form: io.drivers = [{ name = \"...\", params = {...} }, ...]
Use one form at a time (do not mix io.driver with io.drivers).
For EtherCAT backend scope and setup details, see:
docs/guides/ETHERCAT_BACKEND_V1.md.
For protocol-commissioning example projects (including GPIO and composed
multi-driver setup), see:
examples/communication/README.md.
Browser UI (Operations)¶
If enabled:
runtime.web.enabled = true
runtime.web.listen = "0.0.0.0:8080"
Open:
http://<device-ip>:8080
Operations UI:
- http://<device-ip>:8080 for status, I/O, settings, deploy.
- http://<device-ip>:8080/hmi for auto-generated read-only HMI.
Dedicated HMI control API (via POST /api/control):
- hmi.schema.get
- hmi.values.get
- hmi.write (phase-gated: enabled only when [write].enabled = true in hmi.toml and target is explicitly allowlisted)
Debug Attach (Development)¶
Debug is off in production mode by default. For development:
runtime.control.mode = "debug"
runtime.control.debug_enabled = true
Use the VS Code extension or trust-runtime ctl for stepping and breakpoints.
Deploy + Rollback¶
Deploy a project folder into a versioned store:
trust-runtime deploy --project <project-folder> --root <deploy-root>
Rollback:
trust-runtime rollback --root <deploy-root>
Local Discovery + Mesh¶
Enable local discovery:
runtime.discovery.enabled = true
Enable mesh sharing:
runtime.mesh.enabled = true
runtime.mesh.publish = ["Status.PLCState"]
[runtime.mesh.subscribe]
"RemoteA:Status.PLCState" = "Local.Status.RemoteState"
Testing¶
Recommended checks: run the runtime reliability and GPIO hardware checklists before deployment.
For CI/CD pipelines and stable machine-readable outputs, see:
docs/guides/PLC_CI_CD.md
For simulation-first workflows, see:
docs/guides/PLC_SIMULATION_WORKFLOW.md