Arduino Polska Forum

Pełna wersja: nodemcu dziwne zachowanie - resetowanie się
Aktualnie przeglądasz uproszczoną wersję forum. Kliknij tutaj, by zobaczyć wersję z pełnym formatowaniem.
Cześć,

mam 2 różna projekty na nodemcu oba podpięte do tej samej sieci WIFI i oba resetują się co jakiś czas.

Wiem że w przypadku sieciowych modułów powinienem w pętlach używać "yield()" - będę to poprawiać.

Ale problem jest chyba bardziej złożony.

Resety "samorzutne" mogę zrzucić winę na moje błędy w kodzie, ale czemu co jakiś czas obie płytki resetują się w tym samym momencie?

Co wykluczyłem?
-zasilanie (różna źródła)
-"cykl" płytki (włączane w różnym czasie)

moje podejrzenie:
jako połączone do sieci WiFi płytki dostają sygnał który je resetuje, albo odpowiedź sieci(czas??) powodują błąd i reset.


Reasumując czy ktoś wie czy jest jakiś "pakiet" który może rozsyłać router powodujący reset?
Płytki resetują się w bardzo losowych odstępach czasu (czasem kilka razy z rzędu w cyklu max 5s, a zazwyczaj 1 na 1-2h)

Będę wdzięczy za każdą podpowiedź  Blush


Załączam kod prostszego projektu.


Kod:
//#include <OneWire1.h>//DS1820
//#include <DS18B20.h>//DS1820
#include <DallasTemperature.h>//DS1820



#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>   // Include the WebServer library
#include <ZBK_Interval.h>

/*
static const uint8_t D0   = 16;
static const uint8_t D1   = 5;
static const uint8_t D2   = 4;
static const uint8_t D3   = 0;
static const uint8_t D4   = 2;
static const uint8_t D5   = 14;
static const uint8_t D6   = 12;
static const uint8_t D7   = 13;
static const uint8_t D8   = 15;
static const uint8_t D9   = 3;
static const uint8_t D10  = 1;
*/


/*                        Konfiguracja sieci                  */
const char* ssid = "#SIEC#"; //your WiFi Name
const char* password = "#HASŁO#";  //Your Wifi Password

const char* login="#LOGIN#";
const char* haslo="#HASŁO#";
const char* klucz="#KLUCZ#";
int IloscBlednychLogowan=0;



const int PinPrzek1 = D0; 
const int PinPrzek2 = D1;  
const int PinPrzek3 = D2; 
const int PinPrzek4 = D4;  //D3 przy włączaniu "pstryka"

const int PinPowerLed=D5;

byte StanPrzek1=51;
byte StanPrzek2=51;
byte StanPrzek3=51;
byte StanPrzek4=51;

 
byte stan=HIGH;

float  Temp=-126;

const float  TempON=32.0;
const float  TempOFF=25.0;
const int OpoznienieWylaczania =100000; //ms

#define ONE_WIRE_BUS 0 //D3
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);


ZBK_Interval Interval(3);
 

ESP8266WebServer server(80);    // Create a webserver object that listens for HTTP request on port 80


void handleRoot();              // function prototypes for HTTP handlers
void handleNotFound();
void handlePrzekaznik();
void handlePOWER();

void setup() {

  pinMode(PinPrzek1, OUTPUT);
  pinMode(PinPrzek2, OUTPUT);
  pinMode(PinPrzek3, OUTPUT);
  pinMode(PinPrzek4, OUTPUT);
  digitalWrite(PinPrzek1, HIGH);
  digitalWrite(PinPrzek2, HIGH);
  digitalWrite(PinPrzek3, HIGH);
  digitalWrite(PinPrzek4, HIGH);
  pinMode(PinPowerLed, INPUT);
  StanPrzek1=HIGH;
  StanPrzek2=61;
  StanPrzek3=61;
  StanPrzek4=61;
 
  Serial.begin(115200);

  Serial.print("Connecting to ");
  Serial.println(ssid);

  Interval.set(1, 1000, TempOdczyt);
  
  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  server.begin();
  Serial.println("Server started");
  Serial.print("Use this URL to connect: ");
  Serial.print("http://");
  Serial.print(WiFi.localIP());
  Serial.println("/");

server.on("/", HTTP_GET, handleLOGIN);  
server.on("/zalogowany", HTTP_POST, handleRoot);   
  server.on("/przekaznik", HTTP_POST, handlePrzekaznik); 
  server.on("/power", HTTP_POST, handlePOWER); 
  server.onNotFound(handleNotFound);      
}

void wlaczaniewiatrakow()
{
if(StanPrzek2!=2){StanPrzek2=0;digitalWrite(PinPrzek2, StanPrzek2);};
if(StanPrzek2!=2){StanPrzek3=0;digitalWrite(PinPrzek3, StanPrzek3);};
}


void wylaczaniewiatrakow()
{
if(StanPrzek2!=2){StanPrzek2=1;digitalWrite(PinPrzek2, StanPrzek2);};
if(StanPrzek3!=2){StanPrzek3=1;digitalWrite(PinPrzek3, StanPrzek3);};
}

void TempOdczyt()
{
sensors.requestTemperatures();

Temp=sensors.getTempCByIndex(0);
if(Temp<-100){Temp=127;}
if(Temp>TempON){wlaczaniewiatrakow();}else{if(Temp<TempOFF){Interval.set(3, (OpoznienieWylaczania*-1), wylaczaniewiatrakow);}}

}

void handleLOGIN() {
  Serial.println("LOGIN");
  String html = "<!DOCTYPE html> <html>\n";
  html +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  html +="<title>TEST</title>\n";
  html +="<meta charset=\"utf-8\">";
  html +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  html +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  html +="</style>\n";
  html +="</head>\n";
  html +="<body>\n";
  html +="<div id=\"webpage\">\n";

   html +="<h1>Logowanie:)</h1><form action='/zalogowany' method='POST'><div>Login:<input type='text' name='login' value=''></div><div>Hasło:<input type='password' name='haslo' value=''></div><input type='submit' value='Zaloguj'></form></br>";



  html +="</div></body></html>";

 
  server.send(200,"text/html", html);   // Send HTTP status 200 (Ok) and send some text to the browser/client
}


void handleRoot() {
   Serial.println("ROOT");
   if(((! server.hasArg("login"))||(! server.hasArg("haslo")))&&(! server.hasArg("klucz"))){ Serial.println("."); server.sendHeader("Location","/"); server.send(303);    }
   else
   {
   if(((server.arg("login")!=login)||(server.arg("haslo")!=haslo))&&(server.arg("klucz")!=klucz)){ Serial.println(".."); server.sendHeader("Location","/"); server.send(303);    }
   else{
    Serial.println(":)");
stan=digitalRead(PinPowerLed);
  String html = "<!DOCTYPE html> <html>\n";
  html +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  html +="<title>Zdalny kontroler</title>\n";
  html +="<meta charset=\"utf-8\">";
  html +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  html +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  html +="p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
 
  html +=".s1{display:inline-block; width:100px;height:25px;background-color:#5f5;}";
  html +=".s0{display:inline-block; width:100px;height:25px;background-color:#f55;}";
  
  html +="</style>\n";
  html +="</head>\n";
  html +="<body>\n";
  html +="<div id=\"webpage\">\n";
  html +="<h1>Komputer</h1>\n";

  html +="Przekażnik 1: ";
  if (stan == LOW)  {html +="<span class='s1'>Włączony</span>";}else{html +="<span class='s0'>Wyłączony</span>";}
   html +="<form action='/power' method='POST'><input type='hidden' name='klucz' value='";html+=klucz;html+="'><input type='hidden' name='przekaznik' value='1'><input type='submit' value='POWER'><select name='czas'> <option value='1000' selected>1s</option><option value='2000'>2s</option><option value='5000'>5s</option><option value='10000'>10s</option><option value='15000'>15s</option></select></form></br>";
     html +="<h1>Temperatura</h1>\n";
   html +=Temp; 
html +=" °C \n";
     html +="<h1>Stan Przekazników</h1>\n";

html +="Przekażnik 2: ";
if ((StanPrzek2%2)==0)  {html +="<span class='s1'>Włączony</span>";}else{html +="<span class='s0'>Wyłączony</span>";}
   html +="<form action='/przekaznik' method='POST'><input type='hidden' name='klucz' value='";html+=klucz;html+="'><input type='hidden'name='przekaznik' value='2'><input type='submit' value='Zmień' title='";
   html +=StanPrzek2;
   html +="'></form></br>";

html +="Przekażnik 3: ";
    if ((StanPrzek3%2)==0)  {html +="<span class='s1'>Włączony</span>";}else{html +="<span class='s0'>Wyłączony</span>";}
   html +="<form action='/przekaznik' method='POST'><input type='hidden' name='klucz' value='";html+=klucz;html+="'><input type='hidden' name='przekaznik' value='3'><input type='submit' value='Zmień' title='";
   html +=StanPrzek3;
   html +="'></form></br>";

    html +="Przekażnik 4: ";
    if ((StanPrzek4%2)==0)  {html +="<span class='s1'>Włączony</span>";}else{html +="<span class='s0'>Wyłączony</span>";}
   html +="<form action='/przekaznik' method='POST'><input type='hidden' name='klucz' value='";html+=klucz;html+="'><input type='hidden'name='przekaznik' value='4'><input type='submit' value='Zmień' title='";
   html +=StanPrzek4;
   html +="'></form></br>";
   html +="<hr><form action='/zalogowany' method='POST'><input type='hidden' name='klucz' value='";html+=klucz;html+="'><input type='submit' value='Odśwież'></form></br>";
  html +="</div></body></html>";

 
  server.send(200,"text/html", html);   // Send HTTP status 200 (Ok) and send some text to the browser/client
}
   }
}

//StanPrzek (dla 2-3)  0- włączny przez terostat 1 wyłączony ptzez teromostat 2 włączony ręzcnie 3 wyłączony ręcznie
void handlePrzekaznik() {
  Serial.println("Przekaznik");
   if(((! server.hasArg("login"))||(! server.hasArg("haslo")))&&(! server.hasArg("klucz"))){ Serial.println("."); server.sendHeader("Location","/"); server.send(303);    }
   else
   {
   if(((server.arg("login")!=login)||(server.arg("haslo")!=haslo))&&(server.arg("klucz")!=klucz)){ Serial.println(".."); server.sendHeader("Location","/"); server.send(303);    }
   else{
    Serial.println(":)");
if(server.hasArg("przekaznik")){
  Serial.println(server.arg("przekaznik"));
if(server.arg("przekaznik")=="1"){if(StanPrzek1==HIGH){StanPrzek1=LOW;}else{StanPrzek1=HIGH;};digitalWrite(PinPrzek1, StanPrzek1%2);}
if(server.arg("przekaznik")=="2"){if((StanPrzek2%2)==1){StanPrzek2=2;}else{if(StanPrzek2==2){StanPrzek2=3;}}digitalWrite(PinPrzek2, StanPrzek2%2);}
if(server.arg("przekaznik")=="3"){if((StanPrzek3%2)==1){StanPrzek3=2;}else{if(StanPrzek3==2){StanPrzek3=3;}}digitalWrite(PinPrzek3, StanPrzek3%2);}
if(server.arg("przekaznik")=="4"){if((StanPrzek4%2)==1){StanPrzek4=2;}else{if(StanPrzek4==2){StanPrzek4=3;}}digitalWrite(PinPrzek4, StanPrzek4%2);}
}
// server.sendHeader("Location","/zalogowany");  
// server.send(303); 
handleRoot() ;
}}}

void handlePOWER() {
  Serial.println("POWER");
   if(((! server.hasArg("login"))||(! server.hasArg("haslo")))&&(! server.hasArg("klucz"))){ Serial.println("."); server.sendHeader("Location","/"); server.send(303);    }
   else
   {
   if(((server.arg("login")!=login)||(server.arg("haslo")!=haslo))&&(server.arg("klucz")!=klucz)){ Serial.println(".."); server.sendHeader("Location","/"); server.send(303);    }
   else{
    Serial.println(":)");
  digitalWrite(PinPrzek1, LOW);

  if(server.hasArg("czas"))
    {
      int czas=server.arg("czas").toInt();
      if((czas>99)&&(czas<30000)){Interval.set(0, (server.arg("czas").toInt()*-1), koniecPOWER);}else{ Interval.set(0, -1000, koniecPOWER);}
    }else{ Interval.set(0, -1000, koniecPOWER);}
// server.sendHeader("Location","/zalogowany");  
  //server.send(303);  
  handleRoot() ;
}}}
void koniecPOWER()
{
Serial.println("-POWER-");
  digitalWrite(PinPrzek1, HIGH);
}



void handleNotFound(){
  server.send(404, "text/plain", "404: Not found"); // Send HTTP status 404 (Not Found) when there's no handler for the URI in the request
}


void loop() {
  Interval.update();
server.handleClient();                    // Listen for HTTP requests from clients
  }


Cechy wspólne projektów:
1) ta sama sieć WIFI
2) BibliotekiSadwszystkie najnowsze)
<DallasTemperature.h>
<ESP8266WiFi.h>
<ESP8266WebServer.h>
<ZBK_Interval.h> - autorska w oparciu o wiele projektów z sieci (poniżej załączę)

Kod:
#include "ZBK_Interval.h"
//v 0.7

/*
Inicjacja ZBK_Interval(byte slots) // deklarujemy ile slotów potrzebujemy (możemy je dynamicznie zmieniać ale nie można dodać nowych...)
update(void) // funkcja wywołująca funkcjonalność powinna być w loop
delay(long time) //funkcja podobna do zwykłego delay z tą różnicą że w trakcie trwania działą zegar - UWAGA! jeśli pod koniec czasu wywołana zosstanie "długa" funkcja może się okazać że czas trawnia przerwy będzie dłuższy niż oczekiwano
set(byte slot,  long interval, intervalFunc func) //funkcja przypisująca czas i funkcje do slotu - funkcje są przekazywane bez zmiennych
interval //zmienna częstotliwości. jeśli <0 zdarzenie tylko raz wywołane jeśli >0 zdażenie cykliczne jeśli 0 to wyłączone
funkcje start i stop dzaiłają tylko dla dodatnich intervałow
*/


ZBK_Interval::ZBK_Interval(byte slots)
{
    _slots = slots;
    _elements = (IntervalElement *) malloc(sizeof(IntervalElement)*slots);

    for (byte slot=0; slot<_slots; slot++)
    {
        _elements[slot].interval = 0;
    }
}

ZBK_Interval::~ZBK_Interval()
{
    free(_elements);
}

void ZBK_Interval::set(byte slot,  long interval, intervalFunc func)
{
    set(slot,interval,func,true);
}
void ZBK_Interval::set(byte slot,  long interval, intervalFunc func, bool active)
{
    
    _elements[slot].func = func;
    //_elements[slot].active=false;
    if(active){
        _elements[slot].interval = interval;
        _elements[slot].exetute_time = millis()+abs(interval);
    }
    else{
        _elements[slot].exetute_time = 0xffffffff-interval;//ponieważ interval zazwyczaj jest mały to żeby execute był wysoki
        _elements[slot].interval = 0;
    }
    
}


void ZBK_Interval::setInterval(byte slot,  long interval)
{
    
    //w wersji exetute_time dodatkowe działania
    //long interval_old=_elements[slot].interval;
    //przypisuję nowy czas wykonania
    
    /*if(interval>0){
    _elements[slot].exetute_time = millis()+interval;
    }else{
    _elements[slot].exetute_time = millis()-interval;
    }
    */
    _elements[slot].exetute_time = millis()+abs(interval);
    
    _elements[slot].interval = interval;
    //w wersji begin_time wystarczy
    
    if(interval==0){_elements[slot].exetute_time =0xffffffff;}///żeby tylko raz na "cyklu" robić więcej kroków
    
    
}


void ZBK_Interval::stop(byte slot)
{
    
    _elements[slot].exetute_time = 0xffffffff-_elements[slot].interval;//ponieważ interval zazwyczaj jest mały to żeby execute był wysoki
    _elements[slot].interval = 0;
    
}

void ZBK_Interval::start(byte slot)
{
    _elements[slot].interval = 0xffffffff-_elements[slot].exetute_time;
    _elements[slot].exetute_time = millis()+_elements[slot].interval;
}



/*
void ZBK_Interval::setTime(byte slot)
{
    _elements[slot].begin_time = millis();
}
*/


void ZBK_Interval::delay(long time)
{
    unsigned long exetute_time= millis()+time;
    if(exetute_time<time){//przepełnienie execute_time
        while((millis()>5000))///mogło by być while((millis()>0)) ale wstawiam 5000 żeby uniknąć zbubienia (
        {
            update();
        }
    }
    
    while((millis()<exetute_time))
    {
        update();
    }

}




void ZBK_Interval::update(void)
{
    unsigned long actual_time = millis();
    unsigned long interval_memory = 0;
    
    for (byte slot=0; slot<_slots; slot++)
    {
        
        if(actual_time>=_elements[slot].exetute_time)
        {//prawdopodobnie wykonać  - trzeba zweryfikować czy przypadkiem exetute_time nie "przeskoczył" granicy
            //if(_elements[slot].active==true){}//jeśli funkcja aktywna to nie można uruchomić nowej... (blokada dodana na potrzeby "wielowątkowości"
            //else{
                if(_elements[slot].interval>0){
                    if((actual_time-_elements[slot].exetute_time)<0x0fffffff)//skoro różnica nie jest tak duża że prawie na pewno zegar nie "przeskoczył"
                    {
                        //_elements[slot].active=true;
                        _elements[slot].exetute_time = _elements[slot].interval;
                        interval_memory=_elements[slot].interval;
                        _elements[slot].interval=0;//wyłączam
                        _elements[slot].func();
                        //_elements[slot].active=false;
                        if(interval_memory==_elements[slot].exetute_time){//na wypadek gdyby ktoś w funkcji zmienił itererval- czyli z poziomu wywołanej funkcji edytował wartość danego slotu
                            _elements[slot].interval=_elements[slot].exetute_time;//włączam spowrotem
                            _elements[slot].exetute_time = actual_time+_elements[slot].interval;        
                        }
                    }
                }else{
                    //if((_elements[slot].exetute_time-_elements[slot].interval)<actual_time){}//skoro execute + interval jest mniejsze to coś się .....
                    if((actual_time-_elements[slot].exetute_time)<0x0fffffff)//skoro różnica nie jest tak duża że prawie na pewno zegar nie "przeskoczył"
                    {
                        if(_elements[slot].interval<0){//wykluczam 0
                            //_elements[slot].active=true;
                            _elements[slot].interval=0;//wyłączam
                            _elements[slot].func();
                            //_elements[slot].active=false;
                            if(_elements[slot].interval==0){//na wypadek gdyby ktoś w funkcji zmienił itererval- czyli że ma działać dalej
                                _elements[slot].exetute_time =0xffffffff;///żeby tylko raz na "cyklu" robić więcej kroków
                            }
                        }
                    }
                }
            //}
        }
        
        

    }
}
Miałem takie serwerki, wyjątkowo proste, ot tabelka na pomiar 4 temperatur i działające w oparciu o odświeżanie strony co 1s. Czas do wywalenia to kilka dni na początku, gdy strona miała 1 linijkę i kilkadziesiąt minut gdy stronę bardziej rozbudowałem. Zmieniłem to na websockets i działa miesiącami bez resetu.
Strona może być napisana jak u Ciebie, linijka po linijce drukowana klientowi, może być cały html+css+js w rawliteral jako jeden blok tekstu, wtedy łatwiej to modyfikować i testowac poza Arduino, CTR+C/V/X i masz update kodu.
Można też użyć spiffs jako takie a la dysk twardy, gdzie po prostu są pliki strony i wtedy można wrzucić pliki strony podzielone na htdml. css, js, grafiki.
Przykład gdzie jest są pola na 4 odczyty i 4 wyjścia: https://github.com/kaczakat/ESP8266/blob...tRGB02.ino
Przypuszczam, że takie masowe tworzenie długich stringów powoduje wycieki pamięci i sieczkę w danych.
Super dzięki za odpowiedź Smile
Czułem że robię jak głupek z stringami Wink
spiffs to chyba na ten moment zbyt dużo zabawy.
Jedyny plus to łatwość grafiki ale grafikę mogę trzymać na zewnętrznym serwerze.

Strona nie jest często odwiedzana - służy tylko do "wciśnięcia" przycisku POWER w komputerze Wink [taki WOL]


Oki, to może tłumaczyć resety "asynchroniczne" ale bardziej mnie zastanawiają te w których 2 projekty w tym samym momencie się resetują myślę że jakieś dane z routera zmuszają modemcu do resetu (albo jako polecenie -wątpię, albo jako zawieszenie pętli i wymuszenie softReset)
W przykładzie który Ci wrzuciłem masz po prostu 4 zmienne zwiększane co 1s o 1, 2,3,4 i one są wyświetlane na WWW. To jest akurat pierwsza prosta, działająca wersja. Wgraj sobie to do jakiegoś modułu i odpal, sprawdzaj do jakiej wartości dojedzie licznik. Możesz tak zmodyfikować kod, że jak 1 >=60, to zwiększasz drugą, analogicznie zliczyć godziny i dni, masz uptime.
Gdyby te moduły odczytywały coś z jakiegoś serwera to może brak dostępności danych (połączenia) może powodować takie synchroniczne resety.
DziękiSmile

pierwszy projekt na razie nie ruszam bo jest w kompie Wink

drugi poprawiłem według Twoich rad.
nie mogłem użyć websockets bo przekraczam 50% a używam OTA.
ale Strona w rawliteral jest wybawieniem Wink
dodałem Ajaxa (trochę na wyrost) zamiast dynamicznej strony.
Przy okazji odkryłem że to drugi projekt wymusza reset pierwszego.
Jak drugi się łączy z siecią to pierwszy się rozłącza - muszę przeanalizować co się dzieje.

Ponieważ drugi jest dużo bardziej zaawansowany to może "sieje" coś po sieci lokalnej..
Ale przekroczyłeś 50% czego? Musiałbyś napisać kod zajmujący ponad 500kB flash, mnie się to jeszcze nigdy nie udało. Kiedyś wkleiłem 1000 linii z przysłowiami do gierki i to było całe 40kB więcej.
Gdzieś czytałem, że OTA potrzebuje 3x wielkość kodu, ale jak nie używasz spiffs to i tak zostaje 3MB wolnej przestrzeni, ewentualnie ustawiasz na 1MB SPIFFS, 1MB kod, 2MB na OTA = pojemność flash NODEMCU. Zresztą to raczej przesadzone, bo jestem prawie pewien, że robiłem OTA na ESP-01 gdzie było 1MB i program zajmował ponad 333kB.
No i oczywiście masz rację chyba źle spojrzałem i przekroczyłem 50% pamięci dynamicznej.

Projekt zajmuje 34% pamięci programu (bez websockets )
Na razie bawię się w dodawanie funkcjonalności i nie patrzę na optymalizację ale chyba najwyższa pora Wink
50% RAM to też dużo, jest go dużo, bo dużo ESP potrzebuje na to swoje WIFI.  Całą stronę trzymam w osobnym pliku index.h w tym samym katalogu, edycja jest jeszcze łatwiejsza i nie trzeba tyle skrolować w szkicu, a kod jest w pamięci flash (PROGMEM):
Kod:
#ifndef INDEX_H
#define INDEX_H
 

static const char PROGMEM INDEX_HTML[] = R"rawliteral(

<!DOCTYPE html>
<html>



/////////////////////////////////////////////
Nieistotny kod HTML/JS
//////////////////////////////////////   

</html>
)rawliteral";

#endif  // ifdef
Do pliku INO wystarczy dodać include z tym plikiem, oba się otwierają w osobnych zakładkach w Arduino IDE.
Z tym + DS+Thinkspeak+OTA + websockets +ESPAsyncWiFiManager.h (to też jest fajne, jak komuś dajesz płytkę, to sam sobie ustawi AP i hasło) , no i w sumie bez HTML 700 linii zajmuje 40% RAM. 
Ajax to coś od czego zaczynałem, niestety tutoriali wtedy nie znalazłem, a do websockets już tak (to następca AJAX, no i właściwie to znalazłem przykład, który przerobiłem, tutorial to HTML, JS i CSS z kanału Youtube "Pasja Informatyki M. Zelent") , a na UNO to jedyna opcja, jest jakiś Ajax mini, na websockets w UNO nie ma perspektyw z 2kB RAM, jest tylko biblioteka w wersji klienta.