From 7ba9c55e2769ea28a10979b323f846c4958cfb94 Mon Sep 17 00:00:00 2001 From: Aleksander Cynarski Date: Fri, 5 Jun 2026 13:44:03 +0200 Subject: [PATCH] sec: dane wifi nie pobierane przez endpoint do konfiguracji --- README.md | 151 +++++++++++++++++++++++++++++++++------------------ src/main.cpp | 24 ++++---- 2 files changed, 108 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 83fbd06..b33d7a8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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) -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 | |---------|--------|------| | `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 | +| `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** — płynny drift do losowej pozycji co 1,5–4 s (tylko `normal`) +- **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) @@ -85,16 +104,19 @@ Content-Type: application/json - 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: +Akcje wyświetlają informacje lub reagują na potrzeby Buddy'ego: -| 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 | +| 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). @@ -112,17 +134,17 @@ 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 +- **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 +# Odczyt konfiguracji gestów GET http:///api/config -# Zapis konfiguracji +# Zapis konfiguracji gestów POST http:///api/config Content-Type: application/json @@ -130,13 +152,13 @@ Content-Type: application/json "wave": { "url": "http://homeassistant.local:8123/api/webhook/my_hook", "mood": 6, - "action": 0, + "action": 4, "enabled": true }, "up": { "url": "", "mood": 0, - "action": 1, + "action": 3, "enabled": true } } @@ -146,13 +168,18 @@ Content-Type: application/json | Wartość | Nastrój | |---------|---------| -| 0 | bez zmiany | +| 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` @@ -161,21 +188,32 @@ Content-Type: application/json | 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/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 { - "wifi": { - "ssid": "twoja_siec", - "password": "twoje_haslo" - } + "ssid": "twoja_siec", + "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 ```bash -cp data/config.json.example data/config.json # lub edytuj ręcznie -# uzupełnij ssid i password w data/config.json +# Konfiguracja WiFi +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 @@ -202,18 +244,19 @@ task flash-monitor # uploadfs + upload + monitor | `task build` | Tylko kompilacja | | `task flash` | Wgraj filesystem + 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 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` | Lista wszystkich zadań | ### Zmiana WiFi bez rekompilacji ```bash -# Edytuj data/config.json, potem: -task uploadfs +task wifi # otwiera edytor + automatycznie uploadfs ``` ### Zależności (pobierane automatycznie) @@ -229,28 +272,29 @@ mathieucarbou/ESPAsyncWebServer@^3.3.12 ## 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() - 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 + 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() ← ~200 fps bez rysowania - sensor.readGesture() ← polling co 500 ms +loop() ← polling + sensor.readGesture() ← 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 + 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 @@ -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 | | `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` | +| `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 | --- @@ -271,3 +315,4 @@ loop() ← ~200 fps bez rysowania 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) diff --git a/src/main.cpp b/src/main.cpp index f58c5a2..7bf520d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1055,22 +1055,18 @@ void setupHttpServer() saveConfig(); 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) { - if (!LittleFS.begin(false)) { - req->send(500, "application/json", "{\"error\":\"LittleFS unavailable\"}"); - return; + JsonDocument doc; + for (uint8_t i = 0; i < NUM_GESTURES; i++) { + 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"); - if (!f) { - LittleFS.end(); - req->send(404, "application/json", "{\"error\":\"not found\"}"); - return; - } - String body = f.readString(); - f.close(); - LittleFS.end(); - req->send(200, "application/json", body); + String out; + serializeJsonPretty(doc, out); + req->send(200, "application/json", out); }); httpServer.begin();