# pm3py Pure-Python library for talking to a [Proxmark3](https://github.com/RfidResearchGroup/proxmark3) over its NG wire protocol. Returns structured Python dicts instead of parsing terminal output. Async-first with a sync wrapper for REPL use. **No dependency on the C client.** Speaks the wire protocol directly over USB serial. ## Install ```bash cd pm3py pip install -e . ``` Requires Python 3.10+ and `pyserial-asyncio`. ## Quick Start ```python from pm3py import Proxmark3 # Sync mode — auto-detects the device pm3 = Proxmark3.sync() print(pm3.firmware.version_string) print(pm3.hw.ping()) print(pm3.hw.version()) pm3.close() ``` Or with async: ```python import asyncio from pm3py import Proxmark3 async def main(): async with Proxmark3() as pm3: print(await pm3.hw.ping()) print(await pm3.hf.a14.scan()) asyncio.run(main()) ``` ## API All commands return Python dicts. The API mirrors the C client's command tree: ### Hardware — `pm3.hw.*` ```python pm3.hw.ping() # verify connectivity pm3.hw.version() # firmware version, chip ID pm3.hw.status() # device status pm3.hw.capabilities() # compiled features, platform (Easy vs RDV4) pm3.hw.tune() # full antenna tuning (LF + HF voltages) pm3.hw.led("green", on=True) # LED control with color names pm3.hw.led("red", pulse=True, speed=300)# PWM effects (validates hardware support) pm3.hw.led("all", on=False) # turn off pm3.hw.dbg(level=2) # set debug level pm3.hw.break_loop() # stop long-running operations pm3.hw.reset() # reset device ``` LED colors are platform-aware — `"green"` maps to the correct physical LED on both PM3 Easy and RDV4. PWM operations (brightness, pulse, fade) validate hardware capability and raise `PM3Error` if the LED doesn't support it. ### Low Frequency — `pm3.lf.*` ```python pm3.lf.tune(divisor=95) # LF antenna voltage at frequency pm3.lf.read(samples=30000) # capture ADC samples pm3.lf.config() # get sampling config pm3.lf.config(divisor=88) # set config (134 kHz) pm3.lf.search() # capture + return LFSearchResult pm3.lf.sniff() # alias for read pm3.lf.sim(gap=0, data=b"...") # simulate from buffer # T55xx tags pm3.lf.t55.readbl(block=0) pm3.lf.t55.writebl(block=1, data=0xDEADBEEF) pm3.lf.t55.wakeup(password=0x12345678) ``` `lf.search()` returns an `LFSearchResult` with callable next steps: ```python result = pm3.lf.search() print(result) # shows available actions raw = result.download() # download raw ADC samples result.tune() # check antenna voltage result.read_t55xx(block=0) # try reading as T55xx ``` Full tag demodulation (EM410x, HID, AWID, etc.) requires the C client's DSP stack. ### High Frequency — `pm3.hf.*` ```python pm3.hf.tune() # HF antenna voltage pm3.hf.search() # try 14a then 15693 pm3.hf.sniff() # sniff HF traffic pm3.hf.dropfield() # turn off field ``` ### ISO 14443-A — `pm3.hf.a14.*` ```python pm3.hf.a14.scan() # → {uid, atqa, sak, ats} pm3.hf.a14.raw(data=b"\x50\x00") # raw APDU pm3.hf.a14.sniff() pm3.hf.a14.sim(uid=b"\x01\x02\x03\x04") ``` ### ISO 15693 — `pm3.hf.iso15.*` ```python pm3.hf.iso15.scan() # → {uid, dsfid} pm3.hf.iso15.rdbl(block=0) # read block pm3.hf.iso15.wrbl(block=0, data="00112233") pm3.hf.iso15.sniff() ``` ### MIFARE Classic — `pm3.hf.mf.*` ```python pm3.hf.mf.rdbl(block=0) # read block (default key FF..FF) pm3.hf.mf.rdbl(block=4, key="A0A1A2A3A4A5", key_type=1) # key B pm3.hf.mf.wrbl(block=4, key="FFFFFFFFFFFF", data="00" * 16) pm3.hf.mf.rdsc(sector=0) # read entire sector pm3.hf.mf.chk(block=0, keys=["FFFFFFFFFFFF", "A0A1A2A3A4A5"]) pm3.hf.mf.sniff() pm3.hf.mf.sim(uid="01020304", size="1k") pm3.hf.mf.nested(block=0, key="FFFFFFFFFFFF") pm3.hf.mf.cident() # identify magic card type ``` ### Raw Commands For anything not wrapped: ```python from pm3py import Cmd resp = pm3.send_ng(Cmd.STATUS) # NG frame resp = pm3.send_mix(Cmd.HF_ISO14443A_READER, arg0=0x0103) # MIX frame ``` ## Firmware Compatibility On connect, pm3py pings the device and fetches the firmware version. Results are in `pm3.firmware`: ```python pm3 = Proxmark3.sync() print(pm3.firmware.compatible) # True if NG protocol works print(pm3.firmware.version_string) # firmware version print(pm3.firmware.chip_id) # e.g. "0x270B0A40" print(pm3.firmware.warnings) # any issues detected ``` ## Architecture ``` pm3py/ ├── protocol.py # Wire protocol constants, CRC-16/A, command IDs, error codes ├── transport.py # Frame encode/decode, async serial I/O, PM3Transport ├── client.py # Proxmark3 class, sync proxy, firmware probe ├── hw.py # hw.* commands ├── lf.py # lf.* commands + T55xx ├── hf.py # hf.* commands (tune, search, sniff) ├── hf_14a.py # hf.14a.* (ISO 14443-A) ├── hf_15.py # hf.15.* (ISO 15693) └── hf_mf.py # hf.mf.* (MIFARE Classic) ``` - **Transport layer** handles serial I/O, CRC, NG/MIX frame encoding/decoding - **Command modules** send structured commands and parse responses into dicts - **USB connections** skip CRC (matching the C client behavior) - **FPC UART connections** use CRC-16/A with byte-swapped wire format ## Tests ```bash cd pm3py python -m pytest tests/ -v ``` All tests use mock transports — no hardware required.