@alteriom/painlessmesh 1.6.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/LICENSE +674 -0
  3. package/README.md +434 -0
  4. package/RELEASE_GUIDE.md +419 -0
  5. package/docs/DOCUMENTATION_MIGRATION_PLAN.md +176 -0
  6. package/docs/README.md +71 -0
  7. package/docs/alteriom/overview.md +508 -0
  8. package/docs/api/core-api.md +607 -0
  9. package/docs/architecture/mesh-architecture.md +379 -0
  10. package/docs/architecture/plugin-system.md +517 -0
  11. package/docs/getting-started/first-mesh.md +410 -0
  12. package/docs/getting-started/installation.md +275 -0
  13. package/docs/getting-started/quickstart.md +158 -0
  14. package/docs/improvements/README.md +69 -0
  15. package/docs/troubleshooting/common-issues.md +521 -0
  16. package/docs/troubleshooting/faq.md +473 -0
  17. package/docs/tutorials/basic-examples.md +718 -0
  18. package/docs/wiki/API-Reference.md +246 -0
  19. package/docs/wiki/Complete-Documentation.md +123 -0
  20. package/examples/alteriom/README.md +82 -0
  21. package/examples/alteriom/alteriom.ino +186 -0
  22. package/examples/alteriom/alteriom_sensor_node.ino +184 -0
  23. package/examples/alteriom/alteriom_sensor_package.hpp +128 -0
  24. package/examples/alteriom/improved_sensor_node.ino +246 -0
  25. package/examples/alteriom/platformio.ini +25 -0
  26. package/examples/basic/basic.ino +66 -0
  27. package/examples/basic/platformio.ini +25 -0
  28. package/examples/bridge/bridge.ino +51 -0
  29. package/examples/bridge/platformio.ini +25 -0
  30. package/examples/echoNode/echoNode.ino +33 -0
  31. package/examples/echoNode/platformio.ini +25 -0
  32. package/examples/logClient/logClient.ino +109 -0
  33. package/examples/logClient/platformio.ini +25 -0
  34. package/examples/logServer/logServer.ino +81 -0
  35. package/examples/logServer/platformio.ini +25 -0
  36. package/examples/mqttBridge/mqttBridge.ino +118 -0
  37. package/examples/mqttBridge/platformio.ini +26 -0
  38. package/examples/namedMesh/namedMesh.ino +97 -0
  39. package/examples/namedMesh/platformio.ini +25 -0
  40. package/examples/otaReceiver/otaReceiver.ino +79 -0
  41. package/examples/otaReceiver/platformio.ini +25 -0
  42. package/examples/otaSender/nodemcu32s_connections.JPG +0 -0
  43. package/examples/otaSender/otaSender.ino +151 -0
  44. package/examples/otaSender/platformio.ini +25 -0
  45. package/examples/startHere/platformio.ini +25 -0
  46. package/examples/startHere/startHere.ino +159 -0
  47. package/examples/webServer/platformio.ini +27 -0
  48. package/examples/webServer/webServer.ino +89 -0
  49. package/keywords.txt +49 -0
  50. package/library.json +34 -0
  51. package/library.properties +11 -0
  52. package/package.json +78 -0
  53. package/src/AlteriomPainlessMesh.h +98 -0
  54. package/src/arduino/wifi.hpp +365 -0
  55. package/src/boost/asynctcp.hpp +279 -0
  56. package/src/painlessMesh.h +70 -0
  57. package/src/painlessMeshSTA.cpp +236 -0
  58. package/src/painlessMeshSTA.h +58 -0
  59. package/src/painlessTaskOptions.h +4 -0
  60. package/src/painlessmesh/base64.hpp +111 -0
  61. package/src/painlessmesh/buffer.hpp +229 -0
  62. package/src/painlessmesh/callback.hpp +91 -0
  63. package/src/painlessmesh/configuration.hpp +77 -0
  64. package/src/painlessmesh/connection.hpp +192 -0
  65. package/src/painlessmesh/layout.hpp +188 -0
  66. package/src/painlessmesh/logger.hpp +158 -0
  67. package/src/painlessmesh/memory.hpp +120 -0
  68. package/src/painlessmesh/mesh.hpp +560 -0
  69. package/src/painlessmesh/metrics.hpp +323 -0
  70. package/src/painlessmesh/ntp.hpp +263 -0
  71. package/src/painlessmesh/ota.hpp +553 -0
  72. package/src/painlessmesh/plugin.hpp +188 -0
  73. package/src/painlessmesh/protocol.hpp +813 -0
  74. package/src/painlessmesh/router.hpp +322 -0
  75. package/src/painlessmesh/tcp.hpp +71 -0
  76. package/src/painlessmesh/validation.hpp +239 -0
  77. package/src/plugin/performance.hpp +214 -0
  78. package/src/plugin/remote.hpp +64 -0
  79. package/src/scheduler.cpp +10 -0
  80. package/src/wifi.cpp +2 -0
@@ -0,0 +1,553 @@
1
+ #ifndef _PAINLESS_MESH_PLUGIN_OTA_HPP_
2
+ #define _PAINLESS_MESH_PLUGIN_OTA_HPP_
3
+
4
+ #include "painlessmesh/configuration.hpp"
5
+
6
+ #include "painlessmesh/base64.hpp"
7
+ #include "painlessmesh/logger.hpp"
8
+ #include "painlessmesh/plugin.hpp"
9
+
10
+ #if !defined(USE_FS_SPIFFS) && !defined(USE_FS_LITTLEFS)
11
+ #if defined(ESP32) || defined(ESP8266)
12
+ #ifdef ESP32
13
+ #define USE_FS_SPIFFS
14
+ #else
15
+ #define USE_FS_LITTLEFS
16
+ #endif
17
+ #endif
18
+ #endif
19
+
20
+ #ifdef ESP32
21
+ #include <Update.h>
22
+ #endif
23
+
24
+ #if defined(USE_FS_SPIFFS)
25
+ #include <SPIFFS.h>
26
+ #elif defined(USE_FS_LITTLEFS)
27
+ #include <LittleFS.h>
28
+ #endif
29
+
30
+ namespace painlessmesh {
31
+ namespace plugin {
32
+
33
+ /** OTA over the mesh
34
+ *
35
+ * OTA is implemented as a painlessmesh::plugin.
36
+ *
37
+ * The protocol consists of three message types: ota::Announce, ota::DataRequest
38
+ * and ota::Data. The first message is generally send by the node that
39
+ * distributes the firmware and announces the current version of firmware
40
+ * available for each hardware and role. Firmware version is determined by
41
+ * its MD5 signature. See
42
+ * [painlessMeshBoost](http://gitlab.com/painlessMesh/painlessMeshBoost) for a
43
+ * possible implementation of a distribution node.
44
+ *
45
+ * Once a node receives an announce message it will check it against its own
46
+ * role and hardware to discover if it is suitable this node. If that checks out
47
+ * and the MD5 is different than its own MD5 it will send a data request back to
48
+ * the firmware distribution node. This request also includes a partNo, to
49
+ * determine which part of the data it needs (starting from zero).
50
+ *
51
+ * When the distribution node receives a data request, it sends the data back to
52
+ * the node (with a data message). The node will then write this data and
53
+ * request the next part of the data. This exchange continuous until the node
54
+ * has all the data, written it and reboots into the new firmware.
55
+ */
56
+ namespace ota {
57
+
58
+ /**
59
+ * Operation codes for various firmware states
60
+ */
61
+ enum class OTA_OP_CODES {
62
+ ANNOUNCE = 10, // Announce a new update
63
+ DATA_REQUEST = 11, // Request data from host
64
+ DATA = 12, // Inbound data to nodes
65
+ };
66
+
67
+ /** Package used by the firmware distribution node to announce new version
68
+ * available
69
+ *
70
+ * This is based on the general BroadcastPackage to ensure it is being
71
+ * broadcasted. It is possible to define a Announce::role, which defines the
72
+ * node role the firmware is meant for.
73
+ *
74
+ * The package type/identifier is set to 10.
75
+ */
76
+ class Announce : public BroadcastPackage {
77
+ public:
78
+ TSTRING md5;
79
+ TSTRING hardware;
80
+
81
+ /**
82
+ * \brief The type of node the firmware is meant for
83
+ *
84
+ * Nodes can fulfill different roles, which require specific firmware. E.g a
85
+ * node can be a sensor and therefore needs the firmware meant for sensor
86
+ * nodes. This allows one to set the type of node (role) this firmware is
87
+ * aimed at.
88
+ *
89
+ * Note that the role should not contain underscores or dots.
90
+ */
91
+ TSTRING role;
92
+
93
+ /** Force an update even if the node already has this firmware version
94
+ *
95
+ * Mainly usefull when testing updates etc.
96
+ */
97
+ bool forced = false;
98
+
99
+ /** Receive broadcast chunks. The default behavior is to have each node
100
+ * request and receive each of its firwmare payload chunks, however this can
101
+ * in some cases create a lot of additional network traffic. If broadcasted is
102
+ * true, the root node will act on behalf of all other nodes in leading the
103
+ * request for packets.
104
+ */
105
+ bool broadcasted = false;
106
+
107
+ size_t noPart;
108
+
109
+ Announce() : BroadcastPackage(10) {}
110
+
111
+ Announce(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
112
+ md5 = jsonObj["md5"].as<TSTRING>();
113
+ hardware = jsonObj["hardware"].as<TSTRING>();
114
+ role = jsonObj["role"].as<TSTRING>();
115
+ #if ARDUINOJSON_VERSION_MAJOR < 7
116
+ if (jsonObj.containsKey("forced")) forced = jsonObj["forced"];
117
+ if (jsonObj.containsKey("broadcasted"))
118
+ broadcasted = jsonObj["broadcasted"];
119
+ #else
120
+ if (jsonObj["forced"].is<bool>()) forced = jsonObj["forced"];
121
+ if (jsonObj["broadcasted"].is<bool>()) broadcasted = jsonObj["broadcasted"];
122
+ #endif
123
+ noPart = jsonObj["noPart"];
124
+ }
125
+
126
+ JsonObject addTo(JsonObject&& jsonObj) const {
127
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
128
+ jsonObj["md5"] = md5;
129
+ jsonObj["hardware"] = hardware;
130
+ jsonObj["role"] = role;
131
+ if (forced) jsonObj["forced"] = forced;
132
+ jsonObj["noPart"] = noPart;
133
+ jsonObj["broadcasted"] = broadcasted;
134
+ return jsonObj;
135
+ }
136
+
137
+ #if ARDUINOJSON_VERSION_MAJOR < 7
138
+ size_t jsonObjectSize() const {
139
+ return JSON_OBJECT_SIZE(noJsonFields + 5) +
140
+ round(1.1 * (md5.length() + hardware.length() + role.length()));
141
+ }
142
+ #endif
143
+
144
+ protected:
145
+ Announce(int type, router::Type routing) : BroadcastPackage(type) {
146
+ this->routing = routing;
147
+ }
148
+ };
149
+
150
+ class Data;
151
+
152
+ /** Request (part of) the firmware update
153
+ *
154
+ * This is send by the node needing the new firmware, to the firmware
155
+ * distribution node to request a part (DataRequest::partNo) of the data.
156
+ *
157
+ * The package type/identifier is set to 11.
158
+ */
159
+ class DataRequest : public Announce {
160
+ public:
161
+ size_t partNo = 0;
162
+ uint32_t dest = 0;
163
+
164
+ DataRequest() : Announce(11, router::SINGLE) {}
165
+
166
+ DataRequest(JsonObject jsonObj) : Announce(jsonObj) {
167
+ dest = jsonObj["dest"];
168
+ partNo = jsonObj["partNo"];
169
+ }
170
+
171
+ JsonObject addTo(JsonObject&& jsonObj) const {
172
+ jsonObj = Announce::addTo(std::move(jsonObj));
173
+ jsonObj["dest"] = dest;
174
+ jsonObj["partNo"] = partNo;
175
+ return jsonObj;
176
+ }
177
+
178
+ static DataRequest replyTo(const Announce& ann, uint32_t from,
179
+ size_t partNo) {
180
+ DataRequest req;
181
+ req.dest = ann.from;
182
+ req.md5 = ann.md5;
183
+ req.hardware = ann.hardware;
184
+ req.role = ann.role;
185
+ req.forced = ann.forced;
186
+ req.noPart = ann.noPart;
187
+ req.partNo = partNo;
188
+ req.from = from;
189
+ req.broadcasted = ann.broadcasted;
190
+ return req;
191
+ }
192
+
193
+ static DataRequest replyTo(const Data& d, size_t partNo);
194
+
195
+ #if ARDUINOJSON_VERSION_MAJOR < 7
196
+ size_t jsonObjectSize() const {
197
+ return JSON_OBJECT_SIZE(noJsonFields + 5 + 2) +
198
+ round(1.1 * (md5.length() + hardware.length() + role.length()));
199
+ }
200
+ #endif
201
+
202
+ protected:
203
+ DataRequest(int type) : Announce(type, router::SINGLE) {}
204
+ };
205
+
206
+ /** Package containing part of the firmware
207
+ *
208
+ * The package type/identifier is set to 12.
209
+ */
210
+ class Data : public DataRequest {
211
+ public:
212
+ TSTRING data;
213
+
214
+ Data() : DataRequest(12) {}
215
+
216
+ Data(JsonObject jsonObj) : DataRequest(jsonObj) {
217
+ data = jsonObj["data"].as<TSTRING>();
218
+ }
219
+
220
+ JsonObject addTo(JsonObject&& jsonObj) const {
221
+ jsonObj = DataRequest::addTo(std::move(jsonObj));
222
+ jsonObj["data"] = data;
223
+ return jsonObj;
224
+ }
225
+
226
+ static Data replyTo(const DataRequest& req, TSTRING data, size_t partNo) {
227
+ Data d;
228
+ d.from = req.dest;
229
+ d.dest = req.from;
230
+ d.md5 = req.md5;
231
+ d.hardware = req.hardware;
232
+ d.role = req.role;
233
+ d.forced = req.forced;
234
+ d.noPart = req.noPart;
235
+ d.partNo = partNo;
236
+ d.data = data;
237
+ d.broadcasted = req.broadcasted;
238
+ return d;
239
+ }
240
+
241
+ #if ARDUINOJSON_VERSION_MAJOR < 7
242
+ size_t jsonObjectSize() const {
243
+ return JSON_OBJECT_SIZE(noJsonFields + 5 + 2 + 1) +
244
+ round(1.1 * (md5.length() + hardware.length() + role.length() +
245
+ data.length()));
246
+ }
247
+ #endif
248
+ };
249
+
250
+ inline DataRequest DataRequest::replyTo(const Data& d, size_t partNo) {
251
+ DataRequest req;
252
+ req.from = d.dest;
253
+ req.dest = d.from;
254
+ req.md5 = d.md5;
255
+ req.hardware = d.hardware;
256
+ req.role = d.role;
257
+ req.forced = d.forced;
258
+ req.noPart = d.noPart;
259
+ req.partNo = partNo;
260
+ req.broadcasted = d.broadcasted;
261
+ return req;
262
+ }
263
+
264
+ /** Data related to the current state of the node update
265
+ *
266
+ * This class is used by the OTA algorithm to keep track of both the current
267
+ * version of the software and the ongoing update.
268
+ *
269
+ * The firmware md5 uniquely identifies each firmware version
270
+ */
271
+ class State : public protocol::PackageInterface {
272
+ public:
273
+ TSTRING md5;
274
+ #ifdef ESP32
275
+ TSTRING hardware = "ESP32";
276
+ #else
277
+ TSTRING hardware = "ESP8266";
278
+ #endif
279
+ TSTRING role;
280
+ size_t noPart = 0;
281
+ size_t partNo = 0;
282
+ bool broadcasted = false;
283
+ TSTRING ota_fn = "/ota_fw.json";
284
+
285
+ State() {}
286
+
287
+ State(JsonObject jsonObj) {
288
+ md5 = jsonObj["md5"].as<TSTRING>();
289
+ hardware = jsonObj["hardware"].as<TSTRING>();
290
+ role = jsonObj["role"].as<TSTRING>();
291
+ broadcasted = jsonObj["broadcasted"].as<bool>();
292
+ }
293
+
294
+ State(const Announce& ann) {
295
+ md5 = ann.md5;
296
+ hardware = ann.hardware;
297
+ role = ann.role;
298
+ noPart = ann.noPart;
299
+ broadcasted = ann.broadcasted;
300
+ }
301
+
302
+ JsonObject addTo(JsonObject&& jsonObj) const {
303
+ jsonObj["role"] = role;
304
+ jsonObj["md5"] = md5;
305
+ jsonObj["hardware"] = hardware;
306
+ jsonObj["broadcasted"] = broadcasted;
307
+ return jsonObj;
308
+ }
309
+
310
+ #if ARDUINOJSON_VERSION_MAJOR < 7
311
+ size_t jsonObjectSize() const {
312
+ return JSON_OBJECT_SIZE(3) +
313
+ round(1.1 * (md5.length() + hardware.length() + role.length()));
314
+ }
315
+ #endif
316
+
317
+ std::shared_ptr<Task> task;
318
+ };
319
+
320
+ typedef std::function<size_t(painlessmesh::plugin::ota::DataRequest,
321
+ char* buffer)>
322
+ otaDataPacketCallbackType_t;
323
+
324
+ template <class T>
325
+ void addSendPackageCallback(Scheduler& scheduler,
326
+ plugin::PackageHandler<T>& mesh,
327
+ otaDataPacketCallbackType_t callback,
328
+ size_t otaPartSize) {
329
+ using namespace logger;
330
+ #if defined(ESP32) || defined(ESP8266)
331
+ mesh.onPackage(
332
+ (int)OTA_OP_CODES::DATA_REQUEST,
333
+ [&mesh, callback, otaPartSize](painlessmesh::protocol::Variant& variant) {
334
+ auto pkg = variant.to<painlessmesh::plugin::ota::DataRequest>();
335
+ char buffer[otaPartSize + 1];
336
+ memset(buffer, 0, otaPartSize + 1);
337
+ auto size = callback(pkg, buffer);
338
+ // Handle zero size
339
+ if (!size) {
340
+ // No data is available by the user app.
341
+
342
+ // todo - doubtful, shall we return true or false. What is the purpose
343
+ // of this return value.
344
+ return true;
345
+ }
346
+ // Encode data as base64 so there are no null characters and can be
347
+ // shown in plaintext
348
+ auto b64Data =
349
+ painlessmesh::base64::encode((unsigned char*)buffer, size);
350
+ auto reply =
351
+ painlessmesh::plugin::ota::Data::replyTo(pkg, b64Data, pkg.partNo);
352
+ mesh.sendPackage(&reply);
353
+ return true;
354
+ });
355
+
356
+ #endif
357
+ }
358
+
359
+ template <class T>
360
+ void addReceivePackageCallback(
361
+ Scheduler& scheduler, plugin::PackageHandler<T>& mesh, TSTRING role = "",
362
+ std::function<void(int, int)> progress_cb = NULL) {
363
+ using namespace logger;
364
+ #if defined(ESP32) || defined(ESP8266)
365
+ auto currentFW = std::make_shared<State>();
366
+ currentFW->role = role;
367
+ auto updateFW = std::make_shared<State>();
368
+ updateFW->role = role;
369
+ #ifdef USE_FS_SPIFFS
370
+ SPIFFS.begin(true); // Start the SPI Flash Files System
371
+ if (SPIFFS.exists(currentFW->ota_fn)) {
372
+ auto file = SPIFFS.open(currentFW->ota_fn, "r");
373
+ #else
374
+ LittleFS.begin(); // Start the SPI Flash Files System
375
+ if (LittleFS.exists(currentFW->ota_fn)) {
376
+ auto file = LittleFS.open(currentFW->ota_fn, "r");
377
+ #endif
378
+ TSTRING msg = "";
379
+ while (file.available()) {
380
+ msg += (char)file.read();
381
+ }
382
+ protocol::Variant var(msg);
383
+ auto fw = var.to<State>();
384
+ if (fw.role == role && fw.hardware == currentFW->hardware) {
385
+ Log(DEBUG, "MD5 found %s\n", fw.md5.c_str());
386
+ currentFW->md5 = fw.md5;
387
+ }
388
+ }
389
+
390
+ mesh.onPackage((int)OTA_OP_CODES::ANNOUNCE, [currentFW, updateFW, &mesh,
391
+ &scheduler](
392
+ protocol::Variant& variant) {
393
+ // convert variant to Announce
394
+ auto pkg = variant.to<Announce>();
395
+ // Check if we want the update
396
+ if (currentFW->role == pkg.role && currentFW->hardware == pkg.hardware) {
397
+ if ((currentFW->md5 == pkg.md5 && !pkg.forced) ||
398
+ updateFW->md5 == pkg.md5) {
399
+ // Either already have it, or already updating to it
400
+ return false;
401
+ } else {
402
+ updateFW->md5 = pkg.md5;
403
+ updateFW->broadcasted = pkg.broadcasted;
404
+ // If we are not in broadcasted mode, or we are the root node, begin
405
+ // requesting data
406
+ if (!pkg.broadcasted || mesh.isRoot()) {
407
+ auto request =
408
+ DataRequest::replyTo(pkg, mesh.getNodeId(), updateFW->partNo);
409
+ // enable the request task
410
+ updateFW->task =
411
+ mesh.addTask(scheduler, 30 * TASK_SECOND, 10,
412
+ [request, &mesh]() { mesh.sendPackage(&request); });
413
+ updateFW->task->setOnDisable([updateFW]() {
414
+ Log(ERROR, "OTA: Did not receive the requested data.\n");
415
+ updateFW->md5 = "";
416
+ });
417
+ }
418
+ }
419
+ }
420
+ return false;
421
+ });
422
+
423
+ // mesh.onPackage(11, [currentFW](protocol::Variant &variant) {
424
+ // Log(ERROR, "Data request should not be send to this node\n");
425
+ // return false;
426
+ // });
427
+
428
+ mesh.onPackage((int)OTA_OP_CODES::DATA, [currentFW, updateFW, progress_cb,
429
+ &mesh, &scheduler](
430
+ protocol::Variant& variant) {
431
+ auto pkg = variant.to<Data>();
432
+ // Check whether it is a new part, of correct md5 role etc etc
433
+ if (updateFW->md5 == pkg.md5 && updateFW->role == pkg.role &&
434
+ updateFW->hardware == pkg.hardware) {
435
+ // Have we have received the next chunk of firmware in sequence?
436
+ if (updateFW->partNo == pkg.partNo) {
437
+ if (progress_cb != NULL) {
438
+ progress_cb(pkg.partNo, pkg.noPart);
439
+ }
440
+ if (pkg.partNo == 0) {
441
+ #ifdef ESP32
442
+ uint32_t maxSketchSpace = UPDATE_SIZE_UNKNOWN;
443
+ #else
444
+ uint32_t maxSketchSpace =
445
+ (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
446
+ #endif
447
+ Log(DEBUG, "Sketch size %d\n", maxSketchSpace);
448
+ if (Update.isRunning()) {
449
+ Update.end(false);
450
+ }
451
+ if (!Update.begin(maxSketchSpace)) { // start with max available size
452
+ Log(DEBUG, "handleOTA(): OTA start failed!");
453
+ Update.printError(Serial);
454
+ Update.end();
455
+ } else {
456
+ Update.setMD5(pkg.md5.c_str());
457
+ }
458
+ }
459
+
460
+ // write data
461
+ auto b64Data = base64::decode(pkg.data);
462
+ if (Update.write((uint8_t*)b64Data.c_str(), b64Data.length()) !=
463
+ b64Data.length()) {
464
+ Log(ERROR, "handleOTA(): OTA write failed!");
465
+ Update.printError(Serial);
466
+ Update.end();
467
+ updateFW->md5 = "";
468
+ updateFW->partNo = 0;
469
+ return false;
470
+ }
471
+
472
+ // If last part then write ota_fn and reboot
473
+ if (pkg.partNo == pkg.noPart - 1) {
474
+ // check md5, reboot
475
+ if (Update.end(true)) { // true to set the size to the
476
+ // current progress
477
+ #ifdef USE_FS_SPIFFS
478
+ auto file = SPIFFS.open(updateFW->ota_fn, "w");
479
+ if (!file) {
480
+ Log(ERROR,
481
+ "handleOTA(): Unable to write md5 of new update to the "
482
+ "SPIFFS "
483
+ "file. This will result in endless update loops for OTA\n");
484
+ }
485
+ #else
486
+ auto file = LittleFS.open(updateFW->ota_fn, "w");
487
+ if (!file) {
488
+ Log(ERROR, "handleOTA(): Unable to write md5 of new binary to the LittleFS file. This will result in endless update loops for OTA\n");
489
+ }
490
+ #endif
491
+
492
+ String msg;
493
+ protocol::Variant var(updateFW.get());
494
+ var.printTo(msg);
495
+ file.print(msg);
496
+ file.close();
497
+
498
+ Log(DEBUG, "handleOTA(): OTA Success! %s, %s\n", msg.c_str(),
499
+ updateFW->role.c_str());
500
+ // Delay restart by 2 seconds to allow mesh activity to finish
501
+ mesh.addTask(scheduler, 2 * TASK_SECOND, TASK_ONCE,
502
+ []() { ESP.restart(); })
503
+ ->enableDelayed();
504
+ } else {
505
+ Log(DEBUG, "handleOTA(): OTA failed!\n");
506
+ Update.printError(Serial);
507
+ updateFW->md5 = "";
508
+ updateFW->partNo = 0;
509
+ }
510
+ if (updateFW->task != NULL) {
511
+ updateFW->task->setOnDisable(NULL);
512
+ updateFW->task->disable();
513
+ }
514
+ } else {
515
+ // else request more
516
+ ++updateFW->partNo;
517
+ if (updateFW->task != NULL) {
518
+ auto request = DataRequest::replyTo(pkg, updateFW->partNo);
519
+ updateFW->task->setCallback(
520
+ [request, &mesh]() { mesh.sendPackage(&request); });
521
+ // updateFW->task->disable();
522
+ updateFW->task->restart();
523
+ }
524
+ }
525
+ } else {
526
+ // We are out-of-sequence, resume with non-broadcasted update
527
+ if (updateFW->broadcasted && pkg.broadcasted) {
528
+ Log(DEBUG, "Out of sequence packet! We may have missed a packet?");
529
+ // Drop of out broadcasted mode
530
+ updateFW->broadcasted = false;
531
+ auto request = DataRequest::replyTo(pkg, updateFW->partNo);
532
+ request.broadcasted = false;
533
+ updateFW->task =
534
+ mesh.addTask(scheduler, 30 * TASK_SECOND, 10,
535
+ [request, &mesh]() { mesh.sendPackage(&request); });
536
+ updateFW->task->setOnDisable([updateFW]() {
537
+ Log(ERROR, "OTA: Did not receive the requested data.\n");
538
+ updateFW->md5 = "";
539
+ });
540
+ }
541
+ }
542
+ }
543
+ return false;
544
+ });
545
+
546
+ #endif
547
+ }
548
+
549
+ } // namespace ota
550
+ } // namespace plugin
551
+ } // namespace painlessmesh
552
+
553
+ #endif