feat: Add NFC spool identification: TigerTag and OpenPrintTag support#880
feat: Add NFC spool identification: TigerTag and OpenPrintTag support#880goeland86 wants to merge 20 commits intoDonkie:masterfrom
Conversation
Fix two TS errors introduced in the TigerTag implementation: - header/index.tsx: use correct RefineThemedLayoutHeaderProps export - spools/show.tsx: use `query` instead of `queryResult` from useShow() Update implementation log with verification results (223 tests pass, frontend builds clean). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix TigerTag API base URL to use correct Xano path (/api:tigertag/) - Rewrite TigerTag sync to use paginated POST /product/get/all endpoint - Update TigerTagProduct model to match actual API response schema - Replace hishel with httpx for TigerTag HTTP calls (POST not cacheable) - Fix Optional[str] -> str | None for Python 3.10+ compatibility - Remove duplicate [project.license] section from pyproject.toml - Regenerate uv.lock with nfcpy optional dependency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ensures the frontend is always built with the correct API URL without needing to pass it manually. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…odec - Add POST /api/v1/nfc/create-from-tag endpoint to create filament+spool from decoded TigerTag data (with TigerTag external DB lookup) - Add browser-side NFC scan support with create-from-tag UI in scanner modal - Fix TigerTag binary codec to use big-endian encoding (matching the actual TigerTag RFID Guide spec; code examples on doc.tigertag.io were misleading) - Fix weight field decoding: upper 24 bits = weight, lower 8 = unit ID - Fix NFC read service page stepping (range 4-40 step 4, not step 1) - Add TigerTag diameter ID mapping (56->1.75mm, 57->2.85mm) - Add client-side TigerTag codec (tigertagCodec.ts) with matching BE format - Add Dockerfile NFC support (libusb, uv --extra nfc) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add openprinttag_codec.py: NDEF TLV parser + CBOR decoder for NFC-V (ISO 15693) tags using the OpenPrintTag spec (Prusa). Decodes all main section fields (UUIDs, material, brand, color, temps, weights) and aux section (consumed_weight). Includes UUID derivation and aux write-back. - Add openprinttag_lookup.py: spool matching by instance_uuid or package_uuid, with auto-create (vendor + filament + spool from tag data). - Update /api/v1/nfc/lookup: auto-detect tag format from raw bytes (0xE1 = OpenPrintTag, 0x5C15E2E4 = TigerTag). Add tag_type, nfc_tag_uid, and auto_create request fields. Return tag_format in response. - Add cbor2 and ndeflib to [nfc] optional dependencies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add NFC tag support for automatic spool identification using the TigerTag format (ISO 14443A / NTAG213). Spools are matched by their TigerTag product ID against the filament external_id field — no schema changes. Backend: - POST /api/v1/nfc/lookup — accepts raw tag binary or id_product, returns matched spool_id - POST /api/v1/nfc/read, /write, /encode — server-side USB reader - POST /api/v1/nfc/create-from-tag — auto-create spool from tag data with TigerTag external product DB lookup - TigerTag binary codec (big-endian NTAG213 144-byte format) - TigerTag product DB sync (paginated Xano API) Frontend: - NFC scanner modal with browser Web NFC API - NFC write modal for encoding spools onto NTAG213 tags - Client-side TigerTag codec Infrastructure: - Dockerfile: add libusb, uv --extra nfc - Environment flags: SPOOLMAN_NFC_ENABLED, SPOOLMAN_TIGERTAG_ENABLED - Optional dependency: nfcpy Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add support for the OpenPrintTag standard (Prusa's open NFC spec) using ISO 15693 / NFC-V tags with NDEF/CBOR-encoded filament data. - NDEF TLV parser + CBOR decoder for NFC-V tag memory (ICODE SLIX2) - Decodes all main section fields: UUIDs, material type/class, brand, color, temperatures, weights, density, diameter - Decodes aux section: consumed_weight for usage tracking - UUID derivation per spec (UUIDv5 from tag UID, brand name, etc.) - Spool matching by instance_uuid (per-spool) or package_uuid (per-product) - Auto-create vendor + filament + spool from tag data on first scan - /api/v1/nfc/lookup auto-detects format: 0xE1 = OpenPrintTag, 0x5C15E2E4 = TigerTag - New request fields: tag_type, nfc_tag_uid, auto_create - New optional dependencies: cbor2, ndeflib Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pr/nfc support
- Add per-spool matching using (id_product, timestamp) as a composite key stored in SpoolField "nfc_tag_id". Both sides of paired tags share the same timestamp, so they resolve to the same spool. - Add real-time TigerTag API product lookup using tag UID + product_id, since the tag's product_id differs from the API's internal database IDs. - Sync and cache TigerTag brand and material lookup tables from the API (GET /brand/get/all, GET /material/get/all) for name resolution. - Fall back to brand/material name resolution when product lookup fails, creating filaments named e.g. "Rosa3D PLA" instead of "TigerTag tigertag_N". - Add auto_create support for TigerTag in the /lookup endpoint. - Fix .unique() call on filament external_id query (joined eager loads). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add per-spool matching using (id_product, timestamp) as a composite key stored in SpoolField "nfc_tag_id". Both sides of paired tags share the same timestamp, so they resolve to the same spool. - Add real-time TigerTag API product lookup using tag UID + product_id, since the tag's product_id differs from the API's internal database IDs. - Sync and cache TigerTag brand and material lookup tables from the API (GET /brand/get/all, GET /material/get/all) for name resolution. - Fall back to brand/material name resolution when product lookup fails, creating filaments named e.g. "Rosa3D PLA" instead of "TigerTag tigertag_N". - Add auto_create support for TigerTag in the /lookup endpoint. - Fix .unique() call on filament external_id query (joined eager loads). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New POST /api/v1/nfc/bind endpoint and "Link NFC Tag" button on the spool detail page. Allows scanning an NFC tag and binding it to an existing spool via SpoolField nfc_tag_id, so future scans resolve to that specific spool. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New POST /api/v1/nfc/bind endpoint and "Link NFC Tag" button on the spool detail page. Allows scanning an NFC tag and binding it to an existing spool via SpoolField nfc_tag_id, so future scans resolve to that specific spool. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
I unfortunately don't have OpenPrintTag tested because I lack the physical hardware at present. If anyone else wants to test this, please do share the test results. |
|
Without digging into this or thinking too much, I wonder if you have considered to cross support with spoolease too - though the NFC info would be coming via a spoolease api instead of direct from the scanner. anyway, cool! more features :) |
|
I had not, but I can easily try to get that implemented? I don't pretend to know every format or effort to support nfc for 3D printing 😉. I would need help getting the tests done for anything I don't have access to, but I'm happy to do it. |
TigerTag+ uses magic number 0x12C4C408 and enables cloud-synced product IDs (0x00000001–0xFFFFFFFE) while sharing the same 144-byte binary format. Both variants are now recognized for reading/lookup; writing still uses Maker V1 format since we lack the ECDSA signing key for TigerTag+. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts: # client/src/components/nfcBindModal.tsx
The TigerTag RFID Guide README has incorrect hex values for magic numbers. The actual values from the id_version.json API database (confirmed by reading real tags written by the TigerTag mobile app): - Maker V1: 0x5BF59264 (was 0x5C15E2E4 from README) - TigerTag+: 0xBC0FCB97 (was 0x12C4C408 from README) - Init: 0x6C41A2E1 (was 0x6C46A3C1 from README) Also adds TigerTag+ detection, isTigerTag() helpers, and improves the browser NFC write warning about NDEF incompatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts: # client/src/utils/tigertagCodec.ts # spoolman/tigertag_codec.py
The ACR122U NFC reader would disappear from the UI after being unplugged/replugged because Docker's devices: directive snapshots device nodes at container start. Switched docker-compose to a live volumes: bind mount of /dev/bus/usb and added auto-reconnect logic to nfc_service.py so the backend recovers without a container restart. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
How easy would it be to also add support for Qidi NFC tags for usage with their Qidi Box system? They have it documented here: https://wiki.qidi3d.com/en/QIDIBOX/RFID |
|
Let me look. The question is whether I have the hardware to test it. I'm mostly using the tiger tag format right now for cost reasons. |
|
@turw41th so looking at it closer, it looks like we can add it easily. But we'd be using the tag's hardware UID to bind it to an entry in Spoolman. It's exactly what it does with Tigertags, so that's not an issue architecturally. But they're MIFARE tags as opposed to NTAG213 which is what I have on hand, so I won't be able to test it. If you're willing to test it out, I'll have my fork updated with it shortly. |
Add support for Qidi RFID tags alongside TigerTag and OpenPrintTag. Qidi tags use MIFARE Classic 1K (FM11RF08S) with a 3-byte payload encoding material code, color code, and manufacturer ID. - Auto-detect Qidi tags from MIFARE Classic product string or block data - Dual-key authentication (Qidi custom + factory default) - UID-based spool binding since tags lack unique spool identifiers - Fuzzy matching by material type + color as fallback - Auto-create spools with Qidi vendor, material name, and mapped color - Read/write Qidi format from web UI with tag format selector - 35 material codes and 24 color codes with RGB hex values - All existing TigerTag/OpenPrintTag endpoints remain backwards-compatible Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Qidi NFC Tag SupportThis update adds support for Qidi RFID tags (MIFARE Classic 1K / FM11RF08S) alongside the existing TigerTag and OpenPrintTag formats. What's newQidi tags are ISO 14443A MIFARE Classic 1K tags used by Qidi 3D printers and the QIDIBOX filament management system. They store a simple 3-byte payload: material code, color code, and manufacturer ID. Spoolman now reads, writes, and auto-detects these tags through the same NFC infrastructure used for TigerTag and OpenPrintTag. How it works
Supported Qidi materials & colors
AuthenticationQidi tags use MIFARE Classic Crypto-1 authentication with Key A. Two keys are tried in sequence:
Files changed
API examplesLook up a Qidi tag (auto-detect): curl -X POST /api/v1/nfc/lookup \
-d '{"raw_data_b64": "ARIBAAAAAAAAAAAAAAAAAA==", "nfc_tag_uid": "A1B2C3D4"}'Auto-create spool from Qidi tag: curl -X POST /api/v1/nfc/lookup \
-d '{"raw_data_b64": "ARIBAAAAAAAAAAAAAAAAAA==", "tag_type": "qidi", "nfc_tag_uid": "DEADBEEF", "auto_create": true}'Write spool as Qidi tag: curl -X POST /api/v1/nfc/write \
-d '{"spool_id": 1, "tag_format": "qidi"}'Reference
|
|
@goeland86 Very cool, I didn't expect you to be this quick! Let me order some matching NFC tags real quick and I'll test it out with your fork as soon as I have them. |
Reading a Qidi tag → The color index (1-24) is mapped to a known RGB hex value via a lookup table (e.g. color code 18 = Red = #FF362D). That hex value gets stored on the Spoolman spool. Writing a Qidi tag from Spoolman → Since Spoolman allows arbitrary hex colors but Qidi only supports 24 predefined colors, I use nearest-color matching: the code computes the Euclidean distance in RGB space between your spool's color and all 24 Qidi palette entries, then picks the closest one. If it's an exact match it returns immediately, otherwise it snaps to the nearest color. I'll admit I had Claude figure that distance-matching for me. For example, a spool with color #FF0000 (pure red) would map to Qidi color code 18 ("Red", #FF362D). The Spoolman spool retains its original hex color — only the Qidi tag gets the quantized palette index. TL;DR: You don't need to scan an existing Qidi tag first. You can write brand new tags from any Spoolman spool and the color is automatically snapped to the nearest of the 24 Qidi colors. The relevant code is in qidi_codec.py — color_code_from_hex() handles the mapping, and COLOR_CODE_MAP defines all 24 palette entries. |
|
@goeland86 I received the tags, but I did not check the WebNFC api specs beforehand and did not notice that it cannot write or read Mifare classic tags. I also don't have an ncf reader at home. I might vibe code a native android app that acts as a front end for spoolman and uses android.nfc and test it this way. Other wise I'd have to wait for another ali express order with an nfc reader. |
|
So, I think my branch has a webNFC fork that basically transmits the byte arrays to and from Spoolman, so you should be able to use your mobile directly with spoolman in the browser? I haven't really looked into it, my use-case was aimed at PN532's hooked up to the printers, so I put a spool on the printer and it automatically sets the right Spoolman ID on klipper. |
|
I can use the tags and write the tags, as my mifare tags have an NDEF layer. But the tag will always be recognized as an NDEF tag by spoolman and it will use the tigertag format which is not being recognized by my multi material system. |
|
Ah, bummer. Let me see if I can tweak the UI to set a preferred format for the tags. Update: re-read what you said about WebNFC not working with Mifare in general. That's annoying. |
|
Are you going to try some hack or should I look how I could test it on other ways? |
I'm not sure that there's much I could hack, the WebNFC is a capability built into the browser. I'd have to build a custom browser to get the hack working, and I'm not that good, even with an LLM assistant. 😓 |
|
Alright no worries, I'll check if I can whip up an android native frontend app. That might take me a day or two though. |
Summary
Adds NFC tag support to Spoolman, enabling automatic spool identification by scanning NFC-equipped filament spools. Two open tag formats are supported:
Usage
There are three ways to scan NFC tags with Spoolman:
Browser-based scanning — The Spoolman web client includes an NFC scanner modal that uses the Web NFC
API to read and write tags directly from your phone or NFC-equipped computer. No
additional hardware or software needed — just open the Spoolman UI in Chrome on Android and tap a tag.
Server-side USB reader — Attach a USB NFC reader (e.g. ACR1552U) to the machine running Spoolman. Enable with
SPOOLMAN_NFC_ENABLED=TRUE. The/api/v1/nfc/readand/api/v1/nfc/writeendpoints control the reader directly.External reader with API integration — For setups where the NFC reader is physically attached to the printer (not the Spoolman server), the
POST /api/v1/nfc/lookupendpoint accepts raw tag memory from any client and handles all decoding and spool matching server-side. An example implementationis klipper-nfc-daemon, a lightweight daemon that runs on a Klipper host, polls an NFC reader, and
automatically sets the active spool in Moonraker. It supports PN532 (UART), PN5180 (SPI), and ACR1552U (USB) readers.
Backend
POST /api/v1/nfc/lookup— unified endpoint that auto-detects tag format from raw bytes, matches to a spool, and optionally auto-createsspool/filament/vendor records from tag data
POST /api/v1/nfc/read/write/encode— server-side NFC reader endpointsPOST /api/v1/nfc/create-from-tag— create spool from decoded TigerTag dataexternal_id(tigertag_{id}oropt_{instance_uuid}) — no database migrations neededFrontend
Infrastructure
libusbanduv --extra nfcfor optional NFC dependenciescbor2,ndeflib,nfcpy(in[nfc]extra)SPOOLMAN_NFC_ENABLED,SPOOLMAN_TIGERTAG_ENABLEDDesign decisions
external_idfield onFilamentfor tag-to-spool mapping0xE1first byte = OpenPrintTag (NFC-V capability container),0x5C15E2E4magic = TigerTagTest plan
external_idinstance_uuidderived from tag UIDenabled: falsewhen NFC is not configured🤖 Generated with Claude Code