# spoko labels: etykiety drukowane prosto z przeglądarki

Wydruk jednej etykiety z magazynu zajmował 5–10 minut: eksport, telefon, aplikacja NIIMBOT. Teraz panel drukuje ją jednym przyciskiem w mniej niż 10 sekund, bo przeglądarka rozmawia z drukarką bezpośrednio, przez Web Bluetooth. Zestaw open source, zbudowany w trzy dni.

**Canonical:** https://spoko.space/pl/drukowanie-etykiet-z-przegladarki/  
**Language:** pl  
**Published:** 2026-10-15  
**Tags:** typescript, web-bluetooth, open-source, filament, qr-code  
**Category:** Portfolio  
**GitHub:** https://github.com/spokospace/spoko-labels

---
<div class="text-center flex gap-4 justify-center flex-wrap">

</div>

## W skrócie {#w-skrócie}

|  |  |
| --- | --- |
| Problem | Wydruk etykiety z magazynu szedł przez eksport, telefon i aplikację NIIMBOT. 5–10 minut, niezależnie od liczby etykiet. |
| Rozwiązanie | Przeglądarka rozmawia z drukarką bezpośrednio, przez Web Bluetooth. W panelu został jeden przycisk. |
| Efekt | Etykieta w mniej niż 10 sekund, z telefonu albo laptopa, bez eksportu. |
| Technologie | TypeScript, Web Bluetooth, Filament, Cloudflare Pages. |
| Czas | Trzy dni od pomysłu do przycisku w panelu, sprawdzonego na prawdziwej drukarce z laptopa, komputera i telefonu. |

## O projekcie {#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 {#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:

**Stary sposób**

1. **Eksport** — Zaznaczyć rekordy w panelu i wyeksportować je do pliku xls.
2. **Przeniesienie** — Przenieść plik na telefon.
3. **Import** — Zaimportować go do aplikacji NIIMBOT i przypisać kolumny do szablonu.
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.

<div class="grid grid-cols-2 gap-4">

</div>

## Jak to rozwiązałem {#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:

| Część | Co robi |
| --- | --- |
| `labels-core` | Protokół drukarki i połączenie przez Web Bluetooth: stan drukarki i baterii, gęstość, liczba kopii, kalibracja, przerwanie wydruku. |
| `labels-render` | Szablony etykiet w milimetrach (tekst, kod QR, linie) z polami z danych, renderowane do obrazu, który drukuje głowica. |
| `labels-niimbot` | Czyta szablony zaprojektowane w aplikacji NIIMBOT, więc gotowe projekty dalej działają. |
| `labels-embed` | Chip ze stanem drukarki i okno drukowania do osadzenia w dowolnej aplikacji webowej. |
| Aplikacja webowa | Szablon wbudowany albo zaimportowany z aplikacji NIIMBOT, plik xlsx albo JSON API, wybór wierszy, druk. Po polsku i po angielsku. |

### Podgląd to wydruk {#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 {#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 AA` z 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 {#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 {#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](https://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 {#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 {#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.
