Files
klubhaus-doorbell/libraries/NTP/NTP.cpp
2026-02-16 19:05:12 -08:00

289 lines
6.6 KiB
C++
Executable File

/**
* NTP library for Arduino framework
* The MIT License (MIT)
* (c) 2022 sstaub
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "NTP.h"
NTP::NTP(UDP& udp) {
this->udp = &udp;
}
NTP::~NTP() {
stop();
}
void NTP::begin(const char* server) {
this->server = server;
init();
}
void NTP::begin(IPAddress serverIP) {
this->serverIP = serverIP;
init();
}
void NTP::init() {
memset(ntpRequest, 0, NTP_PACKET_SIZE);
ntpRequest[0] = 0b11100011; // LI, Version, Mode
ntpRequest[1] = 0; // Stratum, or type of clock
ntpRequest[2] = 6; // Polling Interval
ntpRequest[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
ntpRequest[12] = 49;
ntpRequest[13] = 0x4E;
ntpRequest[14] = 49;
ntpRequest[15] = 52;
udp->begin(NTP_PORT);
ntpUpdate();
if (dstZone) {
timezoneOffset = dstEnd.tzOffset * SECS_PER_MINUTES;
dstOffset = (dstStart.tzOffset - dstEnd.tzOffset) * SECS_PER_MINUTES;
currentTime();
beginDST();
}
}
void NTP::stop() {
udp->stop();
}
bool NTP::update() {
if ((millis() - lastUpdate >= interval) || lastUpdate == 0) {
return ntpUpdate();
}
return false;
}
bool NTP::ntpUpdate() {
if (server == nullptr) udp->beginPacket(serverIP, NTP_PORT);
else udp->beginPacket(server, NTP_PORT);
udp->write(ntpRequest, NTP_PACKET_SIZE);
udp->endPacket();
uint8_t timeout = 0;
uint8_t size = 0;
do {
delay (10);
size = udp->parsePacket();
if (timeout > 100) return false;
timeout++;
} while (size != 48);
lastUpdate = millis() - (10 * (timeout + 1));
udp->read(ntpQuery, NTP_PACKET_SIZE);
#ifdef __AVR__
unsigned long highWord = word(ntpQuery[40], ntpQuery[41]);
unsigned long lowWord = word(ntpQuery[42], ntpQuery[43]);
timestamp = highWord << 16 | lowWord;
if (timestamp != 0) {
ntpTime = timestamp;
utcTime = ntpTime - NTP_OFFSET;
}
else return false;
#else
timestamp = ntpQuery[40] << 24 | ntpQuery[41] << 16 | ntpQuery[42] << 8 | ntpQuery[43];
if (timestamp != 0) {
ntpTime = timestamp;
utcTime = ntpTime - SEVENTYYEARS;
}
else return false;
#endif
return true;
}
void NTP::updateInterval(uint32_t interval) {
this->interval = interval;
}
void NTP::ruleDST(const char* tzName, int8_t week, int8_t wday, int8_t month, int8_t hour, int tzOffset) {
strcpy(dstStart.tzName, tzName);
dstStart.week = week;
dstStart.wday = wday;
dstStart.month = month;
dstStart.hour = hour;
dstStart.tzOffset = tzOffset;
}
const char* NTP::ruleDST() {
if(dstZone) {
return ctime(&dstTime);
}
else return RULE_DST_MESSAGE;
}
void NTP::ruleSTD(const char* tzName, int8_t week, int8_t wday, int8_t month, int8_t hour, int tzOffset) {
strcpy(dstEnd.tzName, tzName);
dstEnd.week = week;
dstEnd.wday = wday;
dstEnd.month = month;
dstEnd.hour = hour;
dstEnd.tzOffset = tzOffset;
}
const char* NTP::ruleSTD() {
if(dstZone) {
return ctime(&stdTime);
}
else return RULE_STD_MESSAGE;
}
const char* NTP::tzName() {
if (dstZone) {
if (summerTime()) return dstStart.tzName;
else return dstEnd.tzName;
}
return GMT_MESSAGE;
}
void NTP::timeZone(int8_t tzHours, int8_t tzMinutes) {
this->tzHours = tzHours;
this->tzMinutes = tzMinutes;
timezoneOffset = tzHours * 3600;
if (tzHours < 0) {
timezoneOffset -= tzMinutes * 60;
}
else {
timezoneOffset += tzMinutes * 60;
}
}
void NTP::isDST(bool dstZone) {
this->dstZone = dstZone;
}
bool NTP::isDST() {
return summerTime();
}
time_t NTP::epoch() {
currentTime();
return utcCurrent;
}
void NTP::currentTime() {
utcCurrent = utcTime + ((millis() - lastUpdate) / 1000);
if (dstZone) {
if (summerTime()) {
local = utcCurrent + dstOffset + timezoneOffset;
current = gmtime(&local);
}
else {
local = utcCurrent + timezoneOffset;
current = gmtime(&local);
}
if ((current->tm_year + 1900) > yearDST) beginDST();
}
else {
local = utcCurrent + timezoneOffset;
current = gmtime(&local);
}
}
int16_t NTP::year() {
currentTime();
return current->tm_year + 1900;
}
int8_t NTP::month() {
currentTime();
return current->tm_mon + 1;
}
int8_t NTP::day() {
currentTime();
return current->tm_mday;
}
int8_t NTP::weekDay() {
currentTime();
return current->tm_wday;
}
int8_t NTP::hours() {
currentTime();
return current->tm_hour;
}
int8_t NTP::minutes() {
currentTime();
return current->tm_min;
}
int8_t NTP::seconds() {
currentTime();
return current->tm_sec;
}
const char* NTP::formattedTime(const char *format) {
currentTime();
memset(timeString, 0, sizeof(timeString));
strftime(timeString, sizeof(timeString), format, current);
return timeString;
}
void NTP::beginDST() {
dstTime = calcDateDST(dstStart, current->tm_year + 1900);
utcDST = dstTime - (dstEnd.tzOffset * SECS_PER_MINUTES);
stdTime = calcDateDST(dstEnd, current->tm_year + 1900);
utcSTD = stdTime - (dstStart.tzOffset * SECS_PER_MINUTES);
yearDST = current->tm_year + 1900;
}
time_t NTP::calcDateDST(struct ruleDST rule, int year) {
uint8_t month = rule.month;
uint8_t week = rule.week;
if (week == 0) {
if (month++ > 11) {
month = 0;
year++;
}
week = 1;
}
struct tm tm;
tm.tm_hour = rule.hour;
tm.tm_min = 0;
tm.tm_sec = 0;
tm.tm_mday = 1;
tm.tm_mon = month;
tm.tm_year = year - 1900;
time_t t = mktime(&tm);
t += ((rule.wday - tm.tm_wday + 7) % 7 + (week - 1) * 7 ) * SECS_PER_DAY;
if (rule.week == 0) t -= 7 * SECS_PER_DAY;
return t;
}
bool NTP::summerTime() {
if ((utcCurrent > utcDST) && (utcCurrent <= utcSTD)) {
return true;
}
else {
return false;
}
}
uint32_t NTP::ntp() {
return ntpTime;
}
uint32_t NTP::utc() {
return utcTime;
}