fix(lf): read() acquired with the field OFF (SNIFF) — no tag was ever powered

lf.read() sent CMD_LF_SNIFF_RAW_ADC (passive sniff, reader field OFF), so it captured
only a dead field — a real hardware dump came back flat at 0x7e-0x80 (±1 count of ADC
noise) with no tag modulation, which is why LF identify found nothing. Reader-mode reads
must use CMD_LF_ACQ_RAW_ADC (SampleLF -> field ON) to power the tag and capture its load
modulation.

- read() -> CMD_LF_ACQ_RAW_ADC (field on); sniff() -> CMD_LF_SNIFF_RAW_ADC (field off),
  no longer delegating to read(). Shared _acquire() packs the real lf_sample_payload_t
  (PACKED bitfield samples:30/realtime:1/verbose:1 + cotag byte = 5 bytes) instead of a
  bare uint32.

Tests: read() uses ACQ with a 5-byte payload and correct samples field; sniff() uses SNIFF.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-14 16:17:11 -07:00
parent 2f221644f9
commit b536294a7a
2 changed files with 42 additions and 9 deletions

View File

@@ -76,3 +76,27 @@ def test_lf_t55_config_client_side():
assert len(result["modes"]) == 4
# No firmware round-trip: SET_CONFIG is write-only and never replies
t.send_ng.assert_not_called()
def test_lf_read_uses_reader_field_acq():
# read() must turn the reader field ON (LF_ACQ_RAW_ADC) — SNIFF is field-off and reads nothing
t = AsyncMock()
lf = LFCommands(t)
t.send_ng.return_value = make_response(Cmd.LF_ACQ_RAW_ADC, 0, struct.pack("<I", 98304))
result = asyncio.get_event_loop().run_until_complete(lf.read(samples=12288))
assert result["samples"] == 98304
args, kwargs = t.send_ng.call_args
assert args[0] == Cmd.LF_ACQ_RAW_ADC
payload = args[1] if len(args) > 1 else kwargs["payload"]
assert len(payload) == 5 # lf_sample_payload_t: 30+1+1 bits + cotag byte
word = struct.unpack_from("<I", payload, 0)[0]
assert (word & 0x3FFFFFFF) == 12288 # samples field
assert (word >> 30) & 1 == 0 # realtime = 0
def test_lf_sniff_uses_field_off():
t = AsyncMock()
lf = LFCommands(t)
t.send_ng.return_value = make_response(Cmd.LF_SNIFF_RAW_ADC, 0, struct.pack("<I", 100))
asyncio.get_event_loop().run_until_complete(lf.sniff(samples=4096))
assert t.send_ng.call_args[0][0] == Cmd.LF_SNIFF_RAW_ADC