diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-21 03:55:01 +0200 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-21 03:55:01 +0200 |
| commit | 88f1cac4fa530fc30fd6ef01970af11c7a959b8e (patch) | |
| tree | 4e2e52b36a6faa2dd50d3b6b07fbfe46fb5dc48c /tools | |
| parent | a69e23fb5266dd942d125239e7dcbe8a7c8ab956 (diff) | |
fix(codes): bound every 2D encoding step under the watchdog; draw large codes from runs
M6's step sizes were estimates, only exercised with small codes. With the
largest inputs the app accepts, every 2D encoder tripped the watchdog on
the Forerunner 255S (and the biggest steps were far over the 768 KB
watches' limit), so a large code added from the phone would have stopped
the app during settings processing at every start.
- QR: resumable segmentation, Reed-Solomon, interleaving, function
patterns and final mask; mask scoring on rows and columns packed 16
modules per word (same penalties, checked against the per-module rules).
- Reed-Solomon: resumable field tables and generator, encoder shifting
with native slice.
- Data Matrix, Aztec, PDF417: resumable encodation, stuffing, compaction,
placement and build; Data Matrix's grid as a byte array (90 KB -> 35 KB);
PDF417 refuses text that can't fit the screen before compacting.
- Cache format 2 with 16-bit sizes (wide PDF417 was cached wrong); packing
in steps; codes drawn as runs, and from a bitmap painted in steps when a
frame can't draw all runs.
- The job runner does two steps per tick on 768 KB watches;
gen_products --check verifies each product's watchdogCount fits that.
- CodeView renews full brightness (AMOLED) or the backlight (MIP) for up to
a minute from its single ticker; a tap restarts the minute.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/gen_products.py | 23 | ||||
| -rw-r--r-- | tools/tests/test_gen_products.py | 23 |
2 files changed, 43 insertions, 3 deletions
diff --git a/tools/gen_products.py b/tools/gen_products.py index 7504287..ff9cf67 100644 --- a/tools/gen_products.py +++ b/tools/gen_products.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # Author: cirakg & claude ai -# Summary: Check that every product in manifest.xml is a watch with API 5.0+ and at least 512 KB for watch apps. +# Summary: Check each manifest product: a watch with API 5.0+, 512 KB or more, and the watchdog the job runner assumes. """Check the manifest's product list against the downloaded device files. The target rule (README section 14, decision D4): a product belongs in @@ -9,6 +9,12 @@ variant supports Connect IQ API 5.0.0 or newer, and watch apps get at least 512 KB of memory. The data comes from each device's compiler.json, which `make sdk` downloads for exactly the products in the manifest. +The app's job runner (source/util/Jobs.mc) also assumes a watchdog limit +from the memory size: watches with more than 600,000 bytes for watch apps +run two job steps per event, which needs 240,000 bytecodes per event +(simulator.json's watchdogCount); the others run one step and need at +least 120,000. + Usage: gen_products.py --check [--manifest manifest.xml] [--devices DIR] Exit: 0 every product qualifies, 1 some don't, 2 device files missing. @@ -29,6 +35,9 @@ EXIT_OK, EXIT_UNQUALIFIED, EXIT_MISSING = 0, 1, 2 IQ_NS = "{http://www.garmin.com/xml/connectiq}" MIN_API = (5, 0, 0) MIN_WATCH_APP_MEMORY = 512 * 1024 +# Jobs.LARGE_WATCH_MEMORY and the watchdog each memory class needs. +LARGE_WATCH_MEMORY = 600_000 +WATCHDOG_SMALL, WATCHDOG_LARGE = 120_000, 240_000 WATCH_GROUP = "Watches/Wearables" DEFAULT_DEVICES = Path.home() / ".Garmin" / "ConnectIQ" / "Devices" @@ -42,6 +51,7 @@ class Device: group: str min_api: tuple[int, ...] watch_app_memory: int + watchdog: int def problems(self) -> list[str]: reasons = [] @@ -51,6 +61,12 @@ class Device: reasons.append(f"API {'.'.join(map(str, self.min_api))} is below 5.0.0") if self.watch_app_memory < MIN_WATCH_APP_MEMORY: reasons.append(f"watch apps get {self.watch_app_memory // 1024} KB, below 512 KB") + needed = WATCHDOG_LARGE if self.watch_app_memory > LARGE_WATCH_MEMORY else WATCHDOG_SMALL + if self.watchdog < needed: + reasons.append( + f"watchdog allows {self.watchdog} bytecodes per event; the job runner needs {needed} at" + f" {self.watch_app_memory // 1024} KB (change Jobs.stepsPerTick)" + ) return reasons @@ -61,12 +77,17 @@ def parse_version(text: str) -> tuple[int, ...]: def load_device(devices: Path, product: str) -> Device: compiler = json.loads((devices / product / "compiler.json").read_text(encoding="utf-8")) memory = {app["type"]: int(app["memoryLimit"]) for app in compiler["appTypes"]} + simulator = devices / product / "simulator.json" + watchdog = 0 + if simulator.exists(): + watchdog = int(json.loads(simulator.read_text(encoding="utf-8")).get("watchdogCount", 0)) return Device( product=product, name=str(compiler.get("displayName", product)), group=str(compiler.get("webDocDeviceGroup", "")), min_api=min(parse_version(p["connectIQVersion"]) for p in compiler["partNumbers"]), watch_app_memory=memory.get("watchApp", 0), + watchdog=watchdog, ) diff --git a/tools/tests/test_gen_products.py b/tools/tests/test_gen_products.py index 47424d2..e57973f 100644 --- a/tools/tests/test_gen_products.py +++ b/tools/tests/test_gen_products.py @@ -1,5 +1,5 @@ # Author: cirakg & claude ai -# Summary: Tests for tools/gen_products.py (the manifest product rule). +# Summary: Tests for tools/gen_products.py (the manifest product rule and the watchdog the job runner assumes). """Tests for tools/gen_products.py, with synthetic device definitions.""" from __future__ import annotations @@ -17,6 +17,7 @@ MANIFEST = """<iq:manifest version="3" xmlns:iq="http://www.garmin.com/xml/conne def add_device(devices: Path, product: str, api: list[str], memory: int, group: str = gp.WATCH_GROUP) -> None: folder = devices / product folder.mkdir(parents=True) + set_watchdog(devices, product, gp.WATCHDOG_LARGE if memory > gp.LARGE_WATCH_MEMORY else gp.WATCHDOG_SMALL) compiler = { "displayName": product.upper(), "webDocDeviceGroup": group, @@ -26,6 +27,10 @@ def add_device(devices: Path, product: str, api: list[str], memory: int, group: (folder / "compiler.json").write_text(json.dumps(compiler)) +def set_watchdog(devices: Path, product: str, count: int) -> None: + (devices / product / "simulator.json").write_text(json.dumps({"watchdogCount": count})) + + def write_manifest(tmp_path: Path, products: list[str]) -> Path: path = tmp_path / "manifest.xml" path.write_text(MANIFEST.format(products="".join(f'<iq:product id="{p}"/>' for p in products))) @@ -55,9 +60,23 @@ def test_missing_device_files(tmp_path: Path) -> None: def test_problem_messages() -> None: - device = gp.Device("x", "X", "Edge", (4, 2, 0), 131072) + device = gp.Device("x", "X", "Edge", (4, 2, 0), 131072, 120000) assert device.problems() == [ "group is 'Edge', not 'Watches/Wearables'", "API 4.2.0 is below 5.0.0", "watch apps get 128 KB, below 512 KB", ] + + +def test_watchdog_must_match_the_job_runner(tmp_path: Path) -> None: + devices = tmp_path / "Devices" + add_device(devices, "big_slow", ["6.0.0"], 786432) + set_watchdog(devices, "big_slow", 120000) # would run 2 steps on 120,000 + add_device(devices, "small_fast", ["6.0.0"], 524288) + set_watchdog(devices, "small_fast", 240000) # fine: 1 step, more room + assert gp.check(write_manifest(tmp_path, ["small_fast"]), devices) == gp.EXIT_OK + assert gp.check(write_manifest(tmp_path, ["big_slow"]), devices) == gp.EXIT_UNQUALIFIED + problems = gp.load_device(devices, "big_slow").problems() + assert problems == [ + "watchdog allows 120000 bytecodes per event; the job runner needs 240000 at 768 KB (change Jobs.stepsPerTick)" + ] |
