Repository: fbiego/ESP32_BLE_OTA_Arduino Branch: main Commit: 905466b49eca Files: 23 Total size: 60.0 KB Directory structure: gitextract_ain1bw9_/ ├── LICENSE ├── README.md ├── esp32_ble_ota/ │ └── esp32_ble_ota.ino ├── esp32_ble_ota_lib_compact/ │ ├── .gitignore │ ├── .vscode/ │ │ ├── extensions.json │ │ └── settings.json │ ├── README.md │ ├── discover.py │ ├── include/ │ │ └── README │ ├── lib/ │ │ ├── README │ │ └── ble_ota_dfu/ │ │ ├── keywords.txt │ │ ├── library.json │ │ ├── library.properties │ │ └── src/ │ │ ├── ble_ota_dfu.cpp │ │ ├── ble_ota_dfu.hpp │ │ ├── freertos_utils.cpp │ │ └── freertos_utils.hpp │ ├── ota_updater.py │ ├── platformio.ini │ ├── src/ │ │ ├── main.cpp │ │ └── main.hpp │ └── test/ │ └── README └── esp32_nim_ble_ota/ └── esp32_nim_ble_ota.ino ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Felix Biego Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ESP32_BLE_OTA_Arduino OTA update on ESP32 via BLE - 1,038,544 bytes uploaded in 1min 25sec - Speed: ~ `12kb/s peak` [`esp32_ble_ota`](https://github.com/fbiego/ESP32_BLE_OTA_Arduino/tree/main/esp32_ble_ota) - 1,008,199 bytes [`esp32_nim_ble_ota`](https://github.com/fbiego/ESP32_BLE_OTA_Arduino/tree/main/esp32_nim_ble_ota) - 563,051 bytes ## Android app Get it on Google Play ## Python Script Update from your computer [`BLE_OTA_Python`](https://github.com/fbiego/BLE_OTA_Python) ## Video [`DIY ESP32 clock with BLE OTA`](https://youtu.be/TU_O4UPm00A) - 12kb/s peak (optimized) [`ESP32 OTA via BLE`](https://youtu.be/j4ELTS7QXFM) - 3kb/s (old) ================================================ FILE: esp32_ble_ota/esp32_ble_ota.ino ================================================ /* MIT License Copyright (c) 2021 Felix Biego Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "FS.h" #include "FFat.h" #include "SPIFFS.h" #include #include #include #include #define BUILTINLED 2 #define FORMAT_SPIFFS_IF_FAILED true #define FORMAT_FFAT_IF_FAILED true #define USE_SPIFFS //comment to use FFat #ifdef USE_SPIFFS #define FLASH SPIFFS #define FASTMODE false //SPIFFS write is slow #else #define FLASH FFat #define FASTMODE true //FFat is faster #endif #define NORMAL_MODE 0 // normal #define UPDATE_MODE 1 // receiving firmware #define OTA_MODE 2 // installing firmware uint8_t updater[16384]; uint8_t updater2[16384]; #define SERVICE_UUID "fb1e4001-54ae-4a28-9f74-dfccb248601d" #define CHARACTERISTIC_UUID_RX "fb1e4002-54ae-4a28-9f74-dfccb248601d" #define CHARACTERISTIC_UUID_TX "fb1e4003-54ae-4a28-9f74-dfccb248601d" static BLECharacteristic* pCharacteristicTX; static BLECharacteristic* pCharacteristicRX; static bool deviceConnected = false, sendMode = false, sendSize = true; static bool writeFile = false, request = false; static int writeLen = 0, writeLen2 = 0; static bool current = true; static int parts = 0, next = 0, cur = 0, MTU = 0; static int MODE = NORMAL_MODE; unsigned long rParts, tParts; static void rebootEspWithReason(String reason) { Serial.println(reason); delay(1000); ESP.restart(); } class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; } void onDisconnect(BLEServer* pServer) { deviceConnected = false; } }; class MyCallbacks: public BLECharacteristicCallbacks { // void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code) { // Serial.print("Status "); // Serial.print(s); // Serial.print(" on characteristic "); // Serial.print(pCharacteristic->getUUID().toString().c_str()); // Serial.print(" with code "); // Serial.println(code); // } void onNotify(BLECharacteristic *pCharacteristic) { uint8_t* pData; std::string value = pCharacteristic->getValue(); int len = value.length(); pData = pCharacteristic->getData(); if (pData != NULL) { // Serial.print("Notify callback for characteristic "); // Serial.print(pCharacteristic->getUUID().toString().c_str()); // Serial.print(" of data length "); // Serial.println(len); Serial.print("TX "); for (int i = 0; i < len; i++) { Serial.printf("%02X ", pData[i]); } Serial.println(); } } void onWrite(BLECharacteristic *pCharacteristic) { uint8_t* pData; std::string value = pCharacteristic->getValue(); int len = value.length(); pData = pCharacteristic->getData(); if (pData != NULL) { // Serial.print("Write callback for characteristic "); // Serial.print(pCharacteristic->getUUID().toString().c_str()); // Serial.print(" of data length "); // Serial.println(len); // Serial.print("RX "); // for (int i = 0; i < len; i++) { // leave this commented // Serial.printf("%02X ", pData[i]); // } // Serial.println(); if (pData[0] == 0xFB) { int pos = pData[1]; for (int x = 0; x < len - 2; x++) { if (current) { updater[(pos * MTU) + x] = pData[x + 2]; } else { updater2[(pos * MTU) + x] = pData[x + 2]; } } } else if (pData[0] == 0xFC) { if (current) { writeLen = (pData[1] * 256) + pData[2]; } else { writeLen2 = (pData[1] * 256) + pData[2]; } current = !current; cur = (pData[3] * 256) + pData[4]; writeFile = true; if (cur < parts - 1) { request = !FASTMODE; } } else if (pData[0] == 0xFD) { sendMode = true; if (FLASH.exists("/update.bin")) { FLASH.remove("/update.bin"); } } else if (pData[0] == 0xFE) { rParts = 0; tParts = (pData[1] * 256 * 256 * 256) + (pData[2] * 256 * 256) + (pData[3] * 256) + pData[4]; Serial.print("Available space: "); Serial.println(FLASH.totalBytes() - FLASH.usedBytes()); Serial.print("File Size: "); Serial.println(tParts); } else if (pData[0] == 0xFF) { parts = (pData[1] * 256) + pData[2]; MTU = (pData[3] * 256) + pData[4]; MODE = UPDATE_MODE; } else if (pData[0] == 0xEF) { FLASH.format(); sendSize = true; } } } }; static void writeBinary(fs::FS &fs, const char * path, uint8_t *dat, int len) { //Serial.printf("Write binary file %s\r\n", path); File file = fs.open(path, FILE_APPEND); if (!file) { Serial.println("- failed to open file for writing"); return; } file.write(dat, len); file.close(); writeFile = false; rParts += len; } void sendOtaResult(String result) { pCharacteristicTX->setValue(result.c_str()); pCharacteristicTX->notify(); delay(200); } void performUpdate(Stream &updateSource, size_t updateSize) { char s1 = 0x0F; String result = String(s1); if (Update.begin(updateSize)) { size_t written = Update.writeStream(updateSource); if (written == updateSize) { Serial.println("Written : " + String(written) + " successfully"); } else { Serial.println("Written only : " + String(written) + "/" + String(updateSize) + ". Retry?"); } result += "Written : " + String(written) + "/" + String(updateSize) + " [" + String((written / updateSize) * 100) + "%] \n"; if (Update.end()) { Serial.println("OTA done!"); result += "OTA Done: "; if (Update.isFinished()) { Serial.println("Update successfully completed. Rebooting..."); result += "Success!\n"; } else { Serial.println("Update not finished? Something went wrong!"); result += "Failed!\n"; } } else { Serial.println("Error Occurred. Error #: " + String(Update.getError())); result += "Error #: " + String(Update.getError()); } } else { Serial.println("Not enough space to begin OTA"); result += "Not enough space for OTA"; } if (deviceConnected) { sendOtaResult(result); delay(5000); } } void updateFromFS(fs::FS &fs) { File updateBin = fs.open("/update.bin"); if (updateBin) { if (updateBin.isDirectory()) { Serial.println("Error, update.bin is not a file"); updateBin.close(); return; } size_t updateSize = updateBin.size(); if (updateSize > 0) { Serial.println("Trying to start update"); performUpdate(updateBin, updateSize); } else { Serial.println("Error, file is empty"); } updateBin.close(); // when finished remove the binary from spiffs to indicate end of the process Serial.println("Removing update file"); fs.remove("/update.bin"); rebootEspWithReason("Rebooting to complete OTA update"); } else { Serial.println("Could not load update.bin from spiffs root"); } } void initBLE() { BLEDevice::init("ESP32 OTA"); BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); pCharacteristicTX = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY ); pCharacteristicRX = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); pCharacteristicRX->setCallbacks(new MyCallbacks()); pCharacteristicTX->setCallbacks(new MyCallbacks()); pCharacteristicTX->addDescriptor(new BLE2902()); pCharacteristicTX->setNotifyProperty(true); pService->start(); // BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue pAdvertising->setMinPreferred(0x12); BLEDevice::startAdvertising(); Serial.println("Characteristic defined! Now you can read it in your phone!"); } void setup() { Serial.begin(115200); Serial.println("Starting BLE OTA sketch"); pinMode(BUILTINLED, OUTPUT); #ifdef USE_SPIFFS if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) { Serial.println("SPIFFS Mount Failed"); return; } #else if (!FFat.begin()) { Serial.println("FFat Mount Failed"); if (FORMAT_FFAT_IF_FAILED) FFat.format(); return; } #endif initBLE(); } void loop() { switch (MODE) { case NORMAL_MODE: if (deviceConnected) { digitalWrite(BUILTINLED, HIGH); if (sendMode) { uint8_t fMode[] = {0xAA, FASTMODE}; pCharacteristicTX->setValue(fMode, 2); pCharacteristicTX->notify(); delay(50); sendMode = false; } if (sendSize) { unsigned long x = FLASH.totalBytes(); unsigned long y = FLASH.usedBytes(); uint8_t fSize[] = {0xEF, (uint8_t) (x >> 16), (uint8_t) (x >> 8), (uint8_t) x, (uint8_t) (y >> 16), (uint8_t) (y >> 8), (uint8_t) y}; pCharacteristicTX->setValue(fSize, 7); pCharacteristicTX->notify(); delay(50); sendSize = false; } // your loop code here } else { digitalWrite(BUILTINLED, LOW); } // or here break; case UPDATE_MODE: if (request) { uint8_t rq[] = {0xF1, (cur + 1) / 256, (cur + 1) % 256}; pCharacteristicTX->setValue(rq, 3); pCharacteristicTX->notify(); delay(50); request = false; } if (cur + 1 == parts) { // received complete file uint8_t com[] = {0xF2, (cur + 1) / 256, (cur + 1) % 256}; pCharacteristicTX->setValue(com, 3); pCharacteristicTX->notify(); delay(50); MODE = OTA_MODE; } if (writeFile) { if (!current) { writeBinary(FLASH, "/update.bin", updater, writeLen); } else { writeBinary(FLASH, "/update.bin", updater2, writeLen2); } } break; case OTA_MODE: if (writeFile) { if (!current) { writeBinary(FLASH, "/update.bin", updater, writeLen); } else { writeBinary(FLASH, "/update.bin", updater2, writeLen2); } } if (rParts == tParts) { Serial.println("Complete"); delay(5000); updateFromFS(FLASH); } else { writeFile = true; Serial.println("Incomplete"); Serial.print("Expected: "); Serial.print(tParts); Serial.print("Received: "); Serial.println(rParts); delay(2000); } break; } } ================================================ FILE: esp32_ble_ota_lib_compact/.gitignore ================================================ .pio .vscode/.browse.c_cpp.db* .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch ================================================ FILE: esp32_ble_ota_lib_compact/.vscode/extensions.json ================================================ { // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ "platformio.platformio-ide" ], "unwantedRecommendations": [ "ms-vscode.cpptools-extension-pack" ] } ================================================ FILE: esp32_ble_ota_lib_compact/.vscode/settings.json ================================================ { "cSpell.words": [ "LOGD", "LOGI", "LOGW", "ledc", "LEDC", ] } ================================================ FILE: esp32_ble_ota_lib_compact/README.md ================================================ # Compact version of the BLE OTA example This directory contains a PlatformIO project which implement a compact version of the OTA. It also provides a library (in the lib folder) that allows to easily port BLE OTA to your own code. **Note:** this version of the code is not compatible with the mobile app for the moment (the update mechanism is not exactly the same). Furthermore, to use the FFAT mode you need to use another partition table (which you must configure in `platformio.ini`). ================================================ FILE: esp32_ble_ota_lib_compact/discover.py ================================================ import asyncio from bleak import BleakScanner async def run(): for device in await BleakScanner.discover(): print(f'{device.name} - {device.address}') if __name__ == '__main__': try: asyncio.run(run()) except KeyboardInterrupt: pass ================================================ FILE: esp32_ble_ota_lib_compact/include/README ================================================ This directory is intended for project header files. A header file is a file containing C declarations and macro definitions to be shared between several project source files. You request the use of a header file in your project source file (C, C++, etc) located in `src` folder by including it, with the C preprocessing directive `#include'. ```src/main.c #include "header.h" int main (void) { ... } ``` Including a header file produces the same results as copying the header file into each source file that needs it. Such copying would be time-consuming and error-prone. With a header file, the related declarations appear in only one place. If they need to be changed, they can be changed in one place, and programs that include the header file will automatically use the new version when next recompiled. The header file eliminates the labor of finding and changing all the copies as well as the risk that a failure to find one copy will result in inconsistencies within a program. In C, the usual convention is to give header files names that end with `.h'. It is most portable to use only letters, digits, dashes, and underscores in header file names, and at most one dot. Read more about using header files in official GCC documentation: * Include Syntax * Include Operation * Once-Only Headers * Computed Includes https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html ================================================ FILE: esp32_ble_ota_lib_compact/lib/README ================================================ This directory is intended for project specific (private) libraries. PlatformIO will compile them to static libraries and link into executable file. The source code of each library should be placed in a an own separate directory ("lib/your_library_name/[here are source files]"). For example, see a structure of the following two libraries `Foo` and `Bar`: |--lib | | | |--Bar | | |--docs | | |--examples | | |--src | | |- Bar.c | | |- Bar.h | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html | | | |--Foo | | |- Foo.c | | |- Foo.h | | | |- README --> THIS FILE | |- platformio.ini |--src |- main.c and a contents of `src/main.c`: ``` #include #include int main (void) { ... } ``` PlatformIO Library Dependency Finder will find automatically dependent libraries scanning project source files. More information about PlatformIO Library Dependency Finder - https://docs.platformio.org/page/librarymanager/ldf.html ================================================ FILE: esp32_ble_ota_lib_compact/lib/ble_ota_dfu/keywords.txt ================================================ # To be filled if you want to use it on Arduino #FUNCTIONS COLOR #D35400 - ORANGE KEYWORD1 #FUNCTIONS COLOR #D35400 - ORANGE KEYWORD2 #STRUCTURE COLOR #728E00 - GREEN KEYWORD3 #VARIABLES COLOR #00979C - BLUE LITERAL1 ================================================ FILE: esp32_ble_ota_lib_compact/lib/ble_ota_dfu/library.json ================================================ { "name": "ble_ota_dfuU", "keywords": "BLE OTA DFU", "description": "BLE OTA DFU", "repository": { "type": "email", "url": "vincent.stragier@outlook.com" }, "version": "1.0.0", "exclude": "doc", "frameworks": "arduino", "platforms": ["esp32"] } ================================================ FILE: esp32_ble_ota_lib_compact/lib/ble_ota_dfu/library.properties ================================================ name=ble_ota_dfu version=1.0.0 author=Vincent STRAGIER maintainer=Vincent STRAGIER sentence=BLE OTA DFU paragraph=BLE OTA DFU category=Other url=mailto:vincent.stragier@outlook.com architectures=esp32 ================================================ FILE: esp32_ble_ota_lib_compact/lib/ble_ota_dfu/src/ble_ota_dfu.cpp ================================================ /* Copyright 2022 Vincent Stragier */ #include "ble_ota_dfu.hpp" QueueHandle_t start_update_queue; QueueHandle_t update_uploading_queue; void task_install_update(void *parameters) { FS file_system = FLASH; const char path[] = "/update.bin"; uint8_t data = 0; BLE_OTA_DFU *OTA_DFU_BLE; OTA_DFU_BLE = reinterpret_cast(parameters); delay(100); // Wait for the upload to be completed bool start_update = false; xQueuePeek(start_update_queue, &start_update, portMAX_DELAY); while (!start_update) { delay(500); xQueuePeek(start_update_queue, &start_update, portMAX_DELAY); } ESP_LOGE(TAG, "Starting OTA update"); // Open update.bin file. File update_binary = FLASH.open(path); // If the file cannot be loaded, return. if (!update_binary) { ESP_LOGE(TAG, "Could not load update.bin from spiffs root"); vTaskDelete(NULL); } // Verify that the file is not a directory if (update_binary.isDirectory()) { ESP_LOGE(TAG, "Error, update.bin is not a file"); update_binary.close(); vTaskDelete(NULL); } // Get binary file size size_t update_size = update_binary.size(); // Proceed to the update if the file is not empty if (update_size <= 0) { ESP_LOGE(TAG, "Error, update file is empty"); update_binary.close(); vTaskDelete(NULL); } ESP_LOGI(TAG, "Starting the update"); // perform_update(update_binary, update_size); String result = (String) static_cast(0x0F); // Init update if (Update.begin(update_size)) { // Perform the update size_t written = Update.writeStream(update_binary); ESP_LOGI(TAG, "Written: %d/%d. %s", written, update_size, written == update_size ? "Success!" : "Retry?"); result += "Written : " + String(written) + "/" + String(update_size) + " [" + String((written / update_size) * 100) + " %] \n"; // Check update if (Update.end()) { ESP_LOGI(TAG, "OTA done!"); result += "OTA Done: "; if (Update.isFinished()) { ESP_LOGI(TAG, "Update successfully completed. Rebooting..."); } else { ESP_LOGE(TAG, "Update not finished? Something went wrong!"); } result += Update.isFinished() ? "Success!\n" : "Failed!\n"; } else { ESP_LOGE(TAG, "Error Occurred. Error #: %d", Update.getError()); result += "Error #: " + String(Update.getError()); } } else { ESP_LOGE(TAG, "Not enough space to begin BLE OTA DFU"); result += "Not enough space to begin BLE OTA DFU"; } update_binary.close(); // When finished remove the binary from spiffs // to indicate the end of the process ESP_LOGI(TAG, "Removing update file"); FLASH.remove(path); if (OTA_DFU_BLE->connected()) { // Return the result to the client (tells the client if the update was a // successfull or not) ESP_LOGI(TAG, "Sending result to client"); OTA_DFU_BLE->send_OTA_DFU(result); // OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(result); // OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); ESP_LOGE(TAG, "%s", result.c_str()); ESP_LOGI(TAG, "Result sent to client"); delay(5000); } ESP_LOGE(TAG, "Rebooting ESP32: complete OTA update"); delay(5000); ESP.restart(); // ESP_LOGI(TAG, "Installation is complete"); vTaskDelete(NULL); } // void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t // code) { // Serial.print("Status "); // Serial.print(s); // Serial.print(" on characteristic "); // Serial.print(pCharacteristic->getUUID().toString().c_str()); // Serial.print(" with code "); // Serial.println(code); // } uint16_t BLEOverTheAirDeviceFirmwareUpdate::write_binary(fs::FS *file_system, const char *path, uint8_t *data, uint16_t length, bool keep_open) { static File file; // Append data to the file if (!file_open) { ESP_LOGI(TAG, "Opening binary file %s\r\n", path); file = file_system->open(path, FILE_WRITE); file_open = true; } if (!file) { ESP_LOGE(TAG, "Failed to open the file to write"); return 0; } if (data != nullptr) { ESP_LOGI(TAG, "Write binary file %s\r\n", path); file.write(data, length); } if (keep_open) { return length; } else { file.close(); file_open = false; return 0; } } void BLEOverTheAirDeviceFirmwareUpdate::onNotify( BLECharacteristic *pCharacteristic) { #ifdef DEBUG_BLE_OTA_DFU_TX // uint8_t *pData; std::string value = pCharacteristic->getValue(); uint16_t len = value.length(); // pData = pCharacteristic->getData(); uint8_t *pData = (uint8_t *)value.data(); if (pData != NULL) { ESP_LOGD(TAG, "Notify callback for characteristic %s of data length %d", pCharacteristic->getUUID().toString().c_str(), len); // Print transferred packets Serial.print("TX "); for (uint16_t i = 0; i < len; i++) { Serial.printf("%02X ", pData[i]); } Serial.println(); } #endif } void BLEOverTheAirDeviceFirmwareUpdate::onWrite( BLECharacteristic *pCharacteristic) { // uint8_t *pData; std::string value = pCharacteristic->getValue(); uint16_t len = value.length(); // pData = pCharacteristic->getData(); uint8_t *pData = (uint8_t *)value.data(); // Check that data have been received if (pData != NULL) { // #define DEBUG_BLE_OTA_DFU_RX #ifdef DEBUG_BLE_OTA_DFU_RX ESP_LOGD(TAG, "Write callback for characteristic %s of data length %d", pCharacteristic->getUUID().toString().c_str(), len); Serial.print("RX "); for (uint16_t index = 0; index < len; index++) { Serial.printf("%02X ", pData[i]); } Serial.println(); #endif switch (pData[0]) { // Send total and used sizes case 0xEF: { FLASH.format(); // Send flash size uint16_t total_size = FLASH.totalBytes(); uint16_t used_size = FLASH.usedBytes(); uint8_t flash_size[] = {0xEF, static_cast(total_size >> 16), static_cast(total_size >> 8), static_cast(total_size), static_cast(used_size >> 16), static_cast(used_size >> 8), static_cast(used_size)}; OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(flash_size, 7); OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); delay(10); } break; // Write parts to RAM case 0xFB: { // pData[1] is the position of the next part for (uint16_t index = 0; index < len - 2; index++) { updater[!selected_updater][(pData[1] * MTU) + index] = pData[index + 2]; } } break; // Write updater content to the flash case 0xFC: { selected_updater = !selected_updater; write_len[selected_updater] = (pData[1] * 256) + pData[2]; current_progression = (pData[3] * 256) + pData[4]; received_file_size += write_binary(&FLASH, "/update.bin", updater[selected_updater], write_len[selected_updater]); if ((current_progression < parts - 1) && !FASTMODE) { uint8_t progression[] = {0xF1, (uint8_t)((current_progression + 1) / 256), (uint8_t)((current_progression + 1) % 256)}; OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(progression, 3); OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); delay(10); } ESP_LOGI(TAG, "Upload progress: %d/%d", current_progression + 1, parts); if (current_progression + 1 == parts) { // If all the file has been received, send the progression uint8_t progression[] = {0xF2, (uint8_t)((current_progression + 1) / 256), (uint8_t)((current_progression + 1) % 256)}; OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(progression, 3); OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); delay(10); if (received_file_size != expected_file_size) { // received_file_size += (pData[1] * 256) + pData[2]; received_file_size += write_binary(&FLASH, "/update.bin", updater[selected_updater], write_len[selected_updater]); if (received_file_size > expected_file_size) { ESP_LOGW(TAG, "Unexpected size:\n Expected: %d\nReceived: %d", expected_file_size, received_file_size); } } else { ESP_LOGI(TAG, "Installing update"); // Start the installation write_binary(&FLASH, "/update.bin", nullptr, 0, false); bool start_update = true; xQueueOverwrite(start_update_queue, &start_update); } } } break; // Remove previous file and send transfer mode case 0xFD: { // Remove previous (failed?) update if (FLASH.exists("/update.bin")) { ESP_LOGI(TAG, "Removing previous update"); FLASH.remove("/update.bin"); } // Send mode ("fast" or "slow") uint8_t mode[] = {0xAA, FASTMODE}; OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(mode, 2); OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); delay(10); } break; // Keep track of the received file and of the expected file sizes case 0xFE: received_file_size = 0; expected_file_size = (pData[1] * 16777216) + (pData[2] * 65536) + (pData[3] * 256) + pData[4]; ESP_LOGI(TAG, "Available space: %d\nFile Size: %d\n", FLASH.totalBytes() - FLASH.usedBytes(), expected_file_size); break; // Switch to update mode case 0xFF: parts = (pData[1] * 256) + pData[2]; MTU = (pData[3] * 256) + pData[4]; break; default: ESP_LOGW(TAG, "Unknown command: %02X", pData[0]); break; } // ESP_LOGE(TAG, "Not stuck in loop"); } delay(1); } bool BLE_OTA_DFU::configure_OTA(NimBLEServer *pServer) { // Init FLASH #ifdef USE_SPIFFS if (!SPIFFS.begin(FORMAT_FLASH_IF_MOUNT_FAILED)) { ESP_LOGE(TAG, "SPIFFS Mount Failed"); return false; } ESP_LOGI(TAG, "SPIFFS Mounted"); #else if (!FFat.begin()) { ESP_LOGE(TAG, "FFat Mount Failed"); if (FORMAT_FLASH_IF_MOUNT_FAILED) FFat.format(); return false; } ESP_LOGI(TAG, "FFat Mounted"); #endif // Get the pointer to the pServer this->pServer = pServer; // Create the BLE OTA DFU Service pServiceOTA = pServer->createService(SERVICE_OTA_BLE_UUID); if (pServiceOTA == nullptr) { return false; } BLECharacteristic *pCharacteristic_BLE_OTA_DFU_RX = pServiceOTA->createCharacteristic(CHARACTERISTIC_OTA_BL_UUID_RX, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR); if (pCharacteristic_BLE_OTA_DFU_RX == nullptr) { return false; } auto *bleOTACharacteristicCallbacksRX = new BLEOverTheAirDeviceFirmwareUpdate(); bleOTACharacteristicCallbacksRX->OTA_DFU_BLE = this; pCharacteristic_BLE_OTA_DFU_RX->setCallbacks(bleOTACharacteristicCallbacksRX); pCharacteristic_BLE_OTA_DFU_TX = pServiceOTA->createCharacteristic( CHARACTERISTIC_OTA_BL_UUID_TX, NIMBLE_PROPERTY::NOTIFY); if (pCharacteristic_BLE_OTA_DFU_TX == nullptr) { return false; } // Start the BLE UART service pServiceOTA->start(); return true; } void BLE_OTA_DFU::start_OTA() { bool state = false; initialize_queue(&start_update_queue, bool, &state, 1); initialize_queue(&update_uploading_queue, bool, &state, 1); // initialize_empty_queue(&update_queue, uint8_t, UPDATE_QUEUE_SIZE); ESP_LOGI(TAG, "Available heap memory: %d", ESP.getFreeHeap()); // ESP_LOGE(TAG, "Available stack memory: %d", ESP_LOGI(TAG, "Available free space: %d", FLASH.totalBytes() - FLASH.usedBytes()); // ESP_LOGE(TAG, "Available free blocks: %d", FLASH.blockCount()); TaskHandle_t taskInstallUpdate = NULL; xTaskCreatePinnedToCoreAndAssert(task_install_update, "task_install_update", 5120, static_cast(this), 5, &taskInstallUpdate, 1); ESP_LOGI(TAG, "Available memory (after task started): %d", ESP.getFreeHeap()); } bool BLE_OTA_DFU::begin(String local_name) { // Create the BLE Device BLEDevice::init(local_name.c_str()); ESP_LOGI(TAG, "Starting BLE UART services"); // Create the BLE Server pServer = BLEDevice::createServer(); if (pServer == nullptr) { return false; } this->configure_OTA(pServer); // Start advertising pServer->getAdvertising()->addServiceUUID(pServiceOTA->getUUID()); pServer->getAdvertising()->start(); this->start_OTA(); return true; } bool BLE_OTA_DFU::connected() { // True if connected return pServer->getConnectedCount() > 0; } void BLE_OTA_DFU::send_OTA_DFU(uint8_t value) { uint8_t _value = value; this->pCharacteristic_BLE_OTA_DFU_TX->setValue(&_value, 1); this->pCharacteristic_BLE_OTA_DFU_TX->notify(); } void BLE_OTA_DFU::send_OTA_DFU(uint8_t *value, size_t size) { this->pCharacteristic_BLE_OTA_DFU_TX->setValue(value, size); this->pCharacteristic_BLE_OTA_DFU_TX->notify(); } void BLE_OTA_DFU::send_OTA_DFU(String value) { this->pCharacteristic_BLE_OTA_DFU_TX->setValue(value.c_str()); this->pCharacteristic_BLE_OTA_DFU_TX->notify(); } ================================================ FILE: esp32_ble_ota_lib_compact/lib/ble_ota_dfu/src/ble_ota_dfu.hpp ================================================ /* Copyright 2022 Vincent Stragier */ // Highly inspired by https://github.com/fbiego/ESP32_BLE_OTA_Arduino #pragma once #ifndef SRC_BLE_OTA_DFU_HPP_ #define SRC_BLE_OTA_DFU_HPP_ #include "./freertos_utils.hpp" #include #include #include #include #include // comment to use FFat #define USE_SPIFFS #ifdef USE_SPIFFS // SPIFFS write is slower #include #define FLASH SPIFFS const bool FASTMODE = false; #else // FFat is faster #include #define FLASH FFat const bool FASTMODE = true; #endif // #endif const char SERVICE_OTA_BLE_UUID[] = "fe590001-54ae-4a28-9f74-dfccb248601d"; const char CHARACTERISTIC_OTA_BL_UUID_RX[] = "fe590002-54ae-4a28-9f74-dfccb248601d"; const char CHARACTERISTIC_OTA_BL_UUID_TX[] = "fe590003-54ae-4a28-9f74-dfccb248601d"; const bool FORMAT_FLASH_IF_MOUNT_FAILED = true; const uint32_t UPDATER_SIZE = 20000; /* Dummy class */ class BLE_OTA_DFU; class BLEOverTheAirDeviceFirmwareUpdate : public BLECharacteristicCallbacks { private: bool selected_updater = true; bool file_open = false; uint8_t updater[2][UPDATER_SIZE]; uint16_t write_len[2] = {0, 0}; uint16_t parts = 0, MTU = 0; uint16_t current_progression = 0; uint32_t received_file_size, expected_file_size; public: friend class BLE_OTA_DFU; BLE_OTA_DFU *OTA_DFU_BLE; uint16_t write_binary(fs::FS *file_system, const char *path, uint8_t *data, uint16_t length, bool keep_open = true); void onNotify(BLECharacteristic *pCharacteristic); void onWrite(BLECharacteristic *pCharacteristic); }; class BLE_OTA_DFU { private: BLEServer *pServer = nullptr; BLEService *pServiceOTA = nullptr; BLECharacteristic *pCharacteristic_BLE_OTA_DFU_TX = nullptr; friend class BLEOverTheAirDeviceFirmwareUpdate; public: BLE_OTA_DFU() = default; ~BLE_OTA_DFU() = default; bool configure_OTA(NimBLEServer *pServer); void start_OTA(); bool begin(String local_name); bool connected(); void send_OTA_DFU(uint8_t value); void send_OTA_DFU(uint8_t *value, size_t size); void send_OTA_DFU(String value); friend void task_install_update(void *parameters); }; #endif /* SRC_BLE_OTA_DFU_HPP_ */ ================================================ FILE: esp32_ble_ota_lib_compact/lib/ble_ota_dfu/src/freertos_utils.cpp ================================================ /* Copyright 2022 Vincent Stragier */ #include "freertos_utils.hpp" BaseType_t xTaskCreatePinnedToCoreAndAssert( TaskFunction_t pvTaskCode, const char *pcName, uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pvCreatedTask, BaseType_t xCoreID) { BaseType_t xReturn = xTaskCreatePinnedToCore(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pvCreatedTask, xCoreID); configASSERT(pvCreatedTask); return xReturn; } ================================================ FILE: esp32_ble_ota_lib_compact/lib/ble_ota_dfu/src/freertos_utils.hpp ================================================ /* Copyright 2022 Vincent Stragier */ #pragma once #ifndef SRC_FREERTOS_UTILS_HPP_ #define SRC_FREERTOS_UTILS_HPP_ #include // Initialize the queue #define initialize_queue(queue, T, initial_value, size) \ _initialize_queue((queue), (#queue), (size), (initial_value)) #define initialize_empty_queue(queue, T, size) \ _initialize_queue((queue), (#queue), (size)) template void _initialize_queue(QueueHandle_t *queue, const char *queue_name, size_t size, T *initial_value = nullptr) { *queue = xQueueCreate(size, sizeof(T)); ESP_LOGI(TAG, "Creating the queue \"%s\"", queue_name); // Init start update queue if (*queue == NULL || *queue == nullptr) { ESP_LOGE(TAG, "Error creating the queue \"%s\"", queue_name); } if (initial_value == nullptr || initial_value == NULL) { return; } if (!xQueueSend(*queue, initial_value, portMAX_DELAY)) { ESP_LOGE(TAG, "Error initializing the queue \"%s\"", queue_name); } } // Create a task and assert it's creation BaseType_t xTaskCreatePinnedToCoreAndAssert( TaskFunction_t pvTaskCode, const char *pcName, uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pvCreatedTask, BaseType_t xCoreID); #endif /* SRC_FREERTOS_UTILS_HPP_ */ ================================================ FILE: esp32_ble_ota_lib_compact/ota_updater.py ================================================ from __future__ import print_function import os import asyncio import sys import re from time import sleep from bleak import BleakClient, BleakScanner # from bleak.exc import BleakError header = """##################################################################### ------------------------BLE OTA update--------------------- Arduino code @ https://github.com/fbiego/ESP32_BLE_OTA_Arduino #####################################################################""" UART_SERVICE_UUID = "fe590001-54ae-4a28-9f74-dfccb248601d" UART_RX_CHAR_UUID = "fe590002-54ae-4a28-9f74-dfccb248601d" UART_TX_CHAR_UUID = "fe590003-54ae-4a28-9f74-dfccb248601d" PART = 19000 MTU = 250 ble_ota_dfu_end = False async def start_ota(ble_address: str, filename: str): device = await BleakScanner.find_device_by_address(ble_address, timeout=20.0) disconnected_event = asyncio.Event() total = 0 file_content = None client = None def handle_disconnect(_: BleakClient): print("Device disconnected !") disconnected_event.set() sleep(1) # sys.exit(0) async def handle_rx(_: int, data: bytearray): # print(f'\nReceived: {data = }\n') match data[0]: case 0xAA: print("Starting transfer, mode:", data[1]) print_progress_bar(0, total, prefix='Upload progress:', suffix='Complete', length=50) match data[1]: case 0: # Slow mode # Send first part await send_part(0, file_content, client) case 1: # Fast mode for index in range(file_parts): await send_part(index, file_content, client) print_progress_bar(index + 1, total, prefix='Upload progress:', suffix='Complete', length=50) case 0xF1: # Send next part and update progress bar next_part_to_send = int.from_bytes( data[2:3], byteorder='little') # print("Next part:", next_part_to_send, "\n") await send_part(next_part_to_send, file_content, client) print_progress_bar(next_part_to_send + 1, total, prefix='Upload progress:', suffix='Complete', length=50) case 0xF2: # Install firmware # ins = 'Installing firmware' # print("Installing firmware") pass case 0x0F: print("OTA result: ", str(data[1:], 'utf-8')) global ble_ota_dfu_end ble_ota_dfu_end = True def print_progress_bar(iteration: int, total: int, prefix: str = '', suffix: str = '', decimals: int = 1, length: int = 100, filler: str = '█', print_end: str = "\r"): """ Call in a loop to create terminal progress bar @params: iteration - Required : current iteration (Int) total - Required : total iterations (Int) prefix - Optional : prefix string (Str) suffix - Optional : suffix string (Str) decimals - Optional : positive number of decimals in percent complete (Int) length - Optional : character length of bar (Int) filler - Optional : bar fill character (Str) print_end - Optional : end character (e.g. "\r", "\r\n") (Str) """ percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total))) filled_length = (length * iteration) // total bar = filler * filled_length + '-' * (length - filled_length) print(f'\r{prefix} |{bar}| {percent} % {suffix}', end=print_end) # Print new line upon complete if iteration == total: print() async def send_part(position: int, data: bytearray, client: BleakClient): start = position * PART end = (position + 1) * PART # print(locals()) if len(data) < end: end = len(data) data_length = end - start parts = data_length // MTU for part_index in range(parts): to_be_sent = bytearray([0xFB, part_index]) for mtu_index in range(MTU): to_be_sent.append( data[(position*PART)+(MTU * part_index) + mtu_index]) await send_data(client, to_be_sent) if data_length % MTU: remaining = data_length % MTU to_be_sent = bytearray([0xFB, parts]) for index in range(remaining): to_be_sent.append( data[(position*PART)+(MTU * parts) + index]) await send_data(client, to_be_sent) await send_data(client, bytearray([0xFC, data_length//256, data_length % 256, position//256, position % 256]), True) async def send_data(client: BleakClient, data: bytearray, response: bool = False): # print(f'{locals()["data"]}') await client.write_gatt_char(UART_RX_CHAR_UUID, data, response) if not device: print("-----------Failed--------------") print(f"Device with address {ble_address} could not be found.") return #raise BleakError(f"A device with address {ble_address} could not be found.") async with BleakClient(device, disconnected_callback=handle_disconnect) as local_client: client = local_client # Load file print("Reading from: ", filename) file_content = open(filename, "rb").read() file_parts = -(len(file_content) // -PART) total = file_parts file_length = len(file_content) print(f'File size: {len(file_content)}') # Set the UUID of the service you want to connect to and the callback await client.start_notify(UART_TX_CHAR_UUID, handle_rx) await asyncio.sleep(1.0) # Send file length await send_data(client, bytearray([0xFE, file_length >> 24 & 0xFF, file_length >> 16 & 0xFF, file_length >> 8 & 0xFF, file_length & 0xFF])) # Send number of parts and MTU value await send_data(client, bytearray([0xFF, file_parts//256, file_parts % 256, MTU // 256, MTU % 256])) # Remove previous update and receive transfer mode (start the update) await send_data(client, bytearray([0xFD])) # Wait til the update is complete while not ble_ota_dfu_end: await asyncio.sleep(1.0) print("Waiting for disconnect... ", end="") await disconnected_event.wait() print("-----------Complete--------------") def is_valid_address(value: str = None) -> bool: # Regex to check valid MAC address regex_0 = (r"^([0-9A-Fa-f]{2}[:-])" r"{5}([0-9A-Fa-f]{2})|" r"([0-9a-fA-F]{4}\\." r"[0-9a-fA-F]{4}\\." r"[0-9a-fA-F]{4}){17}$") regex_1 = (r"^[{]?[0-9a-fA-F]{8}" r"-([0-9a-fA-F]{4}-)" r"{3}[0-9a-fA-F]{12}[}]?$") # Compile the ReGex regex_0 = re.compile(regex_0) regex_1 = re.compile(regex_1) # If the string is empty return false if value is None: return False # Return if the string matched the ReGex if re.search(regex_0, value) and len(value) == 17: return True return re.search(regex_1, value) and len(value) == 36 if __name__ == "__main__": print(header) # Check if the user has entered enough arguments # sys.argv.append("C8:C9:A3:D2:60:8E") # sys.argv.append("firmware.bin") if len(sys.argv) < 3: print("Specify the device address and firmware file") import sys import os filename = os.path.join(os.path.dirname( __file__), '.pio', 'build', 'esp32doit-devkit-v1', 'firmware.bin') filename = filename if os.path.exists(filename) else "firmware.bin" print( f"$ {sys.executable} \"{__file__}\" \"C8:C9:A3:D2:60:8E\" \"{filename}\"") exit(1) print("Trying to start OTA update") ble_address = sys.argv[1] filename = sys.argv[2] # Check if the address is valid if not is_valid_address(ble_address): print(f"Invalid Address: {ble_address}") exit(2) # Check if the file exists if not os.path.exists(filename): print(f"File not found: {filename}") exit(3) try: # Start the OTA update asyncio.run(start_ota(ble_address, filename)) except KeyboardInterrupt: print("\nExiting...") exit(0) except OSError: print("\nExiting (OSError)...") exit(1) except Exception: import traceback traceback.print_exc() exit(2) ================================================ FILE: esp32_ble_ota_lib_compact/platformio.ini ================================================ ; PlatformIO Project Configuration File ; ; Build options: build flags, source filter ; Upload options: custom upload port, speed and extra flags ; Library options: dependencies, extra library storages ; Advanced options: extra scripting ; ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html [env:esp32doit-devkit-v1] platform = espressif32 board = esp32dev framework = arduino ; ESP_LOGE - error (lowest) = 1 ; ESP_LOGW - warning = 2 ; ESP_LOGI - info = 3 ; ESP_LOGD - debug = 4 ; ESP_LOGV - verbose (highest) = 5 ; build_flags = -DCORE_DEBUG_LEVEL=3 ; build_type = debug ; board_build.partitions = default_ffat.csv board_build.partitions = default.csv ; Serial Monitor options ; https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html monitor_speed = 115200 monitor_rts = 0 monitor_dtr = 0 monitor_filters = ; time ; send_on_enter esp32_exception_decoder ; hexlify ; colorize ; Libraries lib_deps = https://github.com/h2zero/NimBLE-Arduino.git ; Run before compilation ; extra_scripts = ; pre:some_script.py ; Configure checking tool ;, pvs-studio, check_tool = cppcheck, clangtidy check_flags = cppcheck: --addon=cert.py check_skip_packages = yes ================================================ FILE: esp32_ble_ota_lib_compact/src/main.cpp ================================================ /* Copyright 2022 Vincent Stragier */ #include "main.hpp" /////////////////// /// Setup /// /////////////////// void setup() { ota_dfu_ble.begin("Test OTA DFU"); } ////////////////// /// Loop /// ////////////////// void loop() { // Kill the holly loop() // Delete the task, comment if you want to keep the loop() vTaskDelete(NULL); } /////////////////////// /// Functions /// /////////////////////// ================================================ FILE: esp32_ble_ota_lib_compact/src/main.hpp ================================================ /* Copyright 2022 Vincent Stragier */ #ifndef SRC_MAIN_HPP_ #define SRC_MAIN_HPP_ /////////////////////// /// Libraries /// /////////////////////// #include #include /////////////////////// /// Variables /// /////////////////////// BLE_OTA_DFU ota_dfu_ble; #endif /* SRC_MAIN_HPP_ */ ================================================ FILE: esp32_ble_ota_lib_compact/test/README ================================================ This directory is intended for PlatformIO Unit Testing and project tests. Unit Testing is a software testing method by which individual units of source code, sets of one or more MCU program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use. Unit testing finds problems early in the development cycle. More information about PlatformIO Unit Testing: - https://docs.platformio.org/page/plus/unit-testing.html ================================================ FILE: esp32_nim_ble_ota/esp32_nim_ble_ota.ino ================================================ /* MIT License Copyright (c) 2021 Felix Biego Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "FS.h" #include "FFat.h" #include "SPIFFS.h" #include //#include // For OTA with SD Card #define BUILTINLED 2 #define FORMAT_SPIFFS_IF_FAILED true #define FORMAT_FFAT_IF_FAILED true #define USE_SPIFFS //comment to use FFat #ifdef USE_SPIFFS #define FLASH SPIFFS #define FASTMODE false //SPIFFS write is slow #else #define FLASH FFat #define FASTMODE true //FFat is faster #endif #define NORMAL_MODE 0 // normal #define UPDATE_MODE 1 // receiving firmware #define OTA_MODE 2 // installing firmware uint8_t updater[16384]; uint8_t updater2[16384]; #define SERVICE_UUID "fb1e4001-54ae-4a28-9f74-dfccb248601d" #define CHARACTERISTIC_UUID_RX "fb1e4002-54ae-4a28-9f74-dfccb248601d" #define CHARACTERISTIC_UUID_TX "fb1e4003-54ae-4a28-9f74-dfccb248601d" static BLECharacteristic* pCharacteristicTX; static BLECharacteristic* pCharacteristicRX; static bool deviceConnected = false, sendMode = false, sendSize = true; static bool writeFile = false, request = false; static int writeLen = 0, writeLen2 = 0; static bool current = true; static int parts = 0, next = 0, cur = 0, MTU = 0; static int MODE = NORMAL_MODE; unsigned long rParts, tParts; static void rebootEspWithReason(String reason) { Serial.println(reason); delay(1000); ESP.restart(); } class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { Serial.println("Connected"); deviceConnected = true; } void onDisconnect(BLEServer* pServer) { Serial.println("disconnected"); deviceConnected = false; } }; class MyCallbacks: public BLECharacteristicCallbacks { // void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code) { // Serial.print("Status "); // Serial.print(s); // Serial.print(" on characteristic "); // Serial.print(pCharacteristic->getUUID().toString().c_str()); // Serial.print(" with code "); // Serial.println(code); // } void onRead(BLECharacteristic* pCharacteristic) { Serial.print(pCharacteristic->getUUID().toString().c_str()); Serial.print(": onRead(), value: "); Serial.println(pCharacteristic->getValue().c_str()); }; void onNotify(BLECharacteristic *pCharacteristic) { //uint8_t* pData; std::string pData = pCharacteristic->getValue(); int len = pData.length(); // Serial.print("Notify callback for characteristic "); // Serial.print(pCharacteristic->getUUID().toString().c_str()); // Serial.print(" of data length "); // Serial.println(len); Serial.print("TX "); for (int i = 0; i < len; i++) { Serial.printf("%02X ", pData[i]); } Serial.println(); } void onWrite(BLECharacteristic *pCharacteristic) { //uint8_t* pData; std::string pData = pCharacteristic->getValue(); int len = pData.length(); // Serial.print("Write callback for characteristic "); // Serial.print(pCharacteristic->getUUID().toString().c_str()); // Serial.print(" of data length "); // Serial.println(len); // Serial.print("RX "); // for (int i = 0; i < len; i++) { // leave this commented // Serial.printf("%02X ", pData[i]); // } // Serial.println(); if (pData[0] == 0xFB) { int pos = pData[1]; for (int x = 0; x < len - 2; x++) { if (current) { updater[(pos * MTU) + x] = pData[x + 2]; } else { updater2[(pos * MTU) + x] = pData[x + 2]; } } } else if (pData[0] == 0xFC) { if (current) { writeLen = (pData[1] * 256) + pData[2]; } else { writeLen2 = (pData[1] * 256) + pData[2]; } current = !current; cur = (pData[3] * 256) + pData[4]; writeFile = true; if (cur < parts - 1) { request = !FASTMODE; } } else if (pData[0] == 0xFD) { sendMode = true; if (FLASH.exists("/update.bin")) { FLASH.remove("/update.bin"); } } else if (pData[0] == 0xFE) { rParts = 0; tParts = (pData[1] * 256 * 256 * 256) + (pData[2] * 256 * 256) + (pData[3] * 256) + pData[4]; Serial.print("Available space: "); Serial.println(FLASH.totalBytes() - FLASH.usedBytes()); Serial.print("File Size: "); Serial.println(tParts); } else if (pData[0] == 0xFF) { parts = (pData[1] * 256) + pData[2]; MTU = (pData[3] * 256) + pData[4]; MODE = UPDATE_MODE; } else if (pData[0] == 0xEF) { FLASH.format(); sendSize = true; } } }; static void writeBinary(fs::FS &fs, const char * path, uint8_t *dat, int len) { //Serial.printf("Write binary file %s\r\n", path); File file = fs.open(path, FILE_APPEND); if (!file) { Serial.println("- failed to open file for writing"); return; } file.write(dat, len); file.close(); writeFile = false; rParts += len; } void sendOtaResult(String result) { byte arr[result.length()]; result.getBytes(arr, result.length()); pCharacteristicTX->setValue(arr, result.length()); pCharacteristicTX->notify(); delay(200); } void performUpdate(Stream &updateSource, size_t updateSize) { char s1 = 0x0F; String result = String(s1); if (Update.begin(updateSize)) { size_t written = Update.writeStream(updateSource); if (written == updateSize) { Serial.println("Written : " + String(written) + " successfully"); } else { Serial.println("Written only : " + String(written) + "/" + String(updateSize) + ". Retry?"); } result += "Written : " + String(written) + "/" + String(updateSize) + " [" + String((written / updateSize) * 100) + "%] \n"; if (Update.end()) { Serial.println("OTA done!"); result += "OTA Done: "; if (Update.isFinished()) { Serial.println("Update successfully completed. Rebooting..."); result += "Success!\n"; } else { Serial.println("Update not finished? Something went wrong!"); result += "Failed!\n"; } } else { Serial.println("Error Occurred. Error #: " + String(Update.getError())); result += "Error #: " + String(Update.getError()); } } else { Serial.println("Not enough space to begin OTA"); result += "Not enough space for OTA"; } if (deviceConnected) { sendOtaResult(result); delay(5000); } } void updateFromFS(fs::FS &fs) { File updateBin = fs.open("/update.bin"); if (updateBin) { if (updateBin.isDirectory()) { Serial.println("Error, update.bin is not a file"); updateBin.close(); return; } size_t updateSize = updateBin.size(); if (updateSize > 0) { Serial.println("Trying to start update"); performUpdate(updateBin, updateSize); } else { Serial.println("Error, file is empty"); } updateBin.close(); // when finished remove the binary from spiffs to indicate end of the process Serial.println("Removing update file"); fs.remove("/update.bin"); rebootEspWithReason("Rebooting to complete OTA update"); } else { Serial.println("Could not load update.bin from spiffs root"); } } void initBLE() { BLEDevice::init("NimBLE OTA"); BLEDevice::setMTU(517); BLEDevice::setPower(ESP_PWR_LVL_P9); BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); pCharacteristicTX = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ ); pCharacteristicRX = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE_NR); pCharacteristicRX->setCallbacks(new MyCallbacks()); pCharacteristicTX->setCallbacks(new MyCallbacks()); pService->start(); pServer->start(); // BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility NimBLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue pAdvertising->setMinPreferred(0x12); pAdvertising->start(); //BLEDevice::startAdvertising(); Serial.println("Characteristic defined! Now you can read it in your phone!"); } void setup() { Serial.begin(115200); Serial.println("Starting BLE OTA sketch"); pinMode(BUILTINLED, OUTPUT); //SPI.begin(18, 22, 23, 5); // For OTA with SD Card //SD.begin(5); // For OTA with SD Card #ifdef USE_SPIFFS if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) { Serial.println("SPIFFS Mount Failed"); return; } #else if (!FFat.begin()) { Serial.println("FFat Mount Failed"); if (FORMAT_FFAT_IF_FAILED) FFat.format(); return; } #endif initBLE(); } void loop() { switch (MODE) { case NORMAL_MODE: if (deviceConnected) { digitalWrite(BUILTINLED, HIGH); if (sendMode) { uint8_t fMode[] = {0xAA, FASTMODE}; pCharacteristicTX->setValue(fMode, 2); pCharacteristicTX->notify(); delay(50); sendMode = false; } if (sendSize) { unsigned long x = FLASH.totalBytes(); unsigned long y = FLASH.usedBytes(); uint8_t fSize[] = {0xEF, (uint8_t) (x >> 16), (uint8_t) (x >> 8), (uint8_t) x, (uint8_t) (y >> 16), (uint8_t) (y >> 8), (uint8_t) y}; pCharacteristicTX->setValue(fSize, 7); pCharacteristicTX->notify(); delay(50); sendSize = false; } // your loop code here } else { digitalWrite(BUILTINLED, LOW); } // or here break; case UPDATE_MODE: if (request) { uint8_t rq[] = {0xF1, (cur + 1) / 256, (cur + 1) % 256}; pCharacteristicTX->setValue(rq, 3); pCharacteristicTX->notify(); delay(50); request = false; } if (writeFile) { if (!current) { writeBinary(FLASH, "/update.bin", updater, writeLen); } else { writeBinary(FLASH, "/update.bin", updater2, writeLen2); } writeFile = false; } if (cur + 1 == parts) { // received complete file uint8_t com[] = {0xF2, (cur + 1) / 256, (cur + 1) % 256}; pCharacteristicTX->setValue(com, 3); pCharacteristicTX->notify(); delay(50); MODE = OTA_MODE; } break; case OTA_MODE: if (writeFile) { if (!current) { writeBinary(FLASH, "/update.bin", updater, writeLen); } else { writeBinary(FLASH, "/update.bin", updater2, writeLen2); } } if (rParts == tParts) { Serial.println("Complete"); delay(5000); updateFromFS(FLASH); } else { writeFile = true; Serial.println("Incomplete"); Serial.print("Expected: "); Serial.print(tParts); Serial.print("Received: "); Serial.println(rParts); delay(2000); } break; } }