78 lines
3.8 KiB
Python
78 lines
3.8 KiB
Python
# CAN-представление запросов регистратора periph_28335. Идентификаторы запросов и ответов
|
||
# зависят от режима; обратное преобразование выполняется по этому контракту, а не по формату
|
||
# общего моста.
|
||
|
||
"""CAN-транспорт расширения регистратора periph_28335.
|
||
|
||
Служебные запросы идут на штатный RX ID платы 0xBA0000 + Mode - 1.
|
||
Ответы имеют отдельный ID 0xBA0020 + Mode - 1. Технологическая телеметрия
|
||
не принимается за ответ логгера. Данные кадра следуют порядку MDL, MDH
|
||
существующей прошивки; каждое слово передаётся старшим байтом вперёд.
|
||
"""
|
||
from dataclasses import dataclass, field
|
||
import struct
|
||
from set_devices.ump_logger import BASE, DATA, LIVE, EMU_CONTROL
|
||
|
||
MARKER = 0x1F00
|
||
MAX_WORDS = 124
|
||
|
||
|
||
def request_ids(mode):
|
||
if not 1 <= mode <= 16:
|
||
raise ValueError('Номер CAN-платы должен быть 1…16 (Mode прошивки)')
|
||
return 0xBA0000 + mode - 1, 0xBA0020 + mode - 1
|
||
|
||
|
||
def build_request(mode, token, function, address, value):
|
||
if not 1 <= token <= 255:
|
||
raise ValueError('Неверный номер CAN-запроса')
|
||
valid = ((function == 3 and ((address == BASE and value == 16) or
|
||
(address == DATA and value in (40, 80, 120, 42, 84)) or
|
||
(address == LIVE and value == MAX_WORDS) or (address == EMU_CONTROL and value == 4))) or
|
||
(function == 6 and ((BASE <= address <= BASE+3 and 0 <= value <= 65535) or
|
||
(address == EMU_CONTROL+2 and value in (0, 1)))))
|
||
if not valid:
|
||
raise ValueError('Недопустимый запрос CAN-логгера')
|
||
can_id, _ = request_ids(mode)
|
||
return can_id, struct.pack('>4H', address, value, MARKER, token << 8 | function)
|
||
|
||
|
||
@dataclass
|
||
class Response:
|
||
"""Собирает один снимок. Дубликаты допустимы, противоречащие — ошибка.
|
||
|
||
Номер фрагмента позволяет восстановить порядок при задержках адаптера.
|
||
До получения всех фрагментов снимок не передаётся в график/архив.
|
||
"""
|
||
mode: int
|
||
token: int
|
||
function: int
|
||
address: int
|
||
value: int
|
||
fragments: dict = field(default_factory=dict)
|
||
|
||
def feed(self, can_id, data):
|
||
if can_id != request_ids(self.mode)[1] or len(data) != 8:
|
||
return None
|
||
first, second, header, tag = struct.unpack('>4H', bytes(data))
|
||
if not MARKER <= header < MARKER + 62 or tag >> 8 != self.token:
|
||
return None
|
||
if tag & 255:
|
||
raise ValueError(f'Плата отклонила CAN-запрос логгера: ошибка {tag & 255}')
|
||
count = self.value if self.function == 3 else 2
|
||
index = header - MARKER
|
||
if index >= count // 2:
|
||
raise ValueError('Лишний фрагмент CAN-ответа логгера')
|
||
words = (first, second)
|
||
if index in self.fragments and self.fragments[index] != words:
|
||
raise ValueError('Противоречащие фрагменты CAN-ответа')
|
||
self.fragments[index] = words
|
||
if len(self.fragments) != count // 2:
|
||
return None
|
||
result = tuple(word for n in range(count // 2) for word in self.fragments[n])
|
||
if self.function == 6:
|
||
if result != (self.address, self.value):
|
||
raise ValueError('CAN-плата подтвердила другую запись')
|
||
return ()
|
||
return result
|