index / work / bubba / nfc writer

The NFC writer

The bench tool that gives every Bubba totem its identity. It writes the tag, reads it straight back off the chip, compares it byte for byte against what was sent, and only then records the unit.

Internal, single-purpose, not shipped to anyone. It has provisioned twenty-two tags for the test cohort, so everything below has run end to end on tags that are now in other people's hands. This page exists because it is the point where the manufacturing and the software of a physical product actually meet.

rolesole designer and engineer
platformmacOS desktop, Python, Tkinter
surface745 lines, 2 files1
readerACR122U over PC/SC
tagsNTAG / Ultralight
registryGoogle Sheet via a service account
provisioned22 tags, test cohort
Eight states of the NFC writer, labelled: ready, filing, written, auto engaged, corrupted, dispossessed, held offline, reader absent
Eight states the tool can be in, and four of them are failures. Half an interface spent on what happens when something goes wrong is the right proportion for a bench tool, where the wrong outcomes are the expensive ones.

Where it sits

the bridge between the object and the system

Every Bubba totem carries a URL of its own, burned into the tag at production time: tapbubba.com/totem/BUBBA-0001, and the next one, and the one after that. The mapping between the object in someone's hand and the record in the system has to stay trustworthy for as long as the object exists, which is longer than any of the software will last in its current form.

This tool creates that mapping, and it is the only place a totem's identity is ever assigned. The identity is deliberately ahead of its uses. Tap a totem today and the URL reaches the same page whichever unit you tapped, telling you to open the app; the serial that distinguishes it is in the registry, and on the chip, waiting for a web side that reads it. Putting it there at production time costs nothing. Retrofitting it onto tags already sitting on other people's desks would cost everything.

What it writes

three NDEF records, at the card protocol

rectypevalue on tag
1URIhttps://tapbubba.com/totem/BUBBA-0001
2Textprogrammed:20260602
3Textversion:1

Rather than depending on a high-level NFC library, the tool speaks PC/SC directly to the reader and issues raw APDUs against tag pages. Page 03 carries the capability container, E1 10 12 00, where E1 is the NDEF magic byte, written if absent. Page 04 carries the TLV header and message length, then the encoded records. From there the payload is written four bytes at a time, terminated with FE and padded to a page boundary.

That was a considered trade. Dropping to the card protocol removed a fragile dependency layer and made tag-format failures diagnosable instead of opaque. A tag that comes back wrong now reports which page failed and what the card answered, rather than raising somewhere inside a library.

Serials come from the registry, never from the machine

one counter, and it is not local

The next serial is not held in a local counter. On each launch it is derived by scanning the shared registry for the highest existing BUBBA-#### and incrementing. Two operators, on two machines, with two readers, cannot silently drift into duplicate identities.

The registry is a Google Sheet reached through a service account, which was chosen rather than settled for. At this stage the fleet record needs to be readable and editable by people who are not engineers, exportable, and easy to audit, and a sheet does all of that on day one. The tool treats it as an append-only log, so it can be swapped for a real datastore later without touching the tag-writing core.

The registry row holds more than the tag does. Batch and unit name are recorded against the serial but never written to the chip, along with the tag's own hardware serial, which is the one identifier on the object that this tool did not choose. Anything that might need to change later stays off the tag, because the tag is the one part of the system that cannot be redeployed.

Four failure modes designed out

not documented around

A finished totem cannot be silently reassigned. Before any write, the existing NDEF is decoded. A tag already carrying a tapbubba.com URL is rejected, and the tool reports which unit it already is, so a live totem in someone's hand is never orphaned by a stray tap on the reader.

The tag is proven correct, not assumed. Every write is read straight back off the tag and compared byte for byte against what was sent. The result appears in the window as verified or failed before the operator moves on, so a bad unit is caught at the bench rather than in a box.

No phantom rows. The record is written only after the tag write succeeds. A failed or aborted write leaves nothing behind, so the registry can never claim a unit that does not physically exist. The registry is the fleet, and a fleet with imaginary members in it is worse than no fleet record at all.

A production run does not stop because the wi-fi did. If the registry append fails or times out, the row is persisted to a local queue and the operator is warned in the window. The queue flushes on the next successful connection, and a partial flush re-queues what did not land rather than dropping it.

The writer showing a corrupted tag: a red CORRUPTED status and the line "The totem is speaking. I cannot understand it."
An unreadable serial, refused. The tool reports it in the product's own voice rather than surfacing an exception, which cost nothing and means the bench tool and the object it makes sound like the same thing. The accent colour is spent once, here, on the same rule the app follows.

Built for a run, not for a demo

the bench, not the desk

Writing is a continuous loop, paced by the tag being physically lifted off the reader rather than by a timer, so the operator sets the speed and never clicks between units. All NFC and network work runs off the UI thread, so the window stays responsive mid-write and a run can be stopped cleanly at any point.

The interface is Tkinter from the standard library, themed to the Bubba palette. Nothing to install, nothing to keep updated, nothing to break on a bench machine that gets touched twice a year. The core module runs standalone as a command line tool and the same verified write path runs headless, so the application layer adds ergonomics and status but never behaviour. That is why the four guarantees above hold whichever way it is run.

Honest limits

known gaps, stated plainly

Sources

  1. Counted from the repository at one commit on 4 September 2026: 745 lines across 2 files. The tool only, not the registry sheet, the packaged application or the README.