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>
pm3py
Pure-Python library for talking to a 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
cd pm3py
pip install -e .
Requires Python 3.10+ and pyserial-asyncio.
Quick Start
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:
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.*
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.*
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:
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.*
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.*
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.*
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.*
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:
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:
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
cd pm3py
python -m pytest tests/ -v
All tests use mock transports — no hardware required.