ESP32-C6 Desk Buddy

Animowany "biurkowy przyjaciel" na Seeed XIAO ESP32-C6 z czujnikiem gestów PAJ7620 i wyświetlaczem OLED 128×64. Reaguje na gesty emocjami, wykonuje webhooki HTTP, wyświetla informacje na żądanie, symuluje potrzeby w stylu Tamagotchi i pokazuje pogodę z Open-Meteo. Konfigurowany przez przeglądarkę.


Sprzęt

Komponent Model Adres I2C
Mikrokontroler Seeed XIAO ESP32-C6
Wyświetlacz SSD1306 OLED 128×64 0x3C
Czujnik gestów CJMCU-7620 (PAJ7620U2) 0x73

Podłączenie

Oba urządzenia dzielą jedną magistralę I2C (hardware, HP I2C bus 0):

SSD1306 / PAJ7620       XIAO ESP32-C6
──────────────────────────────────────
VCC                 →   3.3V
GND                 →   GND
SDA                 →   D4 (GPIO22)
SCL                 →   D5 (GPIO23)

Ważne: ESP32-C6 ma dwa kontrolery I2C:

  • I2C0 (HP, Wire) — dowolne piny GPIO ✓
  • I2C1 (LP, Wire1) — zablokowany sprzętowo na GPIO6/GPIO7, niedostępne na XIAO ✗

Nie używaj Wire1 ani SW I2C (bit-bang blokuje WiFi na single-core CPU ~40 ms/frame).


Funkcje

Animacja oczu (Buddy)

Ciągła animacja na OLED ~20 fps. Buddy ma 12 nastrojów z różnymi kształtami oczu i ust:

Nastrój Wygląd Opis
normal pełne oczy, dryfujące źrenice domyślny
happy pełne oczy, uśmiech wesołe policzki
sleepy poziome kreski zZz bąbelki ZZZ, wolne mruganie
surprised wielkie oczy o_O szeroko otwarte
angry klinowe oczy >_< usta w dół
sad dolna połowa oka T_T łzy, usta w górę
excited gwiazdy *_* wzór × zamiast źrenic
wink L ;) prawe oko otwarte, lewe zamknięte
wink R (; lewe oko otwarte, prawe zamknięte
hungry zmniejszone oczy, usta otwarte aktywowany przez głód
playful iskrzące oczy aktywowany przez nudę
dirty rozognione źrenice aktywowany przez potrzebę mycia

Dodatkowe animacje:

  • Mruganie — co 2,56 s (szybsze w trybie sleepy)
  • Ruch źrenic — saccady do losowej pozycji co 1,54,5 s we wszystkich nastrojach; podczas snu wolny drift co 49 s
  • Micro-tremor — drobne drżenie podczas fiksacji (tylko normal/happy)
  • Auto-uśpienie — po 5 minutach bez gestu przechodzi w sleepy
  • Nocne wygaszenie — między 00:00 a 05:00, po 5 minutach braku aktywności wyświetlacz gaśnie; wraca przy geście

Tamagotchi — potrzeby

Buddy ma trzy potrzeby narastające z czasem:

Potrzeba Kierunek Tempo Próg alarmu Nastrój
Głód 0=syt → 100=głodny +1/2 min ≥ 80 hungry
Szczęście 100=wesoły → 0=znudzony 1/3 min ≤ 20 playful
Higiena 100=czysty → 0=brudny 1/4 min ≤ 20 dirty

Gdy potrzeba przekroczy próg, Buddy automatycznie zmienia nastrój. W lewym dolnym rogu ekranu pojawiają się litery: G (głód), Z (zabawa), M (mycie).

Pogoda (Open-Meteo)

Co jakiś czas (konfigurowalny interwał) wyświetlany jest ekran pogodowy — zamiast oczu pojawia się ikona pogodowa, a zamiast ust — temperatura i ciśnienie:

Ikona Warunki
Słońce Bezchmurnie (WMO 0)
Chmura + słońce Częściowe zachmurzenie (WMO 12)
Chmura Zachmurzenie, mgła (WMO 348)
Deszcz Mżawka, deszcz, przelotny deszcz (WMO 5182)
Śnieg Śnieg i opady śnieżne (WMO 7186)
Burza Burza z piorunami (WMO 9599)

Dane z Open-Meteodarmowe, bez klucza API. Dwuetapowe pobieranie: geocoding (nazwa miasta → współrzędne) + aktualna prognoza.

Gesty (PAJ7620)

Czujnik rozpoznaje 9 gestów ręką:

Gest Domyślna reakcja
up happy
down sad
left surprised
right surprised
forward sleepy (trwały)
backward angry
clockwise excited
anticlockwise normal
wave excited

Każdemu gestowi można przypisać własny nastrój, webhook i akcję przez panel konfiguracyjny.

Webhooki

Każdy gest może wywoływać webhook HTTP POST na skonfigurowany URL:

POST http://twoj-serwer/endpoint
Content-Type: application/json

{"gesture": "wave"}
  • Wywołanie asynchroniczne (FreeRTOS task) — nie blokuje animacji
  • Timeout 3 sekundy

Akcje

Akcje wyświetlają informacje lub reagują na potrzeby Buddy'ego:

Akcja Czas Co robi
Data i godzina 8 s HH:MM:SS, DD.MM.RRRR, dzień tygodnia
Status WiFi 8 s SSID, IP, RSSI, słupki siły sygnału
Nakarm 3 s głód 30, nastrój happy
Pobaw się 3 s szczęście +25, nastrój excited
Umyj 3 s higiena +40, nastrój surprised
Status tamagotchi 8 s paski postępu: głód / szczęście / higiena

Czas synchronizowany przez NTP (pool.ntp.org) — strefa CET/CEST (Polska).


Konfiguracja

Panel webowy

Po uruchomieniu wejdź przeglądarką na adres IP wypisany w Serial (lub na OLED przy starcie):

http://<IP>/

Panel zawiera cztery sekcje:

Gesty

Dla każdego gestu można ustawić URL webhoka, nastrój, akcję i czy gest jest aktywny.

Tamagotchi

Podgląd aktualnych potrzeb (głód, szczęście, higiena) z paskami postępu oraz przyciski: Nakarm, Pobaw się, Umyj.

Pogoda

Aktualny stan pogody (ikona, temperatura, ciśnienie) oraz ustawienia:

  • Miasto — nazwa w języku polskim lub angielskim (np. Warszawa, Kraków)
  • Co ile sekund — interwał wyświetlania ekranu pogodowego (0 = wyłączony)
  • Pokazuj przez (s) — czas trwania ekranu pogodowego

Test nastroju

12 przycisków do ręcznego ustawienia dowolnego nastroju na 10 sekund — do testowania wyglądu oczu bez czujnika gestów.

JSON API

# Odczyt / zapis konfiguracji gestów
GET  http://<IP>/api/config
POST http://<IP>/api/config          (Content-Type: application/json)

# Stan Tamagotchi
GET  http://<IP>/api/tama

# Akcje Tamagotchi
POST http://<IP>/api/tama/feed
POST http://<IP>/api/tama/play
POST http://<IP>/api/tama/clean

# Aktualna pogoda + konfiguracja
GET  http://<IP>/api/weather

# Zapis konfiguracji pogody (form POST)
POST http://<IP>/weather/save

# Test nastroju (011) na 10 sekund
POST http://<IP>/api/mood/test?m=1

Przykład zapisu gestów:

{
  "wave": {
    "url": "http://homeassistant.local:8123/api/webhook/my_hook",
    "mood": 6,
    "action": 0,
    "enabled": true
  },
  "up": {
    "url": "",
    "mood": 0,
    "action": 3,
    "enabled": true
  }
}

Wartości mood

Wartość Nastrój
0 bez zmiany (domyślna reakcja gestu)
1 happy
2 sleepy
3 surprised
4 angry
5 sad
6 excited
7 wink L
8 wink R
9 hungry
10 playful
11 dirty

Wartości action

Wartość Akcja
0 brak
1 data i godzina
2 status WiFi
3 nakarm (głód 30)
4 pobaw się (szczęście +25)
5 umyj (higiena +40)
6 status tamagotchi

Konfiguracja WiFi

Dane sieci przechowywane są w pliku data/wifi.json na LittleFS. Plik nie jest commitowany i nigdy nie jest serwowany przez HTTP — hasło WiFi pozostaje tylko na urządzeniu.

{
  "ssid": "twoja_siec",
  "password": "twoje_haslo"
}

Konfiguracja gestów

Plik data/config.json zawiera wyłącznie konfigurację gestów (bez danych WiFi). Jest dostępny przez GET /config.json i pobieralny komendą task config-download.

{
  "up":   { "url": "", "mood": 0, "action": 3, "enabled": true },
  "wave": { "url": "http://ha.local/webhook/x", "mood": 6, "action": 0, "enabled": true }
}

Konfiguracja pogody

Konfiguracja przechowywana w /weather.json na LittleFS — zapisywana przez panel webowy, persystuje przez restarty. Nie wymaga reflashowania.


Instalacja i build

Wymagania

Pierwsze uruchomienie

# Konfiguracja WiFi
cp data/wifi.json.example data/wifi.json
nano data/wifi.json          # uzupełnij ssid i password

# Konfiguracja gestów (opcjonalnie — domyślne wartości działają od razu)
cp data/config.json.example data/config.json

task flash-monitor           # uploadfs + upload + monitor

Typowe komendy

Komenda Opis
task build Tylko kompilacja
task flash Wgraj filesystem + firmware
task upload Wgraj tylko firmware (zachowuje LittleFS)
task uploadfs Wgraj data/wifi.json + data/config.json
task monitor Monitor portu szeregowego
task flash-monitor Pełne wgranie + monitor
task wifi Edytuj WiFi (data/wifi.json) + wgraj FS
task gestures Edytuj gesty (data/config.json) + wgraj FS
task config-download Pobierz konfigurację gestów z urządzenia
task erase-flash Skasuj flash i wgraj od nowa
task test Uruchom testy jednostkowe (native, na Mac)
task Lista wszystkich zadań

Uwaga: task uploadfs nadpisuje LittleFS — konfiguracja pogody zapisana przez panel webowy zostanie utracona. Użyj task upload do aktualizacji samego firmware'u.

Zmiana WiFi bez rekompilacji

task wifi   # otwiera edytor + automatycznie uploadfs

Zależności (pobierane automatycznie)

olikraus/U8g2
acrandal/RevEng PAJ7620
bblanchon/ArduinoJson@^7.2.1
mathieucarbou/ESPAsyncWebServer@^3.3.12

Architektura kodu

Logika domenowa wydzielona do bibliotek w lib/ — zero zależności od Arduino, kompilowalne natywnie.

src/main.cpp          — hardware + HTTP server + WiFi + FreeRTOS (~1100 linii)
lib/
  BuddyDomain/        — BuddyTypes.h, BuddyLogic.h/.cpp (nastroje, mruganie, źrenice)
  TamaLogic/          — TamaLogic.h/.cpp (potrzeby, ticki, override nastroju)
  GestureConfig/      — GestureConfig.h/.cpp (struct, enums, tablice stringów)
test/native/
  test_buddy/         — ~21 testów Unity (initBuddy, mruganie, saccady)
  test_tama/          — ~19 testów Unity (ticki, progi, tamaFeed/Play/Clean)
data/
  wifi.json           — dane WiFi (nie commitowane)
  config.json         — konfiguracja gestów
partitions.csv        — własna tablica partycji (app 1.75 MB, LittleFS 2.19 MB)

Przepływ inicjalizacji

setup()
  loadAllConfig()
    /wifi.json    → WIFI_SSID/PASS (nigdy nie serwowane)
    /config.json  → gConfig[] (gesty)
    /weather.json → weatherCfg (miasto, interwał)
  initBuddy() + initTama()
  Wire.begin(22, 23)     ← I2C0 HP dla obu urządzeń
  u8g2.begin()           ← SSD1306 HW I2C
  sensor.begin(&Wire)    ← PAJ7620 HW I2C
  connectWiFi() → NTP sync
  setupHttpServer()      ← AsyncWebServer port 80

loop()
  sensor.readGesture()   ← co 500 ms
    handleGesture()
      executeAction()    ← efekt tama + overlay OLED
      setBuddyMood()     ← skonfigurowany lub domyślny
      fireWebhook()      ← FreeRTOS task (async)
  updateTama()           ← co 10 s (potrzeby + mood override)
  updateBuddyAnim()      ← co 50 ms (mruganie, saccady, ZZZ)
  showBuddyScreen()      ← co 50 ms (~20 fps)
    drawWeatherFace()    ← priorytet gdy weatherShowUntil aktywny

Kluczowe decyzje techniczne

Problem Rozwiązanie
SW I2C blokuje WiFi 40ms/frame na single-core ESP32-C6 HW I2C (Wire) dla obu urządzeń na wspólnej magistrali
WebServer.h nie działa na ESP32-C6 z IDF 5.x ESPAsyncWebServer (callback-based, brak handleClient())
Wire1 (LP I2C) zablokowany na GPIO6/7 (niedostępne na XIAO) Jeden Wire (HP I2C) dla SSD1306 + PAJ7620
setContrast() ma zbyt wąski zakres wizualny na SSD1306 setPowerSave(1/0) — komenda 0xAE/0xAF wyłącza panel
Hasło WiFi dostępne przez HTTP Oddzielny /wifi.json — nigdy nie serwowany; /config.json zawiera tylko gesty
Firmware nie mieścił się w partycji OTA (1.28 MB) Własna partitions.csv: app0 1.75 MB, LittleFS 2.19 MB
Domena logiki — testy natywne bez sprzętu Biblioteki w lib/ bez zależności Arduino; pio test -e native

Testy

task test           # pio test -e native — 40 testów na Mac, bez urządzenia

Testy pokrywają: inicjalizację stanu, cykl mrugania (OPEN→CLOSING→CLOSED→OPENING), saccady źrenic, ticki tamagotchi, progi nastrojów, tamaFeed/Play/Clean z wartościami brzegowymi.


Dodawanie nowych akcji

  1. Dodaj wartość do enum Action w GestureConfig.h
  2. Zaktualizuj NUM_ACTIONS i ACTION_LABELS[] w GestureConfig.cpp
  3. Napisz funkcję showXxxScreen()
  4. Dodaj case ACTION_XXX: w showBuddyScreen() (sekcja overlay)
  5. Opcjonalnie: dodaj efekt tama w executeAction() (np. zmiana potrzeby)
S
Description
No description provided
Readme 172 KiB
Languages
C++ 90.9%
C 6.2%
Shell 2.9%