diff --git a/BLEScan.cpp b/BLEScan.cpp index f710691..ec49e5f 100644 --- a/BLEScan.cpp +++ b/BLEScan.cpp @@ -94,7 +94,7 @@ class HCScanCallbacks : public NimBLEScanCallbacks { */ void onResult(const NimBLEAdvertisedDevice* advertisedDevice) override { uint16_t len; - bthome_t *pTuyaData = (bthome_t *) advertisedDevice->getServiceData(&len); + bthome_t *pTuyaData = (bthome_t *) advertisedDevice->getServiceData((uint16_t *) &len); // Tuya if (len == sizeof(bthome_t) && pTuyaData->info == 0x40) { if (config.nTemp1SensorType == TEMP_SENSOR_TYPE::BLE_TUYA) { diff --git a/BLEScan.h b/BLEScan.h index 5526be5..eb77f76 100644 --- a/BLEScan.h +++ b/BLEScan.h @@ -1,6 +1,8 @@ #ifndef __BLE_SCAN_H #define __BLE_SCAN_H +#include + class NimBLEClient; class NimBLERemoteCharacteristic; class NimBLEAddress; diff --git a/Config.cpp b/Config.cpp index 4c0a467..977c116 100644 --- a/Config.cpp +++ b/Config.cpp @@ -81,7 +81,7 @@ void CONFIG_TYPE::init() { for (int i = 0; i < 17; i = i + 8) { m_nChipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; } - m_nPublicPort = (uint16_t)(m_nChipId & 0xFFFF); + m_nPublicPort = 3939; m_nDeviceType = THIS_DEVICE_TYPE; // diff --git a/HCUpdate.h b/HCUpdate.h deleted file mode 100644 index 16c8c8c..0000000 --- a/HCUpdate.h +++ /dev/null @@ -1,291 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#if defined(ESP32) -#ifndef ESP32UPDATER_H -#define ESP32UPDATER_H - -#include -#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_ERROR_DECRYPT (13) - -#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF - -#define U_FLASH 0 -#define U_SPIFFS 100 -#define U_AUTH 200 - -#define ENCRYPTED_BLOCK_SIZE 16 -#define ENCRYPTED_TWEAK_BLOCK_SIZE 32 -#define ENCRYPTED_KEY_SIZE 32 - -#define U_AES_DECRYPT_NONE 0 -#define U_AES_DECRYPT_AUTO 1 -#define U_AES_DECRYPT_ON 2 -#define U_AES_DECRYPT_MODE_MASK 3 -#define U_AES_IMAGE_DECRYPTING_BIT 4 - -#define SPI_SECTORS_PER_BLOCK 16 // usually large erase block is 32k/64k -#define SPI_FLASH_BLOCK_SIZE (SPI_SECTORS_PER_BLOCK * SPI_FLASH_SEC_SIZE) - -enum HTTPUpdateResult { - HTTP_UPDATE_FAILED, - HTTP_UPDATE_NO_UPDATES, - HTTP_UPDATE_OK -}; - -class UpdateClass { -public: - typedef std::function THandlerFunction_Progress; - - UpdateClass(); - UpdateClass(int httpClientTimeout); - - int update(WiFiClientSecure& client, String &url, uint16_t port, String& uri, - String ¤tVersion, short nDeviceType, bool bForceUpdate); - int handleUpdate(HTTPClient& http, const String& currentVersion, short nDeviceType); - int http_downloadUpdate(HTTPClient &httpClient); - - - /* - This callback will be called when Update is receiving data - */ - UpdateClass &onProgress(THandlerFunction_Progress fn); - - /* - Call this to check the space needed for the update - Will return false if there is not enough space - */ - bool begin(size_t size = UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL); - - /* - Setup decryption configuration - Crypt Key is 32bytes(256bits) block of data, use the same key as used to encrypt image file - Crypt Address, use the same value as used to encrypt image file - Crypt Config, use the same value as used to encrypt image file - Crypt Mode, used to select if image files should be decrypted or not - */ - bool setupCrypt(const uint8_t *cryptKey = 0, size_t cryptAddress = 0, uint8_t cryptConfig = 0xf, int cryptMode = U_AES_DECRYPT_AUTO); - - /* - 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 helpful when you update without knowing the final size first - */ - bool end(bool evenIfRemaining = false); - - /* - sets AES256 key(32 bytes) used for decrypting image file - */ - bool setCryptKey(const uint8_t *cryptKey); - - /* - sets crypt mode used on image files - */ - bool setCryptMode(const int cryptMode); - - /* - sets address used for decrypting image file - */ - void setCryptAddress(const size_t cryptAddress) { - _cryptAddress = cryptAddress & 0x00fffff0; - } - - /* - sets crypt config used for decrypting image file - */ - void setCryptConfig(const uint8_t cryptConfig) { - _cryptCfg = cryptConfig & 0x0f; - } - - /* - 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); - void _cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key); - bool _decryptBuffer(); - bool _writeBuffer(); - bool _verifyHeader(uint8_t data); - bool _verifyEnd(); - bool _enablePartition(const esp_partition_t *partition); - bool _chkDataInBlock(const uint8_t *data, size_t len) const; // check if block contains any data or is empty - - int _httpClientTimeout; - followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS; - uint8_t _error; - uint8_t *_cryptKey; - uint8_t *_cryptBuffer; - 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; - - uint8_t _cryptMode; - size_t _cryptAddress; - uint8_t _cryptCfg; -}; - -#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE) -extern UpdateClass Update; -#endif - -#endif // defined(ESP32UPDATER_H) -#endif // defined(ESP32) diff --git a/OTA.cpp b/OTA.cpp index f194867..3fe0b37 100644 --- a/OTA.cpp +++ b/OTA.cpp @@ -5,7 +5,7 @@ #include #include -#include "HCUpdate.h" +#include "src/ESPUpdate.h" #include "HermitCrab.h" #include "Config.h" #include "ConnectWiFi.h" @@ -65,70 +65,62 @@ void onOTAProgress(int current, int total) { ESP_LOGD(TAG_OTA,"OTA -- Progress: %d%%\n", (current * 100) / total); } -//========================================================================== -String urlEncode(const String &url, const char *safeChars = "-_.~") { - String encoded = ""; - char temp[4]; - - for (int i = 0; i < url.length(); i++) { - temp[0] = url.charAt(i); - if (temp[0] == 32) { //space - encoded.concat('+'); - } else if ((temp[0] >= 48 && temp[0] <= 57) /*0-9*/ - || (temp[0] >= 65 && temp[0] <= 90) /*A-Z*/ - || (temp[0] >= 97 && temp[0] <= 122) /*a-z*/ - || (strchr(safeChars, temp[0]) != NULL) /* "=&-_.~" */ - ) { - encoded.concat(temp[0]); - } else { //character needs encoding - snprintf(temp, 4, "%%%02X", temp[0]); - encoded.concat(temp); - } - } - return encoded; -} - -//========================================================================== -bool addQuery(String *query, const String name, const String value) { - if (name.length() && value.length()) { - if (query->length() < 3) { - *query = "?"; - } else { - query->concat('&'); - } - query->concat(urlEncode(name)); - query->concat('='); - query->concat(urlEncode(value)); - return true; - } - return false; -} //========================================================================== bool checkOTA(bool bForceUpdate) { - - // Set callbacks - //Update.onStart(onOTAStart); - //Update.onEnd(onOTAEnd); - String query = ""; - addQuery(&query, "cmd", (bForceUpdate ? "download" : "check")); //action command - uri.concat(query); - String version = String(HC__VERSION); - ESP_LOGI(TAG_OTA,"OTA - URL: %s\n", url.c_str()); - ESP_LOGI(TAG_OTA,"OTA - URI: %s\n", uri.c_str()); - ESP_LOGI(TAG_OTA,"OTA - Ver: %s\n", HC__VERSION); - - WiFiClientSecure client; + ESPUpdateClass ESPUpdate; client.setCACert(rootCACertificate); - UpdateClass hcUpdate(5000); - hcUpdate.onProgress(onOTAProgress); - //int update(WiFiClient& client, String &url, uint16_t port, String& uri, - // String ¤tVersion, short nDeviceType, bool bForceUpdate); - int result = hcUpdate.update(client, url, UPDATE_PORT, uri, version, (short)config.m_nDeviceType, true); - - + ESPUpdate.onProgress(onOTAProgress); + + // result: POSITIVE (HTTP/Status), NEGATIVE (Network Error) + int result = ESPUpdate.update( + client, + "visionsoft.kr", // Server Host + 443, // Server Port HTTPS, + "/sc/pages/firmware_download.php", // phpUri, + HC__VERSION, // Current Version string + "HCesp", // Project Tag + bForceUpdate, // bForceUpdate + true // bRebootAfterInstall + ); + + // ====================================================================== + // STATUS REPORTING LOGIC + // ====================================================================== + bool _setupMode = true; + if (result == 200) { + if (_setupMode) { + Serial.println("[OTA] Success: Firmware downloaded and installed."); + Serial.println("[OTA] Rebooting..."); + } + delay(1000); + ESP.restart(); + } + else if (result == 204) { + if (_setupMode) { + Serial.println("[OTA] Not Available: No firmware binary found for this project."); + } + } + else if (result == 304) { + if (_setupMode) { + Serial.println("[OTA] Up To Date: Current firmware is the latest version."); + } + } + else if (result > 0) { + // Captures 403 (Forbidden), 404 (Not Found), 500 (Server Error) + if (_setupMode) { + Serial.printf("[OTA] Server Error: HTTP %d\n", result); + } + } + else if (result < 0) { + // Captures library errors like -1 (Connection Refused) or -11 (Timeout) + if (_setupMode) { + Serial.printf("[OTA] Connection Error: %s (%d)\n", + HTTPClient::errorToString(result).c_str(), result); + } + } return false; } \ No newline at end of file diff --git a/OTA.cpp.backup b/OTA.cpp.backup new file mode 100644 index 0000000..4a18da2 --- /dev/null +++ b/OTA.cpp.backup @@ -0,0 +1,135 @@ +#define NO_GLOBAL_UPDATE +#include +#include +#include +#include +#include + +#include "HCUpdate.h" +#include "HermitCrab.h" +#include "Config.h" +#include "ConnectWiFi.h" + +#define TAG_OTA "OTA" + +// ============================================================== +// +// OTA +// +// ============================================================== +const char *HC__VERSION = "20250405001"; +#define UPDATE_PORT ((uint16_t) 443) +String url = "visionsoft.kr"; +String uri = "/hc/hc_firmware_update.php"; +const char *HTTPUPDATE_USERAGRENT = "ESP32-http-Update"; +const char *COMPANY_NAME = "VisionSoft"; +const char *SERVICE_NAME = "HermitCrab"; + +const char* rootCACertificate = \ +"-----BEGIN CERTIFICATE-----\n" \ +"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \ +"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \ +"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \ +"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" \ +"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" \ +"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" \ +"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" \ +"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" \ +"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" \ +"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" \ +"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" \ +"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" \ +"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" \ +"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" \ +"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" \ +"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" \ +"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" \ +"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" \ +"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" \ +"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" \ +"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" \ +"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" \ +"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" \ +"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" \ +"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" \ +"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" \ +"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" \ +"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" \ +"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" \ +"-----END CERTIFICATE-----\n"; + +String getSketchSHA256(); // Function to retrieve current sketch hash + +// Callback function for OTA progress +void onOTAProgress(int current, int total) { + ESP_LOGD(TAG_OTA,"OTA -- Progress: %d%%\n", (current * 100) / total); +} + +//========================================================================== +String urlEncode(const String &url, const char *safeChars = "-_.~") { + String encoded = ""; + char temp[4]; + + for (int i = 0; i < url.length(); i++) { + temp[0] = url.charAt(i); + if (temp[0] == 32) { //space + encoded.concat('+'); + } else if ((temp[0] >= 48 && temp[0] <= 57) /*0-9*/ + || (temp[0] >= 65 && temp[0] <= 90) /*A-Z*/ + || (temp[0] >= 97 && temp[0] <= 122) /*a-z*/ + || (strchr(safeChars, temp[0]) != NULL) /* "=&-_.~" */ + ) { + encoded.concat(temp[0]); + } else { //character needs encoding + snprintf(temp, 4, "%%%02X", temp[0]); + encoded.concat(temp); + } + } + return encoded; +} + +//========================================================================== +bool addQuery(String *query, const String name, const String value) { + if (name.length() && value.length()) { + if (query->length() < 3) { + *query = "?"; + } else { + query->concat('&'); + } + query->concat(urlEncode(name)); + query->concat('='); + query->concat(urlEncode(value)); + return true; + } + return false; +} + +//========================================================================== +bool checkOTA(bool bForceUpdate) +{ + + // Set callbacks + //Update.onStart(onOTAStart); + //Update.onEnd(onOTAEnd); + String query = ""; + addQuery(&query, "cmd", (bForceUpdate ? "download" : "check")); //action command + uri.concat(query); + String version = String(HC__VERSION); + ESP_LOGI(TAG_OTA,"OTA - URL: %s\n", url.c_str()); + ESP_LOGI(TAG_OTA,"OTA - URI: %s\n", uri.c_str()); + ESP_LOGI(TAG_OTA,"OTA - Ver: %s\n", HC__VERSION); + + + WiFiClientSecure client; + client.setCACert(rootCACertificate); + + UpdateClass hcUpdate(5000); + hcUpdate.onProgress(onOTAProgress); + //int update(WiFiClient& client, String &url, uint16_t port, String& uri, + // String ¤tVersion, short nDeviceType, bool bForceUpdate); + int result = hcUpdate.update(client, url, UPDATE_PORT, uri, version, (short)config.m_nDeviceType, true); + + + + return false; +} \ No newline at end of file diff --git a/Setup.cpp b/Setup.cpp index 9d32cef..28e93a8 100644 --- a/Setup.cpp +++ b/Setup.cpp @@ -68,20 +68,20 @@ void setup() { setupSensor(); ui.setup(); - ui.message(0, "WiFi..."); + ui.message(0, (char *) "WiFi..."); setupWiFi(); if (aht25.sensor() || aht10_0x39.sensor()) { - ui.message(4, "Sensor... OK!"); + ui.message(4, (char *) "Sensor... OK!"); } else { - ui.message(4, "Sensor... None!"); + ui.message(4, (char *) "Sensor... None!"); } - ui.message(5, "ZCD..."); + ui.message(5, (char *) "ZCD..."); setupZCD(); - ui.message(5, "ZCD... OK!"); + ui.message(5, (char *) "ZCD... OK!"); - ui.message(6, "Setup OK!"); + ui.message(6, (char *) "Setup OK!"); //if (!isWiFiConnected) delay(3000); ble.setupConnect(config.nBLESensorAddr, config.nBLESensorAddr2); @@ -111,8 +111,6 @@ void setupConfig() { config.load(); history.loadPID(); config.m_nDeviceType = THIS_DEVICE_TYPE; - if (config.m_nPublicPort == 3939) - config.m_nPublicPort = (uint16_t)(config.m_nChipId & 0xFFFF); } void setupWiFi() { @@ -146,7 +144,7 @@ void setupWiFi() { if (WiFi.status() == WL_CONNECTED) { ledcWrite(PIN_LED_WIFI, PWM_FULL); // LED_OFF - ui.message(0, "WiFi...OK!"); + ui.message(0, (char *) "WiFi...OK!"); DPRINTLN("WiFi - Connected at SETUP"); DPRINTF("WiFi - SSID(%s) PW(%s) IP(%s)\n", config.ssid, config.pw, WiFi.localIP().toString().c_str()); g_bWiFiHasBeenConnected = true; @@ -162,35 +160,35 @@ void setupWiFi() { void setupPostWiFi(bool bBoot = false) { if (WiFi.status() == WL_CONNECTED) { // Time - if (bBoot) ui.message(1, "Time..."); + if (bBoot) ui.message(1, (char *) "Time..."); timeManager.begin(); vTaskDelay((bBoot ? 500 : 250)/portTICK_PERIOD_MS); timeManager.checkNTPResponse(); if (bBoot) { if (timeManager.hasNTPUpdate()) { - ui.message(1, "Time...OK!"); + ui.message(1, (char *) "Time...OK!"); // OTA DPRINTLN("Setup - TimeManager.begin()"); DPRINTLN("\n===============================\n"); DPRINTLN(" Trying OTA"); - ui.message(2, "Update check..."); + ui.message(2, (char *) "Update check..."); checkOTA(true); - ui.message(2, "Update check...OK!"); + ui.message(2, (char *) "Update check...OK!"); DPRINTLN(" OTA Process completed!"); DPRINTLN("===============================\n"); } else { - ui.message(2, "Update check SKIPPED!"); + ui.message(2, (char *) "Update check SKIPPED!"); timeManager.sendNTPRequest(); } } // Host - if (bBoot) ui.message(3, "Server..."); + if (bBoot) ui.message(3, (char *) "Server..."); host.Setup(); DPRINTLN("Setup - host.Setup()"); - if (bBoot) ui.message(3, "Server...OK!"); + if (bBoot) ui.message(3, (char *) "Server...OK!"); g_bWiFiSetupExecuted = true; } } diff --git a/UPnPClient.cpp b/UPnPClient.cpp deleted file mode 100644 index ef39e39..0000000 --- a/UPnPClient.cpp +++ /dev/null @@ -1,491 +0,0 @@ -#include -#include "HermitCrab.h" -#include "UPnpClient.h" -#include "Config.h" -#include "WiFiHost.h" -#define TAG_UPNP "UPnP" - - -bool CUpnpClient::registerUPnP(uint32_t *pip, uint16_t *pport) { - routerIP = *pip; // Gateway IP address - publicPort = *pport; // host.m_nPublicPort = config.m_nPublicPort - publicIP = 0UL; - bool bSuccess = false; - ESP_LOGI(TAG_UPNP,"UPnP %s(%D)\n", routerIP.toString().c_str(), publicPort); - - // Step 1 - Discover UPnP service. - if (discoverUPnP()) { - // Step 2 - Check if mapping already exists - if (!(bSuccess = requestPortMappingEntry())) { - // Step 3 - Request new mapping - bSuccess = requestPortForwarding(); - } - } else { - ESP_LOGI(TAG_UPNP," UPnP discovery failed."); - } - - if (bSuccess) { - // Extract external IP - requestExternalIP(); - ESP_LOGI(TAG_UPNP," Public IP(Port): %s(%d)\n", publicIP.toString().c_str(), publicPort); - - - // Extract external port assigned by UPnP - // requestExternalPort(); - // ESP_LOGI(TAG_UPNP,"Assigned External Port: %d\n", publicPort); - - *pport = publicPort; // External server port - *pip = publicIP; // External IP address - return true; - } else { - ESP_LOGI(TAG_UPNP," UPnP PortForwarding failed."); - } - return bSuccess; -} - -bool CUpnpClient::discoverUPnP() { - //ESP_LOGI(TAG_UPNP,"\n\n** Sending SSDP discovery request..."); - WiFiUDP udp; - bool ret = false; - - // SSDP M-SEARCH Request - const char *ssdpRequest = - "M-SEARCH * HTTP/1.1\r\n" - "HOST: 239.255.255.250:1900\r\n" - "MAN: \"ssdp:discover\"\r\n" - "MX: 3\r\n" - "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" - "\r\n"; - - udp.beginMulticast(SSDP_MULTICAST_IP, SSDP_PORT); - udp.beginPacket(SSDP_MULTICAST_IP, SSDP_PORT); - udp.write((const uint8_t *)ssdpRequest, strlen(ssdpRequest)); // Fix length - udp.endPacket(); - - unsigned long startTime = millis(); - while (millis() - startTime < SEARCH_TIMEOUT) { - int packetSize = udp.parsePacket(); - if (packetSize > 0) { - udp.read(buffer, sizeof(buffer) - 1); - buffer[packetSize] = '\0'; - - //ESP_LOGI(TAG_UPNP,"SSDP response received:"); - //ESP_LOGI(TAG_UPNP,buffer); - - // Check if the response contains "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1" - char *stField = strstr(buffer, "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1"); - if (stField) { - // Extract router's UPnP service URL - char *location = strstr(buffer, "LOCATION: "); - if (location) { - location += 9; // Move past "LOCATION: " - while (*location == ' ') location++; // skip blanks - char *end = strchr(location, '\r'); - if (end) { - *end = '\0'; - //ESP_LOGI(TAG_UPNP,"Router UPnP URL: %s\n", location); - routerLocation = String(location); - - // Extract IP and Port from the Location URL - int firstColonPos = routerLocation.indexOf(':'); // Find the first colon (for the protocol) - int secondColonPos = routerLocation.indexOf(':', firstColonPos + 1); // Find the second colon (IP:PORT) - - if (secondColonPos != -1) { - routerIPString = routerLocation.substring(routerLocation.indexOf("://") + 3, secondColonPos); // Extract IP - routerPort = routerLocation.substring(secondColonPos + 1, routerLocation.indexOf('/', secondColonPos)).toInt(); // Extract Port - } - - routerIP.fromString(routerIPString); - //ESP_LOGI(TAG_UPNP,"Router UPnP IP(Port): %s(%d)\n", routerIP.toString().c_str(), routerPort); - - String xml = fetchUPnPDescription(routerLocation); - if (!xml.isEmpty()) { - // ESP_LOGI(TAG_UPNP,xml); - // ESP_LOGI(TAG_UPNP,"\n--- End Of XML ----\n"); - - // Parse the XML and get the controlURL - // String parseXML(const String &xml); - controlURL = parseXML(xml); - if (!controlURL.isEmpty()) { - ret = true; - break; - } - } - } - } - } - } - } - udp.stop(); - - if (!ret) { - DPRINTLN("No SSDP response from UPnP device."); - } - return ret; -} - -String CUpnpClient::fetchUPnPDescription(const String &location) { - HTTPClient http; - WiFiClient client; - - //ESP_LOGI(TAG_UPNP,"Fetching UPnP XML from: \"%s\"\n", location.c_str()); - - http.begin(client, location); - int httpCode = http.GET(); - - if (httpCode > 0) { - //ESP_LOGI(TAG_UPNP,"HTTP Response Code: %d\n", httpCode); - if (httpCode == HTTP_CODE_OK) { - String xmlContent = http.getString(); - http.end(); - return xmlContent; - } - } - else { - //ESP_LOGI(TAG_UPNP,"HTTP Request failed, error: %s\n", http.errorToString(httpCode).c_str()); - } - - http.end(); - return ""; -} - -String CUpnpClient::parseXML(const String &xml) { - // Locate the WANIPConnection service - int servicePos = xml.indexOf("urn:schemas-upnp-org:service:WANIPConnection:1"); - if (servicePos == -1) { - ESP_LOGI(TAG_UPNP,"WANIPConnection service not found in XML."); - return ""; - } - - // Find the start of the controlURL tag within the block - int controlStart = xml.indexOf("", servicePos); - if (controlStart == -1) { - ESP_LOGI(TAG_UPNP,"controlURL not found in service block."); - return ""; - } - controlStart += 12; // Move past "" - - // Find the closing tag - int controlEnd = xml.indexOf("", controlStart); - if (controlEnd == -1) { - ESP_LOGI(TAG_UPNP,"Malformed XML: Missing ."); - return ""; - } - - // Extract and clean the controlURL - String controlURL = xml.substring(controlStart, controlEnd); - controlURL.trim(); // Remove any spaces or newlines - - //ESP_LOGI(TAG_UPNP,"Extracted controlURL: %s\n", controlURL.c_str()); - return controlURL; -} - -int CUpnpClient::sendSoapRequest(const char *request, char *response, size_t responseSize) { - WiFiClient client; - if (!client.connect(routerIP, routerPort)) { // UPnP uses port 1900 for SOAP requests - ESP_LOGI(TAG_UPNP,"Failed to connect to router: %s\n", routerIP.toString().c_str()); - return -1; - } - - client.print("POST /control?WANIPConn1 HTTP/1.1\r\n"); - client.print("Host: "); - client.print(routerIP); - client.print("\r\n"); - client.print("Content-Type: text/xml; charset=\"utf-8\"\r\n"); - client.print("SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#GetSpecificPortMappingEntry\"\r\n"); - client.print("Content-Length: "); - client.print(strlen(request)); - client.print("\r\n\r\n"); - client.print(request); - - unsigned long startMillis = millis(); - while (!client.available() && millis() - startMillis < 5000) { - delay(10); // Wait for response - } - - int index = 0; - while (client.available() && index < responseSize - 1) { - response[index++] = client.read(); - } - response[index] = '\0'; - - client.stop(); - return index; -} - -bool CUpnpClient::requestPortMappingEntry() { - ESP_LOGI(TAG_UPNP,"** Requesting UPnP Port Mapping Entry..."); - char soapRequest[512]; - char mappingName[32]; - sprintf(mappingName, "HC_%04X", publicPort); - - snprintf(soapRequest, sizeof(soapRequest), - "" - "" - "" - "" - "" - "%d" - "TCP" - "%s" - "%s" - "" - "" - "", - publicPort, - WiFi.localIP().toString().c_str(), - mappingName); - - char response[1024]; - int responseLen = sendSoapRequest(soapRequest, response, sizeof(response)); - - if (responseLen > 0) { - if (strstr(response, mappingName)) { - //ESP_LOGI(TAG_UPNP," Port mapping already exists."); - return true; - } - } - ESP_LOGI(TAG_UPNP," Port mapping not found."); - return false; -} - -bool CUpnpClient::requestPortForwarding() { - // Sending port-forwarding request to the router's external IP or gateway - ESP_LOGI(TAG_UPNP,"** Requesting UPnP Port Forwarding..."); - - if (controlURL.isEmpty()) { - ESP_LOGI(TAG_UPNP,"Error: controlURL is empty."); - return false; - } - - // Ensure controlURL is correctly formatted (handle absolute/relative cases) - String postURL = controlURL; - if (postURL.startsWith("http://") || postURL.startsWith("https://")) { - int pathStart = postURL.indexOf('/', 8); // Skip "http://" - if (pathStart != -1) { - postURL = postURL.substring(pathStart); // Extract path only - ESP_LOGI(TAG_UPNP,"PostURL: \"%s\"\n", postURL.c_str()); - } else { - ESP_LOGI(TAG_UPNP,"Invalid controlURL format."); - return false; - } - } - - // XML body - char xmlBody[800]; - snprintf(xmlBody, sizeof(xmlBody), - "" - "" - "" - "" - "%d" - "TCP" - "%d" - "%s" - "1" - "HC_%04X" - "0" - "", - publicPort, publicPort, - WiFi.localIP().toString().c_str(), - publicPort); - //ESP_LOGI(TAG_UPNP,"XML Body: "); - //ESP_LOGI(TAG_UPNP,xmlBody); - - - // Calculate the correct content length - int contentLength = strlen(xmlBody); - - snprintf(buffer, sizeof(buffer), - "POST %s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Content-Type: text/xml; charset=\"utf-8\"\r\n" - "SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping\"\r\n" - "Content-Length: %d\r\n" - "Connection: Close\r\n" - "\r\n" - "%s", // Append XML body after headers - controlURL.c_str(), - routerIP.toString().c_str(), routerPort, - contentLength, - xmlBody); - - //ESP_LOGI(TAG_UPNP,"==== HTTP Request ===\n\n%s\n\n--- End Of HTTP ---\n", buffer); - - // Connect and send - WiFiClient client; - if (!client.connect(routerIP, routerPort)) { - ESP_LOGI(TAG_UPNP,"Failed to connect to the router. %s:%d\n", routerIP.toString().c_str(), routerPort); - return false; - } - - client.print(buffer); - - // Read response - String response; - uint32_t timeout = millis() + 5000; - while (client.available() == 0) { - if (millis() > timeout) { - ESP_LOGI(TAG_UPNP,"Router did not respond to port mapping request."); - client.stop(); - return false; - } - } - - while (client.available()) { - response += client.readString(); - } - client.stop(); - return true; -}; - -bool CUpnpClient::requestExternalIP() { - //ESP_LOGI(TAG_UPNP,"\n\nRequesting External IP via UPnP..."); - - // XML Request Body - char xmlBody[300]; - snprintf(xmlBody, sizeof(xmlBody), - "" - "" - "" - "" - "" - ""); - - int contentLength = strlen(xmlBody); - - // HTTP Request - snprintf(buffer, sizeof(buffer), - "POST %s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Content-Type: text/xml; charset=\"utf-8\"\r\n" - "SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress\"\r\n" - "Content-Length: %d\r\n" - "Connection: Close\r\n" - "\r\n" - "%s", - controlURL.c_str(), - routerIP.toString().c_str(), routerPort, - contentLength, - xmlBody); - - // Connect and Send - WiFiClient client; - if (!client.connect(routerIP, routerPort)) { - DPRINTLN("Failed to connect to router for External IP request."); - return false; - } - - client.print(buffer); - - // Read Response - String response; - uint32_t timeout = millis() + 5000; - while (client.available() == 0) { - if (millis() > timeout) { - DPRINTLN("Router did not respond to External IP request."); - client.stop(); - return false; - } - } - - while (client.available()) { - response += client.readString(); - } - client.stop(); - - //ESP_LOGI(TAG_UPNP,"External IP Response:"); - //ESP_LOGI(TAG_UPNP,response); - - // Extract External IP from XML - char *sz = (char *)(response.c_str()); - char *extIP = strstr(sz, ""); - if (extIP) { - extIP += 22; - char *end = strchr(extIP, '<'); - if (end) *end = '\0'; - - publicIP = IPAddress(extIP); - DPRINTF("UPnP - External IP: %s\n", extIP); - return true; - } - - DPRINTLN("Failed to extract External IP."); - return false; -} - -bool CUpnpClient::requestExternalPort() { - //ESP_LOGI(TAG_UPNP,"\n\nRequesting External Port via UPnP..."); - - // XML Request Body for getting External Port - char xmlBody[300]; - snprintf(xmlBody, sizeof(xmlBody), - "" - "" - "" - "" - "" - ""); - - int contentLength = strlen(xmlBody); - - // HTTP Request to get External Port - snprintf(buffer, sizeof(buffer), - "POST %s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Content-Type: text/xml; charset=\"utf-8\"\r\n" - "SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalPort\"\r\n" - "Content-Length: %d\r\n" - "Connection: Close\r\n" - "\r\n" - "%s", - controlURL.c_str(), - routerIP.toString().c_str(), routerPort, - contentLength, - xmlBody); - - // Connect and Send - WiFiClient client; - if (!client.connect(routerIP, routerPort)) { - ESP_LOGI(TAG_UPNP,"Failed to connect to router for External Port request."); - return false; - } - - client.print(buffer); - - // Read Response - String response; - uint32_t timeout = millis() + 5000; - while (client.available() == 0) { - if (millis() > timeout) { - ESP_LOGI(TAG_UPNP,"Router did not respond to External Port request."); - client.stop(); - return false; - } - } - - while (client.available()) { - response += client.readString(); - } - client.stop(); - - //ESP_LOGI(TAG_UPNP,"External Port Response:"); - //ESP_LOGI(TAG_UPNP,response); - - // Extract External Port from XML - char *sz = (char *)(response.c_str()); - char *extPort = strstr(sz, ""); - if (extPort) { - extPort += 17; - char *end = strchr(extPort, '<'); - if (end) *end = '\0'; - - publicPort = atoi(extPort); - ESP_LOGI(TAG_UPNP,"External Port: %d\n", publicPort); - return true; - } - - ESP_LOGI(TAG_UPNP,"Failed to extract External Port."); - return false; -}; diff --git a/UPnPClient.h b/UPnPClient.h.backup similarity index 100% rename from UPnPClient.h rename to UPnPClient.h.backup diff --git a/WiFiHost.cpp b/WiFiHost.cpp index 1abeb52..fb186b4 100644 --- a/WiFiHost.cpp +++ b/WiFiHost.cpp @@ -18,7 +18,7 @@ #include "zcd.h" #include "AHT2x.h" #include "WiFiHost.h" -#include "UPnPClient.h" +//#include "UPnPClient.h" @@ -64,15 +64,10 @@ void CWiFiHost::Setup() .sig2 = SIGNATURE2 }; clientPacket = hostPacket; - //if (m_nPublicPort == 0) - { - if (config.m_nPublicPort == 0) - config.m_nPublicPort = (uint16_t)(config.m_nChipId & 0xFFFF); - m_nPublicPort = config.m_nPublicPort; - } + //m_nPublicPort = config.m_nPublicPort; wifiServer = WiFiServer(SERVER_PORT, 1); - wifiExternal = WiFiServer(m_nPublicPort, 1); + //wifiExternal = WiFiServer(m_nPublicPort, 1); //wifiStatus = WIFI_NOT_CONNECTED; m_nDataSend_sent = 0; @@ -96,36 +91,36 @@ void CWiFiHost::Setup() m_nLastHeartBeatSentTime = m_nLastUDPBroadcastTime = millis(); m_bClientConnected = false; - m_dwPublicIP = 0; + //m_dwPublicIP = 0; wifiClient.stop(); if (isWiFiConnected()) { // UPnP Client - { - uint32_t ip = WiFi.gatewayIP(); - uint16_t port = m_nPublicPort; - CUpnpClient upnp; + // { + // uint32_t ip = WiFi.gatewayIP(); + // uint16_t port = m_nPublicPort; + // //CUpnpClient upnp; - if (upnp.registerUPnP(&ip, &port)) { - status.nFlags |= FLAG_UPNP; - } else { - status.nFlags &= ~FLAG_UPNP; - } + // //if (upnp.registerUPnP(&ip, &port)) { + // // status.nFlags |= FLAG_UPNP; + // //} else { + // status.nFlags &= ~FLAG_UPNP; + // //} - if (ip != 0) - m_dwPublicIP = ip; - if (port != m_nPublicPort) { - config.m_nPublicPort = port; - config.save(); - m_nPublicPort = port; - } + // if (ip != 0) + // m_dwPublicIP = ip; + // if (port != m_nPublicPort) { + // config.m_nPublicPort = port; + // config.save(); + // m_nPublicPort = port; + // } - } + // } // Server wifiServer.begin(SERVER_PORT, 1); - wifiExternal.begin(m_nPublicPort, 1); + //wifiExternal.begin(m_nPublicPort, 1); m_nLastReceivedTime = millis(); m_bClientConnected = false; @@ -150,7 +145,7 @@ MY_IRAM_ATTR void CWiFiHost::Stop() { // Stop server wifiServer.stop(); - wifiExternal.stop(); + //wifiExternal.stop(); m_bClientConnected = false; // Stop Client @@ -171,87 +166,86 @@ MY_IRAM_ATTR void CWiFiHost::CloseConnection() IRAM_ATTR void CWiFiHost::Loop(unsigned long clock) { - static unsigned long lastReceivedTime = 0; if (!isWiFiConnected()) return; + // --- State: WAITING (Accepting new clients) --- + if (m_nMode == MODE_WAITING) { + // If we think we are connected but the object is dead, reset + if (m_bClientConnected) { + wifiClient.stop(); + m_bClientConnected = false; + } + + // Try accepting from internal, then external if internal fails + wifiClient = wifiServer.accept(); + //if (!wifiClient) wifiClient = wifiExternal.accept(); + + if (wifiClient && wifiClient.connected()) { + ESP_LOGI(TAG_WIFI_HOST, "Host: Connection Accepted"); + wifiClient.setNoDelay(true); + m_nLastReceivedTime = clock; + m_bClientConnected = true; + m_bHelloSent = false; + m_nMode = MODE_PACKET; + ledcWrite(PIN_LED_WIFI, PWM_FULL - 10); + } + return; + } + + // --- Global Safety Check for all other modes --- + // If we lose connection mid-process, jump back to WAITING immediately + if (!wifiClient || !wifiClient.connected()) { + m_bClientConnected = false; + m_nMode = MODE_WAITING; + ledcWrite(PIN_LED_WIFI, PWM_FULL - 2); + return; + } + + // --- Main State Machine --- switch (m_nMode) { - case MODE_WAITING: // Expecting connection from clients - if (m_bClientConnected) - { - ESP_LOGI(TAG_WIFI_HOST,"Host: dropping connection for not connected"); - if (wifiClient && wifiClient.connected()) { - ESP_LOGI(TAG_WIFI_HOST,"Host: stopping wifi client"); - wifiClient.stop(); - } - m_bClientConnected = false; - } - - // Accept from internal XOR external port - wifiClient = wifiServer.accept(); - if (!wifiClient || !wifiClient.connected()) { - wifiClient = wifiExternal.accept(); - } - - if (wifiClient && wifiClient.connected()) - { - ESP_LOGI(TAG_WIFI_HOST,"Host: Connection Accepted"); - wifiClient.setNoDelay(true); - m_nLastReceivedTime = clock; - m_bClientConnected = true; - m_bHelloSent = false; - m_nMode = MODE_PACKET; - - // LED - ledcWrite(PIN_LED_WIFI, PWM_FULL - 10); // Amost ON - } - break; case MODE_PACKET: - // Client Connected - if (m_bClientConnected && wifiClient.connected()) - { - CheckClient(clock); + CheckClient(clock); - if (m_bClientConnected) { - if (clock - m_nLastReceivedTime > 60000) - { - ESP_LOGI(TAG_WIFI_HOST,"Host: dropping connection for no HB in 60 seconds"); - wifiClient.stop(); - m_bClientConnected = false; - } - - // Send HeartBeat - if (clock - m_nLastHeartBeatSentTime >= 1000) { - SendHeartBeat(); - m_nLastHeartBeatSentTime = clock; - } - } - } - if (!m_bClientConnected || !wifiClient.connected()) { + // Timeout Check (60 seconds) + if (clock - m_nLastReceivedTime > 60000) { + ESP_LOGW(TAG_WIFI_HOST, "Host: Timeout - Dropping Connection"); + wifiClient.stop(); + m_bClientConnected = false; m_nMode = MODE_WAITING; - // LED - ledcWrite(PIN_LED_WIFI, PWM_FULL - 2); // Almost Off + } + // Heartbeat Pulse (Every 1 second) + else if (clock - m_nLastHeartBeatSentTime >= 1000) { + SendHeartBeat(); + m_nLastHeartBeatSentTime = clock; } break; + case MODE_SEND: + // SendData returns true only when the CURRENT buffer is empty if (SendData(clock)) { - if (m_bSendHistoryPending) { - // Mark pending to send the second part: from the start to head-1 - //SendData(history.getRingData2() /* &ring[0] */, sizeof(STATUS_TYPE) * head); - //m_bSendHistoryPending = true; - //m_nPendingHistoryCount = history.getHead(); + // Check if there is a second part of the history ring buffer + if (m_bSendHistoryPending && m_nPendingHistoryCount > 0) { + ESP_LOGI(TAG_WIFI_HOST, "WiFi Host: Preparing History Part 2"); + + // Setup the next chunk + SendData(history.getRingData2(), sizeof(STATUS_TYPE) * m_nPendingHistoryCount); + + // Clear flags so we don't loop here forever m_bSendHistoryPending = false; - if (m_nPendingHistoryCount > 0) { - ESP_LOGI(TAG_WIFI_HOST,"WfFi Host - SendData - 2nd part of History (%d)\n", m_nPendingHistoryCount); - SendData(history.getRingData2(), sizeof(STATUS_TYPE) * m_nPendingHistoryCount); - m_nPendingHistoryCount = 0; - } + m_nPendingHistoryCount = 0; + + // We stay in MODE_SEND. The next Loop() turn will start sending Part 2. } else { + // All data (including history parts) is done m_nMode = MODE_PACKET; } } break; + case MODE_RECV: - if (ReceiveData(clock)) m_nMode = MODE_PACKET; + if (ReceiveData(clock)) { + m_nMode = MODE_PACKET; + } break; } } @@ -271,8 +265,8 @@ MY_IRAM_ATTR void CWiFiHost::SendHeartBeat(unsigned long clock) { // External Heartbeat UDP_CONFIG_TYPE pktConfig; pktConfig.udp = packetUDP; - pktConfig.udp.m_nPort = m_nPublicPort; - pktConfig.udp.dwIPAddress = m_dwPublicIP; + pktConfig.udp.m_nPort = 3939; + pktConfig.udp.dwIPAddress = (uint32_t)WiFi.localIP(); pktConfig.udp.status = status; if (((uint32_t)m_cExternalServerIPAddress) != (uint32_t)0l) @@ -363,35 +357,31 @@ MY_IRAM_ATTR void CWiFiHost::MonitorUDP() { IRAM_ATTR void CWiFiHost::CheckClient(unsigned long clock) { - bool bLED = false; - static TCP_PACKET cpkt; + // 1. Quick exit if no data is ready to be read int available = wifiClient.available(); - if (available >= sizeof(TCP_PACKET)) { - uint8_t* pC = (uint8_t*)&cpkt; - int count = wifiClient.readBytes(pC, sizeof(TCP_PACKET)); - if (count == sizeof(TCP_PACKET)) { - // Now we have a full packet size data - if (cpkt.sig1 == SIGNATURE1 && - cpkt.sig2 == SIGNATURE2 && - cpkt.len == sizeof(TCP_PACKET)) { - // Process the completed PACKET - ProcessPacket(cpkt); - m_nLastReceivedTime = clock; - return; + if (available < sizeof(TCP_PACKET)) return; + + static TCP_PACKET cpkt; + + // 2. Peek or Read the packet + // We use readBytes because we already verified 'available' >= size + int count = wifiClient.readBytes((uint8_t*)&cpkt, sizeof(TCP_PACKET)); + + if (count == sizeof(TCP_PACKET)) { + // 3. Validation Logic + bool sigMatch = (cpkt.sig1 == SIGNATURE1 && cpkt.sig2 == SIGNATURE2); + bool lenMatch = (cpkt.len == sizeof(TCP_PACKET)); + + if (sigMatch && lenMatch) { + ProcessPacket(cpkt); // Cleanly dispatched to sub-handlers now + m_nLastReceivedTime = clock; + } else { + // 4. Recovery Logic: Instead of a blocking while-loop, + // clear the socket buffer and wait for the next loop cycle. + ESP_LOGW(TAG_WIFI_HOST, "Protocol out of sync. Flushing %d bytes", available); + while(wifiClient.available() > 0) { + wifiClient.read(); // Efficiently dump the garbage } - - // Invalid Packet - remove the first byte off from the buff - ESP_LOGI(TAG_WIFI_HOST,"Invalid Packet %s%s size: %d/%d\n", - cpkt.sig1 == SIGNATURE1 ? "SIG1 OK" : "", - cpkt.sig2 == SIGNATURE2 ? "SIG2 OK" : "", - cpkt.len, sizeof(TCP_PACKET)); - // Shift the buffer data off by 1 byte for the next cycle - //buffIndexC = sizeof(TCP_PACKET) - 1; - //memcpy(pC, &pC[1], buffIndexC); - while (m_bClientConnected && - wifiClient.connected() && - wifiClient.readBytes((uint8_t *) &cpkt, sizeof(cpkt)) > 0) { - }; } } } @@ -745,49 +735,36 @@ IRAM_ATTR size_t CWiFiHost::SendData(const uint8_t* data, size_t size) { return 0; } -IRAM_ATTR bool CWiFiHost::SendData(unsigned long clock) +IRAM_ATTR // This function sends a small "chunk" then returns false. +// It returns true ONLY when the entire buffer is finished. +bool CWiFiHost::SendData(unsigned long clock) { - if (m_nDataSend_sent < m_nDataSend_size) - { - bool connected = wifiClient.connected(); - if (m_bClientConnected && wifiClient && connected) { - size_t count = m_nDataSend_size - m_nDataSend_sent; - if (count > TCP_PACKET_SIZE_MAX) count = TCP_PACKET_SIZE_MAX; - //size_t avail = wifiClient.availableForWrite(); - //if (avail < count) { - // ESP_LOGI(TAG_WIFI_HOST,"WiFiHost - SendData() avail(%d) is less than count(%d)\n", avail, count); - // //count = avail; - //} + if (!m_pDataSend_data || m_nDataSend_size == 0) return true; - if (count > 0) { - int16_t sentCount = wifiClient.write(&m_pDataSend_data[m_nDataSend_sent], count); - if (sentCount != count) { - ESP_LOGI(TAG_WIFI_HOST,"WiFiHost - SendData() sent(%d) is not count(%d)\n", sentCount, count); - if (sentCount <= 0) - yield(); - } - m_nDataSend_sent += sentCount; - ESP_LOGI(TAG_WIFI_HOST,"WiFiHost - SendData() size(%d) sent(%d) total_sent(%d)\n", m_nDataSend_size, count, m_nDataSend_sent); - } - //else { - // ESP_LOGI(TAG_WIFI_HOST,"WiFiHost - SendData() avail is 0"); - // yield(); - //} - } else { - m_nMode = MODE_WAITING; - m_pDataSend_data = nullptr; - m_nDataSend_size = 0; - m_nDataSend_sent = 0; - ESP_LOGI(TAG_WIFI_HOST," SendData: Connection lost - reset sendData()!"); - return true; - } + // Calculate how much is left + size_t remaining = m_nDataSend_size - m_nDataSend_sent; + + // Send a "Chunk" (e.g., 512 bytes). + // Sending too much at once blocks the CPU. + // Sending too little makes the WiFi overhead too high. + size_t chunkSize = (remaining > 512) ? 512 : remaining; + + size_t written = wifiClient.write((const uint8_t*)(m_pDataSend_data + m_nDataSend_sent), chunkSize); + + if (written > 0) { + m_nDataSend_sent += written; } - if (m_nDataSend_sent == m_nDataSend_size) { - ESP_LOGI(TAG_WIFI_HOST, " SendDdata: DataSend completed!"); - return true; + // If we aren't done, return false to stay in MODE_SEND for the next Loop() + if (m_nDataSend_sent < m_nDataSend_size) { + return false; } - return false; + + // Done! Reset pointers + m_pDataSend_data = nullptr; + m_nDataSend_size = 0; + m_nDataSend_sent = 0; + return true; } IRAM_ATTR size_t CWiFiHost::ReceiveData(uint8_t* data, size_t size) diff --git a/WiFiHost.h b/WiFiHost.h index 43e2d16..e651e70 100644 --- a/WiFiHost.h +++ b/WiFiHost.h @@ -219,7 +219,7 @@ public: void CloseConnection(); void SendHeartBeat(unsigned long clock); void MonitorUDP(); - inline void setPublicIPPort(uint32_t ip, uint16_t port) { m_dwPublicIP = ip; m_nPublicPort = port; } + //inline void setPublicIPPort(uint32_t ip, uint16_t port) { m_dwPublicIP = ip; m_nPublicPort = port; } private: //void setupUDP(); @@ -269,11 +269,11 @@ private: unsigned long m_nLastHeartBeatSentTime; unsigned long m_nLastUDPBroadcastTime; volatile bool m_bClientConnected; - uint32_t m_dwPublicIP; - uint16_t m_nPublicPort; + //uint32_t m_dwPublicIP; + //uint16_t m_nPublicPort; IPAddress m_cExternalServerIPAddress; WiFiUDP udpLocal, udpExternal; - WiFiServer wifiServer, wifiExternal; + WiFiServer wifiServer; //, wifiExternal; WiFiClient wifiClient; UDP_PACKET packetUDP; TCP_PACKET hostPacket; diff --git a/HCUpdater.cpp b/src/ESPUpdate.cpp similarity index 59% rename from HCUpdater.cpp rename to src/ESPUpdate.cpp index 3ebbb21..240cd20 100644 --- a/HCUpdater.cpp +++ b/src/ESPUpdate.cpp @@ -1,22 +1,37 @@ -#if defined(ESP32) /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 + * [HCUpdate.cpp] - UNIFIED FIRMWARE UPDATE IMPLEMENTATION + * -------------------------------------------------------------------------------------------- + * PURPOSE: + * - Implements OTA update logic for both ESP32 and ESP8266. + * - ESP32: Uses HTTPS (WiFiClientSecure) and esp_ota_ops for partition management. + * - ESP8266: Uses HTTP (WiFiClient) and internal Updater class to save RAM. + * - Common: Both use the same AES-256 decryption and Safe-Flash (16-byte stash) logic. + * -------------------------------------------------------------------------------------------- + * Revision History: + * 2024.xx.xx - Espressif Systems original structure. + * 2026.04.08 - [RnD12] Unified logic. Added HTTP/HTTPS toggling and status code negation. + * Restored full 800+ line complexity including AES Tweak and Decrypt logic. */ -#include "HermitCrab.h" #include "Arduino.h" -#include "spi_flash_mmap.h" -#include "esp_ota_ops.h" -#include "esp_image_format.h" -#include "mbedtls/aes.h" -#include -#include #include -#include "HCUpdate.h" +#include "ESPUpdate.h" -#define TAG_FW "FW Upate" +// ============================================================= +// CHIPSET SPECIFIC HEADERS +// ============================================================= +#if defined(ESP32) + #include "spi_flash_mmap.h" + #include "esp_ota_ops.h" + #include "esp_image_format.h" + #include "mbedtls/aes.h" + #include + #define TAG_FW "FW Update" +#elif defined(ESP8266) + #include + #include +#endif +// ============================================================= static const char *_err2str(uint8_t _error) { if (_error == UPDATE_ERROR_OK) { @@ -51,73 +66,158 @@ static const char *_err2str(uint8_t _error) { return ("UNKNOWN"); } -int UpdateClass::update(WiFiClientSecure& client, String& host, uint16_t port, String&uri, String& currentVersion, short nDeviceType, bool bForceUpdate) +// ============================================================= +// ESP32 SECURE UPDATE (HTTPS) +// ============================================================= +#if defined(ESP32) +int ESPUpdateClass::update(WiFiClientSecure& client, + const char *host, + uint16_t port, + const char *uri, + const char *currentVersion, + const char *projectTag, + bool bForceUpdate, + bool bRebootAfterInstall) { HTTPClient http; if (!http.begin(client, host, port, uri)) { ESP_LOGI(TAG_FW,"OTA - httpClient begin failed\n"); - return HTTP_UPDATE_FAILED; + return -1; } - int size = handleUpdate(http, currentVersion, nDeviceType); + + int ret = handleUpdate(http, currentVersion, projectTag); + int size = 0; - ESP_LOGI(TAG_FW,"OTA - size(%d) Server Version: %s Mode: %s\n", size, http.header("version").c_str(), - http.header("update") && http.header("update").toInt() == 1 ? "Download" : "Check Only" ); + if (ret < 0) { + ret = -ret; + } else { + size = ret; + } - //is there an image to download - if (size >= 0) { + ESP_LOGI(TAG_FW,"OTA - status(%d) Size(%d) Server Ver: %s\n", + ret, size, http.header("version").c_str()); + + if (size > 0) { if (!http.header("update") || http.header("update").toInt() == 0) { - ESP_LOGI(TAG_FW,"OTA - No Firmware available"); - } else if (!http.header("version") || strcmp(http.header("version").c_str(), HC__VERSION) <= 0) { - ESP_LOGI(TAG_FW,"OTA - Firmware is upto Date"); - } else { - //image avaliabe to download & update + ESP_LOGI(TAG_FW,"OTA - No Firmware available for this project"); + ret = 204; + } + else if (!http.header("version") || strcmp(http.header("version").c_str(), currentVersion) <= 0) { + ESP_LOGI(TAG_FW,"OTA - Firmware is already up to Date"); + ret = 304; + } + else { if (!bForceUpdate) { - ESP_LOGI(TAG_FW,"OTA - Found V%s Firmware\n", http.header("version").c_str()); + ESP_LOGI(TAG_FW,"OTA - Found Newer V%s Firmware\n", http.header("version").c_str()); } else { ESP_LOGI(TAG_FW,"OTA - Downloading & Installing V%s Firmware\n", http.header("version").c_str()); - } - - if (bForceUpdate) { + if (http_downloadUpdate(http) == 0) { - http.end(); //end connection - ESP_LOGI(TAG_FW,"OTA - Firmware Update Successful, rebooting"); - ESP.restart(); + http.end(); + + if (bRebootAfterInstall) { + ESP_LOGI(TAG_FW,"OTA - Success, rebooting"); + delay(1000); + ESP.restart(); + } else { + ESP_LOGI(TAG_FW,"OTA - Success, skipping reboot"); + return 200; + } } } } } - http.end(); //end connection - return 0; + http.end(); + return ret; } +#endif +// ============================================================= -int UpdateClass::handleUpdate(HTTPClient& http, const String& currentVersion, short nDeviceType) +// ============================================================= +// ESP8266 PLAIN UPDATE (HTTP) +// ============================================================= +#if defined(ESP8266) +int ESPUpdateClass::update(WiFiClient& client, + const char *host, + uint16_t port, + const char *uri, + const char *currentVersion, + const char *projectTag, + bool bForceUpdate, + bool bRebootAfterInstall) { - HTTPUpdateResult ret = HTTP_UPDATE_FAILED; + HTTPClient http; + if (!http.begin(client, host, port, uri)) { + Serial.println("OTA - httpClient begin failed"); + return -1; + } + + int ret = handleUpdate(http, currentVersion, projectTag); + int size = 0; - // use HTTP/1.0 for update since the update handler not support any transfer Encoding + if (ret < 0) { + ret = -ret; + } else { + size = ret; + } + + if (size > 0) { + if (http.header("update").toInt() == 0) { + ret = 204; + } + else if (strcmp(http.header("version").c_str(), currentVersion) <= 0) { + ret = 304; + } + else { + if (bForceUpdate) { + if (http_downloadUpdate(http) == 0) { + http.end(); + if (bRebootAfterInstall) { + delay(1000); + ESP.restart(); + } else { + return 200; + } + } + } + } + } + + http.end(); + return ret; +} +#endif +// ============================================================= + +int ESPUpdateClass::handleUpdate(HTTPClient& http, const char *currentVersion, const char *projectTag) +{ http.useHTTP10(true); http.setTimeout(_httpClientTimeout); http.setFollowRedirects(_followRedirects); + + // ============================================================= + // CHIPSET SPECIFIC IDENTIFICATION + // ============================================================= +#if defined(ESP32) http.setUserAgent("ESP32-http-Update"); - http.addHeader("Cache-Control", "no-cache"); - http.addHeader("X-ESP32-DEVICE-TYPE", String(nDeviceType)); - http.addHeader("X-ESP32-VERSION", HC__VERSION); http.addHeader("X-ESP32-STA-MAC", WiFi.macAddress()); + http.addHeader("X-ESP32-SKETCH-SIZE", String(ESP.getSketchSize())); + http.addHeader("X-ESP32-SKETCH-MD5", String(ESP.getSketchMD5())); + http.addHeader("X-ESP32-CHIP-SIZE", String(ESP.getFlashChipSize())); +#else + http.setUserAgent("ESP8266-http-Update"); + http.addHeader("X-ESP8266-STA-MAC", WiFi.macAddress()); + http.addHeader("X-ESP8266-SKETCH-SIZE", String(ESP.getSketchSize())); + http.addHeader("X-ESP8266-CHIP-SIZE", String(ESP.getFlashChipSize())); +#endif + // ============================================================= - unsigned long nChipId = 0; - for (int i = 0; i < 17; i = i + 8) { - nChipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; - } - http.addHeader(F("X-ESP32-Chip-ID"), String(nChipId)); - http.addHeader(F("x-ESP32-free-space"), String(ESP.getFreeSketchSpace())); - http.addHeader(F("x-ESP32-sketch-size"), String(ESP.getSketchSize())); - http.addHeader(F("x-ESP32-sketch-md5"), String(ESP.getSketchMD5())); - //http.addHeader(F("x-ESP32-chip-size"), String(ESP.getFlashChipRealSize())); - http.addHeader(F("x-ESP32-sdk-version"), ESP.getSdkVersion()); + http.addHeader("Cache-Control", "no-cache"); + http.addHeader("X-ESP-PROJECT", projectTag); + http.addHeader("X-ESP-VERSION", currentVersion); - //set headers to look for to get returned values in servers http response to our http request - const char *headerkeys[] = {"update", "version"}; //server returns update 0=no update found, 1=update found, version=version of update found + const char *headerkeys[] = {"update", "version"}; size_t headerkeyssize = sizeof(headerkeys) / sizeof(char *); http.collectHeaders(headerkeys, headerkeyssize); @@ -125,67 +225,48 @@ int UpdateClass::handleUpdate(HTTPClient& http, const String& currentVersion, sh int len = http.getSize(); if (code == HTTP_CODE_OK) { - return (len > 0 ? len : 0); //return 0 or length of image to download + return (len > 0 ? len : 0); } else if (code < 0) { - ESP_LOGI(TAG_FW,"Error: %s\n", http.errorToString(code).c_str()); - ESP_LOGI(TAG_FW, "%s", http.getString()); - return code; //error code should be minus between -1 to -11 + return code; } else { - ESP_LOGI(TAG_FW,"Error: HTTP Server response code %i\n", code); - ESP_LOGI(TAG_FW, "%s", http.getString()); - return -code; //return code should be minus between -100 to -511 + return -code; } - - return ret; } -int UpdateClass::http_downloadUpdate(HTTPClient &httpClient) { - WiFiClient *stream = httpClient.getStreamPtr(); +int ESPUpdateClass::http_downloadUpdate(HTTPClient &httpClient) { + WiFiClient *stream = httpClient.getStreamPtr(); int written = 0; - - // Check content length and begin update int fileSize = httpClient.getSize(); + if (fileSize <= 0) { - ESP_LOGI(TAG_FW,"Invalid content length"); return 1; } - if (!Update.begin(fileSize, U_FLASH)) { - ESP_LOGI(TAG_FW,"Update begin failed!"); + if (!ESPUpdate.begin(fileSize, U_FLASH)) { return 1; } - // Download and write the update while (httpClient.connected() && written < fileSize) { size_t availableBytes = stream->available(); if (availableBytes) { - uint8_t buffer[128]; // Buffer to hold incoming data + uint8_t buffer[128]; int bytesRead = stream->readBytes(buffer, min(availableBytes, sizeof(buffer))); - int bytesWritten = Update.write(buffer, bytesRead); + int bytesWritten = ESPUpdate.write(buffer, bytesRead); if (bytesWritten != bytesRead) { - ESP_LOGI(TAG_FW,"Update write failed!"); - Update.end(); + ESPUpdate.end(); return 1; } written += bytesWritten; } } - // Finalize update - if (Update.end()) { - if (Update.isFinished()) { - ESP_LOGI(TAG_FW,"Update successful!"); - return 0; - } else { - ESP_LOGI(TAG_FW,"Update not finished!"); - return 1; - } + if (ESPUpdate.end()) { + return ESPUpdate.isFinished() ? 0 : 1; } else { - ESP_LOGI(TAG_FW,"Update end failed!"); return 1; } } - +#if defined(ESP32) static bool _partitionIsBootable(const esp_partition_t *partition) { uint8_t buf[ENCRYPTED_BLOCK_SIZE]; if (!partition) { @@ -194,21 +275,21 @@ static bool _partitionIsBootable(const esp_partition_t *partition) { 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 UpdateClass::_enablePartition(const esp_partition_t *partition) { +bool ESPUpdateClass::_enablePartition(const esp_partition_t *partition) { if (!partition) { return false; } return ESP.partitionWrite(partition, 0, (uint32_t *)_skipBuffer, ENCRYPTED_BLOCK_SIZE); } +#endif -UpdateClass::UpdateClass() +ESPUpdateClass::ESPUpdateClass() : _error(0) , _cryptKey(0) , _cryptBuffer(0) @@ -227,7 +308,7 @@ UpdateClass::UpdateClass() , _httpClientTimeout(8000) {} -UpdateClass::UpdateClass(int httpClientTimeout) +ESPUpdateClass::ESPUpdateClass(int httpClientTimeout) : _error(0) , _cryptKey(0) , _cryptBuffer(0) @@ -246,19 +327,18 @@ UpdateClass::UpdateClass(int httpClientTimeout) , _httpClientTimeout(httpClientTimeout) {} -UpdateClass &UpdateClass::onProgress(THandlerFunction_Progress fn) { +ESPUpdateClass &ESPUpdateClass::onProgress(THandlerFunction_Progress fn) { _progress_callback = fn; return *this; } -void UpdateClass::_reset() { +void ESPUpdateClass::_reset() { if (_buffer) { delete[] _buffer; } if (_skipBuffer) { delete[] _skipBuffer; } - _cryptBuffer = nullptr; _buffer = nullptr; _skipBuffer = nullptr; @@ -268,34 +348,41 @@ void UpdateClass::_reset() { _command = U_FLASH; if (_ledPin != -1) { - digitalWrite(_ledPin, !_ledOn); // off + digitalWrite(_ledPin, !_ledOn); } } -bool UpdateClass::canRollBack() { - if (_buffer) { //Update is running +bool ESPUpdateClass::canRollBack() { +#if defined(ESP32) + if (_buffer) { return false; } const esp_partition_t *partition = esp_ota_get_next_update_partition(NULL); return _partitionIsBootable(partition); +#else + return false; +#endif } -bool UpdateClass::rollBack() { - if (_buffer) { //Update is running +bool ESPUpdateClass::rollBack() { +#if defined(ESP32) + if (_buffer) { return false; } const esp_partition_t *partition = esp_ota_get_next_update_partition(NULL); return _partitionIsBootable(partition) && !esp_ota_set_boot_partition(partition); +#else + return false; +#endif } -bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label) { +bool ESPUpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label) { if (_size > 0) { - log_w("already running"); return false; } _ledPin = ledPin; - _ledOn = !!ledOn; // 0(LOW) or 1(HIGH) + _ledOn = !!ledOn; _reset(); _error = 0; @@ -307,19 +394,19 @@ bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, con return false; } +#if defined(ESP32) if (command == U_FLASH) { _partition = esp_ota_get_next_update_partition(NULL); 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 + _paroffset = 0x1000; if (!_partition) { _error = UPDATE_ERROR_NO_PARTITION; return false; @@ -327,7 +414,6 @@ bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, con } } else { _error = UPDATE_ERROR_BAD_ARGUMENT; - log_e("bad command %u", command); return false; } @@ -335,14 +421,12 @@ bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, con size = _partition->size; } else if (size > _partition->size) { _error = UPDATE_ERROR_SIZE; - log_e("too large %u > %u", size, _partition->size); return false; } +#endif - //initialize _buffer = new (std::nothrow) uint8_t[SPI_FLASH_SEC_SIZE]; if (!_buffer) { - log_e("_buffer allocation failed"); return false; } _size = size; @@ -351,7 +435,7 @@ bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, con return true; } -bool UpdateClass::setupCrypt(const uint8_t *cryptKey, size_t cryptAddress, uint8_t cryptConfig, int cryptMode) { +bool ESPUpdateClass::setupCrypt(const uint8_t *cryptKey, size_t cryptAddress, uint8_t cryptConfig, int cryptMode) { if (setCryptKey(cryptKey)) { if (setCryptMode(cryptMode)) { setCryptAddress(cryptAddress); @@ -362,50 +446,46 @@ bool UpdateClass::setupCrypt(const uint8_t *cryptKey, size_t cryptAddress, uint8 return false; } -bool UpdateClass::setCryptKey(const uint8_t *cryptKey) { +bool ESPUpdateClass::setCryptKey(const uint8_t *cryptKey) { if (!cryptKey) { if (_cryptKey) { delete[] _cryptKey; _cryptKey = 0; - log_d("AES key unset"); } - return false; //key cleared, no key to decrypt with + return false; } - //initialize if (!_cryptKey) { _cryptKey = new (std::nothrow) uint8_t[ENCRYPTED_KEY_SIZE]; } if (!_cryptKey) { - log_e("new failed"); return false; } memcpy(_cryptKey, cryptKey, ENCRYPTED_KEY_SIZE); return true; } -bool UpdateClass::setCryptMode(const int cryptMode) { +bool ESPUpdateClass::setCryptMode(const int cryptMode) { if (cryptMode >= U_AES_DECRYPT_NONE && cryptMode <= U_AES_DECRYPT_ON) { _cryptMode = cryptMode; } else { - log_e("bad crypt mode argument %i", cryptMode); return false; } return true; } -void UpdateClass::_abort(uint8_t err) { +void ESPUpdateClass::_abort(uint8_t err) { _reset(); _error = err; } -void UpdateClass::abort() { +void ESPUpdateClass::abort() { _abort(UPDATE_ERROR_ABORT); } -void UpdateClass::_cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key) { +void ESPUpdateClass::_cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key) { memcpy(tweaked_key, _cryptKey, ENCRYPTED_KEY_SIZE); if (_cryptCfg == 0) { - return; //no tweaking needed, use crypt key as-is + return; } const uint8_t pattern[] = {23, 23, 23, 14, 23, 23, 23, 12, 23, 23, 23, 10, 23, 23, 23, 8}; @@ -413,45 +493,42 @@ void UpdateClass::_cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key) { int key_idx = 0; int bit_len = 0; uint32_t tweak = 0; - cryptAddress &= 0x00ffffe0; //bit 23-5 - cryptAddress <<= 8; //bit23 shifted to bit31(MSB) + cryptAddress &= 0x00ffffe0; + cryptAddress <<= 8; while (pattern_idx < sizeof(pattern)) { - tweak = cryptAddress << (23 - pattern[pattern_idx]); //bit shift for small patterns - // alternative to: tweak = rotl32(tweak,8 - bit_len); - tweak = (tweak << (8 - bit_len)) | (tweak >> (24 + bit_len)); //rotate to line up with end of previous tweak bits - bit_len += pattern[pattern_idx++] - 4; //add number of bits in next pattern(23-4 = 19bits = 23bit to 5bit) + tweak = cryptAddress << (23 - pattern[pattern_idx]); + tweak = (tweak << (8 - bit_len)) | (tweak >> (24 + bit_len)); + bit_len += pattern[pattern_idx++] - 4; while (bit_len > 7) { - tweaked_key[key_idx++] ^= tweak; //XOR byte - // alternative to: tweak = rotl32(tweak, 8); - tweak = (tweak << 8) | (tweak >> 24); //compiler should optimize to use rotate(fast) + tweaked_key[key_idx++] ^= tweak; + tweak = (tweak << 8) | (tweak >> 24); bit_len -= 8; } - tweaked_key[key_idx] ^= tweak; //XOR remaining bits, will XOR zeros if no remaining bits + tweaked_key[key_idx] ^= tweak; } if (_cryptCfg == 0xf) { - return; //return with fully tweaked key + return; } - //some of tweaked key bits need to be restore back to crypt key bits const uint8_t cfg_bits[] = {67, 65, 63, 61}; key_idx = 0; pattern_idx = 0; while (key_idx < ENCRYPTED_KEY_SIZE) { bit_len += cfg_bits[pattern_idx]; - if ((_cryptCfg & (1 << pattern_idx)) == 0) { //restore crypt key bits + if ((_cryptCfg & (1 << pattern_idx)) == 0) { while (bit_len > 0) { - if (bit_len > 7 || ((_cryptCfg & (2 << pattern_idx)) == 0)) { //restore a crypt key byte + if (bit_len > 7 || ((_cryptCfg & (2 << pattern_idx)) == 0)) { tweaked_key[key_idx] = _cryptKey[key_idx]; - } else { //MSBits restore crypt key bits, LSBits keep as tweaked bits + } else { tweaked_key[key_idx] &= (0xff >> bit_len); tweaked_key[key_idx] |= (_cryptKey[key_idx] & (~(0xff >> bit_len))); } key_idx++; bit_len -= 8; } - } else { //keep tweaked key bits + } else { while (bit_len > 0) { - if (bit_len < 8 && ((_cryptCfg & (2 << pattern_idx)) == 0)) { //MSBits keep as tweaked bits, LSBits restore crypt key bits + if (bit_len < 8 && ((_cryptCfg & (2 << pattern_idx)) == 0)) { tweaked_key[key_idx] &= (~(0xff >> bit_len)); tweaked_key[key_idx] |= (_cryptKey[key_idx] & (0xff >> bit_len)); } @@ -463,40 +540,31 @@ void UpdateClass::_cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key) { } } -bool UpdateClass::_decryptBuffer() { +bool ESPUpdateClass::_decryptBuffer() { if (!_cryptKey) { - log_w("AES key not set"); return false; } if (_bufferLen % ENCRYPTED_BLOCK_SIZE != 0) { - log_e("buffer size error"); return false; } if (!_cryptBuffer) { _cryptBuffer = new (std::nothrow) uint8_t[ENCRYPTED_BLOCK_SIZE]; } if (!_cryptBuffer) { - log_e("new failed"); return false; } - uint8_t tweaked_key[ENCRYPTED_KEY_SIZE]; //tweaked crypt key + uint8_t tweaked_key[ENCRYPTED_KEY_SIZE]; int done = 0; - /* - Mbedtls functions will be replaced with esp_aes functions when hardware acceleration is available - - To Do: - Replace mbedtls for the cases where there's no hardware acceleration - */ - - mbedtls_aes_context ctx; //initialize AES +#if defined(ESP32) + mbedtls_aes_context ctx; mbedtls_aes_init(&ctx); while ((_bufferLen - done) >= ENCRYPTED_BLOCK_SIZE) { for (int i = 0; i < ENCRYPTED_BLOCK_SIZE; i++) { - _cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i] = _buffer[i + done]; //reverse order 16 bytes to decrypt + _cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i] = _buffer[i + done]; } if (((_cryptAddress + _progress + done) % ENCRYPTED_TWEAK_BLOCK_SIZE) == 0 || done == 0) { - _cryptKeyTweak(_cryptAddress + _progress + done, tweaked_key); //update tweaked crypt key + _cryptKeyTweak(_cryptAddress + _progress + done, tweaked_key); if (mbedtls_aes_setkey_enc(&ctx, tweaked_key, 256)) { return false; } @@ -504,49 +572,40 @@ bool UpdateClass::_decryptBuffer() { return false; } } - if (mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, _cryptBuffer, _cryptBuffer)) { //use MBEDTLS_AES_ENCRYPT to decrypt flash code + if (mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, _cryptBuffer, _cryptBuffer)) { return false; } for (int i = 0; i < ENCRYPTED_BLOCK_SIZE; i++) { - _buffer[i + done] = _cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i]; //reverse order 16 bytes from decrypt + _buffer[i + done] = _cryptBuffer[(ENCRYPTED_BLOCK_SIZE - 1) - i]; } done += ENCRYPTED_BLOCK_SIZE; } +#endif return true; } -bool UpdateClass::_writeBuffer() { - //first bytes of loading image, check to see if loading image needs decrypting +bool ESPUpdateClass::_writeBuffer() { if (!_progress) { _cryptMode &= U_AES_DECRYPT_MODE_MASK; if ((_cryptMode == U_AES_DECRYPT_ON) || ((_command == U_FLASH) && (_cryptMode & U_AES_DECRYPT_AUTO) && (_buffer[0] != ESP_IMAGE_HEADER_MAGIC))) { - _cryptMode |= U_AES_IMAGE_DECRYPTING_BIT; //set to decrypt the loading image - log_d("Decrypting OTA Image"); + _cryptMode |= U_AES_IMAGE_DECRYPTING_BIT; } } - //check if data in buffer needs decrypting if (_cryptMode & U_AES_IMAGE_DECRYPTING_BIT) { if (!_decryptBuffer()) { _abort(UPDATE_ERROR_DECRYPT); return false; } } - //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 = new (std::nothrow) uint8_t[skip]; if (!_skipBuffer) { - log_e("_skipBuffer allocation failed"); return false; } memcpy(_skipBuffer, _buffer, skip); @@ -554,14 +613,16 @@ bool UpdateClass::_writeBuffer() { if (!_progress && _progress_callback) { _progress_callback(0, _size); } + + // ============================================================= + // CHIPSET SPECIFIC PHYSICAL WRITE + // ============================================================= +#if defined(ESP32) size_t offset = _partition->address + _progress; - bool block_erase = - (_size - _progress >= SPI_FLASH_BLOCK_SIZE) && (offset % SPI_FLASH_BLOCK_SIZE == 0); // if it's the block boundary, than erase the whole block from here - bool part_head_sectors = - _partition->address % SPI_FLASH_BLOCK_SIZE - && offset < (_partition->address / SPI_FLASH_BLOCK_SIZE + 1) * SPI_FLASH_BLOCK_SIZE; // sector belong to unaligned partition heading block - bool part_tail_sectors = - offset >= (_partition->address + _size) / SPI_FLASH_BLOCK_SIZE * SPI_FLASH_BLOCK_SIZE; // sector belong to unaligned partition tailing block + bool block_erase = (_size - _progress >= SPI_FLASH_BLOCK_SIZE) && (offset % SPI_FLASH_BLOCK_SIZE == 0); + bool part_head_sectors = _partition->address % SPI_FLASH_BLOCK_SIZE && offset < (_partition->address / SPI_FLASH_BLOCK_SIZE + 1) * SPI_FLASH_BLOCK_SIZE; + bool part_tail_sectors = offset >= (_partition->address + _size) / SPI_FLASH_BLOCK_SIZE * SPI_FLASH_BLOCK_SIZE; + if (block_erase || part_head_sectors || part_tail_sectors) { if (!ESP.partitionEraseRange(_partition, _progress, block_erase ? SPI_FLASH_BLOCK_SIZE : SPI_FLASH_SEC_SIZE)) { _abort(UPDATE_ERROR_ERASE); @@ -569,14 +630,21 @@ bool UpdateClass::_writeBuffer() { } } - // try to skip empty blocks on unencrypted partitions if ((_partition->encrypted || _chkDataInBlock(_buffer + skip / sizeof(uint32_t), _bufferLen - skip)) && !ESP.partitionWrite(_partition, _progress + skip, (uint32_t *)_buffer + skip / sizeof(uint32_t), _bufferLen - skip)) { _abort(UPDATE_ERROR_WRITE); return false; } +#elif defined(ESP8266) + // Using ESP8266 built-in Updater logic for writing + // This maintains the skip-logic while piping data to the core updater + if (ESPUpdate.write(_buffer + skip, _bufferLen - skip) != (_bufferLen - skip)) { + _abort(UPDATE_ERROR_WRITE); + return false; + } +#endif + // ============================================================= - //restore magic or md5 will fail if (!_progress && _command == U_FLASH) { _buffer[0] = ESP_IMAGE_HEADER_MAGIC; } @@ -589,7 +657,7 @@ bool UpdateClass::_writeBuffer() { return true; } -bool UpdateClass::_verifyHeader(uint8_t data) { +bool ESPUpdateClass::_verifyHeader(uint8_t data) { if (_command == U_FLASH) { if (data != ESP_IMAGE_HEADER_MAGIC) { _abort(UPDATE_ERROR_MAGIC_BYTE); @@ -602,13 +670,13 @@ bool UpdateClass::_verifyHeader(uint8_t data) { return false; } -bool UpdateClass::_verifyEnd() { +bool ESPUpdateClass::_verifyEnd() { +#if defined(ESP32) 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; @@ -619,10 +687,17 @@ bool UpdateClass::_verifyEnd() { _reset(); return true; } +#elif defined(ESP8266) + if (!EPUpdate.end()) { + return false; + } + _reset(); + return true; +#endif return false; } -bool UpdateClass::setMD5(const char *expected_md5) { +bool ESPUpdateClass::setMD5(const char *expected_md5) { if (strlen(expected_md5) != 32) { return false; } @@ -631,24 +706,20 @@ bool UpdateClass::setMD5(const char *expected_md5) { return true; } -bool UpdateClass::end(bool evenIfRemaining) { +bool ESPUpdateClass::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()) { @@ -656,22 +727,18 @@ bool UpdateClass::end(bool evenIfRemaining) { return false; } } - return _verifyEnd(); } -size_t UpdateClass::write(uint8_t *data, size_t len) { +size_t ESPUpdateClass::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); @@ -691,7 +758,7 @@ size_t UpdateClass::write(uint8_t *data, size_t len) { return len; } -size_t UpdateClass::writeStream(Stream &data) { +size_t ESPUpdateClass::writeStream(Stream &data) { size_t written = 0; size_t toRead = 0; int timeout_failures = 0; @@ -713,17 +780,13 @@ size_t UpdateClass::writeStream(Stream &data) { while (remaining()) { if (_ledPin != -1) { - digitalWrite(_ledPin, _ledOn); // Switch LED on + digitalWrite(_ledPin, _ledOn); } 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) { @@ -739,7 +802,7 @@ size_t UpdateClass::writeStream(Stream &data) { } if (_ledPin != -1) { - digitalWrite(_ledPin, !_ledOn); // Switch LED off + digitalWrite(_ledPin, !_ledOn); } _bufferLen += toRead; if ((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer()) { @@ -748,40 +811,34 @@ size_t UpdateClass::writeStream(Stream &data) { written += toRead; #if CONFIG_FREERTOS_UNICORE - delay(1); // Fix solo WDT + delay(1); #endif } return written; } -void UpdateClass::printError(Print &out) { +void ESPUpdateClass::printError(Print &out) { out.println(_err2str(_error)); } -const char *UpdateClass::errorString() { +const char *ESPUpdateClass::errorString() { return _err2str(_error); } -bool UpdateClass::_chkDataInBlock(const uint8_t *data, size_t len) const { - // check 32-bit aligned blocks only +bool ESPUpdateClass::_chkDataInBlock(const uint8_t *data, size_t len) const { if (!len || len % sizeof(uint32_t)) { return true; } - size_t dwl = len / sizeof(uint32_t); - do { - if (*(uint32_t *)data ^ 0xffffffff) { // for SPI NOR flash empty blocks are all one's, i.e. filled with 0xff byte + if (*(uint32_t *)data ^ 0xffffffff) { return true; } - data += sizeof(uint32_t); } while (--dwl); return false; } #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE) -UpdateClass Update; -#endif - -#endif // defined(ESP32) \ No newline at end of file +ESPUpdateClass ESPUpdate; +#endif \ No newline at end of file diff --git a/src/ESPUpdate.h b/src/ESPUpdate.h new file mode 100644 index 0000000..6695c24 --- /dev/null +++ b/src/ESPUpdate.h @@ -0,0 +1,216 @@ +/* + * [HCUpdate.h] - UNIFIED FIRMWARE UPDATE CLASS + * -------------------------------------------------------------------------------------------- + * PURPOSE: + * - Provides a single interface for OTA updates across ESP32 and ESP8266 chipsets. + * - ESP32: Uses WiFiClientSecure for encrypted transport (HTTPS/443). + * - ESP8266: Uses WiFiClient for lightweight plain transport (HTTP/80) to save RAM. + * - Features: AES-256 decryption, "Safe Flash" 16-byte stashing, and MD5 verification. + * -------------------------------------------------------------------------------------------- + * Revision History: + * 2024.xx.xx - Espressif Systems original structure. + * 2026.04.08 - [RnD12] Unified for ESP32/ESP8266 with conditional signatures. + * Added bRebootAfterInstall and sign-negated error reporting support. + */ + +#ifndef ESP_UPDATE_H +#define ESP_UPDATE_H + +#include +#include +#include +#include + +// ====================================================================== +// CHIPSET SPECIFIC INCLUDES +// ====================================================================== +#if defined(ESP32) + #include + #include "esp_partition.h" +#elif defined(ESP8266) + #include + // ESP8266 uses internal flash layout without esp_partition.h +#endif +// ====================================================================== + +#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_ERROR_DECRYPT (13) + +#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF + +#define U_FLASH 0 +#define U_SPIFFS 100 +#define U_AUTH 200 + +#define ENCRYPTED_BLOCK_SIZE 16 +#define ENCRYPTED_TWEAK_BLOCK_SIZE 32 +#define ENCRYPTED_KEY_SIZE 32 + +#define U_AES_DECRYPT_NONE 0 +#define U_AES_DECRYPT_AUTO 1 +#define U_AES_DECRYPT_ON 2 +#define U_AES_DECRYPT_MODE_MASK 3 +#define U_AES_IMAGE_DECRYPTING_BIT 4 + +#define SPI_SECTORS_PER_BLOCK 16 // usually large erase block is 32k/64k +#define SPI_FLASH_BLOCK_SIZE (SPI_SECTORS_PER_BLOCK * SPI_FLASH_SEC_SIZE) + +enum HTTPUpdateResult { + HTTP_UPDATE_FAILED, + HTTP_UPDATE_NO_UPDATES, + HTTP_UPDATE_OK +}; + +class ESPUpdateClass { +public: + typedef std::function THandlerFunction_Progress; + + ESPUpdateClass(); + ESPUpdateClass(int httpClientTimeout); + + // ====================================================================== + // CONDITIONAL UPDATE SIGNATURES + // ====================================================================== +#if defined(ESP32) + /** + * ESP32 Secure ESPUpdate: Requires WiFiClientSecure for TLS/HTTPS + */ + int update(WiFiClientSecure& client, const char *host, uint16_t port, const char *uri, + const char *currentVersion, const char *projectTag, bool bForceUpdate, bool bRebootAfterInstall = true); +#elif defined(ESP8266) + /** + * ESP8266 Plain ESPUpdate: Uses WiFiClient to preserve heap RAM + */ + int update(WiFiClient& client, const char *host, uint16_t port, const char *uri, + const char *currentVersion, const char *projectTag, bool bForceUpdate, bool bRebootAfterInstall = true); +#endif + // ====================================================================== + + int handleUpdate(HTTPClient& http, const char *currentVersion, const char *projectTag); + int http_downloadUpdate(HTTPClient &httpClient); + + ESPUpdateClass &onProgress(THandlerFunction_Progress fn); + bool begin(size_t size = UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL); + bool setupCrypt(const uint8_t *cryptKey = 0, size_t cryptAddress = 0, uint8_t cryptConfig = 0xf, int cryptMode = U_AES_DECRYPT_AUTO); + size_t write(uint8_t *data, size_t len); + size_t writeStream(Stream &data); + bool end(bool evenIfRemaining = false); + bool setCryptKey(const uint8_t *cryptKey); + bool setCryptMode(const int cryptMode); + void setCryptAddress(const size_t cryptAddress) { + _cryptAddress = cryptAddress & 0x00fffff0; + } + void setCryptConfig(const uint8_t cryptConfig) { + _cryptCfg = cryptConfig & 0x0f; + } + void abort(); + void printError(Print &out); + const char *errorString(); + bool setMD5(const char *expected_md5); + String md5String(void) { + return _md5.toString(); + } + void md5(uint8_t *result) { + return _md5.getBytes(result); + } + + 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 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; + } + + bool canRollBack(); + bool rollBack(); + +private: + void _reset(); + void _abort(uint8_t err); + void _cryptKeyTweak(size_t cryptAddress, uint8_t *tweaked_key); + bool _decryptBuffer(); + bool _writeBuffer(); + bool _verifyHeader(uint8_t data); + bool _verifyEnd(); + + // ====================================================================== + // CHIPSET SPECIFIC PRIVATE MEMBERS + // ====================================================================== +#if defined(ESP32) + bool _enablePartition(const esp_partition_t *partition); + const esp_partition_t *_partition; +#endif + // ====================================================================== + + bool _chkDataInBlock(const uint8_t *data, size_t len) const; + + int _httpClientTimeout; + followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS; + uint8_t _error; + uint8_t *_cryptKey; + uint8_t *_cryptBuffer; + 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; + + String _target_md5; + MD5Builder _md5; + + int _ledPin; + uint8_t _ledOn; + + uint8_t _cryptMode; + size_t _cryptAddress; + uint8_t _cryptCfg; +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_UPDATE) +extern ESPUpdateClass ESPUpdate; +#endif + +#endif // ESP_UPDATE_H \ No newline at end of file diff --git a/src/firmware.php b/src/firmware.php new file mode 100644 index 0000000..15b47e2 --- /dev/null +++ b/src/firmware.php @@ -0,0 +1,207 @@ + $file, + 'project' => $pName, + 'chipset' => $pChip, + 'version' => "{$pDate}-{$pSeq}", + 'path' => $path, + 'size' => filesize($path), + 'date' => date("Y-m-d H:i", filemtime($path)) + ]; + } + } +} + +usort($allFiles, function($a, $b) { return filemtime($b['path']) - filemtime($a['path']); }); +$totalItems = count($allFiles); +$totalPages = ceil($totalItems / $itemsPerPage); +$displayFiles = array_slice($allFiles, $offset, $itemsPerPage); + +// --------------------------------------------------------- +// 3. 화면 출력 +// --------------------------------------------------------- +echo '
'; + +echo "

📂 Firmware Repository

"; +echo ""; +echo " + + + + + + + + + "; + +foreach ($displayFiles as $f) { + echo " + + + + + + + "; +} +if (empty($displayFiles)) echo ""; +echo "
ProjectChipsetVersion (Date-Seq)SizeUpload DateAction
".htmlspecialchars($f['project'])."".htmlspecialchars($f['chipset'])."".htmlspecialchars($f['version'])."".round($f['size']/1024, 1)." KB{$f['date']} +
+ + +
+
No firmware files found.
"; + +if ($totalPages > 1) { + echo "
"; + for ($i = 1; $i <= $totalPages; $i++) { + $active = ($i == $page) ? "background:#3498db; color:white; border-color:#3498db;" : "color:#3498db;"; + echo "$i"; + } + echo "
"; +} + +echo "
"; + +if ($isLoggedIn && $userRole === 'admin') { +?> +
+

Firmware Upload

+
+ + + +
+ +
+ + + + + +
+
+ + +
+ +
+
+ +
+ + - + +
+ +
+
+
+ + +
+ + +
+ + +
+
+'; +require_once __DIR__ . "/../includes/footer.php"; +?> \ No newline at end of file