"""Служебный уровень моста 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