fix(lf): BigBuf download spoke the wrong protocol — LF sample download hung
download_samples sent a DOWNLOAD_BIGBUF request per chunk and read replies with the NG-only reader. But the firmware answers ONE request by streaming the data as OLD-format CMD_DOWNLOADED_BIGBUF frames (no magic) then a single NG ACK (appmain.c) — so the NG reader scanned for a magic that never came and timed out (hardware repro: lf.read() ok, download_samples -> Response timeout). This blocked all LF identify on real hardware. - transport.download_bulk: send the request once, then read with the existing NG/OLD- agnostic reader, accumulating CMD_DOWNLOADED_BIGBUF payloads (trimmed to oldarg[1], not the 512-byte padding) until the ACK or `count` bytes; drains the trailing ACK so it can't poison the next command. Reusable for EML/SPIFFS/flashmem bulk reads. - download_samples: one call into download_bulk instead of the broken per-chunk loop. Tests: count-terminated + ACK-terminated streams, oldarg-length trimming, single request. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -180,34 +180,18 @@ class LFCommands:
|
||||
on_progress: ProgressCallback = None) -> bytes:
|
||||
"""Download raw ADC samples from the device's BigBuf.
|
||||
|
||||
Must call read() or search() first to capture samples.
|
||||
Returns raw bytes (1 byte per sample, unsigned 0-255).
|
||||
Must call read() or search() first to capture samples. Returns raw bytes (1 byte per
|
||||
sample, unsigned 0-255). The device streams the data as OLD-format CMD_DOWNLOADED_BIGBUF
|
||||
frames then a final ACK — handled by the transport's bulk-download reader.
|
||||
"""
|
||||
buf = bytearray()
|
||||
remaining = count
|
||||
offset = start
|
||||
|
||||
while remaining > 0:
|
||||
chunk = min(remaining, PM3_CMD_DATA_SIZE)
|
||||
resp = await self._t.send_mix(
|
||||
Cmd.DOWNLOAD_BIGBUF, offset, chunk, 0, timeout=5.0,
|
||||
)
|
||||
# Device responds with CMD_DOWNLOADED_BIGBUF containing the data
|
||||
dl_resp = await self._t.read_response(timeout=5.0)
|
||||
if dl_resp.data:
|
||||
buf.extend(dl_resp.data)
|
||||
|
||||
if on_progress:
|
||||
ret = on_progress({
|
||||
"type": "download", "downloaded": len(buf), "total": count,
|
||||
})
|
||||
if asyncio.iscoroutine(ret):
|
||||
await ret
|
||||
|
||||
offset += chunk
|
||||
remaining -= chunk
|
||||
|
||||
return bytes(buf[:count])
|
||||
data = await self._t.download_bulk(
|
||||
Cmd.DOWNLOAD_BIGBUF, start, count, Cmd.DOWNLOADED_BIGBUF, Cmd.ACK, timeout=5.0,
|
||||
)
|
||||
if on_progress:
|
||||
ret = on_progress({"type": "download", "downloaded": len(data), "total": count})
|
||||
if asyncio.iscoroutine(ret):
|
||||
await ret
|
||||
return data
|
||||
|
||||
async def search(self, on_progress: ProgressCallback = None) -> "LFSearchResult":
|
||||
"""Search for LF tags. Captures samples and returns a result with callable next steps.
|
||||
|
||||
@@ -278,6 +278,38 @@ class PM3Transport:
|
||||
frame = encode_old_frame(cmd, arg0, arg1, arg2, data)
|
||||
await self.send_frame(frame)
|
||||
|
||||
async def download_bulk(self, cmd: int, start: int, count: int, data_cmd: int,
|
||||
ack_cmd: int, timeout: float = 5.0, on_bytes=None) -> bytes:
|
||||
"""Run a bulk BigBuf/EML/SPIFFS download and return the concatenated bytes.
|
||||
|
||||
The firmware answers one download request (a MIX frame) by streaming ``count`` bytes as
|
||||
a run of *OLD*-format ``data_cmd`` frames (no magic — that's why the plain NG reader
|
||||
can't see them and the naive download hangs), then a final ``ack_cmd`` frame. We read
|
||||
with the NG/OLD-agnostic reader until the ACK, or until we've collected ``count`` bytes
|
||||
(draining the trailing ACK best-effort so it can't poison the next command)."""
|
||||
async with self._lock:
|
||||
await self.send_frame(encode_mix_frame(cmd, start, count, 0))
|
||||
buf = bytearray()
|
||||
while True:
|
||||
try:
|
||||
resp = await self._read_any_response(timeout=timeout)
|
||||
except PM3Error:
|
||||
break # timeout (e.g. ACK lost) — return what we have
|
||||
if resp.cmd == ack_cmd:
|
||||
break
|
||||
if resp.cmd == data_cmd and resp.data:
|
||||
length = resp.oldarg[1] if resp.oldarg and len(resp.oldarg) > 1 else len(resp.data)
|
||||
buf.extend(resp.data[:max(0, length)])
|
||||
if on_bytes is not None:
|
||||
on_bytes(len(buf))
|
||||
if count and len(buf) >= count:
|
||||
try: # got all the data; consume the trailing ACK
|
||||
await self._read_any_response(timeout=1.0)
|
||||
except PM3Error:
|
||||
pass
|
||||
break
|
||||
return bytes(buf[:count]) if count else bytes(buf)
|
||||
|
||||
async def _read_any_response(self, timeout: float = 5.0) -> PM3Response:
|
||||
"""Read one frame that may be NG or OLD and return a PM3Response."""
|
||||
if not self._reader:
|
||||
|
||||
Reference in New Issue
Block a user