Internet de les coses amb ESP32 i ESP8266

Exemples Referència Plaques   Recursos CITCEA
Projectes Programació Perifèrics   Inici

Llegim l'hora d'internet

Els servidors NTP (Network Time Protocol) ens permeten saber l'hora actual, encara que amb un format una mica peculiar. Fent una connexió UDP amb els paràmetres apropiats a un d'aquests servidors obtindrem, entre altres dades, un nombre de quatre bytes que conté el nombre total de segons que han transcorregut des del dia 1 de gener de 1900 (en el meridià de Greenwich).

Amb la placa Arduino MKR WIFI 1010, hi ha una altra manera més simple d'obtenir l'hora i la data actuals fent servir la funció getTime.

Podem generar, a partir dels quatre bytes, el valor dels segons transcorreguts. A partir d'aquest valor podem calcular amb certa facilitat el nombre de segons transcorreguts del dia actual i, a partir d'aquest valor, l'hora. Amb una mica més de feina, atès que els anys tenen durades diferents, podem calcular també la data.

// Aquest programa està parcialment basat en els exemples de la pàgina
// https://www.arduino.cc/en/Tutorial/LibraryExamples#wifi1010
#include <SPI.h>    // Carreguem la biblioteca SPI
#include <WiFiNINA.h>    // Carreguem la biblioteca WiFiNINA
#include <WiFiUdp.h>    // Farem servir el protocol UDP
const char idXarxa[] = "xarxa-wifi";    // Nom del punt d'accés 
const char contrasenya[] = "contrasenya-wifi";    // Contrasenya de connexió 
int status = WL_IDLE_STATUS;
unsigned int localPort = 2390;    // Port per a la recepció UDP
IPAddress timeServer(130, 206, 3, 166);    // Servidor hora.rediris.es
const int NTP_PACKET_SIZE = 48;    // Número de paquets necessaris per obtenir la informació de l'hora
byte packetBuffer[NTP_PACKET_SIZE];    // Buffer on guardarem els paquets
int hora, minuts, segons, dia, mes, any, diaSet;
String DiesSetm[] = {"Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte", "Diumenge"};
WiFiUDP Udp;    // Creem un objecte UDP
bool Traspas(int any){
    bool bixest = false;
    // Són anys de traspàs els múltiples de 4 excepte si són múltiples de 100 i no ho són de 400
    if((any % 400 == 0) || ((any % 4 == 0) && (any % 100 != 0))){
        bixest = true;
    }
    return bixest;
}
void calcData(unsigned long Segs){
    // Cada dia té 86400 s
    // El nombre de dies des de l'1-1-1900 és el quocient de dividir per aquesta quantitat
    unsigned long diesTotals = Segs / 86400UL;
        // Posem UL perquè ens ho guardi en un unsigned long (ja que no cap en un int)
    unsigned long TotalDies = 0;
    unsigned long AfegirDies;
    // Anirem sumant els dies de cada any des de 1900 fins superar el nombre total de dies
    int compt = 1899;    // Comencem un any abans perquè sumarem 1
    int DiesMes[]={31,28,31,30,31,30,31,31,30,31,30,31};
    diaSet = 1 + diesTotals % 7;    // Valor entre 1 i 7 (sumem 1 perquè no pugui valer 0
    // El dia 1-1-1900 era dilluns, per tant no cal cap correcció
    while(TotalDies <= diesTotals){
        compt++;
        if(Traspas(compt)){
            AfegirDies = 366;
        } else {
            AfegirDies = 365;
        }
        TotalDies += AfegirDies;
    }
    // Ja ens hem passat, restem el darrer any (que està incomplet)
    TotalDies -= AfegirDies; 
    any = compt;    // El valor del comptador és l'any actual
    compt = 0;    // Ara farem el mateix amb els mesos d'aquest any
    while(TotalDies <= diesTotals){
        compt++;    // compt ara és el mes, d'1 a 12
        AfegirDies = DiesMes[compt - 1];    // DiesMes va de 0 a 11
        if((compt == 2) && Traspas(any)){    // Estem a febrer d'un any de traspàs?
            AfegirDies++;    // Afegim el 29
        }
        TotalDies += AfegirDies;
    }
    // Ja ens hem passat, restem el darrer mes (que està incomplet)
    TotalDies -= AfegirDies; 
    mes = compt;    // El valor del comptador és el mes actual
    // El que queda és el nombre de dies complets (fins ahir)
    dia = diesTotals - TotalDies + 1;    // Sumem 1 per tenir avui
}
void escriuData() {
    Serial.print(DiesSetm[diaSet - 1]);
    Serial.print(", ");
    Serial.print(dia);
    Serial.print("-");
    Serial.print(mes);
    Serial.print("-");
    Serial.println(any);
}
void calculHora(unsigned long Segs) {
    // Cada dia té 86400 s
    // El nombre de segons del dia d'avui és el residu de dividir per aquesta quantitat
    Segs = Segs % 86400UL;
        // Posem UL perquè ens ho guardi en un unsigned long (ja que no cap en un int)
    hora = Segs / 3600;
    Segs = Segs % 3600;
    minuts = Segs / 60;
    segons = Segs % 60;
}
void escriuHora() {
    Serial.print(hora);
    Serial.print(".");
    if (minuts < 10) {
        Serial.print("0");    // Afegim el 0 si és menor que 10
    }
    Serial.print(minuts);
    Serial.print(".");
    if (segons < 10) {
        Serial.print("0");    // Afegim el 0 si és menor que 10
    }
    Serial.println(segons);
}
void setup() {    // Inicialització
    Serial.begin(9600);    // Monitor sèrie
    while (!Serial) {
        ;    // Esperem que l'usuari obri el monitor sèrie
    }
    if (WiFi.status() == WL_NO_MODULE) {
        Serial.println("No s'ha trobat el dispositiu Wi-Fi");
        while (true);    // Bloquegem el programa
    }
    String versio = WiFi.firmwareVersion();
    if (versio < "1.0.0") {
        Serial.println("Convindria actualitzar el firmware");
    }
    while (status != WL_CONNECTED) {
        Serial.print("Connectant a la xarxa ");
        Serial.println(idXarxa);
        status = WiFi.begin(idXarxa, contrasenya);
        delay(10000);    // Ho tornarem a intentar passats 10 s
    }
    Serial.print("Connectat a "); 
    Serial.println(WiFi.SSID());
    Serial.print("Estat de la connexió: ");
    Serial.println(WiFi.status()); 
    Serial.print("Adreça IP del dispositiu: ");
    Serial.println(WiFi.localIP()); 
    Serial.print("Intensitat del senyal: ");
    Serial.print(WiFi.RSSI()); 
    Serial.println(" dBm");
    Serial.println(); 
    Serial.println("Anem a connectar al servidor");
    Udp.begin(localPort);
}
void loop() {    // Programa que es repeteix indefinidament
    // Preparem el paquet que cal enviar per llegir l'hora
    for (int k = 0; k < NTP_PACKET_SIZE; k++) {
        packetBuffer[k] = 0;
    }
    packetBuffer[0] = 0b11100011;
    packetBuffer[1] = 0;
    packetBuffer[2] = 6;
    packetBuffer[3] = 0xEC;
    packetBuffer[12]  = 49;
    packetBuffer[13]  = 0x4E;
    packetBuffer[14]  = 49;
    packetBuffer[15]  = 52;
    Udp.beginPacket(timeServer, 123);    // La lectura de l'hora es fa pel port 123
    Udp.write(packetBuffer, NTP_PACKET_SIZE);    // Enviem el paquet
    Udp.endPacket();
    delay(1000);
    if (Udp.parsePacket()) {    // Ha arribat un paquet?
        Serial.println("Nou paquet");
        Udp.read(packetBuffer, NTP_PACKET_SIZE); // Llegim el paquet
        // Ens arriben quatre bytes de dades en les posicions 40-43
        // Corresponen a un sol valor unsigned long que hem de construir
        // D'entrada els guardarem en quatre variables
        unsigned long Word1 = packetBuffer[40];    // byte més significatiu
        unsigned long Word2 = packetBuffer[41];
        unsigned long Word3 = packetBuffer[42];
        unsigned long Word4 = packetBuffer[43];    // byte menys significatiu
        unsigned long Temps1900 = Word1 << 24;
        Temps1900 |= Word2 << 16;
        Temps1900 |= Word3 << 8;
        Temps1900 |= Word4;
        Serial.print("Han passat ");
        Serial.print(Temps1900);
        Serial.println(" segons des de l'1-1-1900");
        calcData(Temps1900);
        escriuData();
        Serial.print("L'hora GMT actual és ");
        calculHora(Temps1900);
        escriuHora();
        Serial.print("L'hora oficial actual (hivern) és ");
        calculHora(Temps1900 + 3600);
        escriuHora();
        Serial.print("L'hora oficial actual (estiu) és ");
        calculHora(Temps1900 + 7200);
        escriuHora();
        // Esperem 15 s
        delay(15000);
    }
}

En aquest programa hi ha la condició que el monitor sèrie estigui obert per a que el programa comenci a funcionar. Això ens permet seguir tot el procés encara que triguem a obrir el monitor sèrie. Quan el microcontrolador hagi de funcionar de manera independent de l'ordinador caldrà eliminar les línies següents per evitar aquest bloqueig.

    while (!Serial) {
        ;    // Esperem que l'usuari obri el monitor sèrie
    }

 

 

 

 

 

 

 

 

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.