docs: pyws integration section; fix hf.mfc -> hf.mf in the quick-ref

Adds the pyws workspace-plugin documentation (discovery, connection modes, resources, replay
effects, tests). Also corrects the client quick-reference: MIFARE Classic is reached via
hf.mf, not hf.mfc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-15 21:04:04 -07:00
parent 2b087a302a
commit 925d2c92f0

View File

@@ -85,7 +85,7 @@ pm3py/
pm3.hw.ping() # hardware
pm3.hf.iso14a.scan() # ISO 14443-A
pm3.hf.iso15.rdbl(4) # ISO 15693
pm3.hf.mfc.rdbl(0) # MIFARE Classic
pm3.hf.mf.rdbl(0) # MIFARE Classic
pm3.lf.t55.readbl(0) # T55xx
pm3.hf.tune() # HF antenna tune
pm3.hf.dropfield() # drop field
@@ -173,3 +173,36 @@ Key facts for maintainers:
4. Parse the response using `struct.unpack_from` on `resp.data`
5. Return a dict with human-readable keys
6. Write test with `AsyncMock` transport — no hardware needed
## pyws integration
pm3py ships a [pyws](https://github.com/dangerousthings/pyws) workspace plugin
(`pm3py/pyws_plugin.py`), so pm3py is a first-class pyws workspace: `pyws` drops you into a
shell with the device connected and the transponder/sim classes in scope. pyws is a separate,
domain-agnostic engine; the plugin is the only pm3py↔pyws coupling. Plan/design:
`docs/plans/2026-07-05-pm3py-pyws-plugin-plan.md`.
- **Discovery.** Registered via the `pyws.plugins` entry point in `pyproject.toml`
(`pm3py = "pm3py.pyws_plugin:Pm3pyPlugin"`). pyws must be installed alongside pm3py
(editable during dev: `.venv/bin/pip install -e /path/to/pyws`; a `[pyws]` extra is the
packaging target once pyws is published).
- **One connection mode per workspace.** `reader` (async `Proxmark3`) OR `sim`
(`SimSession`) — never both live; they contend on `/dev/ttyACM*`. Declaring both refuses
the second.
- **`reader` resource** → the connected `Proxmark3` (`.hw/.hf/.lf`). Missing device is
non-fatal (reports disconnected).
- **`sim` resource** → a `Sim` facade over `SimSession`: opens the port on load; the sim is
armed **explicitly** with `sim.start(tag)` (dispatches 14a/15693 by tag type). `sim.stop()`,
`sim.push(tag)` (→ `tag.sync()`), `sim.frames` (decoded trace).
- **Namespace.** The transponder classes (NTAG21x, Ultralight, MifareClassic, Type5/15693,
implant factories), `SimSession`, `Proxmark3`, NDEF helpers — injected so `startup.py` needs
no imports.
- **Replay safety (effects).** `Pm3pyPlugin.effects` declares pattern policies against the
full dotted call path. pyws replays safe-by-default (deny-list): **writes** (`*.wrbl`,
`*.writebl`, `tag.sync`, `sim.push`), **field** (`tune`, `dropfield`), **sim**
(`sim.start`, firmware sims) and **destructive** ops are withheld on autoreplay; **reads**
and pure model edits (`tag.set_page`/`set_ndef`) replay. So reconstructing a session rebuilds
the tag *model* but never re-pushes to firmware — your explicit `sim.start(tag)` does that.
- **Tests.** `tests/test_pyws_plugin.py`, hardware-free (injected clients/sessions,
`AsyncMock`). Uses pm3py's shared-event-loop `_run` convention (NOT `asyncio.run`, which
closes the loop and poisons later tests).