Skip to content

Commit 2d0771f

Browse files
authored
Merge pull request #4840 from DedeHai/0_15_x_bootloopdetect
Backport of Bootloop detection & recovery (#4793)
2 parents 1706fdc + 7d0a338 commit 2d0771f

File tree

6 files changed

+363
-5
lines changed

6 files changed

+363
-5
lines changed

wled00/cfg.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,9 +636,32 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
636636
return (doc["sv"] | true);
637637
}
638638

639-
640639
static const char s_cfg_json[] PROGMEM = "/cfg.json";
641640

641+
bool backupConfig() {
642+
return backupFile(s_cfg_json);
643+
}
644+
645+
bool restoreConfig() {
646+
return restoreFile(s_cfg_json);
647+
}
648+
649+
bool verifyConfig() {
650+
return validateJsonFile(s_cfg_json);
651+
}
652+
653+
// rename config file and reboot
654+
// if the cfg file doesn't exist, such as after a reset, do nothing
655+
void resetConfig() {
656+
if (WLED_FS.exists(s_cfg_json)) {
657+
DEBUG_PRINTLN(F("Reset config"));
658+
char backupname[32];
659+
snprintf_P(backupname, sizeof(backupname), PSTR("/rst.%s"), &s_cfg_json[1]);
660+
WLED_FS.rename(s_cfg_json, backupname);
661+
doReboot = true;
662+
}
663+
}
664+
642665
bool deserializeConfigFromFS() {
643666
[[maybe_unused]] bool success = deserializeConfigSec();
644667
#ifdef WLED_ADD_EEPROM_SUPPORT
@@ -676,6 +699,7 @@ bool deserializeConfigFromFS() {
676699

677700
void serializeConfig() {
678701
serializeConfigSec();
702+
backupConfig(); // backup before writing new config
679703

680704
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
681705

wled00/fcn_declare.h

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ void handleIO();
2424
void IRAM_ATTR touchButtonISR();
2525

2626
//cfg.cpp
27+
bool backupConfig();
28+
bool restoreConfig();
29+
bool verifyConfig();
30+
void resetConfig();
2731
bool deserializeConfig(JsonObject doc, bool fromFS = false);
2832
bool deserializeConfigFromFS();
2933
bool deserializeConfigSec();
@@ -114,10 +118,15 @@ bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest
114118
bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest);
115119
void updateFSInfo();
116120
void closeFile();
117-
inline bool writeObjectToFileUsingId(const String &file, uint16_t id, JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); };
118-
inline bool writeObjectToFile(const String &file, const char* key, JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); };
119-
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
120-
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest) { return readObjectFromFile(file.c_str(), key, dest); };
121+
inline bool writeObjectToFileUsingId(const String &file, uint16_t id, const JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); };
122+
inline bool writeObjectToFile(const String &file, const char* key, const JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); };
123+
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
124+
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest); };
125+
bool copyFile(const char* src_path, const char* dst_path);
126+
bool backupFile(const char* filename);
127+
bool restoreFile(const char* filename);
128+
bool validateJsonFile(const char* filename);
129+
void dumpFilesToSerial();
121130

122131
//hue.cpp
123132
void handleHue();
@@ -399,6 +408,15 @@ void enumerateLedmaps();
399408
uint8_t get_random_wheel_index(uint8_t pos);
400409
float mapf(float x, float in_min, float in_max, float out_min, float out_max);
401410

411+
void handleBootLoop(); // detect and handle bootloops
412+
#ifndef ESP8266
413+
void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
414+
#endif
415+
416+
void handleBootLoop(); // detect and handle bootloops
417+
#ifndef ESP8266
418+
void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
419+
#endif
402420
// RAII guard class for the JSON Buffer lock
403421
// Modeled after std::lock_guard
404422
class JSONBufferGuard {

wled00/file.cpp

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,156 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
438438
}
439439
return false;
440440
}
441+
442+
// copy a file, delete destination file if incomplete to prevent corrupted files
443+
bool copyFile(const char* src_path, const char* dst_path) {
444+
DEBUG_PRINTF("copyFile from %s to %s\n", src_path, dst_path);
445+
if(!WLED_FS.exists(src_path)) {
446+
DEBUG_PRINTLN(F("file not found"));
447+
return false;
448+
}
449+
450+
bool success = true; // is set to false on error
451+
File src = WLED_FS.open(src_path, "r");
452+
File dst = WLED_FS.open(dst_path, "w");
453+
454+
if (src && dst) {
455+
uint8_t buf[128]; // copy file in 128-byte blocks
456+
while (src.available() > 0) {
457+
size_t bytesRead = src.read(buf, sizeof(buf));
458+
if (bytesRead == 0) {
459+
success = false;
460+
break; // error, no data read
461+
}
462+
size_t bytesWritten = dst.write(buf, bytesRead);
463+
if (bytesWritten != bytesRead) {
464+
success = false;
465+
break; // error, not all data written
466+
}
467+
}
468+
} else {
469+
success = false; // error, could not open files
470+
}
471+
if(src) src.close();
472+
if(dst) dst.close();
473+
if (!success) {
474+
DEBUG_PRINTLN(F("copy failed"));
475+
WLED_FS.remove(dst_path); // delete incomplete file
476+
}
477+
return success;
478+
}
479+
480+
// compare two files, return true if identical
481+
bool compareFiles(const char* path1, const char* path2) {
482+
DEBUG_PRINTF("compareFile %s and %s\n", path1, path2);
483+
if (!WLED_FS.exists(path1) || !WLED_FS.exists(path2)) {
484+
DEBUG_PRINTLN(F("file not found"));
485+
return false;
486+
}
487+
488+
bool identical = true; // set to false on mismatch
489+
File f1 = WLED_FS.open(path1, "r");
490+
File f2 = WLED_FS.open(path2, "r");
491+
492+
if (f1 && f2) {
493+
uint8_t buf1[128], buf2[128];
494+
while (f1.available() > 0 || f2.available() > 0) {
495+
size_t len1 = f1.read(buf1, sizeof(buf1));
496+
size_t len2 = f2.read(buf2, sizeof(buf2));
497+
498+
if (len1 != len2) {
499+
identical = false;
500+
break; // files differ in size or read failed
501+
}
502+
503+
if (memcmp(buf1, buf2, len1) != 0) {
504+
identical = false;
505+
break; // files differ in content
506+
}
507+
}
508+
} else {
509+
identical = false; // error opening files
510+
}
511+
512+
if (f1) f1.close();
513+
if (f2) f2.close();
514+
return identical;
515+
}
516+
517+
static const char s_backup_fmt[] PROGMEM = "/bkp.%s";
518+
519+
bool backupFile(const char* filename) {
520+
DEBUG_PRINTF("backup %s \n", filename);
521+
if (!validateJsonFile(filename)) {
522+
DEBUG_PRINTLN(F("broken file"));
523+
return false;
524+
}
525+
char backupname[32];
526+
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
527+
528+
if (copyFile(filename, backupname)) {
529+
DEBUG_PRINTLN(F("backup ok"));
530+
return true;
531+
}
532+
DEBUG_PRINTLN(F("backup failed"));
533+
return false;
534+
}
535+
536+
bool restoreFile(const char* filename) {
537+
DEBUG_PRINTF("restore %s \n", filename);
538+
char backupname[32];
539+
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
540+
541+
if (!WLED_FS.exists(backupname)) {
542+
DEBUG_PRINTLN(F("no backup found"));
543+
return false;
544+
}
545+
546+
if (!validateJsonFile(backupname)) {
547+
DEBUG_PRINTLN(F("broken backup"));
548+
return false;
549+
}
550+
551+
if (copyFile(backupname, filename)) {
552+
DEBUG_PRINTLN(F("restore ok"));
553+
return true;
554+
}
555+
DEBUG_PRINTLN(F("restore failed"));
556+
return false;
557+
}
558+
559+
bool validateJsonFile(const char* filename) {
560+
if (!WLED_FS.exists(filename)) return false;
561+
File file = WLED_FS.open(filename, "r");
562+
if (!file) return false;
563+
StaticJsonDocument<0> doc, filter; // https://arduinojson.org/v6/how-to/validate-json/
564+
bool result = deserializeJson(doc, file, DeserializationOption::Filter(filter)) == DeserializationError::Ok;
565+
file.close();
566+
if (!result) {
567+
DEBUG_PRINTF_P(PSTR("Invalid JSON file %s\n"), filename);
568+
} else {
569+
DEBUG_PRINTF_P(PSTR("Valid JSON file %s\n"), filename);
570+
}
571+
return result;
572+
}
573+
574+
// print contents of all files in root dir to Serial except wsec files
575+
void dumpFilesToSerial() {
576+
File rootdir = WLED_FS.open("/", "r");
577+
File rootfile = rootdir.openNextFile();
578+
while (rootfile) {
579+
size_t len = strlen(rootfile.name());
580+
// skip files starting with "wsec" and dont end in .json
581+
if (strncmp(rootfile.name(), "wsec", 4) != 0 && len >= 6 && strcmp(rootfile.name() + len - 5, ".json") == 0) {
582+
Serial.println(rootfile.name());
583+
while (rootfile.available()) {
584+
Serial.write(rootfile.read());
585+
}
586+
Serial.println();
587+
Serial.println();
588+
}
589+
rootfile.close();
590+
rootfile = rootdir.openNextFile();
591+
}
592+
}
593+

wled00/ota_update.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ static void endOTA(AsyncWebServerRequest *request) {
7373
// If the upload is incomplete, Update.end(false) should error out.
7474
if (Update.end(context->uploadComplete)) {
7575
// Update successful!
76+
#ifndef ESP8266
77+
bootloopCheckOTA(); // let the bootloop-checker know there was an OTA update
78+
#endif
7679
doReboot = true;
7780
context->needsRestart = false;
7881
}
@@ -109,6 +112,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
109112
strip.suspend();
110113
strip.resetSegments(); // free as much memory as you can
111114
context->needsRestart = true;
115+
backupConfig(); // backup current config in case the update ends badly
112116

113117
DEBUG_PRINTF_P(PSTR("OTA Update Start, %x --> %x\n"), (uintptr_t)request,(uintptr_t) context);
114118

0 commit comments

Comments
 (0)