"""Протокол кольцевого логгера УМП v2. Технологические регистры 0..127 не изменены. Расширение 0x1000 сначала проверяется по сигнатуре и версии: старая прошивка игнорировала старший байт адреса, поэтому диагностическое чтение безопасно попадёт в 0..15, но управляющие записи до успешной проверки запрещены в панели. CRC и проверка ответов остаются в общей библиотеке протокола ПМ35. """ from dataclasses import dataclass from protocan import periph28335 as protocol BASE = 0x1000 DATA = 0x1010 LIVE = 0x1100 EMU_CONTROL = 0x1200 FAST_CAPABILITY = 0x1220 FAST_DATA = 0x2000 WORDS = 40 CAPACITY = 64000 // (2 * WORDS) FIELDS = ( ('off_trigger', 'Событие отключения', 2, 8), ('post_truncated', 'Усечение постинтервала', 2, 9), ('start', 'Команда пуск УМП', 3, 7), ('reset', 'Команда сброс УМП', 3, 6), ('omega', 'Вход OMEGA', 4, 0), ('start_in', 'Вход Start_1', 4, 1), ('res_in', 'Вход Res_in', 4, 2), ('start_out', 'Выход START_DPTB', 5, 0), ('stop_out', 'Выход STOP_DPTB', 5, 1), ('res_out', 'Выход RES_OUT_2', 5, 2), ('impulse', 'Остаток импульса, мс', 6, None), ('wake', 'Инициализация ЦАП, тики ЦАП', 7, None), ('dac', 'Код ЦАП', 8, None), ('current', 'Расчётное задание тока, мА', 9, None), ('sensor0', 'Измерение 0', 10, None), ('sensor1', 'Измерение 1', 11, None), ('sensor2', 'Измерение 2', 12, None), ('sensor3', 'Измерение 3', 13, None), ('error0', 'Ошибки 0 (маска)', 14, None), ('error1', 'Ошибки 1 (маска)', 15, None), ('error2', 'Ошибки 2 (маска)', 16, None), ('error3', 'Ошибки 3 (маска)', 17, None), ('can_header', 'CAN адрес/маска', 18, None), ('can0', 'CAN слово 0', 19, None), ('can1', 'CAN слово 1', 20, None), ('can2', 'CAN слово 2', 21, None), ('can_count', 'Количество CAN RX (mod 65536)', 22, None), ('can_timeout', 'Таймауты CAN TX (mod 65536)', 23, None), ('commands', 'Команды (маска)', 3, None), ('go', 'Начальная уставка, мА', 24, None), ('stop', 'Конечная уставка, мА', 25, None), ('dac_min', 'Код калибровки 4 мА', 26, None), ('dac_max', 'Код калибровки 20 мА', 27, None), ('dac_cal', 'Калибровочный код ЦАП', 28, None), ('dac04', 'DAC_04 (регистр)', 29, None), ('dac20', 'DAC_20 (регистр)', 30, None), ('ramp', 'Счётчик разгона', 31, None), ('duration', 'Длительность разгона, тики ЦАП', 32, None), ('effective_go', 'Начальная уставка после ограничений, мА', 33, None), ('effective_stop', 'Конечная уставка после ограничений, мА', 34, None), ('position', 'Положение разгона (kod)', 35, None), ('adc0', 'АЦП канал 0', 36, None), ('adc1', 'АЦП канал 1', 37, None), ('adc2', 'АЦП канал 2', 38, None), ('adc3', 'АЦП канал 3', 39, None), ) def request(controller: int, function: int, address: int, value: int) -> bytes: if not 0 <= controller <= 255 or not 0 <= value <= 65535: raise ValueError('Недопустимый адрес контроллера или значение') if function == 3: valid = ((address == BASE and value == 16) or (address == DATA and 1 <= value <= 120 and value % WORDS == 0) or (address == LIVE and value == 124) or (address == EMU_CONTROL and value == 4) or (address == FAST_CAPABILITY and value == 4) or (FAST_DATA <= address < FAST_DATA + CAPACITY * WORDS and (address - FAST_DATA) % WORDS == 0 and 1 <= value <= 120 and value % WORDS == 0 and address + value <= FAST_DATA + CAPACITY * WORDS)) else: valid = function == 6 and (address in (BASE, BASE + 1, BASE + 2, BASE + 3) or (address == EMU_CONTROL + 2 and value in (0, 1))) if not valid: raise ValueError('Недопустимый запрос логгера УМП') return protocol.with_crc(bytes((controller, function)) + address.to_bytes(2, 'big') + value.to_bytes(2, 'big')) @dataclass(frozen=True) class Status: state: int count: int period: int mode: int frequency: int generation: int archive_count: int post_ms: int live_state: int @classmethod def decode(cls, words): if (len(words) != 16 or words[0:2] != (0x554D, 2) or words[4] != WORDS or words[5] != CAPACITY): raise ValueError('Прошивка не поддерживает логгер УМП v2') if words[9] != 6: raise ValueError('Логгер УМП доступен только на плате нагрузки dsk_LOAD') if words[2] not in (0, 1, 2, 3) or words[3] > CAPACITY or not 1 <= words[6] <= 1000 or words[11] != 1000 or words[13] > CAPACITY or words[14] > 1000 or words[15] not in (0, 1, 3): raise ValueError('Повреждён статус логгера') return cls(words[2], words[3], words[6], words[10], words[11], *words[12:16]) def sample(words, frequency=1000): if len(words) != WORDS or frequency <= 0: raise ValueError('Неверная запись логгера') result = {'time_ms': (words[0] | words[1] << 16) * 1000 / frequency, 'event': words[2]} for key, _label, index, bit in FIELDS: value = words[index] if bit is not None: value = (value >> bit) & 1 elif 10 <= index <= 13 and value >= 32768: value -= 65536 # Физические единицы приводим здесь, одинаково для CSV и графика. if key in ('current', 'effective_go', 'effective_stop'): value /= 10.0 result[key] = value return result def emulator_enabled(words): if len(words) != 4 or tuple(words[:2]) != (0x454D, 1) or words[2] not in (0, 1): raise ValueError('Плата не поддерживает управление эмуляцией STM') return bool(words[2])