Skip to content

Commit

Permalink
Add config migration v3 -> v4
Browse files Browse the repository at this point in the history
  • Loading branch information
Slider0007 committed Nov 10, 2024
1 parent 4f50307 commit 039c743
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 65 deletions.
3 changes: 1 addition & 2 deletions code/components/config_handling/cfgDataStruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,7 @@ struct GpioElement {
struct CfgData {
// Config File
struct SectionConfig {
int desiredConfigVersion = 3; // Set version in configMigration.cpp
int version = 3;
int version = 4; // NOTE: Increment when existing parameter name changed and add migration routine
std::string lastModified = "";
} sectionConfig;

Expand Down
39 changes: 29 additions & 10 deletions code/components/config_handling/configClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <nvs_flash.h>
#include <nvs.h>

#include "configMigration.h"
#include "webserver.h"
#include "MainFlowControl.h"
#include "psram.h"
Expand Down Expand Up @@ -112,6 +113,9 @@ ConfigClass::~ConfigClass()
}


//**************************************************************************************************
// Read configuration from file (JSON notation)
//**************************************************************************************************
void ConfigClass::readConfigFile(bool unityTest, std::string unityTestData)
{
std::stringstream streamBuffer;
Expand All @@ -123,22 +127,16 @@ void ConfigClass::readConfigFile(bool unityTest, std::string unityTestData)
else { // Read data from file
std::ifstream file(CONFIG_PERSISTENCE_FILE);

if (file.good() && file.is_open()) {
LogFile.writeToFile(ESP_LOG_INFO, TAG, "readConfigFile: Config file found");
if (file.is_open()) {
LogFile.writeToFile(ESP_LOG_INFO, TAG, "Config file found");
streamBuffer << file.rdbuf();
file.close();
}
else {
if (file.is_open()) {
file.close();
}
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "readConfigFile: Failed to open config file");
}
}

// Check for empty content -> either empty file or no / bad file
if (streamBuffer.rdbuf()->in_avail() == 0) {
LogFile.writeToFile(ESP_LOG_INFO, TAG, "readConfigFile: No persistent config found");
LogFile.writeToFile(ESP_LOG_INFO, TAG, "No persistent config found");
streamBuffer.str("{}"); // Ensure any content
}

Expand All @@ -154,6 +152,7 @@ void ConfigClass::readConfigFile(bool unityTest, std::string unityTestData)

// Parse content to cJSON object structure
cJsonObject = cJSON_Parse(streamBuffer.str().c_str());
streamBuffer.str(""); // Clear stream buffer

// Reset cJSON hooks to default
cJSON_InitHooks(NULL);
Expand All @@ -169,6 +168,9 @@ void ConfigClass::readConfigFile(bool unityTest, std::string unityTestData)
}


//**************************************************************************************************
// Update configuration via REST API (JSON notation)
//**************************************************************************************************
esp_err_t ConfigClass::setConfigRequest(httpd_req_t *req)
{
int remaining = req->content_len; // Content length of the request gives the size of the file being uploaded
Expand Down Expand Up @@ -225,6 +227,9 @@ esp_err_t ConfigClass::setConfigRequest(httpd_req_t *req)
}


//**************************************************************************************************
// Parse JSON string and save to internal struct
//**************************************************************************************************
esp_err_t ConfigClass::parseConfig(httpd_req_t *req, bool init, bool unityTest)
{
// Config Verison
Expand Down Expand Up @@ -1417,6 +1422,11 @@ esp_err_t ConfigClass::parseConfig(httpd_req_t *req, bool init, bool unityTest)
if (cJSON_IsNumber(objEl))
cfgDataTemp.sectionWebUi.AutoRefresh.dataGraphPage.refreshTime = std::max(objEl->valueint, 1);

// Check for configuration migration
if (!unityTest) {
migrateConfiguration(cJsonObject);
}

// Init active config struct with latest configuration data
if (init) {
cfgData = cfgDataTemp;
Expand All @@ -1442,6 +1452,9 @@ esp_err_t ConfigClass::parseConfig(httpd_req_t *req, bool init, bool unityTest)
}


//**************************************************************************************************
// Serialize internal struct to JSON string
//**************************************************************************************************
esp_err_t ConfigClass::serializeConfig(bool unityTest)
{
portENTER_CRITICAL(&mutex);
Expand Down Expand Up @@ -2062,6 +2075,9 @@ esp_err_t ConfigClass::serializeConfig(bool unityTest)
}


//**************************************************************************************************
// Retrieve actual configuration via REST API (JSON notation)
//**************************************************************************************************
esp_err_t ConfigClass::getConfigRequest(httpd_req_t *req)
{
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
Expand All @@ -2078,11 +2094,14 @@ esp_err_t ConfigClass::getConfigRequest(httpd_req_t *req)
}


//**************************************************************************************************
// Persist actual configuration to file (JSON string)
//**************************************************************************************************
esp_err_t ConfigClass::writeConfigFile()
{
std::ofstream file(CONFIG_PERSISTENCE_FILE);

if (!file.good() || !file.is_open()) {
if (!file.is_open()) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "writeConfigFile: Failed to write JSON file");
return ESP_FAIL;
}
Expand Down
11 changes: 4 additions & 7 deletions code/components/config_handling/configClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
* 1. Restore Config : readConfigFile() > parseConfig > serializeConfig > writeConfigFile
* 2. REST API Set : setConfigRequest() > parseConfig > serializeConfig > REST API Response
* 3. REST API Get : getConfigRequest() > serializeConfig > REST API Response
* 4. Cfg Migration : migrateConfiguration() > serializeConfig > writeConfigFile
* 4. Cfg Migration : readConfigFile() > parseConfig > migrateConfiguration() > serializeConfig > writeConfigFile
*/

class ConfigClass
{
private:
static ConfigClass cfgClass; // Config class init here instead of global variable + extern declaration
CfgData cfgDataTemp; // Keeps last parameter modifications, but not in use by process (gets promoted to active config by reinitConfig())
CfgData cfgData; // Keep active parameter configuration in use by process
CfgData cfgDataTemp; // Keeps last parameter modifications, but not in yet used by process (gets promoted to active config by reinitConfig())
CfgData cfgData; // Keep parameter configuration (in use by process)

portMUX_TYPE mutex = portMUX_INITIALIZER_UNLOCKED;
cJSON *cJsonObject = NULL;
Expand Down Expand Up @@ -61,7 +61,7 @@ class ConfigClass
esp_err_t setConfigRequest(httpd_req_t *req);

// Only for migration and internal parameter modification purpose
void initCfgTmp(void) { cfgDataTemp = {}; };
void initCfgTmp(void) { clearCfgDataTemp(); cfgDataTemp = {}; };
CfgData *cfgTmp(void) { return &cfgDataTemp; };
bool saveMigDataToNVS(std::string key, std::string value) { return saveDataToNVS(key, value); };

Expand All @@ -70,9 +70,6 @@ class ConfigClass
char *getJsonBuffer(void) { return jsonBuffer; };
};

bool loadDataFromNVS(std::string key, std::string &value);
bool saveDataToNVS(std::string key, std::string value);

void registerConfigFileUri(httpd_handle_t server);

#endif // CONFIGCLASS_H
91 changes: 50 additions & 41 deletions code/components/config_handling/configMigration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,58 +13,66 @@
static const char *TAG = "CFGMIGRATION";


void migrateConfiguration(void)
// ********************************************************************************
// Migrate config based on JSON notation
// Firmware version: v17.0 and newer, Config version 3 and newer
// ********************************************************************************
void migrateConfiguration(cJSON* cJsonObject)
{
// ********************************************************************************
// Legacy: Support config.ini / wlan.ini migration
// Firmware version: v15.0 - v16.x, Config version: 0 - 2
// ********************************************************************************
migrateConfigIni();


// ********************************************************************************
// Config based on JSON notation
// Firmware version: v17.0 and newer, Config version: 3 and newer
// ********************************************************************************
bool migrated = false;
ConfigClass::getInstance()->cfgTmp()->sectionConfig.desiredConfigVersion = 3; // Set to new version whenever to data structure was modified

// Process every version iteration beginning from actual version
// Version 3 and newer is handled in internal struct (peristant to config.json)
for (int configFileVersion = 3; configFileVersion < ConfigClass::getInstance()->get()->sectionConfig.desiredConfigVersion; configFileVersion++) {
//*************************************************************************************************
// Migrate from version 3 to version 4
// Description ....
//*************************************************************************************************
if (configFileVersion == 3) {
// Update config version
// ---------------------
ConfigClass::getInstance()->cfgTmp()->sectionConfig.version = configFileVersion + 1;
LogFile.writeToFile(ESP_LOG_WARN, TAG, "cfgData: Migrate v" + std::to_string(configFileVersion) +
" > v" + std::to_string(configFileVersion+1));
migrated = true;

// Update parameter
// ---------------------
// @TODO: Migration from 3 to 4
uint16_t migratedVersion = 0;

//*************************************************************************************************
// Migrate from version 3 to version 4
// Date: November 2024
// Description: Add camera model OV5460 and enhance zoom functionality
//*************************************************************************************************
if (ConfigClass::getInstance()->cfgTmp()->sectionConfig.version == 3) {
// Update config version
// ---------------------
migratedVersion = ConfigClass::getInstance()->cfgTmp()->sectionConfig.version;
ConfigClass::getInstance()->cfgTmp()->sectionConfig.version += 1;
ConfigClass::getInstance()->cfgTmp()->sectionConfig.lastModified = ""; // Reset last modified
LogFile.writeToFile(ESP_LOG_WARN, TAG, "cfgData: Migrate v" + std::to_string(migratedVersion) +
" > v" + std::to_string(ConfigClass::getInstance()->cfgTmp()->sectionConfig.version));

// Update parameter
// ---------------------
const cJSON *objEl = cJSON_GetObjectItem(cJSON_GetObjectItem(cJSON_GetObjectItem(cJsonObject, "takeimage"), "camera"), "zoommode");
if (cJSON_IsNumber(objEl)) {
if (objEl->valueint == 0) { // Disabled
ConfigClass::getInstance()->cfgTmp()->sectionTakeImage.camera.zoomFactor = 1000; // 1.0x
}
else if (objEl->valueint == 1) { // Crop (1600 x 1200 -> 640 x 480)
ConfigClass::getInstance()->cfgTmp()->sectionTakeImage.camera.zoomFactor = 2500; // 2.5x
}
else if (objEl->valueint == 2) { // Scale & Crop (800 x 600 -> 640 x 480)
ConfigClass::getInstance()->cfgTmp()->sectionTakeImage.camera.zoomFactor = 1250; // 1.25x
}
else {
ConfigClass::getInstance()->cfgTmp()->sectionTakeImage.camera.zoomFactor = 1000; // 1.0x
}
}
}

// Migration detected
if (migrated) {
deleteFile(CONFIG_PERSISTENCE_FILE_BACKUP);
// Backup config file
if (migratedVersion >= 3) {
std::string ConfigBackupFile = std::string(CONFIG_PERSISTENCE_FILE_BACKUP) + "_v" + std::to_string(migratedVersion);
deleteFile(ConfigBackupFile);

if (!renameFile(CONFIG_PERSISTENCE_FILE, CONFIG_PERSISTENCE_FILE_BACKUP)) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "Failed to create backup of config.json file");
if (!renameFile(CONFIG_PERSISTENCE_FILE, ConfigBackupFile)) {
LogFile.writeToFile(ESP_LOG_WARN, TAG, "Config migrated. Failed to create backup file: " + std::string(ConfigBackupFile));
return;
}

ConfigClass::getInstance()->persistConfig();
LogFile.writeToFile(ESP_LOG_INFO, TAG, "Config file migrated. Saved backup to " + std::string(CONFIG_PERSISTENCE_FILE_BACKUP));
LogFile.writeToFile(ESP_LOG_INFO, TAG, "Config migrated. Backup file: " + std::string(ConfigBackupFile));
}
}


// ********************************************************************************
// Migrate config based on legacy config.ini / wlan.ini
// Firmware version: v15.0 - v16.x, Config version: 0 - 2
// ********************************************************************************
void migrateConfigIni(void)
{
// No migration from config.ini needed
Expand Down Expand Up @@ -122,7 +130,8 @@ void migrateConfigIni(void)

//*************************************************************************************************
// Migrate from version 2 to version 3
// Migrate config.ini to internal struct which gets persistant to config.json
// Date: October 2024
// Description: Migrate config.ini to internal struct which gets persistant to config.json
//*************************************************************************************************
if (configFileVersion == 2) {
std::vector<std::string> splitted;
Expand Down
4 changes: 3 additions & 1 deletion code/components/config_handling/configMigration.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#include <vector>
#include <string>

#include <cJSON.h>

void migrateConfiguration(void);

void migrateConfiguration(cJSON *cJsonObject);
void migrateConfigIni(void);
void migrateWlanIni();
std::vector<std::string> splitString(std::string input, std::string delimiter = " = \t");
Expand Down
10 changes: 6 additions & 4 deletions code/main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ extern "C" void app_main(void)
// Correct creation of these folders will be checked with function "checkSdCardFolderFilePresence"
// ********************************************
makeDir("/sdcard/config"); // mandatory for config handling
makeDir("/sdcard/config/backup"); // mandatory for config migration
makeDir("/sdcard/config/certs"); // mandatory for TLS encryption
makeDir("/sdcard/config/models"); // mandatory for TFLite models
makeDir("/sdcard/firmware"); // mandatory for OTA firmware update
Expand All @@ -114,10 +115,11 @@ extern "C" void app_main(void)
#endif
checkOTAUpdate();

// Migrate parameter from older version to actual version
// Do migration task before first parameter usage
// ********************************************
migrateConfiguration();
// Configuration migration for legacy config.ini / wlan.ini
// Firmware version: v15.0 - v16.x, Config version: 0 - 2
// Note: Migration of v17.x and newer is handled while pasing JSON config (migrateConfiguration)
// ********************************************************************************
migrateConfigIni();

// Load persistent config from file (json notation)
ConfigClass::getInstance()->readConfigFile();
Expand Down

0 comments on commit 039c743

Please sign in to comment.