4.2 KiB
4.2 KiB
ESP32-C6 Desk Buddy — Project Memory
Hardware
- Board: Seeed XIAO ESP32-C6
- Display: SSD1306 128x64 OLED
- Gesture sensor: CJMCU-7620 (PAJ7620U2, I2C addr 0x73)
I2C — CRITICAL
- ESP32-C6 has two I2C buses:
- I2C0 (HP,
Wire): configurable to any GPIO — USE THIS - I2C1 (LP,
Wire1): hardware-locked to SDA=GPIO6, SCL=GPIO7 — NOT usable on XIAO (pins not exposed)
- I2C0 (HP,
- Both SSD1306 and PAJ7620 share one bus:
Wire.begin(22, 23)— SDA=GPIO22(D4), SCL=GPIO23(D5) - SSD1306=0x3C, PAJ7620=0x73 — different addresses, coexist fine
- SW I2C (U8g2 bit-bang) blocks CPU ~40ms/frame — kills WiFi on single-core ESP32-C6. Always use HW I2C.
- U8g2 HW I2C constructor:
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 23, 22)
PAJ7620 API
- Library:
acrandal/RevEng PAJ7620 - Gesture type:
Gesture(NOTGesture_t) - begin() takes pointer:
sensor.begin(&Wire)(NOT reference) Wire1pre-defined in framework — do not redeclareTwoWire Wire1(1)- Gesture index:
(int)g - 1maps GES_UP(1)..GES_WAVE(9) to 0..8
HTTP Server — CRITICAL
WebServer.hdoes NOT work reliably on ESP32-C6 with IDF 5.x (pioarduino platform)- Use
AsyncWebServerfrommathieucarbou/ESPAsyncWebServer@^3.3.12 - No
handleClient()needed — callback-based, works on interrupts - JSON POST body handler: use the 3-arg
on()with body callback (4th param)
NVS / Preferences
- Always open with
prefs.begin("namespace", false)—true(read-only) fails with NOT_FOUND if namespace doesn't exist yet - NVS key max 15 chars — use short prefixes: u/d/l/r/f/b/cw/ccw/w for gestures
platformio.ini
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = seeed_xiao_esp32c6
framework = arduino
lib_deps =
olikraus/U8g2
acrandal/RevEng PAJ7620
bblanchon/ArduinoJson@^7.2.1
mathieucarbou/ESPAsyncWebServer@^3.3.12
build_flags =
-std=gnu++17
board_build.partitions = default # no Zigbee partitions needed
Desk Buddy — Architecture
- Moods (0-11): NORMAL, HAPPY, SLEEPY, SURPRISED, ANGRY, SAD, EXCITED, WINK_L, WINK_R, HUNGRY, PLAYFUL, DIRTY
- Actions (0-6): NONE, DATETIME, WIFI, FEED, PLAY, CLEAN, STATUS
- Eyes:
drawFilledEllipse+ mood-specific shapes; wink =drawCircleUPPER arc - Wink: WINK_L closes screen-RIGHT eye (!isLeft = buddy's left), WINK_R closes screen-LEFT
- Blink: state machine OPEN→CLOSING→CLOSED→OPENING, 4px/tick
- Pupil: smooth drift toward random target every 1.5-4s (NORMAL only)
- Sleepy: triggered after 5 min idle (
lastEventtracking), ZZZ bubbles - Auto-revert:
revertAttimestamp, 0 = permanent mood - Display refresh: 50ms (~20fps) — OK with HW I2C
Webhook System
- Per-gesture config: URL (128 chars) + mood (0=none, 1-6) + enabled
- Fired async via FreeRTOS
xTaskCreate— non-blocking - POST
{"gesture":"wave"}to configured URL, 3s timeout - Config persisted in NVS, served/edited via HTTP at GET/POST /
- JSON API: GET /api/config, POST /api/config
Key Files
src/main.cpp— hardware + drawing + HTTP server + WiFi + FreeRTOS (~400 lines)lib/BuddyDomain/— BuddyTypes.h, BuddyLogic.h/.cpp (mood, blink, pupil — no Arduino)lib/TamaLogic/— TamaLogic.h/.cpp (hunger/happiness/hygiene ticks — no Arduino)lib/GestureConfig/— GestureConfig.h/.cpp (struct, enums, string tables)platformio.ini— [env:esp32-c6] + [env:native]test/native/test_buddy/+test/native/test_tama/— Unity tests (40 total)- Reference project:
../handsensor/— webhook/config patterns
Testing
pio test -e nativeortask test— runs 40 unit tests on Mac (no device needed)pio run -e esp32-c6— firmware build- Domain libs have zero Arduino deps:
<algorithm>only; RngFn injection for determinism
Domain API
initBuddy(BuddyState &b, uint32_t now)— pass millis()setBuddyMood(BuddyState &b, Mood m, uint32_t now, uint32_t durationMs=0)updateBuddyAnim(BuddyState &b, uint32_t now, RngFn rng)— RngFn = int32_t(*)(lo,hi)initTama(TamaState &t, uint32_t now)updateTama(TamaState &t, const BuddyState &b, uint32_t now)→ returns Mood overridetamaFeed/Play/Clean(TamaState &t)— direct state mutations