feat(rawcli): finish T5577 catalog — page/password, RESET, hex data words
Round out the ATA5577C command set to the datasheet-catalog standard: - RESET command (downlink opcode 00), backed by a new lf.t55.reset() that drives CMD_LF_T55XX_RESET_READ. Hardware-validated (status 0). - READ/WRITE gain optional <page> (1 = traceability) and <password> for password-mode access, threaded through read_config / read_t55xx_block. - WRITE data and all passwords now parse as hex words (like the pm3 client's -d/-p and every other rawcli WRITE); blocks/pages stay decimal. Previously T5577 WRITE data alone was base-10, so WRITE(0, 00088040) wrote decimal. Live-validated on a T5577 emulating EM4100 00FFFFFFFF: DETECT/READ(0) decode the config 00148040 (ASK/RF64/EM4100), RESET/WAKE return ok. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,16 @@ def test_lf_t55_wakeup():
|
||||
assert pwd == 0x11223344
|
||||
assert flags == (3 << 3)
|
||||
|
||||
def test_lf_t55_reset():
|
||||
t = AsyncMock()
|
||||
lf = LFCommands(t)
|
||||
t.send_ng.return_value = make_response(Cmd.LF_T55XX_RESET_READ, 0, b"")
|
||||
asyncio.get_event_loop().run_until_complete(lf.t55.reset(downlink_mode=2))
|
||||
cmd, payload = t.send_ng.call_args.args[0], t.send_ng.call_args.args[1]
|
||||
assert cmd == Cmd.LF_T55XX_RESET_READ
|
||||
# single flags byte carrying downlink_mode<<3
|
||||
assert payload == struct.pack("<B", 2 << 3)
|
||||
|
||||
def test_lf_t55_config_client_side():
|
||||
t = AsyncMock()
|
||||
lf = LFCommands(t)
|
||||
|
||||
@@ -564,8 +564,12 @@ class TestCatalog:
|
||||
assert T5577.get("READ").build("0")[0] == 0x01
|
||||
assert T5577.get("WRITE").build("0", "0")[0] == 0x02
|
||||
assert T5577.get("WAKE").build("0")[0] == 0x03
|
||||
assert {c.name for c in T5577.commands.values()} == {"READ", "WRITE", "WAKE", "DETECT"}
|
||||
assert T5577.get("RESET").build()[0] == 0x00 # reset command = downlink opcode 00
|
||||
assert {c.name for c in T5577.commands.values()} == {"READ", "WRITE", "WAKE", "RESET", "DETECT"}
|
||||
assert T5577.get("READ").run is not None and LF_READONLY.get("INFO").run is not None
|
||||
# READ/WRITE take optional page + password; build() tolerates the extra args
|
||||
assert T5577.get("READ").build("4", "1", "0xAABBCCDD")[0] == 0x01
|
||||
assert T5577.get("WRITE").build("4", "12345678", "1", "0xAABBCCDD")[0] == 0x02
|
||||
|
||||
def test_opcode_covers_two_phase_write(self):
|
||||
# opcode() tolerates data args (which need valid hex) and multi-frame builds
|
||||
@@ -588,6 +592,52 @@ class TestCatalog:
|
||||
assert TYPE2.by_opcode(0x99) is None
|
||||
|
||||
|
||||
class TestT5577Run:
|
||||
"""The T5577 run() functions thread page/password through to the lf.t55 device methods."""
|
||||
|
||||
def test_write_threads_page_and_password(self):
|
||||
dev = MagicMock()
|
||||
dev.lf.t55.writebl.return_value = {"status": 0}
|
||||
out = T5577.get("WRITE").run(dev, "4", "0x12345678", "1", "0xAABBCCDD")
|
||||
dev.lf.t55.writebl.assert_called_once_with(4, 0x12345678, page=1, password=0xAABBCCDD)
|
||||
assert "block 4 (page 1)" in out and "ok" in out and "AABBCCDD" in out
|
||||
|
||||
def test_write_defaults_are_page0_no_password(self):
|
||||
dev = MagicMock()
|
||||
dev.lf.t55.writebl.return_value = {"status": 0}
|
||||
T5577.get("WRITE").run(dev, "4", "0x12345678")
|
||||
dev.lf.t55.writebl.assert_called_once_with(4, 0x12345678, page=0, password=None)
|
||||
|
||||
def test_write_data_and_password_are_hex(self):
|
||||
# data/password are hex words (like pm3 `lf t55 write -d/-p`), no 0x needed
|
||||
dev = MagicMock()
|
||||
dev.lf.t55.writebl.return_value = {"status": 0}
|
||||
T5577.get("WRITE").run(dev, "0", "00088040", "0", "50524F58")
|
||||
dev.lf.t55.writebl.assert_called_once_with(0, 0x00088040, page=0, password=0x50524F58)
|
||||
|
||||
def test_reset_sends_reset(self):
|
||||
dev = MagicMock()
|
||||
dev.lf.t55.reset.return_value = {"status": 0}
|
||||
assert "ok" in T5577.get("RESET").run(dev)
|
||||
dev.lf.t55.reset.assert_called_once_with()
|
||||
|
||||
def test_read_config_forwards_password(self):
|
||||
# block 0 -> read_config; a password reads in password mode (readbl(0, password=...))
|
||||
dev = MagicMock()
|
||||
dev.lf.t55.readbl.return_value = None # no envelope -> "no clean config"
|
||||
dev.lf.download_samples.return_value = b""
|
||||
out = T5577.get("READ").run(dev, "0", "0", "0x11223344")
|
||||
dev.lf.t55.readbl.assert_called_with(0, password=0x11223344)
|
||||
assert "11223344" in out
|
||||
|
||||
def test_read_block_forwards_page_and_password(self):
|
||||
dev = MagicMock()
|
||||
dev.lf.t55.readbl.return_value = None
|
||||
dev.lf.download_samples.return_value = b""
|
||||
T5577.get("READ").run(dev, "2", "1", "0x11223344")
|
||||
dev.lf.t55.readbl.assert_called_with(2, page=1, password=0x11223344)
|
||||
|
||||
|
||||
class TestFunctionCalls:
|
||||
def _id(self, sak=0x00):
|
||||
return RawSession(device=_mock_device(
|
||||
|
||||
Reference in New Issue
Block a user