[114] Arduino, sieć i watchdog

[114] Arduino, sieć i watchdog

Od ukazania się serii artykułów o Arduino i pingach minęło nieco czasu. Jeden z czytelników słusznie zwrócił uwagę na to, że sytuacją awaryjną może być także zwiększenie opóźnień pingów ponad ustaloną wartość. I słusznie, bowiem sam miewam z tym problemy od czasu do czasu i pomaga wówczas rozłączenie się routera z linią – u mnie przynajmniej na 20 sekund. Uzupełnijmy więc nasz projekt o moduł wykrywania takich opóźnień.


Najlepsi dostawcy gwarantują kilkumilisekundowe opóźnienia. Zwykle otrzymuje się kilkunastomilisekundowe. Do powiedzmy 30 ms praca w sieci jest jeszcze w miarę komfortowa, jednak gdy te zaczną oscylować wokół 200 ms, przeglądać współczesnego internetu nie sposób, choć dla IoT nie musi to mieć aż takiego znaczenia. Dodajmy więc do programu kolejną funkcjonalność: wykrywanie zbyt dużych pingów.

// Deklaracje
char ssid[] = "Smialek";                  // Nazwa sieci WiFi.
char pass[] = "1234";                     // Hasło do sieci WiFi.
const char* adresWWW = "www.google.com";  // Adres stromy do badania dostępności internetu.
const int opoznienie = 3000;              // Opóźnienie w ms dla ustabilizowania połączeń z WiFi.
const int przerwa = 10000;                // Przerwa między wysyłaniem pingów.
const byte iloscProb = 10;                // Ilość prób połączenia z routerem, zanim zostanie zresetowany.
const int czasWylaczenia = 35000;         // Czas w ms wyłączenia routera.
const int czasWlaczenia = 60000;          // Czas w ms wyłączenia routera.
const int pingMaksymalny = 100;           // Maksymalny dopuszczalny czas pinga w milisekundach.

#include "WiFiS3.h"                                                    // Biblioteka obsługująca WiFi na Arduino R4
int modulWiFi = WL_IDLE_STATUS;                                        // Zmienna stanu WiFi.
int czas;                                                              // Czas w milisekundach dla funkcji ping.
byte numerProby = 0;                                                   // Numer bieżącej próby połączenia się z wybraną stroną internetową.
const byte czerwonyLed = 9;                                            // Czerwona dioda.
const byte zielonyLed = 10;                                            // Zielona dioda.
const byte przekaznik = 13;                                            // Przekaźnik rozłączający router.
#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD

void setup() {
  pinMode(czerwonyLed, OUTPUT);
  pinMode(zielonyLed, OUTPUT);
  pinMode(przekaznik, OUTPUT);
  czerwone();                           // Włącz czerwone światło.
  lcd.begin(16, 2);                     // Inicjalizacja wyświetlacza LCD
  if (WiFi.status() == WL_NO_MODULE) {  // Sprawdź połączenie z modułem WiFi na płytce.
    while (true) {}                     // Brak połączenia z modułem WiFi, zakończ pracę.
  }
}
void loop() {
  zolte();               // Badamy połączenie z wybraną stroną internetową, włącz żółte światło.
  lcd.setCursor(10, 0);  // Wyświetl RSSI.
  lcd.print(abs(WiFi.RSSI()));
  lcd.print(" dBm  ");
  lcd.setCursor(0, 0);
  czas = WiFi.ping(adresWWW);  // Wyślij ping do wybranej strony internetowej.

  if (czas > 0 && czas <= pingMaksymalny) {  // Jeśli wrócił i w czasie nie dłuższym niż określony w pingMaksymalny...
    zielone();                               // Włącz zielone światło.
    lcd.print(czas);                         // Wyświetl opóźnienie.
    lcd.print(" ms    ");
    lcd.setCursor(0, 1);  // Wyświetl adres pingowanej strony.
    lcd.print(adresWWW);

    numerProby = 0;  // Resetuj licznik nieudanych połączeń.

  } else {       // Jeśli nie wrócił...
    czerwone();  // Włącz czerwone światło.
    lcd.print("Czekam...");
    numerProby++;         // Zmniejszaj licznik nieudanych połączeń.
    lcd.setCursor(0, 1);  // Wyświetl komunikat o próbach połączenia.
    lcd.print("Odliczam: ");
    lcd.print(numerProby);
    lcd.print("/");
    lcd.print(iloscProb);
    lcd.print("   ");
    resetWifi();  // Odłącz i podłącz się ponownie do WiFi.

    if (numerProby == iloscProb) {     // Jeśli wyczepał się limit prób połączeń...
      digitalWrite(przekaznik, HIGH);  // Wyłącz zasilanie routera.
      lcd.setCursor(0, 1);
      lcd.print("Router odlaczony");
      delay(czasWylaczenia);          // Czas, przez jaki router będzie odłączony od zasilania.
      digitalWrite(przekaznik, LOW);  // Włącz zasilanie routera.
      lcd.setCursor(0, 1);
      lcd.print("Router startuje ");
      delay(czasWlaczenia);  // Czas na ustabilizowanie się routera po jego włączeniu.
      resetWifi();           // Odłącz i podłącz się ponownie do WiFi.
      numerProby = 0;        // resetuj licznik prób połączeń.
      lcd.setCursor(0, 1);
      lcd.print("                ");
    }
  }
  delay(przerwa);  // Przerwa między wysyłaniem pingów.
}
void resetWifi() {
  modulWiFi = WiFi.disconnect();       // Odłącz się od sieci WiFi.
  delay(opoznienie);                   // Opóźnienie niezbędne do ustabilizowania się połączenia.
  modulWiFi = WiFi.begin(ssid, pass);  // Połącz się z siecią WiFi.
  delay(opoznienie);                   // Opóźnienie niezbędne do ustabilizowania się połączenia.
}
void czerwone() {  // Włącz czerwone światło.
  digitalWrite(czerwonyLed, HIGH);
  digitalWrite(zielonyLed, LOW);
}
void zolte() {  // Włącz żółte światło.
  digitalWrite(czerwonyLed, HIGH);
  digitalWrite(zielonyLed, HIGH);
}
void zielone() {  // Włącz zielone światło.
  digitalWrite(czerwonyLed, LOW);
  digitalWrite(zielonyLed, HIGH);
}

I tutaj oczywiście można by program rozwinąć o dodatkowe powiadomienia, ale postanowiłem pokazać jak takie opcje zrobić w sposób wymagający najmniejszych nakładów. Pominiemy wyświetlanie informacji o przyczynie resetu, bo nie jest ona istotna. Bardzo nam to uprości zmiany – konkretnie do wpisu jednej linii i modyfikacji drugiej.

const int pingMaksymalny = 100;  // Maksymalny dopuszczalny czas pinga w milisekundach.

Najpierw zadeklarujemy nową stałą o nazwie pingMaksymalny i ustalimy jej wartość na… a to już zależy jak kto chce. Oczywiście musi być dużo wyższa od przeciętnych pingów w danym miejscu, ale też nie jakaś kosmiczna. U siebie ustaliłem ją na 100 ms, bo w wypadku zjawiska, o którym wspominałem, pingi rosną ponad tą wartość.

if (czas > 0 && czas <= pingMaksymalny) {  // Jeśli wrócił i w czasie nie dłuższym niż określony w pingMaksymalny...

Druga modyfikacja jest tutaj: teraz za zjawisko akceptowalne uznamy nie tylko pingi pojawiające się w ogóle, ale też mieszczące się w limicie. I to wszystko. Obecnie przed resetem routera nasze Arduino będzie wykonywać dziesięć prób nie tylko połączeń, ale i odpowiednio szybkiego zwrotu pinga. Dopiero jeśli dziesięć prób z kolei nie powiedzie się, nastąpi reset. Jak pisałem poprzednio, wszystkie te ilości i opóźnienia możemy ustawiać według własnych preferencji w bloku deklaracji stałych. Tu jeszcze jedna uwaga, o zjawisku, o którym wspomniał miły czytelnik: niektóre routery czasem mogą się uaktualniać. Dobrze byłoby zmierzyć czas takiej operacji i tak ustawić pułapkę na brak pingów, by w tym okienku router zdążył przejść cały ten proces. Inaczej może zapętlić się na stałe.

Czas na drugą poprawkę. Skoro Arduino pilnuje router, kto pilnuje Arduino? No właśnie – w tej odsłonie aplikacji nikt i to jest błąd. Co prawda doświadczenie wskazuje na wysoką stabilność modułów, zwłaszcza firmowych, ale strzeżonego watchdog strzeże. O watchdogu pisałem już kiedyś, więc tylko przypomnę: obok właściwego mikrokontrolera – w tej samej obudowie – czuwa układ licznikowy, możliwie prosty i niezawodny, z własnym układem taktowania, maksymalnie niezależny.

Układ ten czeka przez ustalony wcześniej czas i jeśli zostanie on przekroczony, resetuje pilnowany mikrokontroler. Na szczęście sam mikrokontroler także ma władzę nad watchdogiem i może go zresetować, wysyłając do niego sygnał, byle szybciej niż ów ustalony czas. W ten sposób niejako oba podzespoły pilnują się nawzajem, ale watchdog został zbudowany tak, by być jak najbardziej odpornym na zawieszenia.

// Deklaracje
char ssid[] = "Smialek";                  // Nazwa sieci WiFi.
char pass[] = "1234";                     // Hasło do sieci WiFi.
const char* adresWWW = "www.google.com";  // Adres stromy do badania dostępności internetu.
const int opoznienie = 3;                 // Opóźnienie w sekundach dla ustabilizowania połączeń z WiFi.
const int przerwa = 10;                   // Przerwa w sekundach między wysyłaniem pingów.
const byte iloscProb = 10;                // Ilość prób połączenia z routerem, zanim zostanie zresetowany.
const int czasWylaczenia = 35;            // Czas w sekundach wyłączenia routera.
const int czasWlaczenia = 60;             // Czas w sekundach wyłączenia routera.
const int pingMaksymalny = 100;           // Maksymalny dopuszczalny czas pinga w milisekundach.

#include "WiFiS3.h"                                                    // Biblioteka obsługująca WiFi na Arduino R4
#include <WDT.h>                                                       // Biblioteka obsługi watchdoga.
int modulWiFi = WL_IDLE_STATUS;                                        // Zmienna stanu WiFi.
int czas;                                                              // Czas w milisekundach dla funkcji ping.
byte numerProby = 0;                                                   // Numer bieżącej próby połączenia się z wybraną stoną interneetową.
const byte czerwonyLed = 9;                                            // Czerwona dioda.
const byte zielonyLed = 10;                                            // Zielona dioda.
const byte przekaznik = 13;                                            // Przekaźnik rozłączający router.
#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD

void setup() {
  WDT.begin(5592);  // Czas odświeżania licznika watchdoga, nie dłuższy od 5592 ms
  pinMode(czerwonyLed, OUTPUT);
  pinMode(zielonyLed, OUTPUT);
  pinMode(przekaznik, OUTPUT);
  czerwone();                           // Włącz czerwone światło.
  lcd.begin(16, 2);                     // Inicjalizacja wyświetlacza LCD
  if (WiFi.status() == WL_NO_MODULE) {  // Sprawdź połączenie z modułem WiFi na płytce.
    while (true) {}                     // Brak połączenia z modułem WiFi, zakończ pracę.
  }
}
void loop() {
  zolte();               // Badamy połączenie z wybraną stroną internetową, włącz żółte światło.
  lcd.setCursor(10, 0);  // Wyświetl RSSI.
  lcd.print(abs(WiFi.RSSI()));
  lcd.print(" dBm  ");
  lcd.setCursor(0, 0);
  czas = WiFi.ping(adresWWW);  // Wyślij ping do wybranej strony internetowej.

  if (czas > 0 && czas <= pingMaksymalny) {  // Jeśli wrócił i w czasie nie dłuższym niż określony w pingMaksymalny...
    zielone();                               // Włącz zielone światło.
    lcd.print(czas);                         // Wyświetl opóźnienie.
    lcd.print(" ms    ");
    lcd.setCursor(0, 1);  // Wyświetl adres pingowanej strony.
    lcd.print(adresWWW);

    numerProby = 0;  // Resetuj licznik nieudanych połączeń.

  } else {       // Jeśli nie wrócił...
    czerwone();  // Włącz czerwone światło.
    lcd.print("Czekam...");
    numerProby++;         // Zmniejszaj licznik nieudanych połączeń.
    lcd.setCursor(0, 1);  // Wyświetl komunikat o próbach połączenia.
    lcd.print("Odliczam: ");
    lcd.print(numerProby);
    lcd.print("/");
    lcd.print(iloscProb);
    lcd.print("   ");
    resetWifi();  // Odłącz i podłącz się ponownie do WiFi.

    if (numerProby == iloscProb) {     // Jeśli wyczepał się limit prób połączeń...
      digitalWrite(przekaznik, HIGH);  // Wyłącz zasilanie routera.
      lcd.setCursor(0, 1);
      lcd.print("Router odlaczony");
      nowyDelay(czasWylaczenia);      // Czas, przez jaki router będzie odłączony od zasilania.
      digitalWrite(przekaznik, LOW);  // Włącz zasilanie routera.
      lcd.setCursor(0, 1);
      lcd.print("Router startuje ");
      nowyDelay(czasWlaczenia);  // Czas na ustabilizowanie się routera po jego włączeniu.
      resetWifi();               // Odłącz i podłącz się ponownie do WiFi.
      numerProby = 0;            // resetuj licznik prób połączeń.
      lcd.setCursor(0, 1);
      lcd.print("                ");
    }
  }
  nowyDelay(przerwa);  // Przerwa między wysyłaniem pingów.
}
void resetWifi() {
  modulWiFi = WiFi.disconnect();       // Odłącz się od sieci WiFi.
  nowyDelay(opoznienie);               // Opóźnienie niezbędne do ustabilizowania się połączenia.
  modulWiFi = WiFi.begin(ssid, pass);  // Połącz się z siecią WiFi.
  nowyDelay(opoznienie);               // Opóźnienie niezbędne do ustabilizowania się połączenia.
}
void czerwone() {  // Włącz czerwone światło.
  digitalWrite(czerwonyLed, HIGH);
  digitalWrite(zielonyLed, LOW);
}
void zolte() {  // Włącz żółte światło.
  digitalWrite(czerwonyLed, HIGH);
  digitalWrite(zielonyLed, HIGH);
}
void zielone() {  // Włącz zielone światło.
  digitalWrite(czerwonyLed, LOW);
  digitalWrite(zielonyLed, HIGH);
}
void nowyDelay(int iloscSekund) {          // Pozyskaj współczynnik długości opóźnienia.
  for (int x = 0; x < iloscSekund; x++) {  // Zorganizuj pętlę zależną od pozyskanego współczynnika.
    delay(1000);                           // Opóźnienie jednosekundowe.
    WDT.refresh();                         // Resetuj timer watchdoga.
  }
}

No to do roboty. Watchdog siedzi już sobie na płytce, wystarczy go aktywować. Do tego będzie potrzebna biblioteka i oraz wpis włączający działanie układu. Zauważmy, że aktywacja wymaga także określenia opóźnienia działania watchdoga i – fatalnie dla nas – nie może przekroczyć mniej więcej pięć i pół sekundy. Fatalnie, ponieważ w szkicu deklarowaliśmy opóźnienia znacznie przekraczające ten czas, a użycie funkcji delay zamraża wszystko, w szczególności także konieczną procedurę resetującą licznik watchdoga. Innymi słowy, urządzenie będzie się w kółko resetować, bo przerwy między pingami wynoszą tu 10 sekund i więcej. Trzeba coś z tym zrobić.

Na początek proponuję odnaleźć wszystkie delay’e. Znajdziemy je w pięciu miejscach i wyznaczają je cztery stałe (dwa korzystają ze wspólnej wartości). W deklaracjach widać, że w przykładowej aplikacji tylko jedna ma wartość mniejszą od pięciu sekund, ale może zachodzić sytuacja, że będzie trzeba ją zwiększyć. Przyjmijmy, że każda wartość może przekroczyć pięć sekund. Co robić?

WDT.begin(5592);  // Czas odświeżania licznika watchdoga, nie dłuższy od 5592 ms

Cóż, najpierw zadeklarujmy sobie maksymalną dopuszczalną wartość dla licznika watchdoga. Teraz musimy przerobić wszystkie funkcje opóźniające tak, by podczas czekania na kolejne etapy nadzoru nad routerem cyklicznie resetować licznik watchdoga, nigdy nie przekraczając pięciu sekund.

Poczynimy więc następujące zmiany: żaden delay nie będzie dłuższy od sekundy. Natomiast dotychczasowe opóźnienia uzyskiwane jedną, prostą funkcją zamienimy na podprogram nowyDelay, w którym sekundowe delay’e będą wykonywane w pętlach tak długo, by iloczyn sekundy i ilości przebiegów odpowiadał temu, co było dotąd. Z tego powodu wszystkie deklaracje czasów podzielimy przez tysiąc. Teraz czasy mamy podane w sekundach i nawet lepiej to wygląda.

void nowyDelay(int iloscSekund) {          // Pozyskaj współczynnik długości opóźnienia.
  for (int x = 0; x < iloscSekund; x++) {  // Zorganizuj pętlę zależną od pozyskanego współczynnika.
    delay(1000);                           // Opóźnienie jednosekundowe.
    WDT.refresh();                         // Resetuj timer watchdoga.
  }
}

Przyjrzyjmy się nowo stworzonemu podprogramowi. Będzie się on różnił od tego, co zwykle miało miejsce przesyłaniem doń parametru. Na początku należy go wyłuskać do lokalnej zmiennej iloscSekund. Następnie organizujemy pętlę, która wykona się tyleż właśnie razy. Wewnątrz już znajdziemy znany nam delay, ale o czasie sekundy i zaraz za nim jakże tu istotny kasownik watchdoga.

Pozostaje pozamieniać delay’e w programie. Będzie to bardzo łatwe, bo wystarczy słowo delay zamienić nazwą naszego podprogramu. To co było w nawiasie staje się w tym momencie elementem przekazywanym niżej, czyli tym, co zadeklarowaliśmy na początku programu.

To już wszystko z mojej strony: taki ulepszony „pilnowacz” routera sam jest już pilnowany. Dodałbym tu jeszcze jedno: wspominałem o odłączaniu routera czy całego systemu. Oczywiście nie jest konieczne robienie tego po stronie napięcia sieciowego. Znacznie bezpieczniej będzie przeciąć przewód między zasilaczem, a gniazdkiem w urządzeniu. Jednak nie zawsze będzie to możliwe, choćby z powodów gwarancyjnych, a w przypadku rozbudowanych systemów będzie utrudnione. Zawsze należy kierować się rozsądkiem i bezpieczeństwem.

Przedstawiony sposób na wydłużenie opóźnień nie jest jedynym, ale tym razem na nim poprzestanę. Jednak biblioteka obsługująca pingi potrafi coś jeszcze, co będzie nawiązywać do najbliższego artykułu.

Powiązane tematy

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2025 arduino.pl