From 98a513a403f228a4a1c246e6c936c4735309544a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hermann=20Bj=C3=B6rgvin=20Haraldsson?= Date: Sun, 24 May 2026 19:10:20 +0000 Subject: [PATCH 1/6] 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 --- firmware/src/boards/template/power.cpp | 4 ++ .../src/boards/waveshare_amoled_18/power.cpp | 56 ++++++++++++++----- .../src/boards/waveshare_amoled_216/power.cpp | 51 +++++++++++------ .../boards/waveshare_amoled_216_c6/power.cpp | 46 ++++++++++----- firmware/src/hal/power_hal.h | 8 +++ 5 files changed, 120 insertions(+), 45 deletions(-) diff --git a/firmware/src/boards/template/power.cpp b/firmware/src/boards/template/power.cpp index c5d36eb..362c9c5 100644 --- a/firmware/src/boards/template/power.cpp +++ b/firmware/src/boards/template/power.cpp @@ -20,3 +20,7 @@ int power_hal_battery_pct(void) { return -1; } bool power_hal_is_charging(void) { return false; } bool power_hal_is_vbus_in(void) { return false; } bool power_hal_pwr_pressed(void) { return false; } +// Hold-to-pair gesture signals. Mirror the 216 (PMU PKEY long/positive IRQs) +// or the 1.8" (software hold-timing off a polled GPIO) port. Stub = no gesture. +bool power_hal_pwr_long_pressed(void) { return false; } +bool power_hal_pwr_released(void) { return false; } diff --git a/firmware/src/boards/waveshare_amoled_18/power.cpp b/firmware/src/boards/waveshare_amoled_18/power.cpp index 5e6a9ed..727d538 100644 --- a/firmware/src/boards/waveshare_amoled_18/power.cpp +++ b/firmware/src/boards/waveshare_amoled_18/power.cpp @@ -7,21 +7,33 @@ // PWR button comes from XCA9554 EXIO4 (active HIGH). The PMU still // provides battery monitoring; we just don't subscribe to its PKEY IRQ. +// +// The AXP2101 PKEY IRQs (short/long/release) aren't available here, so we +// synthesize the same three edges in software from the polled EXIO4 level: +// short — fired on release if the hold was shorter than PWR_LONG_MS +// long — fired once when a hold crosses PWR_LONG_MS +// release — fired on every falling edge +// This keeps the hold-to-pair gesture logic in main.cpp board-agnostic. #define BATTERY_POLL_MS 2000 #define CHARGING_POLL_MS 500 #define PWR_POLL_MS 50 +#define PWR_LONG_MS 1500 // hold threshold, mirrors the AXP LONG IRQ static XPowersPMU pmu; -static int cached_pct = -1; -static bool cached_charging = false; -static bool cached_vbus = false; -static bool pwr_pressed_flag = false; -static bool last_pwr_state = false; // edge detector for EXIO4 -static uint32_t last_battery_ms = 0; -static uint32_t last_charging_ms = 0; -static uint32_t last_pwr_ms = 0; +static int cached_pct = -1; +static bool cached_charging = false; +static bool cached_vbus = false; +static bool pwr_pressed_flag = false; +static bool pwr_long_flag = false; +static bool pwr_released_flag = false; +static bool last_pwr_state = false; // edge detector for EXIO4 +static uint32_t pwr_press_started_ms = 0; +static bool pwr_long_fired = false; // long already fired for this hold +static uint32_t last_battery_ms = 0; +static uint32_t last_charging_ms = 0; +static uint32_t last_pwr_ms = 0; void power_hal_init(void) { if (!pmu.begin(Wire, AXP2101_ADDR, IIC_SDA, IIC_SCL)) { @@ -54,8 +66,17 @@ void power_hal_tick(void) { if (now - last_pwr_ms >= PWR_POLL_MS) { last_pwr_ms = now; bool pwr_now = io_expander_get(IOX_PIN_PWR_BTN); - if (pwr_now && !last_pwr_state) { - pwr_pressed_flag = true; + if (pwr_now && !last_pwr_state) { // rising edge — hold begins + pwr_press_started_ms = now; + pwr_long_fired = false; + } else if (pwr_now && last_pwr_state) { // held + if (!pwr_long_fired && (now - pwr_press_started_ms >= PWR_LONG_MS)) { + pwr_long_flag = true; + pwr_long_fired = true; + } + } else if (!pwr_now && last_pwr_state) { // falling edge — release + pwr_released_flag = true; + if (!pwr_long_fired) pwr_pressed_flag = true; // short press } last_pwr_state = pwr_now; } @@ -66,9 +87,16 @@ bool power_hal_is_charging(void) { return cached_charging; } bool power_hal_is_vbus_in(void) { return cached_vbus; } bool power_hal_pwr_pressed(void) { - if (pwr_pressed_flag) { - pwr_pressed_flag = false; - return true; - } + if (pwr_pressed_flag) { pwr_pressed_flag = false; return true; } + return false; +} + +bool power_hal_pwr_long_pressed(void) { + if (pwr_long_flag) { pwr_long_flag = false; return true; } + return false; +} + +bool power_hal_pwr_released(void) { + if (pwr_released_flag) { pwr_released_flag = false; return true; } return false; } diff --git a/firmware/src/boards/waveshare_amoled_216/power.cpp b/firmware/src/boards/waveshare_amoled_216/power.cpp index 5533263..1c04fb6 100644 --- a/firmware/src/boards/waveshare_amoled_216/power.cpp +++ b/firmware/src/boards/waveshare_amoled_216/power.cpp @@ -4,7 +4,10 @@ #include #include -// PWR button comes from AXP2101 PKEY short-press IRQ. +// PWR button comes from AXP2101 PKEY IRQs: +// SHORT — quick tap (cycle splash animations) +// LONG — ~1.5s mark, starts the hold-to-pair countdown +// POSITIVE — release edge, completes/cancels the gesture #define BATTERY_POLL_MS 2000 #define CHARGING_POLL_MS 500 @@ -12,13 +15,15 @@ static XPowersPMU pmu; -static int cached_pct = -1; -static bool cached_charging = false; -static bool cached_vbus = false; -static bool pwr_pressed_flag = false; -static uint32_t last_battery_ms = 0; -static uint32_t last_charging_ms = 0; -static uint32_t last_pwr_ms = 0; +static int cached_pct = -1; +static bool cached_charging = false; +static bool cached_vbus = false; +static bool pwr_pressed_flag = false; +static bool pwr_long_flag = false; +static bool pwr_released_flag = false; +static uint32_t last_battery_ms = 0; +static uint32_t last_charging_ms = 0; +static uint32_t last_pwr_ms = 0; void power_hal_init(void) { if (!pmu.begin(Wire, AXP2101_ADDR, IIC_SDA, IIC_SCL)) { @@ -32,7 +37,14 @@ void power_hal_init(void) { pmu.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); pmu.clearIrqStatus(); - pmu.enableIRQ(XPOWERS_AXP2101_PKEY_SHORT_IRQ); + pmu.enableIRQ(XPOWERS_AXP2101_PKEY_SHORT_IRQ + | XPOWERS_AXP2101_PKEY_LONG_IRQ + | XPOWERS_AXP2101_PKEY_POSITIVE_IRQ); + + // Default press-off (force-shutdown) is 6s, only 2s after the pair gesture + // arms at ~3s and disarms at ~6s. Bump to 8s so a slightly-too-long hold + // doesn't shut the device down mid-gesture. + pmu.setPowerKeyPressOffTime(XPOWERS_POWEROFF_8S); cached_charging = pmu.isCharging(); cached_vbus = pmu.isVbusIn(); @@ -54,9 +66,9 @@ void power_hal_tick(void) { if (now - last_pwr_ms >= PWR_POLL_MS) { last_pwr_ms = now; pmu.getIrqStatus(); - if (pmu.isPekeyShortPressIrq()) { - pwr_pressed_flag = true; - } + if (pmu.isPekeyShortPressIrq()) pwr_pressed_flag = true; + if (pmu.isPekeyLongPressIrq()) pwr_long_flag = true; + if (pmu.isPekeyPositiveIrq()) pwr_released_flag = true; pmu.clearIrqStatus(); } } @@ -66,9 +78,16 @@ bool power_hal_is_charging(void) { return cached_charging; } bool power_hal_is_vbus_in(void) { return cached_vbus; } bool power_hal_pwr_pressed(void) { - if (pwr_pressed_flag) { - pwr_pressed_flag = false; - return true; - } + if (pwr_pressed_flag) { pwr_pressed_flag = false; return true; } + return false; +} + +bool power_hal_pwr_long_pressed(void) { + if (pwr_long_flag) { pwr_long_flag = false; return true; } + return false; +} + +bool power_hal_pwr_released(void) { + if (pwr_released_flag) { pwr_released_flag = false; return true; } return false; } diff --git a/firmware/src/boards/waveshare_amoled_216_c6/power.cpp b/firmware/src/boards/waveshare_amoled_216_c6/power.cpp index 395ef05..659162c 100644 --- a/firmware/src/boards/waveshare_amoled_216_c6/power.cpp +++ b/firmware/src/boards/waveshare_amoled_216_c6/power.cpp @@ -16,13 +16,15 @@ extern XPowersPMU board_pmu; #define pmu board_pmu -static int cached_pct = -1; -static bool cached_charging = false; -static bool cached_vbus = false; -static bool pwr_pressed_flag = false; -static uint32_t last_battery_ms = 0; -static uint32_t last_charging_ms = 0; -static uint32_t last_pwr_ms = 0; +static int cached_pct = -1; +static bool cached_charging = false; +static bool cached_vbus = false; +static bool pwr_pressed_flag = false; +static bool pwr_long_flag = false; +static bool pwr_released_flag = false; +static uint32_t last_battery_ms = 0; +static uint32_t last_charging_ms = 0; +static uint32_t last_pwr_ms = 0; void power_hal_init(void) { // pmu.begin() already ran in board_init(); just configure battery + @@ -36,7 +38,14 @@ void power_hal_init(void) { pmu.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); pmu.clearIrqStatus(); - pmu.enableIRQ(XPOWERS_AXP2101_PKEY_SHORT_IRQ); + pmu.enableIRQ(XPOWERS_AXP2101_PKEY_SHORT_IRQ + | XPOWERS_AXP2101_PKEY_LONG_IRQ + | XPOWERS_AXP2101_PKEY_POSITIVE_IRQ); + + // Default press-off is 6s, only 2s after the pair gesture arms at ~3s and + // disarms at ~6s. Bump to 8s so a slightly-too-long hold doesn't shut the + // device down mid-gesture. + pmu.setPowerKeyPressOffTime(XPOWERS_POWEROFF_8S); cached_charging = pmu.isCharging(); cached_vbus = pmu.isVbusIn(); @@ -58,9 +67,9 @@ void power_hal_tick(void) { if (now - last_pwr_ms >= PWR_POLL_MS) { last_pwr_ms = now; pmu.getIrqStatus(); - if (pmu.isPekeyShortPressIrq()) { - pwr_pressed_flag = true; - } + if (pmu.isPekeyShortPressIrq()) pwr_pressed_flag = true; + if (pmu.isPekeyLongPressIrq()) pwr_long_flag = true; + if (pmu.isPekeyPositiveIrq()) pwr_released_flag = true; pmu.clearIrqStatus(); } } @@ -70,9 +79,16 @@ bool power_hal_is_charging(void) { return cached_charging; } bool power_hal_is_vbus_in(void) { return cached_vbus; } bool power_hal_pwr_pressed(void) { - if (pwr_pressed_flag) { - pwr_pressed_flag = false; - return true; - } + if (pwr_pressed_flag) { pwr_pressed_flag = false; return true; } + return false; +} + +bool power_hal_pwr_long_pressed(void) { + if (pwr_long_flag) { pwr_long_flag = false; return true; } + return false; +} + +bool power_hal_pwr_released(void) { + if (pwr_released_flag) { pwr_released_flag = false; return true; } return false; } diff --git a/firmware/src/hal/power_hal.h b/firmware/src/hal/power_hal.h index 05b08d8..08bbd10 100644 --- a/firmware/src/hal/power_hal.h +++ b/firmware/src/hal/power_hal.h @@ -17,3 +17,11 @@ bool power_hal_is_vbus_in(void); // USB cable present (true even without a bat // Edge-triggered: returns true once per PWR short-press, then clears. bool power_hal_pwr_pressed(void); + +// Edge-triggered: true once when a PWR hold crosses the long-press threshold +// (~1.5s), then clears. Starts the hold-to-pair gesture. +bool power_hal_pwr_long_pressed(void); + +// Edge-triggered: true once on the PWR release edge, then clears. Completes +// or cancels the hold-to-pair gesture. +bool power_hal_pwr_released(void); From ddb812ab5d3e0eba8738ee215541993c021a21c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hermann=20Bj=C3=B6rgvin=20Haraldsson?= Date: Sun, 24 May 2026 19:10:29 +0000 Subject: [PATCH 2/6] 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 --- firmware/src/brightness.cpp | 39 +++++++++++++++++++++++++++++++++++++ firmware/src/brightness.h | 10 ++++++++++ firmware/src/idle.cpp | 18 ++++++++++++----- firmware/src/idle.h | 6 ++++++ 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 firmware/src/brightness.cpp create mode 100644 firmware/src/brightness.h diff --git a/firmware/src/brightness.cpp b/firmware/src/brightness.cpp new file mode 100644 index 0000000..03f26db --- /dev/null +++ b/firmware/src/brightness.cpp @@ -0,0 +1,39 @@ +#include "brightness.h" +#include "idle.h" +#include +#include + +// Four-step ramp. The default (index 2) is 200 — identical to the prior +// hard-coded DISPLAY_DEFAULT_BRIGHTNESS, so cycling is purely additive. +static const uint8_t LEVELS[] = {64, 128, 200, 255}; +#define LEVELS_COUNT (sizeof(LEVELS) / sizeof(LEVELS[0])) +#define DEFAULT_IDX 2 + +static uint8_t cur_idx = DEFAULT_IDX; + +void brightness_init(void) { + Preferences prefs; + prefs.begin("clawdmeter", true); + uint8_t saved_idx = prefs.getUChar("brt_idx", 0xFF); + prefs.end(); + + if (saved_idx < LEVELS_COUNT) cur_idx = saved_idx; + idle_set_awake_brightness(LEVELS[cur_idx]); + Serial.printf("Brightness init: level=%u (idx=%u)\n", LEVELS[cur_idx], cur_idx); +} + +void brightness_cycle(void) { + cur_idx = (cur_idx + 1) % LEVELS_COUNT; + + Preferences prefs; + prefs.begin("clawdmeter", false); + prefs.putUChar("brt_idx", cur_idx); + prefs.end(); + + idle_set_awake_brightness(LEVELS[cur_idx]); + Serial.printf("Brightness cycled: level=%u (idx=%u)\n", LEVELS[cur_idx], cur_idx); +} + +uint8_t brightness_get(void) { + return LEVELS[cur_idx]; +} diff --git a/firmware/src/brightness.h b/firmware/src/brightness.h new file mode 100644 index 0000000..c956507 --- /dev/null +++ b/firmware/src/brightness.h @@ -0,0 +1,10 @@ +#pragma once +#include + +// User-controlled display brightness, persisted to NVS. The middle (PWR) +// button short-press cycles through the levels via brightness_cycle(). +// idle owns the actual panel brightness, so this routes the chosen level +// through idle_set_awake_brightness(). +void brightness_init(void); // load saved level from NVS and apply +void brightness_cycle(void); // advance to next level, save, apply +uint8_t brightness_get(void); // current PWM level (0..255) diff --git a/firmware/src/idle.cpp b/firmware/src/idle.cpp index 9552e67..b491655 100644 --- a/firmware/src/idle.cpp +++ b/firmware/src/idle.cpp @@ -17,13 +17,14 @@ static uint32_t fade_started_ms = 0; static uint32_t fade_last_step_ms = 0; static uint8_t fade_from = DISPLAY_DEFAULT_BRIGHTNESS; static uint8_t fade_to = 0; +static uint8_t awake_brightness = DISPLAY_DEFAULT_BRIGHTNESS; // user-set "full" level (brightness.cpp) static void apply_brightness(uint8_t b) { display_hal_set_brightness(b); } static void begin_fade(uint8_t to, uint32_t now) { - fade_from = (to == 0) ? DISPLAY_DEFAULT_BRIGHTNESS : 0; + fade_from = (to == 0) ? awake_brightness : 0; fade_to = to; fade_started_ms = now; fade_last_step_ms = now; @@ -32,7 +33,14 @@ static void begin_fade(uint8_t to, uint32_t now) { void idle_init(void) { state = STATE_AWAKE; last_activity_ms = millis(); - apply_brightness(DISPLAY_DEFAULT_BRIGHTNESS); + apply_brightness(awake_brightness); +} + +void idle_set_awake_brightness(uint8_t level) { + awake_brightness = level; + // Apply now if fully awake so a button press is visible immediately; + // during fades/sleep the next fade-in picks it up. + if (state == STATE_AWAKE) apply_brightness(level); } void idle_note_activity(void) { @@ -41,7 +49,7 @@ void idle_note_activity(void) { if (state == STATE_AWAKE) return; // Asleep/fading-out shouldn't reach here in normal flow (callers gate via // idle_consume_wake_press first), but if it does: trigger a wake. - begin_fade(DISPLAY_DEFAULT_BRIGHTNESS, last_activity_ms); + begin_fade(awake_brightness, last_activity_ms); state = STATE_FADING_IN; } @@ -49,7 +57,7 @@ bool idle_consume_wake_press(void) { if (state == STATE_ASLEEP || state == STATE_FADING_OUT) { uint32_t now = millis(); last_activity_ms = now; - begin_fade(DISPLAY_DEFAULT_BRIGHTNESS, now); + begin_fade(awake_brightness, now); state = STATE_FADING_IN; return true; } @@ -75,7 +83,7 @@ void idle_tick(void) { if (!IDLE_SLEEP_WHEN_CHARGING && power_hal_is_vbus_in()) { last_activity_ms = now; if (state == STATE_ASLEEP || state == STATE_FADING_OUT) { - begin_fade(DISPLAY_DEFAULT_BRIGHTNESS, now); + begin_fade(awake_brightness, now); state = STATE_FADING_IN; } } diff --git a/firmware/src/idle.h b/firmware/src/idle.h index 0ce424c..4dd9c00 100644 --- a/firmware/src/idle.h +++ b/firmware/src/idle.h @@ -5,6 +5,12 @@ void idle_init(void); void idle_tick(void); void idle_note_activity(void); +// Set the "awake" brightness target (0..255). idle owns display brightness +// (it fades between this and 0), so user brightness control routes through +// here. Applied immediately if the screen is currently fully awake; otherwise +// picked up by the next fade-in. See brightness.{h,cpp}. +void idle_set_awake_brightness(uint8_t level); + // Returns true if this press was consumed as a wake-up (caller MUST skip the // button's normal action). Returns false when already awake — also notes the // activity, so callers don't need a separate idle_note_activity() call. From 2b4a0802fa4aca1e997a76f477359f2e2cdc6a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hermann=20Bj=C3=B6rgvin=20Haraldsson?= Date: Sun, 24 May 2026 19:10:39 +0000 Subject: [PATCH 3/6] Collapse UX to single Usage view + hold-to-pair + status line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- firmware/src/ble.cpp | 4 + firmware/src/ble.h | 1 + firmware/src/main.cpp | 59 +++++++++- firmware/src/ui.cpp | 253 +++++++++++++++--------------------------- firmware/src/ui.h | 2 - 5 files changed, 152 insertions(+), 167 deletions(-) diff --git a/firmware/src/ble.cpp b/firmware/src/ble.cpp index afe0fe4..14d0a0f 100644 --- a/firmware/src/ble.cpp +++ b/firmware/src/ble.cpp @@ -236,6 +236,10 @@ void ble_clear_bonds(void) { need_advertise = true; } +bool ble_has_bonds(void) { + return NimBLEDevice::getNumBonds() > 0; +} + bool ble_has_data(void) { return data_ready; } diff --git a/firmware/src/ble.h b/firmware/src/ble.h index 675fc88..55c0e97 100644 --- a/firmware/src/ble.h +++ b/firmware/src/ble.h @@ -14,6 +14,7 @@ ble_state_t ble_get_state(void); const char* ble_get_device_name(void); const char* ble_get_mac_address(void); void ble_clear_bonds(void); +bool ble_has_bonds(void); bool ble_has_data(void); const char* ble_get_data(void); void ble_send_ack(void); diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 08a5cb2..1207553 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -11,6 +11,7 @@ #include "usage_rate.h" #include "idle.h" #include "idle_cfg.h" +#include "brightness.h" #include "hal/board_caps.h" #include "hal/display_hal.h" @@ -185,7 +186,8 @@ void setup() { display_hal_init(); display_hal_begin(); - idle_init(); // takes over brightness (DISPLAY_DEFAULT_BRIGHTNESS) and starts the idle timer + idle_init(); // takes over panel brightness and starts the idle timer + brightness_init(); // load the user's saved brightness level and apply via idle power_hal_init(); imu_hal_init(); @@ -226,6 +228,52 @@ void setup() { static ble_state_t last_ble_state = BLE_STATE_INIT; +// Hold-to-pair gesture: hold the PWR button ~3s, then RELEASE → clear all BLE +// bonds and re-advertise. Clearing on *release* (not while held) is deliberate: +// holding to power the device OFF (AXP hardware shutdown at 8s) must not wipe +// the bond — a power-off hold never releases before shutdown. To stop a +// "chicken-out" release just before 8s from pairing, the gesture disarms at 6s. +// +// ~1.5s long-press edge → PENDING +// 3.0s (+1500) → ARMED (release from here clears bonds) +// 6.0s (+4500) → DISARMED (no clear; AXP powers off at 8s) +#define PAIR_ARM_AFTER_LONG_MS 1500 // 3.0s total +#define PAIR_DISARM_AFTER_LONG_MS 4500 // 6.0s total +enum pair_state_t { PAIR_IDLE, PAIR_PENDING, PAIR_ARMED }; +static pair_state_t pair_state = PAIR_IDLE; +static uint32_t pair_long_seen_ms = 0; + +static void pair_tick(void) { + if (pair_state == PAIR_IDLE && power_hal_pwr_long_pressed()) { + pair_state = PAIR_PENDING; + pair_long_seen_ms = millis(); + (void)power_hal_pwr_released(); // drain any stale release edge + Serial.println("PWR long-press: hold to ~3s then release to pair"); + return; + } + if (pair_state == PAIR_IDLE) return; + + if (power_hal_pwr_released()) { + if (pair_state == PAIR_ARMED) { + Serial.println("Pair: released in window — clearing bonds, advertising"); + ble_clear_bonds(); + } else { + Serial.println("Pair: released too early — cancelled"); + } + pair_state = PAIR_IDLE; + return; + } + + uint32_t held = millis() - pair_long_seen_ms; + if (pair_state == PAIR_PENDING && held >= PAIR_ARM_AFTER_LONG_MS) { + pair_state = PAIR_ARMED; + Serial.println("Pair: armed — release to pair"); + } else if (pair_state == PAIR_ARMED && held >= PAIR_DISARM_AFTER_LONG_MS) { + pair_state = PAIR_IDLE; // power-off territory; don't pair + Serial.println("Pair: disarmed (holding toward power-off)"); + } +} + void loop() { idle_tick(); lv_timer_handler(); @@ -242,7 +290,8 @@ void loop() { // ---- Physical buttons ---- // PRIMARY → HID Space (Claude Code voice-mode PTT) // SECONDARY → HID Shift+Tab (mode toggle; only if the board has one) - // PWR → cycle screens; on splash, cycle animations + // PWR → on splash: cycle animations; on usage: cycle brightness; + // hold ~3s + release: pairing mode // First press from sleep is consumed as a wake-only event by // idle_consume_wake_press(); the normal action fires from the second // press. Activity bookkeeping happens inside idle_consume_wake_press @@ -280,10 +329,14 @@ void loop() { if (power_hal_pwr_pressed()) { if (!idle_consume_wake_press()) { + // On splash: cycle animations. On the usage view: cycle + // screen brightness (single non-splash view, no more screens). if (ui_get_current_screen() == SCREEN_SPLASH) splash_next(); - else ui_cycle_screen(); + else brightness_cycle(); } } + + pair_tick(); } ble_state_t bs = ble_get_state(); diff --git a/firmware/src/ui.cpp b/firmware/src/ui.cpp index 6e12188..395b936 100644 --- a/firmware/src/ui.cpp +++ b/firmware/src/ui.cpp @@ -99,9 +99,11 @@ static void compute_layout(const BoardCaps& c) { #define COL_RED THEME_RED #define COL_BAR_BG THEME_BAR_BG -// ---- Usage screen widgets ---- +// ---- Usage screen widgets (single non-splash view) ---- static lv_obj_t* usage_container; static lv_obj_t* lbl_title; +static lv_obj_t* usage_group; // the two usage panels — shown when connected +static lv_obj_t* pair_group; // pairing hint — shown when disconnected static lv_obj_t* bar_session; static lv_obj_t* lbl_session_pct; static lv_obj_t* lbl_session_label; @@ -110,13 +112,7 @@ static lv_obj_t* bar_weekly; static lv_obj_t* lbl_weekly_pct; static lv_obj_t* lbl_weekly_label; static lv_obj_t* lbl_weekly_reset; -static lv_obj_t* lbl_anim; - -// ---- Bluetooth screen widgets ---- -static lv_obj_t* ble_container; -static lv_obj_t* lbl_ble_status; -static lv_obj_t* lbl_ble_device; -static lv_obj_t* lbl_ble_mac; +static lv_obj_t* lbl_anim; // status line: connection state + whimsical idle // ---- Battery indicator (shared, on top) ---- static lv_obj_t* battery_img; @@ -126,6 +122,8 @@ static lv_image_dsc_t battery_dscs[5]; // empty, low, medium, full, charging // ---- Shared ---- static lv_image_dsc_t logo_dsc; static screen_t current_screen = SCREEN_USAGE; +static bool s_ble_connected = false; // cached BLE connection state +static uint32_t connected_at_ms = 0; // when we last entered CONNECTED ("Connected" dwell) // Animation state static uint32_t anim_last_ms = 0; @@ -201,7 +199,6 @@ static void format_reset_time(int mins, char* buf, size_t len) { // Forward decls — callbacks defined near ui_show_screen below static void global_click_cb(lv_event_t* e); -static void ble_reset_click_cb(lv_event_t* e); static lv_obj_t* make_panel(lv_obj_t* parent, int x, int y, int w, int h) { lv_obj_t* panel = lv_obj_create(parent); @@ -235,15 +232,6 @@ static lv_obj_t* make_bar(lv_obj_t* parent, int x, int y, int w, int h) { return bar; } -static void init_icon_dsc(lv_image_dsc_t* dsc, int w, int h, const uint16_t* data) { - dsc->header.w = w; - dsc->header.h = h; - dsc->header.cf = LV_COLOR_FORMAT_RGB565; - dsc->header.stride = w * 2; - dsc->data = (const uint8_t*)data; - dsc->data_size = w * h * 2; -} - static void init_icon_dsc_rgb565a8(lv_image_dsc_t* dsc, int w, int h, const uint8_t* data) { dsc->header.w = w; dsc->header.h = h; @@ -301,6 +289,39 @@ static void make_usage_panel(lv_obj_t* parent, int y, const char* pill_text, lv_obj_set_pos(*out_reset, 0, L.usage_reset_y); } +// Pairing hint — shown when disconnected so the screen isn't empty and the +// user knows how to (re)pair. Wording matches the 3-second release gesture. +static void build_pair_group(lv_obj_t* parent) { + pair_group = lv_obj_create(parent); + lv_obj_set_size(pair_group, L.scr_w, L.scr_h - L.content_y); + lv_obj_set_pos(pair_group, 0, L.content_y); + lv_obj_set_style_bg_opa(pair_group, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(pair_group, 0, 0); + lv_obj_set_style_pad_all(pair_group, 0, 0); + lv_obj_clear_flag(pair_group, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(pair_group, LV_OBJ_FLAG_EVENT_BUBBLE); + + lv_obj_t* l1 = lv_label_create(pair_group); + lv_label_set_text(l1, "To pair"); + lv_obj_set_style_text_font(l1, L.bt_status_font, 0); + lv_obj_set_style_text_color(l1, COL_TEXT, 0); + lv_obj_align(l1, LV_ALIGN_TOP_MID, 0, 40); + + lv_obj_t* l2 = lv_label_create(pair_group); + lv_label_set_text(l2, "hold the power button"); + lv_obj_set_style_text_font(l2, L.bt_device_font, 0); + lv_obj_set_style_text_color(l2, COL_DIM, 0); + lv_obj_align(l2, LV_ALIGN_TOP_MID, 0, 120); + + lv_obj_t* l3 = lv_label_create(pair_group); + lv_label_set_text(l3, "for 3 seconds, then release"); + lv_obj_set_style_text_font(l3, L.bt_device_font, 0); + lv_obj_set_style_text_color(l3, COL_DIM, 0); + lv_obj_align(l3, LV_ALIGN_TOP_MID, 0, 160); + + lv_obj_add_flag(pair_group, LV_OBJ_FLAG_HIDDEN); // ui_update_ble_status decides +} + static void init_usage_screen(lv_obj_t* scr) { usage_container = lv_obj_create(scr); lv_obj_set_size(usage_container, L.scr_w, L.scr_h); @@ -317,14 +338,28 @@ static void init_usage_screen(lv_obj_t* scr) { lv_obj_set_style_text_color(lbl_title, COL_TEXT, 0); lv_obj_align(lbl_title, LV_ALIGN_TOP_MID, 16, L.title_y); - make_usage_panel(usage_container, L.content_y, "Current", + // Usage panels (shown when connected) live in a transparent full-size group + // so they can be toggled against the pairing hint as one unit. + usage_group = lv_obj_create(usage_container); + lv_obj_set_size(usage_group, L.scr_w, L.scr_h); + lv_obj_set_pos(usage_group, 0, 0); + lv_obj_set_style_bg_opa(usage_group, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(usage_group, 0, 0); + lv_obj_set_style_pad_all(usage_group, 0, 0); + lv_obj_clear_flag(usage_group, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(usage_group, LV_OBJ_FLAG_EVENT_BUBBLE); + + make_usage_panel(usage_group, L.content_y, "Current", &lbl_session_pct, &lbl_session_label, &bar_session, &lbl_session_reset); - make_usage_panel(usage_container, + make_usage_panel(usage_group, L.content_y + L.usage_panel_h + L.usage_panel_gap, "Weekly", &lbl_weekly_pct, &lbl_weekly_label, &bar_weekly, &lbl_weekly_reset); + build_pair_group(usage_container); + + // Status line — always visible on the usage view. Driven by ui_tick_anim(). lbl_anim = lv_label_create(usage_container); lv_label_set_text(lbl_anim, ""); lv_obj_set_style_text_font(lbl_anim, &font_mono_32, 0); @@ -332,91 +367,6 @@ static void init_usage_screen(lv_obj_t* scr) { lv_obj_align(lbl_anim, LV_ALIGN_BOTTOM_MID, 0, -15); } -// ======== Bluetooth Screen ======== - -static void init_bluetooth_screen(lv_obj_t* scr) { - ble_container = lv_obj_create(scr); - lv_obj_set_size(ble_container, L.scr_w, L.scr_h); - lv_obj_set_pos(ble_container, 0, 0); - lv_obj_set_style_bg_opa(ble_container, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(ble_container, 0, 0); - lv_obj_set_style_pad_all(ble_container, 0, 0); - lv_obj_clear_flag(ble_container, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_add_event_cb(ble_container, global_click_cb, LV_EVENT_CLICKED, NULL); - - lv_obj_t* lbl_ble_title = lv_label_create(ble_container); - lv_label_set_text(lbl_ble_title, "Bluetooth"); - lv_obj_set_style_text_font(lbl_ble_title, L.bt_title_font, 0); - lv_obj_set_style_text_color(lbl_ble_title, COL_TEXT, 0); - lv_obj_align(lbl_ble_title, LV_ALIGN_TOP_MID, 16, L.title_y); - - lv_obj_t* p_info = make_panel(ble_container, L.margin, L.content_y, - L.content_w, L.bt_info_panel_h); - - static lv_image_dsc_t icon_bt_dsc; - init_icon_dsc(&icon_bt_dsc, ICON_BLUETOOTH_W, ICON_BLUETOOTH_H, icon_bluetooth_data); - - lv_obj_t* bt_img = lv_image_create(p_info); - lv_image_set_src(bt_img, &icon_bt_dsc); - lv_obj_set_pos(bt_img, 0, 0); - - lbl_ble_status = lv_label_create(p_info); - lv_label_set_text(lbl_ble_status, "Initializing..."); - lv_obj_set_style_text_font(lbl_ble_status, L.bt_status_font, 0); - lv_obj_set_style_text_color(lbl_ble_status, COL_DIM, 0); - lv_obj_set_pos(lbl_ble_status, 56, 2); - - lbl_ble_device = lv_label_create(p_info); - lv_label_set_text(lbl_ble_device, "Device: ---"); - lv_obj_set_style_text_font(lbl_ble_device, L.bt_device_font, 0); - lv_obj_set_style_text_color(lbl_ble_device, COL_DIM, 0); - lv_obj_set_pos(lbl_ble_device, 0, 64); - - lbl_ble_mac = lv_label_create(p_info); - lv_label_set_text(lbl_ble_mac, "Address: ---"); - lv_obj_set_style_text_font(lbl_ble_mac, L.bt_device_font, 0); - lv_obj_set_style_text_color(lbl_ble_mac, COL_DIM, 0); - lv_obj_set_pos(lbl_ble_mac, 0, 100); - - int reset_y = L.content_y + L.bt_info_panel_h + 16; - lv_obj_t* reset_zone = lv_obj_create(ble_container); - lv_obj_set_pos(reset_zone, L.margin, reset_y); - lv_obj_set_size(reset_zone, L.content_w, L.bt_reset_zone_h); - lv_obj_set_style_bg_color(reset_zone, COL_PANEL, 0); - lv_obj_set_style_bg_opa(reset_zone, LV_OPA_COVER, 0); - lv_obj_set_style_radius(reset_zone, 8, 0); - lv_obj_set_style_border_width(reset_zone, 0, 0); - lv_obj_set_style_pad_column(reset_zone, 14, 0); - lv_obj_set_flex_flow(reset_zone, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(reset_zone, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_clear_flag(reset_zone, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_add_event_cb(reset_zone, ble_reset_click_cb, LV_EVENT_CLICKED, NULL); - - static lv_image_dsc_t icon_trash_dsc; - init_icon_dsc(&icon_trash_dsc, ICON_TRASH2_W, ICON_TRASH2_H, icon_trash2_data); - lv_obj_t* trash_img = lv_image_create(reset_zone); - lv_image_set_src(trash_img, &icon_trash_dsc); - - lv_obj_t* reset_lbl = lv_label_create(reset_zone); - lv_label_set_text(reset_lbl, "Reset Bluetooth"); - lv_obj_set_style_text_font(reset_lbl, L.bt_device_font, 0); - lv_obj_set_style_text_color(reset_lbl, COL_DIM, 0); - - lv_obj_t* lbl_credit = lv_label_create(ble_container); - lv_label_set_text(lbl_credit, "Built by @hermannbjorgvin"); - lv_obj_set_style_text_font(lbl_credit, L.bt_credit_1_font, 0); - lv_obj_set_style_text_color(lbl_credit, COL_DIM, 0); - lv_obj_align(lbl_credit, LV_ALIGN_BOTTOM_MID, 0, -46); - - lv_obj_t* lbl_credit2 = lv_label_create(ble_container); - lv_label_set_text(lbl_credit2, "Clawd animation by @amaanbuilds"); - lv_obj_set_style_text_font(lbl_credit2, L.bt_credit_2_font, 0); - lv_obj_set_style_text_color(lbl_credit2, COL_DIM, 0); - lv_obj_align(lbl_credit2, LV_ALIGN_BOTTOM_MID, 0, -20); - - lv_obj_add_flag(ble_container, LV_OBJ_FLAG_HIDDEN); -} - // ======== Public API ======== void ui_init(void) { @@ -430,7 +380,6 @@ void ui_init(void) { init_battery_icons(); init_usage_screen(scr); - init_bluetooth_screen(scr); splash_init(scr); if (splash_get_root()) { @@ -478,18 +427,27 @@ void ui_tick_anim(void) { anim_msg_start = now; } - if (now - anim_last_ms >= spinner_ms[anim_spinner_idx]) { - anim_last_ms = now; - anim_phase = (anim_phase + 1) % SPINNER_PHASES; - anim_spinner_idx = (anim_phase < SPINNER_COUNT) ? anim_phase - : (SPINNER_PHASES - anim_phase); + if (now - anim_last_ms < spinner_ms[anim_spinner_idx]) return; + anim_last_ms = now; + anim_phase = (anim_phase + 1) % SPINNER_PHASES; + anim_spinner_idx = (anim_phase < SPINNER_COUNT) ? anim_phase + : (SPINNER_PHASES - anim_phase); - static char buf[80]; - snprintf(buf, sizeof(buf), "%s %s\xE2\x80\xA6", - spinner_frames[anim_spinner_idx], - anim_messages[anim_msg_idx]); - lv_label_set_text(lbl_anim, buf); + // Status text by priority. Whimsical messages only when connected & settled. + const char* text; + if (!s_ble_connected) { + text = ble_has_bonds() ? "Disconnected" : "Pairing"; + } else if (now - connected_at_ms < 5000) { + text = "Connected"; + } else { + text = anim_messages[anim_msg_idx]; } + + // All states share the whimsical style: " …" + static char buf[80]; + snprintf(buf, sizeof(buf), "%s %s\xE2\x80\xA6", + spinner_frames[anim_spinner_idx], text); + lv_label_set_text(lbl_anim, buf); } static screen_t prev_non_splash_screen = SCREEN_USAGE; @@ -505,20 +463,13 @@ static void global_click_cb(lv_event_t* e) { else ui_show_screen(SCREEN_SPLASH); } -static void ble_reset_click_cb(lv_event_t* e) { - (void)e; - ble_clear_bonds(); -} - void ui_show_screen(screen_t screen) { lv_obj_add_flag(usage_container, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(ble_container, LV_OBJ_FLAG_HIDDEN); splash_hide(); switch (screen) { - case SCREEN_SPLASH: splash_show(); break; - case SCREEN_USAGE: lv_obj_clear_flag(usage_container, LV_OBJ_FLAG_HIDDEN); break; - case SCREEN_BLUETOOTH: lv_obj_clear_flag(ble_container, LV_OBJ_FLAG_HIDDEN); break; + case SCREEN_SPLASH: splash_show(); break; + case SCREEN_USAGE: lv_obj_clear_flag(usage_container, LV_OBJ_FLAG_HIDDEN); break; default: break; } @@ -532,16 +483,6 @@ void ui_show_screen(screen_t screen) { apply_battery_visibility(); } -void ui_cycle_screen(void) { - screen_t next; - switch (current_screen) { - case SCREEN_USAGE: next = SCREEN_BLUETOOTH; break; - case SCREEN_BLUETOOTH: next = SCREEN_USAGE; break; - default: next = SCREEN_USAGE; break; - } - ui_show_screen(next); -} - void ui_toggle_splash(void) { if (current_screen == SCREEN_SPLASH) ui_show_screen(prev_non_splash_screen); else ui_show_screen(SCREEN_SPLASH); @@ -552,35 +493,23 @@ screen_t ui_get_current_screen(void) { } void ui_update_ble_status(ble_state_t state, const char* name, const char* mac) { - switch (state) { - case BLE_STATE_CONNECTED: - lv_label_set_text(lbl_ble_status, "Connected"); - lv_obj_set_style_text_color(lbl_ble_status, COL_GREEN, 0); - break; - case BLE_STATE_ADVERTISING: - lv_label_set_text(lbl_ble_status, "Advertising..."); - lv_obj_set_style_text_color(lbl_ble_status, COL_AMBER, 0); - break; - case BLE_STATE_DISCONNECTED: - lv_label_set_text(lbl_ble_status, "Disconnected"); - lv_obj_set_style_text_color(lbl_ble_status, COL_RED, 0); - break; - default: - lv_label_set_text(lbl_ble_status, "Initializing..."); - lv_obj_set_style_text_color(lbl_ble_status, COL_DIM, 0); - break; + (void)name; (void)mac; + bool was_connected = s_ble_connected; + s_ble_connected = (state == BLE_STATE_CONNECTED); + + // Connected → usage panels; otherwise → pairing hint. The bottom status + // line carries the live state word (Connected / Disconnected / Pairing). + if (usage_group && pair_group) { + if (s_ble_connected) { + lv_obj_clear_flag(usage_group, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(pair_group, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(usage_group, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(pair_group, LV_OBJ_FLAG_HIDDEN); + } } - if (name) { - static char nbuf[48]; - snprintf(nbuf, sizeof(nbuf), "Device: %s", name); - lv_label_set_text(lbl_ble_device, nbuf); - } - if (mac) { - static char mbuf[48]; - snprintf(mbuf, sizeof(mbuf), "Address: %s", mac); - lv_label_set_text(lbl_ble_mac, mbuf); - } + if (s_ble_connected && !was_connected) connected_at_ms = lv_tick_get(); } void ui_update_battery(int percent, bool charging) { diff --git a/firmware/src/ui.h b/firmware/src/ui.h index b18068c..60d8155 100644 --- a/firmware/src/ui.h +++ b/firmware/src/ui.h @@ -5,7 +5,6 @@ enum screen_t { SCREEN_SPLASH, SCREEN_USAGE, - SCREEN_BLUETOOTH, SCREEN_COUNT, }; @@ -13,7 +12,6 @@ void ui_init(void); void ui_update(const UsageData* data); void ui_tick_anim(void); void ui_show_screen(screen_t screen); -void ui_cycle_screen(void); void ui_toggle_splash(void); screen_t ui_get_current_screen(void); void ui_update_ble_status(ble_state_t state, const char* name, const char* mac); From bd5d45cef352d390752ec6e2e67957f6a4b40379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hermann=20Bj=C3=B6rgvin=20Haraldsson?= Date: Sun, 24 May 2026 19:10:51 +0000 Subject: [PATCH 4/6] 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 --- README.md | 18 +++++++++--------- firmware/src/splash_animations.h | 26 +++++++++++++------------- screenshots/amoled_18/bluetooth.png | Bin 16966 -> 0 bytes screenshots/bluetooth.png | Bin 14926 -> 0 bytes tools/convert_to_c.js | 9 +++++++++ 5 files changed, 31 insertions(+), 22 deletions(-) delete mode 100644 screenshots/amoled_18/bluetooth.png delete mode 100644 screenshots/bluetooth.png diff --git a/README.md b/README.md index 1f03645..6854f33 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,14 @@ The Clawd animations come from [claudepix](https://claudepix.vercel.app), [@amaa ## Screens -The device boots into the splash and stays there until you press the middle (PWR) button, which cycles between Usage and Bluetooth. Tap the screen anywhere (except the Reset zone on the Bluetooth screen) to flip back to the splash; tap again to dismiss it. +The device boots into the splash. Tap the screen anywhere to switch to the Usage view; tap again to flip back to the splash. -| Splash | Usage | Bluetooth | -| :-------------------------------: | :-----------------------------: | :-------------------------------------: | -| ![Splash](screenshots/splash.png) | ![Usage](screenshots/usage.png) | ![Bluetooth](screenshots/bluetooth.png) | -| Splash; touch-toggle anytime | Session and weekly utilization | Connection status and bond reset | +| Splash | Usage | +| :-------------------------------: | :-----------------------------: | +| ![Splash](screenshots/splash.png) | ![Usage](screenshots/usage.png) | +| Splash; touch-toggle anytime | Session and weekly utilization | -While the splash is up, the middle button cycles animations instead of screens. The firmware also auto-rotates every 20 s within the current usage-rate group, so a long stretch on the splash isn't just one Clawd on loop. +While the splash is up, the middle (PWR) button cycles animations. **Hold the power button for 3 seconds, then release, to put the device into pairing mode** — this clears the saved Bluetooth bond and re-advertises. The firmware also auto-rotates animations every 20 s within the current usage-rate group, so a long stretch on the splash isn't just one Clawd on loop. ## Hardware @@ -103,7 +103,7 @@ bluetoothctl pair F4:12:FA:C0:8F:E5 # use your device's MAC bluetoothctl trust F4:12:FA:C0:8F:E5 ``` -The MAC address is shown on the Bluetooth screen — press the middle (PWR) button to cycle to it. +To re-pair later, hold the power button for 3 seconds then release — the device clears its saved bond and re-advertises. ### Install the daemon @@ -130,12 +130,12 @@ View logs: `journalctl --user -u claude-usage-daemon -f` ## Physical buttons -The board has three side buttons. Left and right do the same thing on every screen; the middle button is screen-aware. +The board has three side buttons. Left and right send HID keys; the middle (PWR) button cycles splash animations and, held for 3 seconds, triggers pairing mode. | Button | GPIO | Function | | ---------------- | ------------ | -------------------------------------------------------------- | | **Left** | GPIO 0 | Hold to send Space (Claude Code voice-mode push-to-talk) | -| **Middle** (PWR) | AXP2101 PKEY | Cycle screens (Usage ↔ Bluetooth); on splash, cycle animations | +| **Middle** (PWR) | AXP2101 PKEY | On splash: cycle animations. Hold 3s + release: pairing mode | | **Right** | GPIO 18 | Press to send Shift+Tab (Claude Code mode toggle) | Space and Shift+Tab go out as standard BLE HID keyboard reports, so they trigger in whatever window has focus on the paired host — not just Claude Code. diff --git a/firmware/src/splash_animations.h b/firmware/src/splash_animations.h index 77e47d7..2bd704f 100644 --- a/firmware/src/splash_animations.h +++ b/firmware/src/splash_animations.h @@ -21,7 +21,7 @@ typedef struct { const uint16_t *holds; } splash_anim_def_t; -static const uint16_t splash_idle_breathe_palette[10] = {0x0000,0xCBED,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; +static const uint16_t splash_idle_breathe_palette[10] = {0x0000,0xDBAA,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; static const uint8_t splash_idle_breathe_frames[16][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -42,7 +42,7 @@ static const uint8_t splash_idle_breathe_frames[16][400] = { }; static const uint16_t splash_idle_breathe_holds[16] = {500,200,280,80,360,200,320,80,700,200,300,500,200,80,80,340}; -static const uint16_t splash_idle_blink_palette[10] = {0x0000,0xCBED,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; +static const uint16_t splash_idle_blink_palette[10] = {0x0000,0xDBAA,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; static const uint8_t splash_idle_blink_frames[16][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -63,7 +63,7 @@ static const uint8_t splash_idle_blink_frames[16][400] = { }; static const uint16_t splash_idle_blink_holds[16] = {2400,60,100,60,80,220,1600,60,90,60,70,60,100,70,200,900}; -static const uint16_t splash_idle_look_around_palette[10] = {0x0000,0xCBED,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; +static const uint16_t splash_idle_look_around_palette[10] = {0x0000,0xDBAA,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; static const uint8_t splash_idle_look_around_frames[17][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,2,1,1,1,1,1,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -85,7 +85,7 @@ static const uint8_t splash_idle_look_around_frames[17][400] = { }; static const uint16_t splash_idle_look_around_holds[17] = {800,200,500,300,200,400,400,200,500,300,200,500,200,400,300,200,700}; -static const uint16_t splash_expression_wink_palette[10] = {0x0000,0xCBED,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; +static const uint16_t splash_expression_wink_palette[10] = {0x0000,0xDBAA,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; static const uint8_t splash_expression_wink_frames[12][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -102,7 +102,7 @@ static const uint8_t splash_expression_wink_frames[12][400] = { }; static const uint16_t splash_expression_wink_holds[12] = {1200,100,120,150,120,100,100,400,100,100,80,800}; -static const uint16_t splash_expression_surprise_palette[10] = {0x0000,0xCBED,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; +static const uint16_t splash_expression_surprise_palette[10] = {0x0000,0xDBAA,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; static const uint8_t splash_expression_surprise_frames[12][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -119,7 +119,7 @@ static const uint8_t splash_expression_surprise_frames[12][400] = { }; static const uint16_t splash_expression_surprise_holds[12] = {900,120,60,80,100,100,100,500,180,180,300,600}; -static const uint16_t splash_expression_sleep_palette[10] = {0x0000,0xCBED,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; +static const uint16_t splash_expression_sleep_palette[10] = {0x0000,0xDBAA,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; static const uint8_t splash_expression_sleep_frames[24][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -148,7 +148,7 @@ static const uint8_t splash_expression_sleep_frames[24][400] = { }; static const uint16_t splash_expression_sleep_holds[24] = {600,400,300,300,300,300,300,300,300,300,300,300,300,400,700,400,500,300,300,300,300,300,300,400}; -static const uint16_t splash_dance_bounce_palette[10] = {0x0000,0xCBED,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; +static const uint16_t splash_dance_bounce_palette[10] = {0x0000,0xDBAA,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; static const uint8_t splash_dance_bounce_frames[16][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -169,7 +169,7 @@ static const uint8_t splash_dance_bounce_frames[16][400] = { }; static const uint16_t splash_dance_bounce_holds[16] = {100,80,140,100,60,80,120,80,100,80,160,80,60,80,120,100}; -static const uint16_t splash_dance_sway_palette[10] = {0x0000,0xCBED,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; +static const uint16_t splash_dance_sway_palette[10] = {0x0000,0xDBAA,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; static const uint8_t splash_dance_sway_frames[12][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -186,7 +186,7 @@ static const uint8_t splash_dance_sway_frames[12][400] = { }; static const uint16_t splash_dance_sway_holds[12] = {240,180,200,240,200,160,180,200,240,200,200,180}; -static const uint16_t splash_work_coding_palette[10] = {0x0000,0xCBED,0x1082,0xD6FC,0x8C93,0x6B8F,0x39E8,0xBDF8,0x2966,0x18E4}; +static const uint16_t splash_work_coding_palette[10] = {0x0000,0xDBAA,0x1082,0xD6FC,0x8C93,0x6B8F,0x39E8,0xBDF8,0x2966,0x18E4}; static const uint8_t splash_work_coding_frames[23][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,3,4,0,0,0,0,0,0,4,3,0,0,0,0,0,0,0,0,0,3,4,1,1,1,1,1,1,1,1,4,3,0,0,0,0,0,0,0,0,3,4,1,2,1,1,1,1,2,1,4,3,0,0,0,0,0,0,0,0,3,4,1,1,1,1,1,1,1,1,4,3,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,5,5,5,5,5,5,5,5,1,1,1,0,0,0,0,0,0,1,1,1,5,5,5,5,5,5,5,5,1,1,1,0,0,0,0,0,0,1,1,1,5,5,5,7,7,5,5,5,1,1,1,0,0,0,0,0,0,1,1,1,5,5,5,7,7,5,5,5,1,1,1,0,0,0,0,0,0,0,1,1,6,6,6,6,6,6,6,6,6,1,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,3,4,0,0,0,0,0,0,4,3,0,0,0,0,0,0,0,0,0,3,4,1,1,1,1,1,1,1,1,4,3,0,0,0,0,0,0,0,0,3,4,1,2,1,1,1,1,2,1,4,3,0,0,0,0,0,0,0,0,3,4,1,1,1,1,1,1,1,1,4,3,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,5,5,5,5,5,5,5,5,1,1,1,0,0,0,0,0,0,1,1,1,5,5,5,5,5,5,5,5,1,1,1,0,0,0,0,0,0,1,1,1,5,5,5,7,7,5,5,5,1,1,1,0,0,0,0,0,0,1,1,1,5,5,5,7,7,5,5,5,1,1,1,0,0,0,0,0,0,0,1,6,6,6,6,6,6,6,6,6,6,1,0,0,0,0,0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -214,7 +214,7 @@ static const uint8_t splash_work_coding_frames[23][400] = { }; static const uint16_t splash_work_coding_holds[23] = {180,180,180,180,140,180,180,180,180,180,180,90,90,400,300,280,300,200,180,180,180,180,180}; -static const uint16_t splash_work_think_palette[10] = {0x0000,0xCBED,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; +static const uint16_t splash_work_think_palette[10] = {0x0000,0xDBAA,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; static const uint8_t splash_work_think_frames[24][400] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,2,1,1,1,1,1,2,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -243,7 +243,7 @@ static const uint8_t splash_work_think_frames[24][400] = { }; static const uint16_t splash_work_think_holds[24] = {400,300,300,200,350,350,350,350,350,350,600,200,400,300,300,300,80,100,80,120,150,300,300,400}; -static const uint16_t splash_dance_bounce_dj_palette[10] = {0x0000,0xCBED,0x1082,0xC6DC,0x2965,0x5459,0x52AA,0xFFFF,0xEFBF,0x7D57}; +static const uint16_t splash_dance_bounce_dj_palette[10] = {0x0000,0xDBAA,0x1082,0xC6DC,0x2965,0x5459,0x52AA,0xFFFF,0xEFBF,0x7D57}; static const uint8_t splash_dance_bounce_dj_frames[16][400] = { {0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,7,0,0,0,0,8,8,8,8,8,8,8,8,0,0,0,0,7,0,0,0,0,0,0,8,9,0,0,0,0,0,0,9,8,0,0,0,0,0,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,2,1,1,1,2,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,8,9,0,0,0,0,0,0,9,8,0,0,0,0,0,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,2,1,1,1,2,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,7,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,7,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -264,7 +264,7 @@ static const uint8_t splash_dance_bounce_dj_frames[16][400] = { }; static const uint16_t splash_dance_bounce_dj_holds[16] = {90,80,140,80,60,80,80,100,90,80,160,80,60,80,80,100}; -static const uint16_t splash_dance_sway_dj_palette[10] = {0x0000,0xCBED,0x1082,0xC6DC,0x2965,0x5459,0x52AA,0xFFFF,0xEFBF,0x7D57}; +static const uint16_t splash_dance_sway_dj_palette[10] = {0x0000,0xDBAA,0x1082,0xC6DC,0x2965,0x5459,0x52AA,0xFFFF,0xEFBF,0x7D57}; static const uint8_t splash_dance_sway_dj_frames[12][400] = { {7,7,0,0,0,0,8,8,8,8,8,8,8,8,0,0,0,0,7,7,7,0,0,0,0,8,9,0,0,0,0,0,0,9,8,0,0,0,0,7,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,2,1,1,1,2,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {7,7,0,0,0,0,8,8,8,8,8,8,8,8,0,0,0,0,7,7,7,0,0,0,0,8,9,0,0,0,0,0,0,9,8,0,0,0,7,7,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,7,7,0,0,0,0,8,9,1,1,2,1,1,1,2,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -281,7 +281,7 @@ static const uint8_t splash_dance_sway_dj_frames[12][400] = { }; static const uint16_t splash_dance_sway_dj_holds[12] = {220,160,200,220,200,160,160,200,220,200,200,180}; -static const uint16_t splash_dance_djmix_palette[10] = {0x0000,0xCBED,0x1082,0xC6DC,0x2945,0x54DB,0x630C,0xFFFF,0xEFBF,0x7D57}; +static const uint16_t splash_dance_djmix_palette[10] = {0x0000,0xDBAA,0x1082,0xC6DC,0x2945,0x54DB,0x630C,0xFFFF,0xEFBF,0x7D57}; static const uint8_t splash_dance_djmix_frames[16][400] = { {7,7,0,0,0,0,8,8,8,8,8,8,8,8,0,0,0,0,7,7,7,7,0,0,0,8,9,0,0,0,0,0,0,9,8,0,0,0,7,7,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,2,1,1,1,2,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,0,4,5,6,5,6,5,6,5,6,5,6,5,6,5,6,4,0,0,0,0,4,5,6,5,6,5,6,5,6,5,6,5,6,5,6,4,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {7,7,0,0,0,8,9,0,0,0,0,0,0,9,8,0,0,0,7,7,7,7,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,7,7,0,0,0,0,8,9,1,1,2,1,1,1,2,1,9,8,0,0,0,0,0,0,0,0,8,9,1,1,1,1,1,1,1,1,9,8,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,0,4,2,6,2,6,2,6,2,6,5,6,5,6,5,6,4,0,0,0,0,4,2,6,2,6,2,6,2,6,5,6,5,6,5,6,4,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, diff --git a/screenshots/amoled_18/bluetooth.png b/screenshots/amoled_18/bluetooth.png deleted file mode 100644 index ad25f2a402c22ecc08b4c0edfe4e350d77ed074c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16966 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 diff --git a/screenshots/bluetooth.png b/screenshots/bluetooth.png deleted file mode 100644 index 4c9b1c3abf473b0caf847274a047da3b66788992..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14926 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 EFGgjDEC2ui diff --git a/tools/convert_to_c.js b/tools/convert_to_c.js index 21ee1b8..1cf3027 100644 --- a/tools/convert_to_c.js +++ b/tools/convert_to_c.js @@ -25,8 +25,17 @@ function safeIdent(s) { return s.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, ''); } +// Brand override: claudepix renders the creature body in a lighter, pinker +// terracotta (#CD7F6A) than Anthropic's brand terracotta. Remap it to the +// brand color (THEME_ACCENT, #D97757) so the splash matches the brand. +const TINT_OVERRIDE = { + '#cd7f6a': '#d97757', +}; + function hexToRgb565(hex) { if (!hex || hex === 'transparent') return 0x0000; // dark bg + const ov = TINT_OVERRIDE[hex.toLowerCase()]; + if (ov) hex = ov; let h = hex.replace('#', ''); if (h.length === 3) h = h.split('').map(c => c + c).join(''); const r = parseInt(h.substr(0, 2), 16); From 13cc4231a0e6655a84eae1214faf7c0c226de195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hermann=20Bj=C3=B6rgvin=20Haraldsson?= Date: Tue, 26 May 2026 09:03:12 +0000 Subject: [PATCH 5/6] =?UTF-8?q?Rename=20BLE=20device=20"Claude=20Controlle?= =?UTF-8?q?r"=20=E2=86=92=20"Clawdmeter"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ). Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 2 +- README.md | 4 ++-- daemon/claude-usage-daemon.sh | 6 +++--- daemon/claude_usage_daemon.py | 4 ++-- firmware/src/ble.cpp | 6 +++--- install-mac.sh | 2 +- install.sh | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 01143f9..66d0431 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -135,7 +135,7 @@ Bash daemon (`daemon/claude-usage-daemon.sh`) reads OAuth token, polls Anthropic **Discovery & resilience:** -- Connects by name (`"Claude Controller"`) on first run, caches resolved MAC at `~/.config/claude-usage-monitor/ble-address`. ESP32 BLE addresses are factory-burned per-chip, so swapping any board invalidates the cache. +- Connects by name (`"Clawdmeter"`) on first run, caches resolved MAC at `~/.config/claude-usage-monitor/ble-address`. ESP32 BLE addresses are factory-burned per-chip, so swapping any board invalidates the cache. - On connect failure: cache is dropped AND device is removed from bluez (`bluetoothctl remove`) so the next scan won't re-pick a dead MAC. Multi-candidate scans pick `head -1` and let the failure cycle converge. - `POLL_INTERVAL=60`, `TICK=5`. Inner loop wakes every 5s to detect disconnects fast; polls Anthropic when 60s elapsed OR when ESP fires a refresh request. diff --git a/README.md b/README.md index 6854f33..28539ab 100644 --- a/README.md +++ b/README.md @@ -92,13 +92,13 @@ The board env name is required. Run `./flash.sh` with no args to see the availab ### Pair the device -After flashing, the device advertises as "Claudemeter". Pair it once: +After flashing, the device advertises as "Clawdmeter". Pair it once: ```bash # Scan for the device bluetoothctl scan le -# When "Claude Controller" appears, pair and trust it +# When "Clawdmeter" appears, pair and trust it bluetoothctl pair F4:12:FA:C0:8F:E5 # use your device's MAC bluetoothctl trust F4:12:FA:C0:8F:E5 ``` diff --git a/daemon/claude-usage-daemon.sh b/daemon/claude-usage-daemon.sh index 0663904..c0ab0bf 100755 --- a/daemon/claude-usage-daemon.sh +++ b/daemon/claude-usage-daemon.sh @@ -1,10 +1,10 @@ #!/bin/bash # Claude Usage Tracker Daemon (BLE) # Reads Claude Code OAuth token, polls usage via API, sends to ESP32 over BLE GATT. -# Auto-connects and reconnects to the Claude Controller BLE device. +# Auto-connects and reconnects to the Clawdmeter BLE device. # Dependencies: curl, awk, bluetoothctl -DEVICE_NAME="Claude Controller" +DEVICE_NAME="Clawdmeter" DEVICE_MAC="${DEVICE_MAC:-}" # auto-discovered if empty SERVICE_UUID="4c41555a-4465-7669-6365-000000000001" RX_CHAR_UUID="4c41555a-4465-7669-6365-000000000002" @@ -60,7 +60,7 @@ save_mac() { echo "$DEVICE_MAC" > "$SAVED_MAC_FILE" } -# Scan for Claude Controller +# Scan for Clawdmeter scan_for_device() { log "Scanning for '$DEVICE_NAME'..." # Start LE scan diff --git a/daemon/claude_usage_daemon.py b/daemon/claude_usage_daemon.py index 311bedb..a207961 100755 --- a/daemon/claude_usage_daemon.py +++ b/daemon/claude_usage_daemon.py @@ -2,7 +2,7 @@ """Claude Usage Tracker Daemon (BLE) — macOS port of claude-usage-daemon.sh. Polls Claude API rate-limit headers and writes a JSON payload to the -ESP32 "Claude Controller" peripheral over a custom GATT service. Uses +ESP32 "Clawdmeter" peripheral over a custom GATT service. Uses bleak (CoreBluetooth backend on macOS). """ @@ -21,7 +21,7 @@ import httpx from bleak import BleakClient, BleakScanner from bleak.exc import BleakError -DEVICE_NAME = "Claude Controller" +DEVICE_NAME = "Clawdmeter" SERVICE_UUID = "4c41555a-4465-7669-6365-000000000001" RX_CHAR_UUID = "4c41555a-4465-7669-6365-000000000002" REQ_CHAR_UUID = "4c41555a-4465-7669-6365-000000000004" diff --git a/firmware/src/ble.cpp b/firmware/src/ble.cpp index 14d0a0f..b9a4642 100644 --- a/firmware/src/ble.cpp +++ b/firmware/src/ble.cpp @@ -3,7 +3,7 @@ #include #include -#define DEVICE_NAME "Claude Controller" +#define DEVICE_NAME "Clawdmeter" // Custom GATT UUIDs for data channel #define SERVICE_UUID "4c41555a-4465-7669-6365-000000000001" @@ -72,8 +72,8 @@ static void start_advertising() { NimBLEAdvertising* adv = NimBLEDevice::getAdvertising(); adv->reset(); // Primary advertising packet (≤31 bytes): - // flags (3) + appearance (4) + HID service 0x1812 (4) + name "Claude Controller" (19) - // = 30 bytes. macOS Bluetooth Settings only surfaces BLE-only devices + // flags (3) + appearance (4) + HID service 0x1812 (4) + name "Clawdmeter" (12) + // = 23 bytes. macOS Bluetooth Settings only surfaces BLE-only devices // that explicitly advertise the standard HID service UUID (0x1812) — // without it the device is recognized internally but hidden from the // GUI nearby-devices list. diff --git a/install-mac.sh b/install-mac.sh index 4303c02..6235bdb 100755 --- a/install-mac.sh +++ b/install-mac.sh @@ -75,7 +75,7 @@ echo "" echo "First-time Bluetooth pairing (after firmware is flashed):" echo " 1. Power on the device." echo " 2. Open System Settings → Bluetooth." -echo " 3. Click 'Connect' next to 'Claude Controller'." +echo " 3. Click 'Connect' next to 'Clawdmeter'." echo " 4. The daemon will discover it within ~30 s and start polling." echo "" echo "Useful commands:" diff --git a/install.sh b/install.sh index 2357258..c1d51ef 100755 --- a/install.sh +++ b/install.sh @@ -37,7 +37,7 @@ echo "" echo "First-time Bluetooth pairing:" echo " 1. Power on the SC01 Plus" echo " 2. Run: bluetoothctl scan le" -echo " 3. Find 'Claude Controller' and note the MAC address" +echo " 3. Find 'Clawdmeter' and note the MAC address" echo " 4. Run: bluetoothctl pair " echo " 5. Run: bluetoothctl trust " echo " 6. Start the daemon: systemctl --user start $SERVICE_NAME" From 65e51a09828907bf941ef74fc4dee4b6659e2e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hermann=20Bj=C3=B6rgvin=20Haraldsson?= Date: Sat, 30 May 2026 18:53:46 +0000 Subject: [PATCH 6/6] 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 --- firmware/src/boards/waveshare_amoled_216/display.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/firmware/src/boards/waveshare_amoled_216/display.cpp b/firmware/src/boards/waveshare_amoled_216/display.cpp index 14b8561..a1cf7b7 100644 --- a/firmware/src/boards/waveshare_amoled_216/display.cpp +++ b/firmware/src/boards/waveshare_amoled_216/display.cpp @@ -1,5 +1,6 @@ #include "../../hal/display_hal.h" #include "../../hal/imu_hal.h" +#include "../../brightness.h" #include "board.h" #include #include @@ -119,8 +120,11 @@ void display_hal_tick(void) { if (now - ramp_last < 25) return; ramp_last = now; - static const uint8_t levels[] = {60, 120, 170, 200}; - display_hal_set_brightness(levels[ramp_step - 1]); + // Ramp back to the user's chosen brightness (not a hardcoded level), so a + // physical rotation doesn't reset what they set via PWR-short-press. + static const uint8_t pct[] = {30, 60, 85, 100}; + uint8_t target = brightness_get(); + display_hal_set_brightness((uint8_t)(((uint16_t)target * pct[ramp_step - 1]) / 100)); if (ramp_step >= 4) ramp_step = 0; else ramp_step++; }