# 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) - **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` (NOT `Gesture_t`) - begin() takes pointer: `sensor.begin(&Wire)` (NOT reference) - `Wire1` pre-defined in framework — do not redeclare `TwoWire Wire1(1)` - Gesture index: `(int)g - 1` maps GES_UP(1)..GES_WAVE(9) to 0..8 ## HTTP Server — CRITICAL - `WebServer.h` does NOT work reliably on ESP32-C6 with IDF 5.x (pioarduino platform) - Use `AsyncWebServer` from `mathieucarbou/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 ```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 = `drawCircle` UPPER 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 (`lastEvent` tracking), ZZZ bubbles - Auto-revert: `revertAt` timestamp, 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 native` or `task test` — runs 40 unit tests on Mac (no device needed) - `pio run -e esp32-c6` — firmware build - Domain libs have zero Arduino deps: `` 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 override - `tamaFeed/Play/Clean(TamaState &t)` — direct state mutations