ESP32 OTA firmware updates via WiFi mesh network.
https://hendrikschutter.com
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
460 lines
16 KiB
460 lines
16 KiB
#include "Mesh_OTA.h" |
|
|
|
|
|
static const char *LOG_TAG = "mesh_ota"; |
|
|
|
xQueueHandle queueNodes; //nodes that should be checked for ota update (contains children and parent) |
|
xQueueHandle queueMessageOTA; //mesh ota controll messages like "OTA_Version_Response" "OTA_ACK" |
|
|
|
SemaphoreHandle_t bsStartStopServerWorker; //binary semaphore |
|
|
|
esp_err_t errMeshOTAInitialize() |
|
{ |
|
esp_err_t err = ESP_OK; |
|
BaseType_t xReturned; |
|
|
|
//create queue to store nodes for ota worker task |
|
queueNodes = xQueueCreate(QUEUE_NODES_SIZE, sizeof(mesh_addr_t)); |
|
if (queueNodes == 0) // Queue not created |
|
{ |
|
ESP_LOGE(LOG_TAG, "Unable to create Queue for Nodes"); |
|
err = ESP_FAIL; |
|
} |
|
|
|
if(err == ESP_OK) |
|
{ |
|
//create queue to store ota messages |
|
queueMessageOTA = xQueueCreate(QUEUE_MESSAGE_OTA_SIZE, sizeof(MESH_PACKET_t)); |
|
if (queueMessageOTA == 0) // Queue not created |
|
{ |
|
ESP_LOGE(LOG_TAG, "Unable to create Queue for OTA Messages"); |
|
err = ESP_FAIL; |
|
} |
|
} |
|
|
|
if(err == ESP_OK) |
|
{ |
|
bsStartStopServerWorker = xSemaphoreCreateBinary(); |
|
if( bsStartStopServerWorker == NULL ) |
|
{ |
|
ESP_LOGE(LOG_TAG, "Unable to create Mutex to represent state of Server worker"); |
|
err = ESP_FAIL; |
|
} |
|
} |
|
|
|
ERROR_CHECK(errMeshNetworkSetChildConnectedHandle(vAddNodeToPossibleUpdatableQueue)); |
|
ERROR_CHECK(errMeshNetworkSetOTAMessageHandleHandle(vAddOTAControllMessageToQueue)); |
|
ERROR_CHECK(errMeshNetworkSetChangeStateOfServerWorkerHandle(vChangeStateOfServerWorker)); |
|
|
|
if(err == ESP_OK) |
|
{ |
|
xReturned = xTaskCreate(vTaskServerWorker, "vTaskServerWorker", 8192, NULL, 5, NULL); |
|
if(xReturned != pdPASS) |
|
{ |
|
ESP_LOGE(LOG_TAG, "Unable to create the server worker task"); |
|
err = ESP_FAIL; |
|
} |
|
} |
|
|
|
return err; |
|
} |
|
|
|
|
|
void vAddNodeToPossibleUpdatableQueue(uint8_t* pu8Data) |
|
{ |
|
//send payload to node queue |
|
mesh_addr_t addrNode; |
|
memcpy(&addrNode.addr, (uint8_t *)pu8Data, 6); //copy MAC |
|
|
|
if (xQueueSend(queueNodes, &addrNode, portMAX_DELAY) != pdPASS) |
|
{ |
|
ESP_LOGE(LOG_TAG, "Unable to push node into node queue"); |
|
} |
|
else |
|
{ |
|
ESP_LOGI(LOG_TAG, "added node \"%x:%x:%x:%x:%x:%x\" to possible updatable queue", addrNode.addr[0], addrNode.addr[1], addrNode.addr[2], addrNode.addr[3], addrNode.addr[4], addrNode.addr[5]); |
|
} |
|
} |
|
|
|
|
|
void vAddOTAControllMessageToQueue(MESH_PACKET_t* puMeshPacket) |
|
{ |
|
//send ota packet to packet queue |
|
if (xQueueSend(queueMessageOTA, puMeshPacket, portMAX_DELAY) != pdPASS) |
|
{ |
|
ESP_LOGE(LOG_TAG, "Unable to push ota packet into packet queue"); |
|
} |
|
else |
|
{ |
|
ESP_LOGI(LOG_TAG, "added ota controll message to queue"); |
|
} |
|
} |
|
|
|
|
|
void vChangeStateOfServerWorker(bool bState) //allow access via function ptn to networl_handler |
|
{ |
|
static bool bLastState = false; |
|
|
|
if(bState != bLastState) //change only if necessary |
|
{ |
|
ESP_LOGI(LOG_TAG, "server worker change handler"); |
|
|
|
if(bState == true) |
|
{ |
|
if (xSemaphoreGive(bsStartStopServerWorker) != pdTRUE) |
|
{ |
|
ESP_LOGE(LOG_TAG, "Unable to give mutex to activate the server worker"); |
|
} |
|
} |
|
else |
|
{ |
|
if (xSemaphoreTake(bsStartStopServerWorker,( TickType_t ) 10 ) != pdTRUE) |
|
{ |
|
ESP_LOGE(LOG_TAG, "Unable to obtain mutex to deactivate the server worker"); |
|
} |
|
} |
|
bLastState = bState; |
|
} |
|
} |
|
|
|
void vTaskOTAWorker(void *arg) |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
void vTaskServerWorker(void *arg) |
|
{ |
|
|
|
esp_err_t err = ESP_OK; |
|
uint32_t u32BufferLenght = 1024U; |
|
char buffer[1024U]; |
|
uint32_t u32BytesRead = 0; |
|
char pcRemoteVersionNumber[12]; |
|
const esp_partition_t * currentPartition; |
|
const esp_partition_t * otaPartition; |
|
static esp_ota_handle_t otaHandle; |
|
uint32_t u32StartOffset; |
|
esp_app_desc_t otaPartitionDesc; |
|
|
|
while(true) |
|
{ |
|
xSemaphoreTake(bsStartStopServerWorker, portMAX_DELAY); //wait for binary semaphore that allows to start the worker |
|
xSemaphoreGive(bsStartStopServerWorker); //free binary semaphore, this allows the handler to change is to taken |
|
|
|
if (esp_mesh_is_root()) //check again that this node is the root node |
|
{ |
|
ESP_LOGI(LOG_TAG, "DEMO HTTP request"); |
|
|
|
//server get version |
|
|
|
currentPartition = esp_ota_get_boot_partition(); |
|
ESP_LOGI(LOG_TAG, "Type: %d", (*currentPartition).subtype); |
|
ESP_LOGI(LOG_TAG, "Start address: %d", (*currentPartition).address); |
|
ESP_LOGI(LOG_TAG, "Size: %d", (*currentPartition).size); |
|
ESP_LOGI(LOG_TAG, "Encrypted: %d", (*currentPartition).encrypted); |
|
|
|
esp_app_desc_t curPartitionDesc; |
|
err = esp_ota_get_partition_description(currentPartition, &curPartitionDesc); |
|
ESP_ERROR_CHECK(err); |
|
ESP_LOGI(LOG_TAG, "currentPartition project_name: %s", (curPartitionDesc).project_name); |
|
ESP_LOGI(LOG_TAG, "currentPartition version: %s", (curPartitionDesc).version); |
|
ESP_LOGI(LOG_TAG, "currentPartition Timestamp: %s %s", (curPartitionDesc).date, (curPartitionDesc).time); |
|
|
|
https_clientInitialize(); |
|
https_clientRetrieveData(buffer, &u32BufferLenght, &u32BytesRead); |
|
ESP_LOGI(LOG_TAG, "Data received: %i", u32BytesRead); |
|
err = errExtractVersionNumber(buffer, &u32BytesRead, pcRemoteVersionNumber); |
|
|
|
if(err == ESP_OK) |
|
{ |
|
if(bNewerVersion((curPartitionDesc).version, pcRemoteVersionNumber)) |
|
{ |
|
ESP_LOGI(LOG_TAG, "Newer Version available"); |
|
//write ota |
|
otaPartition= esp_ota_get_next_update_partition(currentPartition); |
|
|
|
err = errFindImageStart(buffer, &u32BufferLenght, &u32StartOffset); |
|
|
|
ESP_LOGI(LOG_TAG, "first byte offset: %i", u32StartOffset); |
|
ESP_LOGI(LOG_TAG, "first byte: %x", buffer[u32StartOffset]); |
|
|
|
err = esp_ota_begin(otaPartition, OTA_SIZE_UNKNOWN, &otaHandle); |
|
ESP_ERROR_CHECK(err); |
|
|
|
do |
|
{ |
|
ESP_LOGI(LOG_TAG, "OTA-Data written: %i", u32BytesRead); |
|
err = esp_ota_write(otaHandle, (const void*) buffer+u32StartOffset, (u32BytesRead-u32StartOffset)); |
|
u32StartOffset = 0U; |
|
https_clientRetrieveData(buffer, &u32BufferLenght, &u32BytesRead); |
|
} |
|
while (u32BytesRead > 0); |
|
|
|
err = esp_ota_end(otaHandle); |
|
ESP_ERROR_CHECK(err); |
|
|
|
err = esp_ota_get_partition_description(otaPartition, &otaPartitionDesc); |
|
ESP_ERROR_CHECK(err); |
|
ESP_LOGI(LOG_TAG, "otaPartition project_name: %s", (otaPartitionDesc).project_name); |
|
err = esp_ota_set_boot_partition(otaPartition); |
|
ESP_ERROR_CHECK(err); |
|
//esp_restart(); |
|
} |
|
else |
|
{ |
|
ESP_LOGI(LOG_TAG, "NO newer Version available"); |
|
} |
|
} |
|
else |
|
{ |
|
ESP_LOGI(LOG_TAG, "errExtractVersionNumber failed: %i", err); |
|
} |
|
|
|
https_clientDeinitialize(); |
|
|
|
//ota update if newer |
|
|
|
//lock ota mutex |
|
|
|
vTaskDelay( (SERVER_CHECK_INTERVAL*1000) / portTICK_PERIOD_MS); //sleep till next server check |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* 999.999.999 |
|
* Return true if remote version is newer (higher) than local version |
|
*/ |
|
bool bNewerVersion(const char* pu8Local, const char* pu8Remote) |
|
{ |
|
char u8LocalTmp[12]; //local version |
|
char u8RemoteTmp[12]; //remote version |
|
char* pu8saveptrLocal; //context for strok_r |
|
char* pu8saveptrRemote; //context for strok_r |
|
bool bReturn = false; //flag to stop loop |
|
uint8_t u8Index = 0; //numbers counter in version string |
|
|
|
strcpy(u8LocalTmp, pu8Local); //copy in tmp |
|
strcpy(u8RemoteTmp, pu8Remote); //copy in tmp |
|
|
|
char* pu8TokenLocal = strtok_r(u8LocalTmp, ".", &pu8saveptrLocal); //split tokens |
|
char* pu8TokenRemote = strtok_r(u8RemoteTmp, ".", &pu8saveptrRemote); //split tokens |
|
|
|
while( (u8Index <= 2) && (bReturn == false)) //loop through tokens |
|
{ |
|
u8Index++; |
|
if(atoi(pu8TokenLocal) < atoi(pu8TokenRemote)) |
|
{ |
|
bReturn = true; //version number difference --> stop loop |
|
} |
|
pu8TokenLocal = strtok_r(NULL, ".", &pu8saveptrLocal); //split tokens |
|
pu8TokenRemote = strtok_r(NULL, ".", &pu8saveptrRemote); //split tokens |
|
} |
|
return bReturn; |
|
} |
|
|
|
esp_err_t errFindImageStart(const char* pu8Data, uint32_t* pu32DataLenght, uint32_t* pu32StartOffset) |
|
{ |
|
/* |
|
Offset value |
|
0 = 0xE9 (first byte in image --> magic byte) |
|
48 = first digit of version number |
|
*/ |
|
|
|
esp_err_t errReturn = ESP_OK; |
|
bool bImageStartOffsetFound = false; |
|
uint32_t u32DataIndex = 0; |
|
uint32_t u32FirstDotOffset = 0; |
|
uint32_t u32SecondDotOffset = 0; |
|
uint8_t u8FirstDotIndex = 0; |
|
uint8_t u8SecondDotIndex = 0; |
|
|
|
*pu32StartOffset = 0U; //reset offset to zero |
|
|
|
while((u32DataIndex < *pu32DataLenght) && (bImageStartOffsetFound == false)) |
|
{ |
|
//search for magic byte |
|
if(pu8Data[u32DataIndex] == 0xe9) |
|
{ |
|
//magic byte found |
|
while ((u8FirstDotIndex < 3) && (u32FirstDotOffset == 0)) |
|
{ |
|
//search first dot in version number |
|
if((u32DataIndex+49+u8FirstDotIndex) < *pu32DataLenght) |
|
{ |
|
if((pu8Data[(u32DataIndex+49+u8FirstDotIndex)] == 0x2e)) |
|
{ |
|
//first dot found |
|
u32FirstDotOffset = (u32DataIndex+49+u8FirstDotIndex); |
|
} |
|
} |
|
u8FirstDotIndex++; |
|
} |
|
|
|
while ((u8SecondDotIndex < 3) && (u32SecondDotOffset == 0) && (u32FirstDotOffset != 0)) |
|
{ |
|
//search first dot in version number |
|
if((u32FirstDotOffset+(u8SecondDotIndex+2)) < *pu32DataLenght) |
|
{ |
|
if((pu8Data[(u32FirstDotOffset+(u8SecondDotIndex+2))] == 0x2e)) |
|
{ |
|
//second dot found |
|
u32SecondDotOffset = (u32FirstDotOffset+(u8SecondDotIndex+2)); |
|
} |
|
} |
|
u8SecondDotIndex++; |
|
} |
|
|
|
if((u32FirstDotOffset != 0) && (u32SecondDotOffset != 0)) |
|
{ |
|
//image start found based on magic byte and version number systax |
|
*pu32StartOffset = u32DataIndex; //store image start offset |
|
bImageStartOffsetFound = true; |
|
} |
|
else |
|
{ |
|
// this is propably not the magic byte --> reset |
|
u32FirstDotOffset = 0; |
|
u32SecondDotOffset = 0; |
|
u8FirstDotIndex = 0; |
|
u8SecondDotIndex = 0; |
|
} |
|
} |
|
u32DataIndex++; |
|
} |
|
|
|
if(bImageStartOffsetFound == false) |
|
{ |
|
errReturn = ESP_ERR_NOT_FOUND; |
|
} |
|
|
|
return errReturn; |
|
} |
|
|
|
esp_err_t errExtractVersionNumber(const char* pu8Data, uint32_t* pu32DataLenght, char* pc8RemoteVersionNumber) |
|
{ |
|
uint32_t u32StartOffset; |
|
esp_err_t err = ESP_OK; |
|
|
|
strcpy(pc8RemoteVersionNumber, "999.999.999"); //init value |
|
err = errFindImageStart(pu8Data, pu32DataLenght, &u32StartOffset); //get image start offset |
|
|
|
if(err == ESP_OK) |
|
{ |
|
//image found |
|
strncpy(pc8RemoteVersionNumber, pu8Data+(u32StartOffset+48), 11); //copy version number |
|
pc8RemoteVersionNumber[12] = '\0'; |
|
} |
|
return err; |
|
} |
|
|
|
/* |
|
esp_err_t esp_mesh_ota_send(mesh_addr_t* dest) |
|
{ |
|
esp_err_t err = ESP_OK; |
|
|
|
static uint32_t u32index; |
|
|
|
const esp_partition_t * currentPartition = esp_ota_get_boot_partition(); |
|
|
|
if((*currentPartition).subtype == 0) |
|
{ |
|
|
|
int data_read = 0; |
|
|
|
struct ota_mesh_packet packet; |
|
packet.type=OTA_Data; |
|
|
|
if(u32index == 1024) |
|
{ |
|
//all data read |
|
data_read = 0; |
|
u32index = 0; |
|
} |
|
else |
|
{ |
|
ESP_LOGI(MESH_TAG, "OTA-Data read: %i", u32index); |
|
err = esp_partition_read(currentPartition, (1024*u32index), packet.au8Payload, 1024 ); |
|
ESP_ERROR_CHECK(err); |
|
data_read = 1024; |
|
u32index++; |
|
} |
|
|
|
if (data_read > 0) |
|
{ |
|
//send ota fragemnt to node |
|
esp_mesh_send_packet(dest, &packet); |
|
} |
|
|
|
ESP_ERROR_CHECK(err); |
|
} |
|
else |
|
{ |
|
ESP_LOGI(MESH_TAG, "Subtype: %d", (*currentPartition).subtype); |
|
} |
|
return err; |
|
} |
|
|
|
esp_err_t esp_mesh_ota_receive(mesh_addr_t* dest, struct ota_mesh_packet* packet) |
|
{ |
|
esp_err_t err = ESP_OK; |
|
static esp_ota_handle_t otaHandle; |
|
static uint32_t u32index; |
|
|
|
const esp_partition_t * currentPartition = esp_ota_get_boot_partition(); |
|
const esp_partition_t * otaPartition = esp_ota_get_next_update_partition(currentPartition); |
|
|
|
if(u32index == 0) |
|
{ |
|
//first run |
|
|
|
err = esp_ota_begin(otaPartition, OTA_SIZE_UNKNOWN, &otaHandle); |
|
ESP_ERROR_CHECK(err); |
|
} |
|
|
|
ESP_LOGI(MESH_TAG, "OTA-Data write: %i", u32index); |
|
err = esp_ota_write(otaHandle, packet->au8Payload, 1024); |
|
|
|
if(err != ESP_OK) |
|
{ |
|
ESP_LOGE(MESH_TAG, "OTA-Data write error: %i at %i", err, u32index); |
|
} |
|
|
|
ESP_ERROR_CHECK(err); |
|
|
|
|
|
|
|
if(u32index >= 1023) |
|
{ |
|
//ota update complete |
|
|
|
ESP_LOGI(MESH_TAG, "OTA-Data complete arrived: %i", u32index); |
|
err = esp_ota_end(otaHandle); |
|
ESP_ERROR_CHECK(err); |
|
esp_app_desc_t otaPartitionDesc; |
|
err = esp_ota_get_partition_description(otaPartition, &otaPartitionDesc); |
|
ESP_ERROR_CHECK(err); |
|
ESP_LOGI(MESH_TAG, "otaPartition project_name: %s", (otaPartitionDesc).project_name); |
|
|
|
err = esp_ota_set_boot_partition(otaPartition); |
|
ESP_ERROR_CHECK(err); |
|
|
|
struct ota_mesh_packet retPacket; |
|
retPacket.type=OTA_Complete; |
|
ESP_ERROR_CHECK (esp_mesh_send_packet(dest, &retPacket)); //send back to parent |
|
|
|
//check if this node has children --> Update them |
|
|
|
esp_restart(); |
|
} |
|
|
|
u32index++; |
|
|
|
|
|
return err; |
|
} |
|
*/ |