Skip to content

Add a QR code API - #79

Open
tschak909 wants to merge 2 commits into
mainfrom
qrcode-api
Open

Add a QR code API#79
tschak909 wants to merge 2 commits into
mainfrom
qrcode-api

Conversation

@tschak909

Copy link
Copy Markdown
Contributor

Adds fuji_qrcode_input / encode / length / output, mirroring the existing base64 INPUT/COMPUTE/LENGTH/OUTPUT shape, plus a fuji_qrcode_v1 one-shot that encodes a string as a version 1 symbol and returns its module matrix.

The FujiNet firmware has had these commands (0xBC0xBF) for a while, but the library had no entry points for them at all.

Why version 1 specifically

Version 1 is 21×21 modules — the largest symbol some 8-bit displays can render. On the Intellivision it is the only size that fits the STIC colored-squares grid at one module per square; version 2 is 25×25 and does not fit at any placement.

So fuji_qrcode_v1 asks for version 1 explicitly rather than letting the FujiNet choose. With version = 0 the firmware also overrides the caller's error correction level, which makes the output size unpredictable.

At ECC LOW a version 1 symbol holds 25 alphanumeric characters (0-9 A-Z space $%*+-./:) or 17 bytes if anything falls outside that set — so an all-uppercase URL encodes far more compactly than a mixed-case one. fuji_qrcode_v1 checks the length locally, so an over-long string fails immediately instead of after three round trips.

API notes

version and ecc are uint8_t, not the enum. Enum size is compiler-dependent, and using qr_ecc_t in the signature would change the ABI between cc65, cmoc and Watcom. This follows fuji_hash_compute, which takes a uint8_t while hash_alg_t exists for readability.

shorten cannot be honoured on Atari or MS-DOS. Their command frames carry only aux1/aux2, so there is no third parameter byte and the FujiNet reads it as zero. Every other bus reads parameters off the wire and supports it. This is documented in the header, since it is otherwise a silent no-op. (Worth knowing: the shortened URL points at the FujiNet's own LAN address, so it is only reachable from the same network.)

adam returns false, not true. The QR commands need parameters ahead of their payload, and the AdamNet port has no existing example of encoding those — its base64/hash functions send a bare command byte followed by data. A stub returning true would leave callers rendering whatever happened to be in their buffer as a QR code, so failing honestly seemed better than guessing at the wire format. Happy to implement it properly if someone can point me at the right pattern.

Verification

Target Result
atari, apple2, apple2enh, c64, plus4, vic20, coco, dragon build clean, no new warnings
atari, apple2, c64, coco a test program calling every entry point links against the built archive
msdos, pmd85, adam no toolchain available here — syntax checked with gcc under their platform macros only

The link test is the useful one: it exercises the header, the cc65/cmoc calling convention and the exported symbols together, which is where an ABI mistake would actually show up.

For the Atari assembly I also checked the generated listing to confirm the DCB tables emit as intended (BD 00 00 00 00 00 for encode, BE 40 04 00 00 00 for length) and that tmp5/tmp6 are used to carry arguments across _copy_fuji_cmd_data, which documents that it trashes tmp9/tmp10.

Nothing has been run against real hardware. The Apple II path in particular follows fuji_read_directory's control-then-status pattern rather than its base64/hash neighbours, which are all return true; stubs — so there was no working local precedent to copy.

I did not add a soft65c02_unit test: the runner is not installed here, so I could not check that such a test even parses, let alone that it asserts the right thing. Worth adding for the Atari DCB tables by someone who can run it.

Dependency

Needs the firmware QR capacity fix (FujiNetWIFI/fujinet-firmware#1579) for inputs over 17 characters — earlier firmware rejects them regardless of mode, because it compares a raw character count against byte-mode codeword capacity.

🤖 Generated with Claude Code

https://claude.ai/code/session_012b4pARGeBH9MMkhipCob55

tschak909 and others added 2 commits August 27, 2026 15:13
Adds fuji_qrcode_input/encode/length/output, mirroring the existing base64
INPUT/COMPUTE/LENGTH/OUTPUT shape, plus a fuji_qrcode_v1 one-shot that
encodes a string as a version 1 symbol and returns its module matrix.

Version 1 is 21x21 modules, the largest symbol some 8-bit displays can
render, so fuji_qrcode_v1 asks for it explicitly rather than letting the
FujiNet choose: with version 0 the firmware also overrides the error
correction level, and a version 2 symbol is 25x25 and will not fit where
21x21 does. At ECC LOW that holds 25 alphanumeric characters, or 17 bytes
if anything falls outside 0-9 A-Z space $%*+-./: -- so an all-uppercase
url encodes far more compactly than a mixed-case one. The limit is
checked locally so an over-long string fails without three round trips.

version and ecc are declared uint8_t rather than the qr_ecc_t enum, since
enum size is compiler dependent and would change the ABI between cc65,
cmoc and Watcom. This follows fuji_hash_compute, which takes a uint8_t
while hash_alg_t exists for readability.

`shorten` is accepted for interface uniformity but cannot be honoured on
Atari or MS-DOS: their command frames carry only aux1 and aux2, so there
is no third parameter byte, and the FujiNet reads it as zero there. The
other buses read parameters off the wire and support it. Noted in the
header, since it is otherwise a silent no-op.

adam returns false rather than a true-returning stub. The QR commands
need parameters ahead of their payload and the AdamNet port has no
existing example of encoding those; a stub returning true would have
callers render whatever was in their buffer as a QR code.

Verified: atari, apple2, apple2enh, c64, plus4, vic20, coco and dragon
all build clean with no new warnings, and a test program calling every
entry point links against the atari, apple2, c64 and coco archives, which
exercises the header, the calling convention and the exported symbols
together. msdos, pmd85 and adam have no toolchain available here and were
syntax checked with gcc under their platform macros only. Nothing has
been run against real hardware.

Requires the firmware QR capacity fix (fujinet-firmware #1579) for inputs
over 17 characters; earlier firmware rejects them regardless of mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012b4pARGeBH9MMkhipCob55
Review of the firmware side (fujinet-firmware#1579) surfaced field
experience that low error correction was causing codes that would not
scan until the level was raised by hand. fuji_qrcode_v1 was hardcoding
ECC LOW, so it had exactly that problem.

Within version 1 the payload rarely fills the symbol, and the spare
capacity would otherwise go to padding bytes. Spend it on damage
tolerance instead: pick the highest level the string still fits.

Selection needs the encoder's mode, since capacity differs sharply
between them. Digits are also members of the alphanumeric set, so the
all-digit case has to be tested first or a numeric payload is measured
against the smaller alphanumeric table and given a weaker level than it
could carry -- an exhaustive check against libqrencode caught exactly
that, 15 lengths choosing one level below optimal before the mode
detection was ordered correctly.

The local length check is now mode-aware too, so an over-long string
fails immediately with the right limit rather than after three round
trips: 41 characters all-numeric, 25 alphanumeric, 17 otherwise.

Verified against libqrencode over 108 selections spanning all three
modes and both boundaries: every chosen level encodes at version 1, and
in no case would the next level up also have fitted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012b4pARGeBH9MMkhipCob55
@tschak909

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up driven by @idolpx's review of the firmware side (FujiNetWIFI/fujinet-firmware#1579): "Some codes wouldn't scan until I bumped up the ECC."

fuji_qrcode_v1 was hardcoding QR_ECC_LOW, so it had exactly that problem. It now picks the strongest error correction the payload leaves room for within version 1. The spare capacity would otherwise go to padding bytes, so this is free damage tolerance.

Selection needs the encoder's mode, since version 1 capacity differs sharply between them:

mode LOW MEDIUM QUARTILE HIGH
numeric 41 34 27 17
alphanumeric 25 20 16 10
byte 17 14 11 7

An exhaustive check against libqrencode caught a bug in my first attempt: digits are also members of the alphanumeric set, so an all-digit payload was being measured against the alphanumeric table and handed one level below optimal — 15 lengths affected. Numeric now has to be ruled out first.

Re-verified over 108 selections spanning all three modes and both boundaries: every chosen level encodes at version 1, and in no case would the next level up also have fitted.

The local length check is mode-aware too now, so an over-long string fails immediately with the right limit rather than after three round trips.

One thing worth flagging for the lobby use case: at 23 characters, HTTP://Q.TNFS.IO/6EWL3G lands on ECC LOW — alphanumeric MEDIUM tops out at 20. Getting MEDIUM would mean a 3-character room code, which is too few codes to be worth it. So for that particular payload the quiet zone and module size on the display are doing the heavy lifting, not the error correction.

All 8 buildable targets still build clean, and the link test still passes for atari, apple2, c64 and coco.

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.

1 participant