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.
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:
- 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.
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
Noch keine Kommentare.