Skip to content

Latest commit

 

History

History
245 lines (198 loc) · 18.4 KB

File metadata and controls

245 lines (198 loc) · 18.4 KB

Architecture

HackyLens 0.4.0 is modular firmware built from common firmware/src layers, the selected boards/<id> BSP, and the K210 HAL/startup under platforms/k210.

HackyLens v0.4 is a layered K210 reference firmware and MicroPython technology preview.

This is the current architecture and implementation status. Roadmap contains product priorities; technical references describe public interfaces. Historical plans and decisions remain in Git history.

The project aims to reuse feature logic across K210 boards, compose firmware from isolated native apps, and share hardware services with MicroPython. Portability and Python-to-native migration must be demonstrated with real applications, measurements and physical ports. They are not established by the number of interfaces or by compiling a second board descriptor.

Native feature apps       MicroPython scripts
        |                         |
   App runtime              Python bindings
        +------------+------------+
                     |
              Typed services
                     |
              Drivers / K210 HAL
                     |
              Selected board BSP

Native apps are statically linked C/C++ code; MicroPython executes scripts. There is no runtime native loader, manifest parser or provider discovery. Camera/KPU/vision are not currently exposed by MicroPython API v1.

Runtime and hardware service ownership

All twelve apps use start/event/render/stop, one foreground switch and one build-generated immutable registry. Render is optional and separate because runtime owns Display transactions. App identity and persisted autostart IDs do not depend on menu order. Existing bundled portable firmware services are not therefore standalone public SDK APIs.

Time and Input have immutable board-lifetime bindings. Input retains one sampler, debounce state and event ring with independent reader cursors. Lights uses stable channel sessions; Display uses stable plane sessions and transactions; External Link owns exclusive mode, incremental operations and IRQ handoff. Native code and MicroPython use the same production implementations. The old generic broker, owner/grant/lease tables and inventory generator are removed. Build-time service selection checks board resources/routes and app requirements; absent optional services have null accessors.

Runtime prepares BASE before app start. Failed preparation and failed stop both reach cleanup. Teardown creates one absolute deadline and passes it unchanged to every scoped retirement, attempts all cleanup, retains the first error and invalidates state even after expiry. Failed safe-off quarantines only the relevant resource. Session storage must not move or be copied while claimed. Context, frame, surface, workspace and asynchronous operation generations remain where they reject stale work. Persistent settings and MicroPython sessions have separate lifetimes; MP resources retire only after worker terminal handoff. Camera frame borrows, KPU completion and core1 executor ownership remain explicit.

Cooperative deadlines are not forced preemption of arbitrary C callbacks, and fixed buffers are not memory isolation for untrusted native code. New shared abstractions should serve a present use case and replace more complexity than they add. Ordinary changes update code, consumers, tests and relevant API docs; they do not require another ADR, evidence schema or governance checker.

firmware/targets/full.c is a small composition root. It configures the runtime loop in runtime/hk_main.c, whose input polling and sleep timing remain platform-dependent. core owns app contracts, screen model, dispatch contracts, and neutral data contracts such as core/pixel_source.h; it does not access hk_input or hal_time directly.

Startup

runtime/firmware_startup.c owns startup orchestration. It initializes the platform clocks and hardware through platforms/k210/startup/platform_bootstrap.c, loads persisted settings, applies brightness and illumination/RGB settings, initializes the boot controller, shows the boot screen, and mounts storage. The neutral autostart controller then opens the persisted registry target or falls back to the menu; it never includes feature headers.

platform_bootstrap is limited to board, HAL, LCD, and hardware-driver initialization. controllers/boot_controller.c registers shell callbacks, prepares the menu view, and writes the boot banner; feature view initialization belongs to each app's start callback.

Layer boundaries

  • drivers contain board-independent device APIs. The selected boards/<id> BSP owns board wiring and platforms/k210/hal owns K210 SDK access.
  • runtime adapts core lifecycle and startup to platform facilities.
  • controllers coordinate scenarios and pass state to services and UI views.
  • services own runtime operations such as camera sessions, settings application, debug console I/O, LCD screenshot sourcing, and screenshot UART streaming.
  • storage encodes and persists data. storage/screenshot_bmp.c only encodes BMP using a supplied screenshot_pixel_source_t; it never accesses the LCD or UART.
  • ui renders views through the typed Display session; raw LCD transport stays below its provider.

Screenshot UART output keeps the HKSHOT BEGIN BMP24 / HKSHOT END protocol and CRC. services/screenshot_source.c supplies the LCD shadow source, storage/screenshot_bmp.c encodes bytes, and services/debug_screenshot_stream.c sends them through services/debug_console_service.c.

tools/check_arch.py guards include boundaries, forbidden compatibility headers, SDK-token placement, include cycles, and feature-module ownership.

The shared AI platform is split across the normal layers. core/ai_model_types.h describes tensors, normalization, post-processing, and exact KModel contracts. storage/ai_model_storage.* owns aligned FAT32 loading plus the optional CRC-protected SD manifest. services/ai_model_runtime.* owns the single-KPU lease, descriptor/manifest/model-identity/output validation, asynchronous run timing, and deferred stop/unload state machine. platforms/k210/hal/hal_kpu.* remains limited to SDK and peripheral operations. None of these shared files knows about a camera, DVP capture policy, a feature app, or a particular post-processor.

Camera KPU consumers share one aligned 320x240 planar RGB input through services/camera_ai_input.*. Neutral conversion hooks in camera_stream arm the DVP AI output at frame start and freeze it at frame finish. KPU therefore receives a complete immutable frame with the exact camera sequence, instead of racing the next display capture. services/core1_executor.* similarly owns the one reusable core-1 loop and accepts one bounded job at a time; feature modules do not replace each other's core-1 entry point.

The shared settings_menu controller is an instance-based UI state machine driven by a constant item descriptor table and owner callbacks. Its passive view owns only LCD rendering. It has no camera, application, storage, persistence, or screen-navigation dependencies; owner controllers retain opening/closing lifecycle and all settings side effects. CAMERA, QR, APRILTAG, OBJECT DETECT, and the system SETTINGS app supply separate descriptor adapters. Cycle-on-OK and edit-on-OK rows share navigation, partial redraw, hold-repeat, commit lifecycle, and optional dynamic choice providers without sharing their data models.

Backlight, illumination, and RGB writes cross the public Lights capability. Its fixed K210 provider owns exclusive overlapping channel masks, validates cancellation/deadlines before register writes, and performs safe-off cleanup. Settings and temporary camera/MicroPython policy remain above that provider; only the K210 adapter includes the lights driver interface.

Display 0.1 is implemented by the production K210 provider and the deterministic fixed-capacity host fake. The provider owns BASE/OVERLAY plane sessions, bounded batch/surface staging, clipped dirty regions, borrowed-buffer lifetime, present retry, repair, and cleanup. A private UI binding holds the stable typed BASE session; MicroPython holds OVERLAY. Both reuse the one ST7789 shadow framebuffer over a raw transport that has no app, run-ID, or Python policy. The provider reports composition-specific bounded batch limits: the full profile reserves the retained MicroPython canvas, while a MicroPython-disabled composition keeps a minimal truthful batch implementation and does not reserve that absent consumer's command/text budget.

Feature modules

All twelve menu applications are self-contained modules: apps/terminal/, apps/camera/, apps/qr_camera/, apps/face_detect/, apps/apriltag/, apps/object_detect/, apps/micropython/, apps/files/, apps/buttons/, apps/pong/, apps/settings/, and apps/sleep/. Each owns its app entry point, controller, view, icon, feature configuration, and feature-specific state/services. The only public header of a module is its *_app.h, and the app implementation owns its typed lifecycle entry. Generated registry code uses typed extern entry objects and does not include app-private headers.

The build manifest maps each app ID to its whole directory. --disable-app therefore removes every source and private header of that feature. Shared camera sources remain while CAMERA, QR-CAMERA, FACE DETECT, APRILTAG, or OBJECT DETECT is enabled; quirc is staged only for QR-CAMERA. The planar AI input is staged only for FACE/OBJECT. The shared core-1 executor is retained while APRILTAG or MICROPYTHON needs it. With no camera consumer, sensor/DVP/camera runtime sources are omitted while the general KPU HAL remains available.

Canonical manifests generate the immutable descriptor and menu arrays. The small app-neutral core registry dispatches primary and secondary screen ownership, menu icons and explicit debug commands. The single foreground runtime dispatches lifecycle callbacks, timer and SD events; inactive apps do not receive background ticks. Shared screen, SD, debug, boot, and system-tick controllers do not include feature headers or select features with conditionals.

CAMERA owns photo capture orchestration, encoders/writers, photo paths, settings adapter, and its view. QR-CAMERA owns quirc integration, luma conversion, result state/view, text persistence, settings, and its view. Both reuse the shared camera session, sensor/frame pipeline, camera preview renderer, and settings persistence.

FILES owns browser navigation/state, previews and deletion, all BMP/PNG/PPM/RAW/GIF decoders, and its view. Shared FAT32 provides neutral mount, directory scan, file, allocation, and stream contracts and has no dependency on FILES browser state. BUTTONS, system SETTINGS, and SLEEP likewise own their controllers and views; the shared auto-sleep controller observes input activity without polling inactive apps.

FACE DETECT owns its YOLO detector adapter, view, icon, and debug command. The feature supplies a constant model descriptor and keeps all face-specific output decoding, while generic aligned storage, KPU ownership, DVP frame handoff, output validation, timing, and deferred unload belong to shared services. Its model remains /hackylens.kmodels/detect.kmodel on the SD card and is not embedded in firmware flash.

APRILTAG owns its TAG36H11 detector adapter, grayscale downsampler, settings/selection model and descriptor adapter, stabilized in-frame result overlay, icon, and HKTAG/HKTAGINFO commands. Its descriptor adapter uses the shared camera-independent settings_menu, while APRILTAG retains persistence and camera pause/resume policy. It reuses the shared 320x240 camera runtime and publishes native tag IDs through vision_result_service as BLOCK results. Core 0 downsamples a leased camera frame only when the single uncached 160x120 luma handoff and shared core-1 executor are free, then immediately releases the frame. Frames seen while detection is busy are discarded instead of queued, preventing stale-result latency. Core-1 create/detect/destroy jobs atomically publish completed result banks and leave the executor available to other apps. refine_edges is a persisted runtime request applied before the next detection rather than a detector restart. The app uses camera session overrides for its independent FPS and LED/RGB profile, leaving CAMERA and QR settings unchanged. Its ALL/SELECTED filter is applied before the shared result snapshot, so the UART/I2C wire format remains unchanged. Preview overlays are composed into the LCD shadow before the full-frame transfer, so rectangles are not temporarily erased by the following camera frame. The detector does not use a KPU model. The BSD-licensed OpenMV AprilTag core is staged from firmware/third_party/apriltag only when this feature is enabled.

OBJECT DETECT owns the VOC20 labels, YOLOv2 decoder/NMS, settings adapter, overlay, icon, and HKOBJECT/HKOBJECTINFO commands. Its pinned KModel v3 accepts planar 1x3x240x320 bytes and returns 1x125x7x10 floats. KPU inference is asynchronous; completed output is decoded on core 0 before a new DVP input is armed, avoiding K210's non-coherent cross-core cache boundary. Newer camera frames are discarded rather than queued. Double result banks and session epochs prevent partial or pre-settings results from becoming visible. Boxes are composed before the single LCD present and published as the existing transport-neutral BLOCK format with class IDs 0..19.

Terminal owns its bounded line ring, viewport, scrolling, font geometry, and log-sink lifecycle. The shared logging service exposes only a generic optional sink and remains independent of Terminal. Font selection remains in reserved feature bits inherited from the legacy settings payload, so old records and erased flash continue to decode as TERMINAL_FONT_NORMAL.

Settings record v5 retains the v4 data layout and widens the stable autostart ID from uint8 to uint16 without increasing the aligned 124-byte record. The 88 opaque app bytes remain unchanged: bytes 0..79 contain APRILTAG preferences and its 587-bit selected-ID map; OBJECT DETECT owns bytes 80..87. The storage layer validates and migrates v1, v2, v3, and v4 records. V4 autostart values are zero-extended, so existing IDs 0..10 and all CAMERA, external-link, APRILTAG, and OBJECT data remain unchanged.

Architecture guard

tools/check_arch.py uses one declarative table for all twelve feature directories. It rejects legacy paths, flat app implementations, external inclusion of private feature headers, private settings-menu view access, layer inversions, include cycles, a mismatch between feature directories and the build manifest, app access to AI storage/HAL internals, and camera/feature dependencies in the shared AI platform.

Supported hardware and current qualification

SEN0305 is the physically accepted runtime port. Maix Cube is descriptor/BSP compile conformance only: its full runtime, packaging and flashing are not qualified. Even a future second K210 port would not establish portability across arbitrary MCU families. Every build explicitly selects a board; its board.toml owns wiring, defaults, flash layout and programming metadata.

Accepted firmware 8527aab is installed on COM10. Full raw image is 1,545,272 B; static RAM (data + bss) is 2,893,216 B. The MicroPython-disabled raw image is 1,353,528 B. Full raw SHA-256 is 811be8cdc6ea18b66fa1d657e1c3850de568ae1e58c76a8408b1306e22bbf18b. Against accepted S7 0405a09, this saves 21,120 B flash and 5,256 B static RAM. The 227-test suite, both builds, architecture/provider evidence and resource guard passed; CI for documentation HEAD 1f304e4 passed in run 35564255150.

The consolidated acceptance includes user-confirmed QR, Input, Lights, Display, Sleep and FILES/GIF; the user confirmed remaining UART/I2C exchange on 2026-09-21. Automated device checks cover boot, settings/camera/Pong/menu switches, four MP terminal paths and an external MP OVERLAY run surviving native SETTINGS → MENU switches. CAMERA present was observed near 31 ms; this is a smoke observation, not a matched-workload latency distribution. No repeat of unaffected accepted checks is needed for documentation/tooling cleanup.

Heavy GIFs can still play below nominal speed; button response may wait for a frame to finish. Original-firmware parity, long-run qualification, a physically qualified second board, matched Python/native migration experiments and broader MicroPython hardware APIs remain product/research work. User UART/I2C acceptance is not an instrumented throughput or electrical-characterization dataset.

A useful Python-to-native experiment separates domain state from hardware, implements equivalent behavior through these shared services, then compares correctness, code changes, latency and memory under the same fixtures. No skeleton generator or project manager is a prerequisite.

Simplification resource comparison

The starting revision 71ec63fffe2926642fba00ff8d28519fd93750d9 was rebuilt with the pinned K210 toolchain for this comparison. Full raw image: 1,562,168 B; static RAM: 2,860,328 B. The accepted final firmware is 16,896 B smaller in flash and uses 32,888 B more static RAM. This is not an across-the-board RAM reduction. Symbol comparison attributes the main additions to 12 × 1,024 B bounded app state and 25,744 B of QR decoder scratch moved from stack to static storage; removed broker/runtime tables offset part of that growth. The QR scratch change addresses the working decoder path and stack pressure; it is not governance metadata or a new full framebuffer. Static RAM alone does not measure peak stack/heap usage. Older plans and exact resource records can be retrieved from Git history; current budgets remain executable in tools/check_resources.py.

Current validation workflow

The documentation/tooling cleanup leaves the accepted firmware source unchanged. The current host suite has 218 tests; removed cases checked retired historical phase schemas and provenance, not the surviving service behavior. Normal-push CI builds full SEN0305 and MicroPython-disabled images and checks their architecture and resource limits. tools/package_release.py --board huskylens-sen0305 validates image/sidecar/attestation identity and creates the release package locally; it does not publish a release. Exact source and workflow status are available in Git and CI, without another rolling qualification schema. Hardware acceptance above carries forward only while the corresponding implementation and image are unchanged. Matched original-versus-final latency distributions remain a research measurement, not a result inferred from reduced source size.