Przejście na kanał zbiorczy LuPort + naprawa reconnectu, watchdog, widoczny offline
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>
This commit is contained in:
@@ -1,145 +1,160 @@
|
||||
# ALS Alert Screen
|
||||
|
||||
Jednostronicowa aplikacja HTML/JS wyświetlająca alarmy maszyn na podstawie danych z WebSocket. Przeznaczona do montażu na monitorze halowym.
|
||||
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 nasłuchuje danych z WebSocket dla każdej skonfigurowanej maszyny. W zależności od statusów maszyn wyświetla jeden z dwóch widoków:
|
||||
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 `http://diluals31/terminal/ift/5/PL/index` |
|
||||
| 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` w danych maszyny zawiera słowo **`alarm`** (bez rozróżnienia wielkości liter), np. `"Alarm - awaria"`, `"ALARM"`.
|
||||
Alarm jest wykrywany, gdy pole `status` maszyny zawiera słowo **`alarm`**
|
||||
(bez rozróżnienia wielkości liter), np. `"Alarm - awaria"`, `"ALARM"`.
|
||||
|
||||
---
|
||||
|
||||
## Ekran alarmowy
|
||||
|
||||
- Karty maszyn wypełniają cały dostępny ekran w układzie **siatki N×M** (możliwie kwadratowej).
|
||||
- Numer maszyny jest wyświetlany bardzo dużą czcionką, widoczną z daleka — rozmiar skaluje się automatycznie zarówno do wymiarów ekranu (`vw`/`vh`), jak i szerokości karty (`cqw`), więc tekst nigdy nie jest ucięty.
|
||||
- Pod numerem wyświetlany jest pełny tekst statusu.
|
||||
- Pasek u góry pokazuje liczbę maszyn w alarmie i miga.
|
||||
|
||||
### Układ siatki w zależności od liczby alarmów
|
||||
|
||||
| Alarmy | Kolumny | Wiersze |
|
||||
|--------|---------|---------|
|
||||
| 1 | 1 | 1 |
|
||||
| 2 | 2 | 1 |
|
||||
| 3 | 2 | 2 |
|
||||
| 4 | 2 | 2 |
|
||||
| 5 | 3 | 2 |
|
||||
| 6 | 3 | 2 |
|
||||
| 9 | 3 | 3 |
|
||||
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 konfiguracyjne znajdują się na początku sekcji `<script>` w pliku `als_alert_screen.html`.
|
||||
Wszystkie parametry na początku sekcji `<script>` w `als_alert_screen.html`:
|
||||
|
||||
### Lista maszyn
|
||||
| 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) |
|
||||
|
||||
```js
|
||||
const MACHINES = [
|
||||
"1.64", "1.76", "2.60", ...
|
||||
];
|
||||
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=...
|
||||
```
|
||||
|
||||
Każdy wpis to numer maszyny używany w adresie WebSocket. Aby dodać lub usunąć maszynę — edytuj tę tablicę.
|
||||
### Token (wymagany przez nowy system)
|
||||
|
||||
### Adres WebSocket
|
||||
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`.
|
||||
|
||||
```js
|
||||
const WS_BASE = "ws://luport.local/ws/als-data/";
|
||||
```
|
||||
### Format wiadomości
|
||||
|
||||
Dla każdej maszyny otwierane jest połączenie pod adresem `WS_BASE + id + "/"`,
|
||||
np. `ws://luport.local/ws/als-data/2.60/`.
|
||||
Strona obsługuje dwa typy ramek — obydwa tak samo (liczy się najnowszy stan):
|
||||
|
||||
### Format wiadomości WebSocket
|
||||
- `machine_data` — migawka wysyłana przez serwer zaraz po połączeniu,
|
||||
- `machine_update` — aktualizacje na żywo.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "machine_update",
|
||||
"machine": "2.60",
|
||||
"data": {
|
||||
"machine": "2.60",
|
||||
"status": "Automatyczny",
|
||||
"mold": 6447,
|
||||
"variant": [3],
|
||||
"cavities": 2,
|
||||
"order": "202605350 0010",
|
||||
"qty_planned": 8400,
|
||||
"qty_produced_ok": 2132,
|
||||
"qty_produced_nok": 10,
|
||||
"cycle_time_planned": 58.0,
|
||||
"cycle_time_average": 57.4,
|
||||
"progress": 25
|
||||
},
|
||||
"timestamp": "2026-05-28T12:00:32.898808"
|
||||
"type": "machine_update",
|
||||
"machine": "2.60",
|
||||
"data": { "status": "Automatyczny", "orders": ["202605350"], "progress": 25, "...": "..." },
|
||||
"timestamp": "2026-07-31T12:00:32"
|
||||
}
|
||||
```
|
||||
|
||||
Strona obsługuje tylko wiadomości z `"type": "machine_update"`. Pozostałe są ignorowane.
|
||||
---
|
||||
|
||||
### Czas ponownego połączenia
|
||||
## Odporność na awarie — dlaczego cron-restart nie jest już potrzebny
|
||||
|
||||
```js
|
||||
const RECONNECT_MS = 5000;
|
||||
1. **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ę.
|
||||
2. **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.
|
||||
3. **Wygasanie danych.** Dane maszyny starsze niż 5 min znikają z ekranu —
|
||||
alarm maszyny usuniętej z widgetu ALS nie wisi w nieskończoność.
|
||||
4. **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.onerror` jest celowy — po błędzie przeglądarka i tak wywołuje
|
||||
`onclose`, 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
|
||||
|
||||
```bash
|
||||
python3 test/testy_ekranu.py
|
||||
# lub na kiosku:
|
||||
CHROME_BIN=/usr/bin/chromium python3 test/testy_ekranu.py
|
||||
```
|
||||
|
||||
Przy zerwaniu połączenia WS strona automatycznie próbuje ponownie po 5 sekundach.
|
||||
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
|
||||
|
||||
Służy do podglądu ekranu alarmowego bez rzeczywistych danych WS.
|
||||
|
||||
### Przez URL
|
||||
|
||||
Otwórz stronę z parametrem `?debug`:
|
||||
Podgląd ekranu alarmowego bez danych i bez połączeń WS:
|
||||
|
||||
```
|
||||
als_alert_screen.html?debug
|
||||
```
|
||||
|
||||
Ładuje przykładowe dane dla maszyn `2.60`, `3.22`, `4.09` i natychmiast pokazuje ekran alarmowy. Połączenia WS **nie są** nawiązywane. Pasek statusu wyświetla `[DEBUG] widok alarmowy`.
|
||||
|
||||
### Przez konsolę przeglądarki
|
||||
|
||||
W każdej chwili można przełączyć widok bez przeładowania strony:
|
||||
|
||||
```js
|
||||
// włącz widok alarmowy
|
||||
DEBUG_ALARM = true; updateDisplay();
|
||||
|
||||
// wróć do normalnego widoku
|
||||
DEBUG_ALARM = false; updateDisplay();
|
||||
```
|
||||
|
||||
### Dane przykładowe
|
||||
|
||||
Maszyny i dane używane w trybie debug są zdefiniowane w zmiennych `DEBUG_MACHINES` i `DEBUG_DATA` — można je swobodnie edytować.
|
||||
|
||||
---
|
||||
|
||||
## Pasek statusu
|
||||
|
||||
W prawym dolnym rogu ekranu widoczny jest mały pasek z liczbą aktywnych połączeń WebSocket:
|
||||
|
||||
```
|
||||
WS: 32 / 35
|
||||
```
|
||||
albo w konsoli przeglądarki: `DEBUG_ALARM = true; updateDisplay();`
|
||||
(powrót: `DEBUG_ALARM = false; updateDisplay();`).
|
||||
|
||||
---
|
||||
|
||||
## Wymagania
|
||||
|
||||
- Przeglądarka z obsługą WebSocket, CSS Container Queries (`cqw`) i `min()` w CSS — spełnia każda nowoczesna przeglądarka (Chrome 105+, Firefox 110+, Edge 105+).
|
||||
- Dostęp sieciowy do `luport.local` (WebSocket) oraz `diluals31` (iframe).
|
||||
- Chromium/Chrome 105+ (CSS Container Queries, `min()`) — kiosk działa na
|
||||
Chromium pod Debianem.
|
||||
- Dostęp sieciowy do LuPort (WebSocket) oraz infoterminala ALS (iframe).
|
||||
|
||||
Reference in New Issue
Block a user