Ühendame mõõdiku internetiga

Selle õppetüki läbimiseks pead olema läbinud Õpime õhuniiskust ja temperatuuri mõõtma. Vajad samu töövahendeid.

Eesmärk on luua väike veebiserver. Teades ESP32 IP-aadressi, saad veebilehitsejaga vaadata jooksvaid andmeid.

ESP32 oskab WiFi võrguga käituda kolmel esineval viisil:

  • Tugijaam. ESP32 tekitab ise WiFi leviala, millele saab määrata parooli. Korraga saab olla ühendatud kuni 4 klienti. Sellist lahendust kasutatakse siis kui keskne WFi võrk puudub. Klient ei vaja samaaegset interneti juurdepääsu. ESP32 võib saaba juurdepääsu internetile läbi kliendi.
  • Tugijaam/klient. ESP32 on ise kliendida ühendatud olemasolevasse WiFi võrku ja samal ajal oskab käituda kui tugijaam ja tekitada oma WiFi võrgu. Sellist lahendust kasutatakse nö teenindusvõrgu  loomiseks kus juba kesksesse WiFi võrku lülitatud ESP32-le on vaja eraldi juurdepääsu.
  • Klient. Enimkasutatud viis kus ESP32 ja klient on ühendatud läbi  keskse WiFi võrgu  internetti ja saavad ka samaaegselt omavahel suhelda.

Testimisel kasutame tugijaama ja kliendi režiimi.

I osa. Tugijaama režiim.

#include <WiFi.h>

void setup() {
  Serial.begin(115200);

  WiFi.disconnect(true);

  Serial.println("ESP32 tugijaama režiimis! \n");

  WiFi.mode(WIFI_AP);
  WiFi.softAP("TestNet", "TestNet123");
  
  Serial.print("Tugijaama IP-aadress: ");
  Serial.println(WiFi.softAPIP());
  Serial.print("Tugijaama MAC-aadress: ");
  Serial.println(WiFi.softAPmacAddress());

  WiFi.onEvent(WiFiStationConnected, ARDUINO_EVENT_WIFI_AP_STACONNECTED);
  WiFi.onEvent(WiFiStationDisconnected, ARDUINO_EVENT_WIFI_AP_STADISCONNECTED);
  WiFi.onEvent(WiFiStationIpassigned, ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED);
}

void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.printf("Klient ühendus tugijaamaga. Kliendi MAC-aadress: %02x:%02x:%02x:%02x:%02x:%02x \n", info.wifi_ap_staconnected.mac[0], info.wifi_ap_staconnected.mac[1], info.wifi_ap_staconnected.mac[2], info.wifi_ap_staconnected.mac[3], info.wifi_ap_staconnected.mac[4], info.wifi_ap_staconnected.mac[5]);
}

void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.printf("Klient MAC-aadressiga %02x:%02x:%02x:%02x:%02x:%02x katkestas ühenduse. \n", info.wifi_ap_staconnected.mac[0], info.wifi_ap_staconnected.mac[1], info.wifi_ap_staconnected.mac[2], info.wifi_ap_staconnected.mac[3], info.wifi_ap_staconnected.mac[4], info.wifi_ap_staconnected.mac[5]);
}

void WiFiStationIpassigned(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.print("Klient sai IP-aadressi: ");
  Serial.println(IPAddress(info.wifi_ap_staipassigned.ip.addr));
}

void loop() {

}

Uute ridade selgitused:

#include <WiFi.h> Lisab WiFi teeki.

WiFi.disconnect(true); Katekstab olemasoleva ühenduse ja keelab automaatse ühenduse loomise. WiFi, nagu ka Serial ei vaja uue objekti loomist!

WiFi.mode(WIFI_AP); Lülitab ESP32 Wifi tugijaama režiimi aga tugijaam ei hakka veel tööle.

WiFi.softAP("TestNet", "TestNet123"); Käivitab WiFi tugijaama. Esimene parameeter on leviala nimi, teine salasõna.

Serial.println(WiFi.softAPIP()); Tagastab arvutile WiFi tugijaama IP-aadressi.

Serial.println(WiFi.softAPmacAddress()); … ja MAC-aadressi.

WiFi.onEvent(WiFiStationConnected, ARDUINO_EVENT_WIFI_AP_STACONNECTED);
WiFi.onEvent(WiFiStationDisconnected, ARDUINO_EVENT_WIFI_AP_STADISCONNECTED);
WiFi.onEvent(WiFiStationIpassigned, ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED);

Registreerib kolm sündmust (Event). Sündmuses kirjeldatud olukorra ilmnemisel käivitatakse automaatselt määratud funktsioon. Esimene argument on käivitatava funktsiooni nimi ja teine spetsiaalne sündmuse nimi. Funktsiooni nime võid siin ise valida – hiljem pead sama nimega funktsiooni looma. Spetsiaalse sündmuse nime leiad vastava teeki juhendmaterjalist.

  • …STACONNECTED – Klient ühendub tugijaama külge.
  • …STADISCONNECTED – Klient ühendub tugijaama küljest lahti.
  • …STAIPASSIGNED – Klient saab IP-aadressi tugijaamalt.
void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info) { ... }
void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { ... }
void WiFiStationIpassigned(WiFiEvent_t event, WiFiEventInfo_t info) { ... }

Loome funktsioonid, mida eelpool kirjeldatud sündmus välja kutsub. Pane tähele nimesid ja kus neid veel kasutatakse! Nendel funktsioonidel peab olema alati kaks argumenti (ise valitavad nimed: event ja info), mille tüübi (tüüp: WiFiEvent_t ja WiFiEventInfo_t) määrame jooksvalt.

Serial.printf("Klient ühendus tugijaamaga. Kliendi MAC-aadress: %02x:%02x:%02x:%02x:%02x:%02x \n", info.wifi_ap_staconnected.mac[0], info.wifi_ap_staconnected.mac[1], info.wifi_ap_staconnected.mac[2], info.wifi_ap_staconnected.mac[3], info.wifi_ap_staconnected.mac[4], info.wifi_ap_staconnected.mac[5]);
Serial.printf("Klient MAC-aadressiga %02x:%02x:%02x:%02x:%02x:%02x katkestas ühenduse. \n", info.wifi_ap_staconnected.mac[0], info.wifi_ap_staconnected.mac[1], info.wifi_ap_staconnected.mac[2], info.wifi_ap_staconnected.mac[3], info.wifi_ap_staconnected.mac[4], info.wifi_ap_staconnected.mac[5]);

printf on sarnane println-ile kuid võimaldab muutujate väärtusi enne tagastamist formateerida. Seepärast ka f ehk format, ln tähendab aga reavahetust. printf kasutab C++ printf funktsiooni, mille kirjelduse leiad siit https://cplusplus.com/reference/cstdio/printf/. %02x tähendab, et siia kohta kirjutatakse kahekohaline kuueteistkümnendsüsteemi konverteeritud arv. Kuna määratud on kuus kohta, siis peab järgnema ka kuus arvu. Semikoolon on lihtsalt arvude üksteisest eraldamiseks.

Samuti on siin kasutusel massiiv (Array). Massiiv on andmete talletamise viis ühe muutuja sisse nii, et igal massiivi elemendil on oma järjekorranumber. Järjekorras esimene on alati numbriga 0. Massiivi on hea kujutada meekärjena kus kärjel on üks kindel nimi (massiivi muutuja nimi) ja iga kärjekann (element) on eraldi numbriga. Nii saab iga kärjekannu sisse salvestada eraldi väärtuse. Antud juhul on massiivi nimeks info.wifi_ap_staconnected.mac ja elemendi järjekorranumbriteks 0 kuni 5. Iga element omab kümnendsüsteemis talletatud arvu, mis %02x abil teisendatakse kuuteistkümnendsüsteemi.

MAC-aadress on nutiseadme võrguliidese unikaalne aadress. Kuigi seda saab ise muuta on eesmärgiks kordumatu (281474976710656 erinevat) kombinatsioon kõikidele arvutivõrku ühendatud seadmetele.

Serial.print("Klient sai IP-aadressi: ");
Serial.println(IPAddress(info.wifi_ap_staipassigned.ip.addr));

Traditsiooniline andmete arvutile tagastamise viis. Programmikäsk IPAddress() konverteerib muutuja info.wifi_ap_staipassigned.ip.addr selliseks, et teda oleka võimalik println käsuga tagastada.

void loop() { ... } on tühi sest kõik tehakse ära eelneva koodiga.

Proovi luua endale meelepärase nime ja parooliga leviala ning ühenda oma arvuti ja telefon sellega. Jälgi Serial Monitori abil olukorda. Proovi ühendada rohkem, kui 4 seadet korraga. Proovi ping käsuga kontrollida ühenduse toimimist. Windows-iga arvutil ava Start-menüü ja kirjuta cmd, käivita rakendus Command Prompt ehk käsurida. Käsureale kirjuta ping tühik tugijaama IP-aadress, näiteks ping 192.168.4.1.

2. osa. Kliendi režiim.

#include <WiFi.h>

void setup() {
  Serial.begin(115200);

  WiFi.disconnect(true);

  Serial.println("ESP32 kliendi režiimis! \n");
  Serial.print("MAC-aadress: ");
  Serial.println(WiFi.macAddress());

  WiFi.mode(WIFI_STA);
  WiFi.begin("leviala nimi", "parool");
  
  WiFi.onEvent(WiFiConnected, ARDUINO_EVENT_WIFI_STA_CONNECTED);
  WiFi.onEvent(WiFiDisconnected, ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
  WiFi.onEvent(WiFiGotIp, ARDUINO_EVENT_WIFI_STA_GOT_IP);
}

void WiFiConnected(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.println("ESP32 ühendus tugijaamaga on loodud. ");
}

void WiFiDisconnected(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.print("ESP32 ühendus tugijaamaga katkes põhjusega: ");
  Serial.println(info.wifi_sta_disconnected.reason);
}

void WiFiGotIp(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.print("ESP32 sai IP-aadressi: ");
  Serial.println(WiFi.localIP());
}

void loop() {

}

Uute ridade selgitused:

Serial.println(WiFi.macAddress()); Tagastab ESP32 MAC-aadressi. Selle abil saab WiFi ruuterist leida õige ESP32-e.

WiFi.mode(WIFI_STA); Lülitab ESP32 kliendi režiimi aga veel ei ühenda tugijaamaga.

WiFi.begin("leviala nimi", "parool"); Alustab tugijaamaga ühendamise protsessi. Esimene parameeter on levila nimi, teine parool.

WiFi.onEvent(WiFiConnected, ARDUINO_EVENT_WIFI_STA_CONNECTED);
WiFi.onEvent(WiFiDisconnected, ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
WiFi.onEvent(WiFiGotIp, ARDUINO_EVENT_WIFI_STA_GOT_IP);

Taas registreerime kolm sündmust. Esimene argument funktsiooni nimi, teine spetsiaalne sündmuse nimetus.

  • …STA_CONNECTED – ESP32 sai tugijaamaga ühenduse aga reaalne andmeliiklus veel ei toimu.
  • …STA_DISCONNECTED – ESP32 kaotas tugijaamaga ühenduse.
  • …STA_GOT_IP – ESP32 sai tugijaamalt IP-aadressi. Nüüd reaalne andmeside toimib ja võib hakkata andmeid edastama või vastu võtma.

Serial.println(info.wifi_sta_disconnected.reason); Annab ühenduse katkemise põhjuse kohta numbri. Selle alusel saab teha edasisi otsuseid. Vaata lisaks https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html.

Serial.println(WiFi.localIP()); Tagastab tugijaamalt saadud IP-aadressi ehk ESP32 aadressi.

Proovi ühendada ESP32, sülearvuti ja telefon samasse WiFi võrku. Proovi ping käsuga kontrollida ühenduse toimimist. Võimalusel muuda töö ajal WiFi võrgu parameetreid. Keela automaatne IP-de jagamine, muuda leviala nime ja parooli.

3. osa. Lihtne veebiserver.

Selleks kasutame taas valmis programmiosist eht teeki.

#include <WebServer.h>

Seejärel loome veebiservi objekti oma valitud nimega ja pordiga. Hiljem pöördume ESP32 IP-aadressi poole. Kui kasutad siin porti 80, siis pöördud http://192.168.4.1, kui porti 88 siis http://192.168.4.1:88 jne.

WebServer MyServer(80);

Setup() osas peame määrama taas sündmused, mis kutsutakse välja siis kui vastav olukord esineb.

MyServer.on("/", handle_OnConnect); Juhul kui pöördutakse otse serveri poole, siis kutsutakse välja funktsioon handle_OnConnect.

MyServer.onNotFound(handle_NotFound); Juhul kui pöördutakse sellisele aadressile, mida pole määratud. Määratud on / aga pole /proov. Võrdub vastavalt päringutega http://192.168.4.1 ja http://192.168.4.1/proov.

Loop() osas kirjutame vaid ühe rea. Seda meetodit jääd ESP32 lõputult käivitama ja see tegeleb kõigi vajalik päringute ning vastustega.

MyServer.handleClient();

Lisaks peame määrama funktsioonid mida sündmused välja kutsuvad.

void handle_OnConnect() {
  Serial.println("Saabus päring / ...");
  MyServer.send(200, "text/html", "Tere maailm!"); 
}

void handle_NotFound() { 
  Serial.println("Saabus tundmatu päring ..."); 
  MyServer.send(404, "text/html", "Sellist lehte ei leitud!"); 
}

Arvutile saadetakse tagasi tekst.

Objekti MyServer meetod send saadab kliendile ehk veebilehitsejale info tagasi ja vajab kolme argumenti. Esimene on veakood (200 tähendab, et vigu ei esinenud). Teine on tagasi saadetavate andmete tüüp (text/html tähendab tavalist inimloetavate märkidega teksti). Kolmas on sisu (antud juhul siis tekst ise aga võib olla ka html-kood).

MyServer.begin(); Lõpuks peab veebiserveri käima panema. Seda võib teha setup() osas. Veebiserver ei vaja oma tööks toimivat võrguühendust. Kui võrk toimima saab, saab ka klient servi poole pöörduda.

Vaata lisaks https://lastminuteengineers.com/creating-esp32-web-server-arduino-ide/.

4. osa. Anduritelt andmete lugemine.

loop() osas on nüüd kasutusel MyServer.handleClient(); ja seda ei tohi delay(); käsuga eksitada. Samuti ei tohi anduri poole pöörduda tihedamini kui iga 5 sekundi järel.

Lahenduseks on tööaja mõõtmine. loop() osas mõõdame pidevalt ESP32 töötamise aega ja kirjutame selle muutujasse newTime. Samuti kontrollime newTime väärtust muutuja oldTime väärtusega. Kui vahe on suurem või võrdne 5000, siis järelikult on vähemalt 5 sekundit (5000 milliseknudit) möödas. Seejärel loeme anduritelt andmed ja anname oldTime väärtuseks newTime väärtuse. Kuidas esimesel korral määrame oldTime väärtuse? Vaata setup() osa!

Selleks peame taas määrama kaks muutujat newTime ja oldTime, tüübiks unsigned long ehk arv vahemikus 0 to 4294967295. Vaata https://www.arduino.cc/reference/en#data-types.

Lisaks määrame veel kaks tekst tüüpi (String) muutujat (nimedega TEMP ja HUMI) ning koheselt anname neile ka väärtuse “Andur pole veel valmis!”. Kui klient pöördub serveri poole esimese 5 seknudi jooksul peale käivitamist, saab ta just sellise vastuse.

Edaspidi antakse TEMP ja HUMI väärtuseteks vastavalt tegelik väärtus või tekst “Kontrolli andurit!”.

Veebiserverile päringut tehes antakse tagasi just TEMP ja HUMI väärtused.

#include <DHT.h>
#include <WiFi.h>
#include <WebServer.h>

#define DHTPIN 4
#define DHTTYPE DHT22

WebServer MyServer(80);
DHT dht(DHTPIN, DHTTYPE);

unsigned long newTime;
unsigned long oldTime;

float h;
float t;

String TEMP = "Andur pole veel valmis!";
String HUMI = "Andur pole veel valmis!";

void setup() {
  Serial.begin(115200);

  WiFi.disconnect(true);

  Serial.println("ESP32 kliendi režiimis! \n");
  Serial.print("MAC-aadress: ");
  Serial.println(WiFi.macAddress());

  WiFi.mode(WIFI_STA);
  WiFi.begin("leviala nimi", "parool");

  dht.begin();
  
  WiFi.onEvent(WiFiConnected, ARDUINO_EVENT_WIFI_STA_CONNECTED);
  WiFi.onEvent(WiFiDisconnected, ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
  WiFi.onEvent(WiFiGotIp, ARDUINO_EVENT_WIFI_STA_GOT_IP);

  MyServer.on("/", handle_OnConnect);
  MyServer.onNotFound(handle_NotFound);

  newTime = millis();
  oldTime = newTime;

  Serial.println("Käivitan veebiserveri. ");
  MyServer.begin(); 
}

void WiFiConnected(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.println("ESP32 ühendus tugijaamaga on loodud. ");
}

void WiFiDisconnected(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.print("ESP32 ühendus tugijaamaga katkes põhjusega: ");
  Serial.println(info.wifi_sta_disconnected.reason);
}

void WiFiGotIp(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.print("ESP32 sai IP-aadressi: ");
  Serial.println(WiFi.localIP());
}

void handle_OnConnect() {
  Serial.println("Saabus päring / ...");
  MyServer.send(200, "text/html", "<h1>Minu tehtud :)</h1><p>Temperatuur: "+TEMP+" &deg;C<br>&Otilde;huniiskus: "+HUMI+" %RH</p>"); 
}

void handle_NotFound() {
  Serial.println("Saabus tundmatu päring ...");
  MyServer.send(404, "text/html", "Sellist lehte ei leitud!"); 
}

void loop() {
  MyServer.handleClient();
  
  newTime = millis();
  
  if (newTime - oldTime >= 5000) {
    
    oldTime = newTime;
    
    h = dht.readHumidity();
    t = dht.readTemperature();

    if (isnan(h) || isnan(t)) {
      TEMP = "Kontrolli andurit!";
      HUMI = "Kontrolli andurit!";
    } else {
      TEMP = String(t,1);
      HUMI = String(h,1);
    }

    //Serial.println(TEMP+" - "+HUMI);
  }
}