"""Classify live SocketCAN traffic. Requires python-can==4.6.1.

Article: https://dbcutility.com/blog/can-frame-types/
Configure the Linux CAN interface before running this script.
Set INTERFACE to "vcan0" for the article's virtual lab.
"""
import can


INTERFACE = "can0"
USE_FD = False


def classify_frame(message):
    if message.is_error_frame:
        return "ERROR report"
    if message.is_remote_frame:
        return "REMOTE request"
    if message.is_fd:
        return "CAN FD data"
    return "Classical CAN data"


def describe_frame(message):
    kind = classify_frame(message)
    if message.is_error_frame:
        details = message.data.hex(" ").upper()
        return f"{kind} | diagnostic bytes={details}"

    id_format = "29-bit" if message.is_extended_id else "11-bit"
    width = 8 if message.is_extended_id else 3
    identifier = f"0x{message.arbitration_id:0{width}X}"
    if message.is_remote_frame:
        content = f"requested length={message.dlc}; data bytes=0"
    else:
        data = message.data.hex(" ").upper() or "(empty)"
        content = f"data bytes={len(message.data)}; data={data}"
    return f"{kind} | {id_format} | ID={identifier} | {content}"


def main():
    try:
        with can.Bus(
            interface="socketcan",
            channel=INTERFACE,
            fd=USE_FD,
            ignore_rx_error_frames=False,
        ) as bus:
            for message in bus:
                print(describe_frame(message), flush=True)
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()
