diff --git a/lib/libesp32/Berry-HttpClientLight/src/HTTPUpdateLight.cpp b/lib/libesp32/Berry-HttpClientLight/src/HTTPUpdateLight.cpp index 694502ea5..a3c798f1c 100644 --- a/lib/libesp32/Berry-HttpClientLight/src/HTTPUpdateLight.cpp +++ b/lib/libesp32/Berry-HttpClientLight/src/HTTPUpdateLight.cpp @@ -117,7 +117,7 @@ String HTTPUpdateLight::getLastErrorString(void) // error from Update class if(_lastError > 0) { StreamString error; - Update.printError(error); + TasUpdate.printError(error); error.trim(); // remove line ending return String("Update error: ") + error; } @@ -418,14 +418,17 @@ bool HTTPUpdateLight::runUpdate(Stream& in, uint32_t size, String md5, int comma StreamString error; if (_cbProgress) { - Update.onProgress(_cbProgress); + TasUpdate.onProgress(_cbProgress); } - if(!Update.begin(size, command, _ledPin, _ledOn)) { - _lastError = Update.getError(); - Update.printError(error); +// Start Tasmota Factory patch +// if(!Update.begin(size, command, _ledPin, _ledOn)) { + if(!TasUpdate.begin(size, command, _ledPin, _ledOn, NULL, _factory)) { +// End Tasmota Factory patch + _lastError = TasUpdate.getError(); + TasUpdate.printError(error); error.trim(); // remove line ending - log_e("Update.begin failed! (%s)\n", error.c_str()); + log_e("TasUpdate.begin failed! (%s)\n", error.c_str()); return false; } @@ -434,20 +437,20 @@ bool HTTPUpdateLight::runUpdate(Stream& in, uint32_t size, String md5, int comma } if(md5.length()) { - if(!Update.setMD5(md5.c_str())) { + if(!TasUpdate.setMD5(md5.c_str())) { _lastError = HTTP_UE_SERVER_FAULTY_MD5; - log_e("Update.setMD5 failed! (%s)\n", md5.c_str()); + log_e("TasUpdate.setMD5 failed! (%s)\n", md5.c_str()); return false; } } // To do: the SHA256 could be checked if the server sends it - if(Update.writeStream(in) != size) { - _lastError = Update.getError(); - Update.printError(error); + if(TasUpdate.writeStream(in) != size) { + _lastError = TasUpdate.getError(); + TasUpdate.printError(error); error.trim(); // remove line ending - log_e("Update.writeStream failed! (%s)\n", error.c_str()); + log_e("TasUpdate.writeStream failed! (%s)\n", error.c_str()); return false; } @@ -455,11 +458,11 @@ bool HTTPUpdateLight::runUpdate(Stream& in, uint32_t size, String md5, int comma _cbProgress(size, size); } - if(!Update.end()) { - _lastError = Update.getError(); - Update.printError(error); + if(!TasUpdate.end()) { + _lastError = TasUpdate.getError(); + TasUpdate.printError(error); error.trim(); // remove line ending - log_e("Update.end failed! (%s)\n", error.c_str()); + log_e("TasUpdate.end failed! (%s)\n", error.c_str()); return false; } diff --git a/lib/libesp32/Berry-HttpClientLight/src/HTTPUpdateLight.h b/lib/libesp32/Berry-HttpClientLight/src/HTTPUpdateLight.h index cbed38bd6..372f9f1b0 100644 --- a/lib/libesp32/Berry-HttpClientLight/src/HTTPUpdateLight.h +++ b/lib/libesp32/Berry-HttpClientLight/src/HTTPUpdateLight.h @@ -31,7 +31,7 @@ #include #include #include -#include +#include #include /// note we use HTTP client errors too so we start at 100 @@ -69,7 +69,7 @@ public: { _rebootOnUpdate = reboot; } - + /** * set redirect follow mode. See `followRedirects_t` enum for avaliable modes. * @param follow @@ -85,6 +85,13 @@ public: _ledOn = ledOn; } +// Start Tasmota Factory patch + void setFactory(bool factory = false) + { + _factory = factory; + } +// End Tasmota Factory patch + // t_httpUpdate_return update(WiFiClient& client, const String& url, const String& currentVersion = ""); // t_httpUpdate_return update(WiFiClient& client, const String& host, uint16_t port, const String& uri = "/", @@ -131,6 +138,9 @@ private: int _ledPin; uint8_t _ledOn; +// Start Tasmota Factory patch + bool _factory; +// End Tasmota Factory patch }; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE) diff --git a/lib/libesp32/Berry-HttpClientLight/src/TasUpdate.h b/lib/libesp32/Berry-HttpClientLight/src/TasUpdate.h new file mode 100644 index 000000000..0aaac13b3 --- /dev/null +++ b/lib/libesp32/Berry-HttpClientLight/src/TasUpdate.h @@ -0,0 +1,194 @@ +#ifndef TASUPDATER_H +#define TASUPDATER_H + +#include +#include +#include +#include "esp_partition.h" + +#define UPDATE_ERROR_OK (0) +#define UPDATE_ERROR_WRITE (1) +#define UPDATE_ERROR_ERASE (2) +#define UPDATE_ERROR_READ (3) +#define UPDATE_ERROR_SPACE (4) +#define UPDATE_ERROR_SIZE (5) +#define UPDATE_ERROR_STREAM (6) +#define UPDATE_ERROR_MD5 (7) +#define UPDATE_ERROR_MAGIC_BYTE (8) +#define UPDATE_ERROR_ACTIVATE (9) +#define UPDATE_ERROR_NO_PARTITION (10) +#define UPDATE_ERROR_BAD_ARGUMENT (11) +#define UPDATE_ERROR_ABORT (12) + +#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF + +#define U_FLASH 0 +#define U_SPIFFS 100 +#define U_AUTH 200 + +#define ENCRYPTED_BLOCK_SIZE 16 + +class TasUpdateClass { + public: + typedef std::function THandlerFunction_Progress; + + TasUpdateClass(); + + /* + This callback will be called when Update is receiving data + */ + TasUpdateClass& onProgress(THandlerFunction_Progress fn); + + /* + Call this to check the space needed for the update + Will return false if there is not enough space + */ +// Start Tasmota Factory patch +// bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL); + bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL, bool factory = false); +// End Tasmota Factory patch + + /* + Writes a buffer to the flash and increments the address + Returns the amount written + */ + size_t write(uint8_t *data, size_t len); + + /* + Writes the remaining bytes from the Stream to the flash + Uses readBytes() and sets UPDATE_ERROR_STREAM on timeout + Returns the bytes written + Should be equal to the remaining bytes when called + Usable for slow streams like Serial + */ + size_t writeStream(Stream &data); + + /* + If all bytes are written + this call will write the config to eboot + and return true + If there is already an update running but is not finished and !evenIfRemaining + or there is an error + this will clear everything and return false + the last error is available through getError() + evenIfRemaining is helpfull when you update without knowing the final size first + */ + bool end(bool evenIfRemaining = false); + + /* + Aborts the running update + */ + void abort(); + + /* + Prints the last error to an output stream + */ + void printError(Print &out); + + const char * errorString(); + + /* + sets the expected MD5 for the firmware (hexString) + */ + bool setMD5(const char * expected_md5); + + /* + returns the MD5 String of the successfully ended firmware + */ + String md5String(void){ return _md5.toString(); } + + /* + populated the result with the md5 bytes of the successfully ended firmware + */ + void md5(uint8_t * result){ return _md5.getBytes(result); } + + //Helpers + uint8_t getError(){ return _error; } + void clearError(){ _error = UPDATE_ERROR_OK; } + bool hasError(){ return _error != UPDATE_ERROR_OK; } + bool isRunning(){ return _size > 0; } + bool isFinished(){ return _progress == _size; } + size_t size(){ return _size; } + size_t progress(){ return _progress; } + size_t remaining(){ return _size - _progress; } + + /* + Template to write from objects that expose + available() and read(uint8_t*, size_t) methods + faster than the writeStream method + writes only what is available + */ + template + size_t write(T &data){ + size_t written = 0; + if (hasError() || !isRunning()) + return 0; + + size_t available = data.available(); + while(available) { + if(_bufferLen + available > remaining()){ + available = remaining() - _bufferLen; + } + if(_bufferLen + available > 4096) { + size_t toBuff = 4096 - _bufferLen; + data.read(_buffer + _bufferLen, toBuff); + _bufferLen += toBuff; + if(!_writeBuffer()) + return written; + written += toBuff; + } else { + data.read(_buffer + _bufferLen, available); + _bufferLen += available; + written += available; + if(_bufferLen == remaining()) { + if(!_writeBuffer()) { + return written; + } + } + } + if(remaining() == 0) + return written; + available = data.available(); + } + return written; + } + + /* + check if there is a firmware on the other OTA partition that you can bootinto + */ + bool canRollBack(); + /* + set the other OTA partition as bootable (reboot to enable) + */ + bool rollBack(); + + private: + void _reset(); + void _abort(uint8_t err); + bool _writeBuffer(); + bool _verifyHeader(uint8_t data); + bool _verifyEnd(); + bool _enablePartition(const esp_partition_t* partition); + + + uint8_t _error; + uint8_t *_buffer; + uint8_t *_skipBuffer; + size_t _bufferLen; + size_t _size; + THandlerFunction_Progress _progress_callback; + uint32_t _progress; + uint32_t _paroffset; + uint32_t _command; + const esp_partition_t* _partition; + + String _target_md5; + MD5Builder _md5; + + int _ledPin; + uint8_t _ledOn; +}; + +extern TasUpdateClass TasUpdate; + +#endif // TASUPDATER_H diff --git a/lib/libesp32/Berry-HttpClientLight/src/TasUpdater.cpp b/lib/libesp32/Berry-HttpClientLight/src/TasUpdater.cpp new file mode 100644 index 000000000..e359ff96f --- /dev/null +++ b/lib/libesp32/Berry-HttpClientLight/src/TasUpdater.cpp @@ -0,0 +1,404 @@ +#include "TasUpdate.h" +#include "Arduino.h" +#include "esp_spi_flash.h" +#include "esp_ota_ops.h" +#include "esp_image_format.h" + +static const char * _err2str(uint8_t _error){ + if(_error == UPDATE_ERROR_OK){ + return ("No Error"); + } else if(_error == UPDATE_ERROR_WRITE){ + return ("Flash Write Failed"); + } else if(_error == UPDATE_ERROR_ERASE){ + return ("Flash Erase Failed"); + } else if(_error == UPDATE_ERROR_READ){ + return ("Flash Read Failed"); + } else if(_error == UPDATE_ERROR_SPACE){ + return ("Not Enough Space"); + } else if(_error == UPDATE_ERROR_SIZE){ + return ("Bad Size Given"); + } else if(_error == UPDATE_ERROR_STREAM){ + return ("Stream Read Timeout"); + } else if(_error == UPDATE_ERROR_MD5){ + return ("MD5 Check Failed"); + } else if(_error == UPDATE_ERROR_MAGIC_BYTE){ + return ("Wrong Magic Byte"); + } else if(_error == UPDATE_ERROR_ACTIVATE){ + return ("Could Not Activate The Firmware"); + } else if(_error == UPDATE_ERROR_NO_PARTITION){ + return ("Partition Could Not be Found"); + } else if(_error == UPDATE_ERROR_BAD_ARGUMENT){ + return ("Bad Argument"); + } else if(_error == UPDATE_ERROR_ABORT){ + return ("Aborted"); + } + return ("UNKNOWN"); +} + +static bool _partitionIsBootable(const esp_partition_t* partition){ + uint8_t buf[ENCRYPTED_BLOCK_SIZE]; + if(!partition){ + return false; + } + if(!ESP.partitionRead(partition, 0, (uint32_t*)buf, ENCRYPTED_BLOCK_SIZE)) { + return false; + } + + if(buf[0] != ESP_IMAGE_HEADER_MAGIC) { + return false; + } + return true; +} + +bool TasUpdateClass::_enablePartition(const esp_partition_t* partition){ + if(!partition){ + return false; + } + return ESP.partitionWrite(partition, 0, (uint32_t*) _skipBuffer, ENCRYPTED_BLOCK_SIZE); +} + +TasUpdateClass::TasUpdateClass() +: _error(0) +, _buffer(0) +, _bufferLen(0) +, _size(0) +, _progress_callback(NULL) +, _progress(0) +, _paroffset(0) +, _command(U_FLASH) +, _partition(NULL) +{ +} + +TasUpdateClass& TasUpdateClass::onProgress(THandlerFunction_Progress fn) { + _progress_callback = fn; + return *this; +} + +void TasUpdateClass::_reset() { + if (_buffer) + delete[] _buffer; + _buffer = 0; + _bufferLen = 0; + _progress = 0; + _size = 0; + _command = U_FLASH; + + if(_ledPin != -1) { + digitalWrite(_ledPin, !_ledOn); // off + } +} + +bool TasUpdateClass::canRollBack(){ + if(_buffer){ //Update is running + return false; + } + const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); + return _partitionIsBootable(partition); +} + +bool TasUpdateClass::rollBack(){ + if(_buffer){ //Update is running + return false; + } + const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); + return _partitionIsBootable(partition) && !esp_ota_set_boot_partition(partition); +} + +// Start Tasmota Factory patch +//bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label) { +bool TasUpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label, bool factory) { +// End Tasmota Factory patch + if(_size > 0){ + log_w("already running"); + return false; + } + + _ledPin = ledPin; + _ledOn = !!ledOn; // 0(LOW) or 1(HIGH) + + _reset(); + _error = 0; + _target_md5 = emptyString; + _md5 = MD5Builder(); + + if(size == 0) { + _error = UPDATE_ERROR_SIZE; + return false; + } + + if (command == U_FLASH) { +// Start Tasmota Factory patch +// _partition = esp_ota_get_next_update_partition(NULL); + if (factory) { + _partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); + } else { + _partition = esp_ota_get_next_update_partition(NULL); + } +// End Tasmota Factory patch + if(!_partition){ + _error = UPDATE_ERROR_NO_PARTITION; + return false; + } + log_d("OTA Partition: %s", _partition->label); + } + else if (command == U_SPIFFS) { + _partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, label); + _paroffset = 0; + if(!_partition){ + _partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL); + _paroffset = 0x1000; //Offset for ffat, assuming size is already corrected + if(!_partition){ + _error = UPDATE_ERROR_NO_PARTITION; + return false; + } + } + } + else { + _error = UPDATE_ERROR_BAD_ARGUMENT; + log_e("bad command %u", command); + return false; + } + + if(size == UPDATE_SIZE_UNKNOWN){ + size = _partition->size; + } else if(size > _partition->size){ + _error = UPDATE_ERROR_SIZE; + log_e("too large %u > %u", size, _partition->size); + return false; + } + + //initialize + _buffer = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE); + if(!_buffer){ + log_e("malloc failed"); + return false; + } + _size = size; + _command = command; + _md5.begin(); + return true; +} + +void TasUpdateClass::_abort(uint8_t err){ + _reset(); + _error = err; +} + +void TasUpdateClass::abort(){ + _abort(UPDATE_ERROR_ABORT); +} + +bool TasUpdateClass::_writeBuffer(){ + //first bytes of new firmware + uint8_t skip = 0; + if(!_progress && _command == U_FLASH){ + //check magic + if(_buffer[0] != ESP_IMAGE_HEADER_MAGIC){ + _abort(UPDATE_ERROR_MAGIC_BYTE); + return false; + } + + //Stash the first 16 bytes of data and set the offset so they are + //not written at this point so that partially written firmware + //will not be bootable + skip = ENCRYPTED_BLOCK_SIZE; + _skipBuffer = (uint8_t*)malloc(skip); + if(!_skipBuffer){ + log_e("malloc failed"); + return false; + } + memcpy(_skipBuffer, _buffer, skip); + } + if (!_progress && _progress_callback) { + _progress_callback(0, _size); + } + if(!ESP.partitionEraseRange(_partition, _progress, SPI_FLASH_SEC_SIZE)){ + _abort(UPDATE_ERROR_ERASE); + return false; + } + if (!ESP.partitionWrite(_partition, _progress + skip, (uint32_t*)_buffer + skip/sizeof(uint32_t), _bufferLen - skip)) { + _abort(UPDATE_ERROR_WRITE); + return false; + } + //restore magic or md5 will fail + if(!_progress && _command == U_FLASH){ + _buffer[0] = ESP_IMAGE_HEADER_MAGIC; + } + _md5.add(_buffer, _bufferLen); + _progress += _bufferLen; + _bufferLen = 0; + if (_progress_callback) { + _progress_callback(_progress, _size); + } + return true; +} + +bool TasUpdateClass::_verifyHeader(uint8_t data) { + if(_command == U_FLASH) { + if(data != ESP_IMAGE_HEADER_MAGIC) { + _abort(UPDATE_ERROR_MAGIC_BYTE); + return false; + } + return true; + } else if(_command == U_SPIFFS) { + return true; + } + return false; +} + +bool TasUpdateClass::_verifyEnd() { + if(_command == U_FLASH) { + if(!_enablePartition(_partition) || !_partitionIsBootable(_partition)) { + _abort(UPDATE_ERROR_READ); + return false; + } + + if(esp_ota_set_boot_partition(_partition)){ + _abort(UPDATE_ERROR_ACTIVATE); + return false; + } + _reset(); + return true; + } else if(_command == U_SPIFFS) { + _reset(); + return true; + } + return false; +} + +bool TasUpdateClass::setMD5(const char * expected_md5){ + if(strlen(expected_md5) != 32) + { + return false; + } + _target_md5 = expected_md5; + return true; +} + +bool TasUpdateClass::end(bool evenIfRemaining){ + if(hasError() || _size == 0){ + return false; + } + + if(!isFinished() && !evenIfRemaining){ + log_e("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size); + _abort(UPDATE_ERROR_ABORT); + return false; + } + + if(evenIfRemaining) { + if(_bufferLen > 0) { + _writeBuffer(); + } + _size = progress(); + } + + _md5.calculate(); + if(_target_md5.length()) { + if(_target_md5 != _md5.toString()){ + _abort(UPDATE_ERROR_MD5); + return false; + } + } + + return _verifyEnd(); +} + +size_t TasUpdateClass::write(uint8_t *data, size_t len) { + if(hasError() || !isRunning()){ + return 0; + } + + if(len > remaining()){ + _abort(UPDATE_ERROR_SPACE); + return 0; + } + + size_t left = len; + + while((_bufferLen + left) > SPI_FLASH_SEC_SIZE) { + size_t toBuff = SPI_FLASH_SEC_SIZE - _bufferLen; + memcpy(_buffer + _bufferLen, data + (len - left), toBuff); + _bufferLen += toBuff; + if(!_writeBuffer()){ + return len - left; + } + left -= toBuff; + } + memcpy(_buffer + _bufferLen, data + (len - left), left); + _bufferLen += left; + if(_bufferLen == remaining()){ + if(!_writeBuffer()){ + return len - left; + } + } + return len; +} + +size_t TasUpdateClass::writeStream(Stream &data) { + size_t written = 0; + size_t toRead = 0; + int timeout_failures = 0; + + if(hasError() || !isRunning()) + return 0; + + if(!_verifyHeader(data.peek())) { + _reset(); + return 0; + } + + if(_ledPin != -1) { + pinMode(_ledPin, OUTPUT); + } + + while(remaining()) { + if(_ledPin != -1) { + digitalWrite(_ledPin, _ledOn); // Switch LED on + } + size_t bytesToRead = SPI_FLASH_SEC_SIZE - _bufferLen; + if(bytesToRead > remaining()) { + bytesToRead = remaining(); + } + + /* + Init read&timeout counters and try to read, if read failed, increase counter, + wait 100ms and try to read again. If counter > 300 (30 sec), give up/abort + */ + toRead = 0; + timeout_failures = 0; + while(!toRead) { + toRead = data.readBytes(_buffer + _bufferLen, bytesToRead); + if(toRead == 0) { + timeout_failures++; + if (timeout_failures >= 300) { + _abort(UPDATE_ERROR_STREAM); + return written; + } + delay(100); + } + } + + if(_ledPin != -1) { + digitalWrite(_ledPin, !_ledOn); // Switch LED off + } + _bufferLen += toRead; + if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer()) + return written; + written += toRead; + + delay(1); // Fix solo WDT + } + return written; +} + +void TasUpdateClass::printError(Print &out){ + out.println(_err2str(_error)); +} + +const char * TasUpdateClass::errorString(){ + return _err2str(_error); +} + +TasUpdateClass TasUpdate; diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 1549f5aca..556569a55 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -837,7 +837,15 @@ void CmndUpgrade(void) TasmotaGlobal.ota_state_flag = 3; char stemp1[TOPSZ]; Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), XdrvMailbox.command, TasmotaGlobal.version, GetOtaUrl(stemp1, sizeof(stemp1))); - } else { + } +#if defined(ESP32) && defined(USE_WEBCLIENT_HTTPS) + else if (EspSingleOtaPartition() && !EspRunningFactoryPartition() && (1 == XdrvMailbox.data_len) && (2 == XdrvMailbox.payload)) { + TasmotaGlobal.ota_factory = true; + TasmotaGlobal.ota_state_flag = 3; + ResponseCmndChar(PSTR("Saveboot")); + } +#endif // ESP32 and WEBCLIENT_HTTPS + else { Response_P(PSTR("{\"%s\":\"" D_JSON_ONE_OR_GT "\"}"), XdrvMailbox.command, TasmotaGlobal.version); } } diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index 171f70d6d..454d59fae 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -1248,6 +1248,21 @@ void Every250mSeconds(void) #ifdef ESP32 #ifndef FIRMWARE_MINIMAL +#ifdef USE_WEBCLIENT_HTTPS + if (TasmotaGlobal.ota_factory) { + char *bch = strrchr(full_ota_url, '/'); // Only consider filename after last backslash prevent change of urls having "-" in it + if (bch == nullptr) { bch = full_ota_url; } // No path found so use filename only + char *ech = strchr(bch, '.'); // Find file type in filename (none, .ino.bin, .ino.bin.gz, .bin, .bin.gz or .gz) + if (ech == nullptr) { ech = full_ota_url + strlen(full_ota_url); } // Point to '/0' at end of full_ota_url becoming an empty string + char ota_url_type[strlen(ech) +1]; + strncpy(ota_url_type, ech, sizeof(ota_url_type)); // Either empty, .ino.bin, .ino.bin.gz, .bin, .bin.gz or .gz + + char *pch = strrchr(bch, '-'); // Find last dash (-) and ignore remainder - handles tasmota-DE + if (pch == nullptr) { pch = ech; } // No dash so ignore filetype + *pch = '\0'; // full_ota_url = http://domus1:80/api/arduino/tasmota + snprintf_P(full_ota_url, sizeof(full_ota_url), PSTR("%s-safeboot%s"), full_ota_url, ota_url_type); // Saveboot filename must be filename-safeboot + } else +#endif // USE_WEBCLIENT_HTTPS if (EspSingleOtaPartition()) { #ifdef CONFIG_IDF_TARGET_ESP32C3 OtaFactoryWrite(true); @@ -1271,6 +1286,7 @@ void Every250mSeconds(void) ota_result = -999; } else { httpUpdateLight.rebootOnUpdate(false); + httpUpdateLight.setFactory(TasmotaGlobal.ota_factory); ota_result = (HTTP_UPDATE_FAILED != httpUpdateLight.update(OTAclient, version)); } #else // standard OTA over HTTP diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 44d39780f..dc815a21b 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -186,6 +186,7 @@ struct TasmotaGlobal_t { bool i2c_enabled; // I2C configured #ifdef ESP32 bool i2c_enabled_2; // I2C configured, second controller on ESP32, Wire1 + bool ota_factory; // Select safeboot binary #endif bool ntp_force_sync; // Force NTP sync bool skip_light_fade; // Temporarily skip light fading