This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
NFC/RFID reader validation plan
Companion to nfc_driver_matrix.md. Turns the matrix into something you can run
against real hardware. Two tracks, because the failure modes differ:
- Track S — shared reader: one reader, tags presented by hand. Homing accuracy irrelevant; what matters is Spoolman resolution and that a missing reader degrades gracefully.
- Track G — per-gate reader: reader per lane, used as a homing endstop. Everything in Track S plus homing behaviour, bus scaling and per-tick cost.
Run B (bench) first regardless. Track G additionally runs H (homing).
Speed note: gear_homing_speed defaults to 150 mm/s (mmu_unit_parameters.py:196),
not the 100 mm/s the manager comment uses. Expected overshoot at default speed:
~3 mm (PN532), ~6 mm (RC522), ~15 mm (PN7160 no-IRQ), ~15–22 mm (PN5180).
0. Instrumentation (set once)
[mmu_nfc_reader gateN]
debug: 3 # 3 = probe watchdog + parse steps. 4 = full trace (slow, only for a failure)
MMU_TEST_CONFIG log_level=2
Watch: tail -f ~/printer_data/logs/klippy.log | grep -E 'mmu_nfc|rfid|NFC'
Status to sample: printer["mmu_nfc_reader gateN"].alive / .present / .last_uid / .reader_type / .interface
Commands used throughout: MMU_RFID_INIT|READ|RELEASE [NAME=] (driver level),
MMU_NFC (manager level), MMU_NFC_SCAN, MMU_PRELOAD.
B. Bench bring-up — run for every reader, before any motion
| ID | Do | Pass | A failure means |
|---|---|---|---|
| B1 | Klippy restart, read the log | No config errors; each reader logs init: gate=N |
Wiring/bus validation in reader_factory rejected something — the message names the section and the option |
| B2 | MMU_NFC |
Every reader listed, alive true |
Chip not answering: power, bus, address, CS |
| B3 | MMU_RFID_READ NAME=x with no tag |
no tag detected, returns promptly |
A long hang here is the blocking-read problem the probe exists to avoid; check transceive_delay / pn5180_rf_timeout |
| B4 | MMU_RFID_READ NAME=x with a tag on the antenna |
UID=..., stable across 10 repeats |
Intermittent = marginal RF, bus speed, or (software I2C) missing pull-ups |
| B5 | MMU_NFC ... READ=1 DEEP=1 on one tag of each brand you own |
Log shows deep read uid=... strategy=<ntag_type2/mifare_classic/iso15693_type5> then parsed tag_format=... |
strategy=uid_only → tag type not classifiable. all MIFARE key attempts failed → wrong vendor or missing pycryptodome (the log says which) |
| B6 | Pull the tag, MMU_RFID_READ, replace, read again |
Clean no tag then a fresh UID |
A sticky UID means release/clear is broken |
| B7 | Note the reader's probe capability from the init log | RC522/PN532: probe path. PN7160: init OK (tag homing probe available) |
PN7160 saying unavailable - no irq_pin = you are on the slow shim; expected if you didn't wire IRQ |
| B8 | Chip-specific trap from §X | — | — |
B7 is the single most important line to read if you intend to home to tags.
S. Shared-reader track
| ID | Do | Pass |
|---|---|---|
| S1 | Present a tag, wait ≥1 s (poll is NFC_CHECK_INTERVAL = 1.0 s) |
UID picked up without any command |
| S2 | Leave the tag sitting on the reader 30 s | Spoolman is not hammered — one lookup, then the 5 s NFC_TAG_HOLD_TIME cooldown + UID dedupe hold |
| S3 | Swap to a second tag | New UID resolves promptly, not blocked by the previous hold |
| S4 | MMU_NFC SHARED=1 REGISTER=1 on an unknown tag with nfc_deep_read=1 + auto-create on |
Spool created from parsed metadata; report-only for shared (no gate map change) |
| S5 | MMU_NFC SHARED=1 ENABLE=0, present a tag |
Nothing read at all. ENABLE=1 re-inits |
| S6 | Start a print, present a tag | Reader inactive during print (the active flag), no lookups |
| S7 | UART only: unplug the adapter, restart klippy | Klippy starts; reader reports not-alive. Replug + MMU_RFID_INIT recovers |
| S8 | UART only: confirm the port is a /dev/serial/by-id/ path |
No not a /dev/serial/by-id/ path warning in the log |
S7 is the shared-reader-specific value claim: a dead reader must not be able to stop the printer from starting.
G. Per-gate track
| ID | Do | Pass |
|---|---|---|
| G1 | MMU_NFC with every gate loaded |
Correct UID attributed to the correct gate — no off-by-one, no cross-talk |
| G2 | Move one tag between two gates' readers | UID follows the tag; the vacated gate reads empty. Catches a shared-bus/address mix-up that G1 can miss |
| G3 | MMU_NFC GATES=0,1,2,3 READ=1 |
All gates answer in one command without a stall or a TTC |
| G4 | MMU_NFC GATE=n REGISTER=1 |
Gate map updated for that gate (per-gate does assign, unlike shared) |
| G5 | Read every gate 20× in a loop | Zero read failures. Intermittents here are bus contention, not RF |
| G6 | Software I2C only: same test with all readers configured | Any silent cross-bus collision shows as intermittent failures on G5/G2 — reader_factory catches pin/address collisions at config time, but not weak pull-ups |
| G7 | PN7160 multi-drop: 2–4 readers at 0x28–0x2B on one bus | Each answers at its own address; G2 confirms no aliasing |
H. Homing track (per-gate only)
Prereq: gate endstop must be a real MCU switch. If the log says is not a real MCU switch - plain homing or missing endstop ... - plain homing, H-series is
inapplicable — the reader is not compounded into the home at all.
| ID | Do | Pass | Notes |
|---|---|---|---|
| H1 | MMU_NFC_SCAN GATE=n with the tag inside the jog window |
Tag found, read, filament re-parked at the gate datum | Requires nfc_gate_jog_scan_window set (neg,pos) |
| H2 | MMU_NFC_SCAN with the tag already on the reader |
No motion at all — the pre-read short-circuits | Cheapest correctness check of the whole path |
| H3 | MMU_NFC_SCAN with no tag on the spool |
Sweeps the window, reports not found, re-parks once — filament does not walk backward on repeat invocations | Run it 5× and measure the parked position; drift here is the bug the "ONE re-park off a datum" comment exists to prevent |
| H4 | MMU_PRELOAD on a tagged spool |
Homed and tag read (they are separate outcomes). Ends parked off the gate switch | |
| H5 | Repeat H4 20× | tag not readable after homing (debug log) appears rarely if ever |
Frequent = the tag is coasting past the antenna in the deceleration ramp; reduce gear_homing_speed or reposition the reader |
| H6 | Speed sweep — see below | Discriminates poll latency from RF geometry | The key test |
| H7 | Run H4 in a loop 30× while the extruder heats / a print runs | No Timer Too Close |
Shim drivers (PN5180, PN7160-no-IRQ) are the ones at risk; this is the bench check the endstop's PROTOTYPE header asks for |
| H8 | Immediately after an H4 trigger, MMU_RFID_READ |
Succeeds | Validates the probe→read handoff. RC522 specifically: its probe leaves tags in READY and the read uses REQA, so a broken probe_stop field-drop fails every post-move read |
| H9 | Disable the gate reader, then MMU_PRELOAD |
Refuses up front with the "reader is disabled" error — does not run the move full length and report no trigger |
H6 — the speed sweep
The one measurement that tells you whether your chip choice matters. Run the same homing move at three speeds and record where it triggers:
MMU_TEST_CONFIG UNIT=0 gear_homing_speed=50
MMU_NFC_SCAN GATE=0 # ×5, record the chased distance from the log
MMU_TEST_CONFIG UNIT=0 gear_homing_speed=100
MMU_NFC_SCAN GATE=0 # ×5
MMU_TEST_CONFIG UNIT=0 gear_homing_speed=150
MMU_NFC_SCAN GATE=0 # ×5
Interpretation:
- Distance grows roughly linearly with speed → poll latency dominates. Implied latency = Δdistance ÷ Δspeed. Compare against §C of the matrix: ~20 ms means the probe path is working; ~100 ms+ means you are on the shim (check B7).
- Distance roughly constant across speeds → RF coupling range and the deceleration ramp dominate. Your chip choice is not the limiting factor and upgrading from RC522 to PN532 will buy nothing.
- Spread within one speed exceeds the shift between speeds → tag/antenna geometry is the problem. Fix the mechanical mount before touching driver config.
Record the within-speed spread too; that is your real homing repeatability figure and nothing in the code can improve it.
X. Chip-specific traps
One test each — the thing most likely to be wrong for that chip.
| Chip | Trap | Test |
|---|---|---|
| PN532 i2c | Only one per bus (0x24 fixed) | Configure two on one hardware bus → must be rejected at config time, not produce intermittent reads |
| PN532 i2c (software) | Missing pull-ups; no clock stretching or bus timeout, so a wedged bus returns bad data rather than raising | G5 loop ×100. Any garbage UID (not just a miss) points here |
| PN532 uart | Mode pads must be HSU (SEL0=0, SEL1=1); one reader per tty | B2 fails → check pads before wiring. Two on one port must be rejected |
| PN532 spi | UNTESTED path. Two specifics to scope: the ACK check compares 6 raw bytes with no status prefix, and every spi_transfer response is indexed [1:] |
Log must show the UNTESTED against real hardware warning; treat all of B and H as new-code validation, not config validation |
| PN7160 | No irq_pin → silently correct but 5× slower homing |
B7 log line; then H6 to confirm the latency you actually get |
| PN7160 | Detection tick ends in _wait_for_irq_release(), bounded 50 ms — right at the drip budget |
H7 (TTC loop) is non-optional for this chip |
| PN5180 | busy_pin + reset_pin are required, no defaults |
Omit one → config error naming it. Then confirm BUSY actually transitions (a stuck-high BUSY shows as busy_timeout in the log) |
| PN5180 | pn5180_tag_format: auto walks both protocols per read, doubling miss cost |
Time B3 with auto vs pinned to ntag/iso15693; pin it if you know your tags |
| RC522 | Probe leaves tags in READY; post-move read uses REQA | H8 — this fails 100% of the time if the field-drop in probe_stop is broken, and 0% if it works. No ambiguity |
| RC522 | is_alive() reports dead if the TX bits are left clear |
After any H-series failure, MMU_NFC must still show alive — probe_stop restores TX bits unconditionally on purpose |
Acceptance summary
| Setup | Must pass | Should pass |
|---|---|---|
| Shared reader | B1–B6, S1–S6 (+S7–S8 if UART) | B7 n/a |
| Per-gate, no homing | B1–B6, G1–G5 (+G6/G7 by topology) | — |
| Per-gate, homing to tag | all of the above, plus B7, H1–H5, H7–H9 | H6 characterised and recorded |
Homing-off-a-tag is not implementable on this code (see matrix §F) — there is nothing to test yet. When it lands, the first test is absence hysteresis: confirm a single missed exchange does not trigger, on a stationary tag, 100 consecutive ticks.
*** NEW V4 DOC IS HERE ***
1. Introduction
2. Installation
3. Essential Configuration
5. Operation
-- YOUR MMU IS READY TO PLAY WITH --
6. Slicer-MMU Setup
-- NOW YOU ARE READY TO PRINT! --
7. Tuning
8. Optional Feature Setup
9. Advanced Configuration
10. Advanced Concepts
- State Persistence
- Statistics and Counters
- Tool and Gate Maps
- Filament Bypass
- Runout/Clog Detection
- Consumption Counters
- Synchronized Gear/Extruder
11. Quick References
12. Troubleshooting
13. FAQ
14. MCU Board Reference 🆕
15. Change Log
Happy Hare Discord
(\_/)
( *,*)
(")_(") Happy Hare Ready
Copyright (C) 2022-2026 Paul Morgan