Skip to content

NFC scan: keep scanning after a rejected tag, parse NDEF properly, explain unsupported tags - #56

Merged
i12bp8 merged 1 commit into
i12bp8:mainfrom
Hoggormino:nfc-scan-vusion-fixes
Sep 17, 2026
Merged

i12bp8 merged 1 commit into
i12bp8:mainfrom
Hoggormino:nfc-scan-vusion-fixes

Conversation

@Hoggormino

@Hoggormino Hoggormino commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes the "+ Scan NFC" flow so it keeps scanning after a rejected tag, parses NDEF the way NTAG21x tags store it, and tells the user what the scan found. It does not add support for radio shelf labels. TagTinker only transmits infrared, and the scan only reads NFC data, so the messages describe the NFC data rather than stating a vendor or display technology as fact.

Touches nfc/tagtinker_nfc.c, nfc/tagtinker_nfc.h, scenes/tagtinker_scene_nfc_scan.c and README.md.

What was wrong on main

  1. Scanning stopped after the first read. The "Not an ESL tag" popup armed a 2 s timeout with a NULL callback, so it never cleared and only Back left it.
  2. The NDEF parser required the NDEF TLV at page 4 byte 0, then copied the URI from its prefix-code byte to the end of a fixed 28-byte window from page 4. Factory-formatted NTAG21x tags put a lock-control TLV first, and URLs longer than about 21 characters lost their id.
  3. "Decode Error / Tag read but barcode invalid" could only mean that the 16-slot target list was full.
  4. decode_tag() printed two 30-bit values into a 20-byte buffer, so a 10-digit value was truncated instead of rejected.
  5. The custom event ids 1 and 2 could be taken by the target menu as saved-target selections if they arrived just after Back.

What this does

  • Keeps polling after a rejected tag. Readable tags are de-duplicated by UID and result together, so a tag resting on the reader is announced once, while a read that stops partway (which the firmware reports as success with fewer pages) does not block a later full read. An unreadable chip carries no UID, so it is announced once until the field is empty.
  • Walks the TLV area (NULL, lock-control and memory-control TLVs skipped; 1- and 3-byte lengths). Parses the NDEF message's first record when it is a short well-known URI record, and copies the body from after the prefix code, bounded by the record length, the buffer and the pages actually read.
  • Moves the decision into tagtinker_nfc_classify() so it can be tested off-device.
  • Rejection messages stay 2.5 s, then the prompt returns. "Target list full" stays until Back. One Back leaves the scene: the prompt returns from the scene tick, and the popup has no callback because one would consume short presses, including Back.
Message When
Likely radio tag / Link: nfc.imagotag.com / TagTinker is IR-only No decodable id, and the URI is an http:// or https:// link whose host is nfc.imagotag.com
Unrecognized tag / No ID TagTinker can decode Pages were read, but the first NDEF record is not a URI, or its URI has no decodable id. Everyday NFC cards land here too
Unreadable chip / Chip answered but no data was read The poller returned success with zero pages: an NFC-A chip that is not NTAG/Ultralight, or a tag that moved
Target list full / Delete a target and scan again An id decoded, but all 16 slots are used. Scanning and the scan LED stop
  • The scanner is stopped on the Back event rather than in on_exit, so the poll loop is already winding down when the scene is popped and the join there is as short as possible.
  • Custom event ids are now 200+, clear of the target menu's saved-target indices and its 99/100 add items.
  • README: a "Which tags work" section with sourced radio-ESL examples and a table of what each scan message means.

Why "Likely": the public VUSION dump in #51 carries an https://nfc.imagotag.com/<id> URL, and SES-imagotag's access-point datasheet documents a proprietary 2.4 GHz label radio. The NFC link alone does not prove the model or its radio.

Verification

  • Builds with zero warnings against release SDK 1.4.3 (API 87.1) and dev SDK 7f0b6e1c (API 88.2), on its own and merged together with Add CI build workflow and root Makefile #54.
  • Host tests (gcc with AddressSanitizer and UndefinedBehaviorSanitizer) against this branch's nfc/tagtinker_nfc.c: 38 synthetic checks covering host matching (case, port, query, fragment, inline scheme, look-alike hosts), URI prefix codes, TLV skipping, truncated and malformed records, over-long URIs and a synthetic decodable id, plus the public Add support for VUSION 2.2" (EDG2-0220-A) — 212×104 b/w #51 dump, which classifies as a likely radio tag.
  • A host simulation of the scan thread's de-duplication confirms that a partial read followed by a full read of the same tag still decodes, and that an unreadable chip is announced once.
  • On-screen text widths checked against the firmware's u8g2 fonts. The widest body line is 95 px of 128.
  • Hardware, this revision, on a Flipper Zero (Momentum mntm-012, API 87.1):
    • A blank NTAG215 written with https://nfc.imagotag.com/00000000 shows "Likely radio tag", the message clears by itself and the prompt returns.
    • An NTAG written with https://example.com/test shows "Unrecognized tag".
    • A contactless bank card reads and also shows "Unrecognized tag", and scanning continues. Not every non-NTAG chip ends up on the zero-page "Unreadable chip" path, so the README describes both outcomes.
    • With nothing on the reader the prompt stays and the scan LED blinks, and Back stops it.
    • Leaving the scene with a single Back while a rejection message is on screen: measured by driving the buttons over the CLI with a build that shows the message without a tag. From the scan scene it took exactly three Back presses to close the app, and the menu stack is three deep (main menu, Targeted Payloads, scan), so each press pops exactly one scene and the message does not swallow one.

Try it without a shelf label

Write https://nfc.imagotag.com/00000000 as an NDEF URL onto a blank NTAG213/215/216 you own, then scan it in Targeted Payloads, + Scan NFC. Expect "Likely radio tag".

🤖 Generated with Claude Code

@Hoggormino
Hoggormino force-pushed the nfc-scan-vusion-fixes branch from 246038d to ecfb0b5 Compare September 16, 2026 15:53
@Hoggormino Hoggormino changed the title NFC scan: keep scanning after a rejected tag, parse NDEF properly, name VUSION tags NFC scan: keep scanning after a rejected tag, parse NDEF properly, explain unsupported tags Sep 16, 2026
@Hoggormino
Hoggormino force-pushed the nfc-scan-vusion-fixes branch from ecfb0b5 to b4097c7 Compare September 16, 2026 18:41
The scan only sees NFC data. It cannot sense whether a label's display
listens for infrared or radio, so the messages now say what was found
instead of naming a vendor or a transport as fact.

- "VUSION tag / SES-imagotag uses radio, not IR" becomes "Likely radio
  tag / Link: nfc.imagotag.com / TagTinker is IR-only".
- Match an http:// or https:// link whose host is nfc.imagotag.com
  (case-insensitive), as prefix code 0x03/0x04 or inline after 0x00,
  instead of the substring "imagotag" anywhere in the URL. A "://"
  inside a path, query or fragment is not taken as the scheme.
- "Not a Pricer tag" becomes "Unrecognized tag / No ID TagTinker can
  decode"; "Unsupported chip" becomes "Unreadable chip / Chip answered
  but no data was read".
- Move the decision into tagtinker_nfc_classify() so it can be tested
  off-device.
- De-duplicate on UID and result together. The firmware reports a read
  that stops partway as a success with fewer pages, so a UID-only check
  could keep a tag stuck on "Unrecognized tag" after a partial first
  read.
- Announce an unreadable chip once until the field is empty. Those reads
  carry no UID, so UID de-duplication never applied and the message
  repeated on every poll.
- Bring the prompt back from the scene tick instead of a popup callback.
  A popup with a callback consumes every short press, so Back only
  dismissed the message and a second Back was needed to leave.
- Stop the scanner on the Back event rather than waiting for on_exit, so
  the poll loop is already winding down when the scene is popped and the
  join there is as short as possible. On device, leaving from a message
  takes exactly one Back.
- Move custom event ids to 200+. NfcScanEventSuccess was 100, the same
  id as the target menu's "+ Type Barcode" item.
- Stop the scan LED when "Target list full" ends scanning.
- README: add "Which tags work", with vendor-documented radio examples
  and a table of what each scan message means.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants