SpokoMeter: cyfrowy wskaźnik samochodowy

· 7 min czytania

Cyfrowy wskaźnik samochodowy na ESP32 z aplikacją na telefon, zbudowany dzięki AI w Kotlinie i C++.

Cyfrowy wskaźnik samochodowy na ESP32 z aplikacją na telefon, zbudowany dzięki AI w Kotlinie i C++.

W skrócie

Problem
Gotowe cyfrowe wskaźniki do auta kosztują kilkaset euro, są zamknięte, a ich rozbudowa jest droga. Chciałem własny, który mogę dowolnie dostosować i rozwijać.
Co powstało
Cyfrowy wskaźnik samochodowy z okrągłym ekranem 360×360 na ESP32-S3, który wygląda jak część z fabryki VW, aplikacja SpokoMeter na Androida, która łączy się z nim przez Bluetooth i go konfiguruje, i widok w Android Auto na ekranie radia.
Moja rola
Pomysł, kierunek wizualny, prowadzenie agentów AI i review każdej zmiany, a poza kodem elektronika, lutowanie i obudowa drukowana w 3D.
Co jest w tym ciekawego
Wcześniej nie napisałem linijki ani w Kotlinie, ani w C++ i nie zrobiłem żadnej natywnej aplikacji mobilnej, tylko PWA i APK z Vue przez Capacitor. Zadziałała metoda pracy przeniesiona z projektów webowych.
Efekt
Działający wskaźnik i aplikacja po kilku dniach: ponad 130 zmergowanych pull requestów w dwóch repozytoriach, każdy przez lokalny build, testy i review.

Skąd pomysł

Mam VW Polo 6R, przy którym modyfikacje stały się moim hobby, więc ten projekt nie wziął się z przypadku. Kilka lat temu zebrałem dostępne opcje w przeglądzie dodatkowych wskaźników do Polo 6R. Od kilku lat jeżdżę też z niemieckim wyświetlaczem CANchecked, który opisałem w recenzji. Działa, ale jak na urządzenie za ponad 650 euro jest wykonany słabo. Obudowa to tani wydruk 3D, a wyświetlacz ma przestarzałe złącze microUSB, które nawet z kątową wtyczką nie mieściło się dobrze, więc producent po prostu je podciął, żeby pasowało. Rozbudowa też kosztuje: sam pasek kilku diod do sygnalizacji zmiany biegu to około 300 zł.

Lubię DIY, więc zrobiłem własne rozwiązanie: otwarte i takie, które mogę dowolnie dostosować i rozwijać. Tak samo podchodzę do stron internetowych, bo zawsze jest coś do poprawienia i ulepszenia.

Gdyby chodziło tylko o klasyczne zegary, wystarczyłyby dodatkowe wskaźniki z Beetle albo Scirocco. Mnie interesował wskaźnik cyfrowy, który da się dowolnie dostosować, a kiedyś może trafi do obudowy środkowych zegarów ze Scirocco. Projekt był przede wszystkim testem możliwości.

Wygląd miał jednak pasować do auta, stąd główna zasada projektu: domyślnie wszystko wygląda jak oryginalne zegary, czyli font VW, czerwone pola z kropek jak w 6R i segmenty LCD jak we wskaźniku paliwa. Każdy dodatek jest opcją, którą się włącza.

Tarcze powstawały metodą prób i błędów. Projektował je Claude Code, a ja wiele razy zlecałem poprawki, porównując wynik z moimi zdjęciami zegarów 6R z bliska: tu przesunąć element, tam pogrubić linię albo zmienić odstęp. Chciałem, żeby wyglądały jak fabryczne, a jednocześnie pozwalały zmienić styl i wyświetlane parametry.

Do tego doszły trzy wymagania:

  • Konfiguracja z telefonu. Telefon łączy się ze wskaźnikiem przez Bluetooth i ustawia strony, układy, kanały i kolory, a podgląd w aplikacji zgadza się z wyświetlaczem co do piksela.
  • Rozszerzalność. Nowy kanał danych, nowy układ albo nowe źródło (CAN, OBD) nie może oznaczać przepisywania połowy kodu.
  • Android Auto. Wartości na żywo, przełączanie stron i kody usterek na ekranie radia.
SpokoMeter, zakładka Gauge: podgląd tarczy i dane z jazdy, czyli obroty, doładowanie, olej, prędkość, bieg, płyn chłodniczy, paliwo, zasięg i napięcieSpokoMeter, zakładka Look: galeria tarcz (VW, Segments) i edycja kolorów, wskazówki oraz skali z oznaczeniem zmienionych elementówSpokoMeter, zakładka Car: lista kodów usterek, jeden aktywny i dwa zapisane, z opisem modułu i usterkiSpokoMeter, zakładka History: zapisane jazdy z wartościami maksymalnymi i minimalnymi oraz eksportem do CSV

Co już potrafi

  • Strony i układy: klasyczna tarcza, kilka wartości naraz, układ boczny z łukami, zegar i mapa.
  • Ikony kanałów w stylu kontrolek VW.
  • Kanał napięcia akumulatora, dodany jednego popołudnia.
  • Mapę z OpenStreetMap, dodaną trochę dla zabawy: wygląda jak minimapa z GTA, ma motywy kolorów i działa offline.
  • Kody usterek (DTC) i Android Auto.

Źródłem danych z auta będzie magistrala CAN. Moduł CAN jest następnym krokiem, więc na razie wartości pochodzą z symulatora wbudowanego w firmware. Cała reszta, czyli wyświetlacz, aplikacja, protokół i Android Auto, działa na prawdziwym sprzęcie.

Tak wygląda wskaźnik podłączony testowo na moim biurku:

Wskaźnik na biurku: sportowa tarcza obrotomierza z biegiem i prędkością na środku, w tle płytka ESP32 z czerwoną diodąWskaźnik na biurku: tarcza w stylu VW z prędkością na środku, doładowaniem u góry i wskazówkami oleju i płynu chłodniczego po bokachWskaźnik na biurku: mapa z OpenStreetMap z drogami wokół i strzałką pozycjiWskaźnik na biurku: analogowa tarcza zegara z pomarańczowymi wskazówkami i datą, obok płytka ESP32 i przewody

Na krótkim filmie z biurka, opublikowanym na kanale polo.blue, wskaźnik przełącza kolejne strony:

Odtwórz film: cyfrowy wskaźnik do VW Polo 6R na ESP32

Architektura

  • Wyświetlacz ESP32-S3 z okrągłym ekranem GC9B72 360×360. C++, LVGL 9, NimBLE, PlatformIO.
  • Aplikacja SpokoMeter Kotlin, Material 3, ekrany przenoszone na Jetpack Compose. Usługa łącza BLE, historia jazd, mapy offline, testy UI w Robolectricu.
  • Android Auto Car App Library na szablonach: wartości na żywo, sterowanie stronami i lista kodów usterek na ekranie radia.

Wyświetlacz i telefon łączą się przez Bluetooth Low Energy (BLE) i rozmawiają własnym, małym protokołem ramek: ustawienia, strony, wartości na żywo, pozycja i większe paczki dzielone na kawałki z sumą kontrolną (zdjęcia tarcz, obszary mapy). Protokół opisuje jeden dokument, z którego korzystam ja i agenci pracujący nad obiema stronami.

Jak pracuję

Claude Code (plan Max 20x) prowadzi implementację, ale jedno polecenie w czacie by tu nie wystarczyło. Całość opiera się na tym samym procesie, którego używam w projektach webowych.

  1. Krok 1

    Opis i zdjęcie

    Mówię po polsku, czego chcę, zwykle ze zdjęciem: fabryczna tarcza 6R z bliska, dodatkowy zegar z Beetle, inne okrągłe wyświetlacze.

  2. Krok 2

    Issue i plan

    Claude zamienia to w issue i plan. Przy zmianach wizualnych najpierw robi szybkie podglądy kilku wariantów, a ja wybieram, zanim cokolwiek trafi na sprzęt.

  3. Krok 3

    Agenci równolegle

    Sub-agenci pracują osobno nad firmware i aplikacją. Część zadań z GitHuba robi samodzielnie Cezar, runner agentów od Open Mercato, i otwiera pull requesty.

  4. Krok 4

    Lokalne bramki

    Build, testy jednostkowe i lint lokalnie, bo nie polegam na GitHub Actions. Do tego test na prawdziwym wyświetlaczu przez port szeregowy: zrzut ekranu skryptem, fps i zużycie pamięci.

  5. Krok 5

    Review i merge

    Każdy PR przechodzi code review skillem om-code-review, a przed commitem kodu idzie /simplify. Merge robię dopiero po moim OK, agent nie scala swojej pracy sam.

  6. Krok 6

    Na biurku

    Flashuję wyświetlacz, instaluję aplikację na telefonie i oglądam efekt na żywo. Dopiero potem zaczyna się kolejna iteracja.

Część zadań przejmuje Cezar, otwartoźródłowy runner, który uruchamia agentów równolegle, a code review robi skill om-code-review z zestawu Open Mercato. Projekt ma też swoje zasady zapisane w plikach CLAUDE.md, które agenci czytają na starcie: kod i dokumentacja po angielsku, rozmowa po polsku, domyślne wartości jak w oryginalnych zegarach, każde ustawienie obsłużone w obu repozytoriach naraz.

Cezar przy zadaniu układu dwukolumnowego: agent renderuje aplikację na szerokim ekranie i tablecie i ogląda zrzuty, zanim otworzy pull request

Na zrzucie Cezar pracuje nad układem na szeroki ekran. Zanim otworzy pull request, sam renderuje aplikację w kilku rozmiarach ekranu i ogląda wynik, więc do review trafia zmiana, którą agent już widział. Ostateczną ocenę i tak robię sam, na telefonie.

Bramki jakości, które miały znaczenie

  • Testy zasad projektu Nic nie nachodzi na skalę, boczne skale są symetryczne, wskazówka nigdy nie zasłania cyfry, segmenty są równe. Zasady wizualne zapisałem jako testy, więc review nie musi ich pilnować.
  • Jedna geometria Podgląd w aplikacji i obraz na wyświetlaczu liczą się z tych samych wymiarów. Porównuję je obok siebie, więc rozjazd widać od razu.
  • Budżety na urządzeniu Klatki na sekundę, najwolniejsza klatka i fragmentacja pamięci LVGL mierzone na prawdziwym ESP32. Klasyczna tarcza z kropkami trzyma 44-45 fps.

Trzy historie z projektu

Wierność oryginałowi: kropki i segmenty

Dobrze widać to na dwóch detalach. Czerwone pole na fabrycznej tarczy 6R to raster kropek, które gęstnieją w stronę końca skali. Pierwsza wersja miała trójkąty, druga kropki, a kolejne poprawiały ich kształt i kierunek na podstawie moich zdjęć z bliska. AI dwa razy źle odczytało zdjęcie: odwróciło kierunek gradientu, a raz uznało wskazówkę za odwróconą, choć nie była. Rozstrzygało zestawienie obok siebie w czterokrotnym powiększeniu.

Porównanie czerwonego pola z kropek: dwa moje zdjęcia fabrycznej tarczy obrotomierza Polo 6R z bliska i ten sam fragment narysowany na wyświetlaczu

Podobnie było z paliwem. Zwykły łuk zamieniłem na segmenty LCD jak w podstawowym Polo, z wydzieloną rezerwą i jednym czerwonym segmentem, gdy paliwa jest mało.

Warianty paska paliwa z segmentami LCD przy pełnym baku i na rezerwie, z czerwonym segmentem rezerwy

Diagnoza na dowodach: wolne pobieranie mapy

Pobieranie mapy offline szło bardzo wolno. Zamiast zgadywać, zmierzyłem, gdzie ucieka czas, i okazało się, że hamują je publiczne serwery Overpass. Po przejściu na kafelki wektorowe OpenFreeMap 50 km dróg wokół domu pobiera się w 11 sekund, a wcześniej po kilku minutach było gotowe 2 z 37 fragmentów.

Motywy kolorów mapy: cztery palety na tym samym obszarze i te same palety na okrągłym ekranie z zaznaczoną trasą

Podgląd na komputerze, zanim coś trafi na wskaźnik

Na początku drobne poprawki wyglądu szły pełnym cyklem: zmiana, build, flash, zrzut z wyświetlacza, więc każda seria poprawek ciągnęła się bardzo długo. Odwróciłem kolejność: najpierw oglądam kilka wariantów obok siebie, renderowanych na komputerze, a na sprzęt trafia tylko wybrany. Tak powstawała większość zmian wyglądu, od pierwszej wersji tarczy do obecnej.

Wczesna wersja tarczy: skale oleju i płynu chłodniczego z czerwonymi polami z kropek i paliwo jako zwykły czerwony łuk
Początek: paliwo jako zwykły łuk
Obecna wersja tarczy: lżejsze skale z czerwonymi wskazówkami i paliwo jako segmenty LCD jak w Polo
Później: segmenty LCD i lżejsze skale

Podgląd i wyświetlacz rysuje ta sama geometria, więc przed każdym mergem kładę je jedno nad drugim. Dwie zmiany z ostatniego etapu sprawdzone w ten sposób:

Wartość na środku w czterech wariantach, czcionki VW i LCD w dużym i małym rozmiarze: u góry wyświetlacz, na dole podgląd w aplikacji
Czcionka i wielkość wartości na środku, wybierane w aplikacji: u góry wyświetlacz, na dole podgląd
Zegar cyfrowy z małymi wskazówkami godzin i minut na skali, w stylach VW i segmentowym: u góry wyświetlacz, na dole podgląd w aplikacji
Małe wskazówki na skali zegara cyfrowego, dodane po jednej uwadze: u góry wyświetlacz, na dole podgląd

Na czym się potykałem

  • Warstwa wizualna. Najwięcej czasu zajęło dopracowanie wyglądu. Z logiką i testami AI radzi sobie wyraźnie lepiej niż z odwzorowaniem projektu co do piksela.
  • Zdjęcia wzorcowe. Model źle odczytuje szczegóły ze zdjęć: kierunek, proporcje, co jest odwrócone. Wątpliwości szybko rozwiewa zestawienie obu wersji obok siebie w powiększeniu.
  • Wspólne zasoby. Równolegli agenci dzielili jeden port szeregowy do wyświetlacza. Raz build testowy, który nie znał nowego kanału, skasował moje ustawione strony. Odtworzyłem je z kopii, a w zasadach projektu pojawiła się nowa: odczytaj stan właściciela, przywróć go po teście i sprawdzaj na najnowszym buildzie.
  • Uprawnienia. Agent nie scala własnych PR-ów bez mojego wyraźnego OK. To bramka, którą zostawiłem celowo.

Poza kodem: lutownica, suwmiarka, drukarka

Claude Code przyspiesza każdą warstwę projektu, ale kilku rzeczy nie zrobi za mnie.

Elektronika. Moduł wyświetlacza przychodzi z luźną listwą pinów do wlutowania. Pin GND siedzi na dużym polu miedzi, więc łatwo o zimny lut, a objaw jest podstępny: podświetlenie gaśnie, gdy poruszy się przewodami. Potem trzeba połączyć wyświetlacz z ESP32-S3 według tabeli pinów, pamiętając, że moduł bierze 3,3 V, a nie 5 V, i zbudować dzielnik napięcia do pomiaru akumulatora, który kalibruję multimetrem.

Wskaźnik podłączony testowo na biurku: okrągły ekran z tarczą, za nim ESP32 i plątanina przewodów, z boku płytka stykowa

Obudowa. Zamiast wyklikać ją w programie CAD, opisałem ją kodem w OpenSCAD. Wymiary modułu wziąłem z rysunku producenta, a tych, których tam nie ma, jak grubość szkła nad płytką czy grubość lameli nawiewu, dopisałem z pomiarów suwmiarką. Model jest parametryczny: grubość ścianki odpowiada dwóm ścieżkom drukarki, a luzy pasowania są dobrane pod wydruk. Każda wersja ma też osobną część testową, czyli sam przód do szybkiego wydruku i przymiarki. To ta sama zasada co z podglądem tarcz na komputerze, tylko w plastiku.

Kolejne wersje obudowy z OpenSCAD: okrągły korpus, dwie ramki do kratki nawiewu, korpus mocowany na lameli, klips na lamelę i złożenie od tyłu

Model napisał Claude, ale pomiar, przymiarka i ocena „pasuje czy nie” zostały po mojej stronie.

Aplikacja i Android Auto

SpokoMeter to moja pierwsza natywna aplikacja. Ma pięć zakładek: podgląd wyświetlacza z danymi z bieżącej jazdy, strony i ich układy, wygląd, ustawienia auta (wersja silnika, skala, pole czerwone, kody usterek) i historię jazd z eksportem do CSV. Każdy element tarczy da się kliknąć w podglądzie i zmienić, a wyświetlacz odświeża się od razu.

Na początku była to po prostu aplikacja: klasyczne widoki Androida, podgląd tarczy i kilka ustawień. Następnego dnia rano doszło Material 3, wieczorem podstawy Jetpack Compose, a po nich obecny układ: pięć zakładek, ustawienia aplikacji pod ikoną koła zębatego i podgląd obok ustawień na szerokim ekranie. Ekrany przechodzą na Compose po kolei, bez przepisywania całości naraz.

Wczesna wersja aplikacji pod nazwą Spoko Gauge: jasny motyw, klasyczna tarcza obrotomierza, przycisk parowania i suwak jasności
Początek: jasna wersja „Spoko Gauge”
Obecna wersja aplikacji SpokoMeter: ciemny motyw, podgląd tarczy połączonego wyświetlacza i karta bieżącej jazdy
Później: SpokoMeter
SpokoMeter, zakładka Screens: lista stron wyświetlacza (tarcza, wartości, układ boczny, mapa) z kolejnością i przełącznikiem widocznościEdytor strony z układem bocznym: przypięty podgląd tarczy i lista miejsc z przypisanymi kanałamiPanel mapy w aplikacji: status, pobieranie na żądanie, motywy kolorów i orientacja północą do górySpokoMeter, zakładka Car: rodzaj paliwa, wersja silnika, skala obrotomierza, początek pola czerwonego i animacja wskazówek przy starcie

Na ekranie radia aplikacja pokazuje wartości na żywo, pozwala przełączać strony wyświetlacza i wyświetla listę kodów usterek.

SpokoMeter w Android Auto: sekcje Silnik i Temperatury z wartościami na żywo oraz przyciski poprzedniej i następnej stronySpokoMeter w Android Auto: lista kodów usterek

Co dalej

  • Moduł CAN i prawdziwe dane z auta: najpierw pasywny podsłuch magistrali, potem zapytania diagnostyczne o doładowanie i temperaturę oleju.
  • Kody usterek z OBD zamiast symulowanych.
  • Trzy wyświetlacze na jednym ESP32, w miejscu dodatkowych zegarów ze Scirocco.
  • Obsługa platformy MQB, a nie tylko PQ25.

Czego się nauczyłem

Przed tym projektem nie napisałem linijki ani w Kotlinie, ani w C++. Po kilku dniach aplikacja działa razem z wyświetlaczem i Android Auto. Pomogły mi nawyki z pracy nad stronami i aplikacjami webowymi: plan przed kodem, testy, review każdej zmiany i sprawdzanie zamiast zgadywania. AI pisze kod szybciej niż ja, ale to ja decyduję, co wchodzi, i odpowiadam za wynik.

Jeśli masz problem, przy którym trzeba połączyć aplikację, urządzenie albo istniejący system, napisz do mnie. Chętnie doradzę i pomogę go rozwiązać.

Powrót do portfolio