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