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.

467 lines
17 KiB

/**
* @file Mesh_OTA_Util.c
* @brief Utility and helper functions to perfrom mesh OTA updates
* @author Hendrik Schutter
* @date 21.01.2021
*/
#include "Mesh_OTA_Util.h"
#include "Mesh_OTA_Globals.h"
static const char *LOG_TAG = "mesh_ota";
/**
* @fn bool bMeshOtaUtilNewerVersion(const char* cpu8Local, const char* cpu8Remote)
* @brief compares to version strings
* @param cpu8Local local image version string
* @param cpu8Remote remote image version string
* @return bool
* @author Hendrik Schutter
* @date 21.01.2021
*
* Returns true if remote is newer
*/
bool bMeshOtaUtilNewerVersion(const char* cpu8Local, const char* cpu8Remote)
{
char u8LocalTmp[12]; //local version
char u8RemoteTmp[12]; //remote version
char* pu8saveptrLocal = NULL; //context for strok_r
char* pu8saveptrRemote = NULL; //context for strok_r
bool bReturn = false; //flag to stop loop
uint8_t u8Index = 0; //numbers counter in version string
strncpy(u8LocalTmp, cpu8Local, 12); //copy in tmp
strncpy(u8RemoteTmp, cpu8Remote, 12); //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;
}
/**
* @fn esp_err_t errMeshOtaUtilExtractVersionNumber(const char* cpu8Data, uint32_t* const cpcu32DataLenght, char* const pc8RemoteVersionNumber)
* @brief extract version number from image data
* @param cpu8Data image data buffer
* @param cpcu32DataLenght pointer to lenght of image data
* @param pc8RemoteVersionNumber pointer version number
* @return ESP32 error code
* @author Hendrik Schutters
* @date 21.01.2021
*
* Search version number in raw image data
*/
esp_err_t errMeshOtaUtilExtractVersionNumber(const char* cpu8Data, uint32_t* const cpcu32DataLenght, char* const pc8RemoteVersionNumber)
{
uint32_t u32StartOffset;
esp_err_t err = ESP_OK;
strcpy(pc8RemoteVersionNumber, "999.999.999"); //init value
err = errMeshOtaUtilFindImageStart(cpu8Data, cpcu32DataLenght, &u32StartOffset); //get image start offset
if(err == ESP_OK)
{
//image found
strncpy(pc8RemoteVersionNumber, cpu8Data+(u32StartOffset+48), 11); //copy version number
pc8RemoteVersionNumber[12] = '\0';
}
return err;
}
/**
* @fn esp_err_t errMeshOtaUtilFindImageStart(const char* const cpu8Data, const uint32_t* const cpcu32DataLenght, uint32_t* const cpu32StartOffset)
* @brief find start offset from image raw data
* @param cpu8Data image data buffer
* @param cpcu32DataLenght pointer to lenght of image data
* @param cpu32StartOffset pointer to determined offset
* @return ESP32 error code
* @author Hendrik Schutter
* @date 21.01.2021
*
* Search offset in raw image data from server (exclude HTTP response)
*/
esp_err_t errMeshOtaUtilFindImageStart(const char* const cpu8Data, const uint32_t* const cpcu32DataLenght, uint32_t* const cpu32StartOffset)
{
// 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;
*cpu32StartOffset = 0U; //reset offset to zero
while((u32DataIndex < *cpcu32DataLenght) && (bImageStartOffsetFound == false))
{
//search for magic byte
if(cpu8Data[u32DataIndex] == 0xe9)
{
//magic byte found
while ((u8FirstDotIndex < 3) && (u32FirstDotOffset == 0))
{
//search first dot in version number
if((u32DataIndex+49+u8FirstDotIndex) < *cpcu32DataLenght)
{
if((cpu8Data[(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)) < *cpcu32DataLenght)
{
if((cpu8Data[(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
*cpu32StartOffset = 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;
}
/**
* @fn esp_err_t errMeshOtaUtilSendOtaVersionRequest(const mesh_addr_t* const cpcMeshReceiverAddr)
* @brief send OTA_Version_Request to node
* @param cpcMeshReceiverAddr node addr
* @return ESP32 error code
* @author Hendrik Schutter
* @date 21.01.2021
*/
esp_err_t errMeshOtaUtilSendOtaVersionRequest(const mesh_addr_t* const cpcMeshReceiverAddr)
{
esp_err_t err = ESP_OK;
MESH_PACKET_t packet;
packet.type = OTA_Version_Request;
const esp_partition_t* pBootPartition = NULL; //pointer to boot partition (that will booted after reset)
esp_app_desc_t bootPartitionDesc; //Metadata from boot partition
pBootPartition = esp_ota_get_boot_partition(); //get boot partition (that will booted after reset), not the running partition
ERROR_CHECK(esp_ota_get_partition_description(pBootPartition, &bootPartitionDesc)); //get metadate of partition
memcpy(&packet.au8Payload, &bootPartitionDesc.version, 12); //copy local version to OTA_Version_Request packet
err = errMeshNetworkSendMeshPacket(cpcMeshReceiverAddr, &packet);
return err;
}
/**
* @fn esp_err_t errMeshOtaUtilSendOtaVersionResponse(const mesh_addr_t* const cpcMeshReceiverAddr)
* @brief send OTA_Version_Response to node
* @param cpcMeshReceiverAddr node addr
* @return ESP32 error code
* @author Hendrik Schutter
* @date 21.01.2021
*/
esp_err_t errMeshOtaUtilSendOtaVersionResponse(const mesh_addr_t* const cpcMeshReceiverAddr)
{
esp_err_t err = ESP_OK;
MESH_PACKET_t packet;
packet.type = OTA_Version_Response;
const esp_partition_t* pBootPartition = NULL; //pointer to boot partition (that will booted after reset)
esp_app_desc_t bootPartitionDesc; //Metadata from boot partition
pBootPartition = esp_ota_get_boot_partition(); //get boot partition (that will booted after reset), not the running partition
ERROR_CHECK(esp_ota_get_partition_description(pBootPartition, &bootPartitionDesc)); //get metadate of partition
memcpy(&packet.au8Payload, &bootPartitionDesc.version, 12); //copy local version to OTA_Version_Response packet
ESP_LOGD(LOG_TAG, "Send OTA_Version_Response to 0x%x", cpcMeshReceiverAddr->addr[5]);
err = errMeshNetworkSendMeshPacket(cpcMeshReceiverAddr, &packet);
return err;
}
/**
* @fn void vMeshOtaUtilPrintOtaProgress(const uint32_t* const cpcu32TotalImageSize, const uint32_t* const cpcu32BytesWritten, const OTA_MESH_ROLE_t ceRole)
* @brief print LOG for OTA process progress
* @param cpcu32TotalImageSize size of OTA partition
* @param cpcu32BytesWritten actual bytes written
* @param ceRole role if this OTA process
* @return void
* @author Hendrik Schutter
* @date 21.01.2021
*/
void vMeshOtaUtilPrintOtaProgress(const uint32_t* const cpcu32TotalImageSize, const uint32_t* const cpcu32BytesWritten, const OTA_MESH_ROLE_t ceRole)
{
uint32_t u32Percentage = 0U;
static uint32_t u32LastPercentage = 0U;
if((*cpcu32BytesWritten) >= (*cpcu32TotalImageSize))
{
u32Percentage = 100;
}
else
{
u32Percentage = (uint32_t) (((float) (*cpcu32BytesWritten)/(float) (*cpcu32TotalImageSize)) * 100.0);
}
if((u32Percentage-u32LastPercentage) >= OTA_PROGRESS_LOG_INTERVAL)
{
if(ceRole == Transmitter)
{
ESP_LOGI(LOG_TAG, "Transmitting OTA update: %i %%", u32Percentage);
}
if(ceRole == Receiver)
{
ESP_LOGI(LOG_TAG, "Receiving OTA update: %i %%", u32Percentage);
}
u32LastPercentage = u32Percentage;
}
}
/**
* @fn void vMeshOtaUtilAddAllNeighboursToQueue(void)
* @brief add all neigbhours (children and parent) to queue, except node used in parameter
* @param const mesh_addr_t* const cpcMeshNodeAddr
* @return void
* @author Hendrik Schutter
* @date 21.01.2021
*/
void vMeshOtaUtilAddAllNeighboursToQueue(const mesh_addr_t* const cpcMeshNodeAddr)
{
esp_err_t err = ESP_OK;
mesh_addr_t addrParent; //addr of parent node
mesh_addr_t childrenAddr[CONFIG_MESH_ROUTE_TABLE_SIZE]; //array of children attached to this node
uint16_t u16ChildrenSize = 0U; //number of children attached to this node
err = errMeshNetworkGetParentNode(&addrParent);
if(err == ESP_OK)
{
if (cpcMeshNodeAddr != NULL)
{
if((bMeshNetworkCheckMacEquality(addrParent.addr, cpcMeshNodeAddr->addr)))
{
//same node --> don't add
}
else
{
vMeshOtaUtilAddNodeToPossibleUpdatableQueue(addrParent.addr);
}
}
else
{
vMeshOtaUtilAddNodeToPossibleUpdatableQueue(addrParent.addr);
}
}
err = ESP_OK; //reset error code
ERROR_CHECK(errMeshNetworkGetChildren(childrenAddr, &u16ChildrenSize)); //get all children
for (uint16_t u16Index = 0; ((u16Index < u16ChildrenSize) && (err == ESP_OK)); u16Index++)
{
if (cpcMeshNodeAddr != NULL)
{
if((bMeshNetworkCheckMacEquality(childrenAddr[u16Index].addr, cpcMeshNodeAddr->addr)))
{
//same node --> don't add
}
else
{
vMeshOtaUtilAddNodeToPossibleUpdatableQueue(childrenAddr[u16Index].addr);
}
}
else
{
vMeshOtaUtilAddNodeToPossibleUpdatableQueue(childrenAddr[u16Index].addr);
}
}
}
/**
* @fn void vMeshOtaUtilClearOtaMessageQueue(const mesh_addr_t* const cpcMeshNodeAddr)
* @brief remode all OTA messages from this node in queue
* @param cpcMeshNodeAddr node addr
* @return void
* @author Hendrik Schutter
* @date 21.01.2021
*/
void vMeshOtaUtilClearOtaMessageQueue(const mesh_addr_t* const cpcMeshNodeAddr)
{
MESH_PACKET_t sMeshPacket; //packet for sending and receiving
for (uint32_t u32Index = 0; (u32Index < QUEUE_MESSAGE_OTA_SIZE); u32Index++) //loop through all OTA messages
{
if (xQueueReceive(queueMessageOTA, &sMeshPacket, 0) == pdTRUE)
{
if(!(bMeshNetworkCheckMacEquality(sMeshPacket.meshSenderAddr.addr, cpcMeshNodeAddr->addr)))
{
//received OTA message is NOT from cpcMeshNodeAddr --> keep it in queue
vMeshOtaUtilAddOtaMessageToQueue(&sMeshPacket);
}
}
}//end OTA message loop
}
/**
* @fn void vMeshOtaUtilClearNeighboursQueue(const mesh_addr_t* const cpcMeshNodeAddr)
* @brief remode all instances of this node in queue
* @param cpcMeshNodeAddr node addr
* @return void
* @author Hendrik Schutter
* @date 21.01.2021
*/
void vMeshOtaUtilClearNeighboursQueue(const mesh_addr_t* const cpcMeshNodeAddr)
{
mesh_addr_t sNode; //packet for sending and receiving
for (uint32_t u32Index = 0; (u32Index < QUEUE_NODES_SIZE); u32Index++) //loop through all node queue
{
if (xQueueReceive(queueNodes, &sNode, 0) == pdTRUE)
{
if((bMeshNetworkCheckMacEquality(sNode.addr, cpcMeshNodeAddr->addr)))
{
//same node --> don't add again
}
else
{
//node is NOT cpcMeshNodeAddr --> keep it in queue
vMeshOtaUtilAddNodeToPossibleUpdatableQueue(sNode.addr);
}
}
}//end nodes
}
/**
* @fn void vMeshOtaUtilAddNodeToPossibleUpdatableQueue(const uint8_t* const cpcu8MAC)
* @brief add node instance to queue
* @param cpcu8MAC MAC addr of node
* @return void
* @author Hendrik Schutter
* @date 21.01.2021
*/
void vMeshOtaUtilAddNodeToPossibleUpdatableQueue(const uint8_t* const cpcu8MAC)
{
//send payload to node queues
mesh_addr_t addrNode;
memcpy(&addrNode.addr, (uint8_t *)cpcu8MAC, 6); //copy MAC
if (xQueueSend(queueNodes, &addrNode, portMAX_DELAY) != pdPASS)
{
ESP_LOGE(LOG_TAG, "Unable to push node into node queue");
}
else
{
ESP_LOGD(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]);
}
}
/**
* @fn void vMeshOtaUtilAddOtaMessageToQueue(const MESH_PACKET_t* const cpcuMeshPacket)
* @brief add OTA message to queue
* @param cpcuMeshPacket OTA message
* @return void
* @author Hendrik Schutter
* @date 21.01.2021
*/
void vMeshOtaUtilAddOtaMessageToQueue(const MESH_PACKET_t* const cpcuMeshPacket)
{
//send ota packet to packet queue
if (xQueueSend(queueMessageOTA, cpcuMeshPacket, portMAX_DELAY) != pdPASS)
{
ESP_LOGE(LOG_TAG, "Unable to push ota packet into packet queue");
}
else
{
switch (cpcuMeshPacket->type)
{
case OTA_Abort:
ESP_LOGD(LOG_TAG, "added ota message to queue: OTA_Abort from 0x%x", cpcuMeshPacket->meshSenderAddr.addr[5]);
break;
case OTA_Version_Request:
ESP_LOGD(LOG_TAG, "added ota message to queue: OTA_Version_Request from 0x%x", cpcuMeshPacket->meshSenderAddr.addr[5]);
break;
case OTA_Version_Response:
ESP_LOGD(LOG_TAG, "added ota message to queue: OTA_Version Response from 0x%x", cpcuMeshPacket->meshSenderAddr.addr[5]);
break;
default:
break;
}
}
}
/**
* @fn void vMeshOtaUtilChangeStateOfServerWorker(const bool cbState)
* @brief callback for mesh network if connectivity to server changed
* @param cbState boolean state
* @return void
* @author Hendrik Schutter
* @date 21.01.2021
*/
void vMeshOtaUtilChangeStateOfServerWorker(const bool cbState)
{
static bool bLastState = false;
if(cbState != bLastState) //change only if necessary
{
if(cbState == 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 = cbState;
}
}

Du besuchst diese Seite mit einem veralteten IPv4-Internetzugang. Möglicherweise treten in Zukunft Probleme mit der Erreichbarkeit und Performance auf. Bitte frage deinen Internetanbieter oder Netzwerkadministrator nach IPv6-Unterstützung.
You are visiting this site with an outdated IPv4 internet access. You may experience problems with accessibility and performance in the future. Please ask your ISP or network administrator for IPv6 support.
Weitere Infos | More Information
Klicke zum schließen | Click to close