Skip to content

Latest commit

 

History

History
652 lines (480 loc) · 31.1 KB

File metadata and controls

652 lines (480 loc) · 31.1 KB

TigerTag Python SDK — Open-source RFID protocol for manufacturing material identification

TigerTag Python SDK

PyPI Tests Python License: Apache 2.0 Protocol Offline first

Offline Python SDK for TigerTag RFID material identification.

TigerTag is the world's most widely deployed open-source RFID protocol for manufacturing material identification — with over 2 million chips deployed worldwide. Adopted by major brands including eSun, Rosa3D, Sunlu, R3D, Landu and many more. Currently covers filament and resin. Designed to extend to any physical material (sheet goods, wood, PMMA, metals, composites…). All material data is stored directly on the NTAG chip — 100% offline.


Industry adoption

TigerTag is the #1 RFID material identification protocol in the 3D printing industry and the only open-source standard with broad manufacturer adoption at scale.

Metric Value
Chips deployed worldwide 2 000 000+
Filament / resin brands eSun · Rosa3D · Sunlu · R3D · Landu · and more
Connected printers & slicers Snapmaker · Bambu Lab · FlashForge · Elegoo · Creality · and more coming
Exclusive integrations HueForge (Transmission Distance) · TD1s by Ajax (filament manager)
Cost for end users 100% free — protocol, SDK, Studio Manager, mobile apps
Protocol status Open specification (CC-BY-4.0) — irrevocable, royalty-free right to implement
Hardware Tiger Scale (DIY ~30 € open-source) · TigerTag Pod (read/write desktop + mobile)
Ecosystem maturity Desktop app · Mobile app · Pod · DIY scale · Firebase · Python SDK
Chip compatibility NTAG213 · NTAG215 · NTAG216 · any ISO 14443-3 compatible

What makes TigerTag unique

1 — Proof of authenticity (ECDSA-P256)

TigerTag is the only material RFID protocol to offer cryptographic proof of authenticity. Each signed chip carries an ECDSA-P256 signature that binds the chip UID to the product data. Any reader — including this SDK — can verify the signature fully offline, with no server call:

result = tag.verify()   # VALID — chip is genuine and untampered
                        # INVALID — data has been modified or chip is cloned
                        # NOT SIGNED — unsigned Maker tag (verification not required)

No other RFID material protocol provides on-chip cryptographic authentication at this level.

2 — Chips reusable forever

TigerTag chips are never write-locked. Once a spool is finished, the chip gets a second life:

  • Erase and reprogram as a fresh TigerTag for a new spool: TigerTag.erase()
  • Reprogram with any NFC / NDEF standard for a completely different use case
  • Use as a plain NTAG tag in any NFC-capable application

Zero electronic waste. The chip is a permanent, reusable asset — not single-use packaging. No competing protocol offers this combination of authentication and unlimited reusability.

3 — Remote update by the manufacturer (TigerTag+)

TigerTag+ is the only material RFID protocol with remote over-the-air update capability for manufacturers. When a filament or material brand publishes improved print settings, corrected temperatures, or updated material data to the TigerTag cloud API, every chip already deployed in the field can receive those updates — without recalling a single spool:

# On any reader / printer / slicer that uses this SDK:
patched_tag, changes = tag.patch_from_api()   # fetch latest manufacturer data + apply to chip
print(f"{len(changes)} field(s) updated by manufacturer")

# Or inspect what changed before applying:
diffs = tag.diff_api()
for d in diffs:
    print(f"{d.field}: chip={d.chip_value!r} → manufacturer={d.api_value!r}")

The update is manufacturer-driven and cryptographically scoped — only the product data fields can change. The chip UID and signature remain protected. No other RFID material protocol gives manufacturers this level of post-deployment control over their product data.

4 — Native HueForge integration

TigerTag is the only RFID protocol natively integrated with HueForge. The TD (Transmission Distance) value is stored directly on the chip and read by HueForge without any manual entry. It is also the only protocol supported by TD1s by Ajax, the open-source filament manager.


Hardware ecosystem

Device What it does Price
Tiger Scale Open-source DIY ESP32 smart scale — reads the TigerTag, weighs the spool, updates measure_available in real time ~30 € in parts
TigerTag Pod Plug-and-play NFC reader/writer — read and write chips from your desktop (via TigerTag Studio Manager) or from your phone (via TigerTag RFID Connect on iOS and Android) —

The TigerTag Pod works with the TigerTag RFID Connect app (iOS + Android) for full mobile read/write access — no soldering, no setup, just plug in and scan.

Everything is free for end users: the protocol, this SDK, TigerTag Studio Manager, the mobile apps, and all community tools. No subscription, no lock-in.


Try the Playground

No NFC hardware required — explore the full SDK output directly in your browser. With an ACS ACR122U (or any PC/SC reader) plugged in, it also reads and writes real chips.

Launch Playground

# Start the dev server
python3 tools/server.py 7432

# Open in browser
open http://localhost:7432/tools/playground.html

One page, two servers. tools/playground.html is the same file, byte for byte, in the JavaScript SDK and the Python SDK; each repository's tools/server.* implements the same server contract (docs/playground-api.md) with its own SDK, and the page adapts its names and code (create() shown in camelCase or snake_case, toRawDict() / to_raw_dict()…) to GET /api/version. python3 scripts/check_playground_sync.py checks that the copy here is identical to the other repository's (the local checkout next to this one, or GitHub main); the test suite runs it and skips it when neither is reachable. Change the page in both repositories together.

The playground has three panels:

  • Left — generate TigerTag / TigerTag+ / Init tags with presets or custom values
  • Center — parsed output cards: Protocol, Material, Colors, Print Settings, Traceability, Cloud API
  • Right — collapsible SDK panel: pretty(), verify(), to_raw_dict(), to_dict(), raw_api(), diff_api()

When served by tools/server.py, the playground also runs the Python SDK's validate() on every generated or imported tag (POST /api/parse), and POST /api/build exposes TigerTag.create().

NFC readers (ACR122U / PC/SC)

pip install "tigertag[nfc]"        # or: pip install pyscard
python3 tools/server.py 7432

Connected readers appear in the header ("ACS ACR122U", with a status dot). Place a chip on a reader and it is read (pages 0x04–0x27), parsed by the SDK and loaded into the form, including tag index / count. Burn writes the full 144-byte image, pages 0x04–0x27, to every reader holding a chip, after a confirmation. The signature pages 0x18–0x27 are always written as 00: only a certified manufacturer can issue a valid signature (it covers the chip UID), so the playground never writes one — it only reads signatures to verify them — and no stale signature from a previous tag is left behind. The UID / lock pages 0x00–0x03 and the configuration pages 0x28+ are never written. Raw Read shows an annotated hex dump per reader. Without pyscard the server still runs and the header shows "No reader" with the install command. Readers are opened in PC/SC shared mode, so another application (e.g. the JS playground) can use them at the same time.

TigerTag+ from the official catalogue

In the TigerTag+ tab, type a product ID and click Load from catalogue: the whole form is filled from the official catalogue (material, aspects, colours, temperatures, quantity), the catalogue title, brand, SKU, barcode and photo are shown, and the Burn preview is built — as a twin tag when two readers hold a chip. Update catalogue downloads the latest id_catalog.json (it changes every day); the status line shows how many products it holds and when it was updated. Needs internet the first time (about 12 MB, then cached). Server endpoints: GET /api/catalog/<id>, GET /api/catalog/info, POST /api/catalog/refresh.


Install

pip install tigertag                    # core only — stdlib, works fully offline (data bundled)
pip install tigertag[sync]              # + requests (optional — updates use the standard library)
pip install tigertag[verify]            # + cryptography (ECDSA signature check)
pip install tigertag[full]              # everything
pip install "tigertag[nfc]"             # + pyscard (playground: ACR122U / PC/SC readers)

Zero configuration. Zero network required on first run. Bundled reference databases ship with the package.


Quick start

from tigertag import TigerTag

tag = TigerTag.from_pages(uid, payload)        # from your NFC SDK
print(tag.pretty())                            # human-readable summary
print(tag.verify())                            # VALID / NOT SIGNED / INVALID
print(tag.to_dict())                           # JSON-ready dict

That's it. Works immediately after pip install tigertag.


What is TigerTag?

TigerTag is an open-source RFID protocol that stores manufacturing material data directly on NFC chips (NTAG213 / NTAG215 / NTAG216, ISO 14443-3 compatible). No cloud dependency for reading — all data lives on the chip.

Tag types:

Tag type ID Product Offline Cloud
TigerTag (Maker) 0xFFFFFFFF Full data on chip —
TigerTag Init 0x00000000 Blank template —
TigerTag+ numeric ID Full data on chip API for live updates

This SDK reads all three types identically. TigerTag+ cloud comparison (diff_api()) requires requests.

Protocol spec: github.com/TigerTag-Project/TigerTag-RFID-Guide


Constructors

Method Input When to use
TigerTag.from_pages(uid, payload) 80 or 144 bytes + 7-byte UID NFC SDK integration (recommended)
TigerTag.from_dump(data) 80 / 144 / 180 bytes Binary dumps, ACR122U raw read
TigerTag.from_file(path) path to .bin file Testing, offline batch processing

from_pages is the primary constructor for production use. NFC SDKs always provide the UID as a separate property — pass it directly for full signature verification.

from_dump with 180 bytes (full chip dump including system pages) auto-extracts the 7-byte UID.


Input formats

from_pages(uid, payload) — NFC SDK workflow

NFC SDKs always expose the UID as a dedicated property. Pages 0–3 (system pages: lock bytes, capability container) are never part of the user data payload.

Payload Pages UID Verifiable
144 bytes 0x04–0x27 (user data + signature) Required (7 bytes) Yes
80 bytes 0x04–0x17 (user data, no signature) Required (7 bytes) N/A

from_dump(data) — binary dump workflow

Dump Content UID Verifiable
180 bytes Full chip (pages 0–44, includes system pages) Auto-extracted Yes
144 bytes Partial dump (user data only, no system pages) Not available No
80 bytes User data only Not available N/A

Key methods

# Read
tag.pretty(db=None, sig_result=None) -> str        # human-readable summary
tag.to_dict(db=None)                 -> dict        # JSON-serializable, all labels resolved
tag.to_raw_dict()                    -> dict        # raw protocol fields, no resolution
tag.to_bytes(include_signature=False)-> bytes       # re-serialize to chip bytes
tag.validate()                       -> list[str]   # sanity check — returns list of warnings
tag.verify(db=None)                  -> SignatureResult

# Write (immutable — all return a new TigerTag)
TigerTag.create(**kwargs)            -> TigerTag    # build from scratch
TigerTag.as_init(uid)               -> TigerTag    # blank Init tag
TigerTag.erase()                    -> bytes        # 80 zero bytes — write to chip to wipe
tag.patch(**kwargs)                  -> TigerTag    # surgical field update

# Cloud (TigerTag+ only — requires requests)
tag.raw_api(db=None)                -> dict | None  # fetch live product data from API
tag.diff_api(api_data, db)          -> list[ApiDiff]           # compare chip vs API
tag.patch_from_api(api_data, db)    -> tuple[TigerTag, list[ApiDiff]]  # apply API values
tag.sync_db(db_path=None, force=False) -> list[str] # update reference databases

Key properties

tag.is_maker          # True if id_product == 0xFFFFFFFF
tag.is_init           # True if id_product == 0x00000000
tag.is_plus           # True if id_product is a valid cloud ID
tag.is_signed         # True if signature bytes are non-zero
tag.uid_hex           # "04AABBCCDDEE11" or None
tag.color1_hex        # "#FF3232"
tag.td_value          # 12.5  (HueForge Transmission Distance)
tag.tag_index         # 1     which of the item's tags this is, from 1 (0=unknown)
tag.tag_count         # 2     tags on the item (0=unknown, 1=single, 2=twin tag)
tag.manufacturing_date# datetime (UTC)
tag.stock_percent     # 75.0  or None

Write / CRUD operations

from tigertag import TigerTag

# Build a new tag from scratch
tag = TigerTag.create(
    uid=bytes.fromhex("04A1B2C3D4E5F6"),
    id_material=38219,        # PLA
    id_brand=19961,           # Rosa3D
    nozzle_temp_min=195,
    nozzle_temp_max=230,
    color1_r=255, color1_g=0, color1_b=0, color1_a=255,
    measure=1000, id_unit=21,
    tag_count=2, tag_index=1,     # twin tag, tag 1 of 2 → byte +39 = 0x12 (tag 2 gets tag_index=2)
)

# Blank TigerTag Init chip (ready for programming)
init_tag = TigerTag.as_init(uid=bytes.fromhex("04A1B2C3D4E5F6"))

# Erase a chip — write the returned 80 bytes to the NFC chip
blank_bytes = TigerTag.erase()

# Immutable surgical update — returns a new TigerTag, original unchanged
patched = tag.patch(nozzle_temp_min=200, dry_temp=55)
twin_2  = tag.patch(tag_count=2, tag_index=2)     # shortcut for the tag_info byte

# TigerTag+ cloud sync
api_data = tag.raw_api()                    # fetch live product data
diffs = tag.diff_api(api_data)              # what differs chip vs cloud?
patched_tag, applied = tag.patch_from_api() # apply all cloud values
print(f"{len(applied)} field(s) updated from cloud")

Protected fields — patch() raises ValueError if you try to modify: id_tigertag, id_product, uid, signature_r, signature_s.

Tag index & tag count (protocol v2.2)

Page 0x0D byte 3 (payload offset +39) holds one byte, tag_info, split in two nibbles: the high nibble is the tag index (which of the item's TigerTags this one is, from 1) and the low nibble is the tag count (how many TigerTags the item carries — a filament spool, a resin bottle…; what the item is comes from id_type), so the hex reads like "index/count". 0 means unknown in both.

tag_info tag_index tag_count Meaning
0x00 0 0 Unknown — every tag written before v2.2
0x11 1 1 Single tag (1/1)
0x12 / 0x22 1 / 2 2 Twin tag: tag 1 of 2 / tag 2 of 2
0x02 0 2 Two tags, index unknown

All tags of an item share the same count and the same timestamp (twin tag pairing ID). describe() names the item from id_type: "Tag 1 of 2 on this filament." ("on this item" when the type is unknown). The byte is not covered by the ECDSA signature, so setting it never invalidates a signed chip. create() and patch() raise ValueError for a count or index outside 0–15; validate() warns when the index exceeds a known count.

TigerTag+ from the official catalogue

Give only a TigerTag+ product ID and get a complete tag ready to write, from the official catalogue (id_catalog.json in TigerTag-RFID-Guide, 14 000+ products):

from tigertag import TigerTag, load_catalog, refresh_catalog, catalog_info, catalog_entry

catalog = load_catalog()                     # downloaded once (~12 MB), then cached
tag = TigerTag.from_catalog(3527039449, catalog=catalog, tag_count=1, tag_index=1)
entry = catalog_entry(3527039449, catalog)   # title, brand, sku, barcode, img_src…
print(entry["title"], tag.nozzle_temp_min, tag.nozzle_temp_max)   # Rapid TPU 95A - Black 200 250
chip.write_pages(4, tag.to_bytes())

refresh_catalog()                            # force an update (unchanged file → HTTP 304, no download)
catalog_info()                               # {"downloaded", "count", "fetched_at", "checked_at", "url", …}
  • from_catalog() returns a TigerTag (TigerTag+, id_product = the product ID); the catalogue metadata is not on the chip — read it with catalog_entry().
  • RFID_Data mapping (the same chip layout for every product type): data1 → diameter id, data2/data3 → nozzle min/max, data4/data5 → dry temp/time, data6/data7 → bed min/max. null values → 0 (id_aspect2: null → 0x00 "(none)"). Colours 2 and 3 come from color_r2/g2/b2, color_r3/g3/b3, or color_info.colors[1..2].
  • The catalogue is not bundled. It is cached in the user cache directory (~/Library/Caches/tigertag, ~/.cache/tigertag, %LOCALAPPDATA%\tigertag\Cache, or TIGERTAG_CACHE_DIR) and refreshed after max_age (default 24 h). Offline, the cached copy is used; offline with no cache raises RuntimeError. An unknown ID raises KeyError.

ApiDiff

ApiDiff is a namedtuple (field, chip_value, api_value):

from tigertag import ApiDiff, TigerTag

tag = TigerTag.from_pages(uid, payload)
diffs = tag.diff_api()

for d in diffs:
    print(f"{d.field}: chip={d.chip_value!r}  →  api={d.api_value!r}")

Fields compared: nozzle_min, nozzle_max, bed_min, bed_max, dry_temp, dry_time, type, material, brand, diameter, aspect_1, aspect_2, color_1, color_2, color_3, measure_g, measure_unit.


Signature verification

result = tag.verify()    # fully autonomous — finds the public key from the bundled DB

result.ok       # True only for VALID
result.status   # "valid" | "invalid" | "unsigned" | "no_crypto" | "no_key" | "no_uid"
str(result)     # "VALID" | "INVALID" | "NOT SIGNED" | "NO CRYPTO — …" | "NO PUBLIC KEY — …" | "NO UID — …"
result.to_dict()# {"status": "valid", "ok": True, "detail": "…"}
Status Meaning
VALID Signature matches — chip is authentic
INVALID Signature present but does not match UID + data
UNSIGNED No signature bytes — Maker tag or unverified
NO_CRYPTO cryptography package not installed — run pip install tigertag[verify]
NO_KEY No matching public key in database for this protocol version
NO_UID UID not provided — cannot verify (use from_pages(uid, payload))

ECDSA-P256 verification uses the public key bundled in id_version.json — works fully offline.


Reference data: offline, automatic and manual updates

The reference tables (id_version, id_material, id_aspect, id_type, id_diameter, id_brand, id_measure_unit, with last_update.json) and the TigerTag+ catalogue (id_catalog.json.gz, about 1 MB) ship inside the package, refreshed at every release, so everything works offline right after pip install tigertag.

from tigertag import TigerTagDB

db = TigerTagDB()                          # bundled + downloaded data, checked once a day
db = TigerTagDB(offline=True)              # zero network access (also TIGERTAG_OFFLINE=1)
db = TigerTagDB(auto_update=False)         # no automatic check; update() still works
db = TigerTagDB(data_dir="./tigertag-data")    # keep downloads in a project folder
db = TigerTagDB("/path/to/my/db")          # your own folder, used exclusively

db.material(38219)     # {"id": 38219, "label": "PLA", "density": 1.24, ...}
db.update()            # check now; returns the files that changed
db.update(force=True, catalog=True)        # re-download every table and the catalogue
db.info()              # per table: source, path, timestamp; last check; data dir; catalogue
db.catalog()           # the TigerTag+ catalogue with the same settings
TigerTagDB.label(entry)                    # safe label extraction helper

Where each table comes from (per file):

Priority Source When
1 Your folder (db_path) Used exclusively: no fallback to the bundled copy, no network unless you call update() (which then updates that folder). A missing table is a FileNotFoundError listing what is missing; the catalogue there is only needed by from_catalog().
2 Downloaded copy in the data directory Default: the user cache directory (~/Library/Caches/tigertag, ~/.cache/tigertag, %LOCALAPPDATA%\tigertag\Cache); data_dir= or TIGERTAG_DATA_DIR to choose another folder.
3 Bundled copy Always present. Between 2 and 3 the newest wins, per table, by the last_update.json timestamps — a fresh release beats an older download.

Automatic update. The first TigerTagDB of a process checks for new data at most once a day (max_age, tracked in db_state.json in the data directory): one small request (last_update, TigerTag API with the GitHub mirror as fallback, 5 s timeout), then only the tables whose timestamp changed are downloaded. It never raises — on any failure the local data is used. The catalogue follows the same rule: the newest of the bundled and downloaded copies, refreshed from GitHub once a day (conditional request, unchanged file not downloaded again).

Offline. offline=True (on TigerTagDB, load_catalog(), TigerTag.from_catalog()) or TIGERTAG_OFFLINE=1 means zero network calls anywhere; an explicit update() is refused with a clear message. auto_update=False only turns off the automatic check.

Command line.

tigertag update                    # check now, download what changed
tigertag update --force --catalog  # re-download every table and the catalogue
tigertag update --data-dir ./data  # into a chosen folder
tigertag dump.bin --offline        # parse without any network access

Behaviour change in 1.3.0: the SDK now makes one small network check per day for new reference data (previously it never did unless asked). Use offline=True, TIGERTAG_OFFLINE=1 or auto_update=False to turn it off. sync(), sync_databases(), tag.sync_db() and tigertag --sync-only keep working (they now use the standard library, requests is no longer needed).


NFC SDK integration

from_pages() accepts exactly what NFC SDKs provide:

# Android (NfcA / MifareUltralight)
uid     = tag.id                           # ByteArray → bytes
payload = mifare.readPages(4, 39)          # 144 bytes

# iOS (CoreNFC)
uid     = tag.identifier                   # Data → bytes
payload = tag.readNDEF(...)                # pages 4–39

# Flutter (flutter_nfc_kit)
uid     = bytes.fromhex(tag.id)
payload = await FlutterNfcKit.readBlock(4, length=144)

# Python nfcpy / ACR122U
uid     = tag.identifier                   # bytes
payload = tag.read(4, 36)                  # 36 pages × 4 bytes = 144 bytes

tag = TigerTag.from_pages(uid, payload)
result = tag.verify()                      # fully autonomous

ACR122U — full example (nfcpy)

pip install nfcpy "tigertag[verify]"
import nfc
from tigertag import TigerTag

def on_connect(tag):
    uid     = tag.identifier        # 7 bytes — provided directly by nfcpy
    payload = tag.read(4, 36)       # 36 pages × 4 bytes = 144 bytes

    tt = TigerTag.from_pages(uid, payload)
    print(tt.pretty())
    print(tt.verify())              # VALID / NOT SIGNED / INVALID
    return True

with nfc.ContactlessFrontend("usb") as clf:
    clf.connect(rdwr={"on-connect": on_connect})

See examples/integrate_nfc_sdk.py for all platforms (Android, iOS, Flutter, Arduino).


Chip memory layout

NTAG chip memory layout — pages 0x04–0x27


CLI

tigertag dump.bin              # parse + human-readable output
tigertag dump.bin --json       # output as JSON
tigertag dump.bin --raw        # raw protocol fields, no DB lookup
tigertag update [--force]      # check for new reference data now (--catalog, --data-dir)
tigertag dump.bin --offline    # no network access at all
tigertag --version             # show SDK version

python -m tigertag dump.bin    # same, via module runner

Standalone single-file usage

For projects where you can't add a dependency — drop parse_tigertag.py directly:

from parse_tigertag import TigerTag

tag = TigerTag.from_dump(open("dump.bin", "rb").read())
tag.sync_db()           # auto-download databases (requires requests)
print(tag.pretty())

parse_tigertag.py is a complete standalone copy — no tigertag/ package required.


TigerTag ecosystem

Official hardware

Device Description Cost
TigerTag Pod Plug-and-play NFC reader/writer — connects to desktop (Studio Manager) or phone (RFID Connect app). Read and write chips with no soldering, no setup. —
Tiger Scale Open-source DIY ESP32 smart scale — reads the tag on scan, weighs the spool, and updates measure_available on the chip in real time. Full BOM and firmware available. ~30 € in parts

Official software

Tool Platform Description
TigerTag-RFID-Guide Spec Open protocol specification
TigerTag-SDK-Python Python This SDK — parse, verify, write, diff
TigerTag-SDK-JS JavaScript / Node.js Official JS SDK — same API, no Python runtime required
TigerTag Studio Manager Windows / macOS / Linux Open-source desktop inventory manager — works with TigerTag Pod and ACR122U
TigerTag RFID Connect iOS Official mobile app — read/write using the phone's built-in NFC
TigerTag RFID Connect Android Official mobile app — read/write using the phone's built-in NFC
TigerTag Firebase Integration Cloud Firebase backend integration example
Tiger Scale ESP32 firmware Open-source firmware for the DIY smart scale

Community integrations: OpenRFID · Home Assistant · Snapmaker U1 firmware · TD1s by Ajax


License

Open source: Apache License 2.0 — see LICENSE

This SDK is Apache-2.0, which carries an express patent grant.

The TigerTag protocol itself requires no licence and no payment to implement, in any product, open source or proprietary, at any volume. See LICENSING.md.

Trademark, TigerTag+ signature issuance, official product-ID allocation, and officially supplied media are separate from the protocol — see LICENSE_COMMERCIAL.md. Contact licensing@tigertag.io

Protocol spec: github.com/TigerTag-Project/TigerTag-RFID-Guide