Home

Awesome

BLEOTA description

Library inspired by https://components.espressif.com/components/espressif/ble_ota that implement the firmware and SPIFFS/LittleFS OTA via BLE and writes it to flash, sector by sector, until the upgrade is complete.

1. Services definition

The library add two services:

ServiceUUID
BLE_OTA_SERVICE00008018-0000-1000-8000-00805f9b34fb
DIS_SERVICE_UUID180A

2. DIS Service Characteristics definition

The DIS Service can contains 5 read only characteristics that can be setted up before BLEOTA.init, if none of these is assigned the service is not added.

CharacteristicsUUIDPropdescriptionMethod
DIS_MODEL_CHAR0x2A24ReadModel Number StringsetModel
DIS_SERIAL_N_CHAR0x2A25ReadSerial Number StringsetSerialNumber
DIS_FW_VER_CHAR0x2A26ReadFirmware Revision StringsetFWVersion
DIS_HW_VERSION_CHAR0x2A27ReadHardware Revision StringsetHWVersion
DIS_MNF_CHAR0x2A29ReadManufacturer Name StringsetManufactuer

3. OTA Service Characteristics definition

The OTA Service can contains 2 characteristics to perform the OTA process.

CharacteristicsUUIDPropdescription
RECV_FW_CHAR00008020-0000-1000-8000-00805f9b34fbWrite, WriteNR, NotifyFirmware received, send ACK
COMMAND_CHAR00008022-0000-1000-8000-00805f9b34fbWrite, WriteNR, NotifySend the command and ACK

4. OTA Service data transmission details

4.1 Command package format

unitCommand_IDPayLoadCRC16
ByteByte: 0 ~ 1Byte: 2 ~ 17Byte: 18 ~ 19

Command_ID:

4.2 Firmware package format

The format of the firmware package sent by the client is as follows:

unitSector_IndexPacket_SeqPayLoad
ByteByte: 0 ~ 1Byte: 2Byte: 3 ~ (MTU_size - 4)

The format of the reply packet is as follows:

unitSector_IndexACK_StatusCRC6
ByteByte: 0 ~ 1Byte: 2 ~ 3Byte: 18 ~ 19

ACK_Status:

5. Compression

To speed up the OTA process the .bin file for the update can be compressed using zlib, the decompression code is based on the work of https://github.com/vortigont/esp32-flashz where you can find more details.

With compression the time is reduced by approximately 30% for FLASH and much more for SPIFFS.

The compression must be done before eventually sign the file, you can use the [https://github.com/madler/pigz].(https://github.com/madler/pigz). tool

pigz -9kzc file.ino.bin > file.compressed.bin

6. Security

After the creation of BLE server call BLEOTA.begin with the Server pointer

// Create the BLE Device
BLEDevice::init("ESP32");

// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());

// Begin BLE OTA with security
BLEOTA.begin(pServer, true);

Add the public key

// Add pub key
BLEOTA.setKey(pub_key, strlen(pub_key));

that has been previously defined as:

const char pub_key[] = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw/rrOWrykXdTPFwZzljd\nPuuhkRDQUJQu0et5dWNd4ntbh+Qp9qDiZMEj9PcUkw6VUCWFTcSFkOR4i3M+H3g3\nJsKGe5y45DGK8HvgOAnGGUtb0/V2UVZAqiUzJ2cXSK+1688/kWRBSv6OTMXFg2Fa\nGnaIEupUIJZfnBjJmZOhqJll+kxvkxE3CjbnnP8SZ31ybItPV3DyML/7RZ3gMBB5\ngVh44kzAIzPD+NtSSU/RNbWOi3rgNPx1SLzUPjThkHAkVRJ96pWEctiblv2XwoIm\n1ZJEeeda3O46+zCpsI1Ph5oo8mi4QWj1MvkQldo3XtLWRtH/IbMLEgRSR5y054Tg\n0QIDAQAB\n-----END PUBLIC KEY-----";

with the content of the .pub file

6.1 OTA File signature

The process for signing the file is:

openssl genrsa -out priv_key.pem 2048
openssl rsa -in priv_key.pem -pubout > rsa_key.pub
openssl dgst -sign priv_key.pem -keyform PEM -sha256 -out signature.sign -binary file.ino.bin
cat file.ino.bin signature.sign > ota.bin

use the ota.bin to perform the update

7. Sample code

BLEOTA

After the creation of BLE server call BLEOTA.begin with the Server pointer

// Create the BLE Device
BLEDevice::init("ESP32");

// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());

// Begin BLE OTA
BLEOTA.begin(pServer);

Eventually set the values of the DIS Service

#ifdef MODEL
  BLEOTA.setModel(MODEL);
#endif
#ifdef SERIAL_NUM
  BLEOTA.setSerialNumber(SERIAL_NUM);
#endif
#ifdef FW_VERSION
  BLEOTA.setFWVersion(FW_VERSION);
#endif
#ifdef HW_VERSION
  BLEOTA.setHWVersion(HW_VERSION);
#endif
#ifdef MANUFACTURER
  BLEOTA.setManufactuer(MANUFACTURER);
#endif

Call BLEOTA.init to add the service

BLEOTA.init();

If you want to add the OTA Service to the advertising in the setup add

pAdvertising->addServiceUUID(BLEOTA.getBLEOTAuuid());

Add to loop the process function

BLEOTA.process();

BLEOTA_SECURE

After the creation of BLE server call BLEOTA.begin with the Server pointer and security enabled and set the public key Security

// Create the BLE Device
BLEDevice::init("ESP32");

// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());

// Begin BLE OTA with security
BLEOTA.begin(pServer, true);
// Add pub key
BLEOTA.setKey(pub_key, strlen(pub_key));

8. Callbacks

Thanks to the contribution of @drik a set of callbacks can be added. I choice to defer the execution of the code inside each callback in the process method to avoid to break the BLE communication in case of heavy process inside the callbacks. The callbacks can be defined with a new class which inherits from BLEOTACallbacks

class OTACallbacks : public BLEOTACallbacks {
public:

    //This callback method is invoked just before the APP OTA update begins.
    void beforeStartOTA() {
        Serial.println("beforeStartOTA called!\n");
    }
    //This callback method is invoked just before the SPIFFS OTA update begins.
    void beforeStartSPIFFS() {
        Serial.println("beforeStartSPIFFS called!\n");
    }
    //This callback method is invoked just after the update completes.
    void afterStop() {
        Serial.println("afterStop called!\n");
    }
    //This callback method is invoked just after the update abort.
    void afterAbort() {
        Serial.println("afterAbort called!\n");
    }
};

To set the callbacks just call setCallbacks after the begin

// Begin BLE OTA
BLEOTA.begin(pServer);
BLEOTA.setCallbacks(new OTACallbacks());

Since the progress function will reset the ESP32 500ms after the completion of the update is possible that the afterStop will no execute correctly, to handle this scenario is possible to disable the automatic reset and handle it some others part of the code.

//automatic reset
//BLEOTA.process();
//to avoid reset after OTA success and manage it in the callback or somewhere else call
BLEOTA.process(false); 

9. WebApp

BLEOTA_WEBAPP

Small web application that implement the OTA process over BLE with Web Bluetooth