Zurück zur Startseite

NTP-Server auf ESP8266: Genaue Zeit ohne Internet | Anleitung

Der Artikel beschreibt die Implementierung eines autonomen NTP-Servers auf Basis von ESP8266 mit Mikrosekunden-Genauigkeit. Netzwerkverzögerungskompensationsalgorithmen und die Aufrechterhaltung der Zeitgenauigkeit zwischen Synchronisationen werden detailliert analysiert. Die Lösung ist relevant unter Bedingungen der Blockierung externer NTP-Server.

Autonomer NTP-Server: Wie ESP8266 Zeitblockade-Probleme löst
Advertisement 728x90

Autonomer NTP-Server auf dem ESP8266: Genaue Zeit ohne externe Abhängigkeiten

Wenn Internetverbindungen unzuverlässig sind und externe NTP-Server blockiert werden, wird eine lokale Quelle für präzise Zeit unerlässlich. Wir führen Sie Schritt für Schritt durch den Bau eines autonomen NTP-Servers auf dem ESP8266 mit Mikrosekunden-Genauigkeit, der auch ohne ständigen Internetzugang weiterläuft.

Warum Standardlösungen nicht ausreichen

Moderne Infrastrukturen können aufgrund von Netzwerkbeschränkungen und Whitelist-Richtlinien oft nicht auf globale NTP-Server zugreifen. Wenn externe Zeitenquellen abgeschnitten sind, geraten die Systemuhren aus dem Takt, was ein großes Problem für:

  • Finanztransaktionen
  • Sicherheitsprotokollierung
  • Verteilte Rechensysteme
  • IoT-Geräte mit strengen Timing-Anforderungen

Die Lösung? Bauen Sie einen lokalen NTP-Server, der die genaue Zeit eigenständig zwischen den Synchronisationen hält. Der ESP8266 ist ideal: geringer Stromverbrauch, integriertes WiFi und ausreichend Rechenleistung für Zeitstempel.

Google AdInline article slot

Architektur des Zeitsystems

Die Hauptchallenge ist, die Grenzen der Standard-Zeitbibliothek von Arduino zu überwinden, die nur ganze Sekunden trackt. NTP (RFC 5905) erfordert Mikrosekunden-Präzision. Hier kommt unsere eigene JbTime-Klasse ins Spiel, die zwei Kernprobleme behebt:

  • Keine Unterstützung für Sekundenbruchteile in der Systemzeit
  • Ansammlung von Drift zwischen Synchronisationen

So funktioniert es:

  • Bei Ankunft der Zeit von einem externen Server erfasst es den Mikrosekunden-Zeitstempel des Prozessors
  • Jede Anfrage berechnet die Differenz vom Sync-Moment bis jetzt
  • Korrekturfaktoren berücksichtigen Netzwerkverzögerung mit dieser Formel: offset = ((t2 - t1) + (t3 - t4)) / 2
#ifndef jb_time
#define jb_time

#include <stdint.h>

#define JB_TIME_MAX_AGE  600

class JbTime {
private:
  uint64_t _last;
  uint64_t _sec;
  uint32_t _usec;
  uint32_t _mark;

public:
  bool ok;
  bool fresh;

  JbTime(){
    _sec = 0;
    _usec = 0;
    _mark = 0;
    _last = 0;
    ok = false;
    fresh = false;
  }

  inline bool old() {
    if(_sec == 0) return true;
    if((_sec - _last) > JB_TIME_MAX_AGE) return true;
    return false;
  }

  inline void settime(uint64_t sec, uint32_t usec, uint32_t mark = micros()){
    _mark = mark;
    _sec = sec;
    _sec += usec / 1000000;
    _usec = usec % 1000000;
    ok = true;
    _last = sec;
  }

  inline void gettime(uint64_t *o_sec, uint32_t *o_usec){
    if(ok){
      uint32_t now = micros();
      uint32_t delta = now - _mark;
      _mark = now;

      uint64_t total = (uint64_t)_usec + delta;
      _sec += total / 1000000;
      _usec = total % 1000000;

      *o_sec = _sec;
      *o_usec = _usec;
    } else {
      *o_sec = 0;
      *o_usec = 0;
    }
  }
};
#endif

Behandlung von NTP-Anfragen

Das NTP-Protokoll läuft auf UDP-Port 123 und sendet die Zeit als 64-Bit-Wert (32 Bit Sekunden, 32 Bit Brüche). Der kniffligste Teil? Die Kompensation der Netzwerkverzögerung. Hier der Algorithmus:

Google AdInline article slot
  • Client sendet Anfrage mit T1-Zeitstempel (lokale Zeit)
  • Server empfängt bei T2, sendet Antwort bei T3
  • Client erhält Antwort bei T4
  • Offset: offset = ((T2 - T1) + (T3 - T4)) / 2

Die JbNTPClient-Klasse meistert das unter Berücksichtigung der ESP8266-Eigenarten:

  • Pakete formatiert nach RFC 5905
  • Automatische Epochen-Anpassung (NTP startet 1900, Unix 1970)
  • Filtert schlechte Antworten aus
bool JbNTPClient::requestTime(const char* server, JbTime * mytime) {
  // ... initialization
  
  // Calculation T1
  uint64_t t1_sec = 0;
  uint32_t t1_usec = 0;
  systime->gettime(&t1_sec, &t1_usec);
  t1_sec += NTP_UNIX_EPOCH_DIFF;
  
  // Sending request
  udp.beginPacket(timeServerIP, ntpPort);
  udp.write((uint8_t*)&packet, sizeof(NTPPacket));
  udp.endPacket();

  // Ozhidanie otveta with taymautom 2000 ms
  while (udp.parsePacket() == 0) {
    if (millis() > timeout) return false;
    delay(10);
  }

  // Raschet T4 and setevoy zaderzhki
  double t4 = ntpToDouble(t4_sec, t4_frac);
  _networkDelay = (t4 - t1) - (t3 - t2);
  double offset = ((t2 - t1) + (t3 - t4)) / 2;

  // Installation skorrektirovannogo vremeni
  mytime->settime(sec, usec, t4_mark);
  return true;
}

Server-Seite: Zeit im lokalen Netzwerk bereitstellen

Sobald mit externen Servern synchronisiert, wird das Gerät zu einer vollwertigen NTP-Quelle. Es bearbeitet Client-Anfragen über die serve()-Methode:

  • Eingehendes UDP-Paket parsen
  • Modus prüfen (erwartet mode=3 – Client-Anfrage)
  • Antwort mit aktueller Zeit aus JbTime aufbauen
  • Verarbeitungszeit für maximale Genauigkeit einbeziehen

Schlüsselmerkmal: Zeitstempel wird direkt beim Versenden der Antwort (T3) erfasst, um Fehler zu minimieren. Im Gegensatz zu Standardlösungen holt es die Zeit direkt aus einem hochauflösenden Zähler und umgeht grobe Systemfunktionen.

Google AdInline article slot

Wichtige Merkmale

  • Autonomie: Hält 20–30 Mikrosekunden Präzision zwischen Syncs, auch stundenlang offline
  • Ressourceneffizienz: ESP8266 verbraucht unter 70 mA im aktiven Betrieb, batteriefreundlich
  • Kompatibilität: Konform zu RFC 5905, funktioniert mit jedem NTP-Client sofort
  • Flexibilität: Maximale Datenalter (JB_TIME_MAX_AGE) nach Bedarf anpassen
  • Fehlertoleranz: Automatischer Failover zu Backups, wenn Primärserver ausfällt

— Editorial Team

Advertisement 728x90

Weiterlesen