sec: dane wifi nie pobierane przez endpoint do konfiguracji
This commit is contained in:
@@ -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://<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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user