feat(registers): NTAG5 STATUS_REG / CONFIG_REG self-describing views

Add Ntag5StatusReg and Ntag5ConfigReg (Register subclasses) and bind
them as tag.status_reg / tag.config_reg on the NTAG 5 platform. Purely
additive: every existing per-field accessor (disable_nfc, arbiter_mode,
sram_enable, ...) stays, and the config_reg setter re-syncs the
_arbiter_mode / _sram_enabled sibling caches the per-field setters keep,
so the sim internals are untouched.

- Ntag5StatusReg (0xA0): field/VCC/boot/lock/EEPROM/SRAM status bits
- Ntag5ConfigReg (0xA1): disable_nfc, arbiter_mode (normal/mirror/
  pass-through/PHDC), sram_enable, config_pt_transfer_dir

Bit layout taken straight from the existing hand-rolled accessors, so
the register view decodes the very same bytes (cross-checked in tests).
5 new tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-14 08:54:24 -07:00
parent 0f597bc933
commit f8d01a9120
4 changed files with 131 additions and 4 deletions

View File

@@ -8,6 +8,7 @@ from pm3py.sim import (
MifareAccessConditions, MifareClassicTag,
EM4100Code, EM4100Tag,
NtagI2CConfig, NtagI2CSession, NtagI2CPtI2C, NT3H2111, NT3H2211,
Ntag5StatusReg, Ntag5ConfigReg, Ntag5LinkTag,
)
@@ -375,3 +376,58 @@ class TestNtagI2CRegisters:
assert "NTAG I2C config registers" in text
assert "transfer_dir" in text and "NFC->I2C" in text
assert "sram_mirror_block" in text
class TestNtag5Registers:
def test_config_reg_field_positions(self):
cfg = Ntag5ConfigReg(0)
cfg.arbiter_mode = "pass-through" # 0b10 at byte0 bits 3:2
cfg.sram_enable = True # byte0 bit1
cfg.disable_nfc = True # byte0 bit5
cfg.config_pt_transfer_dir = "NFC->I2C" # byte0 bit0
# byte0 = 0b0010_1011 = 0x2B (disable_nfc bit5 + arb=10 at bit3 + sram bit1 + ptdir bit0)
assert bytes(cfg)[0] == 0x2B
assert cfg.arbiter_mode == "pass-through"
assert cfg.disable_nfc is True
def test_status_reg_decode(self):
# byte0: nfc_field_ok(0)+vcc_supply_ok(1)+eeprom_wr_busy(7); byte1: nfc_if_locked(0)
st = Ntag5StatusReg(int.from_bytes(bytes([0b1000_0011, 0x01, 0, 0]), "big"))
assert st.nfc_field_ok is True
assert st.vcc_supply_ok is True
assert st.eeprom_wr_busy is True
assert st.sram_data_ready is False
assert st.nfc_if_locked is True
def test_view_agrees_with_existing_accessors(self):
tag = Ntag5LinkTag()
tag.arbiter_mode = 2 # existing per-field setter
tag.disable_nfc = True
tag.sram_enable = True
# the register view must decode the very same bytes
assert tag.config_reg.arbiter_mode == "pass-through"
assert tag.config_reg.disable_nfc is True
assert tag.config_reg.sram_enable is True
def test_assign_config_reg_updates_accessors_and_siblings(self):
tag = Ntag5LinkTag()
cfg = Ntag5ConfigReg(0)
cfg.arbiter_mode = "mirror" # 1
cfg.sram_enable = True
tag.config_reg = cfg
# existing property accessors (read straight off the bytes) must reflect it
assert tag.arbiter_mode == 1
assert tag.sram_enable is True
# and the sibling caches the individual setters maintain stay in sync
if hasattr(tag, "_arbiter_mode"):
assert tag._arbiter_mode == 1
if hasattr(tag, "_sram_enabled"):
assert tag._sram_enabled is True
def test_status_reg_binding_roundtrip(self):
tag = Ntag5LinkTag()
st = tag.status_reg
st.i2c_if_locked = True
tag.status_reg = st
assert tag.i2c_if_locked is True
assert tag.status_reg.i2c_if_locked is True