The sync REPL proxy forwarded methods via __getattr__, so dir()/tab-completion never saw them and the sync wrapper reported (*args, **kwargs) instead of the real signature. Add __dir__ delegating to the wrapped target, and decorate the async wrapper with functools.wraps so inspect.signature()/help() report the real params + docstring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lf.tune sends the 2-byte {mode,divisor} the firmware requires (was 3 -> EINVARG/0V). t55.writebl packs the 10-byte {data,pwd,blockno,flags} struct with a single bit-packed flags byte (was 12). t55.wakeup packs 5 bytes {pwd,flags}. t55.config is maintained client-side (firmware has no GET). LF_SAMPLING_SET_CONFIG uses send_ng_no_response (firmware never replies). Pre-existing, independent of the rebase.
14a.sim sent a MIX frame but the handler is NG casting a packed {tagtype,u16 flags,uid[10],exitAfter,rats[20],ulauth...} struct -> whole struct garbled, flags never arrived. Rewrite to send_ng with the exact struct and expose tagtype (2=Ultralight,7=NTAG) so Ultralight/NTAG sim works. 14a.sniff sent MIX (args stripped -> empty asBytes); send NG with the 1-byte param. Pre-existing, independent of rebase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dropfield waited on a reply the firmware never sends (bare hf_field_off();break;) -> 2s stall + raise; use send_ng_no_response. hf.sniff under-sent the 10-byte {u32,u32,u8,u8} struct and mis-parsed the uint16 reply. hw.standalone sent an empty payload for a {arg,mlen,mode[10]} struct and blocked; pack it + fire-and-forget. Pre-existing, independent of rebase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wrbl padded key to 16 (firmware reads block at asBytes+10) -> corrupt writes; pad to 10. nested sent {block,keytype,key,target...} but firmware struct is {block,keytype,target_block,target_keytype,calibrate,key[6]} -> attacked wrong target; reorder + add calibrate + decode reply. cident/sim sent empty/MIX payloads for NG handlers -> send the packed structs. mf.sniff (no firmware handler) reroutes to CMD_HF_ISO14443A_SNIFF. Pre-existing, independent of rebase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rdbl omitted the ISO15693 OPTION flag (0x40) so the tag returned [flags][data] without the lock byte the parser assumes (shifting data one byte) and included the 2-byte CRC in block_data. wrbl reused the read flags (no LONG_WAIT) so the firmware used the short RX timeout and the ~20ms write ack arrived too late -> false failure.
rdbl: set OPTION (0x62/0x42), strip CRC (data[2:-2]). wrbl: CONNECT|READ_RESPONSE|LONG_WAIT (0x61). Mirrors C client CmdHF15Readblock / hf_15_write_blk. Pre-existing, independent of the rebase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ISO15_READ_RESPONSE was 0x10 but the firmware bit is 0x20 (0x10 is HIGH_SPEED, which pm3py lacked), so scan/rdbl/wrbl sent flags 0x19 with READ_RESPONSE never set -> firmware never listened and replied empty. The ARM firmware also ignores ISO15_APPEND_CRC (the C client adds the CRC host-side via AddCrc15), so pm3py's CRC-less frames were never answered by a real tag.
Fix flag values to match firmware include/iso15.h, add crc15 (CRC-16/X-25), and route all iso15 commands through _iso15_payload (appends CRC, sends CONNECT|HIGH_SPEED|READ_RESPONSE=0x31). Payload now byte-matches the C client getUID. Pre-existing bug, independent of the firmware rebase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CMD_MEASURE_ANTENNA_TUNING_HF requires a 1-byte mode arg (firmware rejects length!=1 with EINVARG) and returns the voltage as uint16, not uint32. tune() sent no arg (empty reply -> 0 mV) and parsed <I. Now sends mode 1/2/3 (START/MEASURE/STOP) and parses <H. Verified vs firmware appmain.c handler; C client reads the same ~15V on this firmware.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scan() sent flags=0x06 (16-slot inventory). Since 16-slot became the firmware default, the device replies with an iso15_slot_result_t struct, which this parser misread as a zeroed UID (response_flags 16, uid 0000...). Set SLOT1 (0x26) to get the simple single-tag [flags][dsfid][uid] response. Use hf 15 reader --all for multi-tag anti-collision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document ALM as analog-only with no command surface, and seed config blocks 0x40-0x44 with the NTA5332 datasheet Rev 3.4 Table 76 defaults (ALM_CONF/ALM_LUT) so a client reading config memory sees the same bytes a real NTA5332 returns.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tag15693.set_uid() now:
- Accepts None (or no args) to generate a random valid UID with the
correct IC-specific prefix (E0 + mfg + IC type)
- Validates length (must be 8 bytes)
- Warns on prefix mismatch (wrong IC type for product-specific tags)
- Warns on non-NXP manufacturer code
- Warns on missing E0 prefix
Replace lightweight async SniffSession with persistent synchronous
session using raw pyserial (mirroring SimSession pattern). Background
reader thread decodes and prints trace frames live as they arrive.
API:
session = SniffSession.open()
session.start_15693() # live streaming (default)
session.start_15693(live=False) # legacy batch mode
session.stop() # or button press to pause/resume
session.entries # all captured frames
session.clear()
.pythonstartup.py auto-creates session in sniff mode.
Add CMD_HF_SNIFF_STREAM (0x0337) and CMD_HF_SNIFF_STATUS (0x0338) for
fire-and-forget trace streaming during sniff. Unified sniff loop uses
the same proven decode path for both streaming and legacy modes —
streaming replaces LogTrace with ring buffer SNIFF_PUSH/SNIFF_FLUSH.
Firmware features:
- Idle flush: sends buffered trace entries over USB when RF is quiet
for idle_timeout_ms (configurable, default 400ms for 15693)
- Button-toggle: pause/resume sniffing without restarting
- DMA restart on resume for clean decoder state
- BREAK_LOOP support for Python-side session.stop()
- Legacy mode (flags=0) preserved unchanged
Python constants: SNIFF_FLAG_STREAMING, SNIFF_FLAG_BUTTON_TOGGLE,
SNIFF_STATE_STARTED/PAUSED/RESUMED/STOPPED, Cmd.HF_SNIFF_STREAM/STATUS
source activate once, then pm3/pm3 sim/pm3 sniff/pm3 all to launch
REPL with appropriate imports. Uses python -i for reliable startup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trace buffer parsing, protocol decoders, NDEF annotation, and
formatting are firmware-level features shared by sniff, sim, and
reader modes — not sniff-specific. Moves these from sniff/ into
trace/. sniff/ retains only SniffSession (orchestration) and
re-exports trace symbols for convenience.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merges the package refactor (core/ sub-package, normalized naming,
scaffolded sub-packages) while preserving all in-progress sniff
infrastructure. Git rename detection carried WIP changes to new paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In-progress work — committing before refactor merge so git rename
detection can carry changes to new file locations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phases 2-4 of package refactor. Creates the full directory tree with
__init__.py files per the design doc. Actual code migration from the
sim worktree is a separate effort — models have deep inheritance chains
and auth mixins that need base class extraction first.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1 of package refactor. Moves all source modules into pm3py/core/
with file renames (hf_14a→hf_iso14a, hf_15→hf_iso15, hf_mf→hf_mfc)
and client attribute normalization (hf.a14→hf.iso14a, hf.mf→hf.mfc).
pm3py/__init__.py re-exports from core for backward compat.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add table compiler match pattern rules to main CLAUDE.md (was only in worktree)
- Update sim framework description (750+ tests, add DNA/NTAG5 to IC list)
- Update "planned" → "in progress" for Python-driven card sim
- Fix firmware patch size: ~320 lines (was inconsistently ~340 on line 49)
4 tasks: update TableEntry to 120-byte format, compile_nxp_icode,
compile_slix2, wire auto-compile into SimSession.start_15693.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tested retry-based relay and spin-wait approaches. Relay works
mechanically via retry but is unreliable (reader-dependent).
Spin-wait fails due to USB stack latency. Table is the only
viable mechanism for 15693 custom commands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers: sim_table entry structure with EML actions and group-based
state machine, AES-CMAC for TAM/MAM, native handler access control,
14443-A integration, timing budget analysis.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>