@alteriom/painlessmesh 1.8.0 → 1.8.2

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.
@@ -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
  *