2023-10-08

Websockets i Viruino cz II

 


Dziś druga część opisu zmagań o przywrócenie połączenia między aplikacją a modułem w komunikacji Websocket.





Jakoś nie mogłem uwierzyć, że tak dopracowana biblioteka może zawierać błąd zawieszający (co prawda sporadycznie) komunikację pomiędzy klientem a serwerem. Podejrzewałem raczej, że to niedostatek mojej wiedzy nie pozwolił mi odpowiednio skonfigurować biblioteki WebSockets by działała bezbłędnie.

I oczywiście nie pomyliłem się.

Jak już wspomniałem kontrolę obecności odbiorcy po drugiej stronie kanału dokonuje procedura zwana Heartbeat . Transmituje i odbiera ona ramki ping-pong służące do wykrycia ewentualnych zakłóceń w przesyle komunikatów i ustawienia ich odbiorcy w status ROZŁĄCZONY.

Ramki ping-pong na pewno generuje aplikacja Virtuino. Ponadto w przypadku rozłączenia się z serwerem próbuje ona cyklicznie (co około 5 sekund) wznowić połączenie. I to powinno wystarczyć by zerwane sztywne łącze między klientem a serwerem powróciło. 

Niestety. Serwer w pierwotnej konfiguracji dostępnej na stronach VIRTUINO ( np. ESP8266 webSocket Server example ) nic nie wie o uracie połączenia z klientem. Status tego wciąż pozostaje jako POŁĄCZONY mimo zerwanej komunikacji. Co więcej, pozostając w stanie połączenia nie odbiera kolejnych poleceń typu Reconnect od klienta. A w zasadzie odbiera ale otwiera nowe kanały komunikacji z kolejnymi numerami klientów ( [1] [2] [3] i [4]).  Kolejni klienci mimo że posiadają ten sam numer IP nie są traktowani jako ten sam pierwotny klient z numerem [0]. I żadne komunikaty z aplikacji nie docierają we właściwe miejsce w serwerze. Stąd i brak komunikacji między stronami.

By ją przywrócić należy zresetować serwer (lub tylko bibliotekę WebSockets). I komunikacja zostaje ponownie nawiązana gdyż serwer staruje z pozycji, iż wszyscy klienci są rozłączeni, 

Byłem ciekaw dlaczego komunikacja zostaje przywrócona również gdy restartuje się aplikacja. I wyszło mi na to, że przed wyłączeniem Virtuino musi wysyłać do serwera komendę ROZŁĄCZ, która powoduje w serwerze rzeczywiste ustawienie statusu klienta  jako ROZŁĄCZONY. Ale to musiałby potwierdzić Ilias :)

Wszystko to można zobaczyć dodając do  funkcji z przykładu dwa ostatnie case 

void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
  switch (type) {
    case WStype_DISCONNECTED:
      Serial.printf("[%u] Disconnected!\n", num);
      break;
    case WStype_CONNECTED:
      {
        IPAddress ip = webSocket.remoteIP(num);
        Serial.printf("[%u] New client connected - IP: %d.%d.%d.%d \n", num, ip[0], ip[1], ip[2], ip[3]);
        sendPinsStatus();  // send the initial pin and variable values to the connected clients
        break;
      }
    case WStype_TEXT:  // a new message received
      {
        Serial.printf("[%u] Received: %s\n", num, payload);
        //-- The incoming payload is a json message. The following code extracts the value from json without extra library
        String str = (char*)payload;
        int p1 = str.indexOf("{\"");
        if (p1 == 0) {
          int p2 = str.indexOf("\":");
          if (p2 > p1) {
            String tag = str.substring(p1 + 2, p2);
            p1 = str.indexOf(":\"", p2);
            if (p1 > 0) {
              p2 = str.lastIndexOf("\"}");
              if (p2 > 0) {
                String value = str.substring(p1 + 2, p2);
                onValueReceived(tag, value);
              }
            }
          }
        }
        break;
      }
    case WStype_PING:  // pong will be send automatically
      {
        
         Serial.printf("get ping*****************");
        break;
      }
    case WStype_PONG:  // answer to a ping we send
      {
             Serial.printf("get pong*---------------*");
        break;
      }
  }
}


Można nimi wyświetlić komunikat gdy ramka ping lub pong zostanie odebrana. W pierwotnym przykładowym kodzie można zobaczyć jak od klienta nadchodzą ramki ping


Nie natomiast ramek pong. Poprzedni mój wpis służył temu by zrobić dodatkowy mechanizm ping-pong od strony serwera tak by serwer mógł testować dostępność klienta po drugiej stronie kanału.

A wystarczyło dodać jedną linijkę kodu w sekcji setup(), która uruchomi procedurę Heartbeat

webSocket.enableHeartbeat(5000, 3000, 2);

i już możemy zobaczyć jak serwer odbiera ramki i ping i pong.


No i problem został rozwiązany w znacznie prostszy sposób.
Pozostaje tyko wdrożyć to rozwiązanie we wszystkie moduły ESP i cierpliwie poczekać co powie na to nasz ulubiony ciąg dalszy.

Dodatek:
Powyższy sposób rozwiązuje problem utraty komunikacji klient serwer w 99%. Niestety pozostaje 1% gdy pomimo zresetowania protokołu Websocket w ESP połączenie nie zostaje przywrócone. NIe znam przyczyny ale zauważyłem że dzieje się to czasami gdy równolegle uruchamiam drugą aplikację Virtuino np. w komórce. Jedynym rozwiązaniem jakie znalazłem jest zresetowanie ESP. Trochę to brutalny sposób ale jak na razie jedyny. Serwer Websocket wysyła ramki ping do aplikacji a gdy brak odpowiedzi na 15 kolejnych pingów bezwarunkowo restartuję procesor komendą
ESP.restart();
Znalezienie przyczyny jest trudne ze względu na rzadkie występowanie przypadłości.














Brak komentarzy:

Prześlij komentarz