Skip to content

feat(nfc): OpenPrintTag NDEF/CBOR codec - #1112

Open
goeland86 wants to merge 2 commits into
Donkie:masterfrom
goeland86:codec/openprinttag
Open

feat(nfc): OpenPrintTag NDEF/CBOR codec#1112
goeland86 wants to merge 2 commits into
Donkie:masterfrom
goeland86:codec/openprinttag

Conversation

@goeland86

Copy link
Copy Markdown

Summary

Split out from #880, which proposed the full NFC feature (TigerTag/Qidi/OpenPrintTag
decoding, server-side reader endpoints, browser Web NFC UI, auto-create/bind) as one
large PR.

While #880 was in review, #1096 landed: a UID-only tag/scan-relay foundation, with
content decoding explicitly deferred to a later phase. @Donkie confirmed in
this thread
that decoding should be phase 2 built on top of #1096's tag table and scan relay, not
a parallel /api/v1/nfc/* surface, and asked for the codec work split into small,
separately reviewable pieces — OpenPrintTag first, since it's what ships on retail
spools today.

This PR is that first piece: just the OpenPrintTag codec and its unit tests.
#880 is being closed in favor of this PR and the ones that follow it.

What's in this PR

  • spoolman/openprinttag_codec.py — decodes the OpenPrintTag NDEF/CBOR format from
    NFC-V (ISO 15693) tag memory: meta/main/aux CBOR sections, UUID/enum/color fields,
    and an aux-section consumed_weight encoder for in-place tag updates.
  • tests/test_openprinttag_codec.py — 22 tests covering the documented field
    semantics and error paths, built against synthetic NFC-V memory dumps (capability
    container + NDEF TLV + CBOR payload) constructed in the test file itself.
  • No endpoint wiring, no scan-relay integration, no hardware dependency
    (nfcpy/libusb) anywhere in the diff. cbor2 is a dev-only dependency here since
    nothing outside the test suite imports the module yet.

What's next — PR2/3/4 already in the pipeline

This is PR1 of four planned pieces. The other three are built and tested locally,
ready to open as their own PRs once this one's shape is agreed on:

  • PR2 — TigerTag NTAG213 binary codec + tests. Already ships on retail spools
    from Sunlu, Rosa3D, R3D and eSun, so there's a case for it following reasonably
    soon.
  • PR3 — Qidi MIFARE Classic codec + tests. Lowest priority of the three — MIFARE
    key derivation buys nothing writable today.
  • PR4 — an optional TigerTag external-database sync/lookup add-on (off by
    default, lazily imported so core never loads it unless explicitly enabled, no new
    dependency since it only uses what's already core to Spoolman).

None of the above touch #1096's endpoints either. Wiring a codec into /tag/scan
decoding a scanned tag's payload_b64, an opt-in create: true flag for
auto-creating spools (default off, per @Donkie's note that a reader shouldn't
silently manufacture records), and consumed_weight surfaced as something a person
confirms rather than something that silently overwrites tracked weight — is a
further PR once the codec shapes here are settled.

Verification gap — help wanted

I don't have real OpenPrintTag hardware or an official spec test-vector set, so
these tests validate internal consistency (round-trips, documented field mappings,
error paths) against my own reading of the spec, not ground truth. @Donkie suggested
test vectors from the spec repo or a dump from someone with a tagged spool as the
way to close that gap — if anyone reading this has a Prusament (or other
OpenPrintTag-tagged) spool and can pull a raw NFC-V memory dump, that would let me
add a real fixture and be a lot more confident this decodes correctly.

Test plan

  • uv run pytest tests — 286 passed (264 pre-existing + 22 new)
  • uv run ruff check . / ruff format --check . — clean
  • uv lock --check — clean
  • Real OpenPrintTag hardware/spec-vector validation — open, see above

Decodes the OpenPrintTag spec (NDEF-wrapped, CBOR-encoded meta/main/aux
sections) from NFC-V tag memory, plus an aux-section consumed_weight
encoder for scales that want to update a tag in place.

Scoped deliberately narrow per discussion on Donkie#880: codec and tests only,
no wiring into the tag scan/link endpoints from Donkie#1096, no hardware
dependency (nfcpy/libusb) in core. cbor2 is a dev-only dependency here
since nothing outside the test suite imports this module yet.

Tests build synthetic NFC-V memory (capability container + NDEF TLV +
CBOR payload) rather than relying on a hardware dump, since none was
available while writing these. They exercise the codec's documented
field semantics and error paths, not official spec test vectors -- real
hardware/spec-vector validation is still open, per Donkie's ask in the
PR thread.

Also fixes a latent bug found while writing the aux round-trip test:
encode_aux_consumed_weight assumed the decoded aux region was always a
dict, but a misaligned/corrupt region can decode as valid CBOR of a
different type (e.g. a stray zero byte decodes to the int 0), which
raised an undocumented TypeError instead of failing cleanly.
@Niko11111 Niko11111 mentioned this pull request Aug 25, 2026
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.

1 participant