Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/opendisplay/protocol/config_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
69 changes: 69 additions & 0 deletions tests/unit/test_nfc_packet_ordering.py
Original file line number Diff line number Diff line change
@@ -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)