All notable changes to this project will be documented in this file. Format based on Keep a Changelog.
catalog_info()now takes the same options as the JS SDK'scatalogInfo():data_dir(alias ofcache_dir) andoffline(accepted for symmetry withload_catalog(); the function only reads local files, so it never uses the network).
-
Unified playground:
tools/playground.htmlis now one page, byte-for-byte identical in the JavaScript and Python SDKs. Eachtools/server.*implements the same server contract (docs/playground-api.md):GET /api/version(now{ version, sdk, label, repo, install, server, style }— the page takes its SDK names, links and thecreate()code style from it),POST /api/parse//api/build(snake_casecreate()fields,measure_available) //api/diff,GET /api/catalog/info/<id>,POST /api/catalog/refresh,GET /api/db/info,POST /api/db/update,GET /api/db/table/<file>,GET /api/nfc/events(Server-Sent Events, chips already present replayed on connect),POST /api/nfc/read/burn/plan. The page talks to the readers through SSE + HTTP on both servers (TIGERTAG_PLAYGROUND_NO_NFC=1starts the server without readers;/api/parseandcard:detectednow carrypretty,describe,verify,raw_dict,dict,validate; burn results and/api/nfc/planreportsignature_dropped).scripts/check_playground_sync.pyfails when the two copies differ (the local checkout next to this one, else GitHubmain); the test suite runs it and skips it when neither is reachable. From the other SDK's page: Read / Burn modes with per-mode panels, the merged Offline · Catalogue / Online · API product loader, twin tag mode, SDK Input create() / HEX / Pages, the decluttered layout with info bubbles, bytes always built by the SDK on the server (POST /api/build). -
to_bytes()andpatch()refuse ameasure_availableabovemeasure(ValueError); reading such a chip still works andvalidate()still reports it. The playground's Generate refuses it too. -
Tag index / tag count (protocol v2.2, spec section 2.11). Page 0x0D byte 3 (payload offset +39), previously reserved, is now read and written as
tag_info: the high nibble is the tag index — which of the item's TigerTags this one is, from 1 (0 = unknown) — and the low nibble is the tag count — how many TigerTags the item carries (0 = unknown, 1 = single tag, 2 = twin tag, up to 15). A TigerTag identifies any item — a filament spool, a resin bottle…; what the item is comes fromid_type. The hex reads like "index/count":0x11single tag,0x12/0x22twin tag 1 of 2 / 2 of 2,0x02two tags with the index unknown,0x00unknown (tags written before v2.2).TigerTag.tag_infofield (u8) with read-onlytag_indexandtag_countproperties.TigerTag.create(..., tag_count=0, tag_index=0)packs both values intotag_info((tag_index << 4) | tag_count);as_init()writes0x00.tag.patch(tag_count=..., tag_index=...)as a shortcut fortag_info(either one alone keeps the other nibble).create()andpatch()raiseValueErrorfor a value outside 0–15.validate()warns when the tag index exceeds a known tag count, or whentag_infodoes not fit in one byte.to_raw_dict()gains"tag_info";to_dict()gains"tag_count"and"tag_index"(Nonewhen unknown);pretty()gains aTag 1 of 2line;describe()mentions the tag position when the count is known, naming the item fromid_type("Tag 1 of 2 on this filament.", "on this item" when unknown).
-
Same changes in the standalone
parse_tigertag.py. -
Playground (
tools/playground.html): "Tag index" / "Tag count" inputs, byte +39 written and read back (build,.binimport / export), a "Tag" row in the Traceability card and inpretty(),tag_infointo_raw_dict(),tag_count/tag_indexinto_dict(), and a "validate() · Python SDK" card listing the SDK's warnings. -
Playground server (
tools/server.py):POST /api/parse(payload hex → raw dict, tag index / count,validate()warnings) andPOST /api/build(TigerTag.create()keyword arguments → payload hex), andGET /api/version, so the playground's header badge shows the installed SDK version instead of a hard-coded "v1.0". -
Playground ecosystem panel: Tiger Scale V3 photo (was the previous-generation scale), new TigerSpool and TigerPOD Mini cards, and repository links to Tiger-Scale-V3, TigerSpool-RFID and TigerPOD, plus TigerSystem-Docs and TigerTag-SDK-JS.
-
Playground: an info bubble on "Tag index" and "Tag count" explains both values.
-
Playground: "SDK Input" panel — the exact
TigerTag.create()call, the HEX that Burn writes (144 bytes, pages 0x04–0x27) and the same bytes page by page. -
Playground: twin tag mode — with two or more readers holding a chip, Tag count is set to the number of readers, each reader gets its own payload (tag index 1…n, same data and Timestamp), shown as #1 / #2 in SDK Input and SDK Output, and Burn writes each chip with its own payload.
-
Playground: "Manufacturing date" is the Timestamp of the moment you click Generate (read-only; it is also the twin tag ID), instead of a hand-picked date.
-
Playground: NFC reader support (ACS ACR122U or any PC/SC reader), matching the JS playground. Header chips list connected readers ("No reader" plus the install command when reader support is missing); a chip placed on a reader is read (pages 0x04–0x27, 80-byte fallback), parsed by the SDK and loaded into the form; Burn writes the current payload to every reader with a chip after a confirmation (never pages 0x00–0x03 or the configuration pages 0x28+); Raw Read shows an annotated hex dump per reader. The server pushes events over Server-Sent Events (
GET /api/nfc/events, same messages as the JS WebSocket) and takesPOST /api/nfc/read/POST /api/nfc/burn. Reader access usespyscard, an optional extra:pip install "tigertag[nfc]". ACR122U APDU helpers live intools/nfc_reader.py, with unit tests that need neither pyscard nor hardware. -
Playground: Burn never writes a signature and never leaves a stale one. It always writes the full 144-byte image, pages 0x04–0x27 (36 pages,
pagesWritten: 36), with the signature pages 0x18–0x27 set to00whatever the payload — only a certified manufacturer can issue a valid signature (it covers the chip UID), so the playground only reads signatures to verify them. A signature carried by the loaded data (e.g. read from a signed chip) is not copied. Pages 0x00–0x03 and 0x28+ are never touched. The SDK Input HEX / Pages tabs and the burn confirmations show the 144-byte image;POST /api/nfc/planreturns the APDUs a burn would send (andsignatureDropped) without touching a reader. -
Playground: TigerTag favicon (
assets/favicon.svg, withassets/apple-touch-icon.png). -
TigerTag+ from the official catalogue.
TigerTag.from_catalog(product_id, *, catalog=None, uid=None, tag_count=0, tag_index=0, timestamp=None, db=None)builds a complete TigerTag+ from the product ID alone, using the entry'sRFID_Data(data1 diameter, data2/data3 nozzle, data4/data5 drying, data6/data7 bed;nullvalues, incl.id_aspect2, → 0; colours 2/3 fromcolor_r2…/color_r3…orcolor_info). New moduletigertag.catalog:load_catalog()(downloadsid_catalog.jsonfrom TigerTag-RFID-Guide once, about 12 MB, and caches it in the user cache directory; refreshed after 24 h; the cached copy is used offline),refresh_catalog()(forced update,ETag/Last-Modifiedso an unchanged file is not downloaded again),catalog_info()(count, fetched / checked time, source URL) andcatalog_entry()(title, brand, SKU, barcode, image). The catalogue is not bundled. Same API in the standaloneparse_tigertag.py. -
Playground: in the TigerTag+ tab, a Product ID field with Load from catalogue fills the whole form, shows the catalogue title, brand, SKU, barcode and photo, switches to Burn and builds the preview (twin tag with two readers); Update catalogue forces a download and the status line shows "Catalogue: N products · updated …" ("not downloaded yet" before the first load). Server:
GET /api/catalog/<id>,GET /api/catalog/info,POST /api/catalog/refresh. -
Reference data always available offline, kept fresh, refreshable, disableable. The package now bundles the TigerTag+ catalogue (
database/id_catalog.json.gz, about 1 MB) next to the 7 reference tables; both are refreshed daily by the sync workflow and before every release build (scripts/sync_bundled_data.py).TigerTagDBgainsdata_dir=,offline=,auto_update=,max_age=,update(force=False, catalog=False),info()andcatalog(); per table it uses your own folder exclusively whendb_pathis given, else the newest of the downloaded copy (user cache directory,TIGERTAG_DATA_DIR) and the bundled copy bylast_update.jsontimestamps.load_catalog()/TigerTag.from_catalog()share the data directory and the offline switch and fall back to the bundled catalogue. CLI:tigertag update [--force] [--catalog] [--data-dir PATH]and--offline/--data-dirfor parsing. Playground: "Update reference tables" with a status line, server endpointsGET /api/db/info,POST /api/db/update,GET /api/db/table/<file>(the page loads the tables in use from the server). -
Playground: in the TigerTag+ tab, one Product ID field with two sources — Offline · Catalogue (bundled / cached official catalogue, no internet) and Online · API (live data from api.tigertag.io, mapped onto the form by label) — and one Load button; the chosen source is remembered. Unknown IDs suggest the other source.
-
Playground: decluttered — on screen only short labels, values, buttons and status words ("Tables · up to date", "Catalogue · 14 164 · 1 oct."); every explanation (TigerTag+ and Init descriptions, data-source hints, catalogue and table details, tag index / count hint, Read / Burn banners, HEX / Pages captions, demo-preset notes) moved into info bubbles, kept inside the sidebar or the viewport.
-
Playground: Read mode shows only the SDK Output panel and Burn mode only the SDK Input panel (the other one is not rendered at all, not even its rail); switching mode opens the mode's panel.
- Behaviour change: the SDK now checks for new reference data once a day. The first
TigerTagDBof a process makes at most one small request per day (last_update, 5 s timeout, never raises) and downloads only the tables that changed. Turn it off withoffline=True,TIGERTAG_OFFLINE=1(zero network anywhere) orauto_update=False. Updates use the standard library:requestsis no longer needed.sync(),sync_databases(),tag.sync_db()andtigertag --sync-onlykeep working;sync_db()and--sync-onlynow update the data directory instead of the installed package. A customdb_pathno longer falls back silently to the bundled copy: missing tables raiseFileNotFoundError. - No emoji anywhere in the SDK's text output or the playground.
SignatureResultlabels are plain words (VALID,INVALID,NOT SIGNED,NO CRYPTO — …,NO PUBLIC KEY — …,NO UID — …);pretty(),describe()and the CLI use words ("signed", "Warning:", "Error:"); the playground uses inline SVG icons (read, burn, raw read, import / export, generate, check / warning / error, cloud, …) and plain words where text cannot hold an icon (confirmation dialogs, toasts' copy, clipboard). - Protocol version is now TigerTag Open Source v2.2. Fully backward compatible:
chips written before v2.2 carry
0x00at +39 and read as unknown. The byte is not covered by the ECDSA signature (UID + pages 4–5), so setting it never invalidates a signed chip.
raw_api()uses certifi's CA bundle when installed (it comes withtigertag[sync]), so the TigerTag+ API no longer fails withCERTIFICATE_VERIFY_FAILEDon the python.org macOS installer, which has no CA certificates until "Install Certificates.command" runs.
1.2.0requiredsetuptools>=77at build time in order to use PEP 639 license fields. setuptools 77 requires Python >= 3.9, so installing from source (pip install .,pip install git+...,--no-binary) failed on Python 3.8, which this package still supports. Installing the published wheel was unaffected.- The licence is now declared as
license = {text = "Apache-2.0"}with the matching trove classifier, which producesLicense: Apache-2.0in the metadata and builds on every supported Python.
No functional change. The licence is Apache-2.0, as in 1.2.0.
- License changed from GPLv3 to Apache-2.0. The TigerTag protocol specification is now published as an open standard: CC-BY-4.0 for the specification, CC0-1.0 for the reference database, Apache-2.0 for code, with an irrevocable, worldwide, royalty-free right to implement it in any product, open source or proprietary. Apache-2.0 carries an express patent grant. See https://github.com/TigerTag-Project/TigerTag-RFID-Guide/blob/main/LICENSING.md.
- Package metadata now declares
License-Expression: Apache-2.0(PEP 639) instead of embedding the full licence text into theLicensefield.
__version__was stuck at1.1.0while the distribution was published as1.1.1. Both now agree.
Versions published to PyPI up to and including
1.1.1remain under GPLv3. This change applies from1.2.0onward.
TigerTag.create()— build a new tag from scratch with all fieldsTigerTag.as_init(uid)— create a blank TigerTag Init chip ready for programmingTigerTag.erase()— return 80 zero bytes to wipe a chip back to blank NDEFtag.patch(**kwargs)— immutable surgical field update, signature-safe (protected fields: id_tigertag, id_product, uid, signature_r/s)tag.patch_from_api()— auto-apply cloud API values to chip fields; returns patched tag + applied diffstag.diff_api()— compare all chip fields vs TigerTag+ cloud API; covers nozzle, bed, drying, type, material, brand, diameter, aspects, colors, quantity, unittag.raw_api()— fetch live TigerTag+ cloud product data (requires requests)ApiDiffnamedtuple — (field, chip_value, api_value) — exported from main packageID_TIGERTAG,ID_TIGERTAG_PLUS,ID_TIGERTAG_INIT,MAKER_PRODUCT_ID,INIT_PRODUCT_ID— exported constants- Playground (
tools/playground.html) — interactive 3-column browser UI for parsing, previewing and diff-checking tags - Dev server (
tools/server.py) — serves playground and exposesPOST /api/diffREST endpoint backed by the Python SDK
from_pages(uid, 144_bytes)is correctly documented as verifiable — the previous README table incorrectly stated 144 bytes was "not verifiable"- Aspect "none" vs "none" is no longer reported as a diff in
diff_api()
TigerTag.from_pages(uid, payload)— primary constructor for NFC SDK integrationTigerTag.from_dump(data)— constructor for binary dumps (180B auto-extracts UID)TigerTag.from_file(path)— convenience constructor from .bin fileTigerTag.verify()— autonomous ECDSA-P256 signature verificationTigerTag.to_dict()— fully resolved dict (all IDs replaced by labels + metadata)TigerTag.pretty()— human-readable summaryTigerTag.validate()— field-level sanity checksTigerTagDB— loads bundled reference JSONs, auto-updates from API or GitHubsync_databases()— standalone database sync with API + GitHub fallback- CLI:
tigertag dump.binandpython -m tigertag dump.bin - Bundled reference databases (offline use, no network required on first run)
- Compatible with NTAG213, NTAG215, NTAG216 and ISO 14443 compatible chips
- Standalone
parse_tigertag.pyfor single-file copy-paste usage - Material identification support: filament, resin (extensible to any material type)