Commit Graph
58 Commits
Author SHA1 Message Date
Hermann Björgvin c25e89672a Merge pull request #27 from orbisai0security/fix-ble-rx-buf-bounds-check
fix: add buffer-length check in ble.cpp
2026-05-21 19:52:00 +00:00
orbisai0security 5079ea1af4 fix: V-002 security vulnerability
Automated security fix generated by OrbisAI Security
2026-05-21 10:04:37 +00:00
Hermann Björgvin 09b5ba8c72 Updated affiliate link 2026-05-21 09:47:01 +00:00
Hermann Björgvin 7ae95a83ab Merge pull request #25 from tobby168/upstream-refactor
Add AMOLED-1.8 board + device-abstraction refactor for community ports
2026-05-21 01:55:22 +00:00
tobby168andClaude Opus 4.7 20351212b2 Device-abstraction refactor: HAL + per-board folders + responsive UI
Replaces the build-flag-driven #ifdef sprawl (~30 blocks across 6 files)
with a small HAL in firmware/src/hal/ and per-board folders under
firmware/src/boards/. Shared code (main.cpp, ui.cpp, splash.cpp) no
longer contains a single `#ifdef BOARD_*` — optional features are
guarded by BoardCaps (runtime) and BOARD_HAS_* macros (compile-time,
inside the board's own files).

Why: lets community contributors port to new ESP32 + AMOLED + touch
combos by dropping in a boards/<name>/ folder + a PlatformIO env,
without touching shared files. See docs/porting/adding-a-board.md.

Highlights:

- New HAL: display_hal, touch_hal, input_hal, power_hal, imu_hal,
  board_caps. Each board provides display.cpp, touch.cpp, input.cpp,
  power.cpp, imu.cpp, caps.cpp, board_init.cpp + private hardware
  drivers (e.g. io_expander.{h,cpp} on AMOLED-1.8).
- PlatformIO build_src_filter selects each board's folder per env.
- ui.cpp picks fonts and layout from board_caps() via compute_layout()
  with screen-height breakpoints (>= 460 → large, else compact).
- splash.cpp computes CELL = min(W,H)/20 — responsive instead of two
  hardcoded values.
- idle.cpp (from #24) rewired through display_hal + power_hal — no
  longer depends on the deleted display_cfg.h / power.h.
- power_hal gains power_hal_is_vbus_in() for idle's
  IDLE_SLEEP_WHEN_CHARGING gate.
- boards/template/ + docs/porting/{adding-a-board,hal-contract,
  capability-flags}.md to bootstrap new ports.
- display_cfg.h, power.{h,cpp}, imu.{h,cpp}, io_expander.{h,cpp}
  deleted from src/ root (moved into boards/<name>/ or hal/).

Verification: both `pio run -e waveshare_amoled_216` and
`pio run -e waveshare_amoled_18` succeed unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:27:24 -07:00
tobby168andClaude Opus 4.7 f3ed2425bf Add Waveshare ESP32-S3-Touch-AMOLED-1.8 (368×448) board support
Port the firmware to a second Waveshare AMOLED board variant alongside
the original 2.16" / 480×480. Selection is at build time via a board
macro; both envs continue to build and flash independently.

Hardware delta vs AMOLED-2.16:
  * Display: SH8601 QSPI (vs CO5300), 368×448 portrait (vs 480² square),
    SCLK on GPIO 11 (vs 38), RST routed through an XCA9554 I/O expander
    (vs direct GPIO).
  * Touch: FT3168 @ 0x38 (vs CST9220 @ 0x5A). Driven by a ~30-line
    inline FocalTech-protocol reader in main.cpp — Waveshare's
    Arduino_DriveBus library is GPLv3 and would force the project's
    license, which the README explicitly avoids.
  * I/O expander: new XCA9554/PCA9554 @ I2C 0x20 gates LCD_RST, TP_RST,
    audio amp enable, and the PWR button. Wrapped in io_expander.{h,cpp}.
    Must initialize BEFORE display/touch or both stay in reset.
  * PMU + IMU: same AXP2101 + QMI8658 as 2.16, so power.cpp/imu.cpp
    largely reuse. IMU is initialized for I2C bus health but rotation
    is fixed at 0° (the portrait panel doesn't benefit from auto-rotate
    and the strip-rotation CPU path is excluded entirely).
  * Buttons: only BOOT (GPIO 0) is a direct GPIO; PWR is read via
    XCA9554 EXIO4 polling. No third button — the AMOLED-2.16's
    Shift+Tab key (GPIO 18) is dropped for this board.
  * Flash: 16MB (vs 8MB), partition table set to default_16MB.csv.

Code structure:
  * display_cfg.h hosts a #ifdef BOARD_AMOLED_18 / #else / #endif split
    with a PlatformDisplay typedef so call sites elsewhere stay clean.
  * main.cpp, power.cpp, ui.cpp, splash.cpp gate board-specific bits on
    the same macro. Layout constants (panel heights, content Y, font
    choices on the Bluetooth screen) compress for the 368×448 portrait
    footprint so two usage panels + bottom animation label fit without
    horizontal overflow.
  * Splash scales the 20×20 pixel-art grid to 360×360 (CELL=18 vs 24)
    and centers in the portrait canvas.
  * Old AMOLED-2.16 env is unchanged at the source level (gated under
    #ifndef BOARD_AMOLED_18) and continues to compile + flash on the
    original hardware.

CLAUDE.md updated with the two-board pin maps, build/flash commands
for both envs (macOS + Linux device paths), and the per-board gotchas.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:19:03 -07:00
Hermann Björgvin 57e5d73c6e Merge pull request #24 from tobby168/feature/auto-sleep
Auto-sleep screen after 30 min idle
2026-05-20 17:36:20 +00:00
tobby168 e0829cebdf idle: add IDLE_SLEEP_WHEN_CHARGING and IDLE_WAKE_ON_TOUCH
Addresses review feedback on #24:

- New config IDLE_SLEEP_WHEN_CHARGING (default false): while USB is
  plugged in we don't enter sleep and we wake immediately if power
  comes back. Covers both "I like watching Clawd animations on my
  plugged-in desk device" and "alternative hardware without a battery
  is always on USB, sleep would be odd."

- New config IDLE_WAKE_ON_TOUCH (default true): a touch on the dark
  panel wakes the device. The first touch is swallowed (mirrors the
  button wake-consumption), so tapping a dark screen wakes without
  also toggling splash<->usage. Set false to keep the original
  pets/sleeves-safe behaviour where touch is fully ignored while
  asleep.

power.{h,cpp} grow a power_is_vbus_in() helper backed by
pmu.isVbusIn() — distinct from isCharging() so the no-battery case
("plugged in, nothing to charge") is detected correctly.
2026-05-19 20:39:03 -07:00
tobby168 6655b0f6d0 firmware: auto-sleep screen after 30 min idle
AMOLED brightness fades to 0 after IDLE_TIMEOUT_MS without a physical
button press; any of the three buttons (BTN_BACK / BTN_FWD / PWR) fades
it back in. The first press from sleep is consumed for wake only —
Space / Shift+Tab / cycle-screen only fire on the second press, so
reaching for the device to glance at it doesn't accidentally send PTT
to Claude.

Touch never counts as activity and is fully swallowed while asleep:
pets and sleeves can't wake it, and LVGL can't secretly toggle
splash<->usage behind a black panel.

Rotation handling defers while asleep so its blank+ramp doesn't fight
the idle fade; a rotation that happens during sleep is detected and
ramped in on wake.

All tunables live in idle_cfg.h (timeout, fade durations, default
brightness) — nothing is hard-coded in main.cpp.

AMOLED brightness=0 is true 0 emission on these self-emissive panels,
so no extra power-management plumbing (light-sleep, AXP rail gating)
is needed to get the battery savings.
2026-05-19 12:41:49 -07:00
Hermann Björgvin 9ff81966fb Merge pull request #21 from georgetasioulis/fix-ble-state-on-reconnect 2026-05-18 18:33:10 +00:00
George TasioulisandClaude Opus 4.7 0b47a54956 Keep BLE UI state at CONNECTED while re-advertising
After #18 (MAX_CONNECTIONS=2), onConnect sets need_advertise=true to
fill the second slot. start_advertising() then unconditionally overwrote
state to ADVERTISING, so with a single connected client the UI flipped
CONNECTED -> ADVERTISING on the next tick and stayed there until a
second central attached.

Only flip the state to ADVERTISING when no clients are connected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:59:16 +03:00
Hermann Björgvin baed1d722b Merge pull request #18 from tobby168/macos-ble-compat 2026-05-18 00:46:19 +00:00
tobby168andClaude Opus 4.7 f252a871c4 Make BLE pairing work on macOS, allow daemon + HID to coexist
macOS Bluetooth quirks made the original BLE setup unusable from a Mac
host. Five focused changes to fix discovery, pairing, the keyboard
identification wizard, and concurrent connections, plus a couple of
cross-platform daemon/script niceties.

Firmware (ble.cpp):
  * Advertise the standard HID Service UUID (0x1812) in the primary
    packet. Without it, macOS Sequoia's Bluetooth Settings GUI
    recognizes the device internally but silently hides it from the
    "Nearby Devices" list. Service UUIDs >16-bit overflow the 31-byte
    advertising packet, so the custom data-service UUID moved to the
    scan response.
  * Switch PnP ID from Apple's USB vendor (0x05AC + Magic Keyboard
    PID 0x820A) to Espressif's BT SIG vendor (0x02E5). macOS validates
    Apple-claimed HIDs against known device IDs and refuses to surface
    a Connect button for spoofers.
  * Add the LED output report (Num/Caps/Scroll Lock) to the HID
    descriptor — macOS treats a keyboard descriptor without LEDs as
    "incomplete" and triggers the Keyboard Setup Assistant repeatedly.
  * Set HID country code to 33 (US ANSI) instead of 0 (Not Supported)
    so macOS can identify the layout without asking the user.
  * Bump CONFIG_BT_NIMBLE_MAX_CONNECTIONS to 2 and restart advertising
    after each accept. macOS holds one connection for the HID keyboard
    link; the daemon now gets its own slot for the data service in
    parallel, instead of either side starving the other.

screenshot.sh: auto-pick /dev/cu.usbmodem101 on macOS vs /dev/ttyACM0
on Linux, fall back to PlatformIO's bundled Python if pyserial isn't
on the system Python (PEP 668 blocks `pip install` on Homebrew Python),
and pass the actual framebuffer dimensions to ffmpeg instead of
hardcoding 480x480.

daemon/claude_usage_daemon.py: log API HTTP status + response body on
4xx/5xx so silent token-expiry failures (the daemon was reporting
{"s":0,"w":0} payloads instead of surfacing a 401) are visible in the
daemon log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:56:05 -07:00
Hermann Björgvin e3709b66e7 Update README.md 2026-05-14 19:01:56 +00:00
Hermann Björgvin 78c3a43c53 Update README.md 2026-05-14 19:01:23 +00:00
Hermann Björgvin 2774ae846e Merge pull request #6 from HermannBjorgvin/macos-support
Add macOS host support
2026-05-14 18:40:43 +00:00
512039e740 Add macOS host support
Cherry-picks the macOS-specific pieces from PR #5: Python daemon using
bleak (CoreBluetooth backend), LaunchAgent template, flash-mac.sh, and
install-mac.sh. Token is read from the macOS Keychain ("Claude
Code-credentials" service); Linux behavior is unchanged. README split
into "macOS installation" and "Linux installation" sections with
parallel Flash / Pair / Install subsections.

Co-Authored-By: Chris Davidson <36679917+lorddavidson@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 18:39:13 +00:00
Hermann Björgvin 9aa6954d42 Merge pull request #4 from BlockedPath/main 2026-05-14 15:50:52 +00:00
Hermann Björgvin 8ceb9f6e4f Update README.md
Added affiliate link
2026-05-14 12:23:27 +00:00
justin c2bd57a300 Fix formatting in CLAUDE.md for code block and add missing line breaks 2026-05-14 01:11:15 -04:00
Hermann Björgvin 40d7af93d8 Update README to reflect changes in demo and animations
Removed demo section and added attribution for Clawd animations.
2026-05-12 01:53:20 +00:00
Hermann Björgvin 4c9363440a Update README.md 2026-05-12 01:51:53 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 bd2afb544f Swap demo video link for inline GIF in README
GitHub's clickable-thumbnail trick worked but was awkward; a 500x500
12fps GIF renders inline everywhere. Drop the now-unused thumbnail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 17:05:36 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 7281fb3c2f Use clickable thumbnail for demo video in README
GitHub strips relative src on <video> tags, so swap to an image link
pointing at the raw mp4. Extracted a frame for the thumbnail so the
two demo cells aren't visually identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 17:02:12 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 11dfa3e4b9 Add Demo section to README with photo and video
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 16:57:49 +00:00
Hermann Björgvin Haraldsson 7736ef1bab Capitalize Clawdmeter and extend tagline 2026-05-11 03:29:34 +00:00
Hermann Björgvin Haraldsson 48783bfb4b Drop open/public from library references in README 2026-05-11 03:10:50 +00:00
Hermann Björgvin Haraldsson 4e56ba4749 Rename to clawdmeter, link claudepix from splash section 2026-05-11 03:09:41 +00:00
Hermann Björgvin Haraldsson 2f40906816 Humanize README prose across remaining sections 2026-05-11 02:56:09 +00:00
Hermann Björgvin Haraldsson 1635729486 Humanize README intro paragraph 2026-05-11 02:51:29 +00:00
Hermann Björgvin Haraldsson 1a8c4038aa Rename creatures to Clawd in README and drop middle name 2026-05-11 02:44:36 +00:00
Hermann Björgvin Haraldsson b48a11c62f Correct Clawd attribution: singular animation, x.com link, mascot wording 2026-05-11 02:43:01 +00:00
Hermann Björgvin Haraldsson 3aa857f3c4 Capture Usage screenshot with live session/weekly data 2026-05-11 02:37:58 +00:00
Hermann Björgvin Haraldsson 62b226b793 Move Screens section above Features in README 2026-05-11 02:36:41 +00:00
Hermann Björgvin Haraldsson f92e1bba3e Remove stale demo.gif from previous Panlee panel 2026-05-11 02:35:43 +00:00
Hermann Björgvin Haraldsson 4b32236f67 Refresh README screenshots and prune controller-era references
Replace the 480x320 Panlee-era usage/bluetooth/controller PNGs with
fresh 480x480 captures of the current UI (Usage, Bluetooth, Splash).
Drop the Controller screen column entirely — that screen was removed
when buttons took over its shortcuts.

README cleanup: daemon poll cadence corrected to 60s, splash mood-
group + auto-rotation behaviour documented, "tap the logo to cycle"
replaced with the actual middle-button gesture, Lucide section
rewritten to point at tools/png_to_lvgl.js instead of the dead inline
Python+inkscape recipe, and intro line drops "touch controller" since
touch only toggles splash now. demo.gif flagged as stale (recapture is
a hardware task).
2026-05-11 02:33:29 +00:00
Hermann Björgvin Haraldsson 68a99cfb87 Extract make_usage_panel helper for Session/Weekly
The two panels in init_usage_screen() differed only in y-offset, pill
text, and which globals received the children — same widget tree, same
sizes, same positions. Fold into make_usage_panel() so the layout intent
shows up in two call sites instead of being duplicated by hand.

Verified by pixel-diff: pre- and post-refactor screenshots of the Usage
screen are byte-identical.
2026-05-11 02:26:18 +00:00
Hermann Björgvin Haraldsson c2f669e39f Move rotation-detect + brightness-ramp into one helper
The detect-rotation/blank/redraw block and the brightness step-up block
were two stateful chunks in loop() glued by a brightness_ramp global.
Fold them into handle_rotation_change() with local static state — the
'on rotation change, flash to black then ramp back' invariant lives in
one place and the loop body shrinks.
2026-05-11 02:22:33 +00:00
Hermann Björgvin Haraldsson d8592f7a03 Gate touch_read on ISR flag instead of double-polling
touch_read() had two branches that both called getPoint() unconditionally
— the touch_data_ready flag served no purpose. Gate the I2C transaction
on the flag so the ISR finally pulls its weight: idle loops no longer
hit I2C, getPoint() runs only when the CST9220 actually raised the
TP_INT line.

Also scope touch_pressed/x/y to file-static (no cross-TU users) and
drop the stale 'permanent while we work on it' comment by the default
boot screen — splash-on-boot is documented in CLAUDE.md.
2026-05-11 02:21:20 +00:00
Hermann Björgvin Haraldsson f1feacb3dc Remove dead functions and unused Montserrat font flags
Six public API functions had no callers anywhere in the firmware:
splash_prev, splash_set_active, splash_current_name, splash_count,
ble_is_connected, imu_set_rotation, power_is_usb_connected,
power_usb_changed. Trimming them lets power.cpp drop the VBUS state
machine (cached_usb, usb_changed_flag, last_vbus_ms,
enableVbusVoltageMeasure). usage_rate_reset becomes file-static since
the only caller is in usage_rate.cpp itself. Montserrat font flags in
platformio.ini were enabled but never referenced.
2026-05-11 02:19:45 +00:00
Hermann Björgvin Haraldsson 7ed4710d28 Drive splash animations by session usage rate
Group splash animations into four moods (idle/normal/active/heavy) and pick
which group plays based on a 5-min sliding rate of session_pct. Thresholds
sit at 0.10/0.20/0.33 %/min so Heavy fires when usage matches or beats the
5-hour session reset pace. Splash auto-rotates within the current group
every 20s; group recomputes on every BLE poll and the splash re-picks
immediately if it changes mid-display. 4-min minimum window prevents
single-sample bumps after boot from falsely landing in Heavy.
2026-05-11 02:05:38 +00:00
Hermann Björgvin Haraldsson 12172d12bf Hide battery indicator on splash screen unconditionally 2026-05-11 01:06:37 +00:00
Hermann Björgvin Haraldsson b61936b42c Centralize colors in theme.h and propagate true-black background
- Extract design tokens to firmware/src/theme.h (THEME_BG, THEME_PANEL,
  THEME_TEXT, etc.); ui.cpp aliases COL_* to the THEME_* tokens
- Splash container + canvas COL_EMPTY now use THEME_BG / 0x0000 instead
  of the hardcoded 0x0f0f0f / 0x10A2 dark grey
- tools/convert_to_c.js maps "transparent" palette entries to 0x0000
  (true black) so regenerated splash_animations.h matches the new bg
- Re-regenerate splash_animations.h
- Convert logo to RGB565A8 (alpha plane) so it composites against any
  background — no more grey halo around the upper-left logo on screens
  where the bg differs from the previous baked-in 0x18E3 color
2026-05-11 01:04:11 +00:00
Hermann Björgvin Haraldsson b34d99def8 Use true black (#000) for screen background
AMOLED pixels turn fully off, saving power and deepening contrast
against COL_PANEL (0x1f1f1e).
2026-05-11 00:54:04 +00:00
Hermann Björgvin Haraldsson 7fc8ed721a Remove Controller screen, global button shortcuts, touch-toggled splash
UX overhaul:
- Drop the Controller screen entirely (touch zones, swipe gestures, HID
  gesture engine, four icon arrays, hand icon)
- Two persistent screens (Usage, Bluetooth) cycled by the middle PWR
  button; splash becomes a touch-toggled welcome animation
- Left button (GPIO 0): hold to send Space (Claude Code voice-mode PTT)
- Right button (GPIO 18): press to send Shift+Tab (mode toggle)
- Middle button on splash cycles animations instead of screens
- Remove USB plug/unplug auto-switch (Controller was the unplug target)
- Hide battery indicator on splash when in LOW state — it's noisy over
  the pixel-art animations

Implementation:
- Delete firmware/src/hid.{cpp,h} and touch.{cpp,h}; their roles fold
  into main.cpp button handlers and LVGL CLICKED events respectively
- splash_get_root() exposes the splash container so ui.cpp can attach
  a click handler for the dismiss-on-tap behavior
- Tap detection uses LVGL's built-in LV_EVENT_CLICKED with event
  bubbling on the inner panels — debouncing is handled by LVGL
- On Bluetooth screen, only the Reset Bluetooth zone is clickable;
  taps elsewhere are no-ops (no conflict with splash toggle)

Docs:
- README: drop Controller from screens table, rewrite Physical buttons
  table, remove Gesture controls section
- CLAUDE.md: update file map, button assignments, drop gesture refs
2026-05-11 00:51:07 +00:00
Hermann Björgvin Haraldsson 90eaeec35b Add credits section to README 2026-05-11 00:17:54 +00:00
Hermann Björgvin Haraldsson 68a31b189b Add @amaanbuilds animation attribution to Bluetooth screen 2026-05-11 00:16:56 +00:00
Hermann Björgvin Haraldsson 36db239569 Stack gesture and action on separate lines with color hierarchy
Each gesture hint now wraps its gesture in white (COL_TEXT) and the
action in dim gray (COL_DIM), stacked vertically. Replaces the prior
single-line "Gesture: action" format that wrapped at arbitrary points.
2026-05-10 01:58:58 +00:00
Hermann Björgvin Haraldsson 0625d99617 Simplify Controller info row and wrap gesture hints
- Drop "of current session" trailing text from the percentage label so
  it no longer overlaps the right-aligned reset countdown
- Shrink hint font from styrene_24 to styrene_20 and enable WRAP mode
  with center alignment so each hint fits the narrow center zone
2026-05-10 01:52:03 +00:00
Hermann Björgvin Haraldsson b4e557c681 Add @hermannbjorgvin attribution to Bluetooth screen 2026-05-10 01:33:00 +00:00