summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md16
-rw-r--r--README.md6
-rw-r--r--docs/DEVELOPER_GUIDE.md7
-rw-r--r--docs/TESTING.md11
4 files changed, 36 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e0acca..87b4b4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,22 @@ All notable changes to this project are documented here. The format follows
## [Unreleased]
+Milestone M6 in progress (released as 0.6.0 when all four parts are done).
+
+### Added
+
+- M6a, linear barcodes (`source/codes/`): EAN-13, EAN-8, UPC-A and UPC-E,
+ with check digits added, verified, fixed or off, and canonical UPC-E zero
+ suppression; Code 128 (sets B and C); Code 39 with an optional mod-43 check
+ character; ITF with an optional check digit; Codabar with start and stop
+ characters.
+- The code screen: black on white, the largest whole module size, human-
+ readable text, and a full-backlight request.
+- Reference rows from zxing-cpp (`tools/gen_code_vectors.py`, `make codes`).
+ Barcodes from the unit tests are decoded with zxing-cpp in every test run
+ (`tools/check_codes.py`), and the UI flow decodes a screenshot of the code
+ screen.
+
## [0.5.0] - 2026-10-20
Milestone M5: import. You can now add entries by pasting `otpauth://` links or
diff --git a/README.md b/README.md
index 5d3dff3..a9ec6af 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
A Garmin Connect IQ watch app that shows one-time passwords (TOTP, HOTP, and optional Steam Guard codes) and stores barcodes and QR codes (loyalty cards, tickets) to show at a scanner. Everything lives encrypted on the watch. There is no cloud, no backend and no analytics, and the app requests no permissions, so it cannot use the network.
-> **Status: M5 (import) done; M1 (settings spike) ready for your phone test.** The plan below was approved on 2026-10-18. M0 built the Docker toolchain and pipeline, M2 the crypto and OTP core, M3 the encrypted vault ([docs/SECURITY.md](docs/SECURITY.md)) and M4 the screens. M5 added import: `otpauth://` links and unencrypted Aegis JSON pasted into the new Import group of the phone settings, parsed on the watch with a streaming JSON tokenizer. It also detects duplicates, supports multi-part pastes and shows a results screen. It's tested with fixtures, a seeded fuzz corpus, and a 100-entry import through the settings path on seven simulated watches. Google Authenticator exports are deferred. Next is M6, barcodes. The settings spike (`make spike`, [docs/SETTINGS_SPIKE.md](docs/SETTINGS_SPIKE.md)) waits for your test. The Phase 0 research, with sources, is in [docs/FEASIBILITY.md](docs/FEASIBILITY.md); the daily workflow is in [docs/DEVELOPER_GUIDE.md](docs/DEVELOPER_GUIDE.md).
+> **Status: M6a (linear barcodes) done; M6b-M6d (QR, Data Matrix, PDF417, Aztec) next; M1 (settings spike) ready for your phone test.** The plan below was approved on 2026-10-18. M0 built the Docker toolchain and pipeline, M2 the crypto and OTP core, M3 the encrypted vault ([docs/SECURITY.md](docs/SECURITY.md)), M4 the screens and M5 import (`otpauth://` and Aegis JSON via the phone settings). M6a added the code screen and the EAN-13, EAN-8, UPC-A, UPC-E, Code 128, Code 39, ITF and Codabar encoders. They're checked against zxing-cpp reference rows and decoded at 1 to 3 px per module, and a screenshot of the code screen decodes on seven simulated watches. The settings spike (`make spike`, [docs/SETTINGS_SPIKE.md](docs/SETTINGS_SPIKE.md)) waits for your test. The Phase 0 research, with sources, is in [docs/FEASIBILITY.md](docs/FEASIBILITY.md); the daily workflow is in [docs/DEVELOPER_GUIDE.md](docs/DEVELOPER_GUIDE.md).
"OTP Wallet" is a working title.
@@ -567,7 +567,7 @@ Imported entries behave exactly like manual ones. You rename, regroup, move, loc
### 12.2 Rendering
- **Area**: 2D codes use the largest centred square (round screens) or rectangle. Linear codes and PDF417 use the widest horizontal band.
-- **Module size**: the largest whole number of pixels per module that fits, including the quiet zone (QR 4 modules, Data Matrix 1, PDF417 2, linear codes 10). The minimum is 2 px to start with. Scan tests in M6 may raise it, or allow 1 px for linear codes on 218 px screens.
+- **Module size**: the largest whole number of pixels per module that fits, including the quiet zone (QR 4 modules, Data Matrix 1, PDF417 2, linear codes 10). The minimum is 2 px for 2D codes. Linear codes may use 1 px: an EAN-13 needs that on a 218 px screen, and the Forerunner 255S screenshot decodes at 1 px. Your phone and real scanners still need to confirm it (manual checklist).
- **Colours**: always black modules on a white area that includes the quiet zone, whatever the theme.
- **Human-readable text**: digits under linear codes when enabled and there's room.
- **Brightness**: on AMOLED watches the app asks for full brightness while a code is shown, for up to about a minute. That's the burn-in protection limit; a tap boosts it again. On MIP watches the backlight is turned on.
@@ -587,7 +587,7 @@ Linear: EAN-13 needs 113 modules, which means 1 px per module on a 218 px screen
### 12.4 How each symbology is proven scannable
-1. **Reference comparison**: Python reference encoders in Docker (zxing-cpp's writer, segno for QR with forced version and mask, and others) generate expected bar patterns and matrices for fixed parameters. The watch encoder must match them exactly wherever the output is deterministic: all linear codes, QR with forced version and mask, and Data Matrix in ASCII mode.
+1. **Reference comparison**: Python reference encoders in Docker (zxing-cpp's writer, segno for QR with forced version and mask, and others) generate expected bar patterns and matrices for fixed parameters. The watch encoder must match them exactly wherever the output is deterministic: the EAN/UPC family, Code 39, ITF, Codabar, QR with forced version and mask, and Data Matrix in ASCII mode. Code 128 is the exception, because it has several equally short encodings. zxing-cpp 3 writes through zint, which breaks those ties differently from the zxing rules the watch uses. For Code 128 the module count must match (it did for 5,000 random strings), and the code must decode (check 2).
2. **Decoding**: the simulator tests dump every encoder output. It's rendered at each device's module size and decoded with zxing-cpp, which reads all 12 symbologies. The decoded text must equal the input, for hundreds of seeded random payloads per symbology.
3. **Screenshots**: simulator screenshots of the code screen on Forerunner 255S, Venu Sq 2 and Forerunner 965 must decode with zxing-cpp.
4. **Real world** (manual checklist): your phone's camera, plus any real shop scanner you can try, indoors and outdoors, on MIP and AMOLED.
diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md
index d03a8fe..14c4e05 100644
--- a/docs/DEVELOPER_GUIDE.md
+++ b/docs/DEVELOPER_GUIDE.md
@@ -127,4 +127,9 @@ Debug builds print `DIAG first_frame startup_ms=... mem_used=... mem_total=...`
- **Import inputs**: new parser cases go into `test/import/` (fixtures are built in `ImportSupport.mc`, all fake). To widen the fuzz corpus, change the seeds or mutations in `tools/gen_fuzz_corpus.py` and run `make corpus`. The Python tests check that the committed corpus matches the generator.
- **A settings form** (M7 adds the rest): declare its properties in `resources/settings/properties.xml` (reading an undeclared one crashes), add the form to `resources/settings/settings.xml` with its strings, and handle it in `source/settings/SettingsProcessor.mc`. The form is applied only when its switch is on. The processor clears the fields afterwards and remembers the form's hash.
- **On-watch numbers**: sideload a debug build (`make build DEVICE=fr70`, copy `build/debug/fr70.prg` to the watch's `GARMIN/APPS` folder over USB). Then open the menu and choose "Debug: benchmark".
-- **A barcode symbology** (from M6): one encoder module under `source/codes/`, its validator, unit tests with reference patterns, and the checks in [README 12.4](../README.md#124-how-each-symbology-is-proven-scannable).
+- **A barcode symbology**: add an encoder module under `source/codes/` and dispatch to it in `Codes.encode`. Then:
+ - Add reference rows in `tools/gen_code_vectors.py` (zxing-cpp's writer, or segno for QR) and run `make codes`.
+ - Add `CODEDUMP` lines for seeded payloads in its test, so `tools/check_codes.py` decodes them in every test run.
+ - Add its zxing-cpp format to `tools/barcode_ref.py`.
+
+ When decoding with zxing-cpp, restrict the format: by default it reads Code 39 as full-ASCII, which changes pairs such as "+S".
diff --git a/docs/TESTING.md b/docs/TESTING.md
index 593b120..29cec49 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -26,6 +26,16 @@ Test devices (`TEST_DEVICES`): Forerunner 255S (weakest: 218 px round MIP, butto
## Results
+### M6a, 2026-10-20 (linear barcodes, in 0.6.0)
+
+Monkey C unit tests: 105 per device, with the checks of README 12.4.
+
+1. **Reference comparison.** 217 rows from zxing-cpp (`tools/gen_code_vectors.py`, `make codes`): fixed edge cases and seeded random payloads for every linear symbology. EAN-13, EAN-8, UPC-A, UPC-E, Code 39 (with and without the check character), ITF and Codabar match module for module. Code 128 matches in length. zxing-cpp 3's zint backend picks a different one of the equally short encodings; on 5,000 random strings the lengths were always equal.
+2. **Decoding.** Every Code 128 reference row, plus 139 codes for seeded random payloads in all eight symbologies, is dumped by the test run. `tools/check_codes.py` renders each with its quiet zone at 1, 2 and 3 px per module and decodes it with zxing-cpp, on each test device: 169 codes, all decoded.
+3. **Screenshots.** The UI flow opens the fixture's EAN-13 card. Its screenshot decodes to `5901234123457` on all seven watches, including the Forerunner 255S at 1 px per module.
+
+Validation messages are tested for every symbology: digits only, lengths, check digits (add, verify, fix, off), UPC-E number system and canonical zero suppression, ITF parity, Code 39 characters, non-ASCII Code 128 input, and Codabar start and stop characters.
+
### M5, 2026-10-20 (version 0.5.0)
Monkey C unit tests: 101 per device (29 new in `test/import/`):
@@ -136,3 +146,4 @@ Things the simulator can't verify, done by you on the Forerunner 70. The checkli
- [ ] M4: with "Debug: demo entries", check the list, a code, the countdown ring, HOTP "next", and the PIN keypad with your finger. Also check a wrong PIN (vibration) and the 30 s wait after five wrong PINs.
- [ ] M4: check that leaving the app and coming back asks for the PIN again for the locked demo entry. Check that the glance in the glance loop shows the number of entries.
- [ ] M5: in Garmin Connect, paste a few `otpauth://` links of test accounts (not your real ones) into the Import fields, turn "Import now" on and save. Check the result on the watch and in the Result field. Also check that the text fields are emptied afterwards: this answers part of the M1 question.
+- [ ] M6a: with "Debug: demo entries", open "Gym card" (an EAN-13). Scan it with your phone's camera, or a shop scanner if you can, indoors and outdoors. Tell me whether it reads.