Datum i vrijeme oduvijek su bili bitni, a ako imate projekt koji prikuplja podatke, prilično je bitno zabilježiti i trenutak u kojem je neki podatak prikupljen. U nastavku slijedi kratki prikaz kako doći do točnog datuma i vremena (a onda ga i sačuvati), korištenjem pločice bazirane na ESP8266 – u ovom slučaju to je Croduino NOVA iz e-radionice.
Kako je ESP8266 po prirodi stvari povezan na internet, logično je podatak o točnom vremenu dohvatiti s nekog NTPservera. Uz malo guglanja može se pronaći nekoliko Arduino biblioteka koje mogu obaviti posao, za ovaj primjer korištena je Time biblioteka Paula Stoffregena.
Početak
Za početak, u projekt je potrebno uključiti standardnu ESP8266 biblioteku iz koje koristimo ESP8266WiFi, WiFiUdp i Ticker, a TimeLib dolazi u prije navedenoj Time biblioteci. Za podatak o točnom vremenu ne koristi se neki konkretni server, nego europski NTP pool koji bi se trebao pobrinuti za to da nam dodjeli geografski najbliži NTP server. Vremenska zona, tj. njena ‘ljetna’ varijanta je hardkodirana, pošto NTP vraća UTC vrijeme, za domaću zadaću pokušajte taj problem riješiti na pametniji način 😉
// Time keeping using Time library for Arduino - https://github.com/PaulStoffregen/Time #include <TimeLib.h> #include <ESP8266WiFi.h> #include <WiFiUdp.h> #include <Ticker.h> const char ssid[] = "*********"; // your network SSID (name) const char pass[] = "*********"; // your network password static const char ntpServerName[] = "0.europe.pool.ntp.org"; //const int timeZone = 1; // Central European Time const int timeZone = 2; // Central European Summer Time WiFiUDP udp; unsigned int udpPort = 8888; Ticker timer; const float tick = 1.0; // in seconds, how long to wait for the next time display
Treba napomenuti i da koristimo UDP protokol za komunikaciju s NTP serverom i jednostavni interni timer kako bi dobili prikaz vremena u zadanom razmaku (u primjeru je to jedna sekunda, ali lako se može promijeniti u neki drugi interval, npr. minutu). Također, ako se pojave problemi s dohvaćanjem NTP servera iz navedenog poola, moguće je promijeniti u npr. “1.europe.pool.ntp.org” ili “hr.pool.ntp.org”. Ajmo dalje…
Setup i loop
void setup() { Serial.begin(115200); Serial.print("Connecting WiFi"); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("Starting UDP"); udp.begin(udpPort); Serial.print("Local port: "); Serial.println(udp.localPort()); Serial.println("waiting for sync"); setSyncProvider(getNtpTime); setSyncInterval(86400); // in seconds, 86400 = 24 hours timer.attach(tick, &showDateTime); //start timer } void loop() { // nothing to do here :-) }
U setup funkciji započinje serijska komunikacija sa računalom i pokušavamo uspostaviti vezu s NTP serverom (nadamo se uspješno) i našem timeru zakačimo interval i adresu funkcije koju poziva kada taj interval prođe:
void showDateTime() { char datetime[20]; sprintf(datetime, "%02d:%02d:%02d %02d.%02d.%04d.", hour(), minute(), second(), day(), month(), year()); Serial.println(datetime); }
Ova funkcija formatira i šalje vrijeme i datum računalu, koje u Arduino IDE-u možemo vidjeti u Serial Monitoru (Ctrl+Shift+M). Lijepo je vidjeti da void loop()
ne radi ništa 🙂
NTP
Kako bi sve funkcioniralo fali nam još samo dio koda koji zna pročitati ono što nam je NTP server poslao putem UDP protokola:
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() { IPAddress ntpServerIP; // NTP server's ip address while (udp.parsePacket() > 0) ; // discard any previously received packets Serial.println("Transmit NTP Request"); // get a random server from the pool WiFi.hostByName(ntpServerName, ntpServerIP); Serial.print(ntpServerName); Serial.print(": "); Serial.println(ntpServerIP); sendNTPpacket(ntpServerIP); 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(); }
Ovaj dio koda preuzet je iz primjera koji se nalazi u sklopu Time biblioteke. Ukoliko imate problema s komunikacijom sa NTP serverom možete u liniji while (millis() - beginWait < 1500)
povećati vrijednost s 1500 naviše, ali u tom slučaju riskirate da će se aktivirati ESP-ov watchdog pa ćete dobiti poruku Soft WDT reset
i blokirani mikrokontroler. Nije niti to nepremostiva prepreka, kako se obraniti od ovog psa čuvara možete pročitati ovdje.
OK, a je’l drifta?
Naravno da drifta! Ova metoda nije posebno točna već prilikom dohvaćanja podataka s NTP servera, za povećanje točnosti trebalo bi koristiti više NTP servera i algoritam za sinkronizaciju. Ali, čak i bez toga, korišteni library gubi bitku s entropijom i počinje zaostajati:
2018-06-29 20:04:08.73: 20:04:08 29.06.2018. . . . . 2018-06-30 08:00:09.05: 08:00:07 30.06.2018.
U 12 sati naš timer zaostao je cca 0.3 s za satom računala na koje je bio spojen (u ovom slučaju više vjerujemo računalu :-)), a biblioteka je izgubila maksimalno 1 sekundu (rezultat je zaokružen na sekundu pa možemo samo nagađati koliki je točno zaostatak). U većini slučajeva ovo nije pretjerano bitno pa se sinkronizacija s NTP serverom ne mora izvoditi prečesto (što se niti ne smije), a za sprečavanje drifta potrebno je koristiti vanjski RTC modul s temperaturnom korekcijom, npr. Adafruit DS3231 precizni RTC. Ipak, za veliku većinu primjena nije bitna velika točnost, pa se ovaj primjer može bez puno razmišljanja iskoristiti u mnogim IoT projektima.