mbus-codegen
Code-generation library for the mbus-ffi server-app layer.
This crate exists to give mbus-ffi/build.rs and xtask a single shared source of truth
for server-app code generation so that the logic does not have to be duplicated across a
build script and a CLI tool.
It is primarily a support crate for the workspace rather than an end-user-facing library,
but it is publishable because mbus-ffi depends on it as a build-dependency.
What It Does
Given a YAML device config that declares the Modbus register/coil map, this crate:
- Parses the YAML into a typed
ServerAppConfigstruct (parse_yaml). - Validates the config — checks schema version, duplicate addresses, and Modbus
read-only constraints on discrete inputs and input registers (
validate_config). - Renders a Rust dispatcher source file (
render_rust_dispatcher) that is compiled intombus-ffias the server-app layer. - Renders a C header (
render_c_header) that exposes the generated API to the host C/C++ application.
YAML Config Format
schema_version: 1
device:
name: PumpController
unit_id: 1
response_timeout_ms: 1000 # optional
hooks:
on_write_coil: my_global_coil_hook # optional: global fallback for coil writes
on_write_holding: ~ # optional: global fallback for holding-reg writes
memory_map:
coils:
- name: pump_run
address: 0
access: rw # ro | wo | rw
on_write: app_on_write_pump_run # optional: per-entry hook, overrides global
discrete_inputs:
- name: fault_active
address: 0
access: ro # must be ro — Modbus protocol constraint
holding_registers:
- name: speed_setpoint
address: 0
access: rw
on_write: app_on_write_speed_setpoint
input_registers:
- name: pressure_actual
address: 0
access: ro # must be ro — Modbus protocol constraint
access values:
| Value | Meaning |
|---|---|
ro |
Read-only from a Modbus client's perspective |
wo |
Write-only (no FC01/FC03/FC04 read support generated) |
rw |
Both readable and writable |
Write-notification hooks (on_write):
When set, the named C function is called before the value is stored in Rust state.
Return MBUS_HOOK_OK to accept the write, or another MbusHookStatus variant to
reject it (the Rust state is left unchanged and a Modbus exception is returned to the
client).
Per-entry on_write takes priority over the global hooks.on_write_coil /
hooks.on_write_holding fallbacks.
Generated Outputs
Rust dispatcher (render_rust_dispatcher)
Written to $OUT_DIR/generated_server.rs by mbus-ffi/build.rs at compile time.
Included via include! macro — never tracked in git.
Contains:
MbusHookStatusenum (also#[repr(C)]for C linkage)AppCoils,AppDiscreteInputs,AppHolding,AppInputmodel structs derived withmbus-serverproc-macros (CoilsModel,HoldingRegistersModel, …)AppModelstruct annotated with#[modbus_app]— wires all models togetherstatic mut APP_MODEL: Option<AppModel>— Rust-owned register state- Address-based
mbus_server_get_*/set_*FFI functions for each table - Named
mbus_server_get_{name}/set_{name}FFI functions per entry - Write-dispatch helpers that call the registered
on_writehooks - Handler callbacks matching the
MbusServerHandlerssignature table mbus_server_model_init()andmbus_server_default_handlers(userdata)convenience exports
C header (render_c_header)
Written to a path specified via --emit-c-header when running gen-server-app.
Checked into the repo alongside the YAML — regenerate with:
Contains:
MbusHookStatusC enummbus_app_lock()/mbus_app_unlock()declarations- Write-notification hook declarations (one per
on_writeentry) - Address-based accessor declarations
mbus_server_model_init()/mbus_server_default_handlers()declarations- Named field accessor declarations
- Generated handler callback declarations (for manual
MbusServerHandlersassembly)
How It Is Used
By mbus-ffi/build.rs (Rust dispatcher)
mbus-ffi/build.rs reads MBUS_SERVER_APP_CONFIG (a path to the YAML config),
calls parse_yaml + validate_config + render_rust_dispatcher, and writes the
result to $OUT_DIR/generated_server.rs.
MBUS_SERVER_APP_CONFIG=path/to/server_app.yaml \
By xtask gen-server-app (C header)
xtask uses the same types and render_c_header to regenerate the C header
whenever the YAML changes:
--check verifies the file is up to date without writing; --dry-run prints
what would be written without touching the filesystem.
By xtask build-c-demo (end-to-end)
build-c-demo reads codegen from demo.yaml, runs gen-server-app for the
C header, then sets MBUS_SERVER_APP_CONFIG when invoking cargo build so
build.rs generates the Rust dispatcher automatically:
Public API Summary
// Parse YAML text into a typed config.
;
// Validate a parsed config (schema version, address uniqueness, ro constraints).
;
// Render the Rust dispatcher source (goes to $OUT_DIR/generated_server.rs).
;
// Render the C header source (goes to target/mbus-ffi/include/mbus_server_app.h).
;
Config types re-exported for use by xtask:
ServerAppConfig, DeviceConfig, HooksConfig, MemoryMap, MapEntry, Access.