W skrócie
O projekcie
Każde pudło, półka i część w magazynie garażowym ma etykietę z kodem QR. Drukuje je NIIMBOT B1, budżetowa drukarka za niecałe 100 zł: Bluetooth, etykiety 50×30 mm, akumulator. Mieści się w dłoni, leży na półce między pudłami i nie ma żadnego kabla. Etykiety wychodzą dobre. Problem był z tym, ile trzeba zrobić, zanim jedna wyjdzie.
spoko labels to zestaw open source, dzięki któremu strona internetowa drukuje prosto na B1. Powstał po to, żeby panel magazynu drukował etykietę jednym przyciskiem, a przy okazji stał się publiczną aplikacją dla każdego, kto ma taką samą drukarkę.
Wyzwanie
B1 łączy się przez Bluetooth tylko z aplikacją NIIMBOT na telefon, a aplikacja na komputer obsługuje wyłącznie kabel USB. Każda etykieta musiała więc przejść przez aplikację NIIMBOT. Na początku najwięcej czasu zajmowało formatowanie: zmieścić nazwę, model, numer i kod QR na 50×30 mm tak, żeby dało się je odczytać. Kiedy był już szablon, dane trzeba było wpisywać etykieta po etykiecie. Gdy lista urosła, przyszedł import z pliku xls:

- Krok 1
Eksport
Zaznaczyć rekordy w panelu i wyeksportować je do pliku xls.
- Krok 2
Przeniesienie
Przenieść plik na telefon.
- Krok 3
Import
Zaimportować go do aplikacji NIIMBOT i przypisać kolumny do szablonu.
- Krok 4
Druk
Połączyć się z drukarką w aplikacji, znaleźć etykietę i wydrukować.
Cztery kroki, dwa urządzenia i 5–10 minut, czy to była jedna etykieta, czy dwadzieścia. Do tego aplikacja ma niewiele fontów z polskimi znakami, a edycja na telefonie przy większej liczbie etykiet męczy. W praktyce etykietę drukowało się „przy okazji”, a okazji nie było. Pudła stały nieopisane, a porządkowanie magazynu stawało w miejscu.
Jak to rozwiązałem
Zamiast ulepszać ten proces, usunąłem go. Przeglądarka potrafi rozmawiać przez Bluetooth tak samo jak aplikacja na telefon, trzeba tylko znać język drukarki. Nikt go nie opisał, więc najpierw powstał prototyp w Pythonie, który potwierdził protokół na prawdziwej B1. Potem przeniosłem go do TypeScriptu jako cztery pakiety i aplikację, każdy do użycia osobno:
labels-corelabels-renderlabels-niimbotlabels-embed
Podgląd to wydruk
Głowica ma 384 punkty szerokości, czyli 8 punktów na milimetr, i drukuje tylko czerń i biel. Etykieta, która ładnie wygląda na ekranie, a wychodzi z rozmazanym kodem QR, jest gorsza niż brak podglądu. Dlatego renderer liczy w punktach drukarki:
- Szablon w milimetrach, wynik w punktach. Obraz jest zamieniany na czystą czerń i biel, a moduły kodu QR zawsze trafiają w całe punkty, więc kody skanują się nawet małe.
- Podgląd pokazuje sprzęt. Czerwone pasy są poza głowicą i się nie wydrukują, szare rogi poza zaokrągloną etykietą, przerywana linia to bezpieczny obszar. Tekst, który się nie mieści, dostaje uwagę, zanim zmarnujesz etykietę.
- Testy na obrazach wzorcowych. Zmiana, która przesuwa jeden punkt, jest świadomą decyzją, z obrazami przed i po w pull requeście.
Bluetooth ze strony internetowej
Web Bluetooth ma swoje zasady i projekt jest wokół nich zbudowany:
- Połączenie zaczyna się od kliknięcia. Przeglądarka pozwala wybrać drukarkę tylko w odpowiedzi na gest użytkownika, więc chip w oknie drukowania robi to jako pierwszą rzecz.
- Jedno polecenie naraz. Drukarka odpowiada na jedno polecenie, zanim przyjmie następne. Wszystkie idą przez jedną kolejkę, więc chip, okno i aplikacja nie mówią jednocześnie.
- Protokół sprawdzany na sprzęcie. Pakiety
55 55 CMD LEN DATA… CHK AA AAz sumą XOR, a między nimi pakiety stanu, które drukarka wysyła sama. Każda zmiana w protokole jest sprawdzana na prawdziwej B1 przed merge. - Nie działa wszędzie i aplikacja to mówi. Chrome i Edge na komputerze i Androidzie działają. iOS i Firefox nie mają Web Bluetooth. Brave wymaga flagi i aplikacja podaje, której.

Jeden przycisk w panelu
Magazyn działa na Filamencie. Pakiet do osadzania zamienił drukowanie w jedną akcję: panel przekazuje szablon i zaznaczone wiersze, okno rysuje podgląd, chip trzyma połączenie. Pakiet niczego nie pobiera ani nie wysyła, więc działa pod restrykcyjnym Content Security Policy bez wyjątków. Na telefonie chip zwija się do ikony w górnym pasku. Stary widok drukowania i eksport, na którym się opierał, zostały usunięte.

Tak wydrukowana etykieta ląduje na części, obok naklejki producenta:

Drukowanie z telefonu działa idealnie, nie muszę już eksportować plików xls. — warsztat, pierwszego dnia drukowania z panelu
Efekty
- Etykieta w mniej niż 10 sekund zamiast 5–10 minut. Otworzyć rekord, stuknąć Drukuj etykietę, odkleić. Z telefonu w warsztacie albo z laptopa.
- Zero eksportu. Arkusz, który był nieaktualny, gdy tylko zmieniła się nazwa, już nie istnieje. Etykieta zawsze pokazuje to, co jest w panelu.
- Porządkowanie ruszyło. Gdy etykieta nic nie kosztuje, pudło dostaje ją od razu, a nie „przy okazji”.
- Dane zostają w przeglądarce. Pliki xlsx są czytane na stronie, API jest pobierane bez danych logowania, nic nie trafia na żaden serwer.
- Publiczna aplikacja. Każdy z B1 może drukować ze swojego arkusza pod adresem spoko-labels.pages.dev, a kod jest na licencji MIT. Kto ma już szablony w aplikacji NIIMBOT, wskazuje jej plik bazy: szablony wczytują się w przeglądarce, z podglądem i listą tego, co zostało uproszczone, i zostają w niej na kolejne wizyty. Tak właśnie przeszły szablony magazynu.

Jak powstało
Od pomysłu do przycisku w panelu minęły trzy dni, od 12 do 14 października 2026, w około trzydziestu małych pull requestach. Przeniesienie prototypu zaplanowałem jako spisane specyfikacje, po jednej na pakiet. Każdy pakiet trafiał do repozytorium z własnymi testami, na koniec było ich 666, a wszystko, co dotykało sprzętu, sprawdzałem dodatkowo na prawdziwej drukarce, z laptopa, komputera stacjonarnego i telefonu. Bramka (typecheck, lint, testy, build) jest uruchamiana lokalnie przed każdym merge, a aplikację wdrażam ręcznie na Cloudflare Pages. W stopce widać commit, z którego została zbudowana.
Co dalej
- Szablony z pliku. Własny szablon jako plik JSON do wczytania, a zaimportowany do pobrania, żeby przenieść go do innej aplikacji.
- Linki bezpośrednie, żeby link otwierał aplikację z wybranym szablonem i źródłem danych.
- Więcej drukarek. Projekt rozwijam na B1. Inne modele NIIMBOT używają podobnego protokołu, zgłoszenia są mile widziane.







