Szopka Elektronika — Aplikacja Szopka Magazyn dla terminala Motorola Omnii XT15 _×

SZOPKA ELEKTRONIKA

Powrót do klasyki

Serwis poświęcony komputerom i systemom z lat 80. i 90.

C:\>RUN_ READY.

Aplikacja Szopka Magazyn dla terminala Motorola Omnii XT15

W testach Wysoki  100%

Rozpoczęty: 2026-07-06 •  Ostatnia aktualizacja: 2026-08-19

Aplikacja magazynowa na terminal Motorola Omnii XT15, czyli programowanie w 2026 roku dla systemu z 2010

Po co to w ogóle

Magazyn kolekcji obsługują dwie aplikacje: panel CMS na stronie i program na Windows (C#, .NET 8, WinForms). Obie mają jedną wadę — trzeba do nich podejść. Terminal kodów kreskowych nie zastępuje żadnej z nich. Jego jedynym atutem jest mobilność: skanujesz przy regale, zamiast chodzić do komputera.

Stąd bardzo wąski zakres pierwszej wersji, ustalony przed napisaniem pierwszej linii kodu:

  1. skan naklejki → karta egzemplarza na ekranie,
  2. inwentaryzacja — kontynuowanie sesji spisu założonej na komputerze,
  3. zmiana lokalizacji po przełożeniu sprzętu na inną półkę,
  4. zmiana stanu technicznego,
  5. krótka notatka,
  6. podgląd danych bez podchodzenia do komputera.

Równie ważna była lista rzeczy, których terminal celowo nie robi: pełnej edycji, dodawania i usuwania egzemplarzy, zakładania i zamykania sesji spisu, zdjęć, druku etykiet. Każde z tych wykluczeń ma powód, a nie jest brakiem czasu — wpisywanie opisu klawiaturą ekranową urządzenia trzymanego w jednej ręce to udręka, a nowy egzemplarz wymaga decyzji (kategoria, cechy, koszty), których nie podejmuje się na stojąco przy regale. Usuwanie jest nieodwracalne i po prostu nie ma czego szukać na takim urządzeniu.

Architektura się nie zmieniła: terminal jest trzecim klientem tego samego API, obok aplikacji Windows i panelu CMS. Żaden klient nie łączy się z bazą bezpośrednio.

MariaDB  ⇄  API v1 (PHP + token)  ⇄  { aplikacja Windows, panel CMS, terminal XT15 }

Miła niespodzianka na starcie: API miało już wszystko, czego terminal potrzebował. Żadnego endpointu nie trzeba było dokładać do funkcji podstawowych.

Czym się to pisze

UrządzenieMotorola Omnii XT15 (rodowód Psion, później Zebra)
SystemWindows Embedded Handheld 6.5.3 Professional, jądro CE 5.2.29202
Framework.NET Compact Framework 3.5.9198.0 (pełne, zaszyte w ROM-ie)
NarzędzieVisual Studio 2008 Professional + Windows Mobile 6 Professional SDK
JęzykC# 3.0 — bez async/await
WgrywanieWMDC (kabel, F5 z VS) albo pakiet CAB

I to jest pierwsza rzecz, która zaskakuje: VS 2022 nie zbuduje projektu na .NET CF. To dwa osobne światy. Aplikacja na PC zostaje w VS 2022, terminal powstaje w VS 2008 — na tej samej maszynie, w dwóch epokach naraz.

Kod aplikacji na PC nie nadawał się do współdzielenia ani w jednym pliku: jej klient API stoi na HttpClient, System.Text.Json i async. Terminal dostał własnego, znacznie prostszego klienta. Wspólna została umowa z API, nie kod.

Problem pierwszy i największy: terminal nie umie się połączyć

Zanim powstał szkielet aplikacji, trzeba było zmierzyć, czy urządzenie w ogóle dogada się z serwerem. Wynik był brutalny.

Pomiar OpenSSL-em, z celowo obniżonym poziomem bezpieczeństwa po stronie testera, żeby wykluczyć winę narzędzia:

TLS 1.0 → alert 70 (protocol version) — odrzucone PRZEZ SERWER
TLS 1.1 → alert 70 (protocol version) — odrzucone PRZEZ SERWER
TLS 1.2 → działa

Windows Mobile 6.5 zna maksymalnie TLS 1.0. Do tego serwer przyjmuje wyłącznie trzy zestawy szyfrów, wszystkie ECDHE-ECDSA z szyfrem AEAD — nie ma prostszego wyjścia awaryjnego w rodzaju CBC. A certyfikat? Cały łańcuch Let's Encrypt jest w ECDSA P-384; magazyn zaufanych certyfikatów w WM 6.5 pochodzi z 2009 roku i nie zawiera korzeni ISRG, więc walidacja nie przeszłaby nawet gdyby problem TLS zniknął.

Co najgorsze: WEH 6.5.3 to ostatnie wydanie platformy. Nie istnieje aktualizacja systemu, która by to naprawiła. Blocker jest trwały.

Rozważanych było pięć wariantów: prośba do hostingu o TLS 1.0 (odrzucona — osłabia szyfrowanie całej strony dla wszystkich), pośrednik w sieci lokalnej, praca wsadowa przy dokowaniu, własny stos TLS 1.2 na Bouncy Castle oraz — ostatecznie wybrany — HTTP dla ścieżek API używanych przez terminal.

Zadecydował zbieg okoliczności. Niezależnie od prac nad terminalem w CMS wdrożono wcześniej warunkowe wymuszanie HTTPS (własna klasa PHP zamiast globalnego przekierowania hostingu), żeby retro przeglądarki mogły w ogóle zobaczyć stronę. Dzięki temu wariant „HTTP dla API" przestał być prośbą do hostingu, a stał się zmianą jednej stałej we własnym kodzie. Koszt spadł z „napisz do hostingu i licz na zgodę" do kilkunastu minut.

Cena jest jednak realna: token wędruje jawnym tekstem po Wi-Fi. Dlatego wdrożenie warunkowo powiązano z drugą zmianą w CMS — tokenem o wąskim zakresie. Terminal dostał uprawnienia dokładnie do tego, co robi: odczyt, zapis trzech pól (lokalizacja, stan techniczny, opis) i skan w sesji spisu. Dodanie egzemplarza, usunięcie czegokolwiek, zmiana nazwy czy kosztów → 403. Po HTTP otwarto zresztą nie całe /api, tylko dziesięć konkretnych żądań.

Docelowo transport ma się zmienić na własny klient TLS 1.2 — dlatego od pierwszej linii kodu siedzi za interfejsem ITransport. Podmiana nie dotknie niczego poza jedną klasą.

Fałszywy trop, który kosztował dwa tygodnie podejrzeń

Wczesne testy na terminalu wyglądały tak, jakby zawodził DNS: adres po http i po https dawał identyczny komunikat „nie odnaleziono", co przy winie wyłącznie TLS-a nie miało prawa się zdarzyć — wersja http powinna najpierw dostać przekierowanie. Podejrzenie padło na router LTE i jego uproszczony resolver.

Rozstrzygnął to jeden prosty test na urządzeniu: http://…/robots.txt wyświetlił treść pliku, strona główna otworzyła się poprawnie. Rozwiązywanie nazw działa bez zarzutu. Winne było globalne przekierowanie 80 → 443 po stronie hostingu, wyłączone dopiero dzień po tamtych pomiarach — stąd ten sam komunikat w obu przypadkach. robots.txt został w projekcie na stałe jako metoda kontrolna łączności: odpowiada 200 text/plain, czystym ASCII, bez przekierowania, i renderuje się na IE Mobile bezbłędnie.

Problem drugi: .NET Compact Framework to nie .NET

CF 3.5 ma znacząco okrojoną bibliotekę standardową, a najgorsze jest to, że braki są nieoczywiste. Brak HttpClient czy async/await da się przewidzieć i obejść (HttpWebRequest, wątek roboczy plus Control.Invoke). Brak System.Text.Json też — powstał własny, minimalny parser JSON w jednym pliku, bo koperta odpowiedzi {ok, data, error, code} jest prosta, a port zewnętrznej biblioteki byłby zależnością do utrzymywania.

Prawdziwy koszt generowały drobiazgi. Pierwszy realny build w VS 2008 wyłapał pięć użyć API, które w pełnym .NET Framework są całkowicie rutynowe, a w CF nie istnieją: MethodInvoker, Application.StartupPath, jedno przeciążenie FileNotFoundException — i czcionki.

Historia czcionki jest w tym projekcie anegdotą samą w sobie. Font(string, float) — nie ma. Poprawka przez new FontFamily("Tahoma")FontFamily(string) też nie ma, i to w sposób kaskadowy psujący otaczające wywołanie. Trzecia próba, sięgnięcie po rodzinę z gotowego obiektu przez Font.FontFamily — tej property również nie ma. Dopiero po trzech rundach zapadła decyzja, żeby zostawić kontrolkom czcionkę domyślną i wrócić do sprawy później. Wróciła w etapie czwartym, gdy okazało się, że wskazówka od początku siedziała w komunikacie kompilatora: „does not contain a constructor that takes '2' arguments" znaczyło, że jakaś inna arność istnieje. Działa wariant trzyargumentowy, Font("Tahoma", 10F, FontStyle.Bold).

Ten sam wzorzec powtórzył się jeszcze dwa razy: TextBox.Clear() nie istnieje (ale Text = "" działa), Timer.Start()/Stop() nie istnieją (ale Enabled = true/false działa — i przy okazji ustawienie Enabled na już włączonym timerze restartuje odliczanie, czyli dokładnie to, czego potrzebowało wykrywanie pauzy skanera). Wyłoniła się z tego reguła kciuka, która potem sprawdzała się bezbłędnie: surowa property działa prawie zawsze, metoda-skrót dodana „dla wygody" w pełnym Frameworku jest podejrzana.

Skutek uboczny tej ostrożności widać w kodzie: rozpoznawanie kodu inwentarzowego SE-000137 napisano ręcznie, znak po znaku, zamiast użyć wyrażenia regularnego, a kodowanie procentowe adresu — własne, zamiast Uri.EscapeDataString. Wzorce są trywialne, a każda dodatkowa zależność w CF 3.5 to kolejna niewiadoma, którą i tak sprawdzi dopiero kompilator.

Problem trzeci: ekran, który kłamie

Kreator VS zapisał w projekcie AutoScaleMode.Dpi i wymiary odniesienia 96 DPI. Urządzenie raportuje 192 DPI — tryb Dpi mnożyłby zatem pozycje i rozmiary wszystkich kontrolek przez dwa. Nie było tego widać, bo jedyna kontrolka w szkielecie miała Dock = Fill i jest na skalowanie odporna. Przy pierwszym normalnym układzie ekranu rozjechałoby się wszystko naraz.

Przy okazji zmierzono realny budżet projektowy: ekran to 480×640 (VGA, portret), ale dla aplikacji zostaje 480×536 — 104 piksele zabiera pasek tytułu i pasek menu. AutoScaleMode przestawiono na None i przyjęto najprostszą możliwą umowę: piksel w designerze = piksel na terminalu. Karta egzemplarza jest potem układana z ołówkiem w ręku — osiem pól zmieściło się w 350 z dostępnych 356 pikseli, a producent i rok musiały zostać zbite w jeden wiersz, żeby zrobić miejsce na kategorię.

Odkryciem był też sposób obsługi akcji. Windows Mobile ma pod ekranem tylko dwa klawisze programowe, lewy i prawy — dwie pozycje najwyższego poziomu w menu aplikacji stają się właśnie nimi. Przeniesienie akcji z przycisków na ekranie do tych klawiszy oddało kilkadziesiąt pikseli obszaru roboczego za darmo, bo pasek menu i tak był już wliczony w chrom systemu.

Problem czwarty: skaner, który nie mówi „koniec"

Pierwsze pytanie brzmiało: czy skaner działa jako emulacja klawiatury (skan → tekst do aktywnego pola → Enter), czy trzeba sięgnąć po EMDK producenta. Powstał osobny ekran diagnostyczny, żeby to rozstrzygnąć na urządzeniu, zamiast zgadywać.

Odpowiedź: emulacja klawiatury działa, EMDK niepotrzebne — ale skaner nie wysyła po skanie ani Enter, ani Tab. Znaki po prostu wpadają do pola i nic się nie dzieje.

Zamiast polegać na konfiguracji skanera, która może się zmienić albo zresetować, koniec skanu wykrywany jest przez pauzę w pisaniu: timer restartowany przy każdym znaku, zatwierdzenie przy pierwszym „ciszy" trwającym 300 ms. Enter i Tab zostały jako natychmiastowy skrót, gdyby jednak przyszły.

I tu wpadka: takie rozwiązanie uniemożliwiło wpisanie kodu z klawiatury. Człowiek robi między znakami przerwy dłuższe niż 300 ms, więc pole zatwierdzało się w połowie wyrazu. To nie jest przypadek teoretyczny — uszkodzonej etykiety nie da się zeskanować i kod trzeba przepisać ręcznie. Autozatwierdzenie działa więc dziś tylko dla wprowadzenia, które wygląda jak skan: żaden odstęp między znakami powyżej 150 ms oraz co najmniej 6 znaków. Oba progi są konieczne — każdy z osobna miał lukę, którą znalazła symulacja przebiegu przed oddaniem kodu do kompilacji. Cała ta logika trafiła później do osobnej klasy, gdy okazało się, że inwentaryzacja potrzebuje dokładnie tego samego.

Kilka pułapek, których nikt nie przewidział

Książka z kodem QR. Egzemplarz użyty do testów miał obok kodu kreskowego kod QR z adresem strony wydawcy — i skaner złapał ten drugi. Aplikacja pokazała „Could not establish secure channel", komunikat nijak niezwiązany z tym, co użytkownik zrobił. Aplikacja nigdy nie próbowała łączyć się z zeskanowanym adresem; tekst z ukośnikami po prostu wypadał poza wzorzec ścieżek otwartych dla terminala, dostawał przekierowanie na HTTPS i kończył na TLS 1.2. Odpowiedzią były dwa poziomy odporności: walidator odsiewający treści, które kodem egzemplarza być nie mogą (adresy, ukośniki, odstępy, ponad 64 znaki), oraz wyłączone podążanie za przekierowaniem — dzięki czemu użytkownik dostaje zrozumiałe zdanie zamiast błędu szyfrowania, a token nie powędruje pod żaden inny adres niż ten z konfiguracji. Efekt uboczny odkrycia: skaner XT15 czyta również kody QR, nie tylko kreskowe.

Notatka, która nie jest notatką. Założenia mówiły o „krótkiej notatce", ale API nie ma osobnego pola na notatki — jest description, czyli pełny opis egzemplarza, ten sam, który wypełnia się na komputerze. Wysłana wartość zastępuje dotychczasową. Puste pole na starcie zapraszałoby do skasowania dłuższego opisu bez wiedzy o jego istnieniu, a przy regale nikt by tego nie zauważył. Stąd: pole startuje wypełnione obecną treścią, etykieta mówi wprost, że zapis zastąpi to, co jest, a wyczyszczenie całości wymaga potwierdzenia z domyślnym „Nie".

Pętla, z której nie dało się wyjść. Wybór sesji spisu odpalany był z OnActivated, żeby nie otwierać okna modalnego z konstruktora. Tyle że OnActivated uruchamia się za każdym razem, gdy formularz staje się aktywny — w tym w chwili zamknięcia okna wyboru, jeszcze wewnątrz trwającego wywołania. Powstawała pętla, z której nie było ucieczki ani przez „Wybierz", ani przez „Anuluj", bo przypisanie wybranej sesji nigdy nie zdążyło się wykonać.

Terminal, który zasypia. Odkładany między regałami terminal usypia, a karta sieciowa traci połączenie — pierwsze żądanie po wybudzeniu zwykle pada, kolejne przechodzi. Klient ponawia je raz, ale wyłącznie odczyty. Zapisy nie są ponawiane i to jest decyzja, nie przeoczenie: awaria sieci nie mówi, czy żądanie doszło do serwera. Automatyczne powtórzenie zapisu zerwanego po wysłaniu, a przed odebraniem odpowiedzi, zapisałoby zmianę dwa razy — przy lokalizacji dwa wpisy w historii egzemplarza, przy spisie podwójny skan.

Przy okazji poszła też optymalizacja baterii: konfiguracja czytana jest z dysku raz, a nie przy każdym żądaniu. Wcześniej skanowanie w pętli podczas spisu oznaczało kilkadziesiąt zbędnych odczytów z pamięci flash na minutę pracy.

Jak to się skończyło

Aplikacja liczy około 4 700 linii C# w 24 plikach i powstawała w siedmiu etapach z jawnymi bramkami decyzyjnymi — każda bramka to pytanie, na które trzeba było odpowiedzieć pomiarem na urządzeniu, zanim ruszyło pisanie dalej. Rozpoznanie sprzętu i łączności zajęło blisko dwa tygodnie; sam kod, gdy blockery były już zdjęte, powstał w dwa dni.

Na terminal trafia przez pakiet CAB, który instaluje się na miejscu bez podłączania urządzenia do komputera. Osobna decyzja przy każdym wydaniu: czy do pakietu wchodzi wypełniony plik konfiguracyjny (wygodniej, ale CAB staje się nośnikiem tokenu), czy sam wzór (bezpieczniej, kosztem jednej czynności po instalacji).

Najważniejsza lekcja z całego projektu jest chyba taka: przy tak starej platformie największym kosztem nie jest pisanie kodu, tylko niepewność. Nie da się z góry sprawdzić, czy dana metoda istnieje w CF 3.5; jedyną weryfikacją jest build w VS 2008 na maszynie użytkownika, a każdy taki cykl kosztuje. Dlatego w tym projekcie tak dużo rzeczy pisano najprostszą możliwą formą, tak wiele mierzono zamiast zakładać, a logikę progów skanera przećwiczono symulacją, zanim kompilator w ogóle ją zobaczył.


Powiązane artykuły z Warsztatu

« Wróć do listy projektów

Licznik odwiedzin: 0003054
Napisz do mnie