Files
templates/python/set_devices/deif_xdi.py
T
Andrey 41e24629ce Расширить DEIF, загрузку CAN и общую обработку сигналов
Добавить общее ядро DEIF, эмуляцию приборов и переносимые панели Python/Qt.
Расширить загрузчики F2812 и F28335, обновление Flash и профиль STM Invertor.
Добавить альтернативный МНК Eigen, модели неисправностей и диагностику сигналов.
Обновить справочники устройств, проекты CCS и регрессионные проверки.
2026-10-24 15:40:51 +03:00

98 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Локализованное представление 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.