Merge pull request #49 from HermannBjorgvin/feature/clawdmeter-ux-collapse
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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; touch-toggle anytime | Session and weekly utilization | Connection status and bond reset |
|
||||
| Splash | Usage |
|
||||
| :-------------------------------: | :-----------------------------: |
|
||||
|  |  |
|
||||
| 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
|
||||
|
||||
@@ -92,18 +92,18 @@ 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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <NimBLEDevice.h>
|
||||
#include <NimBLEHIDDevice.h>
|
||||
|
||||
#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.
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "../../hal/display_hal.h"
|
||||
#include "../../hal/imu_hal.h"
|
||||
#include "../../brightness.h"
|
||||
#include "board.h"
|
||||
#include <Arduino.h>
|
||||
#include <Arduino_GFX_Library.h>
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
#include <Wire.h>
|
||||
#include <XPowersLib.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "brightness.h"
|
||||
#include "idle.h"
|
||||
#include <Preferences.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
// 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];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// 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)
|
||||
@@ -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);
|
||||
|
||||
+13
-5
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
+56
-3
@@ -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();
|
||||
|
||||
@@ -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},
|
||||
|
||||
+91
-162
@@ -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: "<glyph> <Title-case word>…"
|
||||
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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
+1
-1
@@ -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:"
|
||||
|
||||
+1
-1
@@ -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 <MAC>"
|
||||
echo " 5. Run: bluetoothctl trust <MAC>"
|
||||
echo " 6. Start the daemon: systemctl --user start $SERVICE_NAME"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user