Extract reusable device protocols, ports and logic analyzers from SETGUI

This commit is contained in:
2026-09-23 20:11:37 +03:00
parent 80ba17d77d
commit 795a1279b1
63 changed files with 10181 additions and 6 deletions

View File

@@ -0,0 +1,102 @@
"""Служебный уровень моста CAN <-> RS485: его кадры и его регистры.
Мост пересылает кадры шины прозрачно, но два вида трафика принадлежат
ему самому: служебный кадр с ``FLAGS.ERR`` (состояние CAN и счётчики
потерь) и блок регистров ``0xFF00`` в общем адресном пространстве.
Разбор обоих собран здесь, чтобы вкладка шины не знала подробностей.
Модуль не импортирует Qt и проверяется host-тестами.
"""
from __future__ import annotations
from . import can_transport as tr
from protocan import protocan as pc
from . import balsam_can
#: Расшифровка поля LEC регистра CAN_ESR.
CAN_ESR_LEC = {
0: "нет ошибок",
1: "stuff error",
2: "form error",
3: "acknowledgment error",
4: "bit recessive error",
5: "bit dominant error",
6: "CRC error",
7: "флаг сброшен ПО",
}
#: Имена диагностических регистров моста, см. CAN_to_RS485/fw/Inc/bridge.h.
GAS_NAMES = {
0xFF00: "версия транспорта",
0xFF01: "время работы, с",
0xFF02: "CAN_ESR, младшие 16 бит",
0xFF03: "CAN TEC — ошибки передачи",
0xFF04: "CAN REC — ошибки приёма",
0xFF05: "принято из CAN (младшие)",
0xFF06: "принято из CAN (старшие)",
0xFF07: "передано в CAN (младшие)",
0xFF08: "передано в CAN (старшие)",
0xFF09: "потеряно кадров CAN",
0xFF0A: "кадров принято с RS485",
0xFF0B: "ошибок CRC на RS485",
0xFF0C: "кадров не отдано в RS485",
0xFF0D: "кадров принято с UART GUI",
0xFF0E: "битрейт CAN, кбит/с",
0xFF0F: "скорость UART / 100",
}
#: Сколько регистров GAS помещается в один кадр шины (8 байт данных).
GAS_REGS_PER_FRAME = 4
def decode_frame(frame: tr.Frame) -> pc.Decoded:
"""@brief Разбирает кадр: служебные кадры моста — отдельно от ProtoCAN.
@param frame Кадр, снятый с полевого канала моста.
@return Разбор прикладного уровня либо расшифровка служебного кадра.
"""
if not frame.is_error:
decoded_balsam = balsam_can.decode(
frame.can_id, frame.data, ide=frame.ide, rtr=frame.rtr)
if decoded_balsam is not None:
return decoded_balsam
return pc.decode(frame.can_id, frame.data, ide=frame.ide, rtr=frame.rtr)
dec = pc.Decoded(id=pc.ProtoCanId.parse(frame.can_id), dlc=len(frame.data),
data=frame.data, ide=frame.ide, rtr=frame.rtr)
d = frame.data
if len(d) >= 7:
esr, tec, rec = d[0], d[1], d[2]
can_ovr = d[3] | (d[4] << 8)
rs_err = d[5] | (d[6] << 8)
lec = (esr >> 4) & 0x7
dec.summary = ("МОСТ: TEC=%d REC=%d, %s%s%s, потери CAN=%d, ошибки RS485=%d"
% (tec, rec, CAN_ESR_LEC.get(lec, "?"),
", BUS-OFF" if esr & 0x4 else "",
", passive" if esr & 0x2 else "",
can_ovr, rs_err))
dec.fields = [
("Источник", "служебный кадр моста (FLAGS.ERR)"),
("CAN_ESR[7:0]", "0x%02X" % esr),
(" Последняя ошибка", CAN_ESR_LEC.get(lec, "?")),
(" EWGF / EPVF / BOFF", "%d / %d / %d" % (esr & 1, (esr >> 1) & 1,
(esr >> 2) & 1)),
("Счётчик ошибок передачи (TEC)", str(tec)),
("Счётчик ошибок приёма (REC)", str(rec)),
("Потеряно кадров CAN", str(can_ovr)),
("Ошибок/потерь RS485", str(rs_err)),
]
if esr & 0x4:
dec.warnings.append("Шина CAN в состоянии BUS-OFF")
elif esr & 0x2:
dec.warnings.append("Шина CAN в состоянии error passive")
if can_ovr:
dec.warnings.append("Мост потерял %d кадр(ов) CAN из-за переполнения" % can_ovr)
if rs_err:
dec.warnings.append("На стороне RS485 %d ошибок/потерянных байт" % rs_err)
else:
dec.summary = "МОСТ: служебный кадр, %d байт" % len(d)
dec.fields = [("Источник", "служебный кадр моста (FLAGS.ERR)"),
("Данные", tr.hex_str(d))]
return dec