Passive Bus Monitoring with WireTap: A Hardware Engineer's Guide

Deep dive into oms-modbus's WireTap feature: how it implements passive bus observation at the I/O boundary, why microsecond timestamps matter for industrial diagnostics, and what the captured data reveals about your RS-485 network.

OrangeHorse Engineering Team 5 min read

Most Modbus debugging follows the same frustrating pattern: your sensor reports a CRC error, but you cannot see the bytes that caused it. You unplug the sensor, connect a USB-to-RS485 converter, fire up a serial monitor, and the error vanishes โ€” because the act of observing changed the bus. This is the observer effect in industrial form.

oms-modbus ships with a WireTap that solves this problem at the protocol level. It is a passive bus observer โ€” not a Modbus client, not a sniffer bolted onto a serial driver, but a first-class feature of a Modbus library that records every frame, gap, and CRC mismatch with wall-clock timestamps. Here is how it works and why the microsecond matters.

What WireTap Actually Observes

A standard Modbus client is an active participant. It sends a request, waits for a response, decodes the payload, and returns a result. Everything between "request sent" and "response received" is opaque โ€” the library does not record the raw bytes, the inter-frame gap, or whether the CRC matched.

WireTap sits at a different layer. It wraps the I/O boundary โ€” the place where bytes enter and leave the library โ€” and records four things for every interaction:

FieldExampleWhy It Matters
directionTx or RxDistinguishes master requests from slave responses on a half-duplex bus
timestamp2026-08-11T14:32:17.004221+08:00ISO 8601 with microsecond precision โ€” correlates frames with oscilloscope traces
raw_bytes01 03 00 00 00 01 84 0AThe complete ADU including CRC, exactly as it appeared on the wire
decodedReadHoldingRegisters { addr: 1, start: 0, qty: 1 }Human-readable PDU interpretation for quick scanning

The key word is passive. WireTap does not modify the bytes, inject delays, or alter the bus state. It observes and records โ€” nothing else. This means you can run it on a production RS-485 bus without affecting the communication between a PLC and its sensors.

How It Differs from a Serial Sniffer

A serial port sniffer like RealTerm or a logic analyzer captures raw bytes. That is useful, but it is only half the picture. You see hex dumps with no Modbus semantics โ€” no way to distinguish a Read Holding Registers request from a Read Coils request without manually decoding each byte.

WireTap combines raw capture with protocol awareness. The same library that decodes your Modbus transactions in production is the one that records them during diagnostics. You get:

  1. Raw bytes โ€” for verifying CRC calculations against your own implementation
  2. Decoded PDU โ€” for understanding what the master actually asked for
  3. Direction markers โ€” for tracing multi-drop bus conversations
  4. Microsecond timestamps โ€” for measuring inter-frame gaps and response latency

A logic analyzer gives you (1) and (4). A Modbus client gives you (2). Nothing else gives you all four in one API.

The Timestamp Story

Why microsecond precision? Because Modbus timing problems happen at the microsecond scale. The 3.5-character silent interval at 115200 baud is about 275 microseconds. A sensor that consistently responds 50 microseconds late will pass most tests but fail on a loaded bus with 20 devices polling at once.

WireTap uses std::time::Instant (monotonic clock on Linux, performance counter on Windows) to stamp each frame. The timestamps are rendered as ISO 8601 strings in the local timezone, making them directly comparable to oscilloscope traces and SCADA event logs. If you capture a CRC error at 14:32:17.004221, you can correlate it to a ground-loop transient your oscilloscope triggered on at the same moment.

Using WireTap

Here is the minimal setup โ€” opening an RTU port and dropping into a passive monitoring loop:

use oms_modbus::prelude::*;
use oms_modbus::transport::rtu::RtuTransportBuilder;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let transport = RtuTransportBuilder::new("COM3")
        .baud_rate(115_200)
        .build()?;

    let tap = WireTap::new(transport)
        .with_format(WireTapFormat::Verbose)
        .enable_timestamps(true);

    // Passive: record every frame, decode it, log it
    for event in tap.iter() {
        let event = event?;
        println!(
            "[{}] {} {:02X?} โ†’ {}",
            event.timestamp.format("%H:%M:%S%.6f"),
            if event.is_tx { "TX" } else { "RX" },
            event.raw,
            event.decoded.unwrap_or("(undecoded)".into())
        );
        // The bus continues. WireTap never interrupts.
    }

    Ok(())
}

Two things to notice: there is no .send_request() call, and the loop does not terminate when a frame is malformed. WireTap records the bad frame, logs the CRC error, and keeps watching. A diagnostic tool that stops on the first error is not a diagnostic tool โ€” it is a script.

Real-World Debugging: A Case Study

During testing of the OHTS1070 weather station, we observed intermittent CRC errors on a bus that looked perfect under an oscilloscope. WireTap captured 10,000 frames over an hour and revealed the pattern: every 47th response from sensor address 0x02 had a valid CRC but arrived before the 3.5T silent interval had elapsed. The sensor was responding too quickly after receiving the master's request โ€” a firmware edge case triggered by a specific register read sequence.

WireTap caught it in 10 minutes. Finding the same bug with an oscilloscope would have meant manually triggering on dozens of frames, decoding each CRC by hand, and measuring the inter-frame gap with cursors. WireTap computed the gap automatically because it had wall-clock timestamps on every frame.

When to Use WireTap

WireTap is not a replacement for an oscilloscope or a logic analyzer. It operates at the application layer, not the physical layer โ€” it sees bytes after the UART, not voltage levels on the wire. Use WireTap when:

  • You suspect a protocol-level issue โ€” bad CRC, wrong function code, unexpected register address
  • You need to correlate Modbus traffic with SCADA events โ€” WireTap's timestamps are in the same format as your event log
  • You are integrating a new sensor and want to verify its register map without writing test scripts
  • You need to monitor a live bus without interrupting communication โ€” WireTap is invisible to both master and slave

Use an oscilloscope when you suspect a physical-layer issue โ€” ground loops, termination resistors, signal reflection. WireTap and an oscilloscope are complementary tools for different layers of the OSI stack.


Read the WireTap API documentation for the complete interface. If you encounter a bus anomaly that WireTap cannot explain, open an issue on GitHub โ€” we use diagnostic data to improve the library's decoding and timestamp accuracy.

modbus rs485 bus-monitoring diagnostic WireTap IIoT rust