Files
templates/python/protocan/devboard_v1.py

61 lines
1.9 KiB
Python

"""CAN485 DevBoard_V1 USB command and frame helpers (no GUI/serial dependency)."""
from __future__ import annotations
import re
from dataclasses import dataclass
FRAME_RE = re.compile(
r"^\[\s*(\d+)\.(\d{3})\]\s+(?:(RS485)\s+)?"
r"(EXT|STD)\s+0x([0-9A-Fa-f]+)\s+"
r"(RTR\s+)?DLC=(\d)(?:\s+DATA=([0-9A-Fa-f ]*))?"
)
@dataclass(frozen=True, slots=True)
class BoardFrame:
timestamp: float
extended: bool
can_id: int
data: bytes
rtr: bool = False
source: str = "CAN"
def parse_frame_line(line: str) -> BoardFrame | None:
match = FRAME_RE.match(line.strip())
if match is None:
return None
seconds, millis, source, frame_type, raw_id, raw_rtr, raw_dlc, raw_data = match.groups()
dlc = int(raw_dlc)
data = bytes.fromhex(raw_data or "")
rtr = bool(raw_rtr)
if (not rtr and len(data) != dlc) or (rtr and data):
return None
extended = frame_type == "EXT"
can_id = int(raw_id, 16)
if can_id > (0x1FFFFFFF if extended else 0x7FF):
return None
return BoardFrame(int(seconds) + int(millis) / 1000.0, extended, can_id,
data, rtr, source or "CAN")
def command_transmit(frame: BoardFrame) -> bytes:
if len(frame.data) > 8:
raise ValueError("DLC cannot exceed 8")
kind = "E" if frame.extended else "S"
return f"T,{kind},{frame.can_id:X},{len(frame.data)},{frame.data.hex().upper()}\n".encode("ascii")
def command_setup(*, can_bitrate: int = 500, rs485_baud: int = 512000,
route: int = 3) -> tuple[bytes, ...]:
if can_bitrate not in (25, 50, 100, 125, 250, 500, 800, 1000):
raise ValueError("unsupported CAN bitrate")
if not 1200 <= rs485_baud <= 4_000_000:
raise ValueError("unsupported RS485 baud")
if route not in range(4):
raise ValueError("route must be 0..3")
return (f"S{can_bitrate}\n".encode("ascii"), b"M0\n",
f"B{rs485_baud}\n".encode("ascii"), f"Q{route}\n".encode("ascii"))