feat(rawcli): INVENTORY_READ / FAST_INVENTORY_READ for the SLIX catalogs

Completes the ICODE SLIX/SLIX2 datasheet command set. These fuse an inventory round
with a block read; unlike every other catalog command they need the inventory bit set
in the request flags. Added an `inventory=` option to iso15_frame (sets inventory +
single-slot, 0x26 with high rate) and both commands to _SLIX (inherited by _SLIX2),
with the mask-less body mask_len 0 | first_block | count-1 (matching the tag model's
_handle_inventory_read: first_block at data[4], num_blocks at data[5]+1).

Verified live (completer names both, opcode 0xA0/0xA1 map back, entry round-trips) and
unit-tested. Mask-based inventory (AFI/mask value) left as a future refinement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-16 12:56:00 -07:00
parent a61e9724a6
commit 7b028f4ddc
3 changed files with 28 additions and 5 deletions

View File

@@ -454,6 +454,11 @@ class TestCatalog:
# opcode = the command byte (frame byte 1), so by_opcode maps correctly
assert _SLIX.get("GET_RANDOM").opcode() == 0xB2
assert _SLIX.by_opcode(0xB2).name == "GET_RANDOM" and _SLIX.by_opcode(0x20).name == "READ_BLOCK"
# INVENTORY READ family: inventory flags (0x26) | 0xA0/0xA1 | mfg | mask_len 0 | first | count-1
assert _SLIX.get("INVENTORY_READ").build("0", "4") == bytes.fromhex("26a004000003")
assert _SLIX.get("FAST_INVENTORY_READ").build("2", "1") == bytes.fromhex("26a104000200")
assert _SLIX.by_opcode(0xA0).name == "INVENTORY_READ"
assert _SLIX.by_opcode(0xA1).name == "FAST_INVENTORY_READ"
# dispatch: an identified SLIX (or generic ICODE) resolves to this catalog
s = RawSession(); s.protocol, s.transponder = "hf15", "ICODE SLIX"
assert catalog_for(s) is _SLIX
@@ -467,6 +472,7 @@ class TestCatalog:
assert _SLIX2.get(name).opcode() == op
assert _SLIX2.by_opcode(op).name == name # raw opcode maps back
assert _SLIX2.get("READ_SIGNATURE").build() == b"\x02\xBD\x04" # NXP mfg inserted
assert _SLIX2.by_opcode(0xA0).name == "INVENTORY_READ" # inherited from SLIX
# dispatch: SLIX2 -> _SLIX2, plain SLIX still -> _SLIX (SLIX2 test comes first)
s = RawSession(); s.protocol = "hf15"
s.transponder = "ICODE SLIX2"; assert catalog_for(s) is _SLIX2