secubox-deb/remote-ui
CyberMind 839bab94a8
Converge round/ + square/ dashboards into secubox_common + pointer input on Pi 4B/400 (#137)
* feat(common): seed secubox_common package skeleton (ref #135)

* fixup(common): complete SPDX header (ref #135)

Code review on T2 flagged the truncated 1-line SPDX header (project
canon is 4 lines: SPDX + Copyright + License + LICENCE.md ref). The
plan's code blocks propagated the short form; restoring the full
header now so it doesn't ripple through T3-T18+.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(common): theme.py — palette constants + DEFAULT_FONT loader (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(common): remove dead ImageFont import from test_theme (ref #135)

Code review flagged: test_theme.py imported PIL.ImageFont but never
referenced it directly — fonts come back through theme.load_default_font().
F401 noise; clean up before downstream lint runs catch it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(common): modules.py — canonical Hamiltonian module table (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(common): MIND extract divides load_avg by os.cpu_count() (ref #135)

Code review on T4 flagged the hardcoded /4.0 divisor: on Pi Zero W
(single-core, round/ target), load_avg=1.0 would render the MIND
arc at only 25% fill even though the CPU is fully saturated.
Reading os.cpu_count() at import time makes the formula self-adapt
per device — round/ (1 core), square/ Pi 4B/400 (4 cores) — without
any per-platform branching.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(common): icons.py — module icon loader with multi-path resolution (closes round/ ICONS_DIR bug, ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(common): canvas.py — DashboardCanvas base + paint_background (ref #135)

* feat(common): canvas — paint_rainbow_ring (HSV hue rotation) (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(common): paint_rainbow_ring erase colour + dead test assert (ref #135)

Code review on T7 flagged two plan defects:
1. The inner-disc erase was hardcoded to (0,0,0,255), but both
   RoundDashboard (T17) and SquareDashboard (T11) composite against
   COSMOS_BLACK (8,8,8) — gaps between primitives (r≈22-50,
   r≈90-125) would show a dark-ring artefact. Add a `background`
   parameter defaulting to theme.COSMOS_BLACK so callers blend
   correctly.
2. test_paint_rainbow_ring_pixels_in_band_are_colored had an
   always-false guard `if 240 + 250 < 480` (490 < 480 = False), so
   the outer-radius assertion was dead code. Use radius 238 instead
   (x=478 in-bounds) and drop the guard — the outside pixel stays
   at the fixture's initial (0,0,0) because rainbow_ring never
   touches it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(common): canvas — paint_concentric_arcs (per-module ring fill) (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(common): saturate concentric_arcs test metrics for CPU-count portability (ref #135)

T8 commit ea7c2781 left test_paint_concentric_arcs_six_rings_present
brittle: MIND's load_avg_1=4.0 only saturates the arc on a 4-core
machine. On the 20-core dev host _CPU_COUNT=20 makes the MIND ring
fill 20% (4/20), leaving the 3 o'clock sample on the dark track and
failing the colour-distance assertion. Push every metric to 999 so
each ring clamps to 100% regardless of os.cpu_count().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fixup(common): type-annotate paint_concentric_arcs modules param (ref #135)

Code review on T8 flagged the `modules` parameter as the only
unannotated arg on `paint_concentric_arcs` — every other arg
(`img`, `center`, `metrics`, `radii`) carries an annotation, so this
one creates an inconsistent surface and gives no IDE assist. Use
`Iterable[Module]` (accurate — zip() only needs an iterable) and
import Module from secubox_common.modules. Required before T9 adds
more callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(common): canvas — paint_pod_cluster (icon or letter on coloured disc) (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(common): exercise paint_pod_cluster icon-paste path + drop dead asserts (ref #135)

Code review on T9 flagged two test-quality gaps:
1. Both T9 tests used pod_size 20 / 30, which have no matching icon
   files (sizes on disk: 22, 48, 96, 128), so load_module_icon
   always returned None and the icon-paste code path went untested.
   Adds test_paint_pod_cluster_uses_icon_when_available at
   pod_size=48 (real file present) — exercises img.paste(icon, ...)
   on every commit. Calls icons._cache_clear() first to avoid stale
   None cache entries from earlier tests in the session.
2. test_paint_pod_cluster_six_coloured_circles computed dr/dg/db
   colour distances but never asserted on them — dead code that
   misled the contract. Drop the computation; the assertion stays
   the same (loose non-black check, justified because the letter
   fallback may paint the centre pixel white).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(common): canvas — paint_central_button + paint_alert_ribbon (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(common): paint_alert_ribbon honest fill + docstring (ref #135)

Code review on T10 flagged that the docstring claimed
"semi-transparent fill" but ImageDraw.rectangle on an RGBA image
just overwrites pixels with the raw tuple — no compositing happens.
The kiosk's framebuffer blit then calls .convert("RGB") which drops
the alpha channel entirely, so the visible result was always a
solid black strip regardless of the alpha=200 hint.

Switch fill to (0, 0, 0, 255), rewrite the docstring to describe
what the code actually does, and add an inline comment explaining
why alpha is pinned to 255 (so future authors don't try to "dim"
the ribbon by lowering it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(square): SquareDashboard via secubox_common; drop ring_dashboard.py (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(square): smoke test uses SquareDashboard, drops RingDashboard tap test (ref #135)

T11 deleted ring_dashboard.py but test_kiosk_smoke.py still imported
RingDashboard, breaking collection. Rewrite the compose-frame test to
drive SquareDashboard (which already composes the 800x480 frame
internally) and drop the on_module_tap test — that routing moves
into __main__.py at T15 and will be covered there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fixup(square): drop dead tm var, clarify paint_background omission (ref #135)

Code review on T11 flagged two housekeeping items:
1. test_kiosk_smoke.py kept TransportManager assignment from the old
   pre-fixup smoke test, but the new SquareDashboard path doesn't
   need it — F841 dead-var noise. Drop both the import and the
   unused assignment.
2. SquareDashboard.layout() skips paint_background because Image.new()
   already fills with the same colour. Add a one-line comment so
   future readers don't wonder why a base-class primitive went
   unused.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(square): pointer_input — mouse + touchpad via python-evdev (ref #135)

* fixup(square): pointer_input motion-event contract + log level (ref #135)

Code review on T12 flagged five items:
1. _handle_evdev_event was silent on REL/ABS but _drain_test_queue
   emitted "motion" events — tests asserted motion presence which
   only worked through the test path. Real Pi 4B + USB mouse would
   never see motion events. Align both paths: emit one motion event
   per axis update from the real path too. T15 ignores motion events,
   so no downstream breakage.
2. log.warning("pointer devices found: ...") fires on every clean
   boot, polluting the log. Switch to log.info for the populated
   case and keep log.warning only when zero devices are found (the
   genuinely unexpected condition).
3. except (OSError, PermissionError) — PermissionError is a subclass
   of OSError, redundant. Drop it.
4. import os, fcntl was inside _discover_devices (PEP 8 E401 +
   non-top-level). Hoist both to module top.
5. _last_motion = 0.0 default keeps the cursor hidden until first
   motion — intentional but non-obvious; add a one-line comment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(square): cursor.py — 12×16 arrow sprite for pointer overlay (ref #135)

* fixup(square): cursor sprite uses theme.GOLD_HERMETIC, real test asserts (ref #135)

Code review on T13 flagged four items:
1. Off-canvas guard used hardcoded 12/16 magic numbers; refactor to
   _W/_H constants so the guard stays coupled to the polygon shape.
2. _OUTLINE comment claimed GOLD_HERMETIC but duplicated the hex —
   import theme.GOLD_HERMETIC so palette changes propagate.
3. test_cursor_clamped_to_image_bounds had a comment but no assert —
   silent green even if draw_cursor were deleted. Add an assertion
   that at least one pixel in the clipped 5×5 corner is non-black.
4. test_cursor_negative_coords_dont_crash had no assert either —
   pin the invariant that the canvas is untouched on no-op return.

Also added the sys.path bootstrap line to the test file so the
secubox_common import resolves on dev hosts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(square): theme + modules_table become re-export shims for secubox_common (ref #135)

theme.py becomes a thin re-export of secubox_common.theme — keeps
`from .theme import COSMOS_BLACK` etc. working in tabs/* without
sweeping import rewrites in this PR. Also exposes DEFAULT_FONT =
load_default_font(12) at module level for legacy module-level imports.

modules_table.py becomes a re-export of secubox_common.modules. The
legacy in-package Module had `radius` and `unit` fields:
- `radius` was form-factor-specific layout (round Pi Zero W used
  214..149, square uses 200..125 via SquareDashboard.RING_RADII).
  Layout properly lives on the dashboard subclass now, not the
  Module dataclass.
- `unit` was defined but never read in production code.

The legacy test_modules_table.py asserted the old [214,201,...] radii
plus extractor behaviour that is fully duplicated in test_modules.py
under secubox_common. Removed as part of the same commit.

Full square/ kiosk suite still 73 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(square): __main__ wires SquareDashboard + PointerInput + cursor overlay (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(square): __main__ drop dead touch_input plumbing (ref #135)

T15's commit kept find_touch_devices()/read_events() in the loop with
a `pass` body — the implementer recognised it as duplicate input but
preserved it to keep the call shape. PointerInput already discovers
every device exposing BTN_LEFT or BTN_TOUCH (T12), which covers USB
mouse, USB touchpad, AND the 7" DSI touchscreen in one path. Calling
read_events on the same fds in parallel would consume events from
the shared device stream without dispatching them.

Drop the empty loop and the four unused imports (TouchEvent, classify,
find_touch_devices, read_events). Leave touch_input.py on disk — it
still has tests and might be useful for a different scenario, but
the kiosk main loop no longer uses it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(square): cross-form-factor secubox_common API stability test (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(round): RoundDashboard via secubox_common (ref #135)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(round): fb_dashboard.draw_dashboard delegates to RoundDashboard (ref #135)

* build(images): ship secubox_common to /var/www/common/python/ on both images (ref #135)

* build(systemd): PYTHONPATH=/var/www/common/python for both kiosks (ref #135)

---------

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:49:24 +02:00
..
common Converge round/ + square/ dashboards into secubox_common + pointer input on Pi 4B/400 (#137) 2026-05-15 09:49:24 +02:00
round Converge round/ + square/ dashboards into secubox_common + pointer input on Pi 4B/400 (#137) 2026-05-15 09:49:24 +02:00
square Converge round/ + square/ dashboards into secubox_common + pointer input on Pi 4B/400 (#137) 2026-05-15 09:49:24 +02:00
README.md feat(remote-ui): Phase 1 — extract common/ shared core (ref #127) 2026-05-14 05:59:40 +02:00

SecuBox-DEB — Remote UI

Interfaces utilisateur déportées pour SecuBox.


Modules

Module Description Hardware
common/ Shared core (JS/CSS/icons/shell) consumed by round/ + square/ hardware-independent
round/ Eye Remote Dashboard HyperPixel 2.1 Round + Pi Zero W
square/ Planned: dual-pane kiosk (round UI + native right column) Pi 4B / Pi 400 + 7" 800×480 (issue #127)

Round Edition

Dashboard kiosk circulaire 480×480 pour monitoring SecuBox.

Features

  • 6 anneaux de status (AUTH, WALL, BOOT, MIND, ROOT, MESH)
  • Transport OTG prioritaire, WiFi fallback
  • Mode simulation intégré
  • USB gadget composite (ECM + ACM + mass_storage)

Documentation

Voir round/README.md pour la documentation complète.


Build Scripts

Script Description
round/build-eye-remote-image.sh Build image Pi Zero W
round/build-storage-img.sh Build storage ESPRESSObin
round/install_zerow.sh Flash et config SD
round/deploy.sh Déployer dashboard

Pipeline complet

# Build complet (depuis racine du repo)
sudo bash build-eye-remote-full.sh

Cela exécute :

  1. Build des packages SecuBox (.deb)
  2. Build de storage.img ESPRESSObin
  3. Build de l'image Pi Zero W

Architecture

remote-ui/
├── round/                      ← Eye Remote Dashboard
│   ├── agent/                  ← Agent multi-SecuBox (v2.0)
│   ├── files/                  ← Fichiers système à installer
│   ├── host-install/           ← Scripts côté host SecuBox
│   ├── scripts/                ← Utilitaires
│   ├── build-eye-remote-image.sh
│   ├── build-storage-img.sh
│   ├── deploy.sh
│   ├── install_zerow.sh
│   ├── CLAUDE.md              ← Instructions Claude Code
│   └── README.md              ← Documentation complète
└── README.md                  ← Ce fichier

Planned Modules

Module Status Description
square/ Planned Dashboard rectangulaire 800×480
web/ Planned WebUI responsive
console/ Planned TUI ncurses

See Also


Author

Gerald KERMA devel@cybermind.fr