Repository navigation
NTP_TIME_SYNC_FEATURE
The NTP Time Synchronization feature enables bridge nodes with Internet connectivity to distribute authoritative NTP time to all nodes in the mesh network. This eliminates the need for each node to query NTP servers individually, saving bandwidth, power, and reducing network congestion.
Internet
|
| NTP Query
v
Bridge Node ---------> Regular Node 1
| |
| Broadcast | Update Time
| Type 614 | Sync RTC
| |
+----------------> Regular Node 2
| |
+----------------> Regular Node 3
|
v
Application Code
class NTPTimeSyncPackage : public painlessmesh::plugin::BroadcastPackage {
public:
uint32_t ntpTime = 0; // Unix timestamp from NTP server (seconds)
uint16_t accuracy = 0; // Milliseconds uncertainty/precision
TSTRING source = ""; // NTP server source (e.g., "pool.ntp.org")
uint32_t timestamp = 0; // Collection timestamp (millis())
uint16_t messageType = 614; // MQTT Schema message_type
};{
"type": 614,
"from": 123456,
"routing": 2,
"ntpTime": 1699564800,
"accuracy": 50,
"source": "pool.ntp.org",
"timestamp": 12345678,
"message_type": 614
}The bridge node queries NTP and broadcasts time to the mesh:
#include "painlessMesh.h"
#include "examples/alteriom/alteriom_sensor_package.hpp"
using namespace alteriom;
painlessMesh mesh;
// Periodic task to broadcast NTP time
Task taskBroadcastNTP(60000, TASK_FOREVER, [](){
auto pkg = NTPTimeSyncPackage();
pkg.from = mesh.getNodeId();
pkg.ntpTime = mesh.getNodeTime() / 1000000; // Convert to seconds
pkg.accuracy = 50; // 50ms uncertainty
pkg.source = "pool.ntp.org";
pkg.timestamp = millis();
mesh.sendBroadcast(pkg.toJson());
});
void setup() {
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
mesh.stationManual(ROUTER_SSID, ROUTER_PASSWORD); // Bridge mode
userScheduler.addTask(taskBroadcastNTP);
taskBroadcastNTP.enable();
}Regular nodes receive and apply NTP time:
#include "painlessMesh.h"
#include "examples/alteriom/alteriom_sensor_package.hpp"
using namespace alteriom;
void receivedCallback(uint32_t from, String& msg) {
DynamicJsonDocument doc(1024);
deserializeJson(doc, msg);
JsonObject obj = doc.as<JsonObject>();
if (obj["type"] == 614) {
auto pkg = NTPTimeSyncPackage(obj);
// Verify sender is a bridge (optional but recommended)
if (isBridgeNode(from)) {
// Apply time synchronization
mesh.setTimeFromNTP(pkg.ntpTime);
// Optional: Sync RTC module if available
#ifdef HAS_RTC
rtc.setTime(pkg.ntpTime);
#endif
Serial.printf("Time synced from %s (±%ums)\n",
pkg.source.c_str(), pkg.accuracy);
}
}
}
void setup() {
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
mesh.onReceive(&receivedCallback);
}- Unix timestamp in seconds since epoch (1970-01-01 00:00:00 UTC)
- Range: 0 to 4,294,967,295 (year 2106)
- Example: 1699564800 = 2023-11-09 20:00:00 UTC
- Time uncertainty in milliseconds
- Range: 0 to 65,535ms (0 to 65.5 seconds)
- Typical values:
- 10-50ms: Good NTP connection
- 50-200ms: Average NTP connection
- 200-1000ms: Poor/distant NTP server
- 1000-5000ms: Very poor connection or local time source
- NTP server hostname or IP address
- Examples:
- "pool.ntp.org"
- "time.google.com"
- "time.nist.gov"
- "192.168.1.1" (local router)
- Empty string if no NTP available
- When the time was collected/broadcast (millis())
- Used for calculating staleness of time data
- Wraps around every 49.7 days
-
Update Frequency: Broadcast every 30-60 seconds
- Too frequent: Wastes bandwidth
- Too infrequent: Nodes may drift
-
NTP Query Strategy: Query NTP less frequently than broadcast
- Query NTP every 5-15 minutes
- Cache and reuse recent NTP time
- Update accuracy field based on staleness
-
Error Handling: Handle NTP failures gracefully
- Stop broadcasting if NTP unavailable for >15 minutes
- Or increase accuracy value to indicate uncertainty
-
Validation: Verify sender is bridge before applying time
bool isBridgeNode(uint32_t nodeId) { // Check if node is in bridge list // Or check if node has Internet connectivity flag }
-
Staleness Check: Don't apply very old time data
uint32_t age = millis() - pkg.timestamp; if (age > 120000) { // Older than 2 minutes Serial.println("Time data too old, ignoring"); return; }
-
Accuracy Threshold: Only apply if accuracy is acceptable
if (pkg.accuracy > 1000) { // Worse than 1 second Serial.println("Time accuracy too poor, ignoring"); return; }
-
RTC Integration: Sync RTC for offline operation
if (rtcEnabled && pkg.accuracy < 500) { rtc.setTime(pkg.ntpTime); Serial.println("RTC synced with NTP time"); }
- Reduced NTP queries: Only bridge queries NTP, not every node
- Lower bandwidth: One NTP query serves entire mesh
- Less congestion: Fewer nodes competing for Internet access
- No WiFi switching: Nodes stay on mesh, don't need router connection
- Lower power: No NTP protocol overhead per node
- Longer battery life: Especially important for sensor nodes
- Authoritative source: Bridge has better NTP access than distant nodes
- Lower latency: Mesh broadcast faster than Internet NTP
- Better synchronization: All nodes sync to same time source
- RTC sync: Nodes can sync RTC modules for offline timekeeping
- Time continuity: Time available even when bridge loses Internet
- Graceful degradation: Mesh continues with cached time
-
Bridge Authentication: Verify time source is trusted bridge
- Use bridge node whitelist
- Check sender's bridge status flag
- Validate against multiple bridges if available
-
Replay Attack Prevention: Check timestamp freshness
static uint32_t lastTimestamp = 0; if (pkg.timestamp <= lastTimestamp) { // Potential replay attack or clock rollback return; } lastTimestamp = pkg.timestamp;
-
Sanity Checks: Validate time is reasonable
const uint32_t MIN_TIME = 1609459200; // 2021-01-01 const uint32_t MAX_TIME = 2147483647; // 2038-01-19 if (pkg.ntpTime < MIN_TIME || pkg.ntpTime > MAX_TIME) { Serial.println("Time out of valid range"); return; }
-
Large Jump Detection: Reject suspicious time changes
uint32_t currentTime = getCurrentTime(); int32_t timeDelta = pkg.ntpTime - currentTime; if (abs(timeDelta) > 86400) { // More than 1 day Serial.println("Time change too large, manual intervention needed"); return; }
- All sensors use synchronized timestamps
- Data correlation across nodes is accurate
- Event ordering is consistent
- Multiple nodes can trigger actions at specific times
- Light shows with precise timing
- Scheduled operations across mesh
- Consistent timestamps for all log entries
- Time-series data from multiple sources can be merged
- Historical analysis is accurate
- Event timestamps are reliable for audit logs
- Video/sensor correlation across devices
- Alarm scheduling with accurate time
The feature includes comprehensive unit tests in test/catch/catch_alteriom_packages.cpp:
- Basic serialization/deserialization
- JSON field validation
- Different NTP sources
- Edge cases (0 values, max values)
- Long source hostnames
- High accuracy values
Run tests:
cd painlessMesh
cmake -G Ninja .
ninja
./bin/catch_alteriom_packagesTest with example sketches:
- Upload
ntpTimeSyncBridge.inoto bridge node with Internet - Upload
ntpTimeSyncNode.inoto regular mesh nodes - Monitor serial output to verify:
- Bridge broadcasts NTP time
- Nodes receive and apply time
- Timestamps are consistent
Symptoms: No NTP broadcasts seen by nodes
Solutions:
- Check Internet connectivity:
ping 8.8.8.8 - Verify NTP server is reachable
- Check broadcast task is enabled
- Increase debug level:
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION)
Symptoms: Nodes connected but not syncing time
Solutions:
- Verify receiver callback is registered:
mesh.onReceive(&receivedCallback) - Check message type parsing:
obj["type"] == 614 - Ensure JSON buffer is large enough:
DynamicJsonDocument doc(1024) - Monitor for JSON parse errors
Symptoms: High accuracy values (>500ms)
Solutions:
- Use closer NTP server (local or regional)
- Check network latency to NTP server
- Reduce NTP query frequency
- Consider using multiple NTP sources and averaging
Symptoms: Time gradually becomes inaccurate
Solutions:
- Increase broadcast frequency (30-60 seconds)
- Verify NTP queries are successful
- Check for mesh network stability issues
- Use RTC module for drift compensation
- Bridge Status (Type 610): Indicates bridge connectivity status
- RTC Integration: Offline timekeeping when NTP unavailable
- Mesh Time Sync: Built-in mesh time synchronization protocol
- v1.8.1: Initial implementation of NTP time sync feature
- Type ID 614 allocated for TIME_SYNC_NTP
- Examples and documentation added
- Issue: Enhancement: Bridge-to-Mesh NTP Time Distribution
- Examples:
examples/ntpTimeSyncBridge/andexamples/ntpTimeSyncNode/ - Tests:
test/catch/catch_alteriom_packages.cpp - Package:
examples/alteriom/alteriom_sensor_package.hpp