Skip to content

nfc_sequence_diagrams

moggieuk edited this page Jul 20, 2026 · 2 revisions

Happy-Hare NFC/RFID — Operation Sequence Diagrams

Companion to nfc_handoff.md. Describes the runtime flow of the three main NFC use cases. Code lives on the private_rfid branch.

Participants (abbreviated): NfcManager = per-unit MmuNfcManager; NfcEndstop = MmuNfcEndstop (virtual, per gate reader); Reader = the driver (RC522 / PN532 / PN7160); Controller = the MMU controller (_nfc_tag_read / _check_pending_filament / _apply_metadata_to_gate); Moonraker = components/mmu_server.py (all Spoolman / SpoolmanDB socket I/O).


1. MMU_PRELOAD — read the tag while seating filament (compound endstop)

One drip-home to a first-wins compound of [gate_switch, nfc_reader]; whichever fires first decides the follow-up move. Only engages when the gate's reader is present + enabled, homing is non-encoder, and the preload endstop (gate_preload_endstop, defaults to gate_homing_endstop) is a real MCU switch. Otherwise it falls back to the normal full-load path.

sequenceDiagram
    autonumber
    actor U as GCode / User
    participant CMD as MMU_PRELOAD
    participant PG as _preload_gate(_with_nfc)
    participant RAIL as Gear rail
    participant CE as CompoundEndstop
    participant NM as NfcManager
    participant ES as NfcEndstop
    participant RD as Reader
    participant GM as Gate map
    participant MR as Moonraker

    U->>CMD: MMU_PRELOAD GATE=x
    CMD->>CMD: guards + select_gate
    CMD->>PG: _preload_gate()
    PG->>PG: gate_es = gate_preload_endstop, nfc_viable? (reader on, non-encoder, MCU switch)
    alt nfc_viable
        PG->>RAIL: get_extra_endstop(gate) + (nfc)
        PG->>RAIL: add_compound_endstop([gate_switch, nfc])
        Note over PG,RD: Phase 1 — drip-home to compound (first wins)
        PG->>CE: move_filament(homing_move=1)
        CE->>ES: home_start (+ gate switch)
        ES->>NM: start_homing_poll
        NM->>RD: clear_uid() + tight poll
        RD-->>CE: gate switch fires OR tag UID read
        CE-->>PG: home_wait → triggered
        alt NFC first
            PG->>NM: read_gate(gate)
            NM->>GM: apply metadata (_nfc_tag_read)
            opt Spoolman on
                NM->>MR: resolve UID→spool (async)
            end
            PG->>RAIL: continue-home to gate switch
        else Gate switch first
            opt nfc_read_window[1] > 0
                PG->>ES: chase forward up to +window
                RD-->>NM: UID on hit → read_gate → GM/MR
                PG->>RAIL: backward re-home to gate switch
            end
        end
        PG->>PG: park (gate_preload_parking_distance)
        PG->>GM: set GATE_AVAILABLE
        PG->>PG: _check_pending_filament(gate)
        PG->>RAIL: remove_compound_endstop()
    else no reader / encoder / not a switch
        PG->>PG: full-load fallback (_load_gate + _unload_gate x attempts)
    end
    PG-->>CMD: POST_PRELOAD macro, restore gate
Loading

2. Shared NFC reader → staged "pending" → applied at the next preload

A shared reader can't know which gate a tag belongs to, so a read is staged (pending_metadata, or a resolved pending_spool_id from Spoolman) and applied to whichever gate the next load/preload targets. A resolved Spoolman spool takes precedence over staged tag metadata.

sequenceDiagram
    autonumber
    participant RX as Reactor timer
    participant NM as NfcManager (shared)
    participant RD as Shared reader
    participant C as Controller
    participant MR as Moonraker
    participant PG as _preload_gate
    participant GM as Gate map

    Note over RX,MR: Phase A — user presents a spool tag at the shared reader
    RX->>NM: _poll_shared_reader (periodic)
    NM->>RD: live read (deep if enabled)
    RD-->>NM: UID (new tag)
    NM->>C: _nfc_tag_read(uid, gate=None, metadata)
    C->>C: _stage_pending_metadata → pending_metadata, arm timeout timer
    opt Spoolman active
        C->>MR: _spoolman_get_spool_by_uid(uid, gate=None)
        MR-->>C: (async) MMU_GATE_MAP NEXT_SPOOLID=id
        C->>C: set_pending_spool_id(id) (supersedes pending_metadata)
    end

    Note over PG,GM: Phase B — later: MMU_PRELOAD GATE=x seats filament
    PG->>PG: seat filament at gate
    PG->>C: _check_pending_filament(gate)
    alt pending_spool_id > 0
        C->>GM: assign_spool_id(gate, id)
        opt PUSH / READONLY
            C->>MR: push / update filament attrs
        end
    else pending_metadata staged
        C->>GM: _apply_metadata_to_gate(gate, uid, meta)
    end
    C->>C: _clear_pending()
Loading

3. Filament already in the MMU → MMU_NFC_SCAN (jog_scan)

The fast-path pre-read means no motion at all if the tag already sits on the reader; otherwise it jogs the configured window to bring the tag to the reader. Homes to the NFC reader alone (no gate endstop, no compound), so it is encoder-agnostic — the gate endstop type only matters for the re-park, which uses the normal _load_gate/_unload_gate (both handle encoder natively).

sequenceDiagram
    autonumber
    actor U as GCode / User
    participant CMD as MMU_NFC_SCAN
    participant JS as _jog_scan
    participant NM as NfcManager
    participant RD as Reader
    participant ES as NfcEndstop
    participant GM as Gate map

    U->>CMD: MMU_NFC_SCAN GATE=x
    CMD->>CMD: guards + select_gate
    CMD->>JS: _jog_scan()
    JS->>JS: read nfc_read_window, snapshot+deactivate other readers, activate target
    Note over JS,RD: Fast path — tag may already be on the reader
    JS->>NM: clear_gate_reader(gate)
    NM->>RD: clear_uid() (release held target)
    JS->>NM: read_gate(gate)
    NM->>RD: live read
    alt tag already at reader
        RD-->>NM: UID
        NM->>GM: apply (_nfc_tag_read)
        Note over JS: found → NO filament motion
    else no tag yet — jog to find it
        loop each window direction (longer first)
            JS->>ES: move_filament(home to mmu_nfc_gate)
            ES->>NM: start_homing_poll
            NM->>RD: clear_uid + tight poll
            RD-->>ES: UID on hit → trigger
            opt homed (tag found)
                JS->>NM: read_gate(gate) → GM
                JS->>JS: re-park (_unload_gate fwd / _load_gate back)
            end
        end
    end
    JS->>JS: restore reader active flags + gate selection
Loading

Cross-cutting notes

  • read_gate(gate) always does a fresh live read and, on a UID, dispatches _nfc_tag_read → applies deep-read metadata to the gate map and (if Spoolman is active) initiates the async Moonraker UID→spool resolution. It's the single "read + apply" primitive used by preload, jog_scan, and the shared/per-gate polls.
  • start_homing_poll clears the reader (clear_uid) before each homing move and suppresses the shared-reader poll (_homing_endstop is not None) so a shared physical reader can't be polled from two roles at once.
  • Drip-homing budget (toolhead.py: ~50ms safe / ~151ms hard): host-polling the reader inside a drip move must stay short. RC522 (SPI) fits; PN532/PN7160 (I²C framed) must be bench-measured. The preload compound adds no extra host polling beyond the single NFC endstop.

*** NEW V4 DOC IS HERE ***

 1. Introduction
 2. Installation
 3. Essential Configuration
 4. Calibration
 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
11. Quick References

12. Troubleshooting
13. FAQ
14. MCU Board Reference 🆕
15. Change Log
Happy Hare Discord

Clone this wiki locally