Effettuare una richiesta NTP e passare il TIMESTAMP alla libreria RTC

Ciao tutti,
premetto che sono un programmatore amatoriale, per giunta per lo più “lato web”, quindi spero perdonerete le eventuali baggianate che forse dirò :slight_smile:
Vorrei usare la ethernet shield ENC28J60 (con la libreria EtherCard) per prendere l’orario da un server NTP e aggiornare l’RTC in modo da visualizzare poi data e ora su un display LCD. Per ora “mi accontenterei” di modificare lo sketch softrtc della libreria RTClib, magari in seguito potrei “farmi male” aggiungendo un DS1307 che ho. Visualizzare anche i secondi, però, mi riesce soltanto se invio al server una richiesta al secondo appunto, il che non mi sembra una cosa molto carina. E non mi sembra abbia senso inviare anche una richiesta al minuto, no? Dovrebbe bastarne una a ogni avvio dello sketch/ogni reset (penso...).
Avrei quindi pensato di effettuare la richiesta NTP nel setup anziché nel loop e, dopo aver trasformato il timestamp, settare l’ora nell’RTC - software o hardware che sia - e lasciare a questo il compito di far scorrere i secondi sul display, fino al prossimo reset o a fino a un eventuale aggiornamento programmato, che fare con la libreria looper. L’immondo e banale codice per ottenere il timestamp (frutto di mix di esempi trovati in giro) è il seguente:

#include <EtherCard.h>

static byte mymac[] = {0x00,0x1A,0x4B,0x38,0x0C,0x5C};
byte Ethernet::buffer[700];

static byte ntpServer[] = {193,204,114,232};
static byte srcPort = 0;

uint32_t timeStamp;

#define INTERVAL                10000
unsigned long lastTime = 0;

void setup () {
  Serial.begin(57600);
  Serial.println("NTP Demo");
 
  if (!ether.begin(sizeof Ethernet::buffer, mymac, 4))
    Serial.println( "Failed to access Ethernet controller");
 else
   Serial.println("Ethernet controller initialized");
 
  if (!ether.dhcpSetup())
    Serial.println("Failed to get configuration from DHCP");
  else
    Serial.println("DHCP configuration done");
 
  ether.printIp("IP Address:\t", ether.myip);
  ether.printIp("Netmask   :\t", ether.mymask);
  ether.printIp("Gateway   :\t", ether.gwip);

  ether.ntpRequest(ntpServer, srcPort);
  ether.ntpProcessAnswer(&timeStamp, srcPort);
    Serial.println("NTP answer received");
    Serial.println();
    Serial.print("Timestamp: ");
    Serial.println(timeStamp);

}

void loop(){
ether.packetLoop(ether.packetReceive());
}

Uhm… cosa mi sfugge? Se le metto nel loop, le richieste NTP funzionano… non capisco :~
Nello sbattimento mentale, avevo addirittura pensato di usare la libreria looper per creare due "eventi di aggiornamento", uno subito e uno magari che so... ogni ora.. ma non funziona uguale, visto che l'addjob del looper va chiamato nel setup(), o almeno così mi pare...
Grazie in anticipo a chi mi darà un chiarimento… :slight_smile:

non e' detto che il server NTP risponda alla prima chiamata, a volte vanno fatte piu' richieste.
ma senza eccedere, altrimenti rischi di essere bannato

Brunello:
non e' detto che il server NTP risponda alla prima chiamata, a volte vanno fatte piu' richieste.
ma senza eccedere, altrimenti rischi di essere bannato

appunto non volevo fare addirittura una richiesta al secondo, e memorizzare il timestamp all'inizio...
il server risponde bene, la versione funzionante dello sketch si comporta bene sia col server che citavo nel codice che con altri...
idee? ::slight_smile:

Metti un check per cui se il server non ha risposto alla prima, nel loop lo richiami ad intervalli regolari finché non ottieni risposta.

leo72:
Metti un check per cui se il server non ha risposto alla prima, nel loop lo richiami ad intervalli regolari finché non ottieni risposta.

E se ottieni risposta imposta l'intervallo successivo a 12 o 24 ore, così tieni sempre aggiornato l'RTC con una buona precisione.

Sì, perché anche se abbastanza preciso, un RTC non è preciso quanto un server NTP e quell'errore di qualche secondo al giorno si accumula pian piano.

la richiesta è asincrona, devi aspettare che arrivi una risposta.

o metti un delay fisso, o un delay finchè non arriva qualcosa, o un mix, aspetti finchè non arriva qualcosa o vai in timeout, nel caso timeout o risposta invalida ripeti la richiesta.

Ciao e grazie a tutti per le risposte :slight_smile:

sono tornato sulla cosa, e mi sembra di esserci finalmente riuscito, ecco il codice

#include <EtherCard.h>

#define INTERVAL                1000

static byte mymac[] = {0x00,0x1A,0x4B,0x38,0x0C,0x5C};
byte Ethernet::buffer[700];

static byte ntpServer[] = {193,204,114,232};
static byte srcPort = 0;
unsigned long lastTime = 0;

uint32_t timeStamp;
boolean requestSent;
boolean timeSet;

void setup () {

  Serial.begin(57600);
  Serial.println("NTP Demo");  
  Serial.println();
  requestSent = false;
  timeSet = false;
  connessione_eth();
}
 
 
 void loop() {
  richiesta_ntp();
  }
  
void connessione_eth(){
  
    if (!ether.begin(sizeof Ethernet::buffer, mymac, 4))
    Serial.println( "Failed to access Ethernet controller");
 else
   Serial.println("Ethernet controller initialized");
 
  if (!ether.dhcpSetup())
    Serial.println("Failed to get configuration from DHCP");
  else
    Serial.println("DHCP configuration done");
 
  ether.printIp("IP Address:\t", ether.myip);
  ether.printIp("Netmask   :\t", ether.mymask);
  ether.printIp("Gateway   :\t", ether.gwip);

}

void richiesta_ntp(){

 ether.packetLoop(ether.packetReceive());
  
    if(requestSent && ether.ntpProcessAnswer(&timeStamp, srcPort)) {
      Serial.println("NTP answer received");
      Serial.println();
      Serial.print("Timestamp: ");
      Serial.println(timeStamp);
      Serial.println();
      requestSent = false;
      timeSet = true;
    }
  
  if (!timeSet){
    unsigned long time = millis();
    if(time - lastTime > INTERVAL) {
      lastTime = time;
      ether.ntpRequest(ntpServer, srcPort);
      Serial.println("NTP request sent");
      requestSent = true;
  
    }
  }
}

non so è correttissimo come ragionamento però almeno ora fa la richiesta e quando riceve la risposta non ne fa più. Ora rimane da passare il timestamp all'RTC che, come supponevo, non supporta di essere configurato nel loop() ma soltanto nel setup...
mumble mumble...

impossibile, NON ci sono differenze tra essere in un setup o in un loop, come invece ci sono nell'essere nel normale flusso di codice o in una funzione interrupt.

Quindi il codice DEVE funzionare.

il punto è che setup è eseguito solo una volta, mentre loop() è eseguito continuamente.

Il tuo codice in richiesta_ntp(); non effettua nessun delay() per attendere risposta, quindi l'unico modo per vedere se è arrivata una risposta è chiamare continuamente richiesta_ntp(); infatti esso non fa solo la richiesta, ma ne effettua anche la lettura e decodifica. Quello che puoi fare è che la funzione ritorna 1 quando la risposta è arrivata, -1 se la risposta tarda troppo ad arrivare, e 0 altrimenti (richiesta fatta, in attesa di risposta)

quindi se nel setup fai:

setup(){
int risposta;
while ( (risposta = richiesta_ntp()) == 0){
 //faccio niente fichè non arriva una risposta NTP;
 //volendo potrei fare altro
}
switch (risposta){
case 1: Serial.println("Data e ora settate correttamnte"); break;
case -1: Serial.println("Timeout della risposta dal server NTP"); break;
default: Serial.println("Risposta sconosciuta");
}
}

ora il tuop loop si attiverà quando avrai una risposta (o un timeout) dall'NTP

ora a questo punto

Non c'entra la libreria rtc, ma metto qui per non aprire un altro topic.
Ho provato a scorrere i vari topic , ma non ho trovato risposta al mio problema.
Ho caricato questo sketch di esempio dalla libreria time su una arduino mega R3 + ethernet shield

/*
 * Time_NTP.pde
 * Example showing time sync to NTP time source
 *
 * This sketch uses the Ethernet library
 */
 
#include <Time.h> 
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <SPI.h>

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0xA8, 0x7A }; 
// 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)


EthernetUDP Udp;
unsigned int localPort = 9998;  // local port to listen for UDP packets

void setup() 
{
  Serial.begin(9600);
  while (!Serial) ; // Needed for Leonardo only
  delay(250);
  Serial.println("TimeNTP Example");
  if (Ethernet.begin(mac) == 0) {
    // no point in carrying on, so do nothing forevermore:
    while (1) {
      Serial.println("Failed to configure Ethernet using DHCP");
      delay(10000);
    }
  }
  Serial.print("IP number assigned by DHCP is ");
  Serial.println(Ethernet.localIP());
  Udp.begin(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();
}

Tutto funziona alla perfezione , ma se vario le seguenti linee del codice per assegnare un ip fisso alla mia scheda ethernet lo sketch non funziona e perché non riceve risposta dal server NTP.

codice variato

byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0xA8, 0x7A };  // MAC Address assigned to the board
IPAddress ip(192, 168, 1, 222);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

e poi

Serial.println("TimeNTP Example");
  Ethernet.begin(mac, ip, gateway, subnet);
  
  Serial.print("IP number  is ");

tutto il resto del programma è invariato
La scheda prende l'IP da me scelto, risponde al ping, ma il monitor seriale dice che non ha ricevuto risposta dal server NTP.
Ho provata anche altri esempi e se lascio la scelta dell'IP al DHCP tutto funziona, ma se metto gli IP fissi non va.
Le varie porte sono state aperte sul router e il firewall è disabilitato.
Grazie per le eventuali risposte.

L'avrò scritto miliardi di volte. :grin:
La Ethernet.begin è:

Ethernet.begin(mac, ip, gateway, gateway, subnet);

il primo gateway funge da DNS.

EDIT:
Se usi la ENC con la libreria EtherCard, controlla nella libreria stessa la corretta sintassi.
Quella precedente è riferita alla shield ufficiale.