@alteriom/painlessmesh 1.8.2 → 1.8.3

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +62 -11
  3. package/RELEASE_GUIDE.md +57 -16
  4. package/docs/ARDUINO_LIBRARY_MANAGER_SUBMISSION.md +331 -0
  5. package/docs/features/DIAGNOSTICS_API.md +534 -0
  6. package/docs/getting-started/arduino-manual-install.md +313 -0
  7. package/docs/implementation/BRIDGE_ARCHITECTURE_IMPLEMENTATION.md +340 -0
  8. package/docs/implementation/BRIDGE_HEALTH_MONITORING_IMPLEMENTATION.md +213 -0
  9. package/docs/implementation/BRIDGE_STATUS_FEATURE.md +635 -0
  10. package/docs/implementation/DIAGNOSTICS_API_IMPLEMENTATION.md +232 -0
  11. package/docs/implementation/IMPLEMENTATION_COMPLETE.md +228 -0
  12. package/docs/implementation/IMPLEMENTATION_NTP_TIME_SYNC.md +325 -0
  13. package/docs/implementation/IMPLEMENTATION_SUMMARY.md +316 -0
  14. package/docs/implementation/MESSAGE_QUEUE_IMPLEMENTATION.md +405 -0
  15. package/docs/implementation/MULTI_BRIDGE_IMPLEMENTATION.md +520 -0
  16. package/docs/implementation/NTP_TIME_SYNC_FEATURE.md +392 -0
  17. package/docs/internal/CUSTOM_AGENT_ANALYSIS.md +391 -0
  18. package/docs/internal/ISSUE_65_VERIFICATION.md +947 -0
  19. package/docs/internal/ISSUE_66_CLOSURE.md +249 -0
  20. package/docs/internal/ISSUE_66_STATUS.md +316 -0
  21. package/docs/internal/PR_SUMMARY.md +315 -0
  22. package/docs/internal/REVIEW_SUMMARY.md +332 -0
  23. package/docs/releases/PUBLISH_v1.8.0_INSTRUCTIONS.md +163 -0
  24. package/docs/releases/QUICK_START_RELEASES.md +113 -0
  25. package/docs/releases/RELEASE_CHECKLIST_v1.8.0.md +331 -0
  26. package/docs/releases/RELEASE_CHECKLIST_v1.8.2.md +309 -0
  27. package/docs/releases/RELEASE_NOTES_v1.8.0.md +685 -0
  28. package/docs/releases/RELEASE_NOTES_v1.8.1.md +221 -0
  29. package/docs/releases/RELEASE_NOTES_v1.8.2.md +421 -0
  30. package/docs/releases/RELEASE_NOTES_v1.8.3.md +292 -0
  31. package/docs/troubleshooting/ARDUINO_IDE_VERSION_FIX_SUMMARY.md +229 -0
  32. package/docs/troubleshooting/ARDUINO_LIBRARY_NAME_FIX.md +197 -0
  33. package/docs/troubleshooting/NPM_PUBLISHING_ISSUE_SUMMARY.md +110 -0
  34. package/docs/troubleshooting/station-reconnection-issues.md +172 -0
  35. package/examples/priority/README.md +274 -0
  36. package/examples/priority/priority_basic_example.ino +115 -0
  37. package/examples/priority/priority_with_queue.ino +249 -0
  38. package/examples/routing_demo/README.md +172 -0
  39. package/examples/routing_demo/routing_demo.ino +102 -0
  40. package/library.json +1 -1
  41. package/library.properties +3 -3
  42. package/package.json +1 -1
  43. package/src/arduino/wifi.hpp +49 -16
  44. package/src/painlessMesh.h +15 -0
  45. package/src/painlessMeshSTA.cpp +7 -1
  46. package/src/painlessmesh/buffer.hpp +218 -37
  47. package/src/painlessmesh/connection.hpp +21 -1
  48. package/src/painlessmesh/mesh.hpp +253 -19
  49. package/src/painlessmesh/router.hpp +31 -0
@@ -2,6 +2,8 @@
2
2
  #define _PAINLESS_MESH_BUFFER_HPP_
3
3
 
4
4
  #include <list>
5
+ #include <map>
6
+ #include <queue>
5
7
 
6
8
  #include "Arduino.h"
7
9
  #include "painlessmesh/configuration.hpp"
@@ -103,30 +105,65 @@ inline void ReceiveBuffer<std::string>::stringAppend(std::string &buffer,
103
105
  }
104
106
  #endif
105
107
 
108
+ /**
109
+ * Structure to hold a message with its priority level
110
+ */
111
+ template <class T>
112
+ struct PrioritizedMessage {
113
+ T message;
114
+ uint8_t priority; // 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW
115
+
116
+ PrioritizedMessage(const T& msg, uint8_t prio = 2) : message(msg), priority(prio) {}
117
+ };
118
+
106
119
  /**
107
120
  * \brief SentBuffer stores messages (strings) and allows them to be read in any
108
- * length
121
+ * length with priority-based scheduling
109
122
  */
110
123
  template <class T>
111
124
  class SentBuffer {
112
125
  public:
113
- SentBuffer() {};
126
+ SentBuffer() {
127
+ current_read_iterator = prioritizedMessages.end();
128
+ };
114
129
 
115
130
  /**
116
- * push a message into the buffer.
131
+ * push a message into the buffer with multi-level priority support.
117
132
  *
118
- * \param priority Whether this is a high priority message.
133
+ * \param message The message to queue
134
+ * \param priority Whether this is a high priority message (legacy bool API)
119
135
  *
120
- * High priority messages will be sent to the front of the buffer
136
+ * Legacy API: High priority messages (true) will be sent to the front of the buffer
137
+ * This maintains backward compatibility with existing code.
121
138
  */
122
139
  void push(const T &message, bool priority = false) {
123
- if (priority) {
124
- if (clean)
125
- jsonStrings.push_front(message);
126
- else
127
- jsonStrings.insert((++jsonStrings.begin()), message);
128
- } else
129
- jsonStrings.push_back(message);
140
+ // Legacy API: map bool to uint8_t priority (false=2 NORMAL, true=1 HIGH)
141
+ uint8_t priorityLevel = priority ? 1 : 2;
142
+ pushWithPriority(message, priorityLevel);
143
+ }
144
+
145
+ /**
146
+ * push a message with explicit priority level (0-3)
147
+ *
148
+ * \param message The message to queue
149
+ * \param priorityLevel Priority level: 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW
150
+ *
151
+ * Messages are scheduled in priority order. Within same priority, FIFO order is maintained.
152
+ */
153
+ void pushWithPriority(const T &message, uint8_t priorityLevel) {
154
+ // Clamp priority to valid range
155
+ if (priorityLevel > 3) priorityLevel = 3;
156
+
157
+ prioritizedMessages.push_back(PrioritizedMessage<T>(message, priorityLevel));
158
+
159
+ // Track statistics
160
+ totalMessagesQueued++;
161
+ switch(priorityLevel) {
162
+ case 0: criticalQueued++; break;
163
+ case 1: highQueued++; break;
164
+ case 2: normalQueued++; break;
165
+ case 3: lowQueued++; break;
166
+ }
130
167
  }
131
168
 
132
169
  /**
@@ -135,12 +172,14 @@ class SentBuffer {
135
172
  * Returns the actual length available (<= the requested length
136
173
  */
137
174
  size_t requestLength(size_t buffer_length) {
138
- if (jsonStrings.empty())
175
+ // Use highest priority message available
176
+ auto* msg = getNextMessage();
177
+ if (!msg)
139
178
  return 0;
140
179
  else
141
180
  // String.toCharArray automatically turns the last character into
142
181
  // a \0, we need the extra space to deal with that annoyance
143
- return std::min(buffer_length - 1, jsonStrings.begin()->length() + 1);
182
+ return std::min(buffer_length - 1, msg->length() + 1);
144
183
  }
145
184
 
146
185
  /**
@@ -156,20 +195,29 @@ class SentBuffer {
156
195
  // Note that toCharrArray always null terminates
157
196
  // independent of whether the whole string was read so we use one extra
158
197
  // space
159
- jsonStrings.front().toCharArray(buf.buffer, length + 1);
160
- last_read_size = length;
198
+ auto* msg = getNextMessage();
199
+ if (msg) {
200
+ msg->toCharArray(buf.buffer, length + 1);
201
+ last_read_size = length;
202
+ last_read_priority = getCurrentMessagePriority();
203
+ }
161
204
  }
162
205
 
163
206
  /**
164
- * Returns a pointer directly to the oldest message
207
+ * Returns a pointer directly to the highest priority message
165
208
  *
166
209
  * Note the user should first make sure the requested length is available
167
210
  * using `SentBuffer.requestLength()`, otherwise this function might fail.
168
211
  * Note that if multiple messages are read then they are separated using '\0'.
169
212
  */
170
213
  const char *readPtr(size_t length) {
171
- last_read_size = length;
172
- return jsonStrings.front().c_str();
214
+ auto* msg = getNextMessage();
215
+ if (msg) {
216
+ last_read_size = length;
217
+ last_read_priority = getCurrentMessagePriority();
218
+ return msg->c_str();
219
+ }
220
+ return nullptr;
173
221
  }
174
222
 
175
223
  /**
@@ -178,27 +226,156 @@ class SentBuffer {
178
226
  * Should be called after a call of read() to clear the buffer.
179
227
  */
180
228
  void freeRead() {
181
- if (last_read_size == jsonStrings.begin()->length() + 1) {
182
- jsonStrings.pop_front();
183
- clean = true;
184
- } else {
185
- // jsonStrings.begin()->remove(0, last_read_size);
186
- stringEraseFront((*jsonStrings.begin()), last_read_size);
187
- clean = false;
229
+ if (prioritizedMessages.empty()) {
230
+ last_read_size = 0;
231
+ current_read_iterator = prioritizedMessages.end();
232
+ return;
233
+ }
234
+
235
+ // Find and remove the message that was just read (highest priority)
236
+ auto it = findHighestPriorityMessage();
237
+ if (it != prioritizedMessages.end()) {
238
+ auto& msg = it->message;
239
+
240
+ if (last_read_size == msg.length() + 1) {
241
+ // Whole message was read, remove it
242
+ // Track statistics
243
+ switch(it->priority) {
244
+ case 0: criticalSent++; break;
245
+ case 1: highSent++; break;
246
+ case 2: normalSent++; break;
247
+ case 3: lowSent++; break;
248
+ }
249
+ prioritizedMessages.erase(it);
250
+ current_read_iterator = prioritizedMessages.end(); // Reset iterator
251
+ clean = true;
252
+ } else {
253
+ // Partial message read, remove the read portion
254
+ stringEraseFront(msg, last_read_size);
255
+ // Keep current_read_iterator pointing to this message for next read
256
+ clean = false;
257
+ }
188
258
  }
189
259
  last_read_size = 0;
190
260
  }
191
261
 
192
- bool empty() { return jsonStrings.empty(); }
262
+ bool empty() { return prioritizedMessages.empty(); }
193
263
 
194
- void clear() { jsonStrings.clear(); }
264
+ void clear() {
265
+ prioritizedMessages.clear();
266
+ current_read_iterator = prioritizedMessages.end();
267
+ totalMessagesQueued = 0;
268
+ criticalQueued = highQueued = normalQueued = lowQueued = 0;
269
+ criticalSent = highSent = normalSent = lowSent = 0;
270
+ }
195
271
 
196
- size_t size() { return jsonStrings.size(); }
272
+ size_t size() { return prioritizedMessages.size(); }
273
+
274
+ /**
275
+ * Get priority of the last read message
276
+ */
277
+ uint8_t getLastReadPriority() const { return last_read_priority; }
278
+
279
+ /**
280
+ * Get statistics about queued and sent messages
281
+ */
282
+ struct SendStats {
283
+ uint32_t totalQueued;
284
+ uint32_t criticalQueued;
285
+ uint32_t highQueued;
286
+ uint32_t normalQueued;
287
+ uint32_t lowQueued;
288
+ uint32_t criticalSent;
289
+ uint32_t highSent;
290
+ uint32_t normalSent;
291
+ uint32_t lowSent;
292
+ };
293
+
294
+ SendStats getStats() const {
295
+ SendStats stats;
296
+ stats.totalQueued = totalMessagesQueued;
297
+ stats.criticalQueued = criticalQueued;
298
+ stats.highQueued = highQueued;
299
+ stats.normalQueued = normalQueued;
300
+ stats.lowQueued = lowQueued;
301
+ stats.criticalSent = criticalSent;
302
+ stats.highSent = highSent;
303
+ stats.normalSent = normalSent;
304
+ stats.lowSent = lowSent;
305
+ return stats;
306
+ }
197
307
 
198
308
  private:
199
309
  size_t last_read_size = 0;
310
+ uint8_t last_read_priority = 2; // NORMAL default
200
311
  bool clean = true;
201
- std::list<T> jsonStrings;
312
+ std::list<PrioritizedMessage<T>> prioritizedMessages;
313
+ typename std::list<PrioritizedMessage<T>>::iterator current_read_iterator;
314
+
315
+ // Statistics tracking
316
+ uint32_t totalMessagesQueued = 0;
317
+ uint32_t criticalQueued = 0;
318
+ uint32_t highQueued = 0;
319
+ uint32_t normalQueued = 0;
320
+ uint32_t lowQueued = 0;
321
+ uint32_t criticalSent = 0;
322
+ uint32_t highSent = 0;
323
+ uint32_t normalSent = 0;
324
+ uint32_t lowSent = 0;
325
+
326
+ /**
327
+ * Find the highest priority message in the buffer
328
+ * Returns iterator to the highest priority message, or end() if empty
329
+ *
330
+ * Special handling: If we're in the middle of reading a message (not clean),
331
+ * we continue with that message even if higher priority messages arrive.
332
+ * This maintains backward compatibility with partial read behavior.
333
+ */
334
+ typename std::list<PrioritizedMessage<T>>::iterator findHighestPriorityMessage() {
335
+ if (prioritizedMessages.empty()) {
336
+ return prioritizedMessages.end();
337
+ }
338
+
339
+ // If we're in the middle of a partial read (!clean), continue with current_read_iterator
340
+ // This maintains backward compatibility with the original buffer behavior
341
+ if (!clean && current_read_iterator != prioritizedMessages.end()) {
342
+ return current_read_iterator;
343
+ }
344
+
345
+ // Find message with lowest priority value (0=CRITICAL is highest)
346
+ auto highest = prioritizedMessages.begin();
347
+ for (auto it = prioritizedMessages.begin(); it != prioritizedMessages.end(); ++it) {
348
+ if (it->priority < highest->priority) {
349
+ highest = it;
350
+ }
351
+ }
352
+
353
+ // Cache this for partial reads
354
+ current_read_iterator = highest;
355
+ return highest;
356
+ }
357
+
358
+ /**
359
+ * Get pointer to next message to send (highest priority)
360
+ */
361
+ T* getNextMessage() {
362
+ auto it = findHighestPriorityMessage();
363
+ if (it != prioritizedMessages.end()) {
364
+ return &(it->message);
365
+ }
366
+ return nullptr;
367
+ }
368
+
369
+ /**
370
+ * Get priority of the current highest priority message
371
+ */
372
+ uint8_t getCurrentMessagePriority() {
373
+ auto it = findHighestPriorityMessage();
374
+ if (it != prioritizedMessages.end()) {
375
+ return it->priority;
376
+ }
377
+ return 2; // NORMAL default
378
+ }
202
379
 
203
380
  inline void stringEraseFront(T &string, size_t length) {
204
381
  string.remove(0, length);
@@ -208,13 +385,17 @@ class SentBuffer {
208
385
  #ifdef PAINLESSMESH_ENABLE_STD_STRING
209
386
  template <>
210
387
  inline void SentBuffer<std::string>::read(size_t length, temp_buffer_t &buf) {
211
- jsonStrings.front().copy(buf.buffer, length);
212
- // Mimic String.toCharArray behaviour, which will insert
213
- // null termination at the end of original string and the last
214
- // character
215
- if (length == jsonStrings.front().length() + 1) buf.buffer[length - 1] = '\0';
216
- buf.buffer[length] = '\0';
217
- last_read_size = length;
388
+ auto* msg = getNextMessage();
389
+ if (msg) {
390
+ msg->copy(buf.buffer, length);
391
+ // Mimic String.toCharArray behaviour, which will insert
392
+ // null termination at the end of original string and the last
393
+ // character
394
+ if (length == msg->length() + 1) buf.buffer[length - 1] = '\0';
395
+ buf.buffer[length] = '\0';
396
+ last_read_size = length;
397
+ last_read_priority = getCurrentMessagePriority();
398
+ }
218
399
  }
219
400
 
220
401
  template <>
@@ -129,6 +129,18 @@ class BufferedConnection
129
129
  sentBufferTask.forceNextIteration();
130
130
  return true;
131
131
  }
132
+
133
+ /**
134
+ * Write data with explicit priority level (0-3)
135
+ *
136
+ * \param data The data to send
137
+ * \param priorityLevel Priority level: 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW
138
+ */
139
+ bool writeWithPriority(const TSTRING &data, uint8_t priorityLevel) {
140
+ sentBuffer.pushWithPriority(data, priorityLevel);
141
+ sentBufferTask.forceNextIteration();
142
+ return true;
143
+ }
132
144
 
133
145
  void onDisconnect(std::function<void()> callback) {
134
146
  disconnectCallback = callback;
@@ -164,7 +176,15 @@ class BufferedConnection
164
176
  auto data_ptr = sentBuffer.readPtr(len);
165
177
  auto written = client->write(data_ptr, len, 1);
166
178
  if (written == len) {
167
- client->send(); // TODO only do this for priority messages
179
+ // Get priority before freeing the read buffer
180
+ uint8_t msgPriority = sentBuffer.getLastReadPriority();
181
+
182
+ // Call send() for high priority messages (CRITICAL=0, HIGH=1)
183
+ // This ensures they are transmitted immediately
184
+ if (msgPriority <= 1) {
185
+ client->send();
186
+ }
187
+
168
188
  sentBuffer.freeRead();
169
189
  sentBufferTask.forceNextIteration();
170
190
  return true;