@alteriom/painlessmesh 1.7.9 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -2
- package/README.md +159 -12
- package/docs/BRIDGE_FAILOVER.md +512 -0
- package/docs/BRIDGE_HEALTH_MONITORING.md +293 -0
- package/docs/CREATE_MISSING_RELEASES.md +321 -0
- package/docs/releases/RELEASE_SUMMARY_v1.7.8.md +523 -0
- package/docs/releases/RELEASE_SUMMARY_v1.7.9.md +542 -0
- package/examples/alteriom/alteriom_sensor_package.hpp +213 -0
- package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +1014 -11
- package/examples/basic/basic.ino +6 -2
- package/examples/bridge/bridge.ino +44 -23
- package/examples/bridge/bridge_health_monitoring_example.ino +188 -0
- package/examples/bridgeAwareSensorNode/alteriom_sensor_package.hpp +1227 -0
- package/examples/bridgeAwareSensorNode/bridgeAwareSensorNode.ino +343 -0
- package/examples/bridgeAwareSensorNode/platformio.ini +26 -0
- package/examples/bridge_failover/README.md +358 -0
- package/examples/bridge_failover/bridge_failover.ino +180 -0
- package/examples/bridge_failover/platformio.ini +27 -0
- package/examples/diagnosticsExample/diagnosticsExample.ino +171 -0
- package/examples/diagnosticsExample/platformio.ini +26 -0
- package/examples/multi_bridge/README.md +346 -0
- package/examples/multi_bridge/primary_bridge.ino +96 -0
- package/examples/multi_bridge/regular_node.ino +141 -0
- package/examples/multi_bridge/secondary_bridge.ino +111 -0
- package/examples/ntpTimeSyncBridge/alteriom_sensor_package.hpp +1383 -0
- package/examples/ntpTimeSyncBridge/ntpTimeSyncBridge.ino +81 -0
- package/examples/ntpTimeSyncNode/alteriom_sensor_package.hpp +1383 -0
- package/examples/ntpTimeSyncNode/ntpTimeSyncNode.ino +109 -0
- package/examples/queued_alarms/README.md +390 -0
- package/examples/queued_alarms/queued_alarms.ino +265 -0
- package/examples/rtcIntegration/README.md +235 -0
- package/examples/rtcIntegration/rtcIntegration.ino +196 -0
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
- package/src/arduino/wifi.hpp +888 -0
- package/src/painlessMeshSTA.cpp +63 -0
- package/src/painlessMeshSTA.h +3 -0
- package/src/painlessmesh/mesh.hpp +1327 -4
- package/src/painlessmesh/message_queue.hpp +368 -0
- package/src/painlessmesh/plugin.hpp +69 -0
- package/src/painlessmesh/rtc.hpp +203 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#ifndef _PAINLESS_MESH_MESSAGE_QUEUE_HPP_
|
|
2
|
+
#define _PAINLESS_MESH_MESSAGE_QUEUE_HPP_
|
|
3
|
+
|
|
4
|
+
#include <vector>
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include "painlessmesh/configuration.hpp"
|
|
7
|
+
#include "painlessmesh/logger.hpp"
|
|
8
|
+
|
|
9
|
+
// External logger instance
|
|
10
|
+
extern painlessmesh::logger::LogClass Log;
|
|
11
|
+
|
|
12
|
+
namespace painlessmesh {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Message priority levels for queue management
|
|
16
|
+
* Higher priority messages are preserved when queue is full
|
|
17
|
+
*/
|
|
18
|
+
enum MessagePriority {
|
|
19
|
+
PRIORITY_CRITICAL = 0, // Life/safety critical (O2 alarms, fire alarms)
|
|
20
|
+
PRIORITY_HIGH = 1, // Important but not life-threatening
|
|
21
|
+
PRIORITY_NORMAL = 2, // Regular sensor data
|
|
22
|
+
PRIORITY_LOW = 3 // Non-essential telemetry
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Queue state for monitoring and callbacks
|
|
27
|
+
*/
|
|
28
|
+
enum QueueState {
|
|
29
|
+
QUEUE_EMPTY, // Queue is empty
|
|
30
|
+
QUEUE_NORMAL, // Queue has space available
|
|
31
|
+
QUEUE_75_PERCENT, // Queue is 75% full - warning threshold
|
|
32
|
+
QUEUE_FULL // Queue is full - dropping low priority messages
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Queued message structure
|
|
37
|
+
* Contains all metadata needed for reliable message delivery
|
|
38
|
+
*/
|
|
39
|
+
struct QueuedMessage {
|
|
40
|
+
uint32_t id; // Unique message ID
|
|
41
|
+
MessagePriority priority; // Message priority level
|
|
42
|
+
uint32_t timestamp; // When message was queued (millis)
|
|
43
|
+
uint32_t attempts; // Number of send attempts
|
|
44
|
+
TSTRING payload; // Message content
|
|
45
|
+
TSTRING destination; // Cloud endpoint/topic (optional metadata)
|
|
46
|
+
|
|
47
|
+
QueuedMessage() : id(0), priority(PRIORITY_NORMAL), timestamp(0), attempts(0) {}
|
|
48
|
+
|
|
49
|
+
QueuedMessage(uint32_t msgId, MessagePriority prio, uint32_t ts,
|
|
50
|
+
const TSTRING& data, const TSTRING& dest = "")
|
|
51
|
+
: id(msgId), priority(prio), timestamp(ts), attempts(0),
|
|
52
|
+
payload(data), destination(dest) {}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Message queue statistics
|
|
57
|
+
*/
|
|
58
|
+
struct QueueStats {
|
|
59
|
+
uint32_t totalQueued = 0; // Total messages ever queued
|
|
60
|
+
uint32_t totalSent = 0; // Total messages successfully sent
|
|
61
|
+
uint32_t totalDropped = 0; // Total messages dropped (queue full)
|
|
62
|
+
uint32_t currentSize = 0; // Current queue size
|
|
63
|
+
uint32_t maxSize = 0; // Configured max size
|
|
64
|
+
|
|
65
|
+
// Priority-specific counts
|
|
66
|
+
uint32_t criticalQueued = 0;
|
|
67
|
+
uint32_t highQueued = 0;
|
|
68
|
+
uint32_t normalQueued = 0;
|
|
69
|
+
uint32_t lowQueued = 0;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Queue state change callback type
|
|
73
|
+
typedef std::function<void(QueueState state, uint32_t messageCount)> queueStateChangedCallback_t;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Message Queue for offline/Internet-unavailable mode
|
|
77
|
+
*
|
|
78
|
+
* Provides priority-based message queueing with support for:
|
|
79
|
+
* - Priority levels (CRITICAL messages never dropped)
|
|
80
|
+
* - Queue size limits with intelligent eviction
|
|
81
|
+
* - Statistics tracking
|
|
82
|
+
* - State change callbacks
|
|
83
|
+
*
|
|
84
|
+
* Example usage:
|
|
85
|
+
* \code
|
|
86
|
+
* MessageQueue queue(1000); // Max 1000 messages
|
|
87
|
+
*
|
|
88
|
+
* // Queue a critical message
|
|
89
|
+
* uint32_t msgId = queue.enqueue(PRIORITY_CRITICAL, "alarm_data", "mqtt://...");
|
|
90
|
+
*
|
|
91
|
+
* // Get queue size
|
|
92
|
+
* uint32_t size = queue.size();
|
|
93
|
+
*
|
|
94
|
+
* // Get all messages (for sending)
|
|
95
|
+
* std::vector<QueuedMessage> messages = queue.getMessages();
|
|
96
|
+
*
|
|
97
|
+
* // Remove sent message
|
|
98
|
+
* queue.remove(msgId);
|
|
99
|
+
* \endcode
|
|
100
|
+
*/
|
|
101
|
+
class MessageQueue {
|
|
102
|
+
public:
|
|
103
|
+
/**
|
|
104
|
+
* Constructor
|
|
105
|
+
* @param maxSize Maximum number of messages in queue
|
|
106
|
+
*/
|
|
107
|
+
explicit MessageQueue(uint32_t maxSize = 1000)
|
|
108
|
+
: maxQueueSize(maxSize), nextMessageId(1) {
|
|
109
|
+
messages.reserve(maxSize);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Enqueue a message with priority
|
|
114
|
+
*
|
|
115
|
+
* @param priority Message priority level
|
|
116
|
+
* @param payload Message content
|
|
117
|
+
* @param destination Optional destination metadata
|
|
118
|
+
* @return Message ID if queued, 0 if dropped
|
|
119
|
+
*/
|
|
120
|
+
uint32_t enqueue(MessagePriority priority, const TSTRING& payload,
|
|
121
|
+
const TSTRING& destination = "") {
|
|
122
|
+
// Check if queue is full
|
|
123
|
+
if (messages.size() >= maxQueueSize) {
|
|
124
|
+
// Try to make space by removing low priority messages
|
|
125
|
+
if (!makeSpace(priority)) {
|
|
126
|
+
stats.totalDropped++;
|
|
127
|
+
Log(logger::ERROR, "MessageQueue: Failed to queue message (queue full, priority too low)\n");
|
|
128
|
+
return 0; // Could not queue
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
uint32_t msgId = nextMessageId++;
|
|
133
|
+
uint32_t timestamp = millis();
|
|
134
|
+
|
|
135
|
+
QueuedMessage msg(msgId, priority, timestamp, payload, destination);
|
|
136
|
+
messages.push_back(msg);
|
|
137
|
+
|
|
138
|
+
// Update statistics
|
|
139
|
+
stats.totalQueued++;
|
|
140
|
+
stats.currentSize = messages.size();
|
|
141
|
+
updatePriorityStats();
|
|
142
|
+
|
|
143
|
+
// Check for state change
|
|
144
|
+
checkStateChange();
|
|
145
|
+
|
|
146
|
+
Log(logger::GENERAL, "MessageQueue: Enqueued message #%u (priority=%d, size=%u/%u)\n",
|
|
147
|
+
msgId, priority, messages.size(), maxQueueSize);
|
|
148
|
+
|
|
149
|
+
return msgId;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Remove a message from the queue
|
|
154
|
+
* @param messageId ID of message to remove
|
|
155
|
+
* @return true if message was found and removed
|
|
156
|
+
*/
|
|
157
|
+
bool remove(uint32_t messageId) {
|
|
158
|
+
auto it = std::find_if(messages.begin(), messages.end(),
|
|
159
|
+
[messageId](const QueuedMessage& msg) {
|
|
160
|
+
return msg.id == messageId;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (it != messages.end()) {
|
|
164
|
+
messages.erase(it);
|
|
165
|
+
stats.totalSent++;
|
|
166
|
+
stats.currentSize = messages.size();
|
|
167
|
+
updatePriorityStats();
|
|
168
|
+
checkStateChange();
|
|
169
|
+
|
|
170
|
+
Log(logger::GENERAL, "MessageQueue: Removed message #%u (size=%u)\n",
|
|
171
|
+
messageId, messages.size());
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get all queued messages
|
|
180
|
+
* @return Vector of queued messages (ordered by timestamp)
|
|
181
|
+
*/
|
|
182
|
+
std::vector<QueuedMessage> getMessages() const {
|
|
183
|
+
return messages;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get current queue size
|
|
188
|
+
* @return Number of messages in queue
|
|
189
|
+
*/
|
|
190
|
+
uint32_t size() const {
|
|
191
|
+
return messages.size();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get count of messages with specific priority
|
|
196
|
+
* @param priority Priority level to count
|
|
197
|
+
* @return Number of messages with that priority
|
|
198
|
+
*/
|
|
199
|
+
uint32_t size(MessagePriority priority) const {
|
|
200
|
+
return std::count_if(messages.begin(), messages.end(),
|
|
201
|
+
[priority](const QueuedMessage& msg) {
|
|
202
|
+
return msg.priority == priority;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check if queue is empty
|
|
208
|
+
* @return true if no messages queued
|
|
209
|
+
*/
|
|
210
|
+
bool empty() const {
|
|
211
|
+
return messages.empty();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Clear all messages from queue
|
|
216
|
+
*/
|
|
217
|
+
void clear() {
|
|
218
|
+
messages.clear();
|
|
219
|
+
stats.currentSize = 0;
|
|
220
|
+
updatePriorityStats();
|
|
221
|
+
checkStateChange();
|
|
222
|
+
Log(logger::GENERAL, "MessageQueue: Cleared all messages\n");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get queue statistics
|
|
227
|
+
* @return QueueStats structure
|
|
228
|
+
*/
|
|
229
|
+
QueueStats getStats() const {
|
|
230
|
+
return stats;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Set queue state change callback
|
|
235
|
+
* @param callback Function to call when queue state changes
|
|
236
|
+
*/
|
|
237
|
+
void onStateChanged(queueStateChangedCallback_t callback) {
|
|
238
|
+
stateChangedCallback = callback;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Increment send attempt counter for a message
|
|
243
|
+
* @param messageId ID of message
|
|
244
|
+
* @return New attempt count, or 0 if message not found
|
|
245
|
+
*/
|
|
246
|
+
uint32_t incrementAttempts(uint32_t messageId) {
|
|
247
|
+
auto it = std::find_if(messages.begin(), messages.end(),
|
|
248
|
+
[messageId](const QueuedMessage& msg) {
|
|
249
|
+
return msg.id == messageId;
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (it != messages.end()) {
|
|
253
|
+
it->attempts++;
|
|
254
|
+
return it->attempts;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Remove old messages (for queue pruning)
|
|
262
|
+
* @param maxAgeMs Maximum age in milliseconds
|
|
263
|
+
* @return Number of messages removed
|
|
264
|
+
*/
|
|
265
|
+
uint32_t pruneOldMessages(uint32_t maxAgeMs) {
|
|
266
|
+
uint32_t currentTime = millis();
|
|
267
|
+
uint32_t removedCount = 0;
|
|
268
|
+
|
|
269
|
+
auto it = messages.begin();
|
|
270
|
+
while (it != messages.end()) {
|
|
271
|
+
if (currentTime - it->timestamp > maxAgeMs) {
|
|
272
|
+
it = messages.erase(it);
|
|
273
|
+
removedCount++;
|
|
274
|
+
} else {
|
|
275
|
+
++it;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (removedCount > 0) {
|
|
280
|
+
stats.currentSize = messages.size();
|
|
281
|
+
updatePriorityStats();
|
|
282
|
+
checkStateChange();
|
|
283
|
+
Log(logger::GENERAL, "MessageQueue: Pruned %u old messages\n", removedCount);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return removedCount;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private:
|
|
290
|
+
std::vector<QueuedMessage> messages;
|
|
291
|
+
uint32_t maxQueueSize;
|
|
292
|
+
uint32_t nextMessageId;
|
|
293
|
+
QueueStats stats;
|
|
294
|
+
QueueState currentState = QUEUE_EMPTY;
|
|
295
|
+
queueStateChangedCallback_t stateChangedCallback;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Try to make space in queue by removing low priority messages
|
|
299
|
+
* @param newPriority Priority of message trying to be added
|
|
300
|
+
* @return true if space was made
|
|
301
|
+
*/
|
|
302
|
+
bool makeSpace(MessagePriority newPriority) {
|
|
303
|
+
// CRITICAL messages can always evict lower priority
|
|
304
|
+
// HIGH messages can evict NORMAL and LOW
|
|
305
|
+
// NORMAL messages can only evict LOW
|
|
306
|
+
// LOW messages cannot evict anything
|
|
307
|
+
|
|
308
|
+
if (newPriority == PRIORITY_LOW) {
|
|
309
|
+
return false; // LOW priority can't evict anything
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Try to remove lowest priority messages first
|
|
313
|
+
for (int targetPriority = PRIORITY_LOW; targetPriority > newPriority; targetPriority--) {
|
|
314
|
+
auto it = std::find_if(messages.begin(), messages.end(),
|
|
315
|
+
[targetPriority](const QueuedMessage& msg) {
|
|
316
|
+
return msg.priority == static_cast<MessagePriority>(targetPriority);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (it != messages.end()) {
|
|
320
|
+
Log(logger::GENERAL, "MessageQueue: Evicting message #%u (priority=%d) to make space\n",
|
|
321
|
+
it->id, it->priority);
|
|
322
|
+
messages.erase(it);
|
|
323
|
+
stats.totalDropped++;
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Update priority-specific statistics
|
|
333
|
+
*/
|
|
334
|
+
void updatePriorityStats() {
|
|
335
|
+
stats.criticalQueued = size(PRIORITY_CRITICAL);
|
|
336
|
+
stats.highQueued = size(PRIORITY_HIGH);
|
|
337
|
+
stats.normalQueued = size(PRIORITY_NORMAL);
|
|
338
|
+
stats.lowQueued = size(PRIORITY_LOW);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Check if queue state has changed and fire callback
|
|
343
|
+
*/
|
|
344
|
+
void checkStateChange() {
|
|
345
|
+
QueueState newState;
|
|
346
|
+
|
|
347
|
+
if (messages.empty()) {
|
|
348
|
+
newState = QUEUE_EMPTY;
|
|
349
|
+
} else if (messages.size() >= maxQueueSize) {
|
|
350
|
+
newState = QUEUE_FULL;
|
|
351
|
+
} else if (messages.size() >= maxQueueSize * 3 / 4) {
|
|
352
|
+
newState = QUEUE_75_PERCENT;
|
|
353
|
+
} else {
|
|
354
|
+
newState = QUEUE_NORMAL;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (newState != currentState) {
|
|
358
|
+
currentState = newState;
|
|
359
|
+
if (stateChangedCallback) {
|
|
360
|
+
stateChangedCallback(currentState, messages.size());
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
} // namespace painlessmesh
|
|
367
|
+
|
|
368
|
+
#endif // _PAINLESS_MESH_MESSAGE_QUEUE_HPP_
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include "painlessmesh/configuration.hpp"
|
|
6
6
|
|
|
7
7
|
#include "painlessmesh/router.hpp"
|
|
8
|
+
#include <vector>
|
|
8
9
|
|
|
9
10
|
namespace painlessmesh {
|
|
10
11
|
|
|
@@ -93,6 +94,74 @@ class NeighbourPackage : public plugin::SinglePackage {
|
|
|
93
94
|
NeighbourPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {}
|
|
94
95
|
};
|
|
95
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Bridge Coordination Package (Type 613)
|
|
99
|
+
*
|
|
100
|
+
* Used for multi-bridge coordination in advanced deployments.
|
|
101
|
+
* Bridges use this to:
|
|
102
|
+
* - Announce their presence and role (primary/secondary)
|
|
103
|
+
* - Exchange peer bridge lists
|
|
104
|
+
* - Report current load for load balancing decisions
|
|
105
|
+
* - Coordinate conflict resolution
|
|
106
|
+
*
|
|
107
|
+
* This enables features like:
|
|
108
|
+
* - Multiple simultaneous bridges (hot standby)
|
|
109
|
+
* - Load balancing across bridges
|
|
110
|
+
* - Geographic distribution
|
|
111
|
+
* - Traffic shaping
|
|
112
|
+
*/
|
|
113
|
+
class BridgeCoordinationPackage : public plugin::BroadcastPackage {
|
|
114
|
+
public:
|
|
115
|
+
uint8_t priority = 5; // Bridge priority (10=highest, 1=lowest)
|
|
116
|
+
TSTRING role = "secondary"; // Role: "primary", "secondary", "standby"
|
|
117
|
+
std::vector<uint32_t> peerBridges; // List of known bridge node IDs
|
|
118
|
+
uint8_t load = 0; // Current load percentage (0-100)
|
|
119
|
+
uint32_t timestamp = 0; // Coordination timestamp
|
|
120
|
+
int noJsonFields = 8; // Base fields (3) + new fields (5)
|
|
121
|
+
|
|
122
|
+
BridgeCoordinationPackage() : BroadcastPackage(613) {}
|
|
123
|
+
|
|
124
|
+
BridgeCoordinationPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
|
|
125
|
+
priority = jsonObj["priority"] | 5;
|
|
126
|
+
role = jsonObj["role"].as<TSTRING>();
|
|
127
|
+
load = jsonObj["load"] | 0;
|
|
128
|
+
timestamp = jsonObj["timestamp"] | 0;
|
|
129
|
+
|
|
130
|
+
// Parse peer bridge array
|
|
131
|
+
if (jsonObj["peerBridges"].is<JsonArray>()) {
|
|
132
|
+
JsonArray peers = jsonObj["peerBridges"];
|
|
133
|
+
for (JsonVariant peer : peers) {
|
|
134
|
+
peerBridges.push_back(peer.as<uint32_t>());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
JsonObject addTo(JsonObject&& jsonObj) const {
|
|
140
|
+
jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
|
|
141
|
+
jsonObj["priority"] = priority;
|
|
142
|
+
jsonObj["role"] = role;
|
|
143
|
+
jsonObj["load"] = load;
|
|
144
|
+
jsonObj["timestamp"] = timestamp;
|
|
145
|
+
|
|
146
|
+
// Add peer bridge array
|
|
147
|
+
JsonArray peers = jsonObj["peerBridges"].to<JsonArray>();
|
|
148
|
+
for (uint32_t peerId : peerBridges) {
|
|
149
|
+
peers.add(peerId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return jsonObj;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#if ARDUINOJSON_VERSION_MAJOR < 7
|
|
156
|
+
size_t jsonObjectSize() const {
|
|
157
|
+
// Base fields + string length + array overhead
|
|
158
|
+
size_t peerArraySize = JSON_ARRAY_SIZE(peerBridges.size()) +
|
|
159
|
+
(peerBridges.size() * sizeof(uint32_t));
|
|
160
|
+
return JSON_OBJECT_SIZE(noJsonFields) + role.length() + peerArraySize;
|
|
161
|
+
}
|
|
162
|
+
#endif
|
|
163
|
+
};
|
|
164
|
+
|
|
96
165
|
/**
|
|
97
166
|
* Handle different plugins
|
|
98
167
|
*
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#ifndef _PAINLESS_MESH_RTC_HPP_
|
|
2
|
+
#define _PAINLESS_MESH_RTC_HPP_
|
|
3
|
+
|
|
4
|
+
#include "Arduino.h"
|
|
5
|
+
#include "painlessmesh/logger.hpp"
|
|
6
|
+
|
|
7
|
+
extern painlessmesh::logger::LogClass Log;
|
|
8
|
+
|
|
9
|
+
namespace painlessmesh {
|
|
10
|
+
namespace rtc {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* RTC module types supported by painlessMesh
|
|
14
|
+
*/
|
|
15
|
+
enum RTCType {
|
|
16
|
+
RTC_NONE = 0,
|
|
17
|
+
RTC_DS3231 = 1,
|
|
18
|
+
RTC_DS1307 = 2,
|
|
19
|
+
RTC_PCF8523 = 3,
|
|
20
|
+
RTC_PCF8563 = 4,
|
|
21
|
+
RTC_ESP32_INTERNAL = 5
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Abstract RTC interface for painlessMesh
|
|
26
|
+
*
|
|
27
|
+
* Users should implement this interface for their specific RTC hardware
|
|
28
|
+
* and pass an instance to mesh.enableRTC()
|
|
29
|
+
*/
|
|
30
|
+
class RTCInterface {
|
|
31
|
+
public:
|
|
32
|
+
virtual ~RTCInterface() {}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the RTC hardware
|
|
36
|
+
*
|
|
37
|
+
* @return true if initialization successful, false otherwise
|
|
38
|
+
*/
|
|
39
|
+
virtual bool begin() = 0;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if RTC is available and responding
|
|
43
|
+
*
|
|
44
|
+
* @return true if RTC is working, false otherwise
|
|
45
|
+
*/
|
|
46
|
+
virtual bool isAvailable() = 0;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get current Unix timestamp from RTC
|
|
50
|
+
*
|
|
51
|
+
* @return Unix timestamp (seconds since 1970-01-01 00:00:00 UTC)
|
|
52
|
+
*/
|
|
53
|
+
virtual uint32_t getUnixTime() = 0;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Set RTC time from Unix timestamp
|
|
57
|
+
*
|
|
58
|
+
* @param timestamp Unix timestamp to set
|
|
59
|
+
* @return true if successful, false otherwise
|
|
60
|
+
*/
|
|
61
|
+
virtual bool setUnixTime(uint32_t timestamp) = 0;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get RTC type identifier
|
|
65
|
+
*
|
|
66
|
+
* @return RTCType enum value
|
|
67
|
+
*/
|
|
68
|
+
virtual RTCType getType() = 0;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* RTC manager class for mesh integration
|
|
73
|
+
*
|
|
74
|
+
* Handles RTC time synchronization and fallback to mesh time
|
|
75
|
+
*/
|
|
76
|
+
class RTCManager {
|
|
77
|
+
public:
|
|
78
|
+
RTCManager() : rtcInterface(nullptr), rtcEnabled(false), lastSyncTime(0) {}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Enable RTC with a user-provided interface
|
|
82
|
+
*
|
|
83
|
+
* @param interface Pointer to RTCInterface implementation
|
|
84
|
+
* @return true if RTC initialized successfully, false otherwise
|
|
85
|
+
*/
|
|
86
|
+
bool enable(RTCInterface* interface) {
|
|
87
|
+
if (!interface) {
|
|
88
|
+
Log(logger::ERROR, "RTCManager::enable() - NULL interface provided\n");
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
rtcInterface = interface;
|
|
93
|
+
|
|
94
|
+
if (!rtcInterface->begin()) {
|
|
95
|
+
Log(logger::ERROR, "RTCManager::enable() - RTC initialization failed\n");
|
|
96
|
+
rtcInterface = nullptr;
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!rtcInterface->isAvailable()) {
|
|
101
|
+
Log(logger::ERROR, "RTCManager::enable() - RTC not available\n");
|
|
102
|
+
rtcInterface = nullptr;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
rtcEnabled = true;
|
|
107
|
+
Log(logger::GENERAL, "RTCManager::enable() - RTC type %d enabled successfully\n",
|
|
108
|
+
rtcInterface->getType());
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Disable RTC
|
|
114
|
+
*/
|
|
115
|
+
void disable() {
|
|
116
|
+
rtcEnabled = false;
|
|
117
|
+
rtcInterface = nullptr;
|
|
118
|
+
Log(logger::GENERAL, "RTCManager::disable() - RTC disabled\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if RTC is enabled and available
|
|
123
|
+
*
|
|
124
|
+
* @return true if RTC can be used, false otherwise
|
|
125
|
+
*/
|
|
126
|
+
bool isEnabled() const {
|
|
127
|
+
return rtcEnabled && rtcInterface != nullptr && rtcInterface->isAvailable();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get current time from RTC
|
|
132
|
+
*
|
|
133
|
+
* @return Unix timestamp, or 0 if RTC unavailable
|
|
134
|
+
*/
|
|
135
|
+
uint32_t getTime() {
|
|
136
|
+
if (!isEnabled()) {
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
return rtcInterface->getUnixTime();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Sync RTC time from NTP/Internet source
|
|
144
|
+
*
|
|
145
|
+
* @param ntpTimestamp Unix timestamp from NTP source
|
|
146
|
+
* @return true if sync successful, false otherwise
|
|
147
|
+
*/
|
|
148
|
+
bool syncFromNTP(uint32_t ntpTimestamp) {
|
|
149
|
+
if (!isEnabled()) {
|
|
150
|
+
Log(logger::ERROR, "RTCManager::syncFromNTP() - RTC not enabled\n");
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (ntpTimestamp == 0) {
|
|
155
|
+
Log(logger::ERROR, "RTCManager::syncFromNTP() - Invalid timestamp\n");
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!rtcInterface->setUnixTime(ntpTimestamp)) {
|
|
160
|
+
Log(logger::ERROR, "RTCManager::syncFromNTP() - Failed to set RTC time\n");
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
lastSyncTime = millis();
|
|
165
|
+
Log(logger::GENERAL, "RTCManager::syncFromNTP() - RTC synced to %u\n",
|
|
166
|
+
ntpTimestamp);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get time since last RTC sync
|
|
172
|
+
*
|
|
173
|
+
* @return Milliseconds since last sync, or 0 if never synced
|
|
174
|
+
*/
|
|
175
|
+
uint32_t getTimeSinceLastSync() const {
|
|
176
|
+
if (lastSyncTime == 0) {
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
return millis() - lastSyncTime;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get RTC type
|
|
184
|
+
*
|
|
185
|
+
* @return RTCType enum, or RTC_NONE if disabled
|
|
186
|
+
*/
|
|
187
|
+
RTCType getType() const {
|
|
188
|
+
if (!isEnabled()) {
|
|
189
|
+
return RTC_NONE;
|
|
190
|
+
}
|
|
191
|
+
return rtcInterface->getType();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private:
|
|
195
|
+
RTCInterface* rtcInterface;
|
|
196
|
+
bool rtcEnabled;
|
|
197
|
+
uint32_t lastSyncTime;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
} // namespace rtc
|
|
201
|
+
} // namespace painlessmesh
|
|
202
|
+
|
|
203
|
+
#endif // _PAINLESS_MESH_RTC_HPP_
|