use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
use oms_modbus::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("═══ OMS Modbus — Bus Monitor ═══\n");
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
let server = tcp::TcpServer::bind(addr).await?;
let bind_addr = server.local_addr()?;
let store = Arc::new(SlaveStore::with_holding_registers(&[
(0, 100),
(1, 200),
(2, 300),
(5, 500),
]));
store.write_coil(0, true);
store.write_coil(1, false);
tokio::spawn(async move {
server.serve_forever(store).await.ok();
});
tokio::time::sleep(Duration::from_millis(50)).await;
println!("[1/4] TCP server started on {bind_addr}");
let cap = Arc::new(BusCapture::unbounded());
println!("[2/4] BusCapture created (unbounded mode)");
let opts = ClientOptions::default()
.with_timeout(Duration::from_secs(3))
.with_tap(cap.clone());
let client = tcp::with_options(bind_addr, opts).await?;
println!("[3/4] Client connected with capture attached\n");
println!("[4/4] Generating Modbus traffic …\n");
println!("─── Reads ───");
let regs = client.read_holding_registers(1, 0, 3).await?;
println!(" FC03 Holding[0..2] = {regs:?}");
let bits = client.read_coils(1, 0, 4).await?;
println!(" FC01 Coils[0..3] = {bits:?}");
client.read_discrete_inputs(1, 0, 2).await?;
println!(" FC02 DiscreteIn OK");
client.read_input_registers(1, 0, 1).await?;
println!(" FC04 InputReg OK\n");
println!("─── Writes ───");
client.write_single_register(1, 0, 7777).await?;
println!(" FC06 WriteSingle OK");
client
.write_multiple_registers(1, 10, &[111, 222, 333])
.await?;
println!(" FC16 WriteMulti OK");
client.write_single_coil(1, 0, false).await?;
println!(" FC05 WriteCoil OK\n");
println!("─── Errors (these appear in capture) ───");
let err = client.read_holding_registers(247, 0, 1).await;
match err {
Err(e) => println!(" FC03 slave=247 → ✗ {e}"),
Ok(_) => println!(" FC03 slave=247 → (unexpected success)"),
}
let err = client.read_holding_registers(1, 60000, 1).await;
match err {
Err(e) => println!(" FC03 addr=60000 → ✗ {e}\n"),
Ok(regs) => println!(" FC03 addr=60000 → {regs:?} (server returned zeros)\n"),
}
println!("═══ Capture Statistics ═══");
println!(" Requests: {}", cap.count_requests());
println!(" Responses: {}", cap.count_responses());
println!(" Errors: {}", cap.count_errors());
println!(" Dropped: {} (zero = no records lost)", cap.dropped());
println!();
let packets = cap.drain();
println!("─── Packet Analysis ({:>3} records) ───", packets.len());
let tx_count = packets
.iter()
.filter(|p| matches!(p.data, PacketData::RawTx(_)))
.count();
let rx_count = packets
.iter()
.filter(|p| matches!(p.data, PacketData::RawRx(_)))
.count();
let err_count = packets
.iter()
.filter(|p| matches!(p.data, PacketData::RawError(..)))
.count();
println!(" TX (requests): {tx_count}");
println!(" RX (responses): {rx_count}");
if err_count > 0 {
println!(" Errors: {err_count}");
}
println!("\n─── Capture Dump ───");
for pkt in &packets {
let prefix = match &pkt.data {
PacketData::RawTx(_) => "[TX]",
PacketData::RawRx(_) => "[RX]",
PacketData::RawError(..) => "[ERR]",
_ => "[???]",
};
println!(" {pkt} {prefix}");
}
println!("\n═══ Monitor Summary ═══");
println!(" BusCapture records: {}", packets.len());
println!(
" Success rate: {}/{}",
cap.count_responses(),
cap.count_requests()
);
println!();
println!("Integrate with any transport:");
println!(" let cap = Arc::new(BusCapture::unbounded());");
println!(" let opts = ClientOptions::default().with_tap(cap.clone());");
println!(" let client = tcp::with_options(addr, opts).await?;");
println!(" // ... run traffic ...");
println!(" for pkt in cap.drain() {{ println!(\"{{pkt}}\"); }}");
Ok(())
}