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
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,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`
- **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://<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
- **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://<IP>/api/config
# Zapis konfiguracji
# Zapis konfiguracji gestów
POST http://<IP>/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)
+10 -14
View File
@@ -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();