Добавить общее ядро DEIF, эмуляцию приборов и переносимые панели Python/Qt. Расширить загрузчики F2812 и F28335, обновление Flash и профиль STM Invertor. Добавить альтернативный МНК Eigen, модели неисправностей и диагностику сигналов. Обновить справочники устройств, проекты CCS и регрессионные проверки.
98 lines
16 KiB
Python
98 lines
16 KiB
Python
"""Локализованное представление DEIF поверх единственного C99-кодека templates.""" # Wire-формат, адресация и диапазоны не реализуются повторно в Python.
|
||
import ctypes as C # Преобразуются только структуры публичного ABI.
|
||
from protocan import protocan as pc # Существующие CAN-мониторы получают прежний тип Decoded.
|
||
from .deif_native import get_deif_native, Frame # C-каталог и готовые метаданные используются без Qt.
|
||
|
||
_API = get_deif_native() # Отсутствие общего DEIF-ядра является явной ошибкой нативной сборки.
|
||
_FIELD_NAMES = (("Электрическая мощность AC2", None), ("Обороты винта", "rpm"), ("Азимут / руль", "°"), ("Электрическая мощность AC2, %", "%"), ("Обороты винта, %", "%"), ("Азимут / руль", "°")) # Это только локализованные подписи, числовые объекты определяет C-каталог.
|
||
_MASTER_FIELDS = (5, 1, 4, 0, 3) # Соответствие прежнего порядка редакторов ID полей ABI; сборку цикла выполняет C.
|
||
MASTER_SELECTORS = tuple(_API.selector_bytes(field, 1) for field in _MASTER_FIELDS) # Селекторы возвращает единый C-каталог.
|
||
SELECTORS = dict(zip(MASTER_SELECTORS, ("Азимут / руль — задание", "RPM / обороты", "Обороты винта, %", "Электрическая мощность AC2", "Электрическая мощность AC2, %"))) # Тексты не влияют на проверку или отправку кадра.
|
||
SELECTORS[_API.selector_bytes(2, 1)] = "Азимут / руль — фактическое значение" # Фактический угол имеет отдельный объект каталога.
|
||
OBJECTS = {} # Сохраняет публичное представление названий семейств для прежних потребителей.
|
||
for _field in range(5): # Пять семейств поддерживаемого профиля индикации.
|
||
_metadata = (C.c_int32 * 3)() # C возвращает base, subindex и разрешение.
|
||
_API.catalog(_field, _metadata) # Базовый индекс не дублируется числовой таблицей Python.
|
||
OBJECTS[_metadata[0]] = _FIELD_NAMES[_field] # Имена остаются локализованными данными представления.
|
||
|
||
|
||
def decode(frame): # Форматирует готовый результат C для обеих таблиц Desktop.
|
||
result = pc.Decoded(pc.ProtoCanId(frame.can_id, 0, 0, 0, 0, 0, 0), len(frame.data), bytes(frame.data), ide=frame.ide, rtr=frame.rtr, protocol="DEIF XDi-192") # Контейнер совместим со штатным монитором CAN.
|
||
result.fields = [("Протокол", "DEIF XDi-net / CANopen SAM-MPDO"), ("CAN ID", "0x%03X" % frame.can_id)] # Начальные подписи не зависят от структуры wire-полей.
|
||
code, metadata = _API.inspect_frame(frame.can_id, frame.data, frame.ide, frame.rtr) # Единственный разбор сообщения выполняется в C.
|
||
node, index, subindex, raw, field, instance, status, dam, padding, nmt = metadata[1:11] # Это поля документированного ABI, не смещения CAN-payload.
|
||
if code in (2, -2, -3): # C определяет принадлежность к CANopen Heartbeat/Boot-up.
|
||
result.device_label, result.message_label = "Node %d" % node, "Heartbeat / Boot-up" # Исходный тип пригоден также для ошибки DLC.
|
||
result.fields.append(("Node-ID", str(node))) # Номер уже вычислен общим кодеком.
|
||
if code != -2: # При корректной длине сохраняется исходный NMT-код, включая неизвестный.
|
||
states = {0: ("Boot-up", "запуск узла"), 4: ("Stopped", "узел остановлен"), 5: ("Operational", "рабочий режим"), 127: ("Pre-operational", "предоперационный режим")} # Локализация кодов состояния; допустимость уже проверена C.
|
||
state, meaning = states.get(nmt, ("состояние 0x%02X" % nmt, "неизвестное состояние NMT")) # Неизвестный байт не теряется в представлении.
|
||
message = "Boot-up" if nmt == 0 else "Heartbeat" if code == 2 else "Error control" # Подтверждённый тип heartbeat сообщает C-декодер.
|
||
result.message_label = "CANopen " + message # Отдельный тип исключает Boot-up из статистики периода HB.
|
||
result.fields[0] = ("Протокол", result.message_label) # Служебный кадр не маркируется как SAM-MPDO.
|
||
result.fields.extend([("COB-ID", "0x%03X = 0x700 + %d" % (frame.can_id, node)), ("NMT state", "0x%02X (%d)" % (nmt, nmt)), ("Состояние", "%s — %s" % (state, meaning))]) # Формулы выводятся для пояснения пользователю, а не для декодирования.
|
||
result.summary = "%s · Node %d: %s — %s" % (message, node, state, meaning) # Сводка остаётся полезной при скрытой колонке типа.
|
||
return result # Heartbeat не трактуется как измерение.
|
||
errors = {-1: "DEIF: ожидается STD data frame; EXT/RTR не интерпретируется", -2: "DEIF: Heartbeat Node %d — ожидается DLC=1" % node, -4: "DEIF RPDO1: ожидается DLC=8", -5: "DEIF: источник или получатель не соответствует COB-ID", -6: "DEIF: неизвестный объект/подиндекс 0x%04X:%02X" % (index, subindex)} # Причины задаёт C, здесь находятся только русские тексты.
|
||
if code in errors: # Неуспешный разбор не получает фиктивных RAW-полей.
|
||
result.summary = errors[code] # Понятная причина отказа видна в таблице без открытия подробностей.
|
||
result.warnings.append(result.summary) # Сохраняет существующий API диагностики.
|
||
return result # Порт не пытается повторно угадать формат байтов.
|
||
if code == 0: # COB-ID другого протокола не считается измерением DEIF.
|
||
result.summary = "DEIF: CAN ID не описан в журнале" # Исходный кадр сохраняется в raw-колонках монитора.
|
||
return result # Неизвестному ID не назначаются физические единицы.
|
||
result.message_label = "CANopen DAM-MPDO" if dam else "XDi-net broadcast / SAM-MPDO" # Режим уже определён общей проверкой адресации.
|
||
result.fields[0] = ("Протокол", "CANopen DAM-MPDO" if dam else "DEIF XDi-net / CANopen SAM-MPDO") # В подробностях показывается действующий режим.
|
||
result.device_label = ("Получатель Node %d" if dam else "Источник Node %d") % node # Node-ID получателя не подписывается источником SAM.
|
||
result.fields.extend([("COB-ID", "0x%03X" % frame.can_id), ("Селектор Byte0..3", bytes(frame.data[:4]).hex(" ").upper()), ("Получатель Node-ID" if dam else "Источник Node-ID", str(node)), ("Индекс объекта", "0x%04X" % index), ("Подиндекс", "0x%02X" % subindex)]) # Raw-байты показываются для проверки, алгоритм их не интерпретирует.
|
||
if code == 3: # C отделяет U8 диммера от переменных I16.
|
||
result.summary = "Node %d · 0x%04X:%02X · %s: %d" % (node, index, subindex, "Яркость, %" if field == 6 else "Color Day/Night/Dusk", raw) # Сохраняет понятную сводку команд подсветки.
|
||
return result # Диммер не получает ложный масштаб десятых долей RPM.
|
||
name, unit = _FIELD_NAMES[field] # Название выбирается по готовому field ID общего каталога.
|
||
kind = "фактическое значение" if subindex == 2 else "задание" # Тип значения показывается пользователю отдельно от физического числа.
|
||
parameter = "%s №%d — %s" % (name, instance, kind) # Номер экземпляра поступает из C-декодера.
|
||
result.fields.extend([("Параметр", parameter), ("RAW INT16 LE", str(raw)), ("Byte6..7", bytes(frame.data[6:]).hex(" ").upper())]) # Для диагностики сохраняется исходное значение и raw-хвост.
|
||
result.summary = "Node %d · 0x%04X:%02X · %s: RAW=%d" % (node, index, subindex, parameter, raw) # Формат совместим с прежними фильтрами и тестами.
|
||
if status: # Зарезервированный I16 проверен единственным C-кодеком.
|
||
text = {1: "ниже диапазона", 2: "выше диапазона", 3: "ошибка данных"}[status] # Статус локализуется без повторного сравнения raw-кодов.
|
||
result.fields.append(("Статус", text)) # Ошибка не выдаётся за огромную физическую величину.
|
||
result.warnings.append(text) # Сохраняет предупреждение для таблицы монитора.
|
||
result.summary += "; " + text # Причина видна без отдельного окна.
|
||
elif unit and (subindex == 2 or field == 2): # Физическое форматирование подтверждено для факта и азимутального задания рабочей библиотеки.
|
||
text = "%.1f %s" % (raw / 10, unit) # Это форматирование числа для человека, принятие и масштаб индикаторов выполняет C.
|
||
result.fields.extend([("Значение", text), ("Масштаб", "RAW ×0.1")]) # Подпись раскрывает используемое разрешение.
|
||
result.summary += "; " + text # Краткая сводка содержит единицу измерения.
|
||
elif field == 0 and subindex == 2: # Абсолютная POWER не имеет подтверждённой физической единицы.
|
||
result.fields.append(("Масштаб / единицы", "RAW ×1; физическая единица не подтверждена")) # Единица не достраивается по предположению.
|
||
result.summary += "; RAW ×1 (физическая единица не подтверждена)" # Сохраняет прежний смысл шкалы мощности.
|
||
else: # Разрешение иных set-point зависит от библиотеки прибора.
|
||
result.fields.append(("Масштаб / единицы", "не установлены")) # Неизвестный масштаб остаётся явным ограничением.
|
||
result.summary += "; масштаб и единицы не установлены" # Не назначает единицы неизвестному объекту.
|
||
if not padding: # C уже проверил старшие байты I16.
|
||
result.warnings.append("Byte6..7: для I16 ожидается нулевое заполнение") # Ненулевой хвост виден в диагностике и отклоняется моделью приёма.
|
||
return result # На выходе находится только представление результата C.
|
||
|
||
|
||
def master_frames(node_id, raw_values, can_id=None, mode="sam", angle_subindex=7): # Совместимый API генерирует полный цикл через C.
|
||
from .can_transport import build_frame # Транспортный контейнер не задаёт правила DEIF.
|
||
values = list(raw_values) # Порт материализует входной iterable для фиксированного ABI.
|
||
if mode not in ("sam", "dam") or type(node_id) is not int or not 0 <= node_id <= 127 or len(values) != 5 or any(type(value) is not int or not -2147483648 <= value <= 2147483647 for value in values) or (can_id is not None and (type(can_id) is not int or not 0 < can_id <= 0x1FFFFFFF)): # Проверяются только представимость ABI и число аргументов цикла.
|
||
raise ValueError("DEIF: неверный Node-ID, режим, CAN ID или пять RAW-параметров") # Значения не усекаются ctypes незаметно.
|
||
source, output = (C.c_int32 * 5)(*values), (Frame * 5)() # Оба буфера имеют явный размер и принадлежат Python.
|
||
if _API.master(node_id, source, 0 if can_id is None else can_id, mode == "dam", angle_subindex, output) != 5: # Порядок объектов, I16 и адресацию проверяет общий C-код.
|
||
raise ValueError("DEIF: Node-ID, RPDO, подиндекс азимута или RAW вне диапазона") # Частично собранный цикл не передаётся.
|
||
return [build_frame(item.can_id, bytes(item.data[:item.dlc]), ide=False) for item in output] # Транспорт получает готовые wire-байты.
|
||
|
||
|
||
_PRESET_FIELDS = {"power": 0, "rpm": 1, "angle": 5, "angle_actual": 2, "power_percent": 3, "rpm_percent": 4} # Совместимые имена редакторов связываются с field ID ABI.
|
||
_PRESET_NAMES = {"power": "POWER · AC2 (RAW)", "rpm": "RPM · обороты (×0.1)", "angle": "Угол · задание (×0.1°)", "angle_actual": "Угол · фактический (×0.1°)", "power_percent": "POWER% · AC2 (×0.1%)", "rpm_percent": "RPM% (×0.1)"} # Подписи относятся только к представлению.
|
||
DISPLAY_PRESETS = {} # Числовые индексы не дублируются таблицей Python.
|
||
for _key, _field in _PRESET_FIELDS.items(): # Значения каталога читаются из C при загрузке расширения.
|
||
_metadata = (C.c_int32 * 3)() # Публичный ABI выдаёт base/subindex/resolution.
|
||
_API.catalog(_field, _metadata) # Общая библиотека является источником объекта и разрешения.
|
||
DISPLAY_PRESETS[_key] = (_PRESET_NAMES[_key], _metadata[0], _metadata[1], None if _metadata[2] == 127 else 10.0 ** _metadata[2]) # Конвертация показателя степени нужна только существующему редактору коэффициента.
|
||
|
||
|
||
def display_preset(key, node, instance=1, dam=False): # Сохраняет прежний API выбора библиотечного объекта.
|
||
return _API.selector_bytes(_PRESET_FIELDS[key], node, instance, dam).hex(" ").upper(), DISPLAY_PRESETS[key][3] # Ни endian, ни арифметика индексов не повторяются в Python.
|