ESP32-Mesh-OTA/components/mesh_ota/Mesh_OTA.c

387 lines
17 KiB
C

/**
* @file Mesh_OTA.c
* @brief Start and implement OTA updates via HTTPS from server and other mesh nodes (bidirectional)
* @author Hendrik Schutter
* @date 21.01.2021
*/
#include "Mesh_OTA.h"
#include "Mesh_OTA_Util.h"
#include "Mesh_OTA_Globals.h"
#include "Mesh_OTA_Partition_Access.h"
static const char *LOG_TAG = "mesh_ota";
/**
* @fn esp_err_t errMeshOTAInitialize(void)
* @brief Starts Mesh OTA functionality
* @param void
* @return ESP32 error code
* @author Hendrik Schutter
* @date 21.01.2021
*
* Initialize queues and tasks
* Set callbacks
*/
esp_err_t errMeshOTAInitialize(void)
{
esp_err_t err = ESP_OK;
BaseType_t xReturned;
bWantReboot = false;
//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;
}
}
if(err == ESP_OK)
{
bsOTAProcess = xSemaphoreCreateBinary();
if( bsOTAProcess == NULL )
{
ESP_LOGE(LOG_TAG, "Unable to create mutex to grant access to OTA process");
err = ESP_FAIL;
}
}
if(err == ESP_OK)
{
xSemaphoreGive(bsOTAProcess); //unlock binary semaphore
if( bsOTAProcess == NULL )
{
ESP_LOGE(LOG_TAG, "Unable to unlock mutex to grant access to OTA process");
err = ESP_FAIL;
}
}
//register callbacks in network
ERROR_CHECK(errMeshNetworkSetChildConnectedHandle(vMeshOtaUtilAddNodeToPossibleUpdatableQueue));
ERROR_CHECK(errMeshNetworkSetOTAMessageHandleHandle(vMeshOtaUtilAddOtaMessageToQueue));
ERROR_CHECK(errMeshNetworkSetChangeStateOfServerWorkerHandle(vMeshOtaUtilChangeStateOfServerWorker));
if(err == ESP_OK)
{
pOTAPartition = esp_ota_get_next_update_partition(NULL); //get ota partition
if(pOTAPartition == NULL)
{
err = ESP_FAIL;
ESP_LOGE(LOG_TAG, "unable to get next ota partition");
}
}
if(err == ESP_OK)
{
xReturned = xTaskCreate(vMeshOtaTaskServerWorker, "vMeshOtaTaskServerWorker", 8192, NULL, 5, NULL);
if(xReturned != pdPASS)
{
ESP_LOGE(LOG_TAG, "Unable to create the server worker task");
err = ESP_FAIL;
}
}
if(err == ESP_OK)
{
xReturned = xTaskCreate(vMeshOtaTaskOTAWorker, "vMeshOtaTaskOTAWorker", 8192, NULL, 5, NULL);
if(xReturned != pdPASS)
{
ESP_LOGE(LOG_TAG, "Unable to create the OTA worker task");
err = ESP_FAIL;
}
}
return err;
}
/**
* @fn void vMeshOtaTaskServerWorker(void *arg)
* @brief Task for updating from server via HTTPS
* @param arg
* @return void
* @author Hendrik Schutter
* @date 21.01.2021
*/
void vMeshOtaTaskServerWorker(void *arg)
{
esp_err_t err = ESP_OK;
bool bNewOTAImage; //true if a new ota image was downloaded and validated
bool bFirstRun = true;
while(true)
{
err = ESP_OK;
bNewOTAImage = false;
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, "Checking firmware image on server");
if(bFirstRun == true)
{
//init on first run
ERROR_CHECK(errHTTPSClientInitialize());
bFirstRun = false;
}
ERROR_CHECK(errHTTPSClientConnectToServer());
ERROR_CHECK(errHTTPSClientValidateServer());
ERROR_CHECK(errHTTPSClientSendRequest());
ERROR_CHECK(errMeshOtaPartitionAccessHttps(&bNewOTAImage));
errHTTPSClientReset();
if(bNewOTAImage == true)
{
//set want reboot
ESP_LOGI(LOG_TAG, "Updated successfully via HTTPS, set pending reboot");
bWantReboot = true;
}
vTaskDelay( (SERVER_CHECK_INTERVAL*1000) / portTICK_PERIOD_MS); //sleep till next server checks
}
}
}
/**
* @fn void vMeshOtaTaskServerWorker(void *arg)
* @brief Task for updating from nodes in mesh network
* @param arg
* @return void
* @author Hendrik Schutter
* @date 21.01.2021
*/
void vMeshOtaTaskOTAWorker(void *arg)
{
esp_err_t err = ESP_OK;
bool bNewOTAImage; //true if a new ota image was downloaded and validated
mesh_addr_t meshNodeAddr; //node that should be checked for ota update
BaseType_t xReturned;
while(true)
{
err = ESP_OK;
bNewOTAImage = false;
if((uxQueueSpacesAvailable(queueNodes) - QUEUE_NODES_SIZE) == 0)
{
//nodes queue is empty
xReturned = xSemaphoreTake(bsOTAProcess, portMAX_DELAY); //wait for binary semaphore that allows to start the OTA process
if((xReturned == pdTRUE) && (bWantReboot == true) && (OTA_ALLOW_REBOOT == 1))
{
ESP_LOGE(LOG_TAG, "ESP32 Reboot ...");
vTaskDelay( (1000) / portTICK_PERIOD_MS);
esp_restart();
}
xSemaphoreGive(bsOTAProcess); //free binary semaphore, this allows other tasks to start the OTA process
ERROR_CHECK(errMeshOtaSlaveEndpoint(&bNewOTAImage, &meshNodeAddr));
}
else
{
//queue not empty
if (xQueueReceive(queueNodes, &meshNodeAddr, ((100) / portTICK_PERIOD_MS)) != pdTRUE)
{
ESP_LOGE(LOG_TAG, "Unable to receive OTA Messages from Queue");
err = ESP_FAIL;
}
ERROR_CHECK(errMeshOtaMasterEndpoint(&bNewOTAImage, &meshNodeAddr));
if (err != ESP_OK)
{
//OTA process faild --> add back to queue
vMeshOtaUtilAddNodeToPossibleUpdatableQueue(meshNodeAddr.addr);
}
else
{
vMeshOtaUtilClearNeighboursQueue(&meshNodeAddr); //remove this node from queue
}
}
if(bNewOTAImage == true)
{
//set want reboot
ESP_LOGI(LOG_TAG, "Updated successfully via Mesh, set pending reboot");
bWantReboot = true;
vMeshOtaUtilAddAllNeighboursToQueue(&meshNodeAddr); //add all existing neighbours to queue
}
vTaskDelay( (1000) / portTICK_PERIOD_MS);
}
}
/**
* @fn esp_err_t errMeshOtaSlaveEndpoint(bool* const cpbNewOTAImage)
* @brief Endpoint for OTA process that is called from remote node
* @param cpbNewOTAImage pointer to boolean to signal if a new image was successfully received
* @param cpcMeshNodeAddr pointer to mesh node that transmitted the update
* @return ESP32 error code
* @author Hendrik Schutter
* @date 21.01.2021
*
* Answers the OTA_Version_Request with OTA_Version_Response
* calls errMeshOtaPartitionAccessMeshReceive OR errMeshOtaPartitionAccessMeshTransmit based on version number
*/
esp_err_t errMeshOtaSlaveEndpoint(bool* const cpbNewOTAImage, mesh_addr_t* const cpcMeshNodeAddr)
{
esp_err_t err = ESP_OK;
MESH_PACKET_t sOTAMessage;
const esp_partition_t* cpBootPartition = NULL; //pointer to boot partition (that will booted after reset)
esp_app_desc_t bootPartitionDesc; //Metadate from boot partition
*cpbNewOTAImage = false; //set default false
//read OTAMessages queue
if(uxQueueSpacesAvailable(queueMessageOTA) < QUEUE_MESSAGE_OTA_SIZE)
{
//queue not empty
if (xQueueReceive(queueMessageOTA, &sOTAMessage, ((100) / portTICK_PERIOD_MS)) != pdTRUE)
{
err = ESP_FAIL;
}
if((err == ESP_OK) && (sOTAMessage.type == OTA_Version_Request)) //if OTA_Version_Request
{
xSemaphoreTake(bsOTAProcess, portMAX_DELAY); //wait for binary semaphore that allows to start the OTA process
memcpy(cpcMeshNodeAddr, &sOTAMessage.meshSenderAddr, sizeof(mesh_addr_t));
cpBootPartition = esp_ota_get_boot_partition(); //get boot partition (that will booted after reset), not the running partition
ERROR_CHECK(esp_ota_get_partition_description(cpBootPartition, &bootPartitionDesc)); //get metadata of partition
//send OTA_Version_Response to sender of OTA_Version_Request packet wirh version in payload
ERROR_CHECK(errMeshOtaUtilSendOtaVersionResponse(&sOTAMessage.meshSenderAddr));
if((bMeshOtaUtilNewerVersion((bootPartitionDesc).version, (char*) sOTAMessage.au8Payload)) && (err == ESP_OK)) //compare local and remote version
{
//remote newer as local
ESP_LOGI(LOG_TAG, "remote image on node is newer --> OTA update required from node \"%x:%x:%x:%x:%x:%x\"", sOTAMessage.meshSenderAddr.addr[0], sOTAMessage.meshSenderAddr.addr[1], sOTAMessage.meshSenderAddr.addr[2], sOTAMessage.meshSenderAddr.addr[3], sOTAMessage.meshSenderAddr.addr[4], sOTAMessage.meshSenderAddr.addr[5]);
// --> this version older --> start OTA_Rx --> set cpbNewOTAImage true
ERROR_CHECK(errMeshOtaPartitionAccessMeshReceive(cpbNewOTAImage, &sOTAMessage.meshSenderAddr));
}
if((bMeshOtaUtilNewerVersion((char*) sOTAMessage.au8Payload, (bootPartitionDesc).version)) && (err == ESP_OK)) //compare remote and local version
{
//local newer as remote
ESP_LOGI(LOG_TAG, "remote image on node is older --> OTA send required to node \"%x:%x:%x:%x:%x:%x\"", sOTAMessage.meshSenderAddr.addr[0], sOTAMessage.meshSenderAddr.addr[1], sOTAMessage.meshSenderAddr.addr[2], sOTAMessage.meshSenderAddr.addr[3], sOTAMessage.meshSenderAddr.addr[4], sOTAMessage.meshSenderAddr.addr[5]);
// --> this version newer --> start OTA_Tx
ERROR_CHECK(errMeshOtaPartitionAccessMeshTransmit(&sOTAMessage.meshSenderAddr));
}
xSemaphoreGive(bsOTAProcess); //free binary semaphore, this allows other tasks to start the OTA process
}
}
return err;
}
/**
* @fn esp_err_t errMeshOtaMasterEndpoint(bool* const cpbNewOTAImage, const mesh_addr_t* const cpcMeshNodeAddr)
* @brief Endpoint for OTA process that calls remote node
* @param cpbNewOTAImage pointer to boolean to signal if a new image was successfully received
* @param cpcMeshNodeAddr pointer to remote node addr
* @return ESP32 error code
* @author Hendrik Schutter
* @date 21.01.2021
*
* Sends the OTA_Version_Request to remote node
* calls errMeshOtaPartitionAccessMeshReceive OR errMeshOtaPartitionAccessMeshTransmit based on version number received
*/
esp_err_t errMeshOtaMasterEndpoint(bool* const cpbNewOTAImage, const mesh_addr_t* const cpcMeshNodeAddr)
{
esp_err_t err = ESP_OK;
MESH_PACKET_t sOTAMessage;
const esp_partition_t* cpBootPartition = NULL; //pointer to boot partition (that will booted after reset)
esp_app_desc_t bootPartitionDesc; //Metadata from boot partition
bool bNodeIsConnected = false;
bool bNodeIsResponding = false;
bool bSameVersion = false;
*cpbNewOTAImage = false; //set default false
if(bMeshNetworkIsNodeNeighbour(cpcMeshNodeAddr) == true) //check if node is still connected
{
bNodeIsConnected = true; //node is one of the neighbours
xSemaphoreTake(bsOTAProcess, portMAX_DELAY); //wait for binary semaphore that allows to start the OTA process
ERROR_CHECK(errMeshOtaUtilSendOtaVersionRequest(cpcMeshNodeAddr)); //send OTA_VERSION_REQUEST with local version in payload
for (uint32_t u32Index = 0; ((u32Index < QUEUE_MESSAGE_OTA_SIZE) && (bSameVersion == false)); u32Index++) //loop through all OTA messages
{
//if(uxQueueSpacesAvailable(queueMessageOTA) < QUEUE_MESSAGE_OTA_SIZE)
// {
//queue not empty
if (xQueueReceive(queueMessageOTA, &sOTAMessage, ((3000) / portTICK_PERIOD_MS)) != pdTRUE)
{
ESP_LOGE(LOG_TAG, "Unable to receive OTA Messages from queue");
err = ESP_FAIL;
}
if((err == ESP_OK) && (sOTAMessage.type == OTA_Version_Response) && (bMeshNetworkCheckMacEquality(sOTAMessage.meshSenderAddr.addr, cpcMeshNodeAddr->addr))) //if OTA_Version_Request
{
bSameVersion = true;
bNodeIsResponding = true;
cpBootPartition = esp_ota_get_boot_partition(); //get boot partition (that will booted after reset), not the running partition
ERROR_CHECK(esp_ota_get_partition_description(cpBootPartition, &bootPartitionDesc)); //get metadata of partition
if((bMeshOtaUtilNewerVersion((bootPartitionDesc).version, (char*) sOTAMessage.au8Payload)) && (err == ESP_OK)) //compare local and remote version
{
//remote newer as local
bSameVersion = false;
ESP_LOGI(LOG_TAG, "remote image on node is newer --> OTA update required from node \"%x:%x:%x:%x:%x:%x\"", sOTAMessage.meshSenderAddr.addr[0], sOTAMessage.meshSenderAddr.addr[1], sOTAMessage.meshSenderAddr.addr[2], sOTAMessage.meshSenderAddr.addr[3], sOTAMessage.meshSenderAddr.addr[4], sOTAMessage.meshSenderAddr.addr[5]);
// --> this version older --> start OTA_Rx --> set cpbNewOTAImage true
ERROR_CHECK(errMeshOtaPartitionAccessMeshReceive(cpbNewOTAImage, &sOTAMessage.meshSenderAddr));
u32Index = QUEUE_MESSAGE_OTA_SIZE;
}
if((bMeshOtaUtilNewerVersion((char*) sOTAMessage.au8Payload, (bootPartitionDesc).version)) && (err == ESP_OK)) //compare remote and local version
{
//local newer as remote
bSameVersion = false;
ESP_LOGI(LOG_TAG, "remote image on node is older --> OTA send required to node \"%x:%x:%x:%x:%x:%x\"", sOTAMessage.meshSenderAddr.addr[0], sOTAMessage.meshSenderAddr.addr[1], sOTAMessage.meshSenderAddr.addr[2], sOTAMessage.meshSenderAddr.addr[3], sOTAMessage.meshSenderAddr.addr[4], sOTAMessage.meshSenderAddr.addr[5]);
// --> this version newer --> start OTA_Tx
ERROR_CHECK(errMeshOtaPartitionAccessMeshTransmit(&sOTAMessage.meshSenderAddr));
u32Index = QUEUE_MESSAGE_OTA_SIZE;
}
}
else
{
// OTA Message queue is empty --> wait some time
vTaskDelay( (1000/QUEUE_MESSAGE_OTA_SIZE) / portTICK_PERIOD_MS);
}
}//end loop
xSemaphoreGive(bsOTAProcess); //free binary semaphore, this allows other tasks to start the OTA process
}
if((bNodeIsResponding == false) && (bNodeIsConnected == true))
{
//add node back to queue if connected and NOT responding
ESP_LOGI(LOG_TAG, "OTA-Master: connected and NOT responding --> add node back to queue ");
vMeshOtaUtilAddNodeToPossibleUpdatableQueue(cpcMeshNodeAddr->addr);
}
return err;
}