Skip to content

1.0.1-beta.5: detect ISO 14443A (NFC-A) tags next to ISO 15693 (#93) - #103

Merged
76cb merged 3 commits into
mainfrom
claude/inspiring-allen-yra5xq
Oct 21, 2026
Merged

76cb merged 3 commits into
mainfrom
claude/inspiring-allen-yra5xq

Conversation

@76cb

@76cb 76cb commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Stage 2a of the tag-support roadmap, issue #93: the reader polls ISO 14443A (NFC-A) next to ISO 15693 and names what it finds. Detection only: nothing is read from an NFC-A tag, nothing is written, and the OpenPrintTag read and write paths are unchanged. Pre-release 1.0.1-beta.5.

What changed

Polling (src/nfc/poll_schedule.hpp, src/nfc/read_only_service.cpp)

  • Every 500 ms cycle runs the NFC-V inventory first, exactly as before. Only when it finds nothing and no NFC-V tag is being tracked (stabilizing, read, or in its one tolerated missed poll) does the same field window also run an NFC-A detection. An OpenPrintTag is therefore found and lost on the same polls as in beta.4; the added cost is one short REQA on cycles with no NFC-V tag.
  • An NFC-A tag gets the same weak-link rule (one empty answer keeps it), two NFC-A tags are multiple, and an NFC-V tag answering while an NFC-A tag is tracked wins at once.
  • New state unsupported_technology; the snapshot carries technology (nfcv/nfca) and an nfca::Identity (NFCID1 4/7/10 bytes, SAK, ATQA, kind from SAK per NXP AN10833: type2, mifare_classic, type4, other). The workflow handoff ignores such a tag (no Spoolman lookup, no automatic weigh).

Driver (src/hardware/nfc/st25r3916b/i2c_reader.cpp)

  • inventory_nfca(): rfalNfcaPollerInitialize (mode switch, field already on) → rfalNfcaPollerTechnologyDetection (REQA) → rfalNfcaPollerFullCollisionResolution → rfalNfcvPollerInitialize to restore NFC-V. This is the sequence RFAL's own discovery worker runs for NFC-A (rfal_nfc.cpp), which the vendor's ESP32_I2C_scan_14443A example exercises over I2C. I2C errors propagate; a timeout or RF-side error is "no NFC-A tag this cycle" so NFC-V polling never pauses in the error state because of NFC-A.
  • tools/check_production_nfc.py: exactly those three rfalNfcaPoller* calls are permitted, in the I2C driver only; the ELF check additionally fails if a T2T read/write, rfalNfcaPollerSelect, ISO-DEP APDU or NFC-DEP routine is linked.

UIs and API

  • Touchscreen: Home "TAG FOUND / Tag type not supported yet / (NFC-A)…", Tag page with kind and ID, NFC READER step "Tag found. Its type is not supported yet."
  • Browser: Dashboard "Tag type not supported yet" / "Not supported yet"; Manage tag names the kind; "Multiple NFC-A tags detected".
  • GET /api/v1/nfc: technology, nfca{nfcid1,length,sak,atqa,kind,description}, lifecycle UNSUPPORTED_TYPE, inventory.technology now dynamic. Serial log adds technology= and an NFC-A detail line.
  • Docs: supported-tags, identify, troubleshooting, FAQ, overview, API reference, architecture, README roadmap, changelog and release notes.

Verified on the host / in CI

  • Native suite 678/678 locally, including 6 new test_nfc_read_only cases (identity kinds, poll schedule, found-and-never-read, removal tolerance, NFC-V-first ordering, transport failure), a touch-flow case and fixture, a UI source contract and a browser (node) test.
  • Local Python/docs checks pass: production NFC source check, production features, release packaging, bump version, version policy, asset generation/size, privacy, hardware docs, mkdocs strict, site validation.
  • CI must still confirm: the ESP32 build, the ELF allow-list, the stack and memory checks, native-writer-sanitized, and the RAM/flash deltas (reported in a comment once the firmware job has run). The screenshot receipts will be refreshed from CI's opentag-ui-review artifact in a follow-up commit, as in 1.0.1-beta.4: delete Community (#86) and finish the #85 leftovers #102.

Needs a physical WT32 (not done by the author)

Flash the beta.5 application update. Open the serial monitor (115200) and GET /api/v1/nfc in a browser tab.

  1. NTAG (any NTAG213/215/216 sticker, including phone-formatted ones). Place it on the reader.
    • Expected within about 1 s: touchscreen Home "TAG FOUND / Tag type not supported yet / NTAG or other Type 2 tag (NFC-A)…"; Manage tag page shows the same with "ID 04…" (7-byte, starts with 04); GET /api/v1/nfc has state: "unsupported_technology", technology: "nfca", nfca.kind: "type2", nfca.sak: 0, nfca.length: 7; serial line NFC-A tag nfcid1=04:… sak=0x00 … kind=type2.
    • Failure looks like: Home stays on "Place a spool" (detection did not work), or state: "error" with last_error naming "NFC-A mode" / "NFC-A detection I2C" / "NFC-V mode" (mode switch failed; please paste the serial lines).
    • Remove it: within about 1.5 s Home returns to "Place a spool".
  2. MIFARE Classic 1K (Bambu/Creality/Anycubic spool tag, or any Classic card). Same as above, expected nfca.kind: "mifare_classic", sak: 8 (0x18 for a 4K card), 4-byte ID.
  3. OpenPrintTag (SLIX2) timing, before and after. With beta.4, then beta.5: place a written OpenPrintTag and count the time from placement until the Home page shows the spool name; lift it and count until "Place a spool" returns. Repeat 5 times each. Expected: no measurable difference (same poll cadence, NFC-V first). Please record the numbers on the PR or in Poll ISO 14443A in addition to ISO 15693 (groundwork for other tag types) #93.
  4. OpenPrintTag after an NFC-A tag. Place an NTAG, then swap it for the OpenPrintTag: the spool must be identified as usual.
  5. Two tags. An NTAG and a MIFARE card together: "Two tags on the reader. Remove one." / "Multiple NFC-A tags detected".

What I could not verify: that the ST25R3916B accepts the mode switch with the field on in this wiring (RFAL's worker does the same, but it was not run on this station by me), the real detection distance for NFC-A tags with the station's antenna, and the timing numbers in step 3.

Not in this PR (by design): reading NTAG (#95) or MIFARE Classic (#94), writing anything, NFC-B/F (#97), other ISO 15693 chips (#96).


_Generated by [Claude Code](## Also in this PR: automatic manual publishing

.github/workflows/docs-publish.yml ("Publish manual") runs on a merge to main that changes documentation-site/**, VERSION or tools/export_docs.py, and on workflow_dispatch. It runs the CI documentation checks, exports the manual, syncs it with rsync -a --delete over a deploy-key checkout of 76cb/OpenTag-Station-Docs (keeping that repo's .git, .github, .gitignore), runs the Docs repository's own four checks, and pushes one commit Manual for <version> from 76cb/OpenTag-Station@<sha>; it stops with "Manual already up to date" when nothing changed. documentation-site/README.md and the manual's release page describe the flow and the one-time setup (deploy key with write access on the Docs repo, secret DOCS_DEPLOY_KEY here, one manual run).

Validated locally: the CI documentation step list passes; a dry run exported the manual, synced it into a fresh clone of the Docs repository (25 files change, bringing it from 1.0.0 to this head) and passed its four checks. Nothing was pushed to the Docs repository. The workflow YAML parses; actionlint is not available in this sandbox, and rsync is not installed here, so the sync was emulated for the dry run (the GitHub runner image has rsync). It rides on this PR because this session may only push to this branch; say the word and I move it to its own branch and PR.

https://claude.ai/code/session_01QcYr11a5B65kS64MagbgF1)_


Generated by Claude Code

Each 500 ms poll cycle still runs the NFC-V inventory first. Only when it
finds nothing and tracks nothing does the same field window also run an
NFC-A detection (mode switch, REQA, anticollision), so OpenPrintTag
detection and removal timing is unchanged. An NFC-A tag is reported as
unsupported_technology with its NFCID1, SAK, ATQA and a kind derived from
the SAK; nothing is read from it and it is not handed to the workflow.

The touchscreen, browser and API name such a tag ("Tag type not supported
yet") instead of staying on "Place a spool". check_production_nfc.py
permits exactly the three detection calls in the I2C driver and forbids
T2T, ISO-DEP and NFC-DEP routines in the ELF.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcYr11a5B65kS64MagbgF1
Regenerated with tools/curate_public_images.py from the opentag-ui-review
artifact of CI run 39650026601 (head d452806); 123 capture receipts
verified, including the new wt32-unsupported-type fixture.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcYr11a5B65kS64MagbgF1
A merge to main that changes documentation-site/, VERSION or
tools/export_docs.py runs .github/workflows/docs-publish.yml: the CI
documentation checks, the export, an rsync --delete over a deploy-key
checkout of the Docs repository (keeping its .git, .github and
.gitignore), the Docs repository's own four checks, then one commit
"Manual for <version> from 76cb/OpenTag-Station@<sha>" pushed to its
main. Nothing is pushed when the export is unchanged. Pre-releases are
published too. The README and the release page describe the flow and
the one-time deploy-key setup.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcYr11a5B65kS64MagbgF1

76cb commented Oct 21, 2026

Copy link
Copy Markdown
Owner Author

CI on 737b302 is green (firmware, ui-review, documentation, version-policy). Numbers from the firmware job, against main at 92deef6 (1.0.1-beta.4):

main (beta.4) this PR delta
RAM (static, from the build) 111308 B 111364 B +56 B
Flash 2654497 B 2662953 B +8456 B
Static internal RAM (check_production_memory.py) 111308 B 111364 B +56 B

The ELF allow-list check passed with the three NFC-A detection calls and the new forbidden-symbol list; the NFC, HTTP and touch stack checks passed. Screenshot receipts were refreshed from this run's opentag-ui-review artifact (123 receipts, including the new wt32-unsupported-type fixture).

Not verified here: anything on a physical WT32; the acceptance steps are in the PR description.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants