Files
Omino/README.md
T

399 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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-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://<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
```bash
# 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:
```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)