NTPClient with QNEthernet

MCollins-Lowell

New member
We are developing a system to log acceleration and angular rate data from SPI-based sensors, using a Teensy-4.1 as the controlling element in a hub. All communication between a host computer which requests and processes inertial data and the Teensy is across a PoE link. We're using Shawn Silverman's QNEthernet library and have found that to be completely stable and reliable. All hub functionality is in place with the exception of one element, an automated mechanism to set the RTC on the Teensy so that reasonably accurate time stamps can be recorded in each command or data response.

It seems that the NTPClient package should be useful for this purpose, however I've not been successful in getting a response from the update method. The example code on the GitHub site expects a WiFi network connection, so I've made several attempts to adapt the example code to our wired Ethernet environment. While I can compile my source without errors or warnings, it simply doesn't work. It's likely that I'm not correctly passing the UDP socket to the NTPClient library, or missing a step before doing so.

The code example below, largely derived from Shawn's ServerWithListeners example, should run on any Teensy 4.1 with wired Ethernet connected to a network with a DHCP server. An NTP connection is attempted after a valid IP address has been issued, and simple intermediate status shows progress. The code consistently stalls at the ntpClient.update() call.

A related question. Am I correct in expecting that a successful call to ntpClient.update() will populate fields as TimeLib expects?

Any assistance will be greatly appreciated.

-- Mike --

C-like:
// SPDX-FileCopyrightText: (c) 2021-2023 Shawn Silverman <shawn@pobox.com>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// Ref: AppWithListenersTemplate
//

#include <NTPClient.h>
#include <QNEthernet.h>
#include <SPI.h>
#include <TimeLib.h>

// #include "ims.h"

using namespace qindesign::network;
constexpr uint32_t kDHCPTimeout = 15'000;  // 15 seconds
constexpr uint32_t kLinkTimeout = 5'000;  // 5 seconds
constexpr bool kWaitForDHCP = true;
constexpr uint16_t kServerPort = 49999;

constexpr int kMessageSize = 80;

struct ClientState {
  ClientState(EthernetClient client)
      : client(std::move(client)) {}

  EthernetClient client;

  int byteCount = 0;  // Keeps track of how many bytes have been read
  uint8_t buf[kMessageSize];
  bool closed = false;
  bool cmdAvailable = false;
};

std::vector<ClientState> clients;
EthernetServer server{kServerPort};
volatile bool networkReadyLatch = false;

time_t t;
tmElements_t DateTime;
EthernetUDP udpSocket;

ClientState *activeSocket;

// --------------------------------------------------------------------------
//  Main Program
// --------------------------------------------------------------------------

void setNetworkReady(bool hasIP, bool hasLink, bool interfaceUp);

void setup() {

  Serial.begin(1000000);
  while (!Serial && millis() < 4000) {
    // Wait for Serial
  }
  delay(1500);  // Give external monitors a chance to start

  if (CrashReport) {
    Serial.println(CrashReport);
    CrashReport.clear();
  }

  printf("Starting...\r\n");

  uint8_t mac[6];
  Ethernet.macAddress(mac);  // This is informative; it retrieves, not sets
  printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x\r\n",
         mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

  Ethernet.onLinkState([](bool state) {
    printf("[Ethernet] Link %s\r\n", state ? "ON" : "OFF");
    bool hasIP = (Ethernet.localIP() != INADDR_NONE);
    setNetworkReady(hasIP, state, Ethernet.interfaceStatus());
  });

  Ethernet.onAddressChanged([]() {
    IPAddress ip = Ethernet.localIP();
    bool hasIP = (ip != INADDR_NONE);
    if (hasIP) {
      IPAddress subnet = Ethernet.subnetMask();
      IPAddress gw = Ethernet.gatewayIP();
      IPAddress dns = Ethernet.dnsServerIP();
      printf(
          "[Ethernet] Address changed:\r\n"
          "    Local IP = %u.%u.%u.%u\r\n"
          "    Subnet   = %u.%u.%u.%u\r\n"
          "    Gateway  = %u.%u.%u.%u\r\n"
          "    DNS      = %u.%u.%u.%u\r\n",
          ip[0], ip[1], ip[2], ip[3],
          subnet[0], subnet[1], subnet[2], subnet[3],
          gw[0], gw[1], gw[2], gw[3],
          dns[0], dns[1], dns[2], dns[3]);
    } else {
      printf("[Ethernet] Address changed: No IP address\r\n");
    }

    setNetworkReady(hasIP, Ethernet.linkState(), Ethernet.interfaceStatus());
  });

  Ethernet.onInterfaceStatus([](bool status) {
    bool hasIP = (Ethernet.localIP() != INADDR_NONE);
    setNetworkReady(hasIP, Ethernet.linkState(), status);
  });

  printf("Attempting to obtain IP address...\r\n");
  if (Ethernet.begin()) {
    if (!Ethernet.waitForLocalIP(kDHCPTimeout)) {
       printf("No address from DHCP...\r\n");
    }
    else {
      IPAddress ip = Ethernet.localIP();
      printf("IP Address: %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);

      //
      // Connect to NTP server
      //
        printf("Attempt to synchronize RTC...\r\n");
        udpSocket = EthernetUDP();
        NTPClient timeClient(udpSocket, "pool.ntp.org");
        printf("  A\r\n");
        timeClient.begin();

        printf("  B\r\n");
        delay(5000);
        printf("  C\r\n");
        timeClient.update();

        printf("  D\r\n");
        t = now();
        breakTime(t, DateTime);

        printf("%.4d-%.2d-%.2d %.2d:%.2d:%.2d\r\n",
               DateTime.Year + 1970, DateTime.Month, DateTime.Day,
               DateTime.Hour, DateTime.Minute, DateTime.Second);
    }
  }

  if (kWaitForDHCP) {
    if (!Ethernet.waitForLocalIP(kDHCPTimeout)) {
      printf("Warning: No address from DHCP\r\n");
      // An address could still come in later
    }
  }
}

void setNetworkReady(bool hasIP, bool hasLink, bool interfaceUp) {
  networkReadyLatch = hasIP && hasLink && interfaceUp;

  printf("Network is%s READY\r\n", networkReadyLatch ? "" : " NOT");
  printf("Listening for clients on port %u...\r\n", kServerPort);
    server.begin();
}

//
// Message handler
//
void processMessage(const ClientState &state) {
}

void loop() {
}
 
The AppWithListenersTemplate and ServerWithListeners examples are TCP servers. Have a look at the SNTPClient example.
 
For our application, it is required that the Teensy-based hub function as a server. To set the RTC from an NTP server, is it necessary to initially configure the system as an SNTP client, then reconfigure as a server for data collection activities?
 
Back
Top