test code for our MBED board

Dependencies:   mbed lwip

Revision:
1:6877bb99aa17
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SNTPClient.cpp	Wed May 04 08:30:52 2011 +0000
@@ -0,0 +1,802 @@
+/*
+ * 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"
+#include "useful.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);
+    lprintf("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
\ No newline at end of file