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
## 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;
}
}