# 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,5–6 s (szybsze w trybie `sleepy`) - **Ruch źrenic** — saccady do losowej pozycji co 1,5–4,5 s we wszystkich nastrojach; podczas snu wolny drift co 4–9 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 1–2) | | Chmura | Zachmurzenie, mgła (WMO 3–48) | | Deszcz | Mżawka, deszcz, przelotny deszcz (WMO 51–82) | | Śnieg | Śnieg i opady śnieżne (WMO 71–86) | | Burza | Burza z piorunami (WMO 95–99) | Dane z [Open-Meteo](https://open-meteo.com/) — **darmowe, 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:/// ``` 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 ```bash # Odczyt / zapis konfiguracji gestów GET http:///api/config POST http:///api/config (Content-Type: application/json) # Stan Tamagotchi GET http:///api/tama # Akcje Tamagotchi POST http:///api/tama/feed POST http:///api/tama/play POST http:///api/tama/clean # Aktualna pogoda + konfiguracja GET http:///api/weather # Zapis konfiguracji pogody (form POST) POST http:///weather/save # Test nastroju (0–11) na 10 sekund POST http:///api/mood/test?m=1 ``` Przykład zapisu gestów: ```json { "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. ```json { "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`. ```json { "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 - [PlatformIO](https://platformio.org/) (CLI lub VS Code extension) - [Task](https://taskfile.dev/) (`brew install go-task`) ### Pierwsze uruchomienie ```bash # 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 ```bash task wifi # otwiera edytor + automatycznie uploadfs ``` ### Zależności (pobierane automatycznie) ```ini 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 ```bash 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)