# 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 i wyświetla informacje na żądanie. 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 7 nastrojów z różnymi kształtami oczu: | Nastrój | Wygląd | Opis | |---------|--------|------| | `normal` | pełne oczy, dryfujące źrenice | domyślny | | `happy` | łukowe oczy `^_^` | górna połowa oka zasłonięta | | `sleepy` | opuszczona powieka `zZz` | bąbelki ZZZ, wolne mruganie | | `surprised` | wielkie oczy `o_O` | uniesione brwi | | `angry` | zmrużone `>_<` | skośne brwi do środka | | `sad` | smutne `T_T` | odwrócone brwi, łzy | | `excited` | gwiazdy `*_*` | wzór × zamiast źrenic | Dodatkowe animacje: - **Mruganie** — co 2,5–6 s (szybsze w trybie `sleepy`) - **Ruch źrenic** — płynny drift do losowej pozycji co 1,5–4 s (tylko `normal`) - **Auto-uśpienie** — po 5 minutach bez gestu przechodzi w `sleepy` ### 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 - Nastrój po wysłaniu webhooka konfigurowalny niezależnie ### Akcje Akcje wyświetlają informacje na OLED przez 8 sekund, potem wracają do twarzy: | Akcja | Co pokazuje | |-------|------------| | **Data i godzina** | `HH:MM:SS` (duża czcionka), `DD.MM.RRRR`, dzień tygodnia | | **Status WiFi** | SSID, adres IP, RSSI w dBm, słupki siły sygnału | 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:/// ``` Dla każdego gestu można ustawić: - **URL webhoka** — endpoint HTTP POST (pusty = wyłączony) - **Nastrój** — jaki nastrój ustawić po geście/webhooku - **Akcja** — co wyświetlić na OLED - **ON** — czy gest jest aktywny ### JSON API ```bash # Odczyt konfiguracji GET http:///api/config # Zapis konfiguracji POST http:///api/config Content-Type: application/json { "wave": { "url": "http://homeassistant.local:8123/api/webhook/my_hook", "mood": 6, "action": 0, "enabled": true }, "up": { "url": "", "mood": 0, "action": 1, "enabled": true } } ``` #### Wartości `mood` | Wartość | Nastrój | |---------|---------| | 0 | bez zmiany | | 1 | happy | | 2 | sleepy | | 3 | surprised | | 4 | angry | | 5 | sad | | 6 | excited | #### Wartości `action` | Wartość | Akcja | |---------|-------| | 0 | brak | | 1 | data i godzina | | 2 | status WiFi | ### Konfiguracja WiFi Dane sieci przechowywane są w pliku `data/config.json` na LittleFS (osobny obszar flash). Plik **nie jest commitowany do repozytorium** (`.gitignore`). ```json { "wifi": { "ssid": "twoja_siec", "password": "twoje_haslo" } } ``` Plik wgrywany jest osobnym poleceniem (`task uploadfs`) — zmiana WiFi nie wymaga rekompilacji firmware. --- ## Instalacja i build ### Wymagania - [PlatformIO](https://platformio.org/) (CLI lub VS Code extension) - [Task](https://taskfile.dev/) (`brew install go-task`) ### Pierwsze uruchomienie ```bash cp data/config.json.example data/config.json # lub edytuj ręcznie # uzupełnij ssid i password w 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 | | `task uploadfs` | Wgraj tylko `data/config.json` | | `task monitor` | Monitor portu szeregowego | | `task flash-monitor` | Pełne wgranie + monitor | | `task config-upload` | Edytuj config WiFi + wgraj FS | | `task erase-flash` | Skasuj flash i wgraj od nowa | | `task` | Lista wszystkich zadań | ### Zmiana WiFi bez rekompilacji ```bash # Edytuj data/config.json, potem: task uploadfs ``` ### Zależności (pobierane automatycznie) ```ini olikraus/U8g2 acrandal/RevEng PAJ7620 bblanchon/ArduinoJson@^7.2.1 mathieucarbou/ESPAsyncWebServer@^3.3.12 ``` --- ## Architektura kodu Cały projekt mieści się w jednym pliku `src/main.cpp` (~700 linii). ``` setup() loadWiFiConfig() ← LittleFS /config.json → WIFI_SSID/PASS Wire.begin(22, 23) ← I2C0 HP dla obu urządzeń u8g2.begin() ← SSD1306 HW I2C sensor.begin(&Wire) ← PAJ7620 HW I2C connectWiFi() configTzTime(...) ← NTP sync setupHttpServer() ← AsyncWebServer port 80 loop() ← ~200 fps bez rysowania sensor.readGesture() ← polling co 500 ms handleGesture() executeAction() ← overlay na OLED fireWebhook() ← FreeRTOS task (async) setBuddyMood() updateBuddyAnim() ← tick co 50 ms (stan) showBuddyScreen() ← rysowanie co 50 ms (~20 fps) showDateTimeScreen() ← overlay jeśli aktywny showWiFiStatusScreen() ← overlay jeśli 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 | | NVS klucze max 15 znaków | Skrócone prefiksy gestów: `u/d/l/r/f/b/cw/ccw/w` | | `Preferences.begin("ns", true)` crash gdy namespace nie istnieje | Otwierać zawsze z `false` | --- ## Dodawanie nowych akcji 1. Dodaj wartość do `enum Action` w `main.cpp` 2. Zaktualizuj `NUM_ACTIONS` i `ACTION_LABELS[]` 3. Napisz funkcję `showXxxScreen()` 4. Dodaj `case ACTION_XXX:` w `showBuddyScreen()` (sekcja overlay)