Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
lwip/SNTPClient/SNTPClient.cpp
- Committer:
- geiineuville
- Date:
- 2011-09-02
- Revision:
- 0:441400ffd086
File content as of revision 0:441400ffd086:
/*
* SNTPClient.cpp
* SNTP (Simple NTP) client
* Written by iva2k
*
* Wrapper around LWIP/sntp for MBED, with DST rules.
* This implementation relies on:
* 1. LWIP (http://www.sics.se/~adam/lwip/) adopted for MBED
* http://mbed.org/projects/cookbook/svn/EMAC/lwip/trunk
* 2. LWIP's contributed SNTP client (sntp.c and sntp.h) from
* http://cvs.savannah.gnu.org/viewvc/contrib/apps/sntp/?root=lwip
* (used version 1.8)
*
* Changes needed in LWIP/sntp:
* - pointer typecast (line 594) - fixes mbed compiler problem
* - #include "sntp.h" moved after lwip/opt.h et.al - for re-defines.
* - pbuf_free(p) in sntp_send_request() (line 602) - fixes memory leak BUG.
* - changing sntp_dns_found() to delayed sntp_try_next_server() - to unblock if network is disconnected.
*
* Changes in MBED's LWIP:
* - modified lwipopts.h (this file is automatically included into sntp.c)
*
* Requirements:
* + direct RTC update from receiving the NTP time packet
* + optionally support more than one NTP server
* + the IP address of the NTP server(s) stored in a local file
* + timeout error recovery should the NTP server(s) not be available. No RTC update should the NTP access fail
* + allow for a UTC offset, also stored in the local file
* + DST correction
* + use DNS for NTP servers IP address resolution
* + periodic updates at specified time intervals
*
* TODO:
* . DST correction
* - record metrics (count of updates, failed tries?, correction more than epsilon, etc.?)
*/
#include "mbed.h"
#include "lwip/opt.h"
#include "SNTPClient.h"
#define SNTP_EPSILON 10 // time difference noticed as big update (in seconds).
//#define SNTP_DST_TESTS // Define this to run DST algorithms tests
typedef struct DST_ZONE_DESCR {
tDST_ZONE zone;
const char *name;
int gmt;
int dst;
int hr1;
int wk1;
int wday1;
int mon1;
int hr2;
int wk2;
int wday2;
int mon2;
pFncDstCalc fnc;
} tDST_ZONE_DESR;
static tDST_ZONE_DESR gSntpDstZones[] = {
#define _(z, fnc, gmt, dst, hr1,wk1,wday1,mon1, hr2,wk2,wday2,mon2) \
{ z, #z, gmt, dst, hr1,wk1,wday1,mon1, hr2,wk2,wday2,mon2, fnc },
#include "DstZones.h"
};
#define DST_ZONE_DESCR_CNT (sizeof(gSntpDstZones)/sizeof(gSntpDstZones[0]))
const char *SNTPDstZoneName(tDST_ZONE zone) {
if (zone >= DST_ZONE_DESCR_CNT)
return "";
return gSntpDstZones[zone].name;
}
#ifdef __cplusplus
extern "C" {
#endif
tDST_ZONE gSntpDstZone = DST_NONE; // DST zone - rule selector
unsigned int gSntpRecvTimeout_s = 3; // 3 sec; SNTP_RECV_TIMEOUT
unsigned int gSntpUpdateDelay_s = 3600; // 1 hour; SNTP_UPDATE_DELAY
signed int gSntpTimezone = 0*3600; // seconds from UTC to local time
signed int gSntpDST = 0*3600; // seconds from UTC to local time
bool gSntpRtcUtc = false; // true to keep RTC in UTC, false to keep in local time
unsigned int gSntpUpdates = 0; // Track number of all clock NTP updates
unsigned int gSntpUpdatesBig = 0; // Track number of significant clock NTP updates
bool gSntpRunning = false; // true if SNTP service is running
Timeout gSntpDelay; // For async calls.
Ticker gSntpTicker; // There is no RTC interrupt on MBED (yet). Use (more wastefull) timer.
time_t gSntpRtcTCR = 0; // Timer Capture Register equivalent (in RTC time - either local or UTC depending on gSntpRtcUtc)
signed int gSntpRtcTCRDST = 0; // New DST value for when RTC crosses gSntpRtcTCR
static void SNTPSetDSTEx(unsigned int dst, bool adjust_clock) {
if (adjust_clock && !gSntpRtcUtc && gSntpDST != dst) {
time_t seconds = time(NULL);
seconds -= gSntpDST; // Convert from old local time
seconds += dst; // Convert to new local time
set_time(seconds);
if (gSntpRtcTCR) {
// Adjust our alarm clock
gSntpRtcTCR -= gSntpDST;
gSntpRtcTCR += dst;
}
}
gSntpDST = dst;
}
#if 0
// Custom DST zone function example
// USA (since 2007)
// Calculate start or stop DST point for given year based on rules
// tz - Timezone
// year - current year. DST changes well away from year end, so does not matter what timezone.
// start - DST_START for start, DST_STOP for stop
// Returns DST point (in local time). DST_STOP time is in DST
static tDstPoint _sntp_dst_calc_custom(int tz, int year, tDST_START start) {
tDstPoint ret;
struct tm t;
// USA: 3600; 2, Second SUN, March; 2, First SUN, November
int dst = 3600; // DST shift (seconds)
int hour; // Hour of the change
int N; // Week in the month
int wday; // Day of the week
int month;
if (start == DST_START) {
hour = 2; // (0-23) Hour of the change
N = 2-1; // 2nd in the month
wday = 0; // Sunday
month = 2; // March
} else { // DST_STOP
hour = 2; // (0-23) Hour of the change
N = 1-1; // 1st in the month
wday = 0; // Sunday
month = 10; // November
}
t.tm_sec = 0; // 0-59
t.tm_min = 0; // 0-59
t.tm_hour = hour; // 0-23
t.tm_year = year - 1900; //
t.tm_mon = month; // 0-11
t.tm_mday = 1+N*7; // 1-31, first possible date for Nth given weekday
mktime(&t); // Calculate tm_wday
t.tm_mday += (wday-t.tm_wday+14)%7; // Shift to wday
ret.t = mktime(&t);
ret.dst = (start == DST_START) ? dst : 0;
ret.dstshift = dst;
return ret;
}
#endif
// Calculate start or stop DST point for given year based on rules for the DST zone
// tz - Timezone
// zone - DST zone
// year - current year. DST changes well away from year end, so does not matter timezone.
// start - DST_START for start, DST_STOP for stop
// Returns DST point (in standard local time). DST_STOP time is in NOT IN DST
static tDstPoint _sntp_dst_calc(int tz, tDST_ZONE zone, int year, tDST_START start) {
tDstPoint ret;
ret.t = 0;
ret.dst = 0;
ret.dstshift = 0;
if (zone == DST_NONE || zone >= DST_ZONE_DESCR_CNT)
return ret;
tDST_ZONE_DESR *descr = &gSntpDstZones[zone];
if (descr->fnc) {
// Use custom function
ret = descr->fnc(tz, year, start);
} else {
// Use rules
struct tm t;
t.tm_sec = 0; // 0-59
t.tm_min = 0; // 0-59
t.tm_hour = (start == DST_START) ? descr->hr1 : descr->hr2;
t.tm_year = year - 1900; //
t.tm_mon = (start == DST_START) ? descr->mon1 : descr->mon2; // 0-11
int wk =((start == DST_START) ? descr->wk1 : descr->wk2) -1;
int wday = (start == DST_START) ? descr->wday1: descr->wday2;
if (wk < 0) {
// For "Last in the month" - we go to next month, then move back by one week
t.tm_mon += 1; // 0-11
if (t.tm_mon > 11) {
t.tm_mon -= 12;
t.tm_year += 1;
}
t.tm_mday = 1+0*7; // 1-31, first possible date for Nth given weekday
} else {
t.tm_mday = 1+wk*7; // 1-31, first possible date for Nth given weekday
}
mktime(&t); // Calculate tm_wday
t.tm_mday += (wday-t.tm_wday+14)%7; // Shift to wday
ret.t = mktime(&t);
if (wk < 0) {
ret.t -= 7 * 24 * 3600;
}
if (descr->gmt) {
ret.t += tz;
}
ret.dst = (start == DST_START) ? descr->dst : 0;
ret.dstshift = descr->dst;
}
if (start == DST_STOP) {
ret.t -= ret.dstshift;
// this correction is due to the fact that rules are given in local time with DST,
// but calculations are made in standard local time (without DST adjustment)
}
return ret;
}
// Calculate desired DST point relative to now based on rules for the DST zone
// tz - timezone
// zone - DST zone
// now - current time (standard local time = excluding DST).
// index - 0 for immediate next, +1 for second next, -1 for immediate previous
// Returns DST point (in standard local time)
static tDstPoint _sntp_dst_point(int tz, tDST_ZONE zone, time_t now, int index) {
tDstPoint ret;
int year;
tDST_START type;
struct tm *pt = localtime(&now);
ret.t = 0;
ret.dst = 0;
ret.dstshift = 0;
if (zone == DST_NONE || zone >= DST_ZONE_DESCR_CNT)
return ret;
// Algorithm
// 1. Determine where now is in respect to current year DST points (find for index=0)
// 2. Use index to shift year and type
// 3. return the point
// This algorithm relies on DST start date being before DST stop date in the year.
year = pt->tm_year + 1900;
type = DST_START;
ret = _sntp_dst_calc(tz, zone, year, type);
if (now > ret.t) {
type = DST_STOP;
ret = _sntp_dst_calc(tz, zone, year, type);
if (now > ret.t) {
// It is next year's start point
type = DST_START;
year += 1;
}
}
// Now year and type are right for index=0
// Calculate where index points to - shift year and type
int norm = (index > 0) ? (int)(index/2) : (int)((index-1)/2);
year += norm;
index -= norm*2; // Now index is (0,1)
if (index) {
// Flip the type
type = (type == DST_START) ? DST_STOP : DST_START;
}
ret = _sntp_dst_calc(tz, zone, year, type);
return ret;
// struct tm t;
// t.tm_sec = 0; // 0-59
// t.tm_min = 0; // 0-59
// t.tm_hour = 0; // 0-23
// t.tm_mday = 0; // 1-31
// t.tm_mon = 0; // 0-11
// t.tm_year = 2005-1900; // year since 1900
// t.tm_wday = 0; // 0-6 Day of week (Sunday = 0)
// t.tm_yday = 0; // Day of year (0 - 365)
// t.tm_isdst = -1; // Nonzero = Daylight saving time
}
#if defined(LWIP_DEBUG)
// Print DST dates report
static void _sntp_dst_dates(int tz, tDST_ZONE zone, int year) {
char _buffer[64];
tDstPoint r;
printf("\r\nDST DATES for zone %d/%s:\r\n", (int)zone, SNTPDstZoneName(zone));
r = _sntp_dst_calc(tz, zone, year, DST_START);
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&r.t));
printf(" %d DST BEGINS (+%04d sec) %s\r\n", year, r.dst, _buffer);
r = _sntp_dst_calc(tz, zone, year, DST_STOP);
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&r.t));
printf(" %d DST ENDS (+%04d sec) %s\r\n", year, r.dst, _buffer);
}
#endif
#ifdef SNTP_DST_TESTS
static void test_sntp_dst_calc(int tz, tDST_ZONE zone) {
char _buffer[64];
tDstPoint r;
int year;
printf("\r\nTEST: _sntp_dst_calc(tz=%d, zone=%d/%s)\r\n", tz, (int)zone, SNTPDstZoneName(zone));
for (year = 1999; year < 2016; year++) {
r = _sntp_dst_calc(tz, zone, year, DST_START);
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&r.t));
printf(" (0,%d,start) %s %d\r\n", year, _buffer, r.dst);
r = _sntp_dst_calc(tz, zone, year, DST_STOP);
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&r.t));
printf(" (0,%d,stop ) %s %d\r\n", year, _buffer, r.dst);
}
}
static void test_sntp_dst_point(int index) {
char _buffer[64];
struct tm t;
tDstPoint r;
int tz=gSntpTimezone;
tDST_ZONE zone=gSntpDstZone;
int year = 2009, day, month;
int norm = (index > 0) ? (int)(index/2) : (int)((index-1)/2);
printf("\r\nTEST: _sntp_dst_point(%d) norm=%d\r\n", index, norm);
t.tm_sec = 0; // 0-59
t.tm_min = 0; // 0-59
t.tm_hour = 9; // 0-23
t.tm_year = year-1900;
day = 1;
for (month = 0; month < 2; month++) {
t.tm_mon = month;
t.tm_mday = day;
t.tm_year = year-1900;
r = _sntp_dst_point(tz, zone, mktime(&t), index);
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&r.t));
printf(" (0,%02d/%02d/%d,%d) %s %d\r\n", month+1, day, year, index, _buffer, r.dst);
}
for (day = 1; day < 32; day++) {
t.tm_mon = month;
t.tm_mday = day;
t.tm_year = year-1900;
r = _sntp_dst_point(tz, zone, mktime(&t), index);
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&r.t));
printf(" (0,%02d/%02d/%d,%d) %s %d\r\n", month+1, day, year, index, _buffer, r.dst);
}
day = 30;
for (; month < 10; month++) {
t.tm_mon = month;
t.tm_mday = day;
t.tm_year = year-1900;
r = _sntp_dst_point(tz, zone, mktime(&t), index);
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&r.t));
printf(" (0,%02d/%02d/%d,%d) %s %d\r\n", month+1, day, year, index, _buffer, r.dst);
}
for (day = 1; day < 32; day++) {
t.tm_mon = month;
t.tm_mday = day;
t.tm_year = year-1900;
r = _sntp_dst_point(tz, zone, mktime(&t), index);
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&r.t));
printf(" (0,%02d/%02d/%d,%d) %s %d\r\n", month+1, day, year, index, _buffer, r.dst);
}
}
#endif // SNTP_DST_TESTS
// Set current DST
static void _sntp_dst_now(void) {
time_t now = time(NULL);
// Convert to standart local time (no DST)
now = gSntpRtcUtc ? (now + gSntpTimezone) : (now - gSntpDST);
// Check DST setting for now
tDstPoint dst = _sntp_dst_point(gSntpTimezone, gSntpDstZone, now, -1);
#ifdef LWIP_DEBUG
char _buffer[64];
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&dst.t));
LWIP_DEBUGF(SNTP_DEBUG_STATE, (
"DEBUG: _sntp_dst_now(%d) based on last DST change on (%d) %s to (+%04d sec)\r\n",
now, dst.t, _buffer, dst.dst));
#endif
// Change RTC
SNTPSetDSTEx(dst.dst, true);
}
// Plan for next DST change
static void _sntp_dst_schedule(void) {
time_t now = time(NULL);
// Convert to standart local time (no DST)
now = gSntpRtcUtc ? (now + gSntpTimezone) : (now - gSntpDST);
// Check next DST change point
tDstPoint dst = _sntp_dst_point(gSntpTimezone, gSntpDstZone, now, 0);
if (dst.t) {
#ifdef LWIP_DEBUG
char _buffer[64];
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&dst.t));
#endif
// Set our alarm clock
// Convert from standard local to UTC time or local time
dst.t = gSntpRtcUtc ? (dst.t - gSntpTimezone) : (dst.t + gSntpDST);
#ifdef LWIP_DEBUG
if (time(NULL) > dst.t) {
LWIP_DEBUGF(SNTP_DEBUG_STATE, (
"DEBUG: _sntp_dst_schedule() ASSERTION FAILED !(%d<%d) trying to schedule for the past: %s (+%04d sec)\r\n",
time(NULL), dst.t, _buffer, dst.dst));
} else {
LWIP_DEBUGF(SNTP_DEBUG_STATE, (
"DEBUG: _sntp_dst_schedule() scheduled in %d seconds, set for %s (+%04d sec)\r\n",
dst.t-time(NULL), _buffer, dst.dst));
}
#endif
}
gSntpRtcTCR = dst.t;
gSntpRtcTCRDST = dst.dst;
}
// RTC ISR - called upon each RTC tick
static void _sntp_isr(void) {
time_t seconds = time(NULL);
if (gSntpRtcTCR && seconds > gSntpRtcTCR ) {
// DST change has arrived
gSntpRtcTCR = 0;
// Disable ISR and avoid extra calcs in SNTPSetDSTEx()
//if (gSntpRtcTCRDST != gSntpDST) {
// Change to/from DST
SNTPSetDSTEx(gSntpRtcTCRDST, true);
gSntpRtcTCRDST = 0;
// Schedule callback to plan for next DST change (take it out of ISR context)
gSntpDelay.attach(_sntp_dst_schedule, 1.0);
#ifdef LWIP_DEBUG
char _buffer[64];
if (gSntpRtcUtc) {
seconds += gSntpTimezone + gSntpDST; // Convert to local time
}
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&seconds));
LWIP_DEBUGF(SNTP_DEBUG_STATE, (
"DEBUG: _sntp_isr() changed DST. datetime=%s\r\n", _buffer));
#endif
//}
}
}
// NTP Callback - timestamp from NTP server arrives here
void SntpClientSet(time_t seconds) {
time_t old_seconds = time(NULL);
if (!gSntpRtcUtc) {
seconds += gSntpTimezone + gSntpDST; // Convert to local time
}
set_time(seconds);
// Special tasks for the very first NTP updates
if (!gSntpUpdates) {
#if defined(LWIP_DEBUG)
// Report DST dates for the zone
struct tm *pt = localtime(&seconds);
_sntp_dst_dates(gSntpTimezone, gSntpDstZone, pt->tm_year+1900);
#endif
// DST scheduler
_sntp_dst_now();
_sntp_dst_schedule();
// Enable RTC ISR
gSntpTicker.attach(_sntp_isr, 1.0);
}
if (gSntpUpdates && abs((int)(old_seconds - seconds)) > SNTP_EPSILON) {
printf("SNTP settime() corrected big difference: (%d) seconds, more than %d. Big updates count=%d.\r\n",
old_seconds-seconds, SNTP_EPSILON, gSntpUpdatesBig);
gSntpUpdatesBig ++;
}
gSntpUpdates++;
if (1) {
char _buffer[64];
if (gSntpRtcUtc) {
seconds += gSntpTimezone + gSntpDST; // Convert to local time
}
strftime(_buffer, sizeof(_buffer), "%A %m/%d/%Y %H:%M:%S", localtime(&seconds));
printf("SNTP settime() #%d seconds=%d TZ=%0.1f datetime=%s\r\n",
gSntpUpdates, seconds, gSntpTimezone/3600.0, _buffer);
}
}
#ifdef __cplusplus
};
#endif
#ifdef __cplusplus
extern "C" {
#endif
#include "lwip/def.h"
#include "lwip/pbuf.h"
#include "lwip/sys.h"
#include "lwip/stats.h"
#include "netif/etharp.h"
#include "string.h"
#ifdef __cplusplus
};
#endif
// Accomodating sntp timeout functions
#if NO_SYS
#include "sntp.h"
extern void sntp_request(void *arg); // this is dirty hack around "static" function. Some linkers may revolt!
extern void sntp_try_next_server(void *arg);
extern char* sntp_server_addresses[];
extern u8_t sntp_current_server;
extern u8_t sntp_num_servers;
static void (*sntp_addresses_free)(void*) = NULL;
static Timeout _sntp_timer1;
static Timeout _sntp_timer2;
void sntp_sys_timeout(u32_t timeout_s, void (*func)(void *arg), void *arg) {
// all we really need to track is only 2 functions: sntp_request, sntp_try_next_server
Timeout *t = NULL;
if (func == &sntp_request) {
t = &_sntp_timer1;
LWIP_DEBUGF(SNTP_DEBUG_STATE, ("DEBUG: IN sntp_sys_timeout(), func=sntp_request\r\n"));
} else if (func == &sntp_try_next_server) {
t = &_sntp_timer2;
LWIP_DEBUGF(SNTP_DEBUG_STATE, ("DEBUG: IN sntp_sys_timeout(), func=sntp_try_next_server\r\n"));
}
if (t) {
t->detach();
t->attach((void(*)(void))func, 1.0*timeout_s);
// Another shortcut - we have no arg to pass, so just typecast the func.
}
}
void sntp_sys_untimeout(void (*func)(void *arg), void *arg) {
Timeout *t = NULL;
if (func == &sntp_request) {
t = &_sntp_timer1;
LWIP_DEBUGF(SNTP_DEBUG_STATE, ("DEBUG: IN sntp_sys_untimeout(), func=sntp_request\r\n"));
} else if (func == &sntp_try_next_server) {
t = &_sntp_timer2;
LWIP_DEBUGF(SNTP_DEBUG_STATE, ("DEBUG: IN sntp_sys_untimeout(), func=sntp_try_next_server\r\n"));
}
if (t) {
t->detach();
}
}
#else // NO_SYS
#error "I don't know how to compile LWIP/SNTP with NO_SYS=0"
#endif // NO_SYS
// Can be called when already running
void SNTPSetDstZone(tDST_ZONE zone) {
if (zone >= DST_ZONE_DESCR_CNT)
return; // ERR_INVALID_ARG
gSntpDstZone = zone;
if (gSntpRunning) {
// DST scheduler
_sntp_dst_now();
_sntp_dst_schedule();
}
}
void SNTPSetRecvTimeout(unsigned int val_s) { gSntpRecvTimeout_s = val_s; }
void SNTPSetUpdateDelay(unsigned int val_s) { gSntpUpdateDelay_s = val_s; }
void SNTPSetTimezone(float hours_from_utc, bool adjust_clock) {
if (adjust_clock && !gSntpRtcUtc) {
time_t seconds = time(NULL);
seconds -= gSntpTimezone; // Convert from old local time
seconds += hours_from_utc * 3600; // Convert to new local time
set_time(seconds);
if (gSntpRtcTCR) {
// Adjust our alarm clock
gSntpRtcTCR -= gSntpTimezone;
gSntpRtcTCR += hours_from_utc * 3600;
}
}
gSntpTimezone = hours_from_utc * 3600;
}
void SNTPSetDST(float hours_from_utc, bool adjust_clock) {
SNTPSetDSTEx(hours_from_utc * 3600, adjust_clock);
}
static int sntp_num_servers_alloc = 0;
static void _SNTPClrAddresses(void) {
if (!sntp_num_servers_alloc) sntp_num_servers_alloc = sntp_num_servers;
// Here we save the original size of the sntp_server_addresses[] array.
if (sntp_addresses_free) {
for (int i=0; i<sntp_num_servers; i++) {
sntp_addresses_free(sntp_server_addresses[i]);
}
}
sntp_current_server = 0;
sntp_num_servers = 0;
sntp_addresses_free = NULL;
}
static int _SNTPAddAddress(const char* server_address) {
if (sntp_num_servers+1 > sntp_num_servers_alloc)
return -1;
sntp_server_addresses[sntp_num_servers] = (char*)malloc(strlen(server_address)+1);
if (sntp_server_addresses[sntp_num_servers] == NULL) return -1; // Out of memory
strcpy(sntp_server_addresses[sntp_num_servers], server_address);
sntp_num_servers++;
return 0;
}
// Override default servers list.
// For no-copy, pass pointer to free() in p_free.
// Returns: actual number of servers set (limited by allocated buffer size)
// WARNING! There is no interlock to ensure that SNTP service does not read its data while we are updating it.
// This function is intended to be called only before SNTPClientInit().
int SNTPSetAddresses(const char* server_addresses[], int count, void (*p_free)(void*)) {
// In order to use sntp.c as-is, we hack into its static variables.
// Not all compilers/linkers will support that.
_SNTPClrAddresses();
if (count > sntp_num_servers_alloc)
count = sntp_num_servers_alloc;
for (int i=0; i<count; i++) {
if (p_free) {
sntp_server_addresses[i] = (char *)server_addresses[i];
} else {
_SNTPAddAddress(server_addresses[i]);
}
}
sntp_num_servers = count;
sntp_current_server = 0;
sntp_addresses_free = p_free ? p_free : &free;
return count;
}
// Trim whitespace/CRLFs from both ends of a given string
char * str_cleanup(char *in) {
char * out = in;
// Trim leading spaces and CR/LF
while (*out == ' ' || *out == '\t' || *out == '\r' || *out == '\n')
out ++;
// Trim trailing spaces and CR/LF
int len = strlen(out)-1;
while (out[len] == ' ' || out[len] == '\t' || out[len] == '\r' || out[len] == '\n') {
out[len] = '\0';
len--;
}
return out;
}
#ifndef CRLF
#define CRLF "\r\n"
#endif
void SNTPWriteIniFile(FILE * f) {
fprintf(f, "# SNTP Configuration file" CRLF);
fprintf(f, CRLF "[Servers]" CRLF);
for (int i=0; i<sntp_num_servers; i++) {
fprintf(f, "%s" CRLF, sntp_server_addresses[i]);
}
fprintf(f, CRLF "[Global]" CRLF);
fprintf(f, "RtcUtc=%d" CRLF, (int)gSntpRtcUtc);
fprintf(f, "Timezone=%0.1f" CRLF, gSntpTimezone / 3600.0);
fprintf(f, "DstZone=%d" CRLF, gSntpDstZone);
fprintf(f, "# %s" CRLF, SNTPDstZoneName(gSntpDstZone));
fprintf(f, "UpdateDelay=%d" CRLF, gSntpUpdateDelay_s);
fprintf(f, "RecvTimeout=%d" CRLF, gSntpRecvTimeout_s);
fprintf(f, CRLF "##END" CRLF);
}
// Simple ini file parser for SNTP configuration (Case-sensitive!)
// This function is intended to be called only before SNTPClientInit().
// Returns: 0 if OK, errno otherwise
enum {
SECT_NONE,
SECT_SERVERS,
SECT_GLOBAL,
};
int SNTPReadIniFile(const char* filename) {
FILE *f;
char buf[512];
bool addresses_cleared = false;
f = fopen(filename, "r");
if (!f)
return -1; // errno not used?
char *buf1, *buf2;
int section=SECT_NONE;
int line = 0;
while (fgets(buf, sizeof(buf)/sizeof(buf[0]), f)) {
line++;
buf1 = str_cleanup(buf);
if (*buf1 == '#' || *buf1 == '\0')
continue; // Comment line or empty line - skip
if (*buf1 == '[') {
// New section
if (0 == strncmp(buf1,"[Servers]", sizeof("[Servers]")-1)) {
section=SECT_SERVERS;
if (!addresses_cleared) {
// Clear addresses only once.
_SNTPClrAddresses();
addresses_cleared = true;
}
} else if (0 == strncmp(buf1,"[Global]", sizeof("[Global]")-1)) {
section=SECT_GLOBAL;
} else {
section=SECT_NONE;
fprintf(stderr, "File \"%s\", line %d - section \"%s\" is not understood.\r\n", filename, line, buf1);
}
} else {
// Section values
switch (section) {
case SECT_SERVERS:
if (_SNTPAddAddress(buf1)) {
fprintf(stderr, "File \"%s\", line %d - cannot add server \"%s\" - exceeded allocated slots.\r\n", filename, line, buf1);
}
break;
case SECT_GLOBAL:
buf2 = strchr(buf1, '=');
if (buf2) {
*buf2++ = '\0'; // Now buf1 has variable name, buf2 has value
buf2 = str_cleanup(buf2);
if (0 == strncmp(buf1, "Timezone", sizeof("Timezone")-1)) {
gSntpTimezone = strtod(buf2, &buf2) * 3600;
} else if (0 == strncmp(buf1, "UpdateDelay", sizeof("UpdateDelay")-1)) {
gSntpUpdateDelay_s = strtoul(buf2, &buf2, 10);
} else if (0 == strncmp(buf1, "RecvTimeout", sizeof("RecvTimeout")-1)) {
gSntpRecvTimeout_s = strtoul(buf2, &buf2, 10);
} else if (0 == strncmp(buf1, "RtcUtc", sizeof("RtcUtc")-1)) {
gSntpRtcUtc = (bool)strtol(buf2, &buf2, 10);
} else if (0 == strncmp(buf1, "DstZone", sizeof("DstZone")-1)) {
// FIXME: It would be nice to allow human-readable string here.
gSntpDstZone = (tDST_ZONE)strtol(buf2, &buf2, 10);
} else {
fprintf(stderr, "File \"%s\", line %d - unrecognized variable \"%s\" in section [Global]\r\n", filename, line, buf1);
}
} else {
fprintf(stderr, "File \"%s\", line %d - unrecognized statement in section [Global]: %s\r\n", filename, line, buf1);
}
break;
default:
fprintf(stderr, "File \"%s\", line %d - unrecognized statement / no section: %s\r\n", filename, line, buf1);
}
}
}
fclose(f);
printf("SNTP configuration read from file \"%s\", %d lines.\r\n", filename, line);
return 0;
}
// Start the SNTP client
void SNTPClientInit(void) {
#ifdef SNTP_DST_TESTS
// Test our DST algorithms
test_sntp_dst_calc(gSntpTimezone, gSntpDstZone);
test_sntp_dst_point(-2);
test_sntp_dst_point(-1);
test_sntp_dst_point(0);
test_sntp_dst_point(1);
test_sntp_dst_point(2);
#endif
// SNTPv4 RFC 4330 enforces a minimum update time of 15 seconds
if (gSntpUpdateDelay_s < 15) {
gSntpUpdateDelay_s = 15;
}
gSntpRunning = true;
// Just call this to start SNTP client:
sntp_init();
// // Enable RTC ISR
// gSntpTicker.attach(_sntp_isr, 1);
// We do it from first NTP response
}
// Use instead of system time()
// Returns local time
time_t SNTPTime(void) {
time_t seconds = time(NULL);
if (gSntpRtcUtc) {
seconds += gSntpTimezone + gSntpDST; // Convert to local time
}
return seconds;
}
// Use instead of system set_time()
// seconds - local time
void SNTPSetTime(time_t seconds) {
if (gSntpRtcUtc) {
seconds -= gSntpTimezone + gSntpDST; // Convert from local time
}
set_time(seconds);
}
// Use instead of system time()
// Returns UTC time
time_t SNTPTimeUTC(void) {
time_t seconds = time(NULL);
if (!gSntpRtcUtc) {
seconds -= gSntpTimezone + gSntpDST; // Convert from local time
}
return seconds;
}
// Use instead of system set_time()
// seconds - UTC time
void SNTPSetTimeUTC(time_t seconds) {
if (!gSntpRtcUtc) {
seconds += gSntpTimezone + gSntpDST; // Convert to local time
}
set_time(seconds);
}
//END