#!/usr/bin/env python3 """ Симуляция логики IR_Encoder::buildGateRuns (IR-protocol) и утилиты CRC8. Запуск из корня репозитория: python docs/scripts/ir_protocol_gate_runs_sim.py Или: python ir_protocol_gate_runs_sim.py из каталога scripts/ """ from __future__ import annotations import sys # --- IR_config.h (фрагмент) --- bitPauseTakts = 12 bitActiveTakts = 25 preambPulse = 3 syncBits = 3 bitPerByte = 8 preambToggle = ((bitPauseTakts * 2 + bitActiveTakts) * 2 - 1) bitHigh = [(bitPauseTakts) * 2 - 1, (bitActiveTakts) * 2 - 1] bitLow = [(bitPauseTakts // 2 + bitActiveTakts) * 2 - 1, (bitPauseTakts) - 1] preamb, data, sync, noSignal = 0, 1, 2, 3 HIGH = True def crc8(data: bytes, start: int, end: int, poly: int) -> int: """Как IR_FOX::crc8 в IR_config.cpp: [start, end).""" crc = 0xFF for i in range(start, end): crc ^= data[i] for _ in range(8): if (crc & 0x80) != 0: crc = ((crc << 1) ^ poly) & 0xFF else: crc = (crc << 1) & 0xFF return crc def crc_pair_over_wire(packet: bytes) -> tuple[int, int]: """Два байта CRC как в IR_Encoder::sendDataFULL (poly1 старший, poly2 младший).""" ps = len(packet) if ps < 2: return 0, 0 b1 = crc8(packet, 0, ps - 2, 0x31) & 0xFF b2 = crc8(packet, 0, ps - 1, 0x8C) & 0xFF return b1, b2 # Как dataByteSizeMax в IR_config.h (msg+addr+addr+bytePerPack+crc) DATA_BYTE_SIZE_MAX = 1 + 2 + 2 + 31 + 2 def build_gate_runs(packet: bytes): """ Повторяет IR_Encoder::buildGateRuns: список (gate: bool, lenTicks: int), сумма lenTicks = totalTicks DMA. Буфер дополняется нулями до dataByteSizeMax, как sendBufferLocal[dataByteSizeMax] в C++. """ send_len = len(packet) send_buf = bytearray(packet) + bytes(max(0, DATA_BYTE_SIZE_MAX - len(packet))) toggle = preambToggle data_bit = bitPerByte - 1 data_byte = 0 preamb_front = preambPulse * 2 - 1 data_seq = bitPerByte * 2 sync_seq = syncBits * 2 sync_last = False sig = preamb state = HIGH cur_seq = bitHigh runs: list[tuple[bool, int]] = [] outer_steps = 0 while True: outer_steps += 1 gate = state run_len = toggle + 1 # как в C++: (uint16_t)toggleCounterLocal + 1U if runs and runs[-1][0] == gate: g, ln = runs[-1] runs[-1] = (g, ln + run_len) else: runs.append((gate, run_len)) while True: if sig == noSignal: return runs, outer_steps if sig == preamb: if preamb_front: preamb_front -= 1 toggle = preambToggle break sig = data state = not False continue if sig == data: if data_seq: if not (data_seq & 1): cur_seq = bitHigh if ((send_buf[data_byte] >> data_bit) & 1) else bitLow data_bit -= 1 toggle = cur_seq[not state] data_seq -= 1 break sync_last = send_buf[data_byte] & 1 data_byte += 1 data_bit = bitPerByte - 1 data_seq = bitPerByte * 2 sig = sync continue if sig == sync: if sync_seq: if not (sync_seq & 1): if sync_seq == 2: cur_seq = bitLow if (send_buf[data_byte] & 0x80) else bitHigh else: cur_seq = bitLow if sync_last else bitHigh sync_last = not sync_last toggle = cur_seq[not state] sync_seq -= 1 break sig = data sync_seq = syncBits * 2 if data_byte >= send_len: sig = noSignal continue return [], 0 state = not state def main() -> int: print("IR-protocol: preambToggle =", preambToggle) print() # Пример из лога: 8-байтный эхо-пакет Version_Query (CRC OK на приёме) echo = bytes.fromhex("C8 FA 2A FD E8 5D AA B4") c1, c2 = crc_pair_over_wire(echo) print("8 байт (эхо): CRC вычисленный:", f"{c1:02X}", f"{c2:02X}", "| на проводе:", f"{echo[6]:02X}", f"{echo[7]:02X}") runs8, steps8 = build_gate_runs(echo) total8 = sum(r[1] for r in runs8) print(" buildGateRuns: внешних шагов FSM =", steps8, ", totalTicks =", total8, ", число run-сегментов =", len(runs8)) print() # 31 байт из лога Frame reject (пример) reject = bytes.fromhex( "DF 00 00 FA 2A 5E 43 61 72 5F 76 34 2E 33 2E 38 5F 5B 31 32 4D 68 7A 5D 6B ED 1D 9A 53 96 62" ) if len(reject) == 31: c1, c2 = crc_pair_over_wire(reject) print("31 байт (reject): CRC по телу 0..28 должен быть:", f"{c1:02X}", f"{c2:02X}", "| байты [29:31]:", f"{reject[29]:02X}", f"{reject[30]:02X}") print(" Совпадение с формулой:", c1 == reject[29] and c2 == reject[30]) runs31, steps31 = build_gate_runs(reject) total31 = sum(r[1] for r in runs31) print(" buildGateRuns: внешних шагов =", steps31, ", totalTicks =", total31, ", run-сегментов =", len(runs31)) print() # Связь totalTicks с моделью «N тиков на сегмент до шага FSM» # total_build = sum(toggle_i + 1); если бы было sum(toggle_i), разница = steps theoretical_isr_ticks = total31 - steps31 print("Для 31-байт пакета: totalTicks (buildGateRuns) =", total31) print(" Если каждый внешний шаг даёт +1 к длине сегмента относительно ISR (runLen = toggle+1 vs toggle),") print(" оценка «ISR-тиков» как totalTicks - outer_steps =", theoretical_isr_ticks) return 0 if __name__ == "__main__": sys.exit(main())