TimeNTP ESP8266

Hallo Experten, :slight_smile:
habe mir im Programm Arduino IDE unter Bibliotheksverwalter die Bibliothek "Time by Michael Margolis Version 1.5.0" heruntergeladen.
Möchte nämlich mit meinem ESP8266 die Internetzeit aus einem NTP-Server auslesen.
Das Programm "TimeNTP_ESP8266" funktioniert leider nicht :frowning: : Der NTP antwortet nicht. Habs auch mit einer anderen IP-Adresse versucht. (129, 6, 15, 28 - time.nist.gov NTP server)
Die Verbindung zu meinem AP funktioniert (habe es mit einem anderen Sketch getestet).
Hat jemand von euch den passenden Tipp?
Wie wird der "local port" für UDP Pakete bestimmt?

/*
 * Time_NTP.pde
 * Example showing time sync to NTP time source
 *
 * This sketch uses the ESP8266WiFi library
 */
 
#include <TimeLib.h> 
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

const char ssid[] = "abc";  //  your network SSID (name)
const char pass[] = "123";       // your network password

// NTP Servers:
IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov
// IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov
// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov


const int timeZone = 1;     // Central European Time
//const int timeZone = -5;  // Eastern Standard Time (USA)
//const int timeZone = -4;  // Eastern Daylight Time (USA)
//const int timeZone = -8;  // Pacific Standard Time (USA)
//const int timeZone = -7;  // Pacific Daylight Time (USA)


WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets

void setup() 
{
  Serial.begin(9600);
  while (!Serial) ; // Needed for Leonardo only
  delay(250);
  Serial.println("TimeNTP Example");
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, pass);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  
  Serial.print("IP number assigned by DHCP is ");
  Serial.println(WiFi.localIP());
  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(Udp.localPort());
  Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);
}

time_t prevDisplay = 0; // when the digital clock was displayed

void loop()
{  
  if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) { //update the display only if time has changed
      prevDisplay = now();
      digitalClockDisplay();  
    }
  }
}

void digitalClockDisplay(){
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(".");
  Serial.print(month());
  Serial.print(".");
  Serial.print(year()); 
  Serial.println(); 
}



void printDigits(int digits){
  // utility for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  sendNTPpacket(timeServer);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:                 
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

Hallo,

als Hinweis weil es offenbar wenig bekannt ist:
Im ESP8266 SDK ist ein Port der Posix time.h enthalten.
Als Beispiel für die Nutzung mal dieser Link:

Infos sind etwas spärlich, die aus der ArduinoIDE unterstützten Funktionen und Strukturen findet man hier:
https://github.com/esp8266/Arduino/blob/master/tools/sdk/libc/xtensa-lx106-elf/include/time.h
Vorteile: kaum Programmieraufwand, nur einbinden und mit

configTime(timezone * 3600, dst * 0, "pool.ntp.org", "time.nist.gov");

im Setup() initalisieren. Standardeinstellung ist 1x pro Stunde aktualisieren, ansonsten läuft eine Softwareuhr.
Außerdem wird die Laufzeitinfo der Timeserver berücksichtigt, die Uhr wird also sehr genau gesetzt.

Gruß aus Berlin
Michael

hier hab ich mal damit gekämpft:
https://forum.arduino.cc/index.php?topic=525378.0

im Beitrag #16 gibts einen Link auf einen funktionierenden Sketch.

edit: die Sache von amithlon muss ich mir mal ansehen...

Hallo,

für den Fall das Du eine Fritzbox hast , die hat einen eigenen NTP Server eingebaut.

Aber die Lösung von Michael ist sicher noch einfacher ob die mit einer Fritzbox geht müsste man mal ausprobieren.

Heinz

amithlon:
Hallo,

als Hinweis weil es offenbar wenig bekannt ist:
Im ESP8266 SDK ist ein Port der Posix time.h enthalten.

Und seit ein paar Wochen wird es in den Esp WiFi Beispielen verwendet.

Ich habe aber auch noch nicht damit gearbeitet!

Gruß Fips

Hallo,

Derfips:
Und seit ein paar Wochen wird es in den Esp WiFi Beispielen verwendet.
Ich habe aber auch noch nicht damit gearbeitet!

der Witz ist, daß die Extension mit der Lib schon seit rund 2 Jahren beim Arduino drin ist, ich bin nur durch einen Zufall im Espressif-Forum drüber gestolpert.
Ich nutze es hier inzwischen auf allen ESP die NTP-Zeit holen und habe seitdem keinerlei Probleme mehr, die Uhren gehen einfach immer richtig. :wink:

Es geht auch problemlos mit dem Fritzbox-NTP, eben fritz.box oder die IP eintragen, nutzt mein Bekannter so.

Gruß aus Berlin
Michael

@amithlon... super Tipp!

nur das
configTime(timezone * 3600, dst * 0, "pool.ntp.org", "time.nist.gov");

muss meines Erachtens

configTime(timezone * 3600, dst, "pool.ntp.org", "time.nist.gov");

sein, denn sonst können die Sekunden in dst ja keinen Einfluss auf die Zeit nehmen.

Hi noiasca,
irgendwie bin ich anderer Meinung als Du. Wie wäre es mit

configTime(timezone * 3600, dst * 3600, "pool.ntp.org", "time.nist.gov");

dst = 1 * 3600 -> + 1h
dst = 0 * 3600 -> + 0h

...oder bin ich jetzt auf einem völlig falschen Dampfer? Ich teste es auf jeden gleich mal.

Gruß Olaf

Hallo nochmals,

es machts wie erwartet, muss aber trotzdem nicht richtig sein. Mir hat sich zur Sekunde noch nicht erschlossen, ob und wann die zeitabhängige Berücksichtigung der Sommerzeit überhaupt stattfindet. Ich vermute in der time.h Library anhand der Zeitzone. Mit der habe ich mich aber noch genauso wenig beschäftigt wie mit dieser genialen ESP Funktion mit wenigen Zeilen Code die Zeit per NTP verfügbar zu haben.
Mein Kurztest basiert auf den hier bereits von amithlon verlinkten Sketch wifi - Get hour with ctime (time library with ESP8266) - Arduino Stack Exchange

Gruß
Olaf

Hallo,

die Variable dst aus dem obigen Beispiel hat im Sinne der Sommerzeitumstellung vermutlich überhaupt keine sinnvolle Auswirkung auf die korrekte Berechnung der Sommer- oder Winterzeit.
Es gibt offensichtlich ein Flag im C++ mit Namen tm_isdst (is day light saving time), dieses Flag bleibt hierkonstant auf 0, egal ob dst 0 oder 1 ist.

Auszug aus meinem bereinigten Sketch anbei. Vielleicht hat hier jemand eine Idee wie ich ohne großen Aufwand die Sommer- Winterzeitumstellung über die vorhandenen Libraries hier elegant erledigen kann.

#include <ESP8266WiFi.h>
#include <time.h>

const char* ssid                  =  "";   // Your network SSID (name)
const char* password              =  "";// Your network password
int timezone                      =  1;            // GMT + 1
int dst                           =  1;            // Summer time, true

void setup() {
.
.
int timezone                   =  1;            // GMT + 1
int dst                           =  1;            // Summer time, true
.
.
}

void loop() {
  time_t now = time(nullptr);
  Serial.print("ctime ");
  Serial.println(ctime(&now));

  /***  Structure (http://www.cplusplus.com/reference/ctime/gmtime/)  ***/
  // time_t now;
  struct tm * timeinfo;
  time(&now);
  timeinfo = gmtime(&now);                         
  Serial.print("gmtime ");
  Serial.print(timeinfo->tm_hour);
  Serial.print(":");
  Serial.print(timeinfo->tm_min);
  Serial.print(":");
  Serial.print(timeinfo->tm_sec);

  Serial.print("  ");
  Serial.print(timeinfo->tm_mday);
  Serial.print(".");
  Serial.print(timeinfo->tm_mon + 1);
  Serial.print(".");
  Serial.println(timeinfo->tm_year+1900);

  Serial.print("Day of week: ");                   // Sunday = 0
  Serial.print(timeinfo->tm_wday);

  
  Serial.print(" DST: ");
  Serial.println(timeinfo->tm_isdst);    // Day light saving
  delay(1000);

}                                                  // End of void loop()

Ansonsten muss ich mal meine uralten Unterlagen sichten....

Hallo,

ich bin das Sommerzeit-Problem auch noch nicht angegangen, allerdings mehr wegen eines logischen Problems:
Die Lib selbst scheint da nichts anzubieten. Libs, Srpite, Funktionen usw., die aus dem Datum feststellen, ob Sommer- oder Winterzeit ist, gibt es ja wie Sand am Meer.

Also müßte man beim Aufruf von configTime() erstmal die Daten holen, dann testen, ob Sommer/Winter und dann configTime() mit dem passenden Wert nochmal aufrufen?

Gruß aus Berlin
Michael

Man kann auch intern alles in UTC bearbeiten (und vor allem abspeichern wenn gebraucht) und nur für die Anzeige auf die lokale Sommer-/Winterzeit umrechnen. z.B. mit der Funktion von Jurs.

Das hat z.B. beim Speichern in einer Datenbank den Vortil, dass es keine doppelten Zeitstempel bzw. Löcher an den Umstellungstagen gibt.

Gruß Tommy

Hallo nochmals,
alle meine gestrigen Recherchen und Untersuchungen bestätigen, dass die Variable dst in der configTime() Funktion nur ein Dummy ist.

Daher macht im Nachinein die Multiplikation von dst mit 0 vielleicht doch Sinn, obwohl man das vermutlich auch anders hätte lösen können. Könnte man aber zumindest als Hinweis interpretieren: "Hey Leute, da tut sich noch nichts"

configTime(tzHours * 3600, [b]dst * 0[/b], "0.europe.pool.ntp.org", "pool.ntp.org", "time.nist.gov");

Der Weg den ich jetzt gegangen bin unterscheideet sich nicht großartig von obigem Beispiel. Im Setup() erst einmal den Zeitstempel holen. Dann in der Loop() mit Hilfe der Funktion von Jurs feststellen ob das dst Flag gesetzt ist oder nicht und nachfolgend entscheiden welcher Wert hinzuaddiert wird.
Der Code Snippet wie folgt:

    dst = summertime_EU(year, month, mday, hour, tzHours);
    Serial.print("DST-Flag: ");
    Serial.println(dst);
    if (dst == 1) {
      configTime(tzHours * 3600, [b]dst * 3600[/b], "0.europe.pool.ntp.org", "pool.ntp.org", "time.nist.gov");// fritz.box
    }
    else
      configTime(tzHours * 3600, [b]dst * 3600[/b], "0.europe.pool.ntp.org", "pool.ntp.org", "time.nist.gov");// fritz.box

Dieser Teil ds Codes wird in einer Heartbeat-Routine sekündlich durchlaufen. Nur falls der Author der configTime() irgendwann mal ein Update mit erweitertem Funktionsumfang bereitstellt, füttere ich die configTime() jetzt bereits mit dem dst Flag. Ginge sonst durchaus einfacher.

Die Fritzbox stellt übrigens tatsächlich einen NTP-Server bereit. Funktioniert prinzipiell auch hier mit der time Library. Gibt bei mir allerdings nur eine falsche Uhrzeit heraus. Das habe ich jetzt jedoch noch nicht weiter untersucht, gibt ja genügend andere Optionen.

Vielen Dank allen für die Hinweise!

Hallo,

ich würde mir da wohl eher Zeit/Datum aus der passenden Struktur holen und nur zur Stundenwechsel testen und einmalig korrigieren. Ich vermute mal, daß der Aufruf von configTime() alles neu initialisiert und damit auch ein holen der Zeit vom NTP auslöst. Das muß aber nicht unbedingt alle Sekunde passieren.

Gruß aus Berlin
Michael

@amithlon ...stundenweises Abfragen ist ein guter Hinweis. Hätte ich auch drauf kommen können, bin ich aber nicht. Danke!

Gruß Olaf

Ich habe meinen NTP Sketch nun versucht etwas übersichtlicher und modularer zu gestalten. Abfrage der NTP Zeit nur noch einmalig direkt beim Einschalten/Reset oder zu jeder vollen Stunde. In der loop() gibt es jetzt nur noch 3 Funktionsaufrufe:

void loop() {
  SetupNTP();                                     // Init WiFI and NTP
  GetTime();                                       // Get updated MEZ/MESZ (NTP)
  PrintTime();                                     // Display time, toggle LED
}                                                       // End of void loop()

Der gesamte Sketch inkl. Jurs Sommerzeit_EU Auswertung in der Anlage. SSID und Passwort muss natürlich dem eigenen Netzwerk entsprechend angepasst werden.

Ich bin erst einmal dabei geblieben alle Zeit-Variablen global zu definieren, erschien mir einfacher und universeller im Handling. In der PrintTime Funktion wird ansonsten auch noch die LED getoggelt. Die vielen Serial.print-Anweisungen können nach Belieben gelöscht werden. Habe sie jedoch erst einmal noch drinnen gelassen um weiter testen zu können. Vielleicht habt ihr ja auch noch Vorschläge, die sich einfach umsetzen lassen.

Grüße aus dem sonnigen Langenhagen
Olaf

ESP8266EasyNTP_1.ino (10.4 KB)

Das ist etwas, auf das ich schon lange gewartet habe. Kurz und knackig.
Aber mit einem WEMOS D1 R2 & mini erhalte ich unter Arduino 1.8.5 die Fehlermeldung:

undefined reference to `gmtime'

Wo liegt der Fehler? Danke!

Hallo,

alte Version des ESP8266-SDK in der IDE?
Die Struktur ist offenbar erst ab 2.4.0 drin.

Ich nehme inzwischen nur noch die 2.4.1, allerdings oft mit dem alten TCP-Stack (lwip 1.4 Higher Bandwidth).
Im lwip 2.0 sind soviele Änderungen drin daß einige Libs ein Update brauchen und (meist geringe) Anpassungen nötig werden.

Gruß aus Berlin
Michael