TCP adatkapcsolat wifi-vel

A NodeMCU alaplap ismertetése közben láthattunk néhány mintapéldát arra, hogyan lehet az alaplappal felcsatlakozni wifi routerünkre, illetve azt is, hogyan lehet Acces Point-ot kialakítani vele, ha nincs wifi routerünk, vagy nem is akarjuk, hogy azon keresztül érjük el. Azt is megismerhettük, hogyan lehet webservert kialakítani az alaplap programjában, ami egy mobiltelefonról fogad parancsokat, illetve arra továbbít információt az alaplapon történt eseményekről. Ez mind nagyon szép, de ennek felhasználáshoz meg kell ismerni a html programozást, miközben lehetséges, hogy csak simán adatokat akarunk eljuttatni A pontból közelei (wifi hatósugáron belüli) B pontba. Peresze ha router-hez csatlakozunk a végpontokkal, akkor bárhol lehet a két végpont a világban.

Ez az ismertető arról szól, hogyan lehet látványos webserver nélkül, pusztán csak adatokat továbbítani ezzel az alaplappal. A leírás végére megismerjük (megismertem), hogyan lehet kialakítani, hogy több NodeMcu kártya beszélgessen egymással! Be is szereztem még néhány alaplapot, bár igyekeztem ezt minél kevesebb költségből megtenni. Így esett a választás a NodeMCU D1 mini alaplapra. Sikerült 2db-ot beszerezni 800Ft/db áron ferdeszemű kereskedőinken keresztül. Hála nekik, bár hazánkban sem drága nagyon! Így néz ki:

Ezen az alaplapon csak egy beépített LED található, és más semmi, ezért a mintapéldákban a beépített LED-et kapcsolgatom ki és be! Gyakorlatilag teljesen azonos a NodeMCU V3 alaplappal. Még másik alaplap típust sem kell választani, maradhat a „NodeMCU 1.0 (ESP-12E module”, de választhatjuk pl a „LOLIN(WEMOS) D1 R2 & mini”-t is! utóbbi gyorsabban tölti fel a programot, mert a port sebességet 921600-ra állítja. Ezt azonban kézzel az előbbi alaplap típusnál is megtehetjük. Ez sok időt spórol a fejlesztésben.

Találtam más olcsó alaplapot is. Ime a másik alkalmazott típus:

Ez a NodeMCU V2. 1500Ft az ára! Ezen van egy flash nyomógomb, amit szabadon felhasználhatunk. Ez teljesen csereszabatos a NodeMCU V3-al, csak olcsóbb. Biztosan szerényebb valamiben, de még nem jöttem rá, hogy miben!

De térjünk végre a lényegre! TCP protokoll felhasználásával fogunk adatokat küldözgetni! Érdemes egy kicsit utána olvasni, hogy mi is ez. Hozzám hasonlóan lusták számára összefoglalom pár mondatban:

A TCP protokollt adatok veszteségmentes továbbítására dolgozták ki. Az interneten általában ezzel kommunikálnak a számítógépek egymással. Mit is jelent az, hogy veszteségmentes? Pusztán csak annyit, hogy biztosan nem veszítünk adatot. Ha valami elveszik vagy megsérül, azt ez a protokoll saját maga megismétli, egyáltalán nem kell tudnunk róla, hogy mi zajlik a háttérben. Vagy az elküldött adatot fogja megkapni a címzett, vagy egy hibaüzenetet, hogy nem sikerült. Érezhető, hogy rossz minőségű kapcsolaton ez nagyon lassú is lehet, mert a protokoll sok időt tölthet az adatok ismételgetésével. Fontos tulajdonság, hogy az elküldött adatok ugyanabban a sorrendben érkeznek meg, mint ahogyan elküldtük. A Protokoll sorszámot ad a csomagoknak, és a címzett „kimeneten” ebben a sorrendben lehet kiolvasni.

Vannak olyan esetek, amikor a TCP nem hatékony. Ha pl. rádióműsort sugároz az internetes rádiócsatorna, vagy videó közvetítést nézünk. Nincs idő az ismétlésekre, mert a vevőben lemaradhatunk az eseményekről. Ekkor inkább essen szét a videókép, vagy némuljon el egy pillanatra a hang, de legyen élő a közvetítés. Erre találták ki az UDP protokollt! Ez nem ellenőrzi, hogy megérkezett-e az adat. Csak nyomatja ki magából az adó oldal az adatokat, függetlenül attól, hogy megérkeznek-e! Sőt! Akár a sorrend is sérülhet. Egy később küldött adatcsomag esetleg hamarabb érkezik meg a vevőbe, mint egy később küldött, mert közben talált az internet egy rövidebb útvonalat. No ezzel a protokollal nem foglalkozunk, de érdemes tudni róla, hogy az általunk használt ESP8266 Community alaplapkezelőbe ez mind mind be lett építve, csak használni kell!

  1. példa: NodeMCU STA módban kapcsolódik lakásunk routerére, és adatokat cserél egy PC-n futó puTTY program.

Az általam internetes forrásokból kiszedett programrészek felhasználásával írt program nagyon egyszerű dolgot csinál. A setup részben a NodeMCU felkapcsolódik a wifi routerre. Kap routertől egy IP címet. Ezt ki is írja, mer szükségünk lesz rá:

(Sajnos utólag vettem észre, hogy rossza felirat! Nem a kliens IP címét írja ki, hanem a sajátját. A képet már nem csináltam újra, de a program forrásben átírtam!)
Ezt követően elindítja a szerver szolgáltatást, ami figyeli a megadott portot (jelen esetben 31200). A loop folyamatosan várakozik arra, hogy a szerverhez kapcsolódjon egy kliens, és ha ez megtörténik a szerver avaiable metódusa true értékkel jelzi, hogy ez megtörtént. Ehhez persze kell egy kliens program, ami jelen esetben a hálózatban található laptopomon egy puTTY program elindításával jött létre:

A puTTY-ban meg kellett adni az IP címet, és a portszámot, amihez kapcsolódhat, valamint a kapcsolat típusát, ami egy „RAW” kapcsolat. Ez nem igazán támogatott kapcsolat a nyílt interneten, mert könnyedén meghekkelhetővé teszi a gépet. Azonban itt a belső lokális hálózatban tökéletesen megfelel. Ha interneten keresztül akarnánk megszólítani a NodeMCU-t (fix ip vagy az aktuális ip ismerete és portátirányítás kell hozzá a routerben), akkor biztonságosabb lenne az SSH használata. Erre is vannak külön könyvtárak, megvalósíthatónak látszik, de nekem nincs rá szükségem, mert maradok házon belül! Szóval elindítjuk a puTTY-ot, ami feldobja a terminálablakot. Ez nálam szép fekete:

A terminál ablakba máris lehet írogatni, illetve itt fognak megjelenni a NodeMCU által küldött adatok. A program közben kiírta, hogy felcsatlakozott egy kliens, kiírja az ip címét és portszámát, és belemegy egy végtelen ciklusba, ami ellenőrzi, hogy jelen van-e még a kliens! Ha igen, akkor ellenőrzi, hogy ban a az olvasási puffereben  adat, és ha igen, akkor azonnal kiolvassa. Ha a kliens lecsatlakozik, ami azonos a puTTY program bezárásával, akkor kilép a végtelen ciklusból, és újra várakozik egy kliensre!

Ha a kliens adatokat küld, akkor a program közvetlenül ki is írja karakterenként a soros monitorra. Ha „b” betű érkezett, bekapcsolja az alaplapra épített LED-et, „k”-val kikapcsolja. Választ is küld a kliensnek, amivel jelzi ha be vagy kikapcsolta a LED-et. Ennyit tud! A fenti puTTY képen látható, hogy mit írtam be, és mit kaptam vissza. A NodeMCU oldalán ugyanekkor ez volt látható:

És itt a forráskód is:

/*********************************************************************************************
* Ezzel a programmal üzenteket küldhetünk az otthoni hálózatunk egyik számítógépéről         *
* pl. puTTY programmal a Wifi STA módban csatlakozott NodeMCU-nak.                           *
* A wifi hálózatunk állomásnevét és a jelszót ki kell cserélni a programban az aktuálisra.   *
* Csatlakozás után a soros portra írja a router által adott IP címet, erre kell küldenünk    *
* az adatainkat a puTTY programmal, amit "RAW" módba kell állítani, megadni ezt az IP címet  *
* és a 32100-as portszámot. A puTTY-ban beírt adatainkat enterrel kell lezárni, a puTTY      *
* ekkor küldi el, és ekkor kerül a NodeMCU programjának olvasási pufferébe.                  *
**********************************************************************************************/
#include "ESP8266WiFi.h"
WiFiServer tcp_szerver(31200);  //A 31200-as porton figyelő TCP szerver létrehozása (a portszám bármi lehet
                                //de ha gyakran használt portot használunk, az okozhat problémát, ha a hálózaton
                                //vannak még más működő TCP eszközeink, pl. egy webszerver, ami alapértelmezetten
                                //a 80-as portot használja, és ennek is 80-at állítunk be stb. Célszerű 1000 feletti 
                                //portszámot megadni.
WiFiClient tcp_kliens;          //WiFiClient típusú objektum, ami a csatlakozott kliens tulajdonságaival rendelkezik 
String uzenet_sor;              //ebben a változóban tároljuk a kliensről küldött üzenet egy sorát 
                                //(a kliens oldalon entert-kell ütni, hogy egy sor megjelenjen a soros monitoron)
byte adat;                      //ebbe a változóba olvassuk be a klienstől érkezett byte-okat.

void setup() {
  pinMode(D4,OUTPUT);      //ez az a kimenet, amire az alaplapi LED van kötve. Fordítva van bekötve, HIGH-ra nem világít
  digitalWrite(D4,HIGH);   //azért, hogy ne világítson a LED  
  Serial.begin(115200);
  Serial.print("Wifi kapcsolódás indul a ***** nevű állomáshoz...");
  WiFi.begin("*****","########");  //kapcsolódás a megadott wifi állomáshoz STA módban (első paraméter állomásnév, második jelszó)
  while (WiFi.status() != WL_CONNECTED) {           //Várakozunk a Wifi hálózathoz történő csatlakozásra
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi kapcsolat OK!");
  Serial.print("Saját IP cím:");  Serial.println(WiFi.localIP());
  delay(1000);
  tcp_szerver.begin();     //elindítjuk a tcp szervert, ami figyeli a megadott portot, hogy érkezik-e valami
}

void loop() {
  tcp_kliens=tcp_szerver.available();         //annak lekérdezése, hogy van-e csatlakozott kliens (tcp_kliens=0 ha nincs, 1 ha van)
  if (tcp_kliens) {                          //ha 1, akkor van kliens csatlakozva
    Serial.print("Csatlakozott a kliens (");
    Serial.print(tcp_kliens.remoteIP());     //a csatlakozott kliens IP címe
    Serial.print(", ");
    Serial.print(tcp_kliens.remotePort());   //a csatlakozott kliens portszáma. Nem azonos a programban figyelő szerver portszámmal
                                             //ami jelen esetben 31200
    Serial.println(")");
    tcp_kliens.setNoDelay(1);                //Arra utasítja a szervert, hogy azonnal küldje az adatokat, ne várjon össze egy csomag méretnyit
                                             //nem tapasztaltam különbséget, ha benne van a programban vagy ha nincs. Félek rosszul értelmezem a szerepét!
    while (tcp_kliens.connected()) {         //ellenőrizzük, hogy a kliens csatlakoztatva van-e még. Ha ugyanis a kliens már nem 
                                             //csatlakozik, ezzel kiléphetünk ebből a ciklusból

      //Karakterenként olvassuk az üzenetet és vizsgáljuk az érkezett byte-okat
      while (tcp_kliens.available()>0) {     //lekérdezzük, hogy van-e elérhető adat a pufferben (kliens által küldött adat)
                                             //ha ezt a vizsgálatot nem tesszük be, akkor folyamatosan 255-öt olvas, de
                                             //közben az általunk küldött adatok is megérkeznek. Ha egy kiolvasott byte értéke
                                             //végzi a beavatkozást, akkor olvashatunk folyamatosan byte-onként is.
        adat = tcp_kliens.read();            //kiolvasunk egy byte-ot a pufferben található adatokból
        Serial.write(adat);                  //A kiolvasott byte-ot kiírjuk a soros portra
        if(adat==98) {digitalWrite(D4, LOW);tcp_kliens.println("LED bekapcsolva");}    //"b" betű (98) bekapcsolja a LED-et
        if(adat==107) {digitalWrite(D4, HIGH);}                                        //"k" betű (107) kikapcsolja a LED-et
      }
      //A fenti while ciklus helyettesíthető egyetlen olvasási függvénnyel:
      //   uzenet_sor = tcp_kliens.readStringUntil('\r');  //ez addig olvassa a puffert, amíg return karaktert (chr13) talál
      //Ha ide tesszük az alábbi soronkénti olvasást, akkor azt fogjuk tapasztalni, hogy kb 5 másodpercenként egy sort dob a soros
      //monitor, mintha kapott volna egy entert (decimális 13 és 10 egymás után két byte-on). 
      //Feltételezem, hogy a puTTY tehet erről az időnként küldött enter-ről! Nem vizsgáltam tovább a dolgot, mert nem kellett!
      //   Serial.println(uzenet_sor);   // Persze kell hozzá kiírás is a soros portra
      delay(10);
    }
    tcp_kliens.stop();
    Serial.println("Kliens kapcsolat bontva");
  }
}

Mint a próbálkozásaim közben kiderült, egyszerre csak egy puTTY programmal tudja tartani a kapcsolatot a fenti program. Ennek oka, hogy a puTTY nem engedi el azt a szálat, amit felépített a NodeMCU-ban működő szerverrel. Ez kissé aggasztott, bár mint később kiderül majd, ez csak a puTTY sajátossága, ha kliens oldal egy másik NodeMCU, akkor ez a probléma kézben tartható, és akár több kliens is lehet párhuzamosan. Hiába, a hálózatelméleti alapok hiányoznak a tudástáramból. Ennek köszönhetően elkezdtem megoldást keresni a problémára, és olyan forrást kialakítani, ami egyszerre több puTTY-al is képes kommunikálni. A dolog nem volt nehéz! A trükk egy újabb metódus, aminek neve hasclient(). Lényegében csak annyit csinál, hogy true értékkel tér vissza, ha van olyan kliens, ami kapcsolatot szeretne felépíteni a szerverrel. Valamint a WifiClient típusú objektumból immár nem csak egy példányra van szükség, hanem pont annyira, amennyivel egyszerre kapcsolatban szeretnénk állni. Én egy három elemű tömböt vettem fel erre a feladatra. A programot sok-sok kommenttel láttam el, abból szerintem a működés megérthető. Három fő része van. Első a setup(), amiben felépítjük a kapcsolatot a wifi routerrel. A második – és ezt külön függvénybe tettem – ami a kliensek kapcsolódását és kapcsolat bontását kezeli. A harmadik a kliensektől érkező adatokat kezeli, azaz ki és bekapcsolja a beépített ledet. Utóbbi két függvényt a loop() hívogatja ciklikusan. Három klienst kezel, az esetleges  negyedik vagy továbbiak várakoznak. Érdemes kipróbálni, és sok puTTY-ot elindítani párhuzamosan.

Íme a tanulságos forrás (nekem volt tanulságos):

/*********************************************************************************************
* Ezzel a programmal üzeneteket küldhetünk az otthoni hálózatunk több számítógépéről         *
* (pl.) a puTTY programmal, a Wifi STA módban csatlakozó NodeMCU-nak. Az is lehetséges       *
* hogy egy számítógépünkön több puTTY programot indítunk el.                                 *
* A wifi hálózatunk állomásnevét és a jelszót ki kell cserélni a programban az aktuálisra.   *
* Csatlakozás után a soros portra írja a router által adott IP címet, erre kell küldenünk    *
* az adatainkat a puTTY programmal, amit "RAW" módba kell állítani, megadni ezt az IP címet  *
* és a 1001-es portszámot. A puTTY-ban beírt adatainkat enterrel kell lezárni, a puTTY       *
* ekkor küldi el, és ekkor kerül a NodeMCU programjának olvasási pufferébe.                  *
**********************************************************************************************/
#include "ESP8266WiFi.h"
WiFiServer tcp_szerver(1001);  //A 31200-as porton figyelő TCP szerver létrehozása (a portszám bármi lehet
                                //de ha gyakran használt portot használunk, az okozhat problémát. Pl. ha a hálózaton
                                //van egy webszerver, ami alapértelmezetten a 80-as portot használja, és ennek 
                                //is 80-at állítunk be, "összeakadhatnak". Célszerű 1000 feletti portszámot megadni.
WiFiClient tcp_kliens[3];       //WiFiClient típusú objektum, ami a csatlakozott kliens tulajdonságaival rendelkezik, 
                                //ebben a programban 3db kliens adatait és kapcsolatát tudunk kezelni. Továbbiakban
                                //tárolónak fogom nevezni az egyszerűség kedvéért.
bool kliens_jelenlet[3];        //ebben a tömbben adminisztrálom, hogy az adott sorszámmal (0,1,2) van-e kliens bejelentkezve
                                //Ha bejelentkezik egy kliens, ízt 1-el jelzi az adott tömbelem
long kliens_ido=millis();       //időnként demó üzeneteket küldök a klienseknek, ennek időzítéséhez használom ezt a változót
bool uzenet[]={0,0,0};          //ha bebillentem a tömbelemeket 1-re, akkor ezzel jelzem, hogy a következő ciklusban üzenetet kell 
                                //küldeni az adott sorszámú kliensnek. Ha elment az üzenet, visszabillentem 0-ra, hogy ne ismétlődjön
                                //(10 másodpercenként billentem be).
bool ismetles_tilt=0;           //ha 3-nál több kliens akar csatlakozni, akkor ez akadályozza meg, hogy a figyelmeztető
                                //üzenetet folyamatosan ismételgessük (1-el letiltja a további kiírásokat)

void setup() {
  pinMode(D4,OUTPUT);      //ez az a kimenet, amire az alaplapi LED van kötve. Fordítva van bekötve, HIGH-re nem világít
  digitalWrite(D4,HIGH);   //azért, hogy ne világítson a LED  
  Serial.begin(115200);
  Serial.print("Wifi kapcsolódás indul a ***** nevű állomáshoz...");
  WiFi.begin("*****","########");  //kapcsolódás a megadott wifi állomáshoz STA módban (első paraméter állomásnév, második jelszó)
  while (WiFi.status() != WL_CONNECTED) {           //Várakozunk a Wifi hálózathoz történő csatlakozásra
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi kapcsolat OK!");
  Serial.print("Kliens saját IP cím:");  Serial.println(WiFi.localIP());
  delay(1000);
  tcp_szerver.begin();     //elindítjuk a tcp szervert, ami figyeli a megadott portot, hogy akar-e csatlakozni kliens, 
                           //illetve a kliens akar-e küldeni adatot.
}

void loop() {
  kliens_csatlakozas();    //ellenőrizzük, hogy vár-e kliens csatlakozásra, ha igen, akkor keresünk neki egy üres tárolót
                           //amiben adminisztráljuk a csatlakozási adatait. Ha közben a kliens lecsatlakozott, akkor 
                           //felszabadítjuk az általa használt tárolót egy másik lehetséges kliensnek (pl. egy negyedik várakozónak)
  
  kliens_kezeles(0);       //az 0-as sorszámú klienst ellenőrizzük, hogy akar-e üzenetet küldeni, ha igen fogadjuk és beavatkozunk, üzenetet küldünk időnként neki
  kliens_kezeles(1);       //az 1-es sorszámú klienst ellenőrizzük, hogy akar-e üzenetet küldeni, ha igen fogadjuk és beavatkozunk, üzenetet küldünk időnként neki
  kliens_kezeles(2);       //az 2-es sorszámú klienst ellenőrizzük, hogy akar-e üzenetet küldeni, ha igen fogadjuk és beavatkozunk, üzenetet küldünk időnként neki

  if (kliens_ido+10000<millis()) {   //tíz másodpercenként küldünk minden kliensnek üzenetet
    kliens_ido=millis();             //innen indul a következő 10 sec időzítés
    uzenet[0]=1;                     //ezzel jelezzük a kliens_kezeles() függvénynek, hogy küldjön üzenetet a 0. kliensnek
    uzenet[1]=1;                     //ezzel jelezzük a kliens_kezeles() függvénynek, hogy küldjön üzenetet a 1. kliensnek
    uzenet[2]=1;                     //ezzel jelezzük a kliens_kezeles() függvénynek, hogy küldjön üzenetet a 2. kliensnek
  }
}

void kliens_csatlakozas() {
/****************************************************************************************
* Ez a függvény ellenőrzi, hogy van-e csatlakozásra váró kliens, és ha van, és szabad   *
* tároló is van az adatainak tárolására, akkor létrehozza a kapcsolatot, és a kliens    *
* adatait betolti a tárolóba. A kliens_jelenlet[] tömbben jelzi, hogy a tároló már      *
* nem szabad. Ha már mindhárom tároló felhasználásra került, akkor hibaüzenetet ír ki   *
* amikor egy további kliens akar csatlakozni.                                           *
* Ha egy kliens bontja a kapcsolatot, akkor a kliens_jelenlet[] tömbben jelzi, hogy     *
* az adott tároló felszabadult, és ha van várakozó kliens, azt azonnal beengedi és      *
* adatait az adott sorszámú tárolóba betölti.                                           *
*****************************************************************************************/
  //Ha már vannak csatlakozott kliensek, akkor ellenőrizzük, hogy él-e még a kapcsolat
  for (byte i=0;i<3;i++) {                //Mindhárom csatlakozási lehetőséget (tárolót) megvizsgáljuk
    if (!tcp_kliens[i].connected()) {     //ha az éppen vizsgált tárolóhoz kapcsolódó  kliense már nincs jelen, 
                                          //akkor felszabadítjuk a tárolót
      if (kliens_jelenlet[i]==1) {        //így csak egyszer írjuk ki a lecsatlakozást, mert az a változó a további ciklusokban már 0
        Serial.print("Kliens ");Serial.print(i);
        Serial.println(" lecsatlakozott!");
        ismetles_tilt=0;                  //ismét előfordulhat, hogy nem tudunk több klienst beengedni, ezzel biztosítjuk
                                          //hogy a negyedik kliens csatlakozásakor egyszer legyen hibaüzenet 
      }
      kliens_jelenlet[i]=0;               //ezzel jelezzük, hogy ebbe a sorszámú tárolóba betölthetjük egy újabb csatlakozó kliens adatait
    }
  }
  //megvizsgáljuk, hogy várakozik-e kliens a csatlakozásra, ha mindhárom tároló foglalt, akkor
  //hibaüzenetet írunk ki. Ha van szabad tároló, akkor ciklussal megkeressük azt a tárolót és
  //engedjük a csatlakozást, a kliens adatait betöltjük a tárolóba
  if(tcp_szerver.hasClient()){              //Ez akkor igaz, ha van várakozó kliens
    if (kliens_jelenlet[0]==1 and kliens_jelenlet[1]==1 and kliens_jelenlet[2]==1) {  //akkor igaz, ha mindhárom tároló foglalt
        if (ismetles_tilt==0) {                      //csak egyszer írjuk ki a hibaüzenetet, különben minden ciklusban kiíródna
          ismetles_tilt=1;
          Serial.println("Nem csatlakozhat több kliens!");
        }
    }

    for (byte i=0;i<3;i++) {                         //Keresünk egy üres tárolót a kliens adataihoz
      if (kliens_jelenlet[i]==0) {                  //Ha ez 0, akkor a tároló üres, ide beolvashatjuk a kliens adatait
        tcp_kliens[i] = tcp_szerver.available();     //ez a lépés tárolja a kliens adatait az adott sorszámú tárolóban (objektum példányban)
        if (tcp_kliens[i]) {                          //Ha a kliens csatlakozása sikerült, kiírjuk az adatait
          Serial.print("Kliens ");Serial.print(i);
          Serial.print(" csatlakozott (");
          Serial.print(tcp_kliens[i].remoteIP());     //a csatlakozott kliens IP címe
          Serial.print(", ");
          Serial.print(tcp_kliens[i].remotePort());   //a csatlakozott kliens portszáma. Nem azonos a programban figyelő szerver portszámmal
          Serial.println(")");
          kliens_jelenlet[i]=1;                       //ez a fenntartott tároló már foglalt
        } 
      }   
    }     //for vége
  }       //kliens várakozás vizsgálat vége
}

void kliens_kezeles(byte sorszam) { 
/***********************************************************************************
* Ez a függvény megvizsgálja a paraméterben átadott sorszámú tárolóhoz tartozó     *
* klienst, hogy küldött-e valamit, és ha igen, akkor kiolvassuk a puffert.         *
* ha az uenet[] változó jelzi, akkor küld egy demó üzenetet a kliensnek.           *
************************************************************************************/
  byte adat;                              //ebbe a változóba olvassuk be a klienstől érkezett byte-okat.
  if (tcp_kliens[sorszam].connected()) {  //ellenőrizzük, hogy a kliens csatlakoztatva van-e még. Ha igen, akkor megnézzük küldött-e valamit 
    if (uzenet[sorszam]==1) {             //üzenetet kell küldeni
      uzenet[sorszam]=0;                  //következő küldési ciklusban így már nem küldünk üzenetet
      tcp_kliens[sorszam].print("Uzenet kliens ");
      tcp_kliens[sorszam].print(sorszam);
      tcp_kliens[sorszam].println("-nek!");
    }
    while (tcp_kliens[sorszam].available()>0) {     //addig olvasunk, amig van küldött adat a pufferben
        adat = tcp_kliens[sorszam].read();          //kiolvasunk egy byte-ot a pufferben található adatokból
        Serial.write(adat);                         //A kiolvasott byte-ot kiírjuk a soros portra
        if(adat==98) {digitalWrite(D4, LOW);tcp_kliens[sorszam].println("LED bekapcsolva");}    //Ha "b" betű (98) bekapcsoljuk a LED-et
        if(adat==107) {digitalWrite(D4, HIGH);tcp_kliens[sorszam].println("LED kikapcsolva");}  //Ha "k" betű (107) kikapcsoljuk a LED-et
    }
  }
}

2. példa: NodeMcu Acces Point és szerver és sok rá csatlakozó NodeMCU kliens.

Szakadjunk most el attól, hogy PC-ről akarjuk a beépített LED-et vezérelni. A végcél egyébként is az volt, hogy legyen egy NodeMCU, ami szerverként működik. Lehetőleg legyen független a lakás routerétől, és egy vagy több NodeMcu klienstől fogadjon adatokat. Kell lennie kell egy Acces Pointnak amire a kliensek kapcsolódnak, és logikusan ez lesz a szerver is. Azt hiszem a kliensek is beszélgethetnek közvetlenül egymással, de akkor a megszólított kliensnek szerver funkciót is el kell látnia, aminek a portja (amin figyeli a kéréseket) nem lehet azonos másik szerver portjával. A feltételezés helyességét azonban nem volt még időm (és igényem) igazolni.

A fenti elképzelést valósítja meg a következő példa program. Az egyik NodeMcu az Acces Point és egyben a szerver. A kliensek erre az Acces Pointra kapcsolódnak rá, és megadott szerver portra konnektálnak. Ebben a tekintetben hasonlítanak a puTTY programra. Mint kiderült számomra, a kliens programjában rendelkezésre áll a „stop” metódus. Ez lezárja a szerverrel felépített kapcsolatot, és a szerver kereshet magának egy másik klienst, aki várakozik kapcsolatra. Tehát nem úgy funkcionál a kliens, mint a puTTY. Még ennél is összetettebb kicsit a dolog, mert a kliensek felől érkező adatokat a szerver megkapja és puffereli, de ebből a pufferből csak akkor tudja a szerver program kiolvasni, ha befejezte egy klienssel való kommunikációt valaminek a hatására. Legalább is így tapasztaltam! A befejezés módja a kezünkben van. Lehet egy időtúllépés, amit nekünk kell meghatároznunk. Nálam egy másodperc után a szerver másik klienst keres magának. Azonban maga a kliens is jelezheti, hogy most éppen nincs küldendő adata. Én erre az „END” karaktersorozatot használom. Ha szerver END-et kap a klienstől akkor másik klienst keres, akinek már van adata a pufferben, és kiolvassa azt.
A kliensek a demó programomban „V1” illetve „V2” karakterekkel azonosított parancsokat küldenek. Ha a szerver ezt felismeri, akkor egyszer, illetve kétszer villantja fel a LED-et! A szerver a led felvillanást vissza is jelzi a kliensnek szövegesen. A program magáért beszél! Nyilván a kipróbáláshoz minimum két, de inkább három NodeMCU kell. Egy Acces Point, azaz szerver funkcióba, és egy vagy több kliens. A kliensekre úgy kell feltölteni a programot (lényegileg ugyanazt a programot, hogy a küldött parancsot ez első kliensnél V1-re, míg a másiknál V2-re „égetjük” a forráskódban. És máris lehet játszadozni!

Szerver program forráskód:

/**************************************************************************************************** 
 * Ez a program egy példa arra, hogyan lehet létrehozni egy szervert AP módban, és a rajta lévő     *
 * beépített LED-et felvillantani, ha a szerver egy előre definiált parancsot kap a kliensektől.    *
 * A kliensek programja V1 illetve V2 és END parancsokat küldözgethet. Amikor a kliensen megnyomjuk *
 * A FLASH gombot, akkor kapcsolódik a szerverhez, és küld egy "V1" parancsot. A második kliens     *
 * V2-őt küld. A szerver programja azonosítja ezeket, és V1 esetében egyszer, V2 esetében kétszer   *
 * Villantja fel a LED-et. A klienseknek a kapcsolat felvétele után max. egy másodpercük van arra   *
 * hogy küldjenek valamit, Ha ez nem történik meg, akkor a szerver kilép abból a ciklusból, ami     *
 * a küldött üzenetekre vár. Ha van más kliens is, ami szeretne parancsot küldeni, akkor ezen       *
 * a ponton sorra kerülhet és küldheti a parancsát.                                                 *
 * A kliensek END karaktersorozattal jelzik, ha már nem akarnak több üzenetet küldeni.              *
 ****************************************************************************************************/
#include "ESP8266WiFi.h"             //ennek használatához a könyvtár kezelőben az "ESP8266 Community" alaplapkezelőt kell telepíteni
                                     //csak akkor találja meg, ha előtte az Arduino IDE file/beállítások menüben a további alaplapkezelőkhöz
                                     //beírjuk ezt: http://arduino.esp8266.com/stable/package_esp8266com_index.json 
IPAddress ip_cim(192,168,5,1);       //ha nem alapértelmezett IP címet akarunk, akkor itt adhatjuk meg (alapértelmezett:192.168.4.1í9
IPAddress ip_atjaro(192,168,5,1);    //nincs internet kapcsolat, így itt simán megismételjük az ip címet
IPAddress ip_maszk(255,255,255,0);   //Hálózati maszk
unsigned int  tcp_port = 1001;       //ezt a portot fogja figyelni a szerver
byte adat1=0;                        //Ebbe olvassuk be az aktuálisan kiolvasott byte-ot a kliens által küldött üzenetek pufferéből 
byte adat2=0;                        //ebben tároljuk az előző kiolvasott byte-ot
byte adat3=0;                        //ebben tároljuk az előző előtti byte-ot (max. három karakteres parancsokat használunk)
long timeout;                        //ha a kliens egy másodpercig nem küld semmit, akkor nem figyeljük tovább. Ezt időzíti.

WiFiServer tcp_szerver(tcp_port);    //létrehozzuk a szervert, ami a megadott portot fogja figyelni
long varakozasi_ido=millis();        //ha a kliens egy másodpercig nem küld semmit, akkor nem figyeljük tovább. Ezt időzíti.
bool ismetles_tilt=0;                //ha nem küld senki üzenetet, akkor kiírjuk, hogy várakozunk, ezt a változót használjuk arra
                                     //hogy csak egyszer írjuk ki.

void setup() {
  pinMode(D4, OUTPUT);    //led kimenet beállítása
  digitalWrite(D4, HIGH); //led nem világít (fordítva működik, LOW-ra világít)
  Serial.begin(115200);
  Serial.println();
  Serial.print("Acces Point beállítása..");
  WiFi.softAPConfig(ip_cim,ip_atjaro,ip_maszk);    //Létrehozni kívánt wifi Acces Point az itt paraméterekben megadott adatokkal jön majd létre. 
                                                   //Nem kell minden paramétert megadni, még az IP cimet sem, ekkor az alapértelmezett
                                                   //192.168.4.1 lesz az állomás IP címe.
  WiFi.softAP("ESP8266_AP", "12345678");           //Az Acces Point elindítása, a megadott állomásnévhez a jelszóval lehet csatlakozni 
  Serial.print("IP cím:");  Serial.println(WiFi.softAPIP());  //megismételjük az AP állomás IP címét, ezt kell a kliensekben beállítani
                                                              //a portszámot a szerver létrehozásakor kellett beállítani a program elején.
  delay(1000);
  tcp_szerver.begin();     //elindítjuk a szervert
}

void loop() {
  WiFiClient kliens=tcp_szerver.available();   //lekérdezzük, hogy van-e várakozó kliens, aki adatot küldött
  if (kliens) {
    ismetles_tilt=0;
    Serial.print("Kliens adatot küld (");
    Serial.print(kliens.remoteIP());Serial.print(",");      //csat
    Serial.print(kliens.remotePort());Serial.println(")");
    delay(100);
    while (true) {                       //Ha vége jelzést küld a kliens, akkor break-al fogjuk befejezni
      if (kliens.available()>0) {        //van adat a pufferben, lehet olvasni
        adat1 = kliens.read();           //kiolvasunk egy byte-ot a pufferben található adatokból
        Serial.write(adat1);
        timeout=millis();                //megjegyezzük az utolsó kiolvasás időpontját, ha egy másodpercig nem jön semmi, akkor lesz timeout
        if (adat1=='D' and adat2=='N' and adat3=='E') {   //ellenőrizzük, hogy megjött-e a vége jelzés (END) és ha igen akkor kiugrunk a ciklusból
          Serial.println();
          Serial.println("Kliens adas vege");
          break;
        }

        //Ez a néhány sor kitalált funkció a TCP kapcsolat felhasználására. A kliensek nyomógombjaival fel tudjuk távolról villantani
        //a beépített led-et. Ha megnyomjuk a FLASH nyomógombot a kliensen, akkor az "V1","V2" stb. karaktersorozatot küld, amit beazonosítunk
        //és felvillantjuk a LED-et.
        if (adat2=='V' and adat1=='1') {   //Ha "V1" karaktersorozat érkezik, akkor egyszer felvillantjuk a LED-et. Ezt a kliens 1 programja küldözgeti
          digitalWrite(D4,LOW);
          delay(100);
          digitalWrite(D4,HIGH);
          kliens.println("LED 1x villant!");   //Küldünk egy választ a kliensnek (visszajelezzük a végrehajtást). Ez az üzenet mindig 
                                               //annak a kliens-nek megy, akivel éppen foglalkozik a szerver.
                                               //A kliens megkapja ezt az üzenetet, de nem kell kiolvasnia a pufferből, a szerver
                                               //programját nem érinti, hogy kezd-e vele valamit a kliens. 
        }
        if (adat2=='V' and adat1=='2') {   //Ha "V2" karaktersorozat érkezik, akkor kétszer felvillantjuk a LED-et. Ezt a kliens 2 programja küldözgeti
          digitalWrite(D4,LOW);
          delay(100);
          digitalWrite(D4,HIGH);
          delay(100);
          digitalWrite(D4,LOW);
          delay(100);
          digitalWrite(D4,HIGH);
          kliens.println("LED 2x villant!"); 
        }
        
        //Az utolsó három karakter kell a szöveges parancsok azonosításához ezért minden karakter után elmásoljuk az előzőt.
        //A kliens V1,V2-val jelzi a LED villantási szándékát, és END-el jelzi, ha már nincs semmi közlendője.
        adat3=adat2;                    
        adat2=adat1;
      }
      if (timeout+1000<millis()) {                //ha egy másodperce nem jött semmi a klienstől, akkor nem várunk tovább, kiugrunk a cilusból
        Serial.println();                         //Ilyen helyzet akkor fordulhat elő, ha egy kliens adatok küldése közben elhal, pl. elvesszük
        Serial.println("Kliens timeout!");        //tőla a tápfeszt. Ha nem figyelnénk, akkor itt a program végtelen ciklusba esik, soha nem jön
        break;                                    //END jelzés, és a szerverünk látszólag lafagyott (nem fogad máskliens-től adatot)
      }
    }
  }
  else {                                              //Ha nincs egyetlen kliens sem, aki adatot küldött, akkor egyszer kiírjuk a várakozás szöveget
    if (ismetles_tilt==0) {
      Serial.println();
      Serial.println("Várakozás kliensre...!");
      Serial.println();
    }
    ismetles_tilt=1;
  }
}

Kliens program forráskód:

/***********************************************************************************************
 * Ez a demó program csatlakozik egy ESP8266-al létrehozott Acces Point-hoz, ahol a szerver    *
 * az 1001-es porton figyel. Amikor a program érzékeli, hogy lenyomtuk a FLASH nyomógombot,    *
 * csatlakozik a szerverhez, és küld neki egy "V1" (vagy "V2") karakteres parancsot. Ha két    *
 * kliensünk is van, akkor célszerű az egyik programjába V1-et, a másikba V2-őt írni.          *
 * A szerver ezt a karakter sorozatot azonosítja, felvillantja a beépített LED-et 1x illetve   *
 * 2x és szöveges válasszal nyugtázza a parancs végrehajtását. Ezt követően ez a program egy   *
 * END karaktersorozattal utasítja a szervert, hogy ne várakozzon több parancsra, foglalkozzon * 
 * az esetleges további kliensekkel                                                            *
 ***********************************************************************************************/

#include <ESP8266WiFi.h>          //enek használatához a könyvtár kezelőben az "ESP8266 Community" alaplapkezelőt kell telepíteni
                                  //csak akkor találja meg, ha előtte az Arduino IDE file/beállítások menüben a további alaplapkezelőkhöz
                                  //beírjuk ezt: http://arduino.esp8266.com/stable/package_esp8266com_index.json 
IPAddress     tcp_szerver_ip(192,168,5,1);   //A szerver ezen az IP címen működő Acces Point-on figyel
unsigned int  tcp_szerver_port=1001;         //ez a szerver által figyelt port
WiFiClient    tcp_kliens;                    //Ez az objektum fogja tárolni a csatlakozáskor kapott adatokat
String valasz;                               //Ebbe a változóba olvassuk be a szerver nyugt ázó üzenetét

void setup(){
  Serial.begin(115200);
  pinMode(D3,INPUT);       //Az alaplapra épített FLASH nyomógomb erre a bemenetre van kötve.
  digitalWrite(D3,HIGH);   //felhúzó ellenállás be
  Serial.print("Wifi kapcsolódás indul a ESP8266_AP nevű állomáshoz...");
  WiFi.begin("ESP8266_AP","12345678");      //kapcsolódás a megadott wifi állomáshoz STA módban
  while (WiFi.status() != WL_CONNECTED) {   //Várakozunk a Wifi hálózathoz történő csatlakozásra
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi kapcsolat OK!");
  Serial.print("TCP Kliens saját IP cím:");  Serial.println(WiFi.localIP());
  delay(1000);
}

void loop(){
  if(digitalRead(D3)==0) {               //Megnyomtuk a nyomógombot
    if(tcp_kliens.connect(tcp_szerver_ip,tcp_szerver_port)){ //A connect hatására a kliens és a szerver felkészül az adatátvitelre. 
      tcp_kliens.setNoDelay(1);                              //utasítja a tcp kapcsolatot, hogy ne várakozzon az üzenet küldéssel, 
                                                             //azonnal küldje (egyébként összevárja az adatokat, míg megtelik a puffer) 
                                                             //Nem így tapasztaltam, ha kiveszem ezt a sort, akkor is azonnal küld.                                
      tcp_kliens.print("V1");                  //üzenet küldése a szervernek (ezt kell egy másik kliensen V2-re cserélni)
      delay(500);                              //Ezzel szimulálom, hogy itt a program egy ideig még várakoztatja a szervert.
                                               //Ha ezt több mint egy másodpercre állítom, a szerver timeout-al abbahagyja a kliens
                                               //figyelését (a szerver programjába egy másodpercig várakozok további üzenetekre)                               
      valasz=tcp_kliens.readStringUntil('\r'); //várunk a szerver válaszára.
      Serial.println("Válasz:"+valasz);        //Megjelenítjük a szerver üzenetét a sorosporton
      tcp_kliens.println("END");               //üzenet a szervernek. Ha ezt megkapja, kilép a kliens figyelését végző ciklusból
      tcp_kliens.stop();                       //lezárja a kapcsolatot a szerverrel. Azt tapasztaltam, hogy ha ezt a sort kihagyom
                                               //a program változatlanul fut tovább, még akkor is, ha a szerver épp egy másik klienssel
                                               //foglalkozik. Vélhetőleg egy pufferbe gyűjti a közben érkező üzeneteket. Ilyenkor a másik
                                               //kliens üzeneteinek megérkezése után azonnal megjelenik a szerver programjában az összes
                                               //addig érkezett adat. Lásd szerver programjának szerkezetét.
                                               //Ha ez a sor benn marad a programban, akkor újra "connect" szükséges az üzenet küldés előtt
                                               //ami viszont addig várakozik, amíg a szerver fel nem szabadul. A jelenlegi program 
                                               //megoldásban elveszíthetünk egy-egy gombnyomást, mert a szerver épp mással van elfoglalva
                                               //és elengedjük a nyomógombot mire lehetséges a connect! 
    }
  }
}

Úgy képzeltem, hogy ez a program páros jó alap ahhoz, hogy adatokat küldjek két kütyü között, bármi is legyen a funkciójuk. Igaz ugyan, hogy a kliensek kezdeményezik a kommunikációt, de nem lehetetlen a programot úgy átalakítani, ahogyan a megelőző példában lett megvalósítva. A kliensek ott folyamatosan kapcsolatban maradnak a szerverrel, és szerver bármelyikre tetszőleges időpontban küldhet üzenetet! Sok sikert a játszadozáshoz!

3. példa:  Internetes időszerver (NTP) használata

Ha már kezünkben egy wifi képes alaplap, és van a lakásban egy internet kapcsolattal rendelkező wifi router, akkor adja magát a lehetőség, hogy a NodeMCU felcsatlakozzon a net-re és kiolvassa az atom pontos időt. Az sem lehetetlen megoldás, hogy szoftveresen szimuláljunk egy RTC órát ami megszakításokkal folyamatosan szolgáltatja a pontos időt, ha kap bekapcsoláskor kezdőértéket, és persze időnként pontosítjuk az idejét. Egy szoftveresen megvalósított óra jelentős, akár napi több másodperces eltérést is összeszedhet, ha a megszakításokat is intenzíven használja az alaplapon futó program. Ezért az idp pontosítás fontos! Örömmel közölhetem, hogy az interneten fellelhető mintapéldák alapján ez roppant egyszerű.

Még akkor is jól használható, ha szerver funkcióban működő NodeMCU Acces Point! Néha le kell állítani ezt a funkciót, csatlakozni a wifi routerhez, pontosítani az időt és vissza az Acces Point üzemmódba. De ez csak egy elképzelés, ha szükségem lenne rá, így csinálnám!

Rengeteg mintapéldát találtam. Végült azt választottam, amihez semmilyen egyéb programkönyvtárra nem volt szükség. Nekem nem volt nyilvánvaló, de az alapból használt ESP8266Wifi.h szinte minden szükséges komponenst tartalmaz. Saját életünk könnyítésre még „include”-olni kell C++-ban alapból megtalálható time.h gyűjteményt. Ez sokat fog segíteni, hogy a programnak elig legyen programsora.

A kipróbált és kommentezett példaprogramom setup() részében csatlakozunk egy wifi router-hez, kiolvassuk a pontos időt egy NTP szerverről, és máris használhatjuk a kész óránkat. A Wifi routerről azonnal le is csatlakozhatunk.
A működés alapja, hogy egy time_t típusú változót hozunk létre, ami alapból 0. Csak annyiban speciális ez a változó, hogy a rendszer másodpercenként növelni fogja az értékét. Létrehozunk még egy „tm” struktúrával definiált változót is (a time_T és tm típusokat a time.h hozza létre). Ennek a struktúrának az lesz a szerepe, hogy tetszőleges ponton a programban a localtime() függvény átkonvertálja ebbe a rendszeridőt, így nem magunkban kell kiszámolgatni, hogy éppen hányadika van, milyen nap, és mennyi az idő. Nekem kicsit szokatlan volt, de végül is érthető, hogy a rendszeridő változónknak a kezdőcímét kapja meg a localtime(). A tm struktúrájú változó, amit és datum_ido-nek neveztem el, nem közvetlenül az adatokat mutatja meg, hanem a változó memóriaterületének kezdőcímét. Így aztán a struktúra egyes tagjaira nem „.”-al elválasztva hivatkozhatunk (ahogy a struktúra típusú változóknál megszokhattuk), hanem „->” elválasztó jellel (vagy akárhogyis hívják a C++-ban ezt).  

Talán még annyi magyarázat tartozik a dologhoz, hogy az NTP szerverről kiolvasott időpont az 1970.01.01 00:00 időpont óta eltelt másodperceket adja vissza. Nem néztem utána pontosabban, de valamilyen módon ez nem csak egész érték lehet, hanem a tizedes értékben rendkívül pontos időpontot kaphatunk akár milliomod másodperces pontossággal. De ehhez már átlagokat is kell számolni és több időszerverről kérdezni adatot stb. Arról nem sokat tudok, hogy a jelen programban lekért eljárás ebből mit végez el, és így mennyire pontos ez az idő! Feltételeztem, hogy másodpercre, esetleg tized másodpercre biztosan ez. A laptopom internethez szinkronizált órája minden esetre szemmel tökéletesen azonos pillanatban vált, mint az így kiolvasott idő!

És íme a forráskód:

//időpontosítás NTP szerverről
#include <ESP8266WiFi.h>
#include "time.h"

time_t rendszerido;
struct tm * datum_ido;  //tm struktúra példány létrehozása, a datum_ido a struktúra kezdőcímét tárolja (tm struktúrát a time.h definiálja)
                        //tm struktúra mező definíciói:
                        //   int8_t   tm_sec   másodprc 
                        //   int8_t   tm_min   perc 
                        //   int8_t   tm_hour  óra 
                        //   int8_t   tm_mday  nap  1 - 31  
                        //   int8_t   tm_wday  hét napja  0-6-0=hétfő 
                        //   int8_t   tm_mon   hónap 0-11 
                        //   int16_t  tm_year  év 1900- pl. 2022 esetén 2022-1900=122
                        //   int16_t  tm_yday  év eltelt napjai jan 1 óta 0-365 0=január 1. 
                        //   int16_t  tm_isdst Nyári időszámitás jelző flag (0 nincs nyári időszámítás, 1 van nyári időszámítás)

void setup()
{
  Serial.begin(115200);
  //csatlakozás egy wifi routerhez 
  Serial.print("Wifi kapcsolódás indul a 74ls734 nevű állomáshoz...");
  WiFi.begin("74ls734","@natloz.igab#kis_andrea");  //kapcsolódás a megadott wifi állomáshoz STA módban
                                                    // első paraméter az állomásnév, második a jelszó
  while (WiFi.status() != WL_CONNECTED) {           //Várakozunk a Wifi hálózathoz történő csatlakozásra
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi kapcsolat OK!");
  Serial.print("Saját IP cím:");  Serial.println(WiFi.localIP());
  
  //NTC lekérdezésea pontos időért
  Serial.print("Induló rendszeridő állapota pontosítás előtt: ");Serial.println(rendszerido);
  datum_ido=localtime(&rendszerido);   //A nyers rendszeridőt a tm típusú struktúrába képezzük le, hogy részenként is használhassuk
  Serial.println(asctime(datum_ido));  //dátum és idő formázott kiírása time.h beépített függvényével
  configTime(7200,0,"pool.ntp.org");   //a rendszeridő változóba beírja az NTC-től kapott időt (1970 óta eltelt másodpercek számát
  delay(2000);                         //Kell egy kicsi idő, amíg az időt a rendszeridőben megkapjuk. Ha ezt nem tesszük be
                                       //úgy zárjuk le a wifi kapcsolatot, hogy nem lesz beállítva a rendszeridő. 1 másodperc néha
                                       //kevés volt!
  //lecsatlakozás a wifi routerről (nem szükséges)
  WiFi.disconnect(true);               //kapcsolat lebontása
  WiFi.mode(WIFI_OFF);                 //wifi egység kikapcsolása az alaplapon
}

void loop()
{
  time(&rendszerido); //A rendszerido változóba írjuk a belső rtc óra idejét
  datum_ido=localtime(&rendszerido);    //A nyers rendszeridőt a tm típusú struktúrába képezzük le, hogy részenként is használhassuk
  Serial.print("1970.01.01 00:00 óta eltelt másodpercek: ");Serial.println(rendszerido);
  Serial.print(asctime(datum_ido));    //dátum és idő formázott kiírása
  //a következő kiírás csak azért, hogy látható legyen, hogyan lehet részenkén megkapni az időt
  Serial.print("Dátum: ");+Serial.print(datum_ido->tm_year+1900);Serial.print(".");Serial.print(datum_ido->tm_mon+1);
  Serial.print(".");Serial.println(datum_ido->tm_mday);
  Serial.print("Hét napja: ");Serial.println(datum_ido->tm_wday);
  Serial.print("Idő: ");Serial.print(datum_ido->tm_hour);Serial.print(":");Serial.print(datum_ido->tm_min);
  Serial.print(":");Serial.println(datum_ido->tm_sec);
  Serial.print("Eltelt napok száma január 1 óta: ");Serial.println(datum_ido->tm_yday);
  Serial.print("Nyári időszámítás: ");Serial.println(datum_ido->tm_isdst);
  Serial.println();
  delay(10000);
}

Mennyire volt hasznos amit olvastál? Értékelés után szövegesen is leírhatod megjegyzéseidet és véleményedet!

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet! Ha kérdésed van, ne felejtsd el megadni az email címedet!