Add a QR code API - #79
Conversation
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
|
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."
Selection needs the encoder's mode, since version 1 capacity differs sharply between them:
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, All 8 buildable targets still build clean, and the link test still passes for atari, apple2, c64 and coco. |
Adds
fuji_qrcode_input/encode/length/output, mirroring the existing base64 INPUT/COMPUTE/LENGTH/OUTPUT shape, plus afuji_qrcode_v1one-shot that encodes a string as a version 1 symbol and returns its module matrix.The FujiNet firmware has had these commands (
0xBC–0xBF) 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_v1asks for version 1 explicitly rather than letting the FujiNet choose. Withversion = 0the 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_v1checks the length locally, so an over-long string fails immediately instead of after three round trips.API notes
versionandeccareuint8_t, not the enum. Enum size is compiler-dependent, and usingqr_ecc_tin the signature would change the ABI between cc65, cmoc and Watcom. This followsfuji_hash_compute, which takes auint8_twhilehash_alg_texists for readability.shortencannot be honoured on Atari or MS-DOS. Their command frames carry onlyaux1/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, nottrue. 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 returningtruewould 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
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 00for encode,BE 40 04 00 00 00for length) and thattmp5/tmp6are used to carry arguments across_copy_fuji_cmd_data, which documents that it trashestmp9/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 allreturn true;stubs — so there was no working local precedent to copy.I did not add a
soft65c02_unittest: 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