Zeitsteuerung und UDP/NTP-Kalibrierung

Guten Tag liebe Forummitglieder,

ich bin noch recht neu im Arduino und auch noch nicht so fit im Programmieren.
Ich habe nun eine Zeitsteuerung programmiert, die zu einem bestimmten Zeitpunkt ein Relais an- bzw. ausschaltet.
Ich möchte nun, dass über UDP/NTP (ich habe ein Ethernetshield) regelmäßig (nicht alle 10 Sekunden) die Zeit der Softwaruhr wieder eingestellt wird bekomme den Programmablauf aber nicht hin. Könnte mir jemand helfen?

Markus Weih

Hallo Markus und Willkommen,

wie immer ist google in solchen Dingen dein erster Freund. Wir aber auch :slight_smile:
UdpNtpClient
Das könnte dir helfen um die Zeit per ntp zu bekommen.

Grüße
Nighti

markusweih:
Könnte mir jemand helfen?

Das Beispielprogramm UdpNTPClient zur Ethernet-Library findest Du in der Arduino-Software wie folgt:

Datei - Beispiele - Ethernet - UdpNTPClient

Danke für die Antworten! Ich war schon einen Schritt weiter. Der UDP/NTP Client ist schon installiert und funktioniert.
Momentan sieht es bei mir der Hauptteil des Programms so aus:
void loop()
{
Relaissteuerung(); /void "Relaissteuerung" zieht Relais zu gewünschten Zeiten (HH:MM) an. Das ist meine Arduinosoftwareuhr
if(Minute==45) //void "Internetzeit" (=UDP/NTP-Client) soll z.B. 1x/Stunde Zeit holen wenn Minute = 45
{
Internetzeit();
}
}
Der UDP-Client läuft aber in einer Endlosschleife und holt alle 10s die aktuelle Zeit. Wie komme ich da raus?

markusweih:
if(Minute==45) //void "Internetzeit" (=UDP/NTP-Client) soll z.B. 1x/Stunde Zeit holen wenn Minute = 45

Unschöne Programmlogik. Mit dem Code aus dem Beispielprogramm UdpNTPClient dauert das Nachstellen der Uhrzeit aus dem Internet ungefähr 1 Sekunde. Eine Minute hat 60 Sekunden, so dass Du mit Deiner Logik die Uhr innerhalb der einen Minute bis zu 60 mal nachstellen würdest. So belastet man keine fremden (und kostenlos bereitgestellten) Server-Ressourcen.

Bessere Logik: Alle x Minuten nachstellen, d.h. Du stellst immer x Minuten nach dem letzten Abholen der Internetzeit die Uhr nach. Und zwar einmal und dann erst wieder nach x Minuten.

markusweih:
{
Internetzeit();
}
}
Der UDP-Client läuft aber in einer Endlosschleife und holt alle 10s die aktuelle Zeit. Wie komme ich da raus?

Was für Programmierkenntnisse hast Du überhaupt?

Im Prinzip bastelst Du Dir die Funktion "loop" aus dem Beispielprogramm UdpNTPClient zur Funktion "Internetzeit" um und läßt das delay(10000) dabei weg. Und immer wenn es soweit ist, ruft Deine loop die Funktion "Internetzeit" auf.

Wenn Du die Systemzeit nur einmal pro Stunde überprüfst, kann die durchaus eine relevante Anzahl von Sekunden von der richtigen Zeit abweichen (bei einem UNO oder Mega2560 ohne Quartz). Du musst also sehr aufpassen, dass Du nicht einen Schaltzeitpunkt verpasst oder ein Gerät zweimal schaltest, weil die Zeit evtl. zweimal “erreicht” wird.

jurs hat Recht. Die NTP Server dürfen nur in einer bestimmten Zeitspanne nur ein Mal abgefragt werden. Wenn man die Server im Sekundentakt abfragt, dann kommt man sehr schnell auf die schwarze Liste.
Man muss seine Logik so anpassen, dass weder der Schaltzeitpunkt verpasst wird (bzw. es nicht schlimm wäre, wenn es nicht passiert) noch die NTP Server zu belasten.

Danke für die kritische Rückmeldungen. Meine Programmierkenntnisse sind wirklich sehr begrenzt, Entschuldigung. Natürlich will ich die Zeitserver nur möglichst selten abfragen. Meine loop ruft nun korrekt alle 60 Minuten den void "Internetzeit" auf. Das ist der UDPNTP-Client aus den Beispielen ohne loop. Nur stoppt der void nun nach einer Abfrage bzw. springt nicht mehr ins Hauptprogramm. Wie kann ich sicherstellen dass ein void nur 1x durchlaufen wird und dann das Hauptprogramm wieder als Schleife läuft? Sollte ich vielleicht doch lieber eine RTC nehmen?

markusweih:
Meine Programmierkenntnisse sind wirklich sehr begrenzt, Entschuldigung.

Ist nicht schlimm. Jeder Anfang ist schwer. Nur hätte ich vielleicht etwas leichteres Projekt für den Anfang genommen.
Poste deinen gesamten Quellcode und markiere die Stelle, welche dir Probleme bereitet. An deinen Posts merkt man, dass
es grundlegende Verständnisprobleme gibt.

markusweih:
Nur stoppt der void nun nach einer Abfrage bzw. springt nicht mehr ins Hauptprogramm.

An der Stelle hättest Du auch schreiben können "nun steppt der Bär und springt in den Graben" und ich hätte von dem Problem genau so viel verstanden wie bei dem, was Du geschrieben hast.

markusweih:
Wie kann ich sicherstellen dass ein void nur 1x durchlaufen wird und dann das Hauptprogramm wieder als Schleife läuft?

Ein void ist kein Wald, der durchlaufen werden kann.
Ich verstehe nur Bahnhof.

Vielleicht postest Du mal Deinen Code und erklärst anhand von Kommentar- bzw. Codezeilen, was Du meinst?

Danke für die Antworten. Danke für das Angebot, euch meinen Programmierschrott anzusehen.
Hier nun der ganze Sketch, die Zeilen, an denen ich nicht weiterkomme sind markiert.

// Softwareuhr schaltet externes Relais zu bestimmten Zeiten. Basierend auf Franzis Arduino
// Schuluhr. Relais ist auf +5V, VCC und GND; IN1 mit Pin 2 verbunden.
#include <SPI.h>         
#include <Ethernet.h>
#include <EthernetUdp.h>
int cnt, Sekunde, Minute, Stunde, Wochentagzahl=0;
char* Wochentag[7] = {"Do ","Fr ","Sa ","So ","Mo ","Di ","Mi "}; // *** lege die Wochentage fest, fängt mit 0 (Do.) an (1.1.1970 war Do) bis 6=Mi.
int RELAIS=2; //Relais ist an Pin 2
byte mac[] = {  
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
//IPAddress ip(192,168,100,12);  //IP Adresse für das Ethernet Shield, soll später eingebaut we
//IPAddress gateway(192,168,100,254);	
//IPAddress subnet(255, 255, 255, 0);
unsigned int localPort = 8888;      // local port to listen for UDP packets
IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov NTP server
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
EthernetUDP Udp;

void setup()
{
  Serial.begin(9600);
  pinMode(RELAIS,OUTPUT);
  analogWrite(RELAIS,255); // Relais auf 5 V = aus. 
  // Zeiteinstellung, beliebiges Beispiel
  Wochentagzahl=1; 
  Stunde=12;
  Minute=29;  
  Sekunde=45; // Freitag 12:29:45
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // no point in carrying on, so do nothing forevermore:
    for(;;)
      ;
  }
  Udp.begin(localPort);
}
void loop()                         // hier komme ich nicht weiter
{
  Relaissteuerung();
  if(Minute==45)  //Internetzeit holen 1x pro Stunde, wenn Minute =45
  {
    Internetzeit();
  }
}

void Relaissteuerung()   //Zieht Relais zu bestimmten Zeiten an
{
  cnt++;
  if(cnt==100)
  {
    Serial.print( Wochentag[Wochentagzahl] ); // *** Wochentag als wort aus Array Wochentag oben
    Serial.print(Stunde);
    Serial.print(":");
    Serial.print(Minute);
    Serial.print(":");
    Serial.println(Sekunde);
    Sekunde++;  
    if(Sekunde==60)
    {
      Sekunde=0;
      Minute++;     
      if(Minute==60)
      {
        Minute=0;
        Stunde++;        
        if(Stunde==24)
        {
          Stunde=0;
        }
      }
    }    
    cnt=0;
  }    
  delay(10);
  
  //  Hier die Zeiten, an denen das Relais ziehen soll 8:30-18h
  analogWrite(RELAIS,0); // Relais an; nur für Testzwecke
  if(Stunde>=8&&Minute>=30&&Wochentagzahl==0)analogWrite(RELAIS,0); //0 heisst 0V, Relais zieht Do um 8:30 an
  if(Stunde==18&&Minute>=00&&Wochentagzahl==0)analogWrite(RELAIS,255); //255 heisst 5V, Relais Do um 18:00 abschalten
  if(Stunde==8&&Minute>=30&&Wochentagzahl==1)analogWrite(RELAIS,0); //0 heisst 0V, Relais Fr 8:30h anschalten.
  if(Stunde==12&&Minute>=30&&Wochentagzahl==1)analogWrite(RELAIS,255); //255 heisst 5V, Relais Fr 12:30 h abschalten.
  if(Stunde>=8&&Minute>=30&&Wochentagzahl==4)analogWrite(RELAIS,0); //0 heisst 0V, Relais zieht Mo um 8:30 an
  if(Stunde==18&&Minute>=00&&Wochentagzahl==4)analogWrite(RELAIS,255); //255 heisst 5V, Relais Mo um 18:00 abschalten
  if(Stunde>=8&&Minute>=30&&Wochentagzahl==5)analogWrite(RELAIS,0); //0 heisst 0V, Relais zieht Di um 8:30 an
  if(Stunde==18&&Minute>=00&&Wochentagzahl==5)analogWrite(RELAIS,255); //255 heisst 5V, Relais Di um 18:00 abschalten
  if(Stunde==8&&Minute>=30&&Wochentagzahl==6)analogWrite(RELAIS,0); //0 heisst 0V, Relais Mi 8:30h anschalten.
  if(Stunde==12&&Minute>=30&&Wochentagzahl==6)analogWrite(RELAIS,255); //255 heisst 5V, Relais Mi 12:30 h abschalten.
}

void Internetzeit() //Holt Zeit aus Internet
{
  sendNTPpacket(timeServer); // send an NTP packet to a time server and waits to see if a reply is available
  delay(1000);  
  if ( Udp.parsePacket() ) {  
    Udp.read(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer
    //the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, esxtract the two words:
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);  
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;  
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;     
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;  
    // print Unix time:
    // Serial.println(epoch);   // print the hour, minute and second:
    const unsigned long wochenseit1170 = epoch/604800;
    const unsigned long tageseit1170 = epoch/86400;
    int wochentag = tageseit1170-wochenseit1170*7;
    Serial.print(Wochentag[wochentag] ); // *** wochentag als wort ausgeben
    Serial.print(" ");       // UTC is the time at Greenwich Meridian (GMT)
    Stunde = epoch % 86400L / 3600+2; // Stunde neu einstellen 
    Serial.print((epoch  % 86400L) / 3600+2); // print the hour (86400 equals secs per day)
    Serial.print(':');  
    if ( ((epoch % 3600) / 60) < 10 ) {
      // In the first 10 minutes of each hour, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.print((epoch  % 3600) / 60); // print the minute (3600 equals secs per minute)
    Serial.print(':'); 
    if ( (epoch % 60) < 10 ) {
      // In the first 10 seconds of each minute, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.println(epoch %60); // print the second
  }
  // wait 20 seconds before asking for the time again
 // delay(20000) habe ich herausgenommen, da keine Schleife in void gewünscht.
}
// send an NTP request to the time server at the given address 
unsigned long 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(); 
  
  while(1); // von mir eingefügt als Ende des void
}

markusweih:
while(1); // von mir eingefügt als Ende des void

Diese Zeile ist eine Endlosschleife.

Wenn der Code da einmal ankommt, wird danach nichts anderes mehr ausgeführt.

Was bitte soll ein "Ende des void" sein?

Wenn der Code nach Ausführen der Funktion Internetzeit() weiterlaufen soll, darfst Du natürlich keine Endlosschleife fabrizieren, in der die Codeausführung endlos hängenbleibt.

Nur mal so zum generellen Verständnis: "void" heisst "ungültig".
Das ist aber nichts Schlimmes.
Eine Funktionsdeklaration void xyz();
bedeuted lediglich, dass xyz ohne Aufrufparameter aufgerufen wird, und kein Ergebnis (also "void") zurückliefert,
was eigentlich schade ist, weil man raten muss, was xyz nun eigentlich macht ( vermutlich irgendwas mit irgendwelchen globalen Variablen ).

Mir persönlich würde eine Funktion
boolean Internetzeit(byte * ip, DateTime * time);  // ip: ntp-Server, *time: Ergebnis ; liefert false bei Problemen mit ntp-Server
besser gefallen...

Vielen Dank für die Rückmeldung! Nun läuft das Programm so, wie ich es mir vorgestellt habe und ich habe noch viel von euch über Programmieren gelernt!