Comms sniffers¶
The comms sniffers are host-facing plugins that passively wiretap a bus in a running emulator and republish every frame over ZeroMQ as a msgpack message. Point any external tool — a logger, a dashboard, a protocol analyser, or the bundled Python client — at the socket to watch the flight software's comms traffic live, without touching the guest.
There is one sniffer per protocol: CAN, SPI, MIL-STD-1553, SpaceWire, serial (UART). They observe passively (an emulated controller cannot tell a sniffer is attached) and never block the simulation (a slow or absent subscriber loses frames rather than back-pressuring the guest).
For the design (how a plugin resolves its target, the observation seams, the wire format), see the plugin system architecture page.
Building¶
The sniffers and their ZeroMQ/msgpack dependencies are opt-in — a normal build fetches nothing new:
Adding a sniffer to a configuration¶
You name the peripheral to watch; the sniffer reaches that peripheral's bus
or stream through it. Add a declarative spec to EmulatorConfig::plugins:
#include "lince/plugins/can_sniffer.hpp"
#include "lince/plugins/serial_sniffer.hpp"
auto cfg = lince::runtime::gr712rc_config();
// Watch the CAN traffic the OCCAN controller "occan0" is connected to:
cfg.plugins.push_back(lince::plugins::make_can_sniffer_spec(
"can0-sniffer", {.peripheral = "occan0", .endpoint = "tcp://127.0.0.1:5556"}));
// Wiretap the console UART:
cfg.plugins.push_back(lince::plugins::make_serial_sniffer_spec(
"uart0-tap", {.peripheral = "apbuart0", .endpoint = "tcp://127.0.0.1:5560"}));
auto emu = lince::runtime::Emulator::create(std::move(cfg));
Each protocol has its own make_*_sniffer_spec and config struct
(CanSnifferConfig, SpiSnifferConfig, MilStd1553SnifferConfig,
SpaceWireSnifferConfig, SerialSnifferConfig). The config names the owning
peripheral (a CAN/SPI/1553 controller, a SpaceWire peripheral + port, or a
UART), the ZeroMQ endpoint, and the send high-water mark. The default endpoints
are 5556 (CAN), 5557 (SPI), 5558 (1553), 5559 (SpaceWire), 5560
(serial).
Filtering: on the subscriber¶
A sniffer publishes the whole wire; you filter on the subscriber using
ZeroMQ's native SUBSCRIBE prefix match — dynamically, with no change to the
running simulation. Every message is two frames, [topic, payload], where the
topic encodes the identifier:
| Protocol | Topic | Example |
|---|---|---|
| CAN | can/<hexid>/ |
can/100/ |
| SPI | spi/cs<n>/ |
spi/cs1/ |
| MIL-1553 | 1553/rt<n>/ |
1553/rt7/ |
| SpaceWire | spw/<hexaddr>/, spw/tc/ |
spw/20/ |
| serial | serial/tx/, serial/rx/ |
serial/tx/ |
The trailing slash means an exact-id subscription (can/100/) is not a prefix
of a longer id; a shorter prefix (can/1) selects a range, can/ a whole
protocol, and "" everything.
The Python client¶
The lince-sniffer Python package (in python/) decodes the envelopes into
typed objects and ships a CLI:
cd python && pip install -e . # pulls pyzmq + msgpack
python -m lince_sniffer tcp://127.0.0.1:5556 # all CAN-bus traffic
python -m lince_sniffer tcp://127.0.0.1:5556 --topic can/100/
python -m lince_sniffer tcp://127.0.0.1:5560 --serial # reassemble UART lines
As a library:
from lince_sniffer import SnifferClient, CanFrame
with SnifferClient("tcp://127.0.0.1:5556", topics=["can/"]) as client:
for msg in client:
print(msg.topic, msg.frame) # msg.frame is a typed dataclass
End-to-end demo¶
The sniffer-demo tool runs an emulator with a serial sniffer on the console
UART and a guest that prints to it. In two terminals:
# terminal 1 — the emulator + sniffer (waits for you to connect):
./build/plugins/demo/sniffer-demo tests/guest-programs/rtems/hello-world/hello-world.elf
# terminal 2 — the Python subscriber:
python -m lince_sniffer tcp://127.0.0.1:5560 --serial
Press Enter in terminal 1 once the subscriber is connected; the guest's
Hello World arrives decoded in terminal 2, confirming the whole
sniffer → ZeroMQ → msgpack → Python pipeline.