Created
September 15, 2025 02:13
-
-
Save mscalora/8517e1823c71c8ab80bf3ddf199aa91b to your computer and use it in GitHub Desktop.
A very small NTP client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <vector> | |
| #include <functional> | |
| const char* poolNTPServerName = "pool.ntp.org"; | |
| const unsigned long seventyYears = 2208988800UL; | |
| class MicroNTP { | |
| public: | |
| static const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message | |
| protected: | |
| byte packetBuffer[MicroNTP::NTP_PACKET_SIZE]; // Buffer to hold incoming and outgoing packets | |
| WiFiUDP* udp; | |
| public: | |
| unsigned int localPort = 2390; // The local port to listen for UDP packets | |
| unsigned long lastSendMS = 0; | |
| unsigned long lastSyncMS = 0; | |
| unsigned long lastSyncGMTEpoch = 0; | |
| unsigned long resyncIntervalMS = 1000 * 60 * 1; | |
| long timezoneOffset = 0; | |
| std::vector<std::function<void (int epochSec)> > firstSync; | |
| std::vector<std::function<void (int epochSec)> > everySync; | |
| MicroNTP(WiFiUDP& clientUdp) { | |
| udp = &clientUdp; | |
| } | |
| /** | |
| * @brief notify callback of first (or next) sync, only called once | |
| */ | |
| void notifyFirstSync(std::function<void (time_t)> listener) { | |
| firstSync.push_back(listener); | |
| } | |
| /** | |
| * @brief notify callback of every time sync | |
| */ | |
| void notifyEverySync(std::function<void (time_t)> listener) { | |
| everySync.push_back(listener); | |
| } | |
| time_t getGMTEpoch() { | |
| return lastSyncGMTEpoch + (millis()-lastSyncMS)/1000; | |
| } | |
| time_t getLocalEpoch() { | |
| return lastSyncGMTEpoch + timezoneOffset + (millis()-lastSyncMS)/1000; | |
| } | |
| void setTimezoneOffset(long offset) { | |
| timezoneOffset = offset; | |
| } | |
| // Function to send an NTP request packet | |
| void sendNTPpacket(const char* server = poolNTPServerName) { | |
| Serial.println(F("[MicroNTP] Sending NTP request")); | |
| // Set all bytes in the buffer to 0 | |
| memset(packetBuffer, 0, NTP_PACKET_SIZE); | |
| // Initialize values needed to form an NTP request | |
| packetBuffer[0] = 0b11100011; // LI, VN, and Mode | |
| // Send the packet | |
| IPAddress ntpServerIp; | |
| if (!WiFi.hostByName(server, ntpServerIp)) { | |
| Serial.println("DNS lookup failed"); | |
| return; | |
| } | |
| udp->beginPacket(ntpServerIp, 123); // NTP requests are sent to port 123 | |
| udp->write(packetBuffer, NTP_PACKET_SIZE); | |
| udp->endPacket(); | |
| lastSendMS = millis(); | |
| } | |
| bool handleNTPPacket() { | |
| unsigned long now = millis(); | |
| int packetSize = udp->parsePacket(); | |
| if (packetSize) { | |
| Serial.printf("Received UDP packet of size %d\n", packetSize); | |
| // Read the packet into the buffer | |
| udp->read(packetBuffer, NTP_PACKET_SIZE); | |
| // The timestamp is in the last 8 bytes of the packet | |
| // Bytes 40-43 hold the seconds part | |
| unsigned long highWord = (packetBuffer[40] << 24) | (packetBuffer[41] << 16) | (packetBuffer[42] << 8) | packetBuffer[43]; | |
| // Combine the bytes to get the NTP timestamp | |
| unsigned long ntpTimestamp = highWord; | |
| // Convert NTP time (since Jan 1, 1900) to Unix time (since Jan 1, 1970) | |
| unsigned long unixTimestamp = ntpTimestamp - seventyYears; | |
| lastSyncMS = now; | |
| Serial.printf("Last Sync: lastSyncMS=%lu %lu\n", lastSyncMS, now); | |
| lastSyncGMTEpoch = unixTimestamp; | |
| Serial.printf("Current Unix epoch time: %lu received at device ms %lu\n", unixTimestamp, now); | |
| time_t localEpoch = unixTimestamp; | |
| for (const auto& callback : firstSync) { | |
| callback(localEpoch); | |
| } | |
| firstSync.clear(); | |
| for (const auto& callback : everySync) { | |
| callback(localEpoch); | |
| } | |
| return true; | |
| } else { | |
| if (now - lastSendMS > resyncIntervalMS) { | |
| if (lastSyncMS < lastSendMS) { | |
| Serial.println(F("[MicroNTP] Missing response for previous NTP request")); | |
| } | |
| sendNTPpacket(); | |
| } | |
| return false; | |
| } | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment