espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
13.73k stars 7.43k forks source link

Add option to update specific partition #10516

Open hitecSmartHome opened 1 month ago

hitecSmartHome commented 1 month ago

Related area

HTTPUpdate

Hardware specification

ESP32

Is your feature request related to a problem?

Yes. I can't update specific partition. I need to do a hack.

Describe the solution you'd like

HTTPUpdateResult HTTPUpdate::updateSpiffs(NetworkClient &client, const String &url, const String &currentVersion, HTTPUpdateRequestCB requestCB,  const char* partitionLabel) {
  HTTPClient http;
  if (!http.begin(client, url)) {
    return HTTP_UPDATE_FAILED;
  }
  return handleUpdate(http, currentVersion, true, requestCB, partitionLabel);
}

HTTPUpdateResult HTTPUpdate::handleUpdate(HTTPClient &http, const String &currentVersion, bool spiffs, HTTPUpdateRequestCB requestCB, const char* partitionLabel) {

  HTTPUpdateResult ret = HTTP_UPDATE_FAILED;

  // use HTTP/1.0 for update since the update handler not support any transfer Encoding
  http.useHTTP10(true);
  http.setTimeout(_httpClientTimeout);
  http.setFollowRedirects(_followRedirects);
  http.setUserAgent("ESP32-http-Update");
  http.addHeader("Cache-Control", "no-cache");
  http.addHeader("x-ESP32-BASE-MAC", Network.macAddress());
#if SOC_WIFI_SUPPORTED
  http.addHeader("x-ESP32-STA-MAC", WiFi.macAddress());
  http.addHeader("x-ESP32-AP-MAC", WiFi.softAPmacAddress());
#endif
  http.addHeader("x-ESP32-free-space", String(ESP.getFreeSketchSpace()));
  http.addHeader("x-ESP32-sketch-size", String(ESP.getSketchSize()));
  String sketchMD5 = ESP.getSketchMD5();
  if (sketchMD5.length() != 0) {
    http.addHeader("x-ESP32-sketch-md5", sketchMD5);
  }
  // Add also a SHA256
  String sketchSHA256 = getSketchSHA256();
  if (sketchSHA256.length() != 0) {
    http.addHeader("x-ESP32-sketch-sha256", sketchSHA256);
  }
  http.addHeader("x-ESP32-chip-size", String(ESP.getFlashChipSize()));
  http.addHeader("x-ESP32-sdk-version", ESP.getSdkVersion());

  if (spiffs) {
    http.addHeader("x-ESP32-mode", "spiffs");
  } else {
    http.addHeader("x-ESP32-mode", "sketch");
  }

  if (currentVersion && currentVersion[0] != 0x00) {
    http.addHeader("x-ESP32-version", currentVersion);
  }
  if (requestCB) {
    requestCB(&http);
  }

  if (!_user.isEmpty() && !_password.isEmpty()) {
    http.setAuthorization(_user.c_str(), _password.c_str());
  }

  if (!_auth.isEmpty()) {
    http.setAuthorization(_auth.c_str());
  }

  const char *headerkeys[] = {"x-MD5"};
  size_t headerkeyssize = sizeof(headerkeys) / sizeof(char *);

  // track these headers
  http.collectHeaders(headerkeys, headerkeyssize);

  int code = http.GET();
  int len = http.getSize();

  if (code <= 0) {
    log_e("HTTP error: %s\n", http.errorToString(code).c_str());
    _lastError = code;
    http.end();
    return HTTP_UPDATE_FAILED;
  }

  log_d("Header read fin.\n");
  log_d("Server header:\n");
  log_d(" - code: %d\n", code);
  log_d(" - len: %d\n", len);

  String md5;
  if (_md5Sum.length()) {
    md5 = _md5Sum;
  } else if (http.hasHeader("x-MD5")) {
    md5 = http.header("x-MD5");
  }
  if (md5.length()) {
    log_d(" - MD5: %s\n", md5.c_str());
  }

  log_d("ESP32 info:\n");
  log_d(" - free Space: %d\n", ESP.getFreeSketchSpace());
  log_d(" - current Sketch Size: %d\n", ESP.getSketchSize());

  if (currentVersion && currentVersion[0] != 0x00) {
    log_d(" - current version: %s\n", currentVersion.c_str());
  }

  switch (code) {
    case HTTP_CODE_OK:  ///< OK (Start Update)
      if (len > 0) {
        bool startUpdate = true;
        if (spiffs) {
          const esp_partition_t *_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, partitionLabel);
          if (!_partition) {
            _lastError = HTTP_UE_NO_PARTITION;
            return HTTP_UPDATE_FAILED;
          }

          printf("Partition label: %s\n", _partition->label);
          if (len > _partition->size) {
            printf("spiffsSize to low (%d) needed: %d\n", _partition->size, len);
            log_e("spiffsSize to low (%d) needed: %d\n", _partition->size, len);
            startUpdate = false;
          }
        } else {
          int sketchFreeSpace = ESP.getFreeSketchSpace();
          if (!sketchFreeSpace) {
            _lastError = HTTP_UE_NO_PARTITION;
            return HTTP_UPDATE_FAILED;
          }

          if (len > sketchFreeSpace) {
            log_e("FreeSketchSpace to low (%d) needed: %d\n", sketchFreeSpace, len);
            startUpdate = false;
          }
        }

        if (!startUpdate) {
          _lastError = HTTP_UE_TOO_LESS_SPACE;
          ret = HTTP_UPDATE_FAILED;
        } else {
          // Warn main app we're starting up...
          if (_cbStart) {
            _cbStart();
          }

          NetworkClient *tcp = http.getStreamPtr();

          // To do?                NetworkUDP::stopAll();
          // To do?                NetworkClient::stopAllExcept(tcp);

          delay(100);

          int command;

          if (spiffs) {
            command = U_SPIFFS;
            log_d("runUpdate spiffs...\n");
          } else {
            command = U_FLASH;
            log_d("runUpdate flash...\n");
          }

          if (!spiffs) {
            /* To do
                    uint8_t buf[4];
                    if(tcp->peekBytes(&buf[0], 4) != 4) {
                        log_e("peekBytes magic header failed\n");
                        _lastError = HTTP_UE_BIN_VERIFY_HEADER_FAILED;
                        http.end();
                        return HTTP_UPDATE_FAILED;
                    }
*/

            // check for valid first magic byte
            //                    if(buf[0] != 0xE9) {
            if (tcp->peek() != 0xE9) {
              log_e("Magic header does not start with 0xE9\n");
              _lastError = HTTP_UE_BIN_VERIFY_HEADER_FAILED;
              http.end();
              return HTTP_UPDATE_FAILED;
            }
            /* To do
                    uint32_t bin_flash_size = ESP.magicFlashChipSize((buf[3] & 0xf0) >> 4);

                    // check if new bin fits to SPI flash
                    if(bin_flash_size > ESP.getFlashChipRealSize()) {
                        log_e("New binary does not fit SPI Flash size\n");
                        _lastError = HTTP_UE_BIN_FOR_WRONG_FLASH;
                        http.end();
                        return HTTP_UPDATE_FAILED;
                    }
*/
          }
          if (runUpdate(*tcp, len, md5, command, partitionLabel) {
            ret = HTTP_UPDATE_OK;
            log_d("Update ok\n");
            http.end();
            // Warn main app we're all done
            if (_cbEnd) {
              _cbEnd();
            }

            if (_rebootOnUpdate && !spiffs) {
              ESP.restart();
            }

          } else {
            ret = HTTP_UPDATE_FAILED;
            log_e("Update failed\n");
          }
        }
      } else {
        _lastError = HTTP_UE_SERVER_NOT_REPORT_SIZE;
        ret = HTTP_UPDATE_FAILED;
        log_e("Content-Length was 0 or wasn't set by Server?!\n");
      }
      break;
    case HTTP_CODE_NOT_MODIFIED:
      ///< Not Modified (No updates)
      ret = HTTP_UPDATE_NO_UPDATES;
      break;
    case HTTP_CODE_NOT_FOUND:
      _lastError = HTTP_UE_SERVER_FILE_NOT_FOUND;
      ret = HTTP_UPDATE_FAILED;
      break;
    case HTTP_CODE_FORBIDDEN:
      _lastError = HTTP_UE_SERVER_FORBIDDEN;
      ret = HTTP_UPDATE_FAILED;
      break;
    default:
      _lastError = HTTP_UE_SERVER_WRONG_HTTP_CODE;
      ret = HTTP_UPDATE_FAILED;
      log_e("HTTP Code is (%d)\n", code);
      break;
  }

  http.end();
  return ret;
}

bool HTTPUpdate::runUpdate(Stream &in, uint32_t size, String md5, int command, const char* partitionLabel) {

  StreamString error;

  if (_cbProgress) {
    Update.onProgress(_cbProgress);
  }

  if (!Update.begin(size, command, _ledPin, _ledOn, partitionLabel)) {
    _lastError = Update.getError();
    Update.printError(error);
    error.trim();  // remove line ending
    log_e("Update.begin failed! (%s)\n", error.c_str());
    return false;
  }

  if (_cbProgress) {
    _cbProgress(0, size);
  }

  if (md5.length()) {
    if (!Update.setMD5(md5.c_str())) {
      _lastError = HTTP_UE_SERVER_FAULTY_MD5;
      log_e("Update.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);
    error.trim();  // remove line ending
    log_e("Update.writeStream failed! (%s)\n", error.c_str());
    return false;
  }

  if (_cbProgress) {
    _cbProgress(size, size);
  }

  if (!Update.end()) {
    _lastError = Update.getError();
    Update.printError(error);
    error.trim();  // remove line ending
    log_e("Update.end failed! (%s)\n", error.c_str());
    return false;
  }

  return true;
}

Describe alternatives you've considered

Now I have to manually modify the partition label on every firmware change because the new one overwrites my changes

Additional context

-

I have checked existing list of Feature requests and the Contribution Guide

hitecSmartHome commented 1 month ago

Update already supports it so basically

HTTPUpdate.h

t_httpUpdate_return updateSpiffs(NetworkClient &client, const String &url, const String &currentVersion = "", HTTPUpdateRequestCB requestCB = NULL, const char* partitionLabel = "spiffs");

t_httpUpdate_return update(HTTPClient &httpClient, const String &currentVersion = "", HTTPUpdateRequestCB requestCB = NULL);

t_httpUpdate_return updateSpiffs(HTTPClient &httpClient, const String &currentVersion = "", HTTPUpdateRequestCB requestCB = NULL, const char* partitionLabel = "spiffs");

t_httpUpdate_return handleUpdate(HTTPClient &http, const String &currentVersion, bool spiffs = false, HTTPUpdateRequestCB requestCB = NULL, const char* partitionLabel = NULL);

bool runUpdate(Stream &in, uint32_t size, String md5, int command = U_FLASH, const char* partitionLabel = NULL);