CAN Trace Converter

Diagnosing a BMS contactor fault in an electric Ford Ranger

Objective

A friend runs electric Ford Ranger conversions on NiMH packs, and one of his trucks developed a battery-side fault. The battery management system narrates everything it knows onto the CAN bus — but his logger captured that traffic as plain text, one frame per line, which no proper CAN analysis tool will open. The job was to build a conversion tool that turns those raw text logs into the .trc trace format native to PEAK-System's PCAN tools, so the BMS traffic could be filtered, decoded, and replayed in PCAN-View instead of being eyeballed in a text editor.

White electric Ford Ranger conversion parked in a lot, charging through a cable plugged in at the front grille
One of the converted Rangers on charge

Technology

CAN Bus Python .trc Trace Format PCAN-View BMS Diagnostics EV Conversion

The Raw Data

The logs arrived as text files with one CAN frame per line: a timestamp in seconds, the (extended) CAN ID in hex, the DLC, and the payload bytes:

0.001 1F334455 8 01 02 03 04 05 06 07 08

Everything needed to reconstruct the bus traffic is there — but trace-analysis tools expect a structured trace file. The .trc format adds what the raw lines lack: a header describing the capture, a record number per frame, the channel, the direction (Tx/Rx), and a frame-type flag, all in fixed columns the software can parse.

The Conversion

The converter is a small Python script: split each line into its fields, then re-emit it as a numbered .trc record under a generated header. The core of it:

# Sample raw CAN data (timestamp, CAN ID, DLC, data)
raw_data = [
    "0.001 1F334455 8 01 02 03 04 05 06 07 08",
    # ... the rest of the log
]

# Convert to .trc format
with open("output.trc", "w") as trc_file:
    # Write a header (customize as needed)
    trc_file.write("date 2023-04-01 base hex  timestamps absolute\n")
    trc_file.write("no internal events logged\n")
    # Begin with line 2 after the header
    line_no = 2

    for line in raw_data:
        parts = line.split()
        timestamp = parts[0]
        can_id = parts[1]
        dlc = parts[2]
        data = ' '.join(parts[3:])

        # Format line according to .trc specifications
        trc_line = f"{line_no} {timestamp} 1  Rx   d {dlc} {can_id} {data}\n"

        trc_file.write(trc_line)
        line_no += 1

The structure is simple on purpose: the parsing assumptions live in one place, so when a log from a different tap shows up with a different line shape, only the split() mapping changes — the .trc emission stays put. Every captured frame is marked Rx, since the logger was a passive listener on the bus.

The Diagnosis

With the logs converted, the BMS traffic could finally be loaded into PCAN-View, filtered by CAN ID, and walked frame-by-frame on a real timeline. The trace pointed to a contactor fault: the high-voltage contactor — the relay that connects the pack to the rest of the truck — wasn't behaving the way the BMS expected, and the converted traces made the mismatch between what the BMS commanded and what it saw visible enough to pin down. A text file full of hex became a diagnosis.