Bibliotheken om het leven makkelijker te maken (part 1)

[ 225 keer bekeken / views ]

Iedereen die weleens een programma schrijft, maakt gebruik van bibliotheken en klassen (classes) om zijn software te ontwikkelen. Als je (in C of C++) een programma schrijft voor een Arduino- of Espressif-bordje, voeg je dergelijke bibliotheken toe met het #include <..>-statement. Deze bibliotheken fungeren vaak als black boxes waarvan je de interne werking niet hoeft te kennen of te begrijpen.

Ik werk veel met Espressif-bordjes (ESP8266 en ESP32), omdat deze veel mogelijkheden bieden. Je kunt er eenvoudig een WiFi-verbinding mee maken (met de WiFi- of WiFiManager-bibliotheek). Met behulp van een ingebouwde WebServer (ook een standaardbibliotheek) kan ik het ontwikkelde programma ‘besturen’ en configureren. Daar heb je dan wel de nodige HTML, stylesheets en JavaScript bij nodig. Daarnaast is het bijna altijd handig om de juiste tijd via het Network Time Protocol (NTP) op te halen. Ook vind ik het prettig om mijn projecten te debuggen via het telnet-protocol, zodat ik met een tekstgebaseerde terminal verbinding met zo’n project kan maken. Hiervoor is de bibliotheek TelnetStream mijn favoriete keuze. De Espressif-bordjes bieden bovendien de mogelijkheid om een deel van het geheugen als opslag te gebruiken (als een soort harde-schijf). Wil je hiervan gebruik maken, dan is een systeem voor het beheren van deze opslag erg prettig. Hiervoor heb ik eerder al code ontwikkeld (géén bibliotheek omdat ik toen nog niet wist hoe dat moet) onder de naam FSmanager en FSexplorer.

Al met al gebruik ik een behoorlijk aantal bibliotheken, die samen toch nog aardig wat code vereisen om deze bibliotheken optimaal in te zetten.

Daarom ben ik begonnen met het ontwikkelen van drie bibliotheken die al het bovenstaande met eenvoudige stukjes code regelen. In drie posts zal ik deze bibliotheken en hun gebruik beschrijven:

Dit is deel 1 en gaat dus over Networking.

Networking

Deze bibliotheek maakt het eenvoudig om je project met je WiFi modem (Access Point) te verbinden. Ben je eenmaal verbonden met WiFi dan maakt deze bibliotheek het mogelijk om nieuwe versies van je programma’s Over The Air (OTA) te flashen. Geen kabels meer nodig! Ook zal deze bibliotheek ervoor zorgen dat je je project niet alleen via het IP-adres kunt benaderen maar óók via een opgegeven hostname (bijvoorbeeld “project.local”). Debug uitvoer kun je zowel via de seriële poort bekijken maar ook via telnet. Tenslotte heb je, zonder er iets extra’s voor te hoeven doen, ook de actuele (atoom) tijd tot je beschikking.

Installatie PlatformIO

Werk je met VSCode/PlatformIO dan hoef je alleen de link naar de repo in je platformio.ini bestand op te nemen:

lib_deps = https://github.com/mrWheel/esp-networking

Installatie Arduino IDE

Voor de Arduino IDE moet je de repo als “.zip” bestand downloaden.

Het esp-networking-main.zip bestand moet je ergens op je computer bewaren.

Start nu de Arduino IDE op en klik op [Sketch] en selecteer [Add .ZIP library…]

Nu navigeer je naar de folder waar je het esp-networking-main.zip bestand eerder hebt opgeslagen ..

… en klik je op [Choose].
Installeer nu ook de volgende bibliotheken (als dat nog niet gebeurd is):

  • WiFiManager
  • ESP8266WiFi (voor esp8266) of WiFi (voor esp32)
  • ESP8266mDNS (voor esp8266) of ESPmDNS (voor esp32)
  • ArduinoOTA

De bibliotheek is nu klaar om gebruikt te worden.

Eenvoudig gebruik (basic)

#include "Networking.h"

Networking* network = nullptr;
Stream* debug = nullptr;

void setup()
{
    network = new Networking();
    
    //-- Parameters: hostname, resetWiFi pin, serial object, baud rate
    debug = network->begin("my-esp", 0, Serial, 115200);
    
    if (!debug)
    {
      //-- Restart if connection fails
      ESP.restart(); 
    }
}

void loop()
{
    //-- Must be called in main loop
    network->loop();
    
    //-- Use debug stream for output
    debug->println("Hello World");
}

Uitleg

In regel 1 vertellen we de compiler dat we de “Networking” bibliotheek gaan gebruiken.
Regel 3 zorgt dat we een pointer creëren naar het Networking object.
Regel 4 maakt een pointer “debug” aan van het type Stream.
In regel 8 maken we een nieuw object “netwerk” aan van de klasse Networking.
Regel 10 is waar het allemaal om draait. We roepen de methode “begin()” aan met een aantal parameters.

  • De eerste is de hostnaam, in dit geval “esp8266”
  • De tweede is het nummer van een GPIO pin (in dit geval “0”). Als tijdens het opstarten deze GPIO pin “Laag” wordt gemaakt worden de WiFi credentials verwijderd en start het WiFi Manager portal op.
  • De derde parameter geeft aan op welk seriële poort debug uitvoer wordt geschreven (naast de uitvoer naar de telnet client).
  • De vierde parameter geeft de snelheid aan waarmee naar de seriële poort moet worden geschreven.
  • Er is ook nog een vijfde parameter maar die heeft standaard de waarde null.

In de loop() functie moet de network->loop() functie worden aangeroepen om alles naar behoren te laten werken.

WiFi Configuratie

Eerste Opstart:

  • Het apparaat maakt een Access Point aan met de naam van de hostnaam.
  • Maak verbinding met dit AP via je telefoon/computer.
  • Configureer je WiFi-instellingen via het captive portal.

WiFi Instellingen Reset:

  • Houd de resetWiFiPin laag tijdens de opstart.
  • WiFi-instellingen worden gewist.
  • Het apparaat gaat terug naar de AP-modus voor herconfiguratie.

Remote Debugging

Verbinden via Telnet:

telnet apparaat-ip-adres

Standaard poort: 23

Verbinden via netcat (nc):

nc apparaat-ip-adres 23

Of gebruik in plaats van apparaat-ip-adres de mDNS-naam hostnaam.local.

Kenmerken:

  • Alle debug-uitvoer wordt zowel naar de seriële poort als naar Telnet geprint.
  • Slechts één telnet-sessies wordt ondersteund (gelijktijdig).
  • Automatisch sessiebeheer.

Uitgebreid Gebruik van OTA en WiFiManager

De bibliotheek biedt callbackfuncties voor OTA (Over-The-Air) update-evenementen en WiFiManager portal-activatie, waarmee je aangepaste code kunt uitvoeren op specifieke momenten:

void setup()
{
    network = new Networking();
    debug = network->begin("my-esp", 0, Serial, 115200);
    
    //-- Registreer OTA callbacks
    network->doAtStartOTA([]() 
    {
        //-- Aangeroepen wanneer de OTA-update begint
        digitalWrite(LED_BUILTIN, LOW);  //-- Zet LED aan
        debug->println("OTA-update begint...");
    });
    
    network->doAtProgressOTA([]() 
    {
        //-- Aangeroepen bij elke 20% voortgang
        digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));  //-- Wissel LED
        debug->println("OTA: Nog eens 20% voltooid");
    });
    
    network->doAtEndOTA([]() 
    {
        //-- Aangeroepen wanneer de OTA-update bijna eindigt
        digitalWrite(LED_BUILTIN, HIGH);  //-- Zet LED uit
        debug->println("OTA-update eindigt...");
    });

    //-- Registreer WiFiManager callback
    network->doAtWiFiPortalStart([]() 
    {
        //-- Aangeroepen wanneer het WiFiManager portal is gestart (geen geldig SSID gevonden)
        digitalWrite(LED_BUILTIN, LOW);  //-- Zet LED aan
        debug->println("WiFi-configuratie portal gestart");
    });
}

Met deze callbacks kun je:

  • Het apparaat voorbereiden wanneer een update start (bijvoorbeeld kritieke gegevens opslaan, lopende processen stoppen).
  • De voortgang monitoren bij 20%-intervallen (bijvoorbeeld een display bijwerken, indicatoren wisselen).
  • Opruimoperaties uitvoeren voordat de update wordt afgerond.
  • Visuele feedback geven tijdens updates met LED’s of displays.
  • Update-voortgang loggen naar externe systemen of opslag.

Netwerkstatus

if (network->isConnected())
{
    debug->print("IP Adres: ");
    debug->println(network->getIPAddressString());
}

NTP Tijdbeheer

De bibliotheek biedt uitgebreide NTP (Network Time Protocol) functionaliteit met automatische synchronisatie:

void setup()
{
    network = new Networking();
    debug = network->begin("my-esp", 0, Serial, 115200);
    
    //-- Start NTP met Amsterdam tijdzone
    if (network->ntpStart("CET-1CEST,M3.5.0,M10.5.0/3"))
    {
        debug->println("NTP succesvol gestart");
    }

    //-- Optioneel: Gebruik aangepaste NTP-servers
    const char* ntpServers[] = {"time.google.com", "time.cloudflare.com", nullptr};
    network->ntpStart("CET-1CEST,M3.5.0,M10.5.0/3", ntpServers);
}

void loop()
{
    network->loop();  //-- Verzorgt automatische NTP-synchronisatie elk uur

    if (network->ntpIsValid())
    {
        debug->print("Huidige tijd: ");
        debug->println(network->ntpGetDateTime());
    }
}

Kenmerken:

  • Automatische synchronisatie elk uur.
  • Ondersteuning voor tijdzones met POSIX tijdzone strings.
  • Ondersteuning voor aangepaste NTP-servers.
  • Tijdzone-wijzigingen zonder de standaardinstellingen te beïnvloeden.
  • Diverse tijdformaten (epoch, datum, tijd, datetime).

Tijdzones

Tijdzones worden gespecificeerd met POSIX tijdzone strings. Veelgebruikte voorbeelden:

  • Centrale Europese Tijd: “CET-1CEST,M3.5.0,M10.5.0/3”
  • Oostelijke Tijd: “EST5EDT,M3.2.0,M11.1.0”
  • UTC: “UTC0”
  • Japan: “JST-9”

Voorbeelden

Alle in deze post gepresenteerde code kun je vinden in de examples folder van de github repository.


ESP Networking Library API Documentation

Wat is een API?

Een API (Application Programming Interface) is een set van duidelijk gedefinieerde methoden, functies en protocollen die ontwikkelaars kunnen gebruiken om met een bepaalde software of hardware te communiceren. Een API fungeert als een brug tussen verschillende softwarecomponenten, waardoor ze met elkaar kunnen communiceren zonder dat de ontwikkelaar de interne werking van elke component hoeft te begrijpen.

In de context van deze bibliotheek biedt de API een eenvoudige manier om netwerkfunctionaliteit te implementeren in ESP8266/ESP32 projecten, zonder dat u zich zorgen hoeft te maken over de complexe details van WiFi-verbindingen, OTA-updates, telnet-servers of NTP-tijdsynchronisatie.

Networking Library Overzicht

De ESP Networking bibliotheek biedt een uitgebreide set functies voor het beheren van netwerkverbindingen op ESP8266 en ESP32 microcontrollers. De bibliotheek vereenvoudigt het proces van:

  • WiFi-verbinding en -configuratie
  • Over-the-Air (OTA) firmware updates
  • Telnet server voor externe debugging
  • mDNS service discovery
  • NTP tijdsynchronisatie
  • Automatische WiFi-herverbinding

Hoofdklassen

Networking Klasse

De Networking klasse is de primaire interface voor alle netwerkfunctionaliteit.

Initialisatie

Networking* network = new Networking();
Stream* debug = network->begin("my-esp", 0, Serial, 115200);

//-- Optioneel: callback voor WiFi portal
network->doAtWiFiPortalStart([]() {
  Serial.println("WiFi configuratie portal gestart");
});

Belangrijke Methoden

begin(hostname, resetWiFiPin, serial, serialSpeed, wifiCallback)

Initialiseert de netwerkfunctionaliteit.

  • hostname: De naam waarmee het apparaat op het netwerk zichtbaar is
  • resetWiFiPin: GPIO-pin die gebruikt wordt om WiFi-instellingen te resetten (verbind met GND om te resetten)
  • serial: HardwareSerial object voor debug output
  • serialSpeed: Baudrate voor seriële communicatie
  • wifiCallback: Optionele callback functie die wordt aangeroepen wanneer de WiFi-configuratieportal start
//-- Voorbeeld: Initialisatie met callback voor WiFi portal
Stream* debug = network->begin("my-esp", 0, Serial, 115200, []() {
  Serial.println("WiFi configuratie portal gestart!");
  //-- Hier kunt u bijvoorbeeld een LED laten knipperen
});

loop()

Moet regelmatig worden aangeroepen in de hoofdlus van uw programma om netwerkgebeurtenissen af te handelen.

void loop() 
{
  network->loop(); //-- Verplicht voor correcte werking

  //-- Uw eigen code hier
}

WiFi-gerelateerde methoden

//-- Controleer of WiFi verbonden is
if (network->isConnected()) 
{
  Serial.println("Verbonden met WiFi");
}

//-- Verkrijg het IP-adres als IPAddress object
IPAddress ip = network->getIPAddress();

//-- Verkrijg het IP-adres als string
String ipStr = network->getIPAddressString();
Serial.print("IP-adres: ");
Serial.println(ipStr); //-- Bijvoorbeeld: "192.168.1.105"

//-- Handmatig opnieuw verbinden met WiFi (indien nodig)
network->reconnectWiFi();

OTA Update Callbacks

//-- Registreer callbacks voor OTA-updates
network->doAtStartOTA([]() {
  Serial.println("OTA update gestart");
  //-- Bijvoorbeeld: schakel LED in om aan te geven dat update bezig is
});

network->doAtProgressOTA([]() {
  Serial.println("OTA update voortgang");
  //-- Wordt ongeveer elke 20% aangeroepen
});

network->doAtEndOTA([]() {
  Serial.println("OTA update voltooid");
  //-- Bijvoorbeeld: schakel LED uit
});

NTP Tijdsynchronisatie

//-- Start NTP met tijdzone voor Amsterdam
if (network->ntpStart("CET-1CEST,M3.5.0,M10.5.0/3")) 
{
  Serial.println("NTP succesvol gestart");
}

//-- Optioneel: aangepaste NTP-servers opgeven
// const char* ntpServers[] = {"time.google.com", "time.cloudflare.com", nullptr};
// network->ntpStart("CET-1CEST,M3.5.0,M10.5.0/3", ntpServers);

//-- Controleer of NTP tijd geldig is
if (network->ntpIsValid()) 
{
  Serial.println("NTP tijd is geldig");
}

//-- Verkrijg huidige datum (YYYY-MM-DD)
Serial.print("Datum: ");
Serial.println(network->ntpGetDate()); //-- Bijvoorbeeld: "2025-03-15"

//-- Verkrijg huidige tijd (HH:MM:SS)
Serial.print("Tijd: ");
Serial.println(network->ntpGetTime()); //-- Bijvoorbeeld: "14:30:25"

//-- Verkrijg huidige datum en tijd (YYYY-MM-DD HH:MM:SS)
Serial.print("Datum en tijd: ");
Serial.println(network->ntpGetDateTime()); //-- Bijvoorbeeld: "2025-03-15 14:30:25"

//-- Verkrijg epoch tijd (seconden sinds 1-1-1970)
time_t epoch = network->ntpGetEpoch();
Serial.print("Epoch: ");
Serial.println(epoch); //-- Bijvoorbeeld: "1742265025"

//-- Verkrijg tijd in andere tijdzone (bijvoorbeeld New York)
Serial.print("New York tijd: ");
Serial.println(network->ntpGetDateTime("EST5EDT,M3.2.0,M11.1.0"));

//-- Verkrijg gedetailleerde tijdinformatie als tm struct
struct tm timeInfo = network->ntpGetTmStruct();
Serial.printf("Jaar: %d\n", timeInfo.tm_year + 1900);
Serial.printf("Maand: %d\n", timeInfo.tm_mon + 1);
Serial.printf("Dag: %d\n", timeInfo.tm_mday);
Serial.printf("Uur: %d\n", timeInfo.tm_hour);
Serial.printf("Minuut: %d\n", timeInfo.tm_min);
Serial.printf("Seconde: %d\n", timeInfo.tm_sec);

MultiStream Klasse

De MultiStream klasse wordt intern gebruikt om output naar zowel de seriële poort als telnet clients te sturen. U hoeft deze klasse normaal gesproken niet direct te gebruiken, maar u kunt het Stream* object dat door begin() wordt geretourneerd gebruiken voor alle debug output.

//-- Gebruik het debug object voor alle output
debug->println("Deze tekst verschijnt zowel op de seriële monitor als via telnet");
debug->printf("Geformatteerde output: %d, %s\n", 42, "tekst");

Volledige Voorbeelden

Basis Voorbeeld

#include "Networking.h"

Networking* network = nullptr;
Stream* debug = nullptr;

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

  //-- Initialiseer networki
  network = new Networking();
  debug = network->begin("my-esp", 0, Serial, 115200);

  if (!debug) 
  {
    ESP.restart(); //-- Herstart als verbinding mislukt
  }

  debug->println("Netwerk geïnitialiseerd!");
  debug->print("IP-adres: ");
  debug->println(network->getIPAddressString());
}

void loop() 
{
  //-- Verplicht: verwerk netwerkgebeurtenissen
  network->loop();

  //-- Uw code hier
  static unsigned long lastTime = 0;
  if (millis() - lastTime > 10000) 
  {
    lastTime = millis();
    debug->println("Hallo wereld!");
  }
}

NTP Voorbeeld

#include "Networking.h"

Networking* network = nullptr;
Stream* debug = nullptr;

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

  //-- Initialiseer network
  network = new Networking();
  debug = network->begin("my-esp", 0, Serial, 115200);

  if (!debug) 
  {
    ESP.restart();
  }

  //-- Start NTP met Amsterdam tijdzone
  if (network->ntpStart("CET-1CEST,M3.5.0,M10.5.0/3")) 
  {
    debug->println("NTP gestart");
  }
}

void loop() 
{
  network->loop();

  //-- Toon tijd elke 5 seconden
  static unsigned long lastTime = 0;
  if (millis() - lastTime > 5000) 
  {
    lastTime = millis();

    if (network->ntpIsValid()) 
    {
      debug->print("Huidige tijd: ");
      debug->println(network->ntpGetDateTime());
    } 
    else 
    {
      debug->println("Wachten op geldige tijd...");
    }
  }
}

OTA Update Voorbeeld

#include "Networking.h"

Networking* network = nullptr;
Stream* debug = nullptr;
bool updateInProgress = false;

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

  //-- Initialiseer network
  network = new Networking();

  //-- Registreer OTA callbacks
  network->doAtStartOTA([]() {
    updateInProgress = true;
    digitalWrite(LED_BUILTIN, HIGH); //-- LED aan tijdens update
  });

  network->doAtEndOTA([]() {
    updateInProgress = false;
    digitalWrite(LED_BUILTIN, LOW); //-- LED uit na update
  });

  //-- Start network
  debug = network->begin("my-esp", 0, Serial, 115200);

  if (!debug) 
  {
    ESP.restart();
  }

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  debug->println("Klaar voor OTA updates!");
  debug->print("IP-adres: ");
  debug->println(network->getIPAddressString());
}

void loop() 
{
  network->loop();

  //-- Normale werking wanneer geen update bezig is
  if (!updateInProgress) 
  {
    //-- Uw code hier
    delay(1000);
    debug->println("Systeem draait normaal...");
  }
}

Geavanceerde Functies

WiFi Reset

De bibliotheek ondersteunt het resetten van WiFi-instellingen door een GPIO-pin laag te maken (verbinden met GND). Dit is handig als u uw apparaat naar een ander netwerk wilt verplaatsen.

//-- Bij initialisatie, geef de reset pin op (hier GPIO 0)
debug = network->begin("my-esp", 0, Serial, 115200);

//-- Als GPIO 0 laag is tijdens opstarten, worden WiFi-instellingen gewist
//-- en start de configuratie portal

Telnet Server

De bibliotheek start automatisch een telnet server op poort 23. U kunt verbinding maken met het apparaat via telnet om debug output te zien en te monitoren zonder fysieke verbinding.

$ telnet my-esp.local
Verbonden met my-esp.local.
Escape character is '^]'.
Welcome to [my-esp] Telnet Server!

mDNS Service Discovery

Het apparaat is vindbaar op het lokale netwerk via mDNS met de hostname die u opgeeft, gevolgd door “.local”. Bijvoorbeeld: “my-esp.local”.

Tijdzone Formaten

Voor NTP-tijdsynchronisatie gebruikt de bibliotheek POSIX tijdzone strings. Hier zijn enkele voorbeelden:

  • Amsterdam/Nederland: "CET-1CEST,M3.5.0,M10.5.0/3"
  • Londen/UK: "GMT0BST,M3.5.0/1,M10.5.0"
  • New York/US Eastern: "EST5EDT,M3.2.0,M11.1.0"
  • Los Angeles/US Pacific: "PST8PDT,M3.2.0,M11.1.0"
  • Tokyo/Japan: "JST-9"
  • Sydney/Australia: "AEST-10AEDT,M10.1.0,M4.1.0/3"

Foutafhandeling en Betrouwbaarheid

De bibliotheek bevat ingebouwde mechanismen voor het automatisch opnieuw verbinden met WiFi bij verbindingsverlies. Als het maximale aantal herverbindingspogingen (standaard 5) wordt bereikt, zal het bordje herstarten.

//-- Controleer periodiek de verbindingsstatus
if (!network->isConnected()) 
{
  debug->println("WiFi-verbinding verloren!");

  //-- De bibliotheek zal automatisch proberen opnieuw verbinding te maken
  //-- U kunt ook handmatig herverbinden:
  network->reconnectWiFi();
}

Compatibiliteit

De bibliotheek werkt met zowel ESP8266 als ESP32 platforms. Het biedt een consistente API over beide platforms, waardoor uw code gemakkelijk overdraagbaar is.

Licensie

This project is licensed under the MIT License – see the LICENSE file for details.

This entry was posted in Computer, ESP32, ESP8266, Firmware, WiFi and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

The maximum upload file size: 4 MB. You can upload: image, other. Links to YouTube, Facebook, Twitter and other services inserted in the comment text will be automatically embedded. Drop file here

This site uses Akismet to reduce spam. Learn how your comment data is processed.