Files
Omino/memory/MEMORY.md
T

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)
  • 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

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: <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 override
  • tamaFeed/Play/Clean(TamaState &t) — direct state mutations