sec: dane wifi nie pobierane przez endpoint do konfiguracji

This commit is contained in:
2026-06-05 13:44:03 +02:00
parent 7f66b93edb
commit 7ba9c55e27
2 changed files with 108 additions and 67 deletions
+98 -53
View File
@@ -1,6 +1,6 @@
# ESP32-C6 Desk Buddy # 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ę. 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ę.
--- ---
@@ -37,22 +37,41 @@ SCL → D5 (GPIO23)
### Animacja oczu (Buddy) ### Animacja oczu (Buddy)
Ciągła animacja na OLED ~20 fps. Buddy ma 7 nastrojów z różnymi kształtami oczu: 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 | | Nastrój | Wygląd | Opis |
|---------|--------|------| |---------|--------|------|
| `normal` | pełne oczy, dryfujące źrenice | domyślny | | `normal` | pełne oczy, dryfujące źrenice | domyślny |
| `happy` | łukowe oczy `^_^` | górna połowa oka zasłonięta | | `happy` | pełne oczy, uśmiech | wesołe policzki |
| `sleepy` | opuszczona powieka `zZz` | bąbelki ZZZ, wolne mruganie | | `sleepy` | poziome kreski `zZz` | bąbelki ZZZ, wolne mruganie |
| `surprised` | wielkie oczy `o_O` | uniesione brwi | | `surprised` | wielkie oczy `o_O` | szeroko otwarte |
| `angry` | zmrużone `>_<` | skośne brwi do środka | | `angry` | klinowe oczy `>_<` | usta w dół |
| `sad` | smutne `T_T` | odwrócone brwi, łzy | | `sad` | dolna połowa oka `T_T` | łzy, usta w górę |
| `excited` | gwiazdy `*_*` | wzór × zamiast źrenic | | `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: Dodatkowe animacje:
- **Mruganie** — co 2,56 s (szybsze w trybie `sleepy`) - **Mruganie** — co 2,56 s (szybsze w trybie `sleepy`)
- **Ruch źrenic** — płynny drift do losowej pozycji co 1,54 s (tylko `normal`) - **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` - **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) ### Gesty (PAJ7620)
@@ -85,16 +104,19 @@ Content-Type: application/json
- Wywołanie asynchroniczne (FreeRTOS task) — nie blokuje animacji - Wywołanie asynchroniczne (FreeRTOS task) — nie blokuje animacji
- Timeout 3 sekundy - Timeout 3 sekundy
- Nastrój po wysłaniu webhooka konfigurowalny niezależnie
### Akcje ### Akcje
Akcje wyświetlają informacje na OLED przez 8 sekund, potem wracają do twarzy: Akcje wyświetlają informacje lub reagują na potrzeby Buddy'ego:
| Akcja | Co pokazuje | | Akcja | Czas | Co robi |
|-------|------------| |-------|------|---------|
| **Data i godzina** | `HH:MM:SS` (duża czcionka), `DD.MM.RRRR`, dzień tygodnia | | **Data i godzina** | 8 s | `HH:MM:SS`, `DD.MM.RRRR`, dzień tygodnia |
| **Status WiFi** | SSID, adres IP, RSSI w dBm, słupki siły sygnału | | **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). Czas synchronizowany przez NTP (`pool.ntp.org`) — strefa CET/CEST (Polska).
@@ -112,17 +134,17 @@ http://<IP>/
Dla każdego gestu można ustawić: Dla każdego gestu można ustawić:
- **URL webhoka** — endpoint HTTP POST (pusty = wyłączony) - **URL webhoka** — endpoint HTTP POST (pusty = wyłączony)
- **Nastrój** — jaki nastrój ustawić po geście/webhooku - **Nastrój** — jaki nastrój ustawić po geście (0 = bez zmiany)
- **Akcja** — co wyświetlić na OLED - **Akcja** — co zrobić/wyświetlić na OLED
- **ON** — czy gest jest aktywny - **ON** — czy gest jest aktywny
### JSON API ### JSON API
```bash ```bash
# Odczyt konfiguracji # Odczyt konfiguracji gestów
GET http://<IP>/api/config GET http://<IP>/api/config
# Zapis konfiguracji # Zapis konfiguracji gestów
POST http://<IP>/api/config POST http://<IP>/api/config
Content-Type: application/json Content-Type: application/json
@@ -130,13 +152,13 @@ Content-Type: application/json
"wave": { "wave": {
"url": "http://homeassistant.local:8123/api/webhook/my_hook", "url": "http://homeassistant.local:8123/api/webhook/my_hook",
"mood": 6, "mood": 6,
"action": 0, "action": 4,
"enabled": true "enabled": true
}, },
"up": { "up": {
"url": "", "url": "",
"mood": 0, "mood": 0,
"action": 1, "action": 3,
"enabled": true "enabled": true
} }
} }
@@ -146,13 +168,18 @@ Content-Type: application/json
| Wartość | Nastrój | | Wartość | Nastrój |
|---------|---------| |---------|---------|
| 0 | bez zmiany | | 0 | bez zmiany (domyślna reakcja gestu) |
| 1 | happy | | 1 | happy |
| 2 | sleepy | | 2 | sleepy |
| 3 | surprised | | 3 | surprised |
| 4 | angry | | 4 | angry |
| 5 | sad | | 5 | sad |
| 6 | excited | | 6 | excited |
| 7 | wink L |
| 8 | wink R |
| 9 | hungry |
| 10 | playful |
| 11 | dirty |
#### Wartości `action` #### Wartości `action`
@@ -161,21 +188,32 @@ Content-Type: application/json
| 0 | brak | | 0 | brak |
| 1 | data i godzina | | 1 | data i godzina |
| 2 | status WiFi | | 2 | status WiFi |
| 3 | nakarm (głód 30) |
| 4 | pobaw się (szczęście +25) |
| 5 | umyj (higiena +40) |
| 6 | status tamagotchi |
### Konfiguracja WiFi ### Konfiguracja WiFi
Dane sieci przechowywane są w pliku `data/config.json` na LittleFS (osobny obszar flash). Plik **nie jest commitowany do repozytorium** (`.gitignore`). 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 ```json
{ {
"wifi": { "ssid": "twoja_siec",
"ssid": "twoja_siec", "password": "twoje_haslo"
"password": "twoje_haslo"
}
} }
``` ```
Plik wgrywany jest osobnym poleceniem (`task uploadfs`) — zmiana WiFi nie wymaga rekompilacji firmware. ### 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 }
}
```
--- ---
@@ -189,10 +227,14 @@ Plik wgrywany jest osobnym poleceniem (`task uploadfs`) — zmiana WiFi nie wyma
### Pierwsze uruchomienie ### Pierwsze uruchomienie
```bash ```bash
cp data/config.json.example data/config.json # lub edytuj ręcznie # Konfiguracja WiFi
# uzupełnij ssid i password w data/config.json cp data/wifi.json.example data/wifi.json
nano data/wifi.json # uzupełnij ssid i password
task flash-monitor # uploadfs + upload + monitor # 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 ### Typowe komendy
@@ -202,18 +244,19 @@ task flash-monitor # uploadfs + upload + monitor
| `task build` | Tylko kompilacja | | `task build` | Tylko kompilacja |
| `task flash` | Wgraj filesystem + firmware | | `task flash` | Wgraj filesystem + firmware |
| `task upload` | Wgraj tylko firmware | | `task upload` | Wgraj tylko firmware |
| `task uploadfs` | Wgraj tylko `data/config.json` | | `task uploadfs` | Wgraj `data/wifi.json` + `data/config.json` |
| `task monitor` | Monitor portu szeregowego | | `task monitor` | Monitor portu szeregowego |
| `task flash-monitor` | Pełne wgranie + monitor | | `task flash-monitor` | Pełne wgranie + monitor |
| `task config-upload` | Edytuj config WiFi + wgraj FS | | `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 erase-flash` | Skasuj flash i wgraj od nowa |
| `task` | Lista wszystkich zadań | | `task` | Lista wszystkich zadań |
### Zmiana WiFi bez rekompilacji ### Zmiana WiFi bez rekompilacji
```bash ```bash
# Edytuj data/config.json, potem: task wifi # otwiera edytor + automatycznie uploadfs
task uploadfs
``` ```
### Zależności (pobierane automatycznie) ### Zależności (pobierane automatycznie)
@@ -229,28 +272,29 @@ mathieucarbou/ESPAsyncWebServer@^3.3.12
## Architektura kodu ## Architektura kodu
Cały projekt mieści się w jednym pliku `src/main.cpp` (~700 linii). Cały projekt mieści się w jednym pliku `src/main.cpp`.
``` ```
setup() setup()
loadWiFiConfig() ← LittleFS /config.json → WIFI_SSID/PASS loadAllConfig()
Wire.begin(22, 23) ← I2C0 HP dla obu urządzeń /wifi.json → WIFI_SSID/PASS (nigdy nie serwowane)
u8g2.begin() ← SSD1306 HW I2C /config.json → gConfig[] (gesty)
sensor.begin(&Wire) ← PAJ7620 HW I2C initBuddy() + initTama()
connectWiFi() Wire.begin(22, 23) ← I2C0 HP dla obu urządzeń
configTzTime(...) ← NTP sync u8g2.begin() ← SSD1306 HW I2C
setupHttpServer() ← AsyncWebServer port 80 sensor.begin(&Wire) ← PAJ7620 HW I2C
connectWiFi() → NTP sync
setupHttpServer() ← AsyncWebServer port 80
loop() ← ~200 fps bez rysowania loop() ← polling
sensor.readGesture() ← polling co 500 ms sensor.readGesture() co 500 ms
handleGesture() handleGesture()
executeAction() overlay na OLED executeAction() ← tama efekt + overlay OLED
fireWebhook() ← FreeRTOS task (async) setBuddyMood() ← skonfigurowany lub domyślny
setBuddyMood() fireWebhook() ← FreeRTOS task (async)
updateBuddyAnim() ← tick co 50 ms (stan) updateTama() co 10 s (potrzeby + mood override)
showBuddyScreen() ← rysowanie co 50 ms (~20 fps) updateBuddyAnim() ← co 50 ms (mruganie, saccady, ZZZ)
showDateTimeScreen() ← overlay jeśli aktywny showBuddyScreen() ← co 50 ms (~20 fps)
showWiFiStatusScreen() ← overlay jeśli aktywny
``` ```
### Kluczowe decyzje techniczne ### Kluczowe decyzje techniczne
@@ -260,8 +304,8 @@ loop() ← ~200 fps bez rysowania
| SW I2C blokuje WiFi 40ms/frame na single-core ESP32-C6 | HW I2C (Wire) dla obu urządzeń na wspólnej magistrali | | 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()`) | | `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 | | `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` | | `setContrast()` ma zbyt wąski zakres wizualny na SSD1306 | `setPowerSave(1/0)` — komenda 0xAE/0xAF wyłącza panel |
| `Preferences.begin("ns", true)` crash gdy namespace nie istnieje | Otwierać zawsze z `false` | | Hasło WiFi dostępne przez HTTP | Oddzielny `/wifi.json` — nigdy nie serwowany; `/config.json` zawiera tylko gesty |
--- ---
@@ -271,3 +315,4 @@ loop() ← ~200 fps bez rysowania
2. Zaktualizuj `NUM_ACTIONS` i `ACTION_LABELS[]` 2. Zaktualizuj `NUM_ACTIONS` i `ACTION_LABELS[]`
3. Napisz funkcję `showXxxScreen()` 3. Napisz funkcję `showXxxScreen()`
4. Dodaj `case ACTION_XXX:` w `showBuddyScreen()` (sekcja overlay) 4. Dodaj `case ACTION_XXX:` w `showBuddyScreen()` (sekcja overlay)
5. Opcjonalnie: dodaj efekt tama w `executeAction()` (np. zmiana potrzeby)
+10 -14
View File
@@ -1055,22 +1055,18 @@ void setupHttpServer()
saveConfig(); saveConfig();
req->send(200, "application/json", "{\"ok\":true}"); }); req->send(200, "application/json", "{\"ok\":true}"); });
// GET /config.json — gesture config only (WiFi credentials are NEVER exposed) // GET /config.json — gesture config only, built from RAM (WiFi NEVER exposed)
httpServer.on("/config.json", HTTP_GET, [](AsyncWebServerRequest *req) { httpServer.on("/config.json", HTTP_GET, [](AsyncWebServerRequest *req) {
if (!LittleFS.begin(false)) { JsonDocument doc;
req->send(500, "application/json", "{\"error\":\"LittleFS unavailable\"}"); for (uint8_t i = 0; i < NUM_GESTURES; i++) {
return; doc[GNAME[i]]["url"] = gConfig[i].url;
doc[GNAME[i]]["mood"] = gConfig[i].mood;
doc[GNAME[i]]["action"] = gConfig[i].action;
doc[GNAME[i]]["enabled"] = gConfig[i].enabled;
} }
File f = LittleFS.open("/config.json", "r"); String out;
if (!f) { serializeJsonPretty(doc, out);
LittleFS.end(); req->send(200, "application/json", out);
req->send(404, "application/json", "{\"error\":\"not found\"}");
return;
}
String body = f.readString();
f.close();
LittleFS.end();
req->send(200, "application/json", body);
}); });
httpServer.begin(); httpServer.begin();