CANtrip
How it's built: an architecture walkthrough
0:00 / 2:58
Every code panel is quoted from the CANtrip v1.5.0 source, lightly trimmed where marked. The incidents are real bugs found and fixed on real hardware.
Transcript
- How CANtrip is built. Processes, interfaces, wire formats and the decisions behind them.
- Windows has no SocketCAN. On Linux every adapter is a network interface behind one kernel API; on Windows every vendor ships its own DLL. The goals: don't write a capture stack, don't write a frame parser, keep every vendor behind one interface.
- Three processes, three channels. cantrip.exe starts tshark, tshark starts can2pcap.exe as an extcap. Frames travel as pcap records through a fifo and come back as JSON on stdout. can2pcap owns the hardware handle; transmit goes through a named pipe on PEAK or a second port on Vector.
- One interface, any vendor. IAvlabsCanBackend has initialize, uninitialize, a non-blocking readFrame and writeFrame. Each backend loads its vendor DLL at runtime, so a missing DLL just hides that vendor's channels.
- Vendor struct in, pcap record out. can2pcap's loop reads a frame and writes a 16-byte SocketCAN record (72 for CAN FD) in network byte order under LINKTYPE 227. The FD DLC code becomes a byte length here. Incident: frames once lost their tail because the raw code was written as the length.
- Wireshark does the parsing. tshark -T ek emits one JSON object per frame; TsharkCapture reads the can or canfd layer and data_data_data into a DecodedCanFrame. Gotcha: AUTOSAR NM matches ID 0, so tshark runs with that dissector disabled.
- Every frame takes the same path. Capture, your own transmits and replay all feed onFrameReceived. Logging sees every frame first; the display is throttled to the display rate. BusLoadTracker taps the capture directly and ignores replay. Incident: the UI froze on a busy bus until the throttle existed.
- DLC code or byte count? Above 8, FD DLC codes map to 12 to 64 bytes. CANtrip converts at three boundaries. Incident: a byte count of 64 sent as a DLC code caused bus errors and a crash on a Vector adapter.
- Sending while capturing. Vector allows a second port with a zero permission mask. PCAN-Basic refuses a second initialization, so frames go through a named pipe to can2pcap, which writes on its own handle. Incident: opening the port at capture start killed PEAK captures, so it now opens on the first send.
- Bus load, bit by bit. Each frame is costed in bits with a stuffing factor and timed at the right bitrate, then aggregated over a rolling one-second window and graphed through the existing Graph view.
- Runes: JSON that tolerates absence. No version field: a missing key gets its default, the graph layout migrates by shape, and the trace header is Qt's own saved state.
- Same classes, no widgets. main.cpp checks for --headless before creating any Qt application; QCoreApplication and HeadlessRunner reuse the plain classes. Ctrl+C sets a flag that a 200 ms timer reads on the main thread.
- Design rules. Reuse proven parts, one interface per vendor, plain non-GUI classes, one path for every frame, tolerate absence.