Skip to content

CTS or NTP

pschatzmann edited this page Nov 1, 2026 · 2 revisions

TinyNTP gets the time from a server on the network. TinyCTS gets it from a device that is next to you, over Bluetooth. Neither is better in general: they solve different problems.

What TinyCTS gives you

  • No network needed. No Wi-Fi, no Ethernet, no internet, no router, no NTP server and no access point password in your firmware. This is the only option for boards that have Bluetooth but no IP connection (nRF52, Nano 33 BLE, ...), for devices that are used outdoors or on the move, and for places where Wi-Fi is not allowed or not available.
  • Lower power. A short BLE connection every now and then costs much less energy than joining a Wi-Fi network, and BLE can run while the Wi-Fi radio stays off.
  • The time zone comes with the time. CTS sends the time zone and the summer time offset (Local Time Information), so a device shows the right local time without a time zone table or a POSIX TZ string. When the user travels or the clocks change, the device that you read follows.
  • Use the clock that is already there. A phone, a smart watch or a computer is usually set by the mobile network or by NTP. Your small device does not need to be able to reach an NTP server itself.
  • Other direction too. A board with Wi-Fi can run a TinyNTPClient and a TinyCTSServer and give the network time to all the BLE-only devices around it (see below). Or a board with a GPS/RTC serves the time to phones and other boards without any infrastructure.
  • Simple deployment. Pairing-free reading, nothing to configure on the network (no UDP port, no firewall, no DNS).

What NTP is better at

  • Accuracy. NTP measures the round trip time and the offset of the clock, and it knows the quality of the server (stratum, dispersion). CTS has none of that: the time is only as good as the clock of the other device, the resolution is 1/256 s (about 4 ms) and the delay of the BLE connection (tens of milliseconds) can only be estimated. TinyCTSClient takes half of the round trip time into account, which is good enough for a clock or a log, but not for a measurement that needs a few milliseconds.
  • Range and reach. NTP works over the whole internet. CTS needs a device with the service within Bluetooth range (a few meters to some ten meters), and it is not the first thing that you find on a phone: many phones offer CTS only through an app, or only to paired accessories.
  • Authority. The pool servers are synchronized to reference clocks. A phone or a watch is only as good as its own source, and the Reference Time Information tells you only what the other device claims.
  • Security. NTP is not secure either, but CTS has no authentication of its own: it relies on BLE pairing, and the time that you read from an unknown device in range is not trustworthy (see Security).

Rules of thumb

You have Use
A board with Wi-Fi/Ethernet and internet TinyNTP
A board with Bluetooth only, or no network at hand TinyCTS (client)
A phone, watch or computer that should get the time of your board TinyCTS (server)
Both kinds of boards TinyNTP on the connected board, plus a TinyCTSServer that repeats its time
A time stamp that must be accurate to a few milliseconds NTP or GPS, not CTS

Using both

A time source of one library can feed the other with the adapter TinyCTSSourceAdapter:

// ESP32: gets the time from the internet and serves it to the BLE devices around
TinyNTPClient<WiFiUDP> ntp(NTP_IP_GOOGLE_1);
TinyCTSSourceAdapter<TinyNTPClient<WiFiUDP>> source(ntp);
TinyCTSServer cts;

void setup() {
  WiFi.begin("SSID", "PASSWORD");
  while (WiFi.status() != WL_CONNECTED) delay(500);
  ntp.begin();
  cts.setReferenceInfo(TinyCTSReferenceTimeInfo::NetworkTimeProtocol);
  cts.begin(source, "ntp-repeater");
}

void loop() { cts.process(); }

In the other direction TinyCTSClient has a getTimeMs(), so it can serve the time to a TinyNTPServer with a small adapter, which makes your board an NTP server for the network that gets its time from a phone:

struct CtsTimeSource : tiny_ntp::TinyTimeSource {
  TinyCTSClient& cts;
  explicit CtsTimeSource(TinyCTSClient& c) : cts(c) {}
  uint64_t getTimeMs() override { return cts.getTimeMs(); }
};

(call cts.update() from time to time to stay in sync).

Clone this wiki locally