feat(set-protocol): добавь порт F407 для DevBoard_V1
This commit is contained in:
60
python/protocan/devboard_v1.py
Normal file
60
python/protocan/devboard_v1.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""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"))
|
||||
14
python/tests/test_devboard_v1.py
Normal file
14
python/tests/test_devboard_v1.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from protocan.devboard_v1 import BoardFrame, command_setup, command_transmit, parse_frame_line
|
||||
|
||||
|
||||
def test_parse_can_and_rs485_lines():
|
||||
can = parse_frame_line("[ 12.345] EXT 0x073700A2 DLC=2 DATA=34 12 |4.|")
|
||||
rs = parse_frame_line("[ 12.346] RS485 STD 0x123 RTR DLC=4")
|
||||
assert can and can.can_id == 0x073700A2 and can.data == b"\x34\x12"
|
||||
assert rs and rs.source == "RS485" and rs.rtr and rs.data == b""
|
||||
|
||||
|
||||
def test_commands_are_firmware_compatible_and_lf_terminated():
|
||||
frame = BoardFrame(0.0, True, 0x073700A2, b"\x34\x12")
|
||||
assert command_transmit(frame) == b"T,E,73700A2,2,3412\n"
|
||||
assert command_setup() == (b"S500\n", b"M0\n", b"B512000\n", b"Q3\n")
|
||||
Reference in New Issue
Block a user