A time interface class. This class replicates the normal time functions, but goes a couple of steps further. mbed library 82 and prior has a defective gmtime function. Also, this class enables access to setting the time, and adjusting the accuracy of the RTC.

Dependencies:   CalendarPage

Dependents:   CI-data-logger-server WattEye X10Svr SSDP_Server

Files at this revision

API Documentation at this revision

Comitter:
WiredHome
Date:
Tue Nov 21 17:04:12 2017 +0000
Parent:
20:5ca2c94d46b8
Child:
22:72ce9bd14c5e
Commit message:
Updates for OS5 and integrate NTPClient for network time sync.

Changed in this revision

NTPClient.lib Show diff for this revision Revisions of this file
NTPClient/NTPClient.cpp Show annotated file Show diff for this revision Revisions of this file
NTPClient/NTPClient.h Show annotated file Show diff for this revision Revisions of this file
TimeInterface.cpp Show annotated file Show diff for this revision Revisions of this file
TimeInterface.h Show annotated file Show diff for this revision Revisions of this file
--- a/NTPClient.lib	Mon Nov 20 17:09:48 2017 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-http://mbed.org/users/WiredHome/code/NTPClient/#0a9c48cb2b10
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NTPClient/NTPClient.cpp	Tue Nov 21 17:04:12 2017 +0000
@@ -0,0 +1,354 @@
+/* NTPClient.cpp */
+/* Copyright (C) 2012 mbed.org, MIT License
+ *
+ * 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 "mbed.h" //time() and set_time()
+
+#include "EthernetInterface.h"
+#include "UDPSocket.h"
+#include "Socket.h"
+#include "NTPClient.h"
+
+
+//#define DEBUG "NTPc"
+
+#if (defined(DEBUG))
+#include <cstdio>
+#define INFO(x, ...) std::printf("[INF %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
+#define WARN(x, ...) std::printf("[WRN %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
+#define ERR(x, ...)  std::printf("[ERR %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
+static void HexDump(const char * title, void * pT, int count)
+{
+    int i;
+    uint8_t * p = (uint8_t *)pT;
+    char buf[100] = "0000: ";
+
+    if (*title)
+        INFO("%s", title);
+    for (i=0; i<count; ) {
+        sprintf(buf + strlen(buf), "%02X ", *(p+i));
+        if ((++i & 0x0F) == 0x00) {
+            INFO("%s", buf);
+            if (i < count)
+                sprintf(buf, "%04X: ", i);
+            else
+                buf[0] = '\0';
+        }
+    }
+    if (strlen(buf))
+        INFO("%s", buf);
+}
+#else
+//Disable debug
+#define INFO(x, ...)
+#define WARN(x, ...)
+#define ERR(x, ...)
+#define HexDump(a,b,c)
+#endif
+
+
+#define NTP_PORT 123
+#define NTP_CLIENT_PORT 0       //Random port
+#define NTP_TIMESTAMP_DELTA 2208988800ull //Diff btw a UNIX timestamp (Starting Jan, 1st 1970) and a NTP timestamp (Starting Jan, 1st 1900)
+
+#if 0 && MBED_MAJOR_VERSION == 5
+#define htonl(x) ((((x) & 0x000000ffUL) << 24) | \
+                     (((x) & 0x0000ff00UL) <<  8) | \
+                     (((x) & 0x00ff0000UL) >>  8) | \
+                     (((x) & 0xff000000UL) >> 24))
+#define ntohl(x) htonl(x)
+#endif
+
+NTPClient::NTPClient(EthernetInterface * _net) : m_sock()
+{
+    net = _net;
+}
+
+
+NTPResult NTPClient::setTime(const char* host, uint16_t port, uint32_t timeout)
+{
+#ifdef DEBUG
+    time_t ctTime;
+    ctTime = time(NULL);
+    INFO("Time is currently (UTC): %s", ctime(&ctTime));
+#endif
+
+    //
+    // MBED OS 5
+    //
+#if MBED_MAJOR_VERSION == 5
+
+    struct NTPPacket pkt;
+
+    SocketAddress nist;
+    int ret_gethostbyname = net->gethostbyname(host, &nist);
+    INFO("gethostbyname(%s) returned %d", host, ret_gethostbyname);
+    if (ret_gethostbyname < 0) {
+        return NTP_DNS;     // Network error on DNS lookup
+    }
+
+    nist.set_port(port);
+    INFO("set_port(%d)", port);
+
+    time_t tQueryTime = time(NULL);
+    //
+    //Prepare NTP Packet for the query:
+    //
+    pkt.li = 0;             //Leap Indicator : No warning
+    pkt.vn = 4;             //Version Number : 4
+    pkt.mode = 3;           //Client mode
+    pkt.stratum = 0;        //Not relevant here
+    pkt.poll = 0;           //Not significant as well
+    pkt.precision = 0;      //Neither this one is
+    pkt.rootDelay = 0;      //Or this one
+    pkt.rootDispersion = 0; //Or that one
+    pkt.refId = 0;          //...
+    pkt.refTm_s = 0;
+    pkt.origTm_s = 0;
+    pkt.rxTm_s = 0;
+    pkt.txTm_s = NTP_TIMESTAMP_DELTA + tQueryTime;
+    pkt.refTm_f = pkt.origTm_f = pkt.rxTm_f = pkt.txTm_f = 0;
+    
+    INFO("      ctime:         %s", ctime(&tQueryTime));
+
+    //WARN: We are in LE format, network byte order is BE
+    INFO("  pkt.txTm_s         %08X, %u, time to send to server", pkt.txTm_s, pkt.txTm_s);
+    pkt.txTm_s = htonl(pkt.txTm_s);
+    INFO("  pkt.txTm_s         %08X, %u, time to send to server", pkt.txTm_s, pkt.txTm_s);
+    HexDump("sending", &pkt, sizeof(NTPPacket));
+
+    UDPSocket sock;
+    nsapi_error_t ret = sock.open(net);
+    INFO("sock.open(...) returned %d", ret);
+    
+    sock.set_timeout(timeout);
+
+    int ret_send = sock.sendto(nist, (void *)&pkt, sizeof(NTPPacket));
+    INFO("sock.sendto(...) returned %d", ret_send);
+    
+    SocketAddress source;
+    source.set_ip_address(nist.get_ip_address());
+    const int n = sock.recvfrom(&source, (void *)&pkt, sizeof(NTPPacket));
+    uint32_t destTimeStamp = NTP_TIMESTAMP_DELTA + time(NULL);
+    INFO("recvfrom(...) returned %d", n);
+    
+    if (pkt.stratum == 0) { //Kiss of death message : Not good !
+        ERR("Kissed to death!");
+        m_sock.close();
+        return NTP_PRTCL;
+    }
+    
+    HexDump("received", &pkt, sizeof(NTPPacket));
+    
+    //Correct Endianness
+    #if 1
+    pkt.refTm_s = ntohl( pkt.refTm_s );
+    pkt.refTm_f = ntohl( pkt.refTm_f );
+    pkt.origTm_s = ntohl( pkt.origTm_s );
+    pkt.origTm_f = ntohl( pkt.origTm_f );
+    pkt.rxTm_s = ntohl( pkt.rxTm_s );
+    pkt.rxTm_f = ntohl( pkt.rxTm_f );
+    pkt.txTm_s = ntohl( pkt.txTm_s );
+    pkt.txTm_f = ntohl( pkt.txTm_f );
+    #endif
+
+    #ifdef DEBUG
+    const char *ModeList[] = {
+        "reserved", "symmetric active", "symmetric passive", "client",
+        "server", "broadcast", "reserved for NTP ctrl", "reserved for priv use"
+    };
+    INFO("  pkt.li (Leap Ind)  %d", pkt.li);
+    INFO("  pkt.vn (Vers #)    %d", pkt.vn);
+    INFO("  pkt.mode           %d, mode %s", pkt.mode, ModeList[pkt.mode]);
+    INFO("  pkt.stratum        %d, 0=kiss-o'-death, 1=prim, 2=secd", pkt.stratum);
+    INFO("  pkt.poll           %d", pkt.poll);
+    INFO("  pkt.precision      %d", pkt.precision);
+    INFO("  pkt.rootDelay      %d", pkt.rootDelay);
+    INFO("  pkt.rootDispersion %d", pkt.rootDispersion);
+    INFO("  pkt.refId          %08X, %u", pkt.refId, pkt.refId);
+    INFO("  pkt.refTm_s        %08X, %u, ref time (last set)", pkt.refTm_s, pkt.refTm_s);
+    INFO("  pkt.origTm_s       %08X, %u, time sent from client", pkt.origTm_s, pkt.origTm_s);
+    INFO("  pkt.rxTm_s         %08X, %u, time rcvd at server", pkt.rxTm_s, pkt.rxTm_s);
+    INFO("  pkt.txTm_s         %08X, %u, time sent from server", pkt.txTm_s, pkt.txTm_s);
+    INFO("  pkt.refTm_f        %08X, %u, fraction", pkt.refTm_f, pkt.refTm_f);
+    #endif
+    
+    ret = sock.close();
+    INFO("sock.close() returned %d", ret);
+
+    if (n == sizeof(NTPPacket)) {
+        int64_t t;
+        #ifdef DEBUG
+        uint32_t T1, T2, T3, T4;
+        T1 = pkt.origTm_s;
+        T2 = pkt.rxTm_s;
+        T3 = pkt.txTm_s;
+        T4 = destTimeStamp;
+
+        uint32_t d = (T4 - T1) - (T3 - T2);
+        INFO("d = %d = (%d - %d) - (%d - %d)", d, T4,T1,T3,T2);
+        INFO("d = %d = (  %d   )    -     (  %d  )", d, (T4-T1), (T3-T2));
+        t = ((T2 - T1) + (T3 - T4))/2;
+        INFO("t = %lld = ((%d - %d) + (%d - %d)) / 2;", t, T2,T1,T3,T4);
+        INFO("t = %lld = ((   %d   )  +  (   %d   )) / 2", t, (T2-T1), (T3-T4));
+        #endif
+
+        // Modification by David Smart
+        // The setTime function was computing the offset incorrectly as the value was promoted to 64-bit.
+        // The side effect was that a negative offset ended up as a very large positive (e.g. jump from 
+        // 2016 to 2084). This change revises that computation.
+        t = (((int64_t)pkt.rxTm_s - pkt.origTm_s) + ((int64_t)pkt.txTm_s - destTimeStamp))/2;
+        set_time( time(NULL) + t );
+    } else {
+        ERR("bad return from recvfrom() %d", n);
+        if (n < 0) {
+            // Network error
+            return NTP_CONN;
+        } else {
+            // No or partial data returned
+            return NTP_PRTCL;
+        }
+    }
+
+#else   // MBED OS 2
+
+    //Create & bind socket
+    INFO("Binding socket");
+    m_sock.bind(0); //Bind to a random port
+
+    //
+    // MBED OS 2
+    //
+    m_sock.set_blocking(false, timeout); //Set not blocking
+
+    struct NTPPacket pkt;
+
+    //Now ping the server and wait for response
+    INFO("Ping");
+    //Prepare NTP Packet:
+    pkt.li = 0; //Leap Indicator : No warning
+    pkt.vn = 4; //Version Number : 4
+    pkt.mode = 3; //Client mode
+    pkt.stratum = 0; //Not relevant here
+    pkt.poll = 0; //Not significant as well
+    pkt.precision = 0; //Neither this one is
+
+    pkt.rootDelay = 0; //Or this one
+    pkt.rootDispersion = 0; //Or that one
+    pkt.refId = 0; //...
+
+    pkt.refTm_s = 0;
+    pkt.origTm_s = 0;
+    pkt.rxTm_s = 0;
+    pkt.txTm_s = htonl( NTP_TIMESTAMP_DELTA + time(NULL) ); //WARN: We are in LE format, network byte order is BE
+    INFO("pkt.txTm_s = %u", ntohl(pkt.txTm_s) );
+    pkt.refTm_f = pkt.origTm_f = pkt.rxTm_f = pkt.txTm_f = 0;
+
+    HexDump("NTP Post", (uint8_t *)&pkt, sizeof(NTPPacket));
+    
+    Endpoint outEndpoint;
+    INFO("outEndpoint instantiated");
+    if( outEndpoint.set_address(host, port) < 0) {
+        m_sock.close();
+        return NTP_DNS;
+    }
+    INFO("outEndpoint: %s:%d", outEndpoint.get_address(), outEndpoint.get_port());
+    //Set timeout, non-blocking and wait using select
+    int ret = m_sock.sendTo( outEndpoint, (char*)&pkt, sizeof(NTPPacket) );
+    if (ret < 0 ) {
+        ERR("Could not send packet");
+        m_sock.close();
+        return NTP_CONN;
+    }
+
+    //Read response
+    Endpoint inEndpoint;
+    INFO(" inEndpoint instantiated: %s.", inEndpoint.get_address());
+    // Set the inEndpoint address property
+    inEndpoint.set_address(outEndpoint.get_address(), 0);
+    INFO(" inEndpoint: %s", inEndpoint.get_address());
+
+    INFO("Pong");
+    int loopLimit = 20;  // semi-randomly selected so it doesn't hang forever here...
+    do {
+        ret = m_sock.receiveFrom( inEndpoint, (char*)&pkt, sizeof(NTPPacket) );
+        if(ret < 0) {
+            ERR("Could not receive packet");
+            m_sock.close();
+            return NTP_CONN;
+        }
+        INFO(".");
+        loopLimit--;
+    } while( strcmp(outEndpoint.get_address(), inEndpoint.get_address()) != 0 && loopLimit > 0);
+
+    if(ret < (int)sizeof(NTPPacket)) { //TODO: Accept chunks
+        ERR("Receive packet size does not match");
+        m_sock.close();
+        return NTP_PRTCL;
+    }
+
+    if( pkt.stratum == 0) { //Kiss of death message : Not good !
+        ERR("Kissed to death!");
+        m_sock.close();
+        return NTP_PRTCL;
+    }
+
+    HexDump("NTP Info", (uint8_t *)&pkt, sizeof(NTPPacket));
+
+    //Correct Endianness
+    pkt.refTm_s = ntohl( pkt.refTm_s );
+    pkt.refTm_f = ntohl( pkt.refTm_f );
+    pkt.origTm_s = ntohl( pkt.origTm_s );
+    pkt.origTm_f = ntohl( pkt.origTm_f );
+    pkt.rxTm_s = ntohl( pkt.rxTm_s );
+    pkt.rxTm_f = ntohl( pkt.rxTm_f );
+    pkt.txTm_s = ntohl( pkt.txTm_s );
+    pkt.txTm_f = ntohl( pkt.txTm_f );
+
+    //Compute offset, see RFC 4330 p.13
+    uint32_t destTm_s = (NTP_TIMESTAMP_DELTA + time(NULL));
+    INFO("destTm_s = %u", destTm_s);
+    INFO("pkt.txTm_s = %u", pkt.txTm_s );
+    
+    // Modification by David Smart
+    // The setTime function was computing the offset incorrectly as the value was promoted to 64-bit.
+    // The side effect was that a negative offset ended up as a very large positive (e.g. jump from 
+    // 2016 to 2084). This change revises that computation.
+    int64_t offset = ( ((int64_t)pkt.rxTm_s - pkt.origTm_s ) + ((int64_t) pkt.txTm_s - destTm_s ) ) / 2; //Avoid overflow
+    
+    // delay is not needed, this was for diagnostic purposes only.
+    //int64_t delay = ((int64_t) destTm_s - pkt.origTm_s) - ((int64_t) pkt.txTm_s - pkt.rxTm_s);
+    INFO("txTm_s   @%u", pkt.txTm_s);
+    INFO("origTm_s @%u", pkt.origTm_s);
+    INFO("rxTm_s   @%u", pkt.rxTm_s);
+    INFO("destTm_s @%u", destTm_s);
+    INFO("Offset: %lld", offset);
+    //INFO("Delay:  %lld", delay);
+    INFO(" time:    %u", time(NULL));
+    //Set time accordingly
+    set_time( time(NULL) + offset );
+
+    m_sock.close();
+#endif // OS version
+
+    #ifdef DEBUG
+    ctTime = time(NULL);
+    INFO("      ctime:         %s", ctime(&ctTime));
+    #endif
+    return NTP_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NTPClient/NTPClient.h	Tue Nov 21 17:04:12 2017 +0000
@@ -0,0 +1,212 @@
+/* NTPClient.h */
+/* Copyright (C) 2012 mbed.org, MIT License
+ *
+ * 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.
+ *
+ */
+
+/** \file
+NTP Client header file
+*/
+
+#ifndef NTPCLIENT_H_
+#define NTPCLIENT_H_
+
+#include <cstdint>
+
+using std::uint8_t;
+using std::uint16_t;
+using std::uint32_t;
+
+#include "EthernetInterface.h"
+#include "UDPSocket.h"
+
+#define NTP_DEFAULT_PORT 123
+#define NTP_DEFAULT_TIMEOUT 4000
+
+///NTP client results
+enum NTPResult {
+    NTP_OK = 0,     ///<Success
+    NTP_DNS,        ///<Could not resolve name
+    NTP_PRTCL,      ///<Protocol error
+    NTP_TIMEOUT,    ///<Connection timeout
+    NTP_CONN,       ///<Connection error
+};
+
+/** NTP Client to update the mbed's RTC using a remote time server
+*
+*/
+class NTPClient
+{
+public:
+    /**
+    Instantiate the NTP client
+    * @param[in] net is a pointer to the EthernetInterface
+    */
+    NTPClient(EthernetInterface * net = NULL);
+
+    /**Get current time (blocking)
+    Update the time using the server host
+    Blocks until completion
+    @param[in] host NTP server IPv4 address or hostname (will be resolved via DNS)
+    @param[in] port port to use; defaults to 123
+    @param[in] timeout waiting timeout in ms (osWaitForever for blocking function, not recommended)
+    @return 0 on success, NTP error code (<0) on failure
+    */
+    NTPResult setTime(const char* host, uint16_t port = NTP_DEFAULT_PORT, uint32_t timeout = NTP_DEFAULT_TIMEOUT); //Blocking
+
+private:
+    EthernetInterface * net;
+    
+    /// The NTP Packet, as defined in RFC 4330 for Simple NTP
+    ///
+    /// @caution We are in Little Endian locally, and Big Endian at the Network
+    ///         so some transformations are required.
+    ///
+    struct NTPPacket { 
+        /// Mode is a 3-bit number indicating the protocol mode
+        ///
+        /// values
+        ///     - 0 = reserved, 
+        ///     - 1 = symmetric active
+        ///     - 2 = symmetric passive
+        ///     - 3 = client
+        ///     - 4 = server
+        ///     - 5 = broadcast
+        ///     - 6 = reserved for NTP control message
+        ///     - 7 = reserved for private use
+        ///
+        unsigned mode : 3;
+        
+        /// Version Number
+        ///
+        /// The current version number for NTP/SNTP is 4
+        ///
+        unsigned vn : 3;    
+
+        /// Leap Indicator
+        ///
+        /// values
+        ///     - 0 = no warning, 
+        ///     - 1 = last min had 61s, 
+        ///     - 2 = last min had 59s, 
+        ///     - 3 = alarm - clock not in sync
+        ///
+        unsigned li : 2;    
+
+        /// This is indicates the stratum
+        ///
+        /// values
+        ///     - 0 = kiss-o'-death
+        ///     - 1 = primary reference
+        ///     - 2-15 = secondary reference
+        ///     - 16-255 = reserved
+        ///
+        uint8_t stratum;
+        
+        /// Poll interval
+        ///
+        /// This is an exponent of two, where the resulting value is the max
+        /// interval between successive messages in seconds.
+        ///
+        uint8_t poll;
+        
+        /// Precision of the clock
+        ///
+        /// This is an eight-bit signed integer used as an exponent of
+        /// two, where the resulting value is the precision of the system clock
+        /// in seconds.  This field is significant only in server messages, where
+        /// the values range from -6 for mains-frequency clocks to -20 for
+        /// microsecond clocks found in some workstations.
+        ///
+        int8_t precision;
+        //32 bits header
+
+        /// Root Delay
+        /// 
+        /// This is a 32-bit signed fixed-point number indicating the
+        /// total roundtrip delay to the primary reference source, in seconds
+        /// with the fraction point between bits 15 and 16.  Note that this
+        /// variable can take on both positive and negative values, depending on
+        /// the relative time and frequency offsets.  This field is significant
+        /// only in server messages, where the values range from negative values
+        /// of a few milliseconds to positive values of several hundred
+        /// milliseconds.
+        ///
+        int32_t rootDelay;
+        
+        /// Root Dispersion: This is a 32-bit unsigned fixed-point number
+        /// indicating the maximum error due to the clock frequency tolerance, in
+        /// seconds with the fraction point between bits 15 and 16.  This field
+        /// is significant only in server messages, where the values range from
+        /// zero to several hundred milliseconds.
+        ///
+        uint32_t rootDispersion;
+        
+        /// This idenfies a particular clock source
+        ///
+        uint32_t refId;
+
+        /// Reference Timestamp identified when the clock was last set.
+        /// 
+        /// It is a 64-bit value, this is in whole seconds
+        ///
+        uint32_t refTm_s;
+        /// Reference Timestamp identified when the clock was last set.
+        /// 
+        /// It is a 64-bit value, this is in fractions of a second
+        ///
+        uint32_t refTm_f;
+
+        /// Originate Timestamp identified when the request departed the client.
+        /// 
+        /// It is a 64-bit value, this is in whole seconds
+        ///
+        uint32_t origTm_s;
+        /// Originate Timestamp identified when the request departed the client.
+        /// 
+        /// It is a 64-bit value, this is in fractions of a second
+        ///
+        uint32_t origTm_f;
+
+        /// Receive Timestamp identified when the request arrived at the server.
+        /// 
+        /// It is a 64-bit value, this is in whole seconds
+        ///
+        uint32_t rxTm_s;
+        /// Receive Timestamp identified when the request arrived at the server.
+        /// 
+        /// It is a 64-bit value, this is in fractions of a second
+        ///
+        uint32_t rxTm_f;
+
+        /// Transmit Timestamp identified when the request departed the client/server.
+        /// 
+        /// It is a 64-bit value, this is in whole seconds
+        ///
+        uint32_t txTm_s;
+        /// Transmit Timestamp identified when the request departed the client/server.
+        /// 
+        /// It is a 64-bit value, this is in fractions of a second
+        ///
+        uint32_t txTm_f;
+    } __attribute__ ((packed));
+
+    UDPSocket m_sock;
+};
+
+
+#endif /* NTPCLIENT_H_ */
--- a/TimeInterface.cpp	Mon Nov 20 17:09:48 2017 +0000
+++ b/TimeInterface.cpp	Tue Nov 21 17:04:12 2017 +0000
@@ -44,18 +44,23 @@
 
 NTPResult TimeInterface::setTime(const char* host, uint16_t port, uint32_t timeout)
 {
-    NTPClient ntp(m_net);
     NTPResult res;
-    // int16_t tzomin = get_tzo_min();
-    INFO("setTime(%s, %d, %d)\r\n", host, port, timeout);
-    res = ntp.setTime(host, port, timeout);
-    INFO("  ret: %d\r\n", res);
-    if (res == NTP_OK) {
-        // if the time was fetched successfully, then
-        // let's save the time last set with the local tzo applied
-        // and this saves the last time set for later precision
-        // tuning.
-        set_time(std::time(NULL));
+    
+    if (m_net) {
+        NTPClient ntp(m_net);
+        // int16_t tzomin = get_tzo_min();
+        INFO("setTime(%s, %d, %d)\r\n", host, port, timeout);
+        res = ntp.setTime(host, port, timeout);
+        INFO("  ret: %d\r\n", res);
+        if (res == NTP_OK) {
+            // if the time was fetched successfully, then
+            // let's save the time last set with the local tzo applied
+            // and this saves the last time set for later precision
+            // tuning.
+            set_time(std::time(NULL));
+        }
+    } else {
+        res = NTP_CONN;
     }
     return res;
 }
--- a/TimeInterface.h	Mon Nov 20 17:09:48 2017 +0000
+++ b/TimeInterface.h	Tue Nov 21 17:04:12 2017 +0000
@@ -4,7 +4,7 @@
 #include "mbed.h"
 #include <ctime>
 
-#include "NTPClient.h"  // ver 9
+#include "NTPClient.h"
 
 // Special Registers and their usage:
 // GPREG0: 32 bits
@@ -15,10 +15,10 @@
 
 
 extern "C" {
-#include "time.h"
+#include "time.h"       // uses some std::time-functions
 }
 
-/// The tm_ex structure is patterened after the traditional tm struct, however
+/// The tm_ex structure is patterned after the traditional tm struct, however
 /// it adds an element - the time zone offset in minutes. From this, it is then
 /// readily able to create a "local time" instead of simply a UTC time.
 ///
@@ -55,7 +55,7 @@
 ///     but manages the time-zone offset as it does so.
 ///
 /// @code
-/// // TimeInterface Architecture
+/// // TimeInterface Architecture and APIs
 /// //
 /// // +--------+
 /// // | clock  |----> clock_t clock()
@@ -106,7 +106,8 @@
 /// //  | +- strftime(char * ptr, ..., tm_ex *) --> | buffer                   |
 /// //  +----strptime(char * buf, ..., tm_ex *) --- | Www Mmm dd hh:mm:ss yyyy |
 /// //                                              +--------------------------+
-/// //  double difftime(time_t end, time_t)
+/// //      double difftime(time_t end, time_t)
+/// //
 /// @endcode
 ///
 class TimeInterface
@@ -117,6 +118,18 @@
     /// @param[in] net is optional and provides the EthernetInterface which is
     ///             used if you want to sync to an NTP server
     ///
+    /// @code
+    /// EthernetInterface net;
+    /// TimeInterface ntp(&net);
+    /// ...
+    ///     ntp.set_tzo_min(-6 * 60);
+    ///     if (NTP_OK == ntp.setTime("time.nist.gov", 123, 10)) {
+    ///         time_t tNow = ntp.timelocal();
+    ///         printf("time is %s\r\n", ntp.ctime(&tNow));
+    ///     }
+    /// ...
+    /// @endcode
+    ///
     TimeInterface(EthernetInterface *m_net = NULL);
     
     /// Destructor, normally not used, because it is typically kept for the life
@@ -130,10 +143,12 @@
     ///
     /// @code
     /// clock_t tstart, tend;
-    /// tstart = clock();
-    /// // do something long
-    /// tend = clock();
-    /// printf("Elapsed time is %5.3f\r\n", (float)(tend - tstart)/CLOCKS_PER_SEC);
+    /// ...
+    ///     tstart = clock();
+    ///     // do something long
+    ///     tend = clock();
+    ///     printf("Elapsed time is %5.3f\r\n", (float)(tend - tstart)/CLOCKS_PER_SEC);
+    /// ...
     /// @endcode
     ///
     /// @returns elapsed tics.
@@ -147,11 +162,14 @@
     ///
     /// @code
     /// time_t t_ref1, t_ref2, t_ref3;
-    /// t_ref1 = time(NULL); t_ref2 = t.time(); t.time(&t_ref3);
+    /// t_ref1 = time(NULL); 
+    /// t_ref2 = t.time(); 
+    /// (void)t.time(&t_ref3);
     /// @endcode
     ///
-    /// @param[in,out] timer is an optional pointer to a time_t value that will be written. 
-    ///     This pointer is ignored when NULL.
+    /// @param[out] timer is an optional pointer to a time_t value that will 
+    ///             be written with the current time. This pointer is ignored 
+    ///             when NULL.
     /// @returns the UTC time value.
     ///
     time_t time(time_t * timer = NULL);
@@ -160,40 +178,45 @@
     /// to a provided buffer.
     ///
     /// This reads the real time clock and returns the current time, adjusted
-    /// for the local timezone and daylight savings time.
+    /// for the local time zone and daylight savings time.
     ///
     /// @code
     /// time_t t_ref2, t_ref3;
-    /// t_ref2 = t.time(); t.timelocal(&t_ref3);
+    /// t_ref2 = t.time(); 
+    /// t.timelocal(&t_ref3);
     /// @endcode
     ///
-    /// @param[in,out] timer is an optional pointer to a time_t value that will be written. 
-    ///     This pointer is ignored when NULL.
-    /// @returns the LOCAL time value (UTC adjusted for the LOCAL time zone).
+    /// @param[out] timer is an optional pointer to a time_t value that will 
+    ///     be written. This pointer is ignored when NULL.
+    /// @returns the LOCAL time value (UTC adjusted for the LOCAL time zone and dst).
     ///
     time_t timelocal(time_t * timer = NULL);
 
-    /// Convert a time value structure into an ASCII printable time Www Mmm dd hh:mm:ss yyyy
+    /// Convert a time value structure into an ASCII printable time "Www Mmm dd hh:mm:ss yyyy"
+    ///
+    /// @note Watch out for race conditions as this returns a pointer to a
+    ///         shared buffer.
+    /// @note Unlike the standard ctime function, this version DOES NOT append 
+    ///         a newline character to the buffer.
+    ///
+    /// @code
+    /// time_t tNow = timelocal();
+    /// printf("time is %s\r\n", ctime(tNow));
+    /// @endcode
+    ///
+    /// @param[in] timer is a pointer to a time_t value containing the time to convert.
+    /// @returns a pointer to a buffer containing the string.
+    ///
+    char * ctime(const time_t * timer);
+
+    /// Convert a tm_ex structure into an ASCII printable "time Www Mmm dd hh:mm:ss yyyy"
+    ///
+    /// @note Unlike the standard asctime, this takes a pointer to a tm_ex, which 
+    ///         has a time zone offset element.
     ///
     /// @note Watch out for race conditions as this returns a pointer to a
     ///     shared buffer.
-    /// @note Unlike the standard ctime function, this version DOES NOT append 
-    ///     a newline character to the buffer.
     ///
-    /// @code
-    /// time_t tsample = timelocal();
-    /// printf("time is %s\r\n", ctime(tsample));
-    /// @endcode
-    ///
-    /// @param[in] timeptr is a pointer to a tm structure containing the time to convert.
-    /// @returns a pointer to a private buffer containing the string.
-    ///
-    char * ctime(const time_t * timer);
-
-    /// Convert a tm structure into an ASCII printable time Www Mmm dd hh:mm:ss yyyy
-    ///
-    /// @note Watch out for race conditions as this returns a pointer to a
-    ///     shared buffer.
     /// @note Unlike the standard ctime function, this version DOES NOT append 
     ///     a newline character to the buffer.
     ///
@@ -203,26 +226,39 @@
     /// printf("Time is %s\r\n", asctime(tEx));
     /// @endcode
     ///
-    /// @param[in] timeptr is a pointer to a tm structure containing the time to convert.
+    /// @param[in] timeptr is a pointer to a tm_ex structure containing the time to convert.
     /// @returns a pointer to a private buffer containing the string.
     ///
     char * asctime(const struct tm_ex *timeptr);
 
     /// Compute the difference in seconds between two time values.
     ///
+    /// @code
+    /// time_t tstart, tend;
+    /// ...
+    ///     tstart = time();
+    ///     // do some long process now
+    ///     tend = time();
+    ///     printf("Elapsed time is %5.3f\r\n", tend - tstart);
+    /// ...
+    /// @endcode
+    ///
     /// @param[in] end is the end time to compare to the beginning time.
     /// @param[in] beginning time is compared to the end time.
     /// @return the difference in seconds, as a double.
     ///
     double difftime(time_t end, time_t beginning);
     
-    /// Convert the referenced time_t value to a tm structure in UTC/GMT format.
+    /// Convert the referenced time_t value to a tm_ex structure in UTC/GMT format.
+    ///
+    /// @note Unlike the standard asctime, this return a pointer to a tm_ex, which 
+    ///         has a time zone offset element.
     ///
     /// @note Watch out for race conditions as this returns a pointer to a
     ///     shared buffer.
     ///
     /// @param[in] timer is a pointer to a time_t structure to convert.
-    /// @returns pointer to a tm structure.
+    /// @returns pointer to a tm_ex structure.
     ///
     struct tm_ex * gmtime(const time_t * timer);
     
@@ -506,7 +542,9 @@
     /// @param[in] host NTP server IPv4 address or hostname (will be resolved via DNS)
     /// @param[in] port port to use; defaults to 123
     /// @param[in] timeout waiting timeout in ms (osWaitForever for blocking function, not recommended)
-    /// @return 0 on success, NTP error code (<0) on failure
+    /// @returns NTP_OK on success, 
+    /// @returns NTP_CONN if no network interface
+    /// @returns other NTP error code (<0) on failure
     ///
     NTPResult setTime(const char* host, uint16_t port = NTP_DEFAULT_PORT, uint32_t timeout = NTP_DEFAULT_TIMEOUT);