Commit Graph
39 Commits
Author SHA1 Message Date
David KohandClaude Opus 4.8 4e54363168 feat(amoled-18): auto-detect CO5300/CST816 panel revision
Waveshare ships two hardware revisions under "ESP32-S3-Touch-AMOLED-1.8":
the original (SH8601 display + FT3168 touch @0x38) and a later one
(CO5300 display + CST816 touch @0x15). On the later board the SH8601
driver left the panel blank and the FT3168 probe spammed
ESP_ERR_INVALID_STATE because nothing answers at 0x38.

Detect the revision at boot by probing the touch address and pick the
matching drivers, so one binary drives both boards:

- board_rev.{h}/board_init.cpp: probe 0x15 (CST816) vs 0x38 (FT3168)
- display.cpp: instantiate Arduino_CO5300 or Arduino_SH8601 behind a
  shared Arduino_OLED*; CO5300 gets a 16px column offset to center the
  368-wide image (verified on hardware)
- touch.cpp: one FocalTech-style reader (regs 0x02..0x06) for both
  chips, address chosen by revision
- board.h: add CST816_ADDR 0x15
- CLAUDE.md: document the dual revision

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:28:17 +08:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 65e51a0982 display: rotation flash ramps back to user brightness
The post-rotation brightness ramp targeted a hard-coded 200 (the old default),
which reset whatever the user had chosen via PWR-short-press after every IMU
rotation. Read brightness_get() instead and scale the 4-step ramp (30/60/85/100%)
toward that target.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 14:01:57 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 13cc4231a0 Rename BLE device "Claude Controller" → "Clawdmeter"
Brand consistency: the device now advertises as "Clawdmeter". Updated the
three functional spots that must agree for discovery-by-name to work —
ble.cpp DEVICE_NAME (advertised) and both daemons' DEVICE_NAME (scan match) —
plus the README/CLAUDE.md/install-script references (and fixed the stray
"Claudemeter" typo in the README).

Note: hosts cache the GATT name, so an already-paired device keeps showing the
old name until removed/re-paired (bluetoothctl remove <mac>).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 14:01:57 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 bd5d45cef3 splash: brand terracotta tint; README + remove BT screenshots
- convert_to_c.js: remap the claudepix body color (#CD7F6A) to Anthropic brand
  terracotta (#D97757) so the splash creature matches the brand; regenerate
  splash_animations.h.
- README: document the 3-second hold-to-pair gesture, drop the now-removed
  Bluetooth screen from the screenshots table + prose, and delete the
  Bluetooth screenshots (216 + 1.8").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 14:01:57 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 2b4a0802fa Collapse UX to single Usage view + hold-to-pair + status line
Removes the Bluetooth screen — the device now has just splash + Usage. Keeps
main's responsive Layout intact.

- ui: drop the Bluetooth screen, ui_cycle_screen, and SCREEN_BLUETOOTH. The
  bottom spinner becomes a status line — Connected / Disconnected / Pairing…
  (via ble_has_bonds()) plus the whimsical messages when connected and idle.
  A "To pair / hold the power button / for 3 seconds, then release" hint shows
  while disconnected so the screen isn't empty.
- main: hold-to-pair gesture (pair_tick) — hold PWR ~3s then RELEASE clears
  bonds and re-advertises. Clearing on release (not while held) means holding
  to power off (8s hardware shutdown) leaves the bond intact; disarms at 6s.
  PWR short-press now cycles brightness on the Usage view (animations on splash).
- ble: add ble_has_bonds() for the status line.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 14:01:57 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 ddb812ab5d brightness: user-cyclable level persisted to NVS, via idle
idle owns the panel brightness (it fades between a "full" level and 0), so
user brightness control routes through a new idle_set_awake_brightness():
idle now fades to a runtime awake level instead of the hard-coded
DISPLAY_DEFAULT_BRIGHTNESS, and the chosen level survives idle fades + wake.

brightness.{h,cpp} holds a 4-step ramp {64,128,200,255} (default 200 == the
old constant, so default behavior is unchanged) persisted to NVS, and applies
the level through idle. Wired to the PWR button in the next commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 14:01:57 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 98a513a403 HAL: add PWR long-press + release edges to power_hal
Adds power_hal_pwr_long_pressed() and power_hal_pwr_released() to the HAL
contract so shared code can implement a hold-to-pair gesture board-agnostically.

- 216: enable the AXP2101 PKEY LONG + POSITIVE IRQs; bump press-off to 8s so a
  ~3-6s pair hold doesn't trip the hardware shutdown.
- 1.8": synthesize the same long/release edges in software from the polled
  XCA9554 EXIO4 level (short-press now fires on release-if-short).
- template: stub the two new functions (no gesture) for new ports.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 14:01:57 +00:00
Alexander Wennerstrøm f5f43aa8aa fix(boards): Remove redundant AXP2101 charging configuration for Waveshare ESP32-C6-Touch-AMOLED-2.16
- These settings were causing conflicts with the on-chip fuel gauge reference configuration
- Simplify power initialization by relying on BSP defaults instead of overriding them
2026-05-24 09:48:35 +02:00
Alexander Wennerstrøm 9f75de55a2 feat(boards): Add secondary button support to Waveshare ESP32-C6-Touch-AMOLED-2.16
- Enable BTN_FWD_GPIO (GPIO 10) as secondary button for mode toggle (Shift+Tab)
- Update BOARD_HAS_SECONDARY_BUTTON flag from 0 to 1
- Clarify button layout: BOOT (primary PTT), PWR (cycle screens via AXP), KEY (secondary mode toggle)
- Document KEY GPIO discovery method (empirical scanning of unused GPIOs)
- Update button_count calculation to include secondary button when available
- Initialize both BTN_BACK_GPIO and BTN_FWD_GPIO in input_hal_init()
- Refactor input_hal_is_held() to use switch statement for cleaner multi-button handling
2026-05-24 09:47:31 +02:00
Alexander Wennerstrøm ab8b64d949 feat(boards): Add Waveshare ESP32-C6-Touch-AMOLED-2.16 support
- Add complete board support for Waveshare ESP32-C6-Touch-AMOLED-2.16 with SH8601 display driver
- Implement board initialization with AXP2101 PMU rail configuration for LCD and touch power
- Add display driver for SH8601 QSPI panel (480×480 resolution)
- Add CST9217 touch controller support via I2C
- Add QMI8658 IMU initialization and sensor reading
- Add AXP2101 power management and battery monitoring
- Add input handling for BOOT button (GPIO 9)
- Configure PlatformIO environment with C6-specific build flags and 16 MB flash layout
- Update capability flags documentation to clarify BOARD_HAS_PSRAM build-flag macro usage
- C6 has no external PSRAM; shared code gates on this flag to use internal SRAM and reduce LVGL buffer sizes
- Screenshot capture disabled on this board due to internal SRAM constraints
2026-05-24 08:36:53 +02:00
orbisai0security 5079ea1af4 fix: V-002 security vulnerability
Automated security fix generated by OrbisAI Security
2026-05-21 10:04:37 +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
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
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
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 Haraldsson b48a11c62f Correct Clawd attribution: singular animation, x.com link, mascot wording 2026-05-11 02:43:01 +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 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
Hermann Björgvin Haraldsson b0fc2b8233 Center page titles and refine Bluetooth screen layout
- Shift all three titles by +16 from screen mid so they center between
  the logo (right edge ~100) and battery icon (left edge ~412)
- Bluetooth screen: increase Reset zone height 70 -> 110 for breathing
  room around the trash icon + label
- Bluetooth screen: drop the "Bluetooth Low Energy" footer
2026-05-10 00:53:26 +00:00
Hermann Björgvin Haraldsson 8b5878724f Refine Usage screen typography and match Claude Code spinner easing
- Drop "Claude" prefix from titles, center all three
- Replace verbose "Current Session" / "Current Week" labels with rounded
  pills above the bar; reset countdown moves below the bar
- Extend bar to fill panel inner width (was 8px short)
- Symmetric vertical pill placement within panel
- Spinner: per-frame hold times + ping-pong indexing, modeled on
  Claude Code's Cavalry triangle oscillator (turn-around frames hold
  ~2x longer)
- Bottom spinner re-centered between weekly panel and screen edge
- screenshot.sh: 480x320 -> 480x480 for the new panel
- CLAUDE.md: document screenshot.sh + temporary boot-screen swap as
  the standard self-QA workflow for UI changes
2026-05-10 00:38:04 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 38d8894016 Make BLE daemon resilient to device swaps and add device-initiated refresh
- Cache invalidation: drop ~/.config/claude-usage-monitor/ble-address and remove
  the dead MAC from bluez on connect failure, so swapping ESP boards no longer
  pins the daemon to a stale factory MAC
- Scan robustness: pick first matching candidate (head -1), sanity-check cached
  content for valid MAC format, handle multiple Claude Controllers in bluez
- 5s tick + 60s poll: inner loop wakes every 5s for fast disconnect detection
  while keeping Anthropic API cadence at one minute
- Refresh-request channel: new GATT characteristic ...0004 NOTIFY, firmware
  fires once in onSubscribe when has_received_data is false; daemon subscribes
  via setsid'd dbus-monitor pipeline (process group cleanup avoids the
  bash-wait-on-job hang we hit on disconnect)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 23:44:39 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.7 97a5443601 Migrate to Waveshare ESP32-S3-Touch-AMOLED-2.16 with auto-rotation, splash, and battery
Full hardware swap from Panlee SC01 Plus (480×320 IPS) to Waveshare 2.16"
square AMOLED (480×480, CO5300 + CST9220 + AXP2101 + QMI8658). Library
stack moves to Arduino_GFX, SensorLib, XPowersLib on the pioarduino
platform 55.03.38-1 (Arduino Core 3.x).

UI:
- 4 screens (splash, usage, controller, bluetooth) with 3-button physical
  navigation: GPIO 0 = prev, AXP PKEY = cycle, GPIO 18 = next.
- IMU-driven 90° auto-rotation. CO5300 can't rotate via MADCTL, so flush
  callback does CPU strip-rotation in PARTIAL render mode. Rotation
  transitions use AMOLED brightness flash (instant black → redraw → ramp).
- Battery indicator (Lucide icons) in upper-right, RGB565A8 alpha so it
  blends over the splash animations.
- USB plugged/unplugged auto-switches between Usage and Controller
  screens (suppressed while on splash).
- Fonts and icons re-scaled ~1.9× for the higher-DPI panel; 20px margins
  to clear rounded corners.

Splash:
- 13 × 20×20 pixel-art creature animations sourced from
  claudepix.vercel.app via tools/scrape_claudepix.js (scraper handles
  both PRESET creature-engine and standalone FRAMES+PAL formats).
  tools/convert_to_c.js emits firmware/src/splash_animations.h.
  Attribution preserved in README and the generated header.

Tooling:
- tools/png_to_lvgl.js converts alpha PNGs to LVGL RGB565A8 (planar
  layout, with --tint to colorize Lucide black-on-transparent sources).
- tools/scrape_claudepix.js, tools/convert_to_c.js for the splash
  pipeline.

Docs:
- New worktree CLAUDE.md with hardware pin map, file inventory, build
  commands, and the 8 critical gotchas (CO5300 rotation, OPI PSRAM,
  pioarduino requirement, LVGL 9 font patching, centralized touch read,
  even-aligned flush regions, touch swap/mirror values, RGB565A8 layout).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 22:04:04 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.6 10b8052bcc Replace USB serial/HID with Bluetooth Low Energy
Move all communication from USB to BLE. The device now advertises as
"Claude Controller" and acts as both a BLE HID keyboard (for touch
gestures) and a GATT data server (for usage updates from the daemon).

- Add NimBLE-Arduino BLE module with custom GATT service + HID keyboard
- Add third screen (Bluetooth) with connection status, MAC, reset button
- Rewrite daemon in bash using bluetoothctl/busctl for BLE GATT writes
- Add screenshot capture via LVGL snapshot over serial
- Remove TinyUSB mode — normal pio upload works again
- Update README with BLE architecture, screenshots, gesture docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 19:30:19 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.6 930055170f Add two-screen UI with controller and Lucide icons
Two screens switchable by tapping the Claude logo:
- Usage screen: session and weekly utilization with animation
- Controller screen: touch gesture HID keyboard controls

Controller screen features:
- Lucide icons for ESC, DEL, left/right arrow buttons
- Gestures panel with hand icon header and hint list
- Session usage bar with reset timer at bottom
- Hold anywhere for voice dictation (space key)
- Hidden Ctrl+Space on logo hold

Also fixes stuck modifier keys with releaseAll() after each keystroke.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 19:15:36 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.6 94d55a88d2 Add USB HID keyboard input via touch gestures
The SC01 Plus now acts as a composite USB device (CDC serial + HID keyboard).
Touch gestures on the screen send keystrokes to the computer:

- Swipe up/down: arrow keys
- Swipe right: Enter
- Swipe left: Shift+Tab (change mode in Claude Code)
- Double-tap logo: Ctrl+Space (voice mode)
- Tap and hold: Space (held while touching)

Gesture-to-key mapping is defined as a simple config table in hid.cpp.
Touch detection uses a state machine with configurable thresholds in touch.cpp.

Switches to TinyUSB composite mode (ARDUINO_USB_MODE=0). Flashing now
requires flash.sh which handles the DTR/RTS bootloader reset.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:29:06 +00:00
Hermann Björgvin HaraldssonandClaude Opus 4.6 2611c70881 Initial commit: Claude Usage Tracker for Panlee SC01 Plus
Physical desk monitor that displays Claude Code usage limits (5-hour session
and 7-day weekly utilization) on an ESP32-S3 touchscreen via USB serial.

Firmware: LVGL 9 dashboard with LovyanGFX on SC01 Plus, Anthropic brand
colors and custom fonts (Tiempos, Styrene B, DejaVu Sans Mono), Claude
spinner animation with rotating status words.

Daemon: Pure bash script that reads the Claude Code OAuth token, makes a
minimal Haiku API call, extracts usage from rate-limit response headers,
and sends JSON to the ESP32 over serial. Uses inotifywait for instant
USB plug/unplug detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 02:37:11 +00:00