Bangle.js 2 kupiłem kilka lat temu. Otwarty hardware, JavaScript na nadgarstku, pełna kontrola nad każdym pikselem i pasywny, kolorowy ekran widoczny przez cały czas — wszystko brzmiało świetnie. A jednak przez kilka lat zegarek służył mi głównie jako zwykły zegarek, bo na porządne pogrzebanie w kodzie nigdy nie było czasu.
Impulsem okazała się banalna rzecz: zrobiło się gorąco i chciałem widzieć na nadgarstku temperaturę z mojego Home Assistanta. Jeden mały widget. Tyle wystarczyło, żeby pociągnąć za nitkę, która rozwinęła się w kompletną, autorską tarczę.
Jak dostać dane na zegarek
Bangle.js 2 nie ma własnego Wi-Fi — łączy się przez Bluetooth z telefonem. Wykorzystałem więc istniejący most: aplikację Bangle.js Gadgetbridge, która utrzymuje stałe połączenie z zegarkiem i obsługuje intent com.banglejs.uart.tx. Dzięki niemu można wykonać dowolny JavaScript bezpośrednio na zegarku.
Przepływ wygląda tak:
Home Assistant (co 5 min)
→ broadcast intent com.banglejs.uart.tx
z ładunkiem: HATEMP.set('22')
→ Bangle.js Gadgetbridge
→ wykonanie kodu na zegarku
Po stronie Home Assistanta wystarczyła aplikacja Companion i komenda command_broadcast_intent, uruchamiana automatyzacją co pięć minut.
Warto znać dwie pułapki. Po pierwsze, komenda Companion wymaga pola intent_package_name. Bez niego intent ląduje jako zwykłe powiadomienie: na telefonie coś widać, ale na zegarku panuje cisza. Po drugie, w ustawieniach urządzenia trzeba włączyć opcję „Allow Intents”, bo domyślnie jest wyłączona.
Widget z degradacją danych
Zacząłem od najmniejszej możliwej rzeczy — widgetu na pasku. Wartość temperatury trzymam w globalnym obiekcie HATEMP, więc intent z Home Assistanta może ją aktualizować niezależnie od tego, co akurat jest wyświetlane na ekranie.
Sama liczba to jednak za mało. Temperatura sprzed dwóch godzin wyglądałaby tak samo jak świeży odczyt. Dodałem więc degradację opartą na wieku danych: świeży odczyt jest duży i czytelny, po dziesięciu minutach font maleje, po dwudziestu pojawia się licznik minut od ostatniej aktualizacji, a po dwóch godzinach widget wraca do stanu „–”. Licznik odświeża się co minutę, nawet wtedy, gdy nie przychodzą nowe dane.
To był moment, w którym jeszcze nie wiedziałem, że wchodzę głębiej, niż planowałem.
Własna tarcza
Skoro temperatura była już rozwiązana, naturalnym kolejnym krokiem stała się pełna tarcza. Chciałem, żeby odczyt z Home Assistanta był jej pełnoprawnym elementem, a nie tylko doczepioną liczbą na pasku.
Klimat: cassette futurism spotyka cyberpunk — ciepły bursztyn na czerni, geometryczne ramki, coś między starym sprzętem audio z lat osiemdziesiątych a terminalem z gry sci-fi.
Układ jest prosty, ale wyrazisty: po lewej duże cyfry godziny nad minutami, osadzone na tle pomarańczowego plastra miodu. Po prawej czarny panel z dwujęzycznym dniem tygodnia, datą z rokiem, temperaturą z Home Assistanta i poziomem baterii.
Bogata grafika na słabym ekranie
Ekran ma 176×176 pikseli i tylko 3 bity koloru — bez gradientów i miękkich przejść. To wymusiło dwie decyzje.
Cieniowanie przez dithering.
Wrażenie, że czarny panel nachodzi na heksy i rzuca cień, uzyskałem rastrowym kropkowaniem. Kropki są najgęstsze przy krawędzi panelu i stopniowo zanikają dalej od niej — podobnie jak w monochromatycznych ekranach starych G-Shocków. Na prawdziwym wyświetlaczu wygląda to nawet lepiej niż w podglądzie.
Tło liczone raz.
Heksy, fazowania, cień i ramki generuję jednorazowo, a potem zapisuję do pamięci jako obraz. Co minutę zegarek nakłada na nie tylko zmienne dane: godzinę, datę, temperaturę i baterię. Dzięki temu kosztowna grafika liczona jest raz, a nie tysiące razy dziennie.

Dopracowywanie detali
Najwięcej czasu pochłonęło coś, czego w ogóle nie planowałem: krawędź panelu. Linia podziału przeszła kilka wersji — od prostej, przez łuk, po geometryczny profil ze ścięciami pod 45 stopni. Później doszły fazowane narożniki, pomarańczowe linie zanikające efektem cyberpunkowego rozpadu i pionowe elementy domykające ramkę.
Każdy detal oznaczał osobną iterację: zobacz, przesuń o dwa piksele, oceń, powtórz. To przesada, ale właśnie takie rzeczy nakręcają mnie do działania. Czasem trudno przestać, dopóki nie jest dokładnie tak, jak powinno. A czasem robi się 2:33 i rozsądek podpowiada, że pora odpocząć.
Font, który zrobił różnicę
Standardowe fonty zupełnie nie pasowały do tej estetyki. Potrzebowałem czegoś bardziej surowego, technicznego i postapokaliptycznego. Znalazłem CRACKWALL — spękany stencil, który od razu zagrał z resztą projektu.
Espruino nie renderuje TTF-ów bezpośrednio, więc font trzeba przekonwertować na bitmapę. Ręczna konwersja dała rozmyte cyfry, bo progowanie do jednego bitu rozsypało spękane krawędzie. Rozwiązaniem okazał się oficjalny konwerter Espruino z ustawieniem 2 bitów na piksel, czyli z prostym antyaliasingiem.
Potem został jeszcze taniec z rozmiarem. Font wygenerowany zbyt duży i skalowany w dół wyglądał nieostro. Dopiero wersja przygotowana natywnie w docelowym rozmiarze dała ostre cyfry z odpowiednią ilością przestrzeni wokół znaków.

Co mam teraz
Mam teraz trzy elementy korzystające z tego samego globalnego HATEMP: pełnoekranową aplikację, widget i tarczę hexclock. Jedna konfiguracja w Home Assistancie zasila więc wszystko.
Tarcza pokazuje godzinę spękanym fontem na tle heksów, dwujęzyczny dzień tygodnia, datę z rokiem, temperaturę i baterię. Całość spina ramka w stylu HUD-a, z bursztynowymi detalami na czarnym tle.
Doszedł też dyskretny wskaźnik nieaktualności danych: kropki nad temperaturą, których przybywa, im dłużej nie było aktualizacji. To subtelny sygnał odróżniający „to sprzed chwili” od „tego dawno nikt nie odświeżał”.
Refleksja
Najciekawsze jest to, jak mała iskra — chęć zobaczenia jednej liczby na nadgarstku — rozpaliła projekt, który leżał odłogiem przez lata. Wystarczył konkretny, namacalny cel, żeby usiąść i zacząć. Potem każdy krok prowadził do następnego: widget zachęcił do tarczy, tarcza do dopracowywania ramek, a ramki do szukania właściwego fontu.
Czasem nie trzeba wielkiej motywacji, żeby ruszyć stary projekt. Wystarczy jeden mały szczegół, który naprawdę chce się mieć zrobiony.
Jest jeszcze co dłubać: ukośne linie rozbijające symetrię ramek, zapis ostatniej temperatury, żeby przeżywała restart, może kilka dodatkowych mikrodetali. Ale to już historia na inny wpis.
Najnowsze komentarze