Multi-protocol debugging and simulation CLI/TUI tool for Modbus RTU
Multi-protocol debugging and simulation tool for Modbus RTU, suitable for both physical serial ports and network-forwarded ports. Provides both CLI and TUI interfaces.
- Modbus RTU (master/slave) debugging and simulation; supports four register types: holding, input, coils, and discrete.
- Full-featured CLI: port discovery and checks (
--list-ports/--check-port), master/slave operations (--master-provide/--slave-listen) and persistent modes (--*-persist). Outputs can be JSON/JSONL, which is script/CI-friendly. - Interactive TUI: configure ports, stations, and registers via terminal UI; supports save/load (
Ctrl+Ssaves and auto-enables ports) and IPC integration with CLI for testing and automation. - Multiple data sources and protocols: physical/virtual serial ports (managed via
socat), HTTP, MQTT, IPC (Unix domain sockets / named pipes), files, and FIFOs. - Port Forwarding: configure source and target ports within the TUI for data replication, monitoring, or bridging.
- Daemon mode: run headless using a saved TUI configuration to start all configured ports/stations (suitable for embedded/CI deployments).
- Virtual port and test tooling: includes
scripts/socat_init.shfor virtual serial ports and example tests inexamples/cli_e2eandexamples/tui_e2efor local/CI testing. - Extensible integrations: forward or receive port data via HTTP/MQTT/IPC for (remote) integrations.
Note: use
--no-config-cacheto disable TUI save/load;--config-file <FILE>and--no-config-cacheare mutually exclusive.
-
Install the Rust toolchain
-
Clone the repo and enter the directory
-
Install:
-
Build from source:
cargo install aoba -
Or install a CI-built release (if available) with
cargo-binstall:-
Example:
cargo binstall --manifest-path ./Cargo.toml --version <version> -
Use
--target <triple>to pick a platform-specific artifact (e.g.x86_64-unknown-linux-gnu).
-
-
-
Run
aobato start the TUI by default; use TUI to configure ports and save the configuration as needed.
--config-file <FILE> explicitly selects a TUI config file (daemon mode uses --daemon-config <FILE>). This conflicts with --no-config-cache, which disables loading/saving of TUI config.
Example:
# Start TUI with a specific config file; load/save enabled
aoba --tui --config-file /path/to/config.json
# Start TUI with no config caching (default) — no load/save
aoba --tui --no-config-cacheRun headless with a saved configuration:
aoba --daemon --config-file /path/to/config.jsonSystemd example:
[Unit]
Description=Aoba Modbus RTU Daemon
Wants=network.target
After=network.target network-service
StartLimitIntervalSec=0
[Service]
Type=simple
WorkingDirectory=/home/youruser
ExecStart=/usr/local/bin/aoba --daemon --config-file /home/youruser/config.json
Restart=always
RestartSec=1s
[Install]
WantedBy=multi-user.target- Automated testing: auto-start Modbus simulators in CI/CD
- Embedded systems: run Aoba as a daemon on embedded devices (e.g., Raspberry Pi) with USB-serial adapters
Aoba provides a trait-based Rust API for embedding Modbus functionality in your applications. The API supports both master (client) and slave (server) roles with customizable hooks and data sources.
Modbus Master (polling a slave):
use aoba::api::modbus::{ModbusBuilder, RegisterMode};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create and start a master that polls a slave
let master = ModbusBuilder::new_master(1)
.with_port("/dev/ttyUSB0")
.with_register(RegisterMode::Holding, 0, 10)
.build_master()?;
// Receive responses via iterator interface
while let Some(response) = master.recv_timeout(std::time::Duration::from_secs(2)) {
println!("Received: {:?}", response.values);
}
Ok(())
}Modbus Slave (responding to requests):
use aoba::api::modbus::{ModbusBuilder, RegisterMode};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create and start a slave that responds to master requests
let slave = ModbusBuilder::new_slave(1)
.with_port("/dev/ttyUSB0")
.with_register(RegisterMode::Holding, 0, 10)
.build_slave()?;
// Receive request notifications via iterator interface
while let Some(notification) = slave.recv_timeout(std::time::Duration::from_secs(10)) {
println!("Processed request: {:?}", notification.values);
}
Ok(())
}Manual Mode (write operations + single-shot polling):
use aoba::api::modbus::{ModbusBuilder, RegisterMode};
fn main() -> anyhow::Result<()> {
let master = ModbusBuilder::new_master(1)
.with_port("/dev/ttyUSB0")
.with_timeout(5000)
.build_master_manual()?;
// Single-shot poll
let resp = master.poll_once(RegisterMode::Holding, 0, 10)?;
println!("Values: {:?}", resp.values);
// Write single holding register (fc 0x06)
master.write_holding(0x00, 0x1234)?;
// Write multiple holding registers (fc 0x10)
master.write_registers(0x00, &[0x1234, 0x5678])?;
// Write coils (fc 0x0F)
master.write_coils(0x00, &[true, false, true])?;
Ok(())
}Method 1: Using the test script (recommended)
A Python test script is provided to run both master and slave examples simultaneously with colored, prefixed output:
# Run for 30 seconds
python3 scripts/run_api_test.py --duration 30
# Run indefinitely (Ctrl+C to stop)
python3 scripts/run_api_test.py
# Custom ports
python3 scripts/run_api_test.py --master-port /dev/ttyUSB0 --slave-port /dev/ttyUSB1
# Skip auto-build (use existing binaries)
python3 scripts/run_api_test.py --no-buildNote: You may see "Operation timed out" warnings in the logs. This is normal behavior:
- The slave times out while waiting for master requests (1s timeout)
- The master times out while waiting for slave responses (2s timeout)
- Both automatically retry and continue operation
- Communication succeeds despite these warnings
Method 2: Manual execution
Run in separate terminals:
# Terminal 1: Start slave first
cargo run --package api_slave -- /tmp/vcom2
# Terminal 2: Start master
cargo run --package api_master -- /tmp/vcom1Note: On Linux/WSL, initialize virtual serial ports first:
./scripts/socat_init.shFor full examples with middleware hooks and data sources, see:
examples/api_master- Master with logging hooksexamples/api_slave- Slave with request monitoring and statistics
Licensed under the Synthetic Source License (SySL), Version 1.0.
