fix: stop the 5 s boot wait, tell the client about a bad restore

NTPSettings::apply() waited up to 5 s in getLocalTime() for a clock that
can't be set before the network is up, then returned before it set TZ.
Every cold boot was 5 s slower and ran without its time zone.  It now
calls configTzTime(), which sets the zone and starts SNTP without
waiting, and save() goes through apply().  /setgeneral checked
ntpServer twice, so a request that changed only the zone was ignored.

A restore ignored the result of ShadeConfigFile::restore() and rebooted
either way, so a bad file failed with no word to the user.  The /restore
reply now validates the file and answers 400, which the restore dialog
shows, and an incomplete upload gets a 400 instead of no reply.  A
failed restore changes nothing, so loop() no longer reboots after one.
/shades.tmp, a full copy of the backup with its rolling codes, is
deleted after every restore and after /updateShadeConfig.

The 6 extra bytes in the 2026-10-18 backup: TRANS_REC_SIZE stayed 74
when noiseDetection was added to the 80-byte transceiver record, so
every backup since declares it short.  It is 80 now.  The size check
stays a warning so those backups still restore, and the sum moves to
expectedFileBytes() with tests.  The repeater record length is checked
as a warning only, since validate() also runs on shades.cfg at boot.

Commands for an unknown shade, tilt or group now log a warning, and a
socket that never pinged says so.  KNOWN_ISSUES.md is updated, including
the real cause of the ~365 ms loop stall (radio_tx busy-waits each frame
on loop()'s core).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-19 18:08:16 +02:00
parent 4b75e8fb08
commit 953d4dc6f7
11 changed files with 168 additions and 77 deletions

View file

@ -13,7 +13,10 @@ static const char *TAG = "ConfigFile";
#define SHADE_HDR_SIZE 76
#define SHADE_REC_SIZE 276
#define GROUP_REC_SIZE 200
#define TRANS_REC_SIZE 74
// writeTransRecord: enabled, 8 one-byte fields, 3 floats, txPower and noiseDetection.
// It was left at 74 when noiseDetection was added, so files written before
// the fix declare this record 6 bytes short.
#define TRANS_REC_SIZE 80
#define ROOM_REC_SIZE 29
#define REPEATER_REC_SIZE 77
@ -436,20 +439,11 @@ bool ShadeConfigFile::validate() {
return false;
}
// We should know the file size based upon the record information in the header
uint32_t fsize = this->header.length + (this->header.shadeRecordSize * this->header.shadeRecords);
if(this->header.version > 10) fsize += (this->header.groupRecordSize * this->header.groupRecords);
if(this->header.version >= 19) fsize += (this->header.roomRecordSize * this->header.roomRecords);
if(this->header.version > 13) {
fsize += (this->header.settingsRecordSize);
fsize += (this->header.netRecordSize);
fsize += (this->header.transRecordSize);
}
if(this->header.version >= 21) {
fsize += (this->header.repeaterRecordSize * this->header.repeaterRecords);
}
// Only a warning: older backups declare the transceiver record 6 bytes
// short (see TRANS_REC_SIZE), and the record checks below catch real damage.
const uint32_t fsize = expectedFileBytes(this->header);
if(this->file.size() != fsize) {
ESP_LOGE(TAG, "File size is not correct should be %d and got %d", fsize, this->file.size());
ESP_LOGW(TAG, "File is %u bytes but its header adds up to %u", (unsigned)this->file.size(), (unsigned)fsize);
}
// Next check to see if the records match the header length.
uint8_t recs = 0;
@ -499,12 +493,16 @@ bool ShadeConfigFile::validate() {
if(this->header.version >= 21) {
recs = 0;
while(recs < this->header.repeaterRecords) {
//uint32_t pos = this->file.position();
// Only logged: this also runs on shades.cfg at boot, and failing it
// there would start the device without its shades.
uint32_t pos = this->file.position();
if(!this->seekChar(CFG_REC_END)) {
ESP_LOGE(TAG, "Failed to find the repeater record end %d", recs);
}
else if(this->file.position() - pos != this->header.repeaterRecordSize) {
ESP_LOGW(TAG, "Repeater record length is %d and should be %d", this->file.position() - pos, this->header.repeaterRecordSize);
}
recs++;
}
}
this->file.seek(startPos, SeekSet);
@ -519,6 +517,14 @@ bool ShadeConfigFile::load(SomfyShadeController *s, const char *filename) {
}
return false;
}
bool ShadeConfigFile::validateFile(const char *filename) {
if(!LittleFS.exists(filename)) return false;
ShadeConfigFile file;
file.begin(filename, true);
const bool valid = file.validate();
file.end();
return valid;
}
bool ShadeConfigFile::restore(SomfyShadeController *s, const char *filename, restore_options_t &opts) {
ShadeConfigFile file;
if(file.begin(filename, true)) {