Skip to content

v1.3.0 — protocol v2.2, catalogue, offline reference data, unified playground

Choose a tag to compare

@TigerTag-Project TigerTag-Project released this 01 Oct 05:56
· 53 commits to main since this release
2ebbf72

Added

  • Unified playground: tools/playground.html is now one page, byte-for-byte identical in the
    JavaScript and Python SDKs. Each tools/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 the create() code style from it),
    POST /api/parse / /api/build (snake_case create() 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=1 starts the server without readers; /api/parse and card:detected now carry pretty, describe, verify, raw_dict, dict, validate; burn results and /api/nfc/plan report signature_dropped).
    scripts/check_playground_sync.py fails when the two copies differ (the local checkout next to
    this one, else GitHub main); 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() and patch() refuse a measure_available above measure (ValueError);
    reading such a chip still works and validate() 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 from id_type. The hex reads like
    "index/count": 0x11 single tag, 0x12 / 0x22 twin tag 1 of 2 / 2 of 2,
    0x02 two tags with the index unknown, 0x00 unknown (tags written before v2.2).

    • TigerTag.tag_info field (u8) with read-only tag_index and tag_count properties.
    • TigerTag.create(..., tag_count=0, tag_index=0) packs both values into tag_info
      ((tag_index << 4) | tag_count); as_init() writes 0x00.
    • tag.patch(tag_count=..., tag_index=...) as a shortcut for tag_info (either one
      alone keeps the other nibble). create() and patch() raise ValueError for a value
      outside 0–15.
    • validate() warns when the tag index exceeds a known tag count, or when
      tag_info does not fit in one byte.
    • to_raw_dict() gains "tag_info"; to_dict() gains "tag_count" and
      "tag_index" (None when unknown); pretty() gains a Tag 1 of 2 line;
      describe() mentions the tag position when the count is known, naming the item
      from id_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, .bin import / export), a "Tag" row in the
    Traceability card and in pretty(), tag_info in to_raw_dict(), tag_count /
    tag_index in to_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) and POST /api/build (TigerTag.create()
    keyword arguments → payload hex), and GET /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 takes POST /api/nfc/read / POST /api/nfc/burn. Reader access
    uses pyscard, an optional extra: pip install "tigertag[nfc]". ACR122U APDU helpers
    live in tools/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 to 00 whatever 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/plan returns the APDUs a burn would send (and signatureDropped)
    without touching a reader.

  • Playground: TigerTag favicon (assets/favicon.svg, with assets/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's RFID_Data (data1 diameter, data2/data3 nozzle,
    data4/data5 drying, data6/data7 bed; null values, incl. id_aspect2, → 0; colours 2/3 from
    color_r2…/color_r3… or color_info). New module tigertag.catalog: load_catalog()
    (downloads id_catalog.json from 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-Modified so an unchanged file is not
    downloaded again), catalog_info() (count, fetched / checked time, source URL) and
    catalog_entry() (title, brand, SKU, barcode, image). The catalogue is not bundled.
    Same API in the standalone parse_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). TigerTagDB gains
    data_dir=, offline=, auto_update=, max_age=, update(force=False, catalog=False),
    info() and catalog(); per table it uses your own folder exclusively when db_path is
    given, else the newest of the downloaded copy (user cache directory, TIGERTAG_DATA_DIR)
    and the bundled copy by last_update.json timestamps. 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-dir for parsing. Playground: "Update reference tables" with a status
    line, server endpoints GET /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.

Changed

  • Behaviour change: the SDK now checks for new reference data once a day. The first
    TigerTagDB of 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 with
    offline=True, TIGERTAG_OFFLINE=1 (zero network anywhere) or auto_update=False.
    Updates use the standard library: requests is no longer needed. sync(),
    sync_databases(), tag.sync_db() and tigertag --sync-only keep working; sync_db()
    and --sync-only now update the data directory instead of the installed package. A custom
    db_path no longer falls back silently to the bundled copy: missing tables raise
    FileNotFoundError.
  • No emoji anywhere in the SDK's text output or the playground. SignatureResult
    labels 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 0x00 at +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.

Fixed

  • raw_api() uses certifi's CA bundle when installed (it comes with tigertag[sync]),
    so the TigerTag+ API no longer fails with CERTIFICATE_VERIFY_FAILED on the python.org
    macOS installer, which has no CA certificates until "Install Certificates.command" runs.