Files
edgetx-gx12/radio/src/storage/sdcard_yaml.cpp
T
Raphael CoefficandGitHub 6ea408230c fix(telemetry): set warning/critical to default values before reading YAML (#2643)
Otherwise, these values will be 0 if either the old or the new tags are not present, which is the case when coming from 2.7.
2022-10-31 10:38:15 +10:00

628 lines
17 KiB
C++

/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "opentx.h"
#include "opentx_helpers.h"
#include "storage.h"
#include "sdcard_common.h"
#include "sdcard_raw.h"
#include "sdcard_yaml.h"
#include "modelslist.h"
#include "yaml/yaml_tree_walker.h"
#include "yaml/yaml_parser.h"
#include "yaml/yaml_datastructs.h"
#include "yaml/yaml_bits.h"
#if defined(EEPROM_RLC)
#include "storage/eeprom_common.h"
#include "storage/eeprom_rlc.h"
#endif
#include "storage/conversions/conversions.h"
const char * readYamlFile(const char* fullpath, const YamlParserCalls* calls, void* parser_ctx, ChecksumResult* checksum_result)
{
FIL file;
UINT bytes_read;
UINT total_bytes = 0;
FRESULT result = f_open(&file, fullpath, FA_OPEN_EXISTING | FA_READ);
if (result != FR_OK) {
return SDCARD_ERROR(result);
}
YamlParser yp; //TODO: move to re-usable buffer
yp.init(calls, parser_ctx);
uint16_t calculated_checksum = 0xFFFF;
uint16_t file_checksum = 0;
bool first_block = true;
char buffer[32];
while (f_read(&file, buffer, sizeof(buffer)-1, &bytes_read) == FR_OK) {
if (bytes_read == 0) // EOF
break;
total_bytes += bytes_read;
uint16_t skip = 0;
if(first_block) {
// Get the 'checksum' value and skip from further YAML processing
// The checksum must be first in the first buffer read from file
first_block = false;
const char *skipValue = "checksum: ";
if(strncmp(buffer, skipValue, strlen(skipValue)) == 0) {
skip = 10;
char* startPos = buffer + strlen(skipValue);
char* endPos = startPos;
// Advance through the value
while((*endPos != '\r') && (*endPos != '\n')) {
if (endPos > buffer + bytes_read) {
return SDCARD_ERROR( FR_INT_ERR );
}
endPos++;
}
// Skip trailing newline
while((*endPos == '\r') || (*endPos == '\n')) {
*endPos = 0;
endPos++;
}
file_checksum = atoi(startPos);
skip = endPos - buffer;
}
}
// Calculate checksum on read block only if we are called with a pointer to write the resulting checksum
if (checksum_result != NULL) {
calculated_checksum = crc16(0, (const uint8_t *)buffer + skip, bytes_read - skip, calculated_checksum);
}
if (f_eof(&file)) yp.set_eof();
if (yp.parse(buffer + skip, bytes_read - skip) != YamlParser::CONTINUE_PARSING)
break;
}
f_close(&file);
if (checksum_result != NULL) {
// Special case to handle "old" files with no checksum field
// 25 was arbitrarily chosen as the minimum realistic file size
// - The issue is to allow old files to pass, while still detecting garbled files
if ( (file_checksum == 0) && (total_bytes > 25) ) {
*checksum_result = ChecksumResult::Success;
} else {
// Normal case - compare read and calculated checksum
if (calculated_checksum == file_checksum) {
*checksum_result = ChecksumResult::Success;
} else {
*checksum_result = ChecksumResult::Failed;
}
}
}
return NULL;
}
//
// SDCARD storage interface
//
static const char * attemptLoad(const char *filename, ChecksumResult* checksum_status)
{
YamlTreeWalker tree;
tree.reset(get_radiodata_nodes(), (uint8_t*)&g_eeGeneral);
return readYamlFile(filename, YamlTreeWalker::get_parser_calls(), &tree, checksum_status);
}
const char * loadRadioSettingsYaml()
{
// YAML reader
TRACE("YAML radio settings reader");
ChecksumResult checksum_status;
const char* p = attemptLoad(RADIO_SETTINGS_YAML_PATH, &checksum_status);
if((p != NULL) || (checksum_status != ChecksumResult::Success) ) {
// Read failed or checksum check failed
FRESULT result = FR_OK;
TRACE("radio settings: Reading failed");
if ( (p == NULL) && g_eeGeneral.manuallyEdited) {
// Read sussessfull, checksum failed, manuallyEdited set
TRACE("File has been manually edited - ignoring checksum mismatch");
g_eeGeneral.manuallyEdited = 0;
storageDirty(EE_GENERAL); // Trigger a save on sucessfull recovery
} else {
TRACE("File is corrupted, attempting alternative file");
f_unlink(RADIO_SETTINGS_ERRORFILE_YAML_PATH);
result = f_rename(RADIO_SETTINGS_YAML_PATH, RADIO_SETTINGS_ERRORFILE_YAML_PATH); // Save corrupted file for later analysis
p = attemptLoad(RADIO_SETTINGS_TMPFILE_YAML_PATH, &checksum_status);
if (p == NULL && (checksum_status == ChecksumResult::Success)) {
f_unlink(RADIO_SETTINGS_YAML_PATH);
result = f_rename(RADIO_SETTINGS_TMPFILE_YAML_PATH, RADIO_SETTINGS_YAML_PATH); // Rename previously saved file to active file
if (result != FR_OK) {
ALERT(STR_STORAGE_WARNING, TR_RADIO_DATA_UNRECOVERABLE, AU_BAD_RADIODATA);
return SDCARD_ERROR(result);
}
}
TRACE("Unable to recover radio data");
ALERT(STR_STORAGE_WARNING, p == NULL ? TR_RADIO_DATA_RECOVERED : TR_RADIO_DATA_UNRECOVERABLE, AU_BAD_RADIODATA);
}
}
return p;
}
const char * loadRadioSettings()
{
FILINFO fno;
if ( (f_stat(RADIO_SETTINGS_YAML_PATH, &fno) != FR_OK) && ((f_stat(RADIO_SETTINGS_TMPFILE_YAML_PATH, &fno) != FR_OK)) ) {
// If neither the radio configuraion YAML file or the temporary file generated on write exist, this must be a first run with YAML support.
// - thus requiring a conversion from binary to YAML.
#if STORAGE_CONVERSIONS < 221
#if defined(STORAGE_MODELSLIST)
uint8_t version;
const char* error = loadFileBin(RADIO_SETTINGS_PATH, nullptr, 0, &version);
if (error) {
TRACE("loadRadioSettings error=%s", error);
return error;
}
convertBinRadioData(RADIO_SETTINGS_PATH, version);
#elif defined(EEPROM_RLC)
// Load from EEPROM
uint8_t versions[3];
uint16_t* variant = (uint16_t*)&versions[1];
if (!eepromOpen() || (eeLoadGeneralSettingsData(versions, 3) != 3)
|| (*variant != EEPROM_VARIANT)) {
return "ERROR";
}
eeConvert(versions[0]);
#else
#error "Unsupported conversion format"
#endif
#else
return "no radio settings";
#endif
}
#if defined(DEFAULT_INTERNAL_MODULE)
g_eeGeneral.internalModule = DEFAULT_INTERNAL_MODULE;
#endif
const char* error = loadRadioSettingsYaml();
if (!error) {
g_eeGeneral.chkSum = evalChkSum();
}
postRadioSettingsLoad();
return error;
}
struct yaml_checksummer_ctx {
FRESULT result;
uint16_t checksum;
bool checksum_invalid;
};
static bool yaml_checksummer(void* opaque, const char* str, size_t len)
{
yaml_checksummer_ctx* ctx = (yaml_checksummer_ctx*)opaque;
ctx->checksum = crc16(0, (const uint8_t *) str, len, ctx->checksum);
return true;
}
bool YamlFileChecksum(const YamlNode* root_node, uint8_t* data, uint16_t* checksum)
{
YamlTreeWalker tree;
tree.reset(root_node, data);
yaml_checksummer_ctx ctx;
ctx.result = FR_OK;
ctx.checksum = 0xFFFF;
ctx.checksum_invalid = false;
if (!tree.generate(yaml_checksummer, &ctx)) {
if (ctx.result != FR_OK) {
ctx.checksum_invalid = true;
return false;
}
}
if(checksum != NULL) {
*checksum = ctx.checksum;
}
return true;
}
struct yaml_writer_ctx {
FIL* file;
FRESULT result;
};
static bool yaml_writer(void* opaque, const char* str, size_t len)
{
UINT bytes_written;
yaml_writer_ctx* ctx = (yaml_writer_ctx*)opaque;
#if defined(DEBUG_YAML)
TRACE_NOCRLF("%.*s",len,str);
#endif
ctx->result = f_write(ctx->file, str, len, &bytes_written);
return (ctx->result == FR_OK) && (bytes_written == len);
}
const char* writeFileYaml(const char* path, const YamlNode* root_node, uint8_t* data, uint16_t checksum)
{
FIL file;
FRESULT result = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
if (result != FR_OK) {
return SDCARD_ERROR(result);
}
YamlTreeWalker tree;
tree.reset(root_node, data);
yaml_writer_ctx ctx;
ctx.file = &file;
ctx.result = FR_OK;
// Try to add CRC
if (checksum != 0) {
if (!yaml_writer(&ctx, YAMLFILE_CHECKSUM_TAG_NAME, strlen(YAMLFILE_CHECKSUM_TAG_NAME))) return NULL;
if (!yaml_writer(&ctx, ": ", 2)) return SDCARD_ERROR(FR_INVALID_PARAMETER);
const char* p_out = NULL;
p_out = yaml_unsigned2str((int)checksum);
if (p_out && !yaml_writer(&ctx, p_out, strlen(p_out))) return SDCARD_ERROR(FR_INVALID_PARAMETER);
yaml_writer(&ctx, "\r\n", 2);
}
if (!tree.generate(yaml_writer, &ctx)) {
if (ctx.result != FR_OK) {
f_close(&file);
return SDCARD_ERROR(ctx.result);
}
}
f_close(&file);
return NULL;
}
const char * writeGeneralSettings()
{
TRACE("YAML radio settings writer");
uint16_t file_checksum = 0;
YamlFileChecksum(get_radiodata_nodes(), (uint8_t*)&g_eeGeneral, &file_checksum);
g_eeGeneral.manuallyEdited = false;
const char *p = writeFileYaml(RADIO_SETTINGS_TMPFILE_YAML_PATH, get_radiodata_nodes(),
(uint8_t*)&g_eeGeneral, file_checksum);
TRACE("generalSettings written with checksum %u", file_checksum);
if (p != NULL) {
return p;
}
f_unlink(RADIO_SETTINGS_YAML_PATH);
FRESULT result = f_rename(RADIO_SETTINGS_TMPFILE_YAML_PATH, RADIO_SETTINGS_YAML_PATH);
if(result != FR_OK)
return SDCARD_ERROR(result);
return nullptr;
}
const char * readModelYaml(const char * filename, uint8_t * buffer, uint32_t size, const char* pathName)
{
// YAML reader
TRACE("YAML model reader");
bool init_model = true;
const YamlNode* data_nodes = nullptr;
if (size == sizeof(g_model)) {
data_nodes = get_modeldata_nodes();
}
else if (size == sizeof(PartialModel)) {
data_nodes = get_partialmodel_nodes();
init_model = false;
}
else {
TRACE("cannot find YAML data nodes for object size (size=%d)", size);
return "YAML size error";
}
char path[256];
getModelPath(path, filename, pathName);
YamlTreeWalker tree;
tree.reset(data_nodes, buffer);
// wipe memory before reading YAML
memset(buffer,0,size);
if (init_model) {
auto md = reinterpret_cast<ModelData*>(buffer);
#if defined(FLIGHT_MODES) && defined(GVARS)
// reset GVars to default values
// Note: taken from opentx.cpp::modelDefault()
//TODO: new func in gvars
for (int p=1; p<MAX_FLIGHT_MODES; p++) {
for (int i=0; i<MAX_GVARS; i++) {
md->flightModeData[p].gvars[i] = GVAR_MAX+1;
}
}
#endif
// is that necessary ???
// md->swashR.collectiveWeight = 100;
// md->swashR.aileronWeight = 100;
// md->swashR.elevatorWeight = 100;
md->rfAlarms.warning = 45;
md->rfAlarms.critical = 42;
}
return readYamlFile(path, YamlTreeWalker::get_parser_calls(), &tree, NULL);
}
static const char _wrongExtentionError[] = "wrong file extension";
const char* readModel(const char* filename, uint8_t* buffer, uint32_t size, const char* pathName)
{
const char* ext = strrchr(filename, '.');
if (!ext || strncmp(ext, YAML_EXT, 4) != 0) {
return _wrongExtentionError;
}
return readModelYaml(filename, buffer, size, pathName);
}
const char * writeModelYaml(const char* filename)
{
TRACE("YAML model writer");
char path[256];
getModelPath(path, filename);
return writeFileYaml(path, get_modeldata_nodes(), (uint8_t*)&g_model,0 );
}
#if !defined(STORAGE_MODELSLIST)
// EEPROM slot simulation based on file names:
// - /MODELS/model[00-99].yml
void getModelNumberStr(uint8_t idx, char* model_idx)
{
memcpy(model_idx, MODEL_FILENAME_PREFIX, sizeof(MODEL_FILENAME_PREFIX));
model_idx[sizeof(MODEL_FILENAME_PREFIX)-1] = '0' + idx / 10;
model_idx[sizeof(MODEL_FILENAME_PREFIX)] = '0' + idx % 10;
model_idx[sizeof(MODEL_FILENAME_PREFIX)+1] = '\0';
}
#endif
const char * writeModel()
{
#if defined(STORAGE_MODELSLIST)
return writeModelYaml(g_eeGeneral.currModelFilename);
#else
char fname[MODELIDX_STRLEN + sizeof(YAML_EXT)];
getModelNumberStr(g_eeGeneral.currModel, fname);
strcat(fname, YAML_EXT);
return writeModelYaml(fname);
#endif
}
#if !defined(STORAGE_MODELSLIST)
void loadModelHeader(uint8_t id, ModelHeader* header)
{
PartialModel partial;
memclear(&partial, sizeof(PartialModel));
if (modelExists(id)) {
char fname[MODELIDX_STRLEN + sizeof(YAML_EXT)];
getModelNumberStr(id, fname);
strcat(fname, YAML_EXT);
readModelYaml(fname, reinterpret_cast<uint8_t*>(&partial), sizeof(partial));
memcpy(header, &partial, sizeof(ModelHeader));
}
}
const char * loadModel(uint8_t idx, bool alarms)
{
char fname[MODELIDX_STRLEN + sizeof(YAML_EXT)];
getModelNumberStr(idx, fname);
strcat(fname, YAML_EXT);
return loadModel(fname, alarms);
}
bool modelExists(uint8_t idx)
{
char model_idx[MODELIDX_STRLEN];
getModelNumberStr(idx, model_idx);
GET_FILENAME(fname, MODELS_PATH, model_idx, YAML_EXT);
FILINFO fno;
return f_stat(fname, &fno) == FR_OK;
}
bool copyModel(uint8_t dst, uint8_t src)
{
// TODO: overwrite possible?
char model_idx_src[MODELIDX_STRLEN];
char model_idx_dst[MODELIDX_STRLEN];
getModelNumberStr(src, model_idx_src);
getModelNumberStr(dst, model_idx_dst);
GET_FILENAME(fname_src, MODELS_PATH, model_idx_src, YAML_EXT);
GET_FILENAME(fname_dst, MODELS_PATH, model_idx_dst, YAML_EXT);
return sdCopyFile(fname_src, fname_dst);
}
static void swapModelHeaders(uint8_t id1, uint8_t id2)
{
char tmp[sizeof(g_model.header)];
memcpy(tmp, &modelHeaders[id1], sizeof(ModelHeader));
memcpy(&modelHeaders[id1], &modelHeaders[id2], sizeof(ModelHeader));
memcpy(&modelHeaders[id2], tmp, sizeof(ModelHeader));
}
void swapModels(uint8_t id1, uint8_t id2)
{
char model_idx_1[MODELIDX_STRLEN];
char model_idx_2[MODELIDX_STRLEN];
getModelNumberStr(id1, model_idx_1);
getModelNumberStr(id2, model_idx_2);
GET_FILENAME(fname1, MODELS_PATH, model_idx_1, YAML_EXT);
GET_FILENAME(fname1_tmp, MODELS_PATH, model_idx_1, ".tmp");
GET_FILENAME(fname2, MODELS_PATH, model_idx_2, YAML_EXT);
FILINFO fno;
if (f_stat(fname2,&fno) != FR_OK) {
if (f_stat(fname1,&fno) == FR_OK) {
if (f_rename(fname1, fname2) == FR_OK)
swapModelHeaders(id1,id2);
}
return;
}
if (f_stat(fname1,&fno) != FR_OK) {
f_rename(fname2, fname1);
return;
}
// just in case...
f_unlink(fname1_tmp);
if (f_rename(fname1, fname1_tmp) != FR_OK) {
TRACE("Error renaming 1");
return;
}
if (f_rename(fname2, fname1) != FR_OK) {
TRACE("Error renaming 2");
return;
}
if (f_rename(fname1_tmp, fname2) != FR_OK) {
TRACE("Error renaming 1 tmp");
return;
}
swapModelHeaders(id1,id2);
}
int8_t deleteModel(uint8_t idx)
{
char model_idx[MODELIDX_STRLEN];
getModelNumberStr(idx, model_idx);
GET_FILENAME(fname, MODELS_PATH, model_idx, YAML_EXT);
if (f_unlink(fname) != FR_OK) {
return -1;
}
modelHeaders[idx].name[0] = '\0';
return 0;
}
const char * backupModel(uint8_t idx)
{
char * buf = reusableBuffer.modelsel.mainname;
// check and create folder here
const char * error = sdCheckAndCreateDirectory(STR_BACKUP_PATH);
if (error) {
return error;
}
strncpy(buf, modelHeaders[idx].name, sizeof(g_model.header.name));
buf[sizeof(g_model.header.name)] = '\0';
int8_t i = sizeof(g_model.header.name)-1;
uint8_t len = 0;
while (i > 0) {
if (!len && buf[i])
len = i+1;
if (len) {
if (!buf[i])
buf[i] = '_';
}
i--;
}
if (len == 0) {
uint8_t num = idx + 1;
strcpy(buf, STR_MODEL);
buf[PSIZE(TR_MODEL)] = (char)((num / 10) + '0');
buf[PSIZE(TR_MODEL) + 1] = (char)((num % 10) + '0');
len = PSIZE(TR_MODEL) + 2;
}
#if defined(RTCLOCK)
char * tmp = strAppendDate(&buf[len]);
len = tmp - buf;
#endif
strcpy(&buf[len], STR_YAML_EXT);
#ifdef SIMU
TRACE("SD-card backup filename=%s", buf);
#endif
char model_idx[MODELIDX_STRLEN + sizeof(YAML_EXT)];
getModelNumberStr(idx, model_idx);
strcat(model_idx, STR_YAML_EXT);
return sdCopyFile(model_idx, STR_MODELS_PATH, buf, STR_BACKUP_PATH);
}
const char * restoreModel(uint8_t idx, char *model_name)
{
char * buf = reusableBuffer.modelsel.mainname;
strcpy(buf, model_name);
strcpy(&buf[strlen(buf)], STR_YAML_EXT);
char model_idx[MODELIDX_STRLEN + sizeof(YAML_EXT)];
getModelNumberStr(idx, model_idx);
strcat(model_idx, STR_YAML_EXT);
const char* error = sdCopyFile(buf, STR_BACKUP_PATH, model_idx, STR_MODELS_PATH);
if (!error) {
loadModelHeader(idx, &modelHeaders[idx]);
}
return error;
}
#endif
bool storageReadRadioSettings(bool checks)
{
if (!sdMounted()) sdInit();
return loadRadioSettingsYaml() == nullptr;
}