From 294f47be0139fab0e10a08ab2cf0b38356c0c212 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 4 Jul 2026 03:15:30 -0400 Subject: [PATCH] fix: emit NFC config packets last so firmware keeps flash/data_extended (M4) Python emitted the NFC packet (0x2A) before flash_config (0x2B) and data_extended (0x2C). Firmware has no case 0x2A and its default case skips straight to the CRC, so any config containing NFC entries silently lost flash_config and data_extended on device load. Emit NFC after every packet type the firmware understands. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JRrm95f1qNZzDM9r2SB6KW --- src/opendisplay/protocol/config_serializer.py | 16 +++-- tests/unit/test_nfc_packet_ordering.py | 69 +++++++++++++++++++ 2 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 tests/unit/test_nfc_packet_ordering.py diff --git a/src/opendisplay/protocol/config_serializer.py b/src/opendisplay/protocol/config_serializer.py index 7a077b7..8e48ff3 100644 --- a/src/opendisplay/protocol/config_serializer.py +++ b/src/opendisplay/protocol/config_serializer.py @@ -612,12 +612,6 @@ def serialize_config(config: GlobalConfig) -> bytes: packet_data += bytes([i, PACKET_TYPE_PASSIVE_BUZZER]) packet_data += serialize_passive_buzzer(bz) - for i, nfc in enumerate(config.nfc_configs): - if i >= 4: - break - packet_data += bytes([i, PACKET_TYPE_NFC_CONFIG]) - packet_data += serialize_nfc_config(nfc) - for i, flash in enumerate(config.flash_configs): if i >= 4: break @@ -628,6 +622,16 @@ def serialize_config(config: GlobalConfig) -> bytes: packet_data += bytes([0, PACKET_TYPE_DATA_EXTENDED]) packet_data += serialize_data_extended(config.data_extended) + # NFC (0x2A) is emitted LAST: firmware has no case 0x2A and its default case + # skips straight to the CRC, so any packet that follows an NFC entry (e.g. + # flash_config 0x2B or data_extended 0x2C) would be silently dropped on the + # device. Keeping NFC after every packet firmware understands avoids that. + for i, nfc in enumerate(config.nfc_configs): + if i >= 4: + break + packet_data += bytes([i, PACKET_TYPE_NFC_CONFIG]) + packet_data += serialize_nfc_config(nfc) + # Validate size (max 4096 bytes including wrapper and CRC) total_size = len(packet_data) + 2 # +2 for CRC if total_size > 4096: diff --git a/tests/unit/test_nfc_packet_ordering.py b/tests/unit/test_nfc_packet_ordering.py new file mode 100644 index 0000000..6e4860d --- /dev/null +++ b/tests/unit/test_nfc_packet_ordering.py @@ -0,0 +1,69 @@ +"""Test that NFC (0x2A) packets are emitted after packets firmware parses (M4).""" + +from __future__ import annotations + +from opendisplay.models.config import ( + DataExtended, + FlashConfig, + GlobalConfig, + ManufacturerData, + NfcConfig, + PowerOption, + SystemConfig, +) +from opendisplay.protocol.config_parser import _get_packet_size +from opendisplay.protocol.config_serializer import ( + PACKET_TYPE_DATA_EXTENDED, + PACKET_TYPE_FLASH_CONFIG, + PACKET_TYPE_NFC_CONFIG, + serialize_config, +) + + +def _packet_type_order(blob: bytes) -> list[int]: + """Return the packet-type IDs in the serialized TLV stream, in order.""" + data = blob[3:-2] # strip 3-byte wrapper header and 2-byte CRC + order: list[int] = [] + off = 0 + while off < len(data) - 1: + ptype = data[off + 1] + size = _get_packet_size(ptype) + if size is None: + break + order.append(ptype) + off += 2 + size + return order + + +def test_nfc_emitted_after_flash_and_data_extended() -> None: + config = GlobalConfig( + system=SystemConfig(ic_type=0, communication_modes=0, device_flags=0, pwr_pin=0xFF, reserved=b"\x00" * 15), + manufacturer=ManufacturerData(manufacturer_id=0, board_type=0, board_revision=0, reserved=b"\x00" * 6), + power=PowerOption( + power_mode=0, + battery_capacity_mah=b"\x00\x00\x00", + sleep_timeout_ms=0, + tx_power=0, + sleep_flags=0, + battery_sense_pin=0xFF, + battery_sense_enable_pin=0xFF, + battery_sense_flags=0, + capacity_estimator=0, + voltage_scaling_factor=0, + deep_sleep_current_ua=0, + deep_sleep_time_seconds=0, + reserved=b"\x00" * 10, + ), + nfc_configs=[NfcConfig.from_bytes(bytes(32))], + flash_configs=[FlashConfig.from_bytes(bytes(32))], + data_extended=DataExtended(), + ) + + order = _packet_type_order(serialize_config(config)) + + assert PACKET_TYPE_NFC_CONFIG in order + assert PACKET_TYPE_FLASH_CONFIG in order + assert PACKET_TYPE_DATA_EXTENDED in order + nfc_pos = order.index(PACKET_TYPE_NFC_CONFIG) + assert nfc_pos > order.index(PACKET_TYPE_FLASH_CONFIG) + assert nfc_pos > order.index(PACKET_TYPE_DATA_EXTENDED)