AlteriomPainlessMesh v2.1.1
Easy ESP32/ESP8266 mesh networking with Alteriom IoT extensions
Loading...
Searching...
No Matches
ntp.hpp
Go to the documentation of this file.
1#ifndef _PAINLESS_MESH_NTP_HPP_
2#define _PAINLESS_MESH_NTP_HPP_
3
4#ifndef TIME_SYNC_INTERVAL
5#define TIME_SYNC_INTERVAL 1 * TASK_MINUTE // Time resync period
6#endif
7
8#ifndef TIME_SYNC_ACCURACY
9#define TIME_SYNC_ACCURACY 5000 // Minimum time sync accuracy (5ms
10#endif
11
12#ifndef MIN_LARGE_OFFSET_STEP
13#define MIN_LARGE_OFFSET_STEP 500 // Minimum offset size before softening
14#endif
15
16#ifndef MAX_CPU_TRIP_RATIO
17#define MAX_CPU_TRIP_RATIO \
18 85 // Maximum ratio between remote cpu time and trip time for NTP calc to be
19 // reasonable
20#endif
21
22#include "Arduino.h"
23
27
29
30namespace painlessmesh {
31namespace ntp {
32
33class MeshTime {
34 public:
43 uint32_t getNodeTime() { return micros() + timeOffset; }
44
45 public: // Windows MSVC: friend functions need access to timeOffset
46 uint32_t timeOffset = 0;
47};
48
54inline int32_t clockOffset(uint32_t time0, uint32_t time1, uint32_t time2,
55 uint32_t time3) {
56 uint32_t offset =
57 ((int32_t)(time1 - time0) / 2) + ((int32_t)(time2 - time3) / 2);
58
59 // Take small steps to avoid over correction
60 int32_t signedOffset = (int32_t)offset;
61 if (abs(signedOffset) < MIN_LARGE_OFFSET_STEP && abs(signedOffset) > 4)
62 signedOffset = signedOffset / 4;
63 // Discard outliers with excessive trip to cpu ratios
64 if ((time3 - time0) > (time2 - time1) * MAX_CPU_TRIP_RATIO) signedOffset = 0;
65 return signedOffset;
66}
67
73inline int32_t tripDelay(uint32_t time0, uint32_t time1, uint32_t time2,
74 uint32_t time3) {
75 return ((time3 - time0) - (time2 - time1)) / 2;
76}
77
78inline bool adopt(protocol::NodeTree mesh, protocol::NodeTree connection) {
79 // Prioritize nodes with time authority (RTC or Internet)
80 // Only adopt from nodes with time authority if we don't have one
81 if (!mesh.hasTimeAuthority && connection.hasTimeAuthority) {
82 Log(logger::S_TIME, "adopt(): Adopting from %u (has time authority)\n",
83 connection.nodeId);
84 return true;
85 }
86
87 // Don't adopt from nodes without time authority if we have one
88 if (mesh.hasTimeAuthority && !connection.hasTimeAuthority) {
89 Log(logger::S_TIME, "adopt(): Not adopting from %u (no time authority)\n",
90 connection.nodeId);
91 return false;
92 }
93
94 // If both have same time authority status, use existing logic
95 auto mySubCount =
96 layout::size(layout::excludeRoute(std::move(mesh), connection.nodeId));
97 auto remoteSubCount = layout::size(connection);
98 if (mySubCount > remoteSubCount) return false;
99 if (mySubCount == remoteSubCount) {
100 if (connection.nodeId == 0)
101 Log(logger::ERROR, "Adopt called on uninitialized connection\n");
102 // TODO: there is a change here that a middle node also lower is than the
103 // two others and will start switching between both. Maybe should do it
104 // randomly instead?
105 return mesh.nodeId < connection.nodeId;
106 }
107 return true;
108}
109
110template <class T>
111void initTimeSync(protocol::NodeTree mesh, std::shared_ptr<T> connection,
112 uint32_t nodeTime) {
113 using namespace painlessmesh::logger;
115 if (adopt(mesh, (*connection))) {
116 timeSync = painlessmesh::protocol::TimeSync(mesh.nodeId, connection->nodeId,
117 nodeTime);
118 Log(S_TIME, "initTimeSync(): Requesting time from %u\n",
119 connection->nodeId);
120 } else {
121 timeSync =
122 painlessmesh::protocol::TimeSync(mesh.nodeId, connection->nodeId);
123 Log(S_TIME, "initTimeSync(): Requesting %u to adopt our time\n",
124 connection->nodeId);
125 }
126
127 router::send<protocol::TimeSync, T>(timeSync, connection, true);
128}
129
130template <class T, class U>
132 std::shared_ptr<U> conn, uint32_t receivedAt) {
133 switch (timeSync.msg.type) {
136 "handleTimeSync(): Received time sync error. Restarting time "
137 "sync.\n");
138 conn->timeSyncTask.forceNextIteration();
139 break;
140 case (painlessmesh::protocol::TIME_SYNC_REQUEST): // Other party request me
141 // to ask it for time
143 "handleTimeSync(): Received requesto to start TimeSync with "
144 "node: %u\n",
145 conn->nodeId);
146 timeSync.reply(mesh.getNodeTime());
147 router::send<painlessmesh::protocol::TimeSync>(timeSync, conn, true);
148 break;
149
151 timeSync.reply(receivedAt, mesh.getNodeTime());
152 router::send<painlessmesh::protocol::TimeSync>(timeSync, conn, true);
153
155 "handleTimeSync(): timeSyncStatus with %u completed\n", conn->nodeId);
156
157 // After response is sent I assume sync is completed
158 conn->timeSyncTask.delay(TIME_SYNC_INTERVAL);
159 break;
160
163 "handleTimeSync(): %u adopting TIME_RESPONSE from %u\n", mesh.nodeId,
164 conn->nodeId);
165 int32_t offset = painlessmesh::ntp::clockOffset(
166 timeSync.msg.t0, timeSync.msg.t1, timeSync.msg.t2, receivedAt);
167 mesh.timeOffset += offset; // Accumulate offset
168
169 // flag all connections for re-timeSync
170 if (mesh.nodeTimeAdjustedCallback) {
171 mesh.nodeTimeAdjustedCallback(offset);
172 }
173
174 if ((offset < TIME_SYNC_ACCURACY) && (offset > -TIME_SYNC_ACCURACY) &&
175 (offset != 0)) {
176 // mark complete only if offset was less than 10 ms
177 conn->timeSyncTask.delay(TIME_SYNC_INTERVAL);
179 "handleTimeSync(): timeSyncStatus with %u completed\n",
180 conn->nodeId);
181
182 // Time has changed, update other nodes
183 for (auto&& connection : mesh.subs) {
184 if (connection->nodeId != conn->nodeId) { // exclude this connection
185 connection->timeSyncTask.forceNextIteration();
187 "handleTimeSync(): timeSyncStatus with %u brought forward\n",
188 connection->nodeId);
189 }
190 }
191 } else {
192 // Iterate sync procedure if accuracy was not enough
193 conn->timeSyncTask.delay(200 * TASK_MILLISECOND); // Small delay
195 "handleTimeSync(): timeSyncStatus with %u needs further tries\n",
196 conn->nodeId);
197 }
198 break;
199 }
200 default:
201 Log(logger::ERROR, "handleTimeSync(): unkown type %u, %u\n",
203 break;
204 }
205 Log(logger::S_TIME, "handleTimeSync(): ----------------------------------\n");
206}
207
208template <class T, class U>
210 std::shared_ptr<U> conn, uint32_t receivedAt) {
211 Log(logger::S_TIME, "handleTimeDelay(): from %u in timestamp\n",
212 timeDelay.from);
213
214 switch (timeDelay.msg.type) {
217 "handleTimeDelay(): Error in requesting time delay. Please try "
218 "again.\n");
219 break;
220
222 // conn->timeSyncStatus == IN_PROGRESS;
223 Log(logger::S_TIME, "handleTimeDelay(): TIME REQUEST received.\n");
224
225 // Build time response
226 timeDelay.reply(receivedAt, mesh.getNodeTime());
227 router::send<protocol::TimeDelay, U>(timeDelay, conn);
228 break;
229
231 Log(logger::S_TIME, "handleTimeDelay(): TIME RESPONSE received.\n");
232 int32_t delay = painlessmesh::ntp::tripDelay(
233 timeDelay.msg.t0, timeDelay.msg.t1, timeDelay.msg.t2, receivedAt);
234 Log(logger::S_TIME, "handleTimeDelay(): Delay is %d\n", delay);
235
236 // conn->timeSyncStatus == COMPLETE;
237
238 if (mesh.nodeDelayReceivedCallback)
239 mesh.nodeDelayReceivedCallback(timeDelay.from, delay);
240 } break;
241
242 default:
244 "handleTimeDelay(): Unknown timeSyncMessageType received. Ignoring "
245 "for now.\n");
246 }
247
248 Log(logger::S_TIME, "handleTimeSync(): ----------------------------------\n");
249}
250
251template <class T, typename U>
253 T& mesh) {
254 // TimeSync
255 callbackList.onPackage(
257 [&mesh](protocol::Variant& variant, std::shared_ptr<U> connection,
258 uint32_t receivedAt) {
259 auto timeSync = variant.to<protocol::TimeSync>();
260 handleTimeSync<T, U>(mesh, timeSync, connection, receivedAt);
261 return false;
262 });
263
264 // TimeDelay
265 callbackList.onPackage(
267 [&mesh](protocol::Variant& variant, std::shared_ptr<U> connection,
268 uint32_t receivedAt) {
269 auto timeDelay = variant.to<protocol::TimeDelay>();
270 handleTimeDelay<T, U>(mesh, timeDelay, connection, receivedAt);
271 return false;
272 });
273}
274
275} // namespace ntp
276} // namespace painlessmesh
277#endif
void onPackage(int id, std::function< void(Args...)> func)
Definition callback.hpp:62
Definition logger.hpp:46
Definition ntp.hpp:33
uint32_t getNodeTime()
Definition ntp.hpp:43
uint32_t timeOffset
Definition ntp.hpp:46
Definition protocol.hpp:167
bool hasTimeAuthority
Definition protocol.hpp:171
uint32_t nodeId
Definition protocol.hpp:169
Definition protocol.hpp:463
Definition protocol.hpp:355
void reply(uint32_t newT0)
Definition protocol.hpp:437
uint32_t from
Definition protocol.hpp:359
time_sync_msg_t msg
Definition protocol.hpp:360
Definition protocol.hpp:481
T to()
Definition protocol.hpp:743
protocol::NodeTree excludeRoute(protocol::NodeTree &&tree, uint32_t exclude)
Definition layout.hpp:87
uint32_t size(protocol::NodeTree nodeTree)
Definition layout.hpp:232
Definition logger.hpp:7
@ S_TIME
Definition logger.hpp:24
@ ERROR
Definition logger.hpp:19
int32_t clockOffset(uint32_t time0, uint32_t time1, uint32_t time2, uint32_t time3)
Definition ntp.hpp:54
void handleTimeDelay(T &mesh, painlessmesh::protocol::TimeDelay timeDelay, std::shared_ptr< U > conn, uint32_t receivedAt)
Definition ntp.hpp:209
void handleTimeSync(T &mesh, painlessmesh::protocol::TimeSync timeSync, std::shared_ptr< U > conn, uint32_t receivedAt)
Definition ntp.hpp:131
void initTimeSync(protocol::NodeTree mesh, std::shared_ptr< T > connection, uint32_t nodeTime)
Definition ntp.hpp:111
void addPackageCallback(callback::MeshPackageCallbackList< U > &callbackList, T &mesh)
Definition ntp.hpp:252
int32_t tripDelay(uint32_t time0, uint32_t time1, uint32_t time2, uint32_t time3)
Definition ntp.hpp:73
bool adopt(protocol::NodeTree mesh, protocol::NodeTree connection)
Definition ntp.hpp:78
@ TIME_REPLY
Definition protocol.hpp:49
@ TIME_SYNC_ERROR
Definition protocol.hpp:46
@ TIME_REQUEST
Definition protocol.hpp:48
@ TIME_SYNC_REQUEST
Definition protocol.hpp:47
@ TIME_SYNC
Definition protocol.hpp:37
@ TIME_DELAY
Definition protocol.hpp:36
Definition wifi.hpp:34
painlessmesh::logger::LogClass Log
Definition wifi.cpp:2
#define TIME_SYNC_ACCURACY
Definition ntp.hpp:9
#define MAX_CPU_TRIP_RATIO
Definition ntp.hpp:17
#define MIN_LARGE_OFFSET_STEP
Definition ntp.hpp:13
#define TIME_SYNC_INTERVAL
Definition ntp.hpp:5
uint32_t t0
Definition protocol.hpp:347
uint32_t t1
Definition protocol.hpp:348
int type
Definition protocol.hpp:346
uint32_t t2
Definition protocol.hpp:349
painlessmesh::logger::LogClass Log
Definition wifi.cpp:2