319 lines
9.5 KiB
Markdown
319 lines
9.5 KiB
Markdown
# 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 i symuluje potrzeby w stylu Tamagotchi. 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).
|
||
|
||
### 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>/
|
||
```
|
||
|
||
Dla każdego gestu można ustawić:
|
||
- **URL webhoka** — endpoint HTTP POST (pusty = wyłączony)
|
||
- **Nastrój** — jaki nastrój ustawić po geście (0 = bez zmiany)
|
||
- **Akcja** — co zrobić/wyświetlić na OLED
|
||
- **ON** — czy gest jest aktywny
|
||
|
||
### JSON API
|
||
|
||
```bash
|
||
# Odczyt konfiguracji gestów
|
||
GET http://<IP>/api/config
|
||
|
||
# Zapis konfiguracji gestów
|
||
POST http://<IP>/api/config
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"wave": {
|
||
"url": "http://homeassistant.local:8123/api/webhook/my_hook",
|
||
"mood": 6,
|
||
"action": 4,
|
||
"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 }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 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 |
|
||
| `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` | Lista wszystkich zadań |
|
||
|
||
### 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
|
||
|
||
Cały projekt mieści się w jednym pliku `src/main.cpp`.
|
||
|
||
```
|
||
setup()
|
||
loadAllConfig()
|
||
/wifi.json → WIFI_SSID/PASS (nigdy nie serwowane)
|
||
/config.json → gConfig[] (gesty)
|
||
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() ← polling
|
||
sensor.readGesture() ← co 500 ms
|
||
handleGesture()
|
||
executeAction() ← tama efekt + 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)
|
||
```
|
||
|
||
### 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 |
|
||
|
||
---
|
||
|
||
## 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)
|
||
5. Opcjonalnie: dodaj efekt tama w `executeAction()` (np. zmiana potrzeby)
|