2026-06-05 13:35:41 +02:00
2026-06-05 13:35:41 +02:00
2026-06-05 01:03:27 +02:00
2026-06-05 01:03:27 +02:00
2026-06-05 13:35:41 +02:00
2026-06-05 01:03:27 +02:00
2026-06-05 13:35:41 +02:00
2026-06-05 13:35:41 +02:00
2026-06-05 13:35:41 +02:00
2026-06-05 13:35:41 +02:00
2026-06-05 01:03:27 +02:00

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

# 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 przechowywane są w pliku data/config.json na LittleFS (osobny obszar flash). Plik nie jest commitowany do repozytorium (.gitignore).

{
  "wifi": {
    "ssid": "twoja_siec",
    "password": "twoje_haslo"
  }
}

Plik wgrywany jest osobnym poleceniem (task uploadfs) — zmiana WiFi nie wymaga rekompilacji firmware.


Instalacja i build

Wymagania

Pierwsze uruchomienie

cp data/config.json.example data/config.json  # lub edytuj ręcznie
# uzupełnij ssid i password w data/config.json

task flash-monitor   # uploadfs + upload + monitor

Typowe komendy

Komenda Opis
task build Tylko kompilacja
task flash Wgraj filesystem + firmware
task upload Wgraj tylko firmware
task uploadfs Wgraj tylko data/config.json
task monitor Monitor portu szeregowego
task flash-monitor Pełne wgranie + monitor
task config-upload Edytuj config WiFi + wgraj FS
task erase-flash Skasuj flash i wgraj od nowa
task Lista wszystkich zadań

Zmiana WiFi bez rekompilacji

# Edytuj data/config.json, potem:
task uploadfs

Zależności (pobierane automatycznie)

olikraus/U8g2
acrandal/RevEng PAJ7620
bblanchon/ArduinoJson@^7.2.1
mathieucarbou/ESPAsyncWebServer@^3.3.12

Architektura kodu

Cały projekt mieści się w jednym pliku src/main.cpp (~700 linii).

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

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)
S
Description
No description provided
Readme 172 KiB
Languages
C++ 90.9%
C 6.2%
Shell 2.9%