PRZYCZYNA ZNIKAJĄCYCH ALARMÓW ZNALEZIONA — i nie była nią liczba połączeń.
Commit 7cf0634 ("hidden status-bar & machine.status") zakomentował element
#status-bar w HTML, ale refreshStatus() nadal po niego sięgał. TypeError na
null przerywał handler ws.onclose PRZED zaplanowaniem reconnectu — każda
maszyna po pierwszym zerwaniu połączenia milkła NA ZAWSZE. Restart z crona
co 2 h maskował problem, zaczynając od zera.
Czerwcowe poprawki na kiosku (timery świeżości per-maszyna, ubijanie "zombie"
połączeń, jitter) szły w dobrą stronę, ale nieświadomie POGORSZYŁY objaw:
watchdog celowo zamykał ciche połączenie, a każde zamknięcie przechodziło
przez ten sam wadliwy onclose — własna obrona uśmiercała maszynę na stałe.
Ich sedno jest zachowane w tej wersji (wykrywanie ciszy, jitter, nakładkowy
iframe z "iframe optimalization").
NOWA ARCHITEKTURA (pod nowy system LuPort):
- JEDNO połączenie na kanał zbiorczy /ws/als-data/ zamiast 35 per-maszyna;
serwer wysyła migawkę stanu wszystkich maszyn zaraz po połączeniu (dodane
w LuPort równolegle), więc trwający alarm wraca na ekran natychmiast po
reconnexie
- token ApiUser w adresie (nowy system wymaga uwierzytelnienia WS); konto
bot-als-alert-screen bez żadnych uprawnień
- reconnect z wykładniczym odstępem (1->30 s) i jitterem; odstęp wraca do
minimum dopiero po 60 s stabilnego połączenia
- watchdog ciszy > 90 s (przeglądarka nie wystawia ping/pong do JS — martwe
półotwarte połączenie inaczej wisi w nieskończoność)
- dane maszyny starsze niż 5 min znikają z ekranu; przy rozłączeniu dane NIE
są czyszczone od razu (alarm nie znika przy 3-sekundowym czknięciu sieci —
o niepewności mówi pomarańczowy pasek, migawka po reconnexie przywraca
prawdę)
- utrata łączności WIDOCZNA: pomarańczowy pasek po 10 s + przywrócony pasek
statusu; element MUSI istnieć w DOM, refreshStatus() ma mimo to osłonę
- MACHINES stało się filtrem wyświetlania; ?ws= i ?token= nadpisują
konfigurację z adresu strony
- karty przez textContent; celowo BRAK ws.onerror (po błędzie przeglądarka
i tak wywołuje onclose)
TESTY (test/testy_ekranu.py): 10 deterministycznych scenariuszy bez sieci —
skrypt wycinany z żywego HTML, WebSocket podmieniony fałszywką, wirtualny
czas Chromium. Scenariusz 6 to bezpośredni test regresji błędu reconnectu;
mutacja (usunięcie planowania reconnectu) wywraca scenariusze 6, 8 i 9.
CRON RESTARTUJĄCY CO 2 H MOŻNA USUNĄĆ — README opisuje czemu.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ALS Alert Screen
Jednostronicowa aplikacja HTML/JS wyświetlająca alarmy maszyn na podstawie danych WebSocket z LuPort. Przeznaczona do montażu na monitorze halowym (Chromium na Debianie).
Działanie
Strona utrzymuje jedno połączenie WebSocket ze zbiorczym kanałem ALS
nowego systemu (/ws/als-data/ bez maszyny w adresie) — kanał niesie
aktualizacje wszystkich maszyn, a zaraz po połączeniu serwer wysyła migawkę
ich bieżącego stanu, więc trwający alarm pojawia się na ekranie natychmiast,
także po każdym wznowieniu połączenia.
| Sytuacja | Widok |
|---|---|
| Brak maszyny w alarmie | Iframe z infoterminalem ALS |
| Co najmniej jedna maszyna w alarmie | Ekran alarmowy z kartami maszyn |
| Brak łączności z LuPort > 10 s | Pomarańczowy pasek u góry (na obu widokach) |
Alarm jest wykrywany, gdy pole status maszyny zawiera słowo alarm
(bez rozróżnienia wielkości liter), np. "Alarm - awaria", "ALARM".
Pomarańczowy pasek to celowo INNY kolor niż czerwień alarmów: mówi „ekran może pokazywać nieświeży obraz", a nie „maszyna stoi".
Konfiguracja
Wszystkie parametry na początku sekcji <script> w als_alert_screen.html:
| Stała | Znaczenie |
|---|---|
WS_URL |
adres kanału zbiorczego, np. ws://luport.local/ws/als-data/ |
WS_TOKEN |
token konta ApiUser (patrz niżej) |
MACHINES |
filtr maszyn pokazywanych na ekranie; [] = wszystkie |
WATCHDOG_SILENCE_MS |
po jakiej ciszy uznać połączenie za martwe (90 s) |
STALE_MS |
po jakim czasie dane maszyny znikają z ekranu (5 min) |
OFFLINE_BANNER_AFTER_MS |
po jakim czasie bez łączności pokazać pasek (10 s) |
Oba kluczowe parametry można też nadpisać w adresie strony — wygodne przy konfiguracji kiosku bez edycji pliku:
als_alert_screen.html?ws=ws%3A%2F%2Fluport.local%2Fws%2Fals-data%2F&token=...
Token (wymagany przez nowy system)
Nowy LuPort wymaga uwierzytelnienia WebSocket. Utwórz dedykowane konto:
Django Admin → Users → Api users → dodaj bot-als-alert-screen
bez żadnych uprawnień (sam odbiór strumienia ALS ich nie wymaga — konto
ma minimalny możliwy dostęp). Wygenerowany token wpisz w WS_TOKEN.
Format wiadomości
Strona obsługuje dwa typy ramek — obydwa tak samo (liczy się najnowszy stan):
machine_data— migawka wysyłana przez serwer zaraz po połączeniu,machine_update— aktualizacje na żywo.
{
"type": "machine_update",
"machine": "2.60",
"data": { "status": "Automatyczny", "orders": ["202605350"], "progress": 25, "...": "..." },
"timestamp": "2026-07-31T12:00:32"
}
Odporność na awarie — dlaczego cron-restart nie jest już potrzebny
- Reconnect z wykładniczym odstępem i jitterem (1 s → 30 s). Odstęp wraca do minimum dopiero po 60 s stabilnego połączenia — serwer, który przyjmuje i zaraz zrywa (np. zły token), nie jest młócony co sekundę.
- Watchdog martwego połączenia. Przeglądarka nie wystawia ping/pong do JS, więc połączenie zabite bez zamknięcia TCP potrafi wisieć „otwarte" w nieskończoność. Na kanale zbiorczym dane płyną praktycznie ciągle (35 maszyn × cykl scrapera 30 s) — cisza dłuższa niż 90 s oznacza martwe połączenie i wymusza reconnect.
- Wygasanie danych. Dane maszyny starsze niż 5 min znikają z ekranu — alarm maszyny usuniętej z widgetu ALS nie wisi w nieskończoność.
- Widoczna utrata łączności. Pasek offline + pasek statusu w rogu. Ekran nigdy nie pokazuje po cichu nieświeżego obrazu.
Historia: dlaczego alarmy „przestawały wyskakiwać" (do 2026-07-31)
Poprzednia wersja utrzymywała 35 osobnych połączeń (po jednym na maszynę)
i miała błąd, który ujawnił się po ukryciu paska statusu: element #status-bar
został zakomentowany w HTML, ale refreshStatus() nadal po niego sięgał.
TypeError na null przerywał handler ws.onclose przed zaplanowaniem
reconnectu — każda maszyna po pierwszym zerwaniu połączenia milkła na zawsze.
Maszyny odpadały jedna po drugiej przy każdym czknięciu sieci, a restart
z crona co 2 h maskował problem, zaczynając od zera.
Wnioski utrwalone w obecnym kodzie:
- element statusu musi istnieć w DOM (ukrywanie wyłącznie przez CSS),
a
refreshStatus()i tak ma osłonę na jego brak, - brak
ws.onerrorjest celowy — po błędzie przeglądarka i tak wywołujeonclose, a osobny handler był tylko drugim miejscem, z którego dało się rzucić wyjątkiem, - scenariusz 6 w testach (
test/testy_ekranu.py) jest bezpośrednim testem regresji tego błędu.
Poprawki robione na kiosku w czerwcu (timery świeżości per-maszyna,
ubijanie „zombie" połączeń, jitter reconnectu) szły w dobrą stronę, ale
nieświadomie POGORSZYŁY objaw: watchdog celowo zamykał ciche połączenie,
a każde zamknięcie przechodziło przez ten sam wadliwy onclose — czyli
własna obrona uśmiercała maszynę na stałe. Ich sedno (wykrywanie ciszy,
jitter) jest zachowane w obecnej wersji; nakładkowy iframe („iframe
optimalization") również. Zrezygnowano z czyszczenia danych przy rozłączeniu:
alarm nie znika przy 3-sekundowym czknięciu sieci — o niepewności informuje
pomarańczowy pasek, a migawka po reconnexie przywraca prawdę.
Cron restartujący przeglądarkę co 2 h można usunąć.
Testy
python3 test/testy_ekranu.py
# lub na kiosku:
CHROME_BIN=/usr/bin/chromium python3 test/testy_ekranu.py
Deterministyczne, bez sieci: skrypt aplikacji jest wycinany z żywego
als_alert_screen.html, window.WebSocket podmieniany na fałszywkę,
a scenariusz (10 asercji: migawka, aktualizacje, reconnect, baner offline,
watchdog, wygasanie danych) jedzie na wirtualnym czasie Chromium — całość
trwa kilka sekund mimo symulowania ~6 minut.
Tryb debug
Podgląd ekranu alarmowego bez danych i bez połączeń WS:
als_alert_screen.html?debug
albo w konsoli przeglądarki: DEBUG_ALARM = true; updateDisplay();
(powrót: DEBUG_ALARM = false; updateDisplay();).
Wymagania
- Chromium/Chrome 105+ (CSS Container Queries,
min()) — kiosk działa na Chromium pod Debianem. - Dostęp sieciowy do LuPort (WebSocket) oraz infoterminala ALS (iframe).