feat: initial commit

This commit is contained in:
2026-06-05 01:03:27 +02:00
commit 65bd552aec
8 changed files with 1285 additions and 0 deletions
+258
View File
@@ -0,0 +1,258 @@
# 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,56 s (szybsze w trybie `sleepy`)
- **Ruch źrenic** — płynny drift do losowej pozycji co 1,54 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://<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/webhooku
- **Akcja** — co wyświetlić na OLED
- **ON** — czy gest jest aktywny
### JSON API
```bash
# Odczyt konfiguracji
GET http://<IP>/api/config
# Zapis konfiguracji
POST http://<IP>/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 są na razie hardcoded w `src/main.cpp`:
```cpp
const char *WIFI_SSID = "twoja_siec";
const char *WIFI_PASS = "twoje_haslo";
```
---
## Instalacja i build
### Wymagania
- [PlatformIO](https://platformio.org/) (CLI lub VS Code extension)
### Build i upload
```bash
cd esp32-c6
pio run --target upload
pio device monitor
```
### Zależności (pobierane automatycznie)
```ini
olikraus/U8g2
acrandal/RevEng PAJ7620
bblanchon/ArduinoJson@^7.2.1
mathieucarbou/ESPAsyncWebServer@^3.3.12
```
### platformio.ini
```ini
[env:esp32-c6]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = seeed_xiao_esp32c6
framework = arduino
lib_deps =
olikraus/U8g2
acrandal/RevEng PAJ7620
bblanchon/ArduinoJson@^7.2.1
mathieucarbou/ESPAsyncWebServer@^3.3.12
build_flags = -std=gnu++17
```
---
## Architektura kodu
Cały projekt mieści się w jednym pliku `src/main.cpp` (~650 linii).
```
setup()
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)