@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.
- package/CHANGELOG.md +144 -0
- package/LICENSE +674 -0
- package/README.md +434 -0
- package/RELEASE_GUIDE.md +419 -0
- package/docs/DOCUMENTATION_MIGRATION_PLAN.md +176 -0
- package/docs/README.md +71 -0
- package/docs/alteriom/overview.md +508 -0
- package/docs/api/core-api.md +607 -0
- package/docs/architecture/mesh-architecture.md +379 -0
- package/docs/architecture/plugin-system.md +517 -0
- package/docs/getting-started/first-mesh.md +410 -0
- package/docs/getting-started/installation.md +275 -0
- package/docs/getting-started/quickstart.md +158 -0
- package/docs/improvements/README.md +69 -0
- package/docs/troubleshooting/common-issues.md +521 -0
- package/docs/troubleshooting/faq.md +473 -0
- package/docs/tutorials/basic-examples.md +718 -0
- package/docs/wiki/API-Reference.md +246 -0
- package/docs/wiki/Complete-Documentation.md +123 -0
- package/examples/alteriom/README.md +82 -0
- package/examples/alteriom/alteriom.ino +186 -0
- package/examples/alteriom/alteriom_sensor_node.ino +184 -0
- package/examples/alteriom/alteriom_sensor_package.hpp +128 -0
- package/examples/alteriom/improved_sensor_node.ino +246 -0
- package/examples/alteriom/platformio.ini +25 -0
- package/examples/basic/basic.ino +66 -0
- package/examples/basic/platformio.ini +25 -0
- package/examples/bridge/bridge.ino +51 -0
- package/examples/bridge/platformio.ini +25 -0
- package/examples/echoNode/echoNode.ino +33 -0
- package/examples/echoNode/platformio.ini +25 -0
- package/examples/logClient/logClient.ino +109 -0
- package/examples/logClient/platformio.ini +25 -0
- package/examples/logServer/logServer.ino +81 -0
- package/examples/logServer/platformio.ini +25 -0
- package/examples/mqttBridge/mqttBridge.ino +118 -0
- package/examples/mqttBridge/platformio.ini +26 -0
- package/examples/namedMesh/namedMesh.ino +97 -0
- package/examples/namedMesh/platformio.ini +25 -0
- package/examples/otaReceiver/otaReceiver.ino +79 -0
- package/examples/otaReceiver/platformio.ini +25 -0
- package/examples/otaSender/nodemcu32s_connections.JPG +0 -0
- package/examples/otaSender/otaSender.ino +151 -0
- package/examples/otaSender/platformio.ini +25 -0
- package/examples/startHere/platformio.ini +25 -0
- package/examples/startHere/startHere.ino +159 -0
- package/examples/webServer/platformio.ini +27 -0
- package/examples/webServer/webServer.ino +89 -0
- package/keywords.txt +49 -0
- package/library.json +34 -0
- package/library.properties +11 -0
- package/package.json +78 -0
- package/src/AlteriomPainlessMesh.h +98 -0
- package/src/arduino/wifi.hpp +365 -0
- package/src/boost/asynctcp.hpp +279 -0
- package/src/painlessMesh.h +70 -0
- package/src/painlessMeshSTA.cpp +236 -0
- package/src/painlessMeshSTA.h +58 -0
- package/src/painlessTaskOptions.h +4 -0
- package/src/painlessmesh/base64.hpp +111 -0
- package/src/painlessmesh/buffer.hpp +229 -0
- package/src/painlessmesh/callback.hpp +91 -0
- package/src/painlessmesh/configuration.hpp +77 -0
- package/src/painlessmesh/connection.hpp +192 -0
- package/src/painlessmesh/layout.hpp +188 -0
- package/src/painlessmesh/logger.hpp +158 -0
- package/src/painlessmesh/memory.hpp +120 -0
- package/src/painlessmesh/mesh.hpp +560 -0
- package/src/painlessmesh/metrics.hpp +323 -0
- package/src/painlessmesh/ntp.hpp +263 -0
- package/src/painlessmesh/ota.hpp +553 -0
- package/src/painlessmesh/plugin.hpp +188 -0
- package/src/painlessmesh/protocol.hpp +813 -0
- package/src/painlessmesh/router.hpp +322 -0
- package/src/painlessmesh/tcp.hpp +71 -0
- package/src/painlessmesh/validation.hpp +239 -0
- package/src/plugin/performance.hpp +214 -0
- package/src/plugin/remote.hpp +64 -0
- package/src/scheduler.cpp +10 -0
- 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
|