├── LICENSE ├── README.md ├── esp32_ble_ota ├── esp32_ble_ota.ino └── esp32_ble_ota.ino.doitESP32devkitV1.bin ├── 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 └── esp32_nim_ble_ota.ino.doitESP32devkitV1.bin /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Felix Biego 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32_BLE_OTA_Arduino 2 | OTA update on ESP32 via BLE 3 | 4 | - 1,038,544 bytes uploaded in 1min 25sec 5 | - Speed: ~ `12kb/s peak` 6 | 7 | [`esp32_ble_ota`](https://github.com/fbiego/ESP32_BLE_OTA_Arduino/tree/main/esp32_ble_ota) - 1,008,199 bytes 8 | 9 | [`esp32_nim_ble_ota`](https://github.com/fbiego/ESP32_BLE_OTA_Arduino/tree/main/esp32_nim_ble_ota) - 563,051 bytes 10 | 11 | 12 | ## Android app 13 | 14 | Get it on Google Play 15 | 16 | ## Python Script 17 | Update from your computer 18 | 19 | [`BLE_OTA_Python`](https://github.com/fbiego/BLE_OTA_Python) 20 | 21 | ## Video 22 | [`DIY ESP32 clock with BLE OTA`](https://youtu.be/TU_O4UPm00A) - 12kb/s peak (optimized) 23 | 24 | [`ESP32 OTA via BLE`](https://youtu.be/j4ELTS7QXFM) - 3kb/s (old) 25 | -------------------------------------------------------------------------------- /esp32_ble_ota/esp32_ble_ota.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 Felix Biego 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | #include 27 | #include "FS.h" 28 | #include "FFat.h" 29 | #include "SPIFFS.h" 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #define BUILTINLED 2 36 | #define FORMAT_SPIFFS_IF_FAILED true 37 | #define FORMAT_FFAT_IF_FAILED true 38 | 39 | #define USE_SPIFFS //comment to use FFat 40 | 41 | #ifdef USE_SPIFFS 42 | #define FLASH SPIFFS 43 | #define FASTMODE false //SPIFFS write is slow 44 | #else 45 | #define FLASH FFat 46 | #define FASTMODE true //FFat is faster 47 | #endif 48 | 49 | #define NORMAL_MODE 0 // normal 50 | #define UPDATE_MODE 1 // receiving firmware 51 | #define OTA_MODE 2 // installing firmware 52 | 53 | uint8_t updater[16384]; 54 | uint8_t updater2[16384]; 55 | 56 | #define SERVICE_UUID "fb1e4001-54ae-4a28-9f74-dfccb248601d" 57 | #define CHARACTERISTIC_UUID_RX "fb1e4002-54ae-4a28-9f74-dfccb248601d" 58 | #define CHARACTERISTIC_UUID_TX "fb1e4003-54ae-4a28-9f74-dfccb248601d" 59 | 60 | static BLECharacteristic* pCharacteristicTX; 61 | static BLECharacteristic* pCharacteristicRX; 62 | 63 | static bool deviceConnected = false, sendMode = false, sendSize = true; 64 | static bool writeFile = false, request = false; 65 | static int writeLen = 0, writeLen2 = 0; 66 | static bool current = true; 67 | static int parts = 0, next = 0, cur = 0, MTU = 0; 68 | static int MODE = NORMAL_MODE; 69 | unsigned long rParts, tParts; 70 | 71 | static void rebootEspWithReason(String reason) { 72 | Serial.println(reason); 73 | delay(1000); 74 | ESP.restart(); 75 | } 76 | 77 | class MyServerCallbacks: public BLEServerCallbacks { 78 | void onConnect(BLEServer* pServer) { 79 | deviceConnected = true; 80 | 81 | } 82 | void onDisconnect(BLEServer* pServer) { 83 | deviceConnected = false; 84 | } 85 | }; 86 | 87 | class MyCallbacks: public BLECharacteristicCallbacks { 88 | 89 | // void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code) { 90 | // Serial.print("Status "); 91 | // Serial.print(s); 92 | // Serial.print(" on characteristic "); 93 | // Serial.print(pCharacteristic->getUUID().toString().c_str()); 94 | // Serial.print(" with code "); 95 | // Serial.println(code); 96 | // } 97 | 98 | void onNotify(BLECharacteristic *pCharacteristic) { 99 | uint8_t* pData; 100 | std::string value = pCharacteristic->getValue(); 101 | int len = value.length(); 102 | pData = pCharacteristic->getData(); 103 | if (pData != NULL) { 104 | // Serial.print("Notify callback for characteristic "); 105 | // Serial.print(pCharacteristic->getUUID().toString().c_str()); 106 | // Serial.print(" of data length "); 107 | // Serial.println(len); 108 | Serial.print("TX "); 109 | for (int i = 0; i < len; i++) { 110 | Serial.printf("%02X ", pData[i]); 111 | } 112 | Serial.println(); 113 | } 114 | } 115 | 116 | void onWrite(BLECharacteristic *pCharacteristic) { 117 | uint8_t* pData; 118 | std::string value = pCharacteristic->getValue(); 119 | int len = value.length(); 120 | pData = pCharacteristic->getData(); 121 | if (pData != NULL) { 122 | // Serial.print("Write callback for characteristic "); 123 | // Serial.print(pCharacteristic->getUUID().toString().c_str()); 124 | // Serial.print(" of data length "); 125 | // Serial.println(len); 126 | // Serial.print("RX "); 127 | // for (int i = 0; i < len; i++) { // leave this commented 128 | // Serial.printf("%02X ", pData[i]); 129 | // } 130 | // Serial.println(); 131 | 132 | if (pData[0] == 0xFB) { 133 | int pos = pData[1]; 134 | for (int x = 0; x < len - 2; x++) { 135 | if (current) { 136 | updater[(pos * MTU) + x] = pData[x + 2]; 137 | } else { 138 | updater2[(pos * MTU) + x] = pData[x + 2]; 139 | } 140 | } 141 | 142 | } else if (pData[0] == 0xFC) { 143 | if (current) { 144 | writeLen = (pData[1] * 256) + pData[2]; 145 | } else { 146 | writeLen2 = (pData[1] * 256) + pData[2]; 147 | } 148 | current = !current; 149 | cur = (pData[3] * 256) + pData[4]; 150 | writeFile = true; 151 | if (cur < parts - 1) { 152 | request = !FASTMODE; 153 | } 154 | } else if (pData[0] == 0xFD) { 155 | sendMode = true; 156 | if (FLASH.exists("/update.bin")) { 157 | FLASH.remove("/update.bin"); 158 | } 159 | } else if (pData[0] == 0xFE) { 160 | rParts = 0; 161 | tParts = (pData[1] * 256 * 256 * 256) + (pData[2] * 256 * 256) + (pData[3] * 256) + pData[4]; 162 | 163 | Serial.print("Available space: "); 164 | Serial.println(FLASH.totalBytes() - FLASH.usedBytes()); 165 | Serial.print("File Size: "); 166 | Serial.println(tParts); 167 | 168 | } else if (pData[0] == 0xFF) { 169 | parts = (pData[1] * 256) + pData[2]; 170 | MTU = (pData[3] * 256) + pData[4]; 171 | MODE = UPDATE_MODE; 172 | 173 | } else if (pData[0] == 0xEF) { 174 | FLASH.format(); 175 | sendSize = true; 176 | } 177 | 178 | 179 | } 180 | 181 | } 182 | 183 | 184 | }; 185 | 186 | static void writeBinary(fs::FS &fs, const char * path, uint8_t *dat, int len) { 187 | 188 | //Serial.printf("Write binary file %s\r\n", path); 189 | 190 | File file = fs.open(path, FILE_APPEND); 191 | 192 | if (!file) { 193 | Serial.println("- failed to open file for writing"); 194 | return; 195 | } 196 | file.write(dat, len); 197 | file.close(); 198 | writeFile = false; 199 | rParts += len; 200 | } 201 | 202 | void sendOtaResult(String result) { 203 | pCharacteristicTX->setValue(result.c_str()); 204 | pCharacteristicTX->notify(); 205 | delay(200); 206 | } 207 | 208 | 209 | void performUpdate(Stream &updateSource, size_t updateSize) { 210 | char s1 = 0x0F; 211 | String result = String(s1); 212 | if (Update.begin(updateSize)) { 213 | size_t written = Update.writeStream(updateSource); 214 | if (written == updateSize) { 215 | Serial.println("Written : " + String(written) + " successfully"); 216 | } 217 | else { 218 | Serial.println("Written only : " + String(written) + "/" + String(updateSize) + ". Retry?"); 219 | } 220 | result += "Written : " + String(written) + "/" + String(updateSize) + " [" + String((written / updateSize) * 100) + "%] \n"; 221 | if (Update.end()) { 222 | Serial.println("OTA done!"); 223 | result += "OTA Done: "; 224 | if (Update.isFinished()) { 225 | Serial.println("Update successfully completed. Rebooting..."); 226 | result += "Success!\n"; 227 | } 228 | else { 229 | Serial.println("Update not finished? Something went wrong!"); 230 | result += "Failed!\n"; 231 | } 232 | 233 | } 234 | else { 235 | Serial.println("Error Occurred. Error #: " + String(Update.getError())); 236 | result += "Error #: " + String(Update.getError()); 237 | } 238 | } 239 | else 240 | { 241 | Serial.println("Not enough space to begin OTA"); 242 | result += "Not enough space for OTA"; 243 | } 244 | if (deviceConnected) { 245 | sendOtaResult(result); 246 | delay(5000); 247 | } 248 | } 249 | 250 | void updateFromFS(fs::FS &fs) { 251 | File updateBin = fs.open("/update.bin"); 252 | if (updateBin) { 253 | if (updateBin.isDirectory()) { 254 | Serial.println("Error, update.bin is not a file"); 255 | updateBin.close(); 256 | return; 257 | } 258 | 259 | size_t updateSize = updateBin.size(); 260 | 261 | if (updateSize > 0) { 262 | Serial.println("Trying to start update"); 263 | performUpdate(updateBin, updateSize); 264 | } 265 | else { 266 | Serial.println("Error, file is empty"); 267 | } 268 | 269 | updateBin.close(); 270 | 271 | // when finished remove the binary from spiffs to indicate end of the process 272 | Serial.println("Removing update file"); 273 | fs.remove("/update.bin"); 274 | 275 | rebootEspWithReason("Rebooting to complete OTA update"); 276 | } 277 | else { 278 | Serial.println("Could not load update.bin from spiffs root"); 279 | } 280 | } 281 | 282 | void initBLE() { 283 | BLEDevice::init("ESP32 OTA"); 284 | BLEServer *pServer = BLEDevice::createServer(); 285 | pServer->setCallbacks(new MyServerCallbacks()); 286 | 287 | BLEService *pService = pServer->createService(SERVICE_UUID); 288 | pCharacteristicTX = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY ); 289 | pCharacteristicRX = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); 290 | pCharacteristicRX->setCallbacks(new MyCallbacks()); 291 | pCharacteristicTX->setCallbacks(new MyCallbacks()); 292 | pCharacteristicTX->addDescriptor(new BLE2902()); 293 | pCharacteristicTX->setNotifyProperty(true); 294 | pService->start(); 295 | 296 | 297 | // BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility 298 | BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); 299 | pAdvertising->addServiceUUID(SERVICE_UUID); 300 | pAdvertising->setScanResponse(true); 301 | pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue 302 | pAdvertising->setMinPreferred(0x12); 303 | BLEDevice::startAdvertising(); 304 | Serial.println("Characteristic defined! Now you can read it in your phone!"); 305 | } 306 | 307 | void setup() { 308 | Serial.begin(115200); 309 | Serial.println("Starting BLE OTA sketch"); 310 | pinMode(BUILTINLED, OUTPUT); 311 | 312 | #ifdef USE_SPIFFS 313 | if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) { 314 | Serial.println("SPIFFS Mount Failed"); 315 | return; 316 | } 317 | #else 318 | if (!FFat.begin()) { 319 | Serial.println("FFat Mount Failed"); 320 | if (FORMAT_FFAT_IF_FAILED) FFat.format(); 321 | return; 322 | } 323 | #endif 324 | 325 | 326 | initBLE(); 327 | 328 | } 329 | 330 | void loop() { 331 | 332 | switch (MODE) { 333 | 334 | case NORMAL_MODE: 335 | if (deviceConnected) { 336 | digitalWrite(BUILTINLED, HIGH); 337 | if (sendMode) { 338 | uint8_t fMode[] = {0xAA, FASTMODE}; 339 | pCharacteristicTX->setValue(fMode, 2); 340 | pCharacteristicTX->notify(); 341 | delay(50); 342 | sendMode = false; 343 | } 344 | 345 | if (sendSize) { 346 | unsigned long x = FLASH.totalBytes(); 347 | unsigned long y = FLASH.usedBytes(); 348 | 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}; 349 | pCharacteristicTX->setValue(fSize, 7); 350 | pCharacteristicTX->notify(); 351 | delay(50); 352 | sendSize = false; 353 | } 354 | 355 | // your loop code here 356 | } else { 357 | digitalWrite(BUILTINLED, LOW); 358 | } 359 | 360 | // or here 361 | 362 | break; 363 | 364 | case UPDATE_MODE: 365 | 366 | if (request) { 367 | uint8_t rq[] = {0xF1, (cur + 1) / 256, (cur + 1) % 256}; 368 | pCharacteristicTX->setValue(rq, 3); 369 | pCharacteristicTX->notify(); 370 | delay(50); 371 | request = false; 372 | } 373 | 374 | if (cur + 1 == parts) { // received complete file 375 | uint8_t com[] = {0xF2, (cur + 1) / 256, (cur + 1) % 256}; 376 | pCharacteristicTX->setValue(com, 3); 377 | pCharacteristicTX->notify(); 378 | delay(50); 379 | MODE = OTA_MODE; 380 | } 381 | 382 | if (writeFile) { 383 | if (!current) { 384 | writeBinary(FLASH, "/update.bin", updater, writeLen); 385 | } else { 386 | writeBinary(FLASH, "/update.bin", updater2, writeLen2); 387 | } 388 | } 389 | 390 | break; 391 | 392 | case OTA_MODE: 393 | 394 | if (writeFile) { 395 | if (!current) { 396 | writeBinary(FLASH, "/update.bin", updater, writeLen); 397 | } else { 398 | writeBinary(FLASH, "/update.bin", updater2, writeLen2); 399 | } 400 | } 401 | 402 | 403 | if (rParts == tParts) { 404 | Serial.println("Complete"); 405 | delay(5000); 406 | updateFromFS(FLASH); 407 | } else { 408 | writeFile = true; 409 | Serial.println("Incomplete"); 410 | Serial.print("Expected: "); 411 | Serial.print(tParts); 412 | Serial.print("Received: "); 413 | Serial.println(rParts); 414 | delay(2000); 415 | } 416 | break; 417 | 418 | } 419 | 420 | } 421 | -------------------------------------------------------------------------------- /esp32_ble_ota/esp32_ble_ota.ino.doitESP32devkitV1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbiego/ESP32_BLE_OTA_Arduino/905466b49eca9b7568bfba2b0ced75d3bb6d4bde/esp32_ble_ota/esp32_ble_ota.ino.doitESP32devkitV1.bin -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "LOGD", 4 | "LOGI", 5 | "LOGW", 6 | "ledc", 7 | "LEDC", 8 | ] 9 | } -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/README.md: -------------------------------------------------------------------------------- 1 | # Compact version of the BLE OTA example 2 | 3 | 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. 4 | 5 | **Note:** this version of the code is not compatible with the mobile app for the moment (the update mechanism is not exactly the same). 6 | 7 | Furthermore, to use the FFAT mode you need to use another partition table (which you must configure in `platformio.ini`). 8 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/discover.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from bleak import BleakScanner 3 | 4 | 5 | async def run(): 6 | for device in await BleakScanner.discover(): 7 | print(f'{device.name} - {device.address}') 8 | 9 | if __name__ == '__main__': 10 | try: 11 | asyncio.run(run()) 12 | except KeyboardInterrupt: 13 | pass 14 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/lib/ble_ota_dfu/keywords.txt: -------------------------------------------------------------------------------- 1 | # To be filled if you want to use it on Arduino 2 | #FUNCTIONS COLOR #D35400 - ORANGE KEYWORD1 3 | #FUNCTIONS COLOR #D35400 - ORANGE KEYWORD2 4 | #STRUCTURE COLOR #728E00 - GREEN KEYWORD3 5 | #VARIABLES COLOR #00979C - BLUE LITERAL1 6 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/lib/ble_ota_dfu/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ble_ota_dfuU", 3 | "keywords": "BLE OTA DFU", 4 | "description": "BLE OTA DFU", 5 | "repository": 6 | { 7 | "type": "email", 8 | "url": "vincent.stragier@outlook.com" 9 | }, 10 | "version": "1.0.0", 11 | "exclude": "doc", 12 | "frameworks": "arduino", 13 | "platforms": ["esp32"] 14 | } 15 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/lib/ble_ota_dfu/library.properties: -------------------------------------------------------------------------------- 1 | name=ble_ota_dfu 2 | version=1.0.0 3 | author=Vincent STRAGIER 4 | maintainer=Vincent STRAGIER 5 | sentence=BLE OTA DFU 6 | paragraph=BLE OTA DFU 7 | category=Other 8 | url=mailto:vincent.stragier@outlook.com 9 | architectures=esp32 -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/lib/ble_ota_dfu/src/ble_ota_dfu.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vincent Stragier */ 2 | #include "ble_ota_dfu.hpp" 3 | 4 | QueueHandle_t start_update_queue; 5 | QueueHandle_t update_uploading_queue; 6 | 7 | void task_install_update(void *parameters) { 8 | FS file_system = FLASH; 9 | const char path[] = "/update.bin"; 10 | uint8_t data = 0; 11 | BLE_OTA_DFU *OTA_DFU_BLE; 12 | OTA_DFU_BLE = reinterpret_cast(parameters); 13 | delay(100); 14 | 15 | // Wait for the upload to be completed 16 | bool start_update = false; 17 | xQueuePeek(start_update_queue, &start_update, portMAX_DELAY); 18 | while (!start_update) { 19 | delay(500); 20 | xQueuePeek(start_update_queue, &start_update, portMAX_DELAY); 21 | } 22 | 23 | ESP_LOGE(TAG, "Starting OTA update"); 24 | 25 | // Open update.bin file. 26 | File update_binary = FLASH.open(path); 27 | 28 | // If the file cannot be loaded, return. 29 | if (!update_binary) { 30 | ESP_LOGE(TAG, "Could not load update.bin from spiffs root"); 31 | vTaskDelete(NULL); 32 | } 33 | 34 | // Verify that the file is not a directory 35 | if (update_binary.isDirectory()) { 36 | ESP_LOGE(TAG, "Error, update.bin is not a file"); 37 | update_binary.close(); 38 | vTaskDelete(NULL); 39 | } 40 | 41 | // Get binary file size 42 | size_t update_size = update_binary.size(); 43 | 44 | // Proceed to the update if the file is not empty 45 | if (update_size <= 0) { 46 | ESP_LOGE(TAG, "Error, update file is empty"); 47 | update_binary.close(); 48 | vTaskDelete(NULL); 49 | } 50 | 51 | ESP_LOGI(TAG, "Starting the update"); 52 | // perform_update(update_binary, update_size); 53 | String result = (String) static_cast(0x0F); 54 | 55 | // Init update 56 | if (Update.begin(update_size)) { 57 | // Perform the update 58 | size_t written = Update.writeStream(update_binary); 59 | 60 | ESP_LOGI(TAG, "Written: %d/%d. %s", written, update_size, 61 | written == update_size ? "Success!" : "Retry?"); 62 | result += "Written : " + String(written) + "/" + String(update_size) + 63 | " [" + String((written / update_size) * 100) + " %] \n"; 64 | 65 | // Check update 66 | if (Update.end()) { 67 | ESP_LOGI(TAG, "OTA done!"); 68 | result += "OTA Done: "; 69 | 70 | if (Update.isFinished()) { 71 | ESP_LOGI(TAG, "Update successfully completed. Rebooting..."); 72 | } else { 73 | ESP_LOGE(TAG, "Update not finished? Something went wrong!"); 74 | } 75 | 76 | result += Update.isFinished() ? "Success!\n" : "Failed!\n"; 77 | } else { 78 | ESP_LOGE(TAG, "Error Occurred. Error #: %d", Update.getError()); 79 | result += "Error #: " + String(Update.getError()); 80 | } 81 | } else { 82 | ESP_LOGE(TAG, "Not enough space to begin BLE OTA DFU"); 83 | result += "Not enough space to begin BLE OTA DFU"; 84 | } 85 | 86 | update_binary.close(); 87 | 88 | // When finished remove the binary from spiffs 89 | // to indicate the end of the process 90 | ESP_LOGI(TAG, "Removing update file"); 91 | FLASH.remove(path); 92 | 93 | if (OTA_DFU_BLE->connected()) { 94 | // Return the result to the client (tells the client if the update was a 95 | // successfull or not) 96 | ESP_LOGI(TAG, "Sending result to client"); 97 | OTA_DFU_BLE->send_OTA_DFU(result); 98 | // OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(result); 99 | // OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); 100 | ESP_LOGE(TAG, "%s", result.c_str()); 101 | ESP_LOGI(TAG, "Result sent to client"); 102 | delay(5000); 103 | } 104 | 105 | ESP_LOGE(TAG, "Rebooting ESP32: complete OTA update"); 106 | delay(5000); 107 | ESP.restart(); 108 | 109 | // ESP_LOGI(TAG, "Installation is complete"); 110 | vTaskDelete(NULL); 111 | } 112 | 113 | // void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t 114 | // code) { 115 | // Serial.print("Status "); 116 | // Serial.print(s); 117 | // Serial.print(" on characteristic "); 118 | // Serial.print(pCharacteristic->getUUID().toString().c_str()); 119 | // Serial.print(" with code "); 120 | // Serial.println(code); 121 | // } 122 | 123 | uint16_t BLEOverTheAirDeviceFirmwareUpdate::write_binary(fs::FS *file_system, 124 | const char *path, 125 | uint8_t *data, 126 | uint16_t length, 127 | bool keep_open) { 128 | static File file; 129 | 130 | // Append data to the file 131 | 132 | if (!file_open) { 133 | ESP_LOGI(TAG, "Opening binary file %s\r\n", path); 134 | file = file_system->open(path, FILE_WRITE); 135 | file_open = true; 136 | } 137 | 138 | if (!file) { 139 | ESP_LOGE(TAG, "Failed to open the file to write"); 140 | return 0; 141 | } 142 | 143 | if (data != nullptr) { 144 | ESP_LOGI(TAG, "Write binary file %s\r\n", path); 145 | file.write(data, length); 146 | } 147 | 148 | if (keep_open) { 149 | return length; 150 | } else { 151 | file.close(); 152 | file_open = false; 153 | return 0; 154 | } 155 | } 156 | 157 | void BLEOverTheAirDeviceFirmwareUpdate::onNotify( 158 | BLECharacteristic *pCharacteristic) { 159 | #ifdef DEBUG_BLE_OTA_DFU_TX 160 | // uint8_t *pData; 161 | std::string value = pCharacteristic->getValue(); 162 | uint16_t len = value.length(); 163 | // pData = pCharacteristic->getData(); 164 | uint8_t *pData = (uint8_t *)value.data(); 165 | 166 | if (pData != NULL) { 167 | ESP_LOGD(TAG, "Notify callback for characteristic %s of data length %d", 168 | pCharacteristic->getUUID().toString().c_str(), len); 169 | 170 | // Print transferred packets 171 | Serial.print("TX "); 172 | for (uint16_t i = 0; i < len; i++) { 173 | Serial.printf("%02X ", pData[i]); 174 | } 175 | Serial.println(); 176 | } 177 | #endif 178 | } 179 | 180 | void BLEOverTheAirDeviceFirmwareUpdate::onWrite( 181 | BLECharacteristic *pCharacteristic) { 182 | // uint8_t *pData; 183 | std::string value = pCharacteristic->getValue(); 184 | uint16_t len = value.length(); 185 | // pData = pCharacteristic->getData(); 186 | uint8_t *pData = (uint8_t *)value.data(); 187 | 188 | // Check that data have been received 189 | if (pData != NULL) { 190 | // #define DEBUG_BLE_OTA_DFU_RX 191 | #ifdef DEBUG_BLE_OTA_DFU_RX 192 | ESP_LOGD(TAG, "Write callback for characteristic %s of data length %d", 193 | pCharacteristic->getUUID().toString().c_str(), len); 194 | Serial.print("RX "); 195 | for (uint16_t index = 0; index < len; index++) { 196 | Serial.printf("%02X ", pData[i]); 197 | } 198 | Serial.println(); 199 | #endif 200 | switch (pData[0]) { 201 | // Send total and used sizes 202 | case 0xEF: { 203 | FLASH.format(); 204 | 205 | // Send flash size 206 | uint16_t total_size = FLASH.totalBytes(); 207 | uint16_t used_size = FLASH.usedBytes(); 208 | uint8_t flash_size[] = {0xEF, 209 | static_cast(total_size >> 16), 210 | static_cast(total_size >> 8), 211 | static_cast(total_size), 212 | static_cast(used_size >> 16), 213 | static_cast(used_size >> 8), 214 | static_cast(used_size)}; 215 | OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(flash_size, 7); 216 | OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); 217 | delay(10); 218 | } break; 219 | 220 | // Write parts to RAM 221 | case 0xFB: { 222 | // pData[1] is the position of the next part 223 | for (uint16_t index = 0; index < len - 2; index++) { 224 | updater[!selected_updater][(pData[1] * MTU) + index] = pData[index + 2]; 225 | } 226 | } break; 227 | 228 | // Write updater content to the flash 229 | case 0xFC: { 230 | selected_updater = !selected_updater; 231 | write_len[selected_updater] = (pData[1] * 256) + pData[2]; 232 | current_progression = (pData[3] * 256) + pData[4]; 233 | 234 | received_file_size += 235 | write_binary(&FLASH, "/update.bin", updater[selected_updater], 236 | write_len[selected_updater]); 237 | 238 | if ((current_progression < parts - 1) && !FASTMODE) { 239 | uint8_t progression[] = {0xF1, 240 | (uint8_t)((current_progression + 1) / 256), 241 | (uint8_t)((current_progression + 1) % 256)}; 242 | OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(progression, 3); 243 | OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); 244 | delay(10); 245 | } 246 | 247 | ESP_LOGI(TAG, "Upload progress: %d/%d", current_progression + 1, parts); 248 | if (current_progression + 1 == parts) { 249 | // If all the file has been received, send the progression 250 | uint8_t progression[] = {0xF2, 251 | (uint8_t)((current_progression + 1) / 256), 252 | (uint8_t)((current_progression + 1) % 256)}; 253 | OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(progression, 3); 254 | OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); 255 | delay(10); 256 | 257 | if (received_file_size != expected_file_size) { 258 | // received_file_size += (pData[1] * 256) + pData[2]; 259 | received_file_size += 260 | write_binary(&FLASH, "/update.bin", updater[selected_updater], 261 | write_len[selected_updater]); 262 | 263 | if (received_file_size > expected_file_size) { 264 | ESP_LOGW(TAG, "Unexpected size:\n Expected: %d\nReceived: %d", 265 | expected_file_size, received_file_size); 266 | } 267 | 268 | } else { 269 | ESP_LOGI(TAG, "Installing update"); 270 | 271 | // Start the installation 272 | write_binary(&FLASH, "/update.bin", nullptr, 0, false); 273 | bool start_update = true; 274 | xQueueOverwrite(start_update_queue, &start_update); 275 | } 276 | } 277 | } break; 278 | 279 | // Remove previous file and send transfer mode 280 | case 0xFD: { 281 | // Remove previous (failed?) update 282 | if (FLASH.exists("/update.bin")) { 283 | ESP_LOGI(TAG, "Removing previous update"); 284 | FLASH.remove("/update.bin"); 285 | } 286 | 287 | // Send mode ("fast" or "slow") 288 | uint8_t mode[] = {0xAA, FASTMODE}; 289 | OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->setValue(mode, 2); 290 | OTA_DFU_BLE->pCharacteristic_BLE_OTA_DFU_TX->notify(); 291 | delay(10); 292 | } break; 293 | 294 | // Keep track of the received file and of the expected file sizes 295 | case 0xFE: 296 | received_file_size = 0; 297 | expected_file_size = (pData[1] * 16777216) + (pData[2] * 65536) + 298 | (pData[3] * 256) + pData[4]; 299 | 300 | ESP_LOGI(TAG, "Available space: %d\nFile Size: %d\n", 301 | FLASH.totalBytes() - FLASH.usedBytes(), expected_file_size); 302 | break; 303 | 304 | // Switch to update mode 305 | case 0xFF: 306 | parts = (pData[1] * 256) + pData[2]; 307 | MTU = (pData[3] * 256) + pData[4]; 308 | break; 309 | 310 | default: 311 | ESP_LOGW(TAG, "Unknown command: %02X", pData[0]); 312 | break; 313 | } 314 | // ESP_LOGE(TAG, "Not stuck in loop"); 315 | } 316 | delay(1); 317 | } 318 | 319 | bool BLE_OTA_DFU::configure_OTA(NimBLEServer *pServer) { 320 | // Init FLASH 321 | #ifdef USE_SPIFFS 322 | if (!SPIFFS.begin(FORMAT_FLASH_IF_MOUNT_FAILED)) { 323 | ESP_LOGE(TAG, "SPIFFS Mount Failed"); 324 | return false; 325 | } 326 | ESP_LOGI(TAG, "SPIFFS Mounted"); 327 | #else 328 | if (!FFat.begin()) { 329 | ESP_LOGE(TAG, "FFat Mount Failed"); 330 | if (FORMAT_FLASH_IF_MOUNT_FAILED) 331 | FFat.format(); 332 | return false; 333 | } 334 | ESP_LOGI(TAG, "FFat Mounted"); 335 | #endif 336 | 337 | // Get the pointer to the pServer 338 | this->pServer = pServer; 339 | // Create the BLE OTA DFU Service 340 | pServiceOTA = pServer->createService(SERVICE_OTA_BLE_UUID); 341 | 342 | if (pServiceOTA == nullptr) { 343 | return false; 344 | } 345 | 346 | BLECharacteristic *pCharacteristic_BLE_OTA_DFU_RX = 347 | pServiceOTA->createCharacteristic(CHARACTERISTIC_OTA_BL_UUID_RX, 348 | NIMBLE_PROPERTY::WRITE | 349 | NIMBLE_PROPERTY::WRITE_NR); 350 | 351 | if (pCharacteristic_BLE_OTA_DFU_RX == nullptr) { 352 | return false; 353 | } 354 | 355 | auto *bleOTACharacteristicCallbacksRX = 356 | new BLEOverTheAirDeviceFirmwareUpdate(); 357 | bleOTACharacteristicCallbacksRX->OTA_DFU_BLE = this; 358 | pCharacteristic_BLE_OTA_DFU_RX->setCallbacks(bleOTACharacteristicCallbacksRX); 359 | 360 | pCharacteristic_BLE_OTA_DFU_TX = pServiceOTA->createCharacteristic( 361 | CHARACTERISTIC_OTA_BL_UUID_TX, NIMBLE_PROPERTY::NOTIFY); 362 | 363 | if (pCharacteristic_BLE_OTA_DFU_TX == nullptr) { 364 | return false; 365 | } 366 | 367 | // Start the BLE UART service 368 | pServiceOTA->start(); 369 | return true; 370 | } 371 | 372 | void BLE_OTA_DFU::start_OTA() { 373 | bool state = false; 374 | initialize_queue(&start_update_queue, bool, &state, 1); 375 | initialize_queue(&update_uploading_queue, bool, &state, 1); 376 | // initialize_empty_queue(&update_queue, uint8_t, UPDATE_QUEUE_SIZE); 377 | 378 | ESP_LOGI(TAG, "Available heap memory: %d", ESP.getFreeHeap()); 379 | // ESP_LOGE(TAG, "Available stack memory: %d", 380 | 381 | ESP_LOGI(TAG, "Available free space: %d", 382 | FLASH.totalBytes() - FLASH.usedBytes()); 383 | // ESP_LOGE(TAG, "Available free blocks: %d", FLASH.blockCount()); 384 | TaskHandle_t taskInstallUpdate = NULL; 385 | 386 | xTaskCreatePinnedToCoreAndAssert(task_install_update, "task_install_update", 387 | 5120, static_cast(this), 5, 388 | &taskInstallUpdate, 1); 389 | 390 | ESP_LOGI(TAG, "Available memory (after task started): %d", ESP.getFreeHeap()); 391 | } 392 | 393 | bool BLE_OTA_DFU::begin(String local_name) { 394 | // Create the BLE Device 395 | BLEDevice::init(local_name.c_str()); 396 | 397 | ESP_LOGI(TAG, "Starting BLE UART services"); 398 | 399 | // Create the BLE Server 400 | pServer = BLEDevice::createServer(); 401 | 402 | if (pServer == nullptr) { 403 | return false; 404 | } 405 | 406 | this->configure_OTA(pServer); 407 | 408 | // Start advertising 409 | pServer->getAdvertising()->addServiceUUID(pServiceOTA->getUUID()); 410 | pServer->getAdvertising()->start(); 411 | 412 | this->start_OTA(); 413 | return true; 414 | } 415 | 416 | bool BLE_OTA_DFU::connected() { 417 | // True if connected 418 | return pServer->getConnectedCount() > 0; 419 | } 420 | 421 | void BLE_OTA_DFU::send_OTA_DFU(uint8_t value) { 422 | uint8_t _value = value; 423 | this->pCharacteristic_BLE_OTA_DFU_TX->setValue(&_value, 1); 424 | this->pCharacteristic_BLE_OTA_DFU_TX->notify(); 425 | } 426 | 427 | void BLE_OTA_DFU::send_OTA_DFU(uint8_t *value, size_t size) { 428 | this->pCharacteristic_BLE_OTA_DFU_TX->setValue(value, size); 429 | this->pCharacteristic_BLE_OTA_DFU_TX->notify(); 430 | } 431 | 432 | void BLE_OTA_DFU::send_OTA_DFU(String value) { 433 | this->pCharacteristic_BLE_OTA_DFU_TX->setValue(value.c_str()); 434 | this->pCharacteristic_BLE_OTA_DFU_TX->notify(); 435 | } 436 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/lib/ble_ota_dfu/src/ble_ota_dfu.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vincent Stragier */ 2 | // Highly inspired by https://github.com/fbiego/ESP32_BLE_OTA_Arduino 3 | #pragma once 4 | #ifndef SRC_BLE_OTA_DFU_HPP_ 5 | #define SRC_BLE_OTA_DFU_HPP_ 6 | 7 | #include "./freertos_utils.hpp" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // comment to use FFat 15 | #define USE_SPIFFS 16 | 17 | #ifdef USE_SPIFFS 18 | // SPIFFS write is slower 19 | #include 20 | #define FLASH SPIFFS 21 | const bool FASTMODE = false; 22 | #else 23 | // FFat is faster 24 | #include 25 | #define FLASH FFat 26 | const bool FASTMODE = true; 27 | #endif 28 | // #endif 29 | 30 | const char SERVICE_OTA_BLE_UUID[] = "fe590001-54ae-4a28-9f74-dfccb248601d"; 31 | const char CHARACTERISTIC_OTA_BL_UUID_RX[] = 32 | "fe590002-54ae-4a28-9f74-dfccb248601d"; 33 | const char CHARACTERISTIC_OTA_BL_UUID_TX[] = 34 | "fe590003-54ae-4a28-9f74-dfccb248601d"; 35 | 36 | const bool FORMAT_FLASH_IF_MOUNT_FAILED = true; 37 | const uint32_t UPDATER_SIZE = 20000; 38 | 39 | /* Dummy class */ 40 | class BLE_OTA_DFU; 41 | 42 | class BLEOverTheAirDeviceFirmwareUpdate : public BLECharacteristicCallbacks { 43 | private: 44 | bool selected_updater = true; 45 | bool file_open = false; 46 | uint8_t updater[2][UPDATER_SIZE]; 47 | uint16_t write_len[2] = {0, 0}; 48 | uint16_t parts = 0, MTU = 0; 49 | uint16_t current_progression = 0; 50 | uint32_t received_file_size, expected_file_size; 51 | 52 | public: 53 | friend class BLE_OTA_DFU; 54 | BLE_OTA_DFU *OTA_DFU_BLE; 55 | 56 | uint16_t write_binary(fs::FS *file_system, const char *path, uint8_t *data, 57 | uint16_t length, bool keep_open = true); 58 | void onNotify(BLECharacteristic *pCharacteristic); 59 | void onWrite(BLECharacteristic *pCharacteristic); 60 | }; 61 | 62 | class BLE_OTA_DFU { 63 | private: 64 | BLEServer *pServer = nullptr; 65 | BLEService *pServiceOTA = nullptr; 66 | BLECharacteristic *pCharacteristic_BLE_OTA_DFU_TX = nullptr; 67 | friend class BLEOverTheAirDeviceFirmwareUpdate; 68 | 69 | public: 70 | BLE_OTA_DFU() = default; 71 | ~BLE_OTA_DFU() = default; 72 | 73 | bool configure_OTA(NimBLEServer *pServer); 74 | void start_OTA(); 75 | 76 | bool begin(String local_name); 77 | 78 | bool connected(); 79 | 80 | void send_OTA_DFU(uint8_t value); 81 | void send_OTA_DFU(uint8_t *value, size_t size); 82 | void send_OTA_DFU(String value); 83 | 84 | friend void task_install_update(void *parameters); 85 | }; 86 | 87 | #endif /* SRC_BLE_OTA_DFU_HPP_ */ 88 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/lib/ble_ota_dfu/src/freertos_utils.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vincent Stragier */ 2 | #include "freertos_utils.hpp" 3 | 4 | BaseType_t xTaskCreatePinnedToCoreAndAssert( 5 | TaskFunction_t pvTaskCode, const char *pcName, uint32_t usStackDepth, 6 | void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pvCreatedTask, 7 | BaseType_t xCoreID) { 8 | 9 | BaseType_t xReturn = 10 | xTaskCreatePinnedToCore(pvTaskCode, pcName, usStackDepth, pvParameters, 11 | uxPriority, pvCreatedTask, xCoreID); 12 | 13 | configASSERT(pvCreatedTask); 14 | 15 | return xReturn; 16 | } 17 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/lib/ble_ota_dfu/src/freertos_utils.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vincent Stragier */ 2 | #pragma once 3 | #ifndef SRC_FREERTOS_UTILS_HPP_ 4 | #define SRC_FREERTOS_UTILS_HPP_ 5 | 6 | #include 7 | 8 | // Initialize the queue 9 | #define initialize_queue(queue, T, initial_value, size) \ 10 | _initialize_queue((queue), (#queue), (size), (initial_value)) 11 | #define initialize_empty_queue(queue, T, size) \ 12 | _initialize_queue((queue), (#queue), (size)) 13 | 14 | template 15 | void _initialize_queue(QueueHandle_t *queue, const char *queue_name, 16 | size_t size, T *initial_value = nullptr) { 17 | *queue = xQueueCreate(size, sizeof(T)); 18 | 19 | ESP_LOGI(TAG, "Creating the queue \"%s\"", queue_name); 20 | // Init start update queue 21 | if (*queue == NULL || *queue == nullptr) { 22 | ESP_LOGE(TAG, "Error creating the queue \"%s\"", queue_name); 23 | } 24 | 25 | if (initial_value == nullptr || initial_value == NULL) { 26 | return; 27 | } 28 | 29 | if (!xQueueSend(*queue, initial_value, portMAX_DELAY)) { 30 | ESP_LOGE(TAG, "Error initializing the queue \"%s\"", queue_name); 31 | } 32 | } 33 | 34 | // Create a task and assert it's creation 35 | BaseType_t xTaskCreatePinnedToCoreAndAssert( 36 | TaskFunction_t pvTaskCode, const char *pcName, uint32_t usStackDepth, 37 | void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pvCreatedTask, 38 | BaseType_t xCoreID); 39 | 40 | #endif /* SRC_FREERTOS_UTILS_HPP_ */ 41 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/ota_updater.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os 3 | import asyncio 4 | import sys 5 | import re 6 | from time import sleep 7 | 8 | from bleak import BleakClient, BleakScanner 9 | # from bleak.exc import BleakError 10 | 11 | header = """##################################################################### 12 | ------------------------BLE OTA update--------------------- 13 | Arduino code @ https://github.com/fbiego/ESP32_BLE_OTA_Arduino 14 | #####################################################################""" 15 | 16 | UART_SERVICE_UUID = "fe590001-54ae-4a28-9f74-dfccb248601d" 17 | UART_RX_CHAR_UUID = "fe590002-54ae-4a28-9f74-dfccb248601d" 18 | UART_TX_CHAR_UUID = "fe590003-54ae-4a28-9f74-dfccb248601d" 19 | 20 | PART = 19000 21 | MTU = 250 22 | 23 | ble_ota_dfu_end = False 24 | 25 | 26 | async def start_ota(ble_address: str, filename: str): 27 | device = await BleakScanner.find_device_by_address(ble_address, timeout=20.0) 28 | disconnected_event = asyncio.Event() 29 | total = 0 30 | file_content = None 31 | client = None 32 | 33 | def handle_disconnect(_: BleakClient): 34 | print("Device disconnected !") 35 | disconnected_event.set() 36 | sleep(1) 37 | # sys.exit(0) 38 | 39 | async def handle_rx(_: int, data: bytearray): 40 | # print(f'\nReceived: {data = }\n') 41 | match data[0]: 42 | case 0xAA: 43 | print("Starting transfer, mode:", data[1]) 44 | print_progress_bar(0, total, prefix='Upload progress:', 45 | suffix='Complete', length=50) 46 | 47 | match data[1]: 48 | case 0: # Slow mode 49 | # Send first part 50 | await send_part(0, file_content, client) 51 | case 1: # Fast mode 52 | for index in range(file_parts): 53 | await send_part(index, file_content, client) 54 | print_progress_bar(index + 1, total, 55 | prefix='Upload progress:', 56 | suffix='Complete', length=50) 57 | 58 | case 0xF1: # Send next part and update progress bar 59 | next_part_to_send = int.from_bytes( 60 | data[2:3], byteorder='little') 61 | # print("Next part:", next_part_to_send, "\n") 62 | await send_part(next_part_to_send, file_content, client) 63 | print_progress_bar(next_part_to_send + 1, total, 64 | prefix='Upload progress:', 65 | suffix='Complete', length=50) 66 | 67 | case 0xF2: # Install firmware 68 | # ins = 'Installing firmware' 69 | # print("Installing firmware") 70 | pass 71 | 72 | case 0x0F: 73 | print("OTA result: ", str(data[1:], 'utf-8')) 74 | global ble_ota_dfu_end 75 | ble_ota_dfu_end = True 76 | 77 | def print_progress_bar(iteration: int, total: int, prefix: str = '', suffix: str = '', decimals: int = 1, length: int = 100, filler: str = '█', print_end: str = "\r"): 78 | """ 79 | Call in a loop to create terminal progress bar 80 | @params: 81 | iteration - Required : current iteration (Int) 82 | total - Required : total iterations (Int) 83 | prefix - Optional : prefix string (Str) 84 | suffix - Optional : suffix string (Str) 85 | decimals - Optional : positive number of decimals in percent complete (Int) 86 | length - Optional : character length of bar (Int) 87 | filler - Optional : bar fill character (Str) 88 | print_end - Optional : end character (e.g. "\r", "\r\n") (Str) 89 | """ 90 | percent = ("{0:." + str(decimals) + "f}").format(100 * 91 | (iteration / float(total))) 92 | filled_length = (length * iteration) // total 93 | bar = filler * filled_length + '-' * (length - filled_length) 94 | print(f'\r{prefix} |{bar}| {percent} % {suffix}', end=print_end) 95 | # Print new line upon complete 96 | if iteration == total: 97 | print() 98 | 99 | async def send_part(position: int, data: bytearray, client: BleakClient): 100 | start = position * PART 101 | end = (position + 1) * PART 102 | # print(locals()) 103 | if len(data) < end: 104 | end = len(data) 105 | 106 | data_length = end - start 107 | parts = data_length // MTU 108 | for part_index in range(parts): 109 | to_be_sent = bytearray([0xFB, part_index]) 110 | for mtu_index in range(MTU): 111 | to_be_sent.append( 112 | data[(position*PART)+(MTU * part_index) + mtu_index]) 113 | await send_data(client, to_be_sent) 114 | 115 | if data_length % MTU: 116 | remaining = data_length % MTU 117 | to_be_sent = bytearray([0xFB, parts]) 118 | for index in range(remaining): 119 | to_be_sent.append( 120 | data[(position*PART)+(MTU * parts) + index]) 121 | await send_data(client, to_be_sent) 122 | 123 | await send_data(client, bytearray([0xFC, data_length//256, data_length % 124 | 256, position//256, position % 256]), True) 125 | 126 | async def send_data(client: BleakClient, data: bytearray, response: bool = False): 127 | # print(f'{locals()["data"]}') 128 | await client.write_gatt_char(UART_RX_CHAR_UUID, data, response) 129 | 130 | if not device: 131 | print("-----------Failed--------------") 132 | print(f"Device with address {ble_address} could not be found.") 133 | return 134 | #raise BleakError(f"A device with address {ble_address} could not be found.") 135 | 136 | async with BleakClient(device, disconnected_callback=handle_disconnect) as local_client: 137 | client = local_client 138 | 139 | # Load file 140 | print("Reading from: ", filename) 141 | file_content = open(filename, "rb").read() 142 | file_parts = -(len(file_content) // -PART) 143 | total = file_parts 144 | file_length = len(file_content) 145 | print(f'File size: {len(file_content)}') 146 | 147 | # Set the UUID of the service you want to connect to and the callback 148 | await client.start_notify(UART_TX_CHAR_UUID, handle_rx) 149 | await asyncio.sleep(1.0) 150 | 151 | # Send file length 152 | await send_data(client, bytearray([0xFE, 153 | file_length >> 24 & 0xFF, 154 | file_length >> 16 & 0xFF, 155 | file_length >> 8 & 0xFF, 156 | file_length & 0xFF])) 157 | 158 | # Send number of parts and MTU value 159 | await send_data(client, bytearray([0xFF, 160 | file_parts//256, 161 | file_parts % 256, 162 | MTU // 256, 163 | MTU % 256])) 164 | 165 | # Remove previous update and receive transfer mode (start the update) 166 | await send_data(client, bytearray([0xFD])) 167 | 168 | # Wait til the update is complete 169 | while not ble_ota_dfu_end: 170 | await asyncio.sleep(1.0) 171 | 172 | print("Waiting for disconnect... ", end="") 173 | 174 | await disconnected_event.wait() 175 | print("-----------Complete--------------") 176 | 177 | 178 | def is_valid_address(value: str = None) -> bool: 179 | # Regex to check valid MAC address 180 | regex_0 = (r"^([0-9A-Fa-f]{2}[:-])" 181 | r"{5}([0-9A-Fa-f]{2})|" 182 | r"([0-9a-fA-F]{4}\\." 183 | r"[0-9a-fA-F]{4}\\." 184 | r"[0-9a-fA-F]{4}){17}$") 185 | regex_1 = (r"^[{]?[0-9a-fA-F]{8}" 186 | r"-([0-9a-fA-F]{4}-)" 187 | r"{3}[0-9a-fA-F]{12}[}]?$") 188 | 189 | # Compile the ReGex 190 | regex_0 = re.compile(regex_0) 191 | regex_1 = re.compile(regex_1) 192 | 193 | # If the string is empty return false 194 | if value is None: 195 | return False 196 | 197 | # Return if the string matched the ReGex 198 | if re.search(regex_0, value) and len(value) == 17: 199 | return True 200 | 201 | return re.search(regex_1, value) and len(value) == 36 202 | 203 | 204 | if __name__ == "__main__": 205 | print(header) 206 | # Check if the user has entered enough arguments 207 | # sys.argv.append("C8:C9:A3:D2:60:8E") 208 | # sys.argv.append("firmware.bin") 209 | 210 | if len(sys.argv) < 3: 211 | print("Specify the device address and firmware file") 212 | import sys 213 | import os 214 | filename = os.path.join(os.path.dirname( 215 | __file__), '.pio', 'build', 'esp32doit-devkit-v1', 'firmware.bin') 216 | filename = filename if os.path.exists(filename) else "firmware.bin" 217 | print( 218 | f"$ {sys.executable} \"{__file__}\" \"C8:C9:A3:D2:60:8E\" \"{filename}\"") 219 | exit(1) 220 | 221 | print("Trying to start OTA update") 222 | ble_address = sys.argv[1] 223 | filename = sys.argv[2] 224 | 225 | # Check if the address is valid 226 | if not is_valid_address(ble_address): 227 | print(f"Invalid Address: {ble_address}") 228 | exit(2) 229 | 230 | # Check if the file exists 231 | if not os.path.exists(filename): 232 | print(f"File not found: {filename}") 233 | exit(3) 234 | 235 | try: 236 | # Start the OTA update 237 | asyncio.run(start_ota(ble_address, filename)) 238 | except KeyboardInterrupt: 239 | print("\nExiting...") 240 | exit(0) 241 | except OSError: 242 | print("\nExiting (OSError)...") 243 | exit(1) 244 | except Exception: 245 | import traceback 246 | traceback.print_exc() 247 | exit(2) 248 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32doit-devkit-v1] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | ; ESP_LOGE - error (lowest) = 1 16 | ; ESP_LOGW - warning = 2 17 | ; ESP_LOGI - info = 3 18 | ; ESP_LOGD - debug = 4 19 | ; ESP_LOGV - verbose (highest) = 5 20 | ; build_flags = -DCORE_DEBUG_LEVEL=3 21 | ; build_type = debug 22 | 23 | ; board_build.partitions = default_ffat.csv 24 | board_build.partitions = default.csv 25 | 26 | ; Serial Monitor options 27 | ; https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html 28 | monitor_speed = 115200 29 | monitor_rts = 0 30 | monitor_dtr = 0 31 | 32 | monitor_filters = 33 | ; time 34 | ; send_on_enter 35 | esp32_exception_decoder 36 | ; hexlify 37 | ; colorize 38 | 39 | ; Libraries 40 | lib_deps = 41 | https://github.com/h2zero/NimBLE-Arduino.git 42 | 43 | ; Run before compilation 44 | ; extra_scripts = 45 | ; pre:some_script.py 46 | 47 | ; Configure checking tool 48 | ;, pvs-studio, 49 | check_tool = cppcheck, clangtidy 50 | check_flags = 51 | cppcheck: --addon=cert.py 52 | check_skip_packages = yes 53 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vincent Stragier */ 2 | #include "main.hpp" 3 | 4 | /////////////////// 5 | /// Setup /// 6 | /////////////////// 7 | void setup() { ota_dfu_ble.begin("Test OTA DFU"); } 8 | 9 | ////////////////// 10 | /// Loop /// 11 | ////////////////// 12 | void loop() { 13 | // Kill the holly loop() 14 | // Delete the task, comment if you want to keep the loop() 15 | vTaskDelete(NULL); 16 | } 17 | 18 | /////////////////////// 19 | /// Functions /// 20 | /////////////////////// 21 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/src/main.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vincent Stragier */ 2 | 3 | #ifndef SRC_MAIN_HPP_ 4 | #define SRC_MAIN_HPP_ 5 | /////////////////////// 6 | /// Libraries /// 7 | /////////////////////// 8 | 9 | #include 10 | #include 11 | 12 | /////////////////////// 13 | /// Variables /// 14 | /////////////////////// 15 | BLE_OTA_DFU ota_dfu_ble; 16 | 17 | #endif /* SRC_MAIN_HPP_ */ 18 | -------------------------------------------------------------------------------- /esp32_ble_ota_lib_compact/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /esp32_nim_ble_ota/esp32_nim_ble_ota.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | Copyright (c) 2021 Felix Biego 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | */ 20 | 21 | #include 22 | #include "FS.h" 23 | #include "FFat.h" 24 | #include "SPIFFS.h" 25 | #include 26 | //#include // For OTA with SD Card 27 | 28 | #define BUILTINLED 2 29 | #define FORMAT_SPIFFS_IF_FAILED true 30 | #define FORMAT_FFAT_IF_FAILED true 31 | 32 | #define USE_SPIFFS //comment to use FFat 33 | 34 | #ifdef USE_SPIFFS 35 | #define FLASH SPIFFS 36 | #define FASTMODE false //SPIFFS write is slow 37 | #else 38 | #define FLASH FFat 39 | #define FASTMODE true //FFat is faster 40 | #endif 41 | 42 | #define NORMAL_MODE 0 // normal 43 | #define UPDATE_MODE 1 // receiving firmware 44 | #define OTA_MODE 2 // installing firmware 45 | 46 | uint8_t updater[16384]; 47 | uint8_t updater2[16384]; 48 | 49 | #define SERVICE_UUID "fb1e4001-54ae-4a28-9f74-dfccb248601d" 50 | #define CHARACTERISTIC_UUID_RX "fb1e4002-54ae-4a28-9f74-dfccb248601d" 51 | #define CHARACTERISTIC_UUID_TX "fb1e4003-54ae-4a28-9f74-dfccb248601d" 52 | 53 | static BLECharacteristic* pCharacteristicTX; 54 | static BLECharacteristic* pCharacteristicRX; 55 | 56 | static bool deviceConnected = false, sendMode = false, sendSize = true; 57 | static bool writeFile = false, request = false; 58 | static int writeLen = 0, writeLen2 = 0; 59 | static bool current = true; 60 | static int parts = 0, next = 0, cur = 0, MTU = 0; 61 | static int MODE = NORMAL_MODE; 62 | unsigned long rParts, tParts; 63 | 64 | static void rebootEspWithReason(String reason) { 65 | Serial.println(reason); 66 | delay(1000); 67 | ESP.restart(); 68 | } 69 | 70 | class MyServerCallbacks: public BLEServerCallbacks { 71 | void onConnect(BLEServer* pServer) { 72 | Serial.println("Connected"); 73 | deviceConnected = true; 74 | 75 | } 76 | void onDisconnect(BLEServer* pServer) { 77 | Serial.println("disconnected"); 78 | deviceConnected = false; 79 | } 80 | }; 81 | 82 | class MyCallbacks: public BLECharacteristicCallbacks { 83 | 84 | // void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code) { 85 | // Serial.print("Status "); 86 | // Serial.print(s); 87 | // Serial.print(" on characteristic "); 88 | // Serial.print(pCharacteristic->getUUID().toString().c_str()); 89 | // Serial.print(" with code "); 90 | // Serial.println(code); 91 | // } 92 | 93 | void onRead(BLECharacteristic* pCharacteristic) { 94 | Serial.print(pCharacteristic->getUUID().toString().c_str()); 95 | Serial.print(": onRead(), value: "); 96 | Serial.println(pCharacteristic->getValue().c_str()); 97 | }; 98 | 99 | void onNotify(BLECharacteristic *pCharacteristic) { 100 | //uint8_t* pData; 101 | std::string pData = pCharacteristic->getValue(); 102 | int len = pData.length(); 103 | // Serial.print("Notify callback for characteristic "); 104 | // Serial.print(pCharacteristic->getUUID().toString().c_str()); 105 | // Serial.print(" of data length "); 106 | // Serial.println(len); 107 | Serial.print("TX "); 108 | for (int i = 0; i < len; i++) { 109 | Serial.printf("%02X ", pData[i]); 110 | } 111 | Serial.println(); 112 | } 113 | 114 | void onWrite(BLECharacteristic *pCharacteristic) { 115 | //uint8_t* pData; 116 | std::string pData = pCharacteristic->getValue(); 117 | int len = pData.length(); 118 | // Serial.print("Write callback for characteristic "); 119 | // Serial.print(pCharacteristic->getUUID().toString().c_str()); 120 | // Serial.print(" of data length "); 121 | // Serial.println(len); 122 | // Serial.print("RX "); 123 | // for (int i = 0; i < len; i++) { // leave this commented 124 | // Serial.printf("%02X ", pData[i]); 125 | // } 126 | // Serial.println(); 127 | 128 | if (pData[0] == 0xFB) { 129 | int pos = pData[1]; 130 | for (int x = 0; x < len - 2; x++) { 131 | if (current) { 132 | updater[(pos * MTU) + x] = pData[x + 2]; 133 | } else { 134 | updater2[(pos * MTU) + x] = pData[x + 2]; 135 | } 136 | } 137 | 138 | } else if (pData[0] == 0xFC) { 139 | if (current) { 140 | writeLen = (pData[1] * 256) + pData[2]; 141 | } else { 142 | writeLen2 = (pData[1] * 256) + pData[2]; 143 | } 144 | current = !current; 145 | cur = (pData[3] * 256) + pData[4]; 146 | writeFile = true; 147 | if (cur < parts - 1) { 148 | request = !FASTMODE; 149 | } 150 | } else if (pData[0] == 0xFD) { 151 | sendMode = true; 152 | if (FLASH.exists("/update.bin")) { 153 | FLASH.remove("/update.bin"); 154 | } 155 | } else if (pData[0] == 0xFE) { 156 | rParts = 0; 157 | tParts = (pData[1] * 256 * 256 * 256) + (pData[2] * 256 * 256) + (pData[3] * 256) + pData[4]; 158 | 159 | Serial.print("Available space: "); 160 | Serial.println(FLASH.totalBytes() - FLASH.usedBytes()); 161 | Serial.print("File Size: "); 162 | Serial.println(tParts); 163 | 164 | } else if (pData[0] == 0xFF) { 165 | parts = (pData[1] * 256) + pData[2]; 166 | MTU = (pData[3] * 256) + pData[4]; 167 | MODE = UPDATE_MODE; 168 | 169 | } else if (pData[0] == 0xEF) { 170 | FLASH.format(); 171 | sendSize = true; 172 | } 173 | 174 | 175 | 176 | } 177 | 178 | 179 | }; 180 | 181 | static void writeBinary(fs::FS &fs, const char * path, uint8_t *dat, int len) { 182 | 183 | //Serial.printf("Write binary file %s\r\n", path); 184 | 185 | File file = fs.open(path, FILE_APPEND); 186 | 187 | if (!file) { 188 | Serial.println("- failed to open file for writing"); 189 | return; 190 | } 191 | file.write(dat, len); 192 | file.close(); 193 | writeFile = false; 194 | rParts += len; 195 | } 196 | 197 | void sendOtaResult(String result) { 198 | byte arr[result.length()]; 199 | result.getBytes(arr, result.length()); 200 | pCharacteristicTX->setValue(arr, result.length()); 201 | pCharacteristicTX->notify(); 202 | delay(200); 203 | } 204 | 205 | 206 | void performUpdate(Stream &updateSource, size_t updateSize) { 207 | char s1 = 0x0F; 208 | String result = String(s1); 209 | if (Update.begin(updateSize)) { 210 | size_t written = Update.writeStream(updateSource); 211 | if (written == updateSize) { 212 | Serial.println("Written : " + String(written) + " successfully"); 213 | } 214 | else { 215 | Serial.println("Written only : " + String(written) + "/" + String(updateSize) + ". Retry?"); 216 | } 217 | result += "Written : " + String(written) + "/" + String(updateSize) + " [" + String((written / updateSize) * 100) + "%] \n"; 218 | if (Update.end()) { 219 | Serial.println("OTA done!"); 220 | result += "OTA Done: "; 221 | if (Update.isFinished()) { 222 | Serial.println("Update successfully completed. Rebooting..."); 223 | result += "Success!\n"; 224 | } 225 | else { 226 | Serial.println("Update not finished? Something went wrong!"); 227 | result += "Failed!\n"; 228 | } 229 | 230 | } 231 | else { 232 | Serial.println("Error Occurred. Error #: " + String(Update.getError())); 233 | result += "Error #: " + String(Update.getError()); 234 | } 235 | } 236 | else 237 | { 238 | Serial.println("Not enough space to begin OTA"); 239 | result += "Not enough space for OTA"; 240 | } 241 | if (deviceConnected) { 242 | sendOtaResult(result); 243 | delay(5000); 244 | } 245 | } 246 | 247 | void updateFromFS(fs::FS &fs) { 248 | File updateBin = fs.open("/update.bin"); 249 | if (updateBin) { 250 | if (updateBin.isDirectory()) { 251 | Serial.println("Error, update.bin is not a file"); 252 | updateBin.close(); 253 | return; 254 | } 255 | 256 | size_t updateSize = updateBin.size(); 257 | 258 | if (updateSize > 0) { 259 | Serial.println("Trying to start update"); 260 | performUpdate(updateBin, updateSize); 261 | } 262 | else { 263 | Serial.println("Error, file is empty"); 264 | } 265 | 266 | updateBin.close(); 267 | 268 | // when finished remove the binary from spiffs to indicate end of the process 269 | Serial.println("Removing update file"); 270 | fs.remove("/update.bin"); 271 | 272 | rebootEspWithReason("Rebooting to complete OTA update"); 273 | } 274 | else { 275 | Serial.println("Could not load update.bin from spiffs root"); 276 | } 277 | } 278 | 279 | void initBLE() { 280 | BLEDevice::init("NimBLE OTA"); 281 | BLEDevice::setMTU(517); 282 | BLEDevice::setPower(ESP_PWR_LVL_P9); 283 | BLEServer *pServer = BLEDevice::createServer(); 284 | pServer->setCallbacks(new MyServerCallbacks()); 285 | 286 | BLEService *pService = pServer->createService(SERVICE_UUID); 287 | pCharacteristicTX = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ ); 288 | pCharacteristicRX = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE_NR); 289 | pCharacteristicRX->setCallbacks(new MyCallbacks()); 290 | pCharacteristicTX->setCallbacks(new MyCallbacks()); 291 | pService->start(); 292 | pServer->start(); 293 | 294 | // BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility 295 | NimBLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); 296 | pAdvertising->addServiceUUID(SERVICE_UUID); 297 | pAdvertising->setScanResponse(true); 298 | pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue 299 | pAdvertising->setMinPreferred(0x12); 300 | pAdvertising->start(); 301 | //BLEDevice::startAdvertising(); 302 | Serial.println("Characteristic defined! Now you can read it in your phone!"); 303 | } 304 | 305 | void setup() { 306 | Serial.begin(115200); 307 | Serial.println("Starting BLE OTA sketch"); 308 | pinMode(BUILTINLED, OUTPUT); 309 | 310 | //SPI.begin(18, 22, 23, 5); // For OTA with SD Card 311 | //SD.begin(5); // For OTA with SD Card 312 | 313 | #ifdef USE_SPIFFS 314 | if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) { 315 | Serial.println("SPIFFS Mount Failed"); 316 | return; 317 | } 318 | #else 319 | if (!FFat.begin()) { 320 | Serial.println("FFat Mount Failed"); 321 | if (FORMAT_FFAT_IF_FAILED) FFat.format(); 322 | return; 323 | } 324 | #endif 325 | 326 | 327 | initBLE(); 328 | 329 | } 330 | 331 | void loop() { 332 | 333 | switch (MODE) { 334 | 335 | case NORMAL_MODE: 336 | if (deviceConnected) { 337 | digitalWrite(BUILTINLED, HIGH); 338 | if (sendMode) { 339 | uint8_t fMode[] = {0xAA, FASTMODE}; 340 | pCharacteristicTX->setValue(fMode, 2); 341 | pCharacteristicTX->notify(); 342 | delay(50); 343 | sendMode = false; 344 | } 345 | if (sendSize) { 346 | unsigned long x = FLASH.totalBytes(); 347 | unsigned long y = FLASH.usedBytes(); 348 | 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}; 349 | pCharacteristicTX->setValue(fSize, 7); 350 | pCharacteristicTX->notify(); 351 | delay(50); 352 | sendSize = false; 353 | } 354 | 355 | // your loop code here 356 | } else { 357 | digitalWrite(BUILTINLED, LOW); 358 | } 359 | 360 | // or here 361 | 362 | break; 363 | 364 | case UPDATE_MODE: 365 | 366 | if (request) { 367 | uint8_t rq[] = {0xF1, (cur + 1) / 256, (cur + 1) % 256}; 368 | pCharacteristicTX->setValue(rq, 3); 369 | pCharacteristicTX->notify(); 370 | delay(50); 371 | request = false; 372 | } 373 | 374 | if (writeFile) { 375 | if (!current) { 376 | writeBinary(FLASH, "/update.bin", updater, writeLen); 377 | } else { 378 | writeBinary(FLASH, "/update.bin", updater2, writeLen2); 379 | } 380 | writeFile = false; 381 | } 382 | 383 | if (cur + 1 == parts) { // received complete file 384 | uint8_t com[] = {0xF2, (cur + 1) / 256, (cur + 1) % 256}; 385 | pCharacteristicTX->setValue(com, 3); 386 | pCharacteristicTX->notify(); 387 | delay(50); 388 | MODE = OTA_MODE; 389 | } 390 | 391 | break; 392 | 393 | case OTA_MODE: 394 | if (writeFile) { 395 | if (!current) { 396 | writeBinary(FLASH, "/update.bin", updater, writeLen); 397 | } else { 398 | writeBinary(FLASH, "/update.bin", updater2, writeLen2); 399 | } 400 | } 401 | 402 | 403 | if (rParts == tParts) { 404 | Serial.println("Complete"); 405 | delay(5000); 406 | updateFromFS(FLASH); 407 | } else { 408 | writeFile = true; 409 | Serial.println("Incomplete"); 410 | Serial.print("Expected: "); 411 | Serial.print(tParts); 412 | Serial.print("Received: "); 413 | Serial.println(rParts); 414 | delay(2000); 415 | } 416 | break; 417 | 418 | } 419 | 420 | } 421 | -------------------------------------------------------------------------------- /esp32_nim_ble_ota/esp32_nim_ble_ota.ino.doitESP32devkitV1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbiego/ESP32_BLE_OTA_Arduino/905466b49eca9b7568bfba2b0ced75d3bb6d4bde/esp32_nim_ble_ota/esp32_nim_ble_ota.ino.doitESP32devkitV1.bin --------------------------------------------------------------------------------