refactor(catalog): single-source the object-type code set from OBJ_TYPES#21
Closed
mrosseel wants to merge 14 commits into
Closed
refactor(catalog): single-source the object-type code set from OBJ_TYPES#21mrosseel wants to merge 14 commits into
mrosseel wants to merge 14 commits into
Conversation
…ill all translations (brickbots#488) * i18n: fix web-template extraction and wrap missing UI strings The Jinja2 extractor in babel.cfg pointed at `views2/` (which doesn't exist) and the extract command only scanned `./PiFinder`, so the 141 `{{ _() }}` strings in python/views/*.html were never extracted — the entire web UI fell back to English regardless of the selected language. Point the extractor at `**.html` and add `./views` as a scan root (babel.cfg, noxfile.py, and the i18n SKILL.md docs). Also wrap user-visible device strings that were missing `_()`: - menu_structure.py: Comets / Confirm / Cancel (their twins elsewhere were already wrapped, so these reuse existing msgids) - location_list.py: action labels (Load/Rename/Delete) and the Loaded/ Deleted/Renamed/Location-Name popups. English keys are kept for action dispatch; labels are translated at draw time via a small label map. - callbacks.py: Location Reset / Time-Date Reset / user-object popup And fix broken `_()` antipatterns: - object_details.py: animated-ellipsis f-strings produced a different msgid per frame; translate the base word and append dots outside `_()` - object_list.py: drop a no-op double `_()` wrap around a runtime string - software.py: positional `{}` -> named `{mode}` placeholder f-string popups are converted to named-placeholder `.format()` so translators get stable msgids. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * i18n: regenerate catalogs and AI-fill all translations (de/es/fr/zh) Run the babel extract/update/compile pipeline against the corrected config. The template (.pot) grows 508 -> 666 msgids: the newly-extracted web-template strings plus the device strings wrapped in the previous commit. Fill every untranslated and fuzzy entry across all four languages (181 each for de/es/fr, 186 for zh) with machine translations, each tagged `# AI-TRANSLATED (claude): needs human review` so a reviewer can find and validate them (`grep -rn AI-TRANSLATED python/locale/`). Placeholders, newlines, and the EQ / RA/Dec / Alt/Az / GPS abbreviations are preserved verbatim; gettext's `msgfmt -c` passes for all languages. One low-confidence zh entry ("det {n}") is additionally flagged `#, fuzzy` so it falls back to English until reviewed. All languages are now fully translated. .mo binaries recompiled. Note: the bulk of the .po line churn is unavoidable re-wrapping — pybabel 2.16.0 does not reproduce the committed files' previous wrapping at any --width, so any `nox -s babel` run reflows them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Loading an observing (Ops) list pushed its objects to UIObjectList as a "custom" list, and refresh_object_list() assigned that list straight through without running it past catalog_filter. As a result, changing the altitude filter (or any filter) had no effect on the list — the object count never changed. Run "custom" lists through catalog_filter.apply(), gated by a new "filtered" flag in the item definition so only observing lists opt in. Name-search results (the other "custom" consumer) stay unfiltered, so searching for an object by name doesn't return zero results when it's below the altitude cutoff. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: NixOS migration support * fix(migration): align tetra3 sys.path with upstream upstream defines utils.tetra3_dir as the inner package path (python/PiFinder/tetra3/tetra3) and every sys.path.append(tetra3_dir) site relies on that. migration had the short submodule-root path plus an extra sys.path.append(tetra3_dir / 'tetra3') workaround in solver_main.py and ui/preview.py, but solver.py never got the workaround — so 'from tetra3 import cedar_detect_client' fails when the inner module does the bare 'import cedar_detect_pb2'. Take upstream's pattern verbatim: long tetra3_dir, single sys.path.append, no workaround. Fixes PR brickbots#433's nox ui_tests failure. * fix+test: cover migration UIs in smoke harness + coerce progress to int Two coupled changes for upstream's new test_all_ui_modules_covered guard (PR brickbots#438): - Wire UIMigrationConfirm and UIMigrationProgress into _DYNAMIC_IDS with item_definition fixtures. Their __init__ methods use .get() with defaults so a stub version_info dict exercises construction + key handlers. - UIReleaseNotes stays in _COVERAGE_SKIP — its active() fetches markdown over HTTP and needs a network mock. - UIMigrationProgress.update() was crashing under the smoke harness because sys_utils mock returned MagicMock for percent/status. Coerce percent to int and accept status only as str; on bad data keep the prior value. This also hardens against a corrupt /tmp/nixos_migration_progress JSON file at runtime. * chore: prune dead code and move dev artifacts off the branch Dead code dropped from the tree: * python/PiFinder/sys_utils_nixos.py (~591 lines): never imported, get_sys_utils() has no NixOS dispatch path. The NixOS-side system utilities ship inside the migration tarball as python/PiFinder/sys_utils.py on the nixos branch. * python/pyproject.toml dbus/gi mypy ignores: only made sense for the above; belong on the nixos branch. * python/scripts/migration_calc.py (~509 lines): described an unimplemented A/B-partition layout; no caller in the active flow (nixos_migration.sh uses nixos_migration_calc.py instead). Out-of-tree (moved to local notes): * MIGRATION_BRANCH_STATE.md (107 lines): internal hand-off notes, not user-facing docs. * python/scripts/test_migration_loopdev.sh (~498 lines): offline test harness that re-implemented an older design (tar.gz + magic-header staging) rather than invoking nixos_migration_init.sh — it had already drifted from the real flow (which is tar.zst + RAM-staged). Useful as a future starting point for a real integration test but misleading to ship in the repo. Documentation: * migration_gate.txt: add header comment explaining the killswitch contract; update _fetch_migration_gate parser to skip "#" lines so the file is self-documenting without breaking semantics. * chore: tighten migration error handling (timeout, except, sha256 hard-fail) Three small fixes from the PR review: - ui/software.py UIMigrationProgress.update: replace the redundant `except (AttributeError, Exception)` with a targeted `AttributeError` guard around the sys_utils.get_migration_progress() lookup (only needed when running against sys_utils_fake). The helper itself swallows OS/JSON errors and returns {}, so wrapping everything in `except Exception` was hiding real failures from the polling loop. - ui/software.py get_release_version: add timeout=REQUEST_TIMEOUT to the requests.get call (it previously had none and would hang the UI thread if GitHub stalled). Widen the except to RequestException so Timeout, ReadTimeout, etc. are all caught. - sys_utils.start_nixos_migration: hard-fail with ValueError when neither migration_sha256_url nor migration_sha256 produces a value. Previously the helper logged a warning and returned "", which the migration script then treated as "skip checksum verification". An in-place OS replacement must not run without integrity verification. * fix(migration): hex-encode SSID + escape PSK in NM keyfile emission The initramfs WiFi-migration step generated NetworkManager .nmconnection files by interpolating the SSID and PSK directly from wpa_supplicant.conf into a heredoc. SSIDs containing characters with semantic meaning in NM keyfile format (semicolon, brackets, equals, leading/trailing whitespace) or in the filesystem (slash, NUL, "..") could break the connection file, the file name, or both. Failure mode: WiFi config goes missing after migration -> headless device is unreachable until re-flashed. Fixes: - Encode SSID as semicolon-separated hex bytes (ssid=4d;79;...). This is NM keyfile's standard binary form and is safe for any byte content including non-ASCII and special chars. - Escape the id= and psk= values for NM keyfile format: backslashes doubled, semicolons backslashed. - Sanitize the filename to [A-Za-z0-9._-]; empty / "." / ".." after sanitization fall back to "wifi". - Use printf %s instead of echo when feeding the parser, so SSIDs starting with "-" or containing backslash escapes are not mangled by echo's flag interpretation. Verified end-to-end with a sample wpa_supplicant.conf containing spaces, slashes, and a semicolon in the PSK -- files generated cleanly with the expected escaping. * feat(migration): SSD1333 display support + JSON config gate ui: render migration status from frame 0, expose underlying error, and allow back/exit on terminal pre-start failure so the user isn't trapped on the failure screen. * fix(migration): chown /nix/store to root after tarball extraction The migration tarball can carry non-root (uid 1000) store paths; tar preserves them, and NetworkManager then refuses to load its wifi plugin so the migrated device comes up with wlan0 "unmanaged" and no wifi. Normalise /nix/store and the nix db dir to root right after the rootfs move, while the new root is still writable (it is mounted read-only once NixOS boots). The fix-nix-store-ownership boot service is the runtime backstop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(migration): keep initramfs progress display readable and flicker-free - nixos_migration_init.sh: after the first stage, drive migration_progress with --update so the OLED framebuffer is overwritten in place instead of resetting/blanking to black between every stage - shorten over-wide stage strings to fit the 128px display ("Loading tarball to RAM" -> "Loading tarball", "Validated (NMB free)" -> "Validated: NMB") - migration_progress.c: render the "DO NOT POWER OFF" banner as a solid bright bar with black text for contrast - migration_progress.c: auto-fit the variable-length stage name (scale down, truncate as last resort) so it never overflows either panel - rebuild the static aarch64 migration_progress binary to match Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(migration): keep the OLED progress display alive across stages The flicker-free --update path left the screen black. migration_progress was spawned fresh per stage, and each process re-requested the RST GPIO (driven low = panel reset) then skipped re-init, so the panel was wiped after the first stage. Replace it with a single long-lived --serve process that initialises the panel once and redraws in place from stdin, so the display updates without ever resetting to black. - migration_progress.c: add --serve (read '<pct> <num> <total> <name>' lines from stdin); drop the broken skip_reset/--update path - nixos_migration_init.sh: start one --serve process fed via a FIFO on fd 3; ignore SIGPIPE so a dead display can never abort the migration - nixos_migration.sh: drop the percent from the 'Downloading...' status so the screen no longer shows two mismatched percentages (download progress is already reflected in the overall bar) - rebuild the static aarch64 migration_progress binary Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `bloom_remap` path swapped the arrow keys (and their LNG_/ALT_ variants) when screen_direction was "as_bloom". This is keyboard input remapping, distinct from the IMU/display/camera orientation that the "as_bloom" screen_direction otherwise drives — so strip it out. Drops the `bloom_remap` parameter from run_keyboard() across the pi, local, and none keyboard backends, removes the swap block in KeyboardPi.__init__ (inlining the now-unconditional UP/DOWN/LEFT/RIGHT keymaps), and stops main.py computing/passing the flag. The "as_bloom" screen_direction value and its orientation handling are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review the release..main delta and update the v2.6.0 release notes: add Polar Alignment Assist (brickbots#459) and Multi-Format Observing List Import (brickbots#394); add comet-propagation speedup (brickbots#470) and missing bug fixes (brickbots#472, brickbots#479, brickbots#465, brickbots#483, brickbots#473, brickbots#474); correct the Telemetry menu path; note the menu reorganization (brickbots#480); refresh i18n (brickbots#488), developer items (brickbots#478, brickbots#481), and the footer commit/file/line stats. Excludes the NixOS migration (brickbots#433). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…al place/time, web i18n) (brickbots#489) Cross-checked the release..main delta for 2.6.0 against the docs and filled the four user-facing gaps that lacked coverage: - quick_start: correct the stale "must be mounted perpendicular" claim — the new quaternion IMU works out its own orientation, so any mounting angle is fine; add a note that location/time can be entered by hand without GPS. - user_guide: add the any-mounting-angle fact to How It Works; add a Push-To note that Mount Type -> Equatorial switches guidance to RA/Dec; add a new Star Chart section (Coordinate Sys. options, Zenith/NCP/SCP up labels, the "!" GPS-not-ready fallback) and a new Place & Time section (Enter Coords, Set Time/Date chaining, GPS-overwrite protection, usable without GPS). - connectivity: document the web interface's DE/FR/ES language support and the Logs configuration switch/upload feature. All claims verified against the code; sphinx-build -n (nitpicky) is clean. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…otes Two statements didn't match the code: - Mount Type -> Equatorial does not enable IMU tracking. The rewritten dead-reckoning runs identically for all mount types (mount_type is only read in aim_degrees to pick Alt/Az vs RA/Dec Push-To deltas). Reword so the equatorial-mount IMU win and the Mount Type readout setting are separate. - The web interface language follows the connecting device's browser Accept-Language (server_locale in server.py), not the device UI Language setting, and is selected per request rather than after a restart. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rickbots#491) The Polar Align adjust screen now always renders directional Alt/Az arrows instead of +/- axis signs, regardless of the configured mount type (PR brickbots#486). Regenerate the two affected user-guide screenshots so the manual matches the shipped UI: - polar_align_adjust_docs.png: "+" signs -> directional arrows - polar_align_marking_menu_docs.png: arrows now show through the wheel (the background adjust screen previously showed "+") The surrounding prose already says "follow the arrows", so no text change is needed. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s#492) The Selenium web tests drive PiFinder's remote keypad with fixed key sequences and assert the resulting screen. Two menu changes on main shifted item indices, deterministically breaking 16 navigation tests: - Objects submenu gained an "Obs Lists" item at index 3, shifting Custom (3->4), Name Search (4->5) and Set Filters (5->6). - Start submenu reordered so GPS Status is now index 3 (was 2). Update the affected key sequences plus their docstrings/comments: - test_web_remote_filter.py (11): Objects->Set Filters hop RDDDDD->RDDDDDD - test_web_remote_objects.py (2): Custom RDDDR->RDDDDR; Set Filters ->RDDDDDDR - test_web_remote.py (2): Name Search RDDDDR->RDDDDDR - test_web_locations.py (1): Start->GPS Status RDD->RDDD Verified: all 16 pass against a live headless instance. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The 0001-0011 ADR namespace had collided under parallel-worktree work: 0003 (x3), 0004 (x2), 0005 (x2), and 0010 (x2) each had multiple files. Resolution rule: the most-referenced file keeps the contested number (ties -> earliest creation); latecomers move to the first globally-free slot (0012+, since 0006-0008 are reserved by unmerged hardware branches). Kept in place: object-image-orientation (0003), pygame-keyboard (0004), focus-hfd-self-contained-in-ui (0005), zero-match-recovery (0010). Renamed via git mv (history preserved): - 0003-solver-integrator-message -> 0012 - 0004-pointing-estimate-timing -> 0013 - 0005-failed-solve-preserves-estimate -> 0014 - 0010-user-docs-page-granularity -> 0015 - 0003-pifinder-native-observing-list-format -> 0016 All in-repo references updated: positioning/catalog CONTEXT.md glossaries and ax docs, the api_extensions/positioning.py docstrings, the obslist schema.json, the docs skill pointer, the 0014->0012 cross-ref, and the 0016 title line. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(nixos): add update_manifest.py helper * ci(nixos): add publish_manifest.sh helper * ci(nixos): add testable-PR build workflow (pull_request_target, label-gated) * ci(nixos): simplify publish_manifest.sh (drop one-time collapse) * ci: rotate Attic dev cache public key after S3 cutover (8UU… → Vkem…)
…tton, SSD1333 display (brickbots#498) * Working ssd1333 and adjustments to brightness control for ssh1351, needs testing * Fixed screen rotation, add in directional buttons * Fix brightness chord on 5-column keypad matrix The keypad matrix grew from 4 to 5 columns when the directional buttons were added, which shifted every key's computed keycode (i*cols+j). The matrix scanner still hard-coded SQUARE as keycode 15 (and the up/down auto-repeat as [17, 18]), so the SQUARE+/- brightness chord and the hold-to-repeat both pointed at the wrong keys. Derive square_keycodes and repeat_keycodes from the keymap instead of hard-coding indices, so they track the layout. This also makes the brightness chord work from the new d-pad SQUARE and enables repeat on both directional clusters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Result of grilling session * Add read-only BQ25895 battery telemetry (plumbing + tests) Adds battery monitoring for the rev-4 board's TI BQ25895 single-cell Li-ion charger (I2C 0x6A, bus 1). Read-only telemetry only: the sole write is pulsing REG02 to trigger a one-shot ADC conversion; the power path is managed in hardware (see docs/adr/0006). No UI/web/warnings yet. - types/hardware.py: ChargeStatus enum, BatteryState, HardwareCapabilities - battery_bq25895.py: pure decode_registers/estimate_soc, BQ25895 I2C wrapper, battery_monitor process (exits, never fakes, on init failure) - battery_fake.py: -fh twin emitting a deterministic discharging cell - hardware_detect.py: import-safe I2C probe + detect_capabilities() - state.py: battery()/set_battery() + hardware()/set_hardware() - main.py: module fork, publish capabilities, conditional Battery spawn - pyproject.toml: mypy override for adafruit_bus_device.* Register scaling verified against BQ25895-datasheet.pdf and live rev-4 readings. 39 unit tests cover decode, SoC interpolation/clamping, charge-status mapping, and hardware detection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Link screen to BQM presence * Drive GPIO14 low at shutdown to cut power (gpio-poweroff latch) Provision the gpio-poweroff device-tree overlay (active_low) so the kernel drives GPIO14 low as the final shutdown step, tripping the LTC2954 power controller to drop EN on the TPS61088 boost and cut power. Free GPIO14 by disabling the serial console so the kernel does not drive console bytes onto the kill line. Document the latch in the Battery glossary and ADR 0007. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add POWER_BTN: power button opens/confirms shutdown menu Wire the new hardware power button (GPIO15, POWER_BTN keycode) through key dispatch to the UI. From any screen a press jumps to the shutdown confirmation menu; on that screen the power button acts as the right key (select), so a first press raises the Confirm/Cancel prompt and a second press confirms. - keyboard_pi.py: emit POWER_BTN on a >1s hold; init power_sent latch in __init__ (was read before assignment, raising AttributeError on the first hold) and drop the dead local + duplicate GPIO.setup. - main.py: dispatch POWER_BTN to MenuManager.key_power. - MenuManager.key_power: dismiss help/marking-menu overlay and swallow, else forward to the active module. - UIBase.key_power: default jumps to the shutdown menu item. - UITextMenu.key_power: on the shutdown screen act as key_right (select); every other text menu falls back to the jump default. - server.py: expose POWER_BTN by name in /api/key's button_dict. - docs/ax/ui: glossary entry for the power key + key_power in the key_* table and dispatch notes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add Sound context docs: glossary, ADR, context-map entry Documents the planned rev-4 buzzer sound system from the grilling session — design only, no implementation yet: - docs/ax/sound/CONTEXT.md: glossary (earcon, note, intent volume, master volume, important/transient earcon, stale/max-age, resonance) - docs/adr/0008-sound-best-effort-delivery.md: best-effort, monotonic-stamped, latest-wins delivery; shutdown bounded-wait as the documented exception against the GPIO14 power latch - CONTEXT-MAP.md: Sound context + its UI/shutdown/hardware-gating relationships Implementation is handed off separately (handoff_sound.md, untracked). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add buzzer earcon sound subsystem (rev-4 PWM ch0) v1 audible feedback for the rev-4 passive piezo buzzer (PWM ch0 / GPIO12): named events become short earcons played on the buzzer. - types/sound.py: pure, import-safe data model (Earcon enum, Note, EarconDef, PlayEarcon/SetVolume wire messages) - sound.py: earcon catalog, pure logic (note_duty / total_duration_ms / select_winner), BuzzerPWM hardware seam, request() helper, and the sound_monitor process entry (mirrors battery_monitor) - has_buzzer capability set from the rev-4 charger probe; the process spawns only on real Pi hardware, so dev/-fh stays silent and request() no-ops on the None queue - wired producers: STARTUP, KEYPRESS, VOLUME_SAMPLE, SHUTDOWN (ERROR / LOW_BATTERY / SOLVE_LOCK defined but unwired in v1) - best-effort delivery: monotonic-stamped, latest-wins drain, stale transients dropped, important earcons exempt (ADR 0008) - shutdown cue routed through the main loop so its bounded wait reliably precedes the GPIO14 power latch - Volume menu (Off/1-5, default 2) under User Pref + sound_volume config default - unit tests for note_duty, select_winner, total_duration_ms, and a player smoke test with a fake driver Tested on rev-4 hardware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add standalone earcon player CLI (python -m PiFinder.sound) A command-line tool for tuning the buzzer by ear, reusing the existing BuzzerPWM / play_earcon / catalog code (no duplication). Requires real hardware (PWM ch0); only --list works without it. - play_tone(): raw-tone primitive (absolute duty, bypasses the master-volume mapping, clamped to MAX_DUTY), sibling to play_earcon - _run_cli(): argparse front-end with --list, --earcon (comma list or 'all'), --all, --level, --tone FREQ:MS:DUTY, --sweep START:STOP:STEP:MS (find resonance), --duty, --repeat, --gap; builds the driver once and always stop()s it in a finally - unit tests for play_tone (duty pass-through + clamping) - tune KEYPRESS to 1000 Hz Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Adjust sounds * Compose startup and shutdown earcons Replace the placeholder STARTUP and SHUTDOWN note data with two tuned, matched cues in G major: - STARTUP: rising G5-A5-C6-G6 (do-re-fa-do'), resolving up an octave to a held tonic. - SHUTDOWN: falling G6-C6-G5 (do'-fa-do), the startup's climb mirrored back down to the held low tonic. They bookend each other (rise to wake, fall to sleep; each ends on the other's starting pitch). Both use de-quantized note lengths for a looser, less metronomic feel. Notes sit below the ~4 kHz resonance, so loudness rises naturally toward the top. KEYPRESS is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add battery level indicator to the title bar Show a battery glyph in the title bar, left of the GPS/solver icons, on battery-enabled (rev-4 BQ25895) hardware only. Driven off the published BatteryState: charge_status picks a charging (bolt) glyph while the cell is charging (when state_of_charge_pct is None), otherwise state_of_charge_pct is quantized into ~20% buckets with an empty-outline glyph at <=10% remaining. Gated on shared_state.hardware().has_bq25895 and a non-None battery() so it stays hidden on non-battery boards and during the startup window before the first sample, rather than showing a fake level. Positioned just left of the GPS icon with a small proportional gap; battery hardware is always paired with a 176px+ display, so it clears the rotating constellation/SQM without reflow. Add unit tests covering the bucket boundaries, the charging states, and the plugged-in-and-full (CHARGE_DONE) case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Adjust spacing for battery indicator * Conditional placement of Constellation/SQM for battery indicator * Turn off Raspberry Pi power LED on startup Add sys_utils.set_power_led(on) to control the Pi's red PWR LED (a plain gpio-led, on/off only — not dimmable) via passwordless sudo, matching the other privileged helpers in the module. Mirror it as a no-op in sys_utils_fake for off-hardware runs. Call it from main.py right after the keypad backlights come up to switch the LED off, since a fixed-on red light hurts night vision. Best-effort and wrapped so it never blocks startup. No config.txt change, so the LED returns to its firmware default at boot and is turned off once PiFinder starts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Make boot splash resolution-flexible for the 176 panel Cherry-picked from a48cd4c (ssd1333_ui_hw_test), splash.py portion only: splash.py (pifinder_splash.service, a boot service separate from the main app) hardcoded get_display("ssd1351") + the 128 welcome.png + rectangle([0,0,128,16]). On real SSD1333 hardware that inits the wrong controller. It now detects the panel via hardware_detect.detect_capabilities() (rev-4 -> ssd1333, else ssd1351, mirroring main.py), scales the welcome image to fill resX x resY, and spans the version/wifi banner across resX at a resolution-scaled height. No-op on the 128 panel. The companion ui/console.py fix from a48cd4c is already present on this branch; the ssd1333-ui-flex-handoff.md dev artifact was intentionally omitted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Apply BQ25895 fast-charge config at runtime The battery monitor now writes a fixed fast-charge configuration to the rev-4 BQ25895 on every poll: disable the I2C watchdog (REG07), and raise the input current limit (REG00 IINLIM) and fast-charge current (REG04 ICHG) to ~1.5 A. Out of reset the chip defaults to a 40 s watchdog, a low (often 500 mA) input limit and 2048 mA charge, and the input limit is the real bottleneck on charge rate. Re-asserting each poll (rather than once at startup) restores the config after a chip reset, brownout, or USB re-detection. It is idempotent: plan_charging_writes() emits only the registers that have drifted, so steady state is three reads and zero writes. EN_ILIM is preserved, so the external ILIM-pin resistor stays a hardware ceiling; OTG/HIZ/ charge-enable are never touched (OTG/boost is disabled in hardware via the /OTG strap). This reverses the read-only stance of ADR 0006 (for charge-rate tuning, not OTG safety); supersede it with ADR 0011 and update the battery CONTEXT.md glossary to match. The encode/plan helpers are pure and covered by tests/test_battery_config.py. Verified live on a rev-4 unit: REG07 0x9D->0x8D, REG00 0x66->0x5C (1500 mA), REG04 0x20->0x17 (1472 mA), idempotent on re-run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(adr): renumber 0011-battery-fast-charge -> 0017 (resolve cross-branch collision) On main, ADR 0011 is observing-list-descriptions; this branch independently created a second 0011 (battery-fast-charge-config). Renumber the branch's ADR to 0017 (the next globally-free slot after main's renumber to 0012-0016) so the two no longer collide when new_hardware_features merges into main. Renamed via git mv (history preserved): - 0011-battery-fast-charge-config -> 0017-battery-fast-charge-config References updated: docs/ax/battery/CONTEXT.md, the 0006 supersede link, battery_bq25895.py, and test_battery_config.py. ADR 0007 (gpio-poweroff) references on the same lines were left untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * i18n: translate "Volume" (de/es/fr/zh) for new sound menu The new_hardware_features sound menu adds a `_("Volume")` setting. The branch's prior `pybabel update` inserted the msgid into all four .po files but left msgstr empty. Fill them in and recompile .mo: de: Lautstärke es: Volumen fr: Volume zh: 音量 Each is tagged `# AI-TRANSLATED (claude): needs human review`. The sibling `_("Off")` value reuses an existing, human-translated msgid, so no new work was needed there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(battery): keep fast-charge input limit across cable replug while off On rev-4 (BQ25895), unplugging and re-plugging the charge cable with the PiFinder powered off dropped the input current limit back to ~500 mA and left it there, since the per-poll re-assert only runs while the app is up. The chip re-runs USB adapter detection (AUTO_DPDM_EN) on each VBUS insertion and classifies the port as a low-current SDP. The charger stays battery-powered when the system is off (the power-off latch only drops the SYS boost) and the watchdog is already disabled, so its registers persist. The only remaining trigger that reverts IINLIM is adapter re-detection. Clear AUTO_DPDM_EN (REG02 bit 0) as part of the fast-charge config so the configured 1.5 A input limit survives later cable replugs with no software running, once the app has configured the chip once. Caveat: a full power-on reset (battery drained/disconnected) restores AUTO_DPDM_EN=1, so the first insertion after that charges slowly until the unit is next booted; the chip has no non-volatile config. - battery_bq25895: add AUTO_DPDM_MASK; thread REG02 through plan_charging_writes / apply_charging_config (RMW, only bit 0 touched). - tests: update signature, add a dedicated AUTO_DPDM case. - docs: ADR 0017, battery CONTEXT.md, bq25895 design notes (incl. manual i2cset recipe and the DCP-signature hardware alternative). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Adjust screen rotation for PF4 * chore: apply pre-PR review fixes (ruff format + comment typos) - sys_utils: two blank lines after set_power_led() (ruff format) - ui/base: fix two battery-indicator comment/docstring typos ("indicated"->"indicator", "a bit of"->"a bit if the"); collapse a title-text draw() call to one line (ruff format) Verified before PR: nox lint clean, nox format clean, mypy clean (137 files), i18n complete (Volume/Off wrapped + translated + compiled in de/es/fr/zh; no full-babel churn since .pot is untracked). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The allowed object-type codes were hand-duplicated in the filter menu, default_config.json and the docs. Make OBJ_TYPES the one source: - generate the Type filter menu items from OBJ_TYPES (drops the hardcoded list) - default filter.object_types to list(OBJ_TYPES) instead of a JSON array - order OBJ_TYPES to the filter-menu display order - add a drift-guard test (README codes table + default_config)
Owner
Author
|
Superseded by brickbots#511 (correct upstream target: brickbots main). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
OBJ_TYPES(python/PiFinder/obj_types.py) becomes the single source of truth for the object-type code set. The allowed codes were hand-duplicated in three places that had already drifted.ui/menu_structure.py) — the Type filter items are now generated fromOBJ_TYPES(drops the hardcoded list).default_config.json— removed the hardcodedfilter.object_typesarray; the filter defaults tolist(OBJ_TYPES)incatalogs.py.obj_types.py— reordered to the filter-menu display order (iteration order only; lookups are unaffected) and documented as the canonical source.tests/test_obj_types_docs.py) — fails ifdefault_config.jsonre-hardcodes the set or the docs "Object type codes" table drifts fromOBJ_TYPES.User-facing change
The only visible effect is the Type filter menu adopting the same labels already used on the object-detail screen (they previously disagreed):
Order is unchanged. No i18n regression — these msgids already exist and are translated in every locale (they are the detail-screen labels).
Test plan
Note: the "Object type codes" docs table the guard checks ships in the related obslist CSV-import branch; the README check no-ops until both land on
main.🤖 Generated with Claude Code