><
← Wszystkie projekty

Urządzenie · Projekt własny · 2026

Zdzichu

Toksyczny helpdesk w kieszeni. Obraża Cię z lodowatą elegancją i mimo to rozwiązuje problem.

Fizyczne urządzenie (M5Stack Core2) z pikselową twarzą wypalonego testera IT: słuchawki z mikrofonem, krzywo osadzone okulary, rozluźniony krawat. Stukasz w twarz, zadajesz pytanie, stukasz znowu, nagranie idzie do własnego backendu na VPS-ie, a wraca gotowa odpowiedź głosowa. Aplikacja na Androida ma tę samą twarz i korzysta z tego samego backendu.

ESP32M5UnifiedKotlinNode.jsGemini APIDocker

Rola: Pomysł, firmware, aplikacja na Androida, backend i wdrożenie, całość solo.

Zdzichu

M5Stack Core2

Urządzenie

ESP32

Procesor

Node.js + Gemini

Backend

( Wyzwanie )

Postać miała być nieznośna i użyteczna jednocześnie, a te dwa wymagania ciągną w przeciwne strony. Żart przestaje być śmieszny w sekundzie, w której zaczyna przeszkadzać w rozwiązaniu problemu. Do tego cały ciężki pipeline (rozpoznawanie mowy, generowanie odpowiedzi, synteza głosu) nie miał jak zmieścić się na ESP32, a klucze API wgrane do firmware oznaczałyby rozdanie ich każdemu, kto weźmie urządzenie do ręki. I jeszcze ekran: 320×240 pikseli bez wygładzania, na którym emocje muszą być czytelne z drugiego końca pokoju.

( Rozwiązanie )

Urządzenie jest cienkim klientem: zna wyłącznie adres backendu i jeden współdzielony sekret, zero kluczy API na sprzęcie. Jedna tura rozmowy to jedno żądanie: surowe PCM leci POST-em do backendu w Dockerze na moim VPS-ie, tam Gemini robi transkrypcję, odpowiedź i syntezę mowy, a wraca gotowe audio do natychmiastowego odtworzenia. Emocje rozwiązałem jak manometr, nie jak minę: pięć stanów twarzy, w których wypiek wściekłości wspina się od kołnierzyka w górę z każdą kolejną turą, a pod całym jadem prompt systemowy pilnuje, żeby została działająca odpowiedź, bo to jedyny zawodowy obowiązek Zdzicha.

( Proces )
  1. 01Twarz jako maszyna stanów: pięć stanów rysowanych tym samym zestawem pikselowych prymitywów wprost na ekranie 320×240, bez wygładzania.
  2. 02Sterowanie głosem: tap-to-talk na dotykowej twarzy, nagranie 16 kHz / 16-bit mono, drugi tap kończy i wysyła.
  3. 03Backend w Node.js (Express) w Dockerze: jedno żądanie na turę, sekret urządzenia w nagłówku, surowe PCM w ciele.
  4. 04Warstwa modelu: Gemini zwraca transkrypcję i odpowiedź w jednym przejściu, potem osobno syntezę mowy, a prompt systemowy trzyma granicę między jadem a treścią.
  5. 05Port na Androida: te same współrzędne twarzy co w firmware i ten sam protokół, więc aplikacja i urządzenie rozmawiają z tym samym backendem.
  6. 06Wdrożenie: build i deploy przez GitHub Actions na własny VPS, za Nginx Proxy Managerem.
( Galeria )
Spokój. Pokrywa trzyma, prawa brew uniesiona, zero błysku w oku.
Spokój. Pokrywa trzyma, prawa brew uniesiona, zero błysku w oku.
Nasłuch. Wypiek wchodzi na szyję, mikrofon świeci na czerwono, jedyny stan, w którym czerwień w ogóle się pojawia.
Nasłuch. Wypiek wchodzi na szyję, mikrofon świeci na czerwono, jedyny stan, w którym czerwień w ogóle się pojawia.
Myślenie. Oczy zamknięte, żwacz zaciśnięty najmocniej z całego zestawu, dusi to w sobie, czekając na backend.
Myślenie. Oczy zamknięte, żwacz zaciśnięty najmocniej z całego zestawu, dusi to w sobie, czekając na backend.
Mówi. Wypiek podchodzi pod oczy, szczęka opada z każdą sylabą, na skroni pierwsza żyła.
Mówi. Wypiek podchodzi pod oczy, szczęka opada z każdą sylabą, na skroni pierwsza żyła.
Eskalacja. Pokrywa puszcza: cała czaszka czerwona, okulary zaparowane, para ze słuchawek.
Eskalacja. Pokrywa puszcza: cała czaszka czerwona, okulary zaparowane, para ze słuchawek.
( Funkcje )

Tap-to-talk

Stukasz w twarz, mówisz, stukasz znowu. Żadnego przycisku, żadnego słowa aktywującego i żadnego nasłuchu w tle.

Twarz jak manometr

Pięć stanów: spokój, nasłuch, myślenie, mówienie i eskalacja. Wypiek wściekłości wspina się od kołnierzyka w górę z każdą kolejną turą rozmowy.

Zero kluczy na sprzęcie

Urządzenie zna tylko adres backendu i jeden współdzielony sekret. Klucze do modelu zna wyłącznie backend, więc rozkręcenie obudowy nic nie daje.

Jedna tura, jedno żądanie

Nagranie w surowym PCM idzie jednym POST-em, wraca gotowe audio, bez sesji do utrzymywania i bez stanu po stronie urządzenia.

Ta sama twarz na Androidzie

Aplikacja to port 1:1 współrzędnych z firmware, nie przybliżenie i rozmawia z tym samym backendem, tym samym protokołem.

Głos, nie napisy

Odpowiedź wraca jako mowa (synteza Gemini, głos „Charon") i odtwarza się od razu po odebraniu.

Sarkazm z granicą

Na system i świat Zdzichu bluzga jak szewc, ale wobec użytkownika trzyma lodowaty, chirurgicznie precyzyjny sarkazm bez wulgaryzmów, to celowa granica wpisana w prompt.

Własna infrastruktura

Cały pipeline stoi w Dockerze na moim VPS-ie i wdraża się sam przy każdym pushu.

( Specyfikacja )
Urządzenie
M5Stack Core2 (ESP32, ekran 320×240)
Firmware
C++ / M5Unified (PlatformIO)
Aplikacja
Android (Kotlin, zero zewnętrznych zależności)
Backend
Node.js (Express) w Dockerze, własny VPS
Model
Gemini, transkrypcja, odpowiedź i synteza mowy
Audio
nagranie 16 kHz / 16-bit mono, odtwarzanie 24 kHz
Wdrożenie
GitHub Actions, Nginx Proxy Manager
( Prywatność )

Urządzenie nie zna żadnego klucza do modelu, ma tylko adres backendu i jeden współdzielony sekret w nagłówku żądania, więc dostęp do sprzętu nie daje dostępu do API. Samo nagranie jest przetwarzane po drodze: idzie do mojego backendu, a stamtąd do Gemini po transkrypcję, odpowiedź i syntezę głosu. Nic nie nagrywa się w tle, mikrofon startuje dopiero po stuknięciu w twarz i kończy po drugim stuknięciu.