Files
edgetx-gx12/radio/src/storage/sdcard_yaml.cpp
T

478 lines
12 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 "modelslist.h"
#include "yaml/yaml_tree_walker.h"
#include "yaml/yaml_parser.h"
#include "yaml/yaml_datastructs.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)
{
FIL file;
UINT bytes_read;
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);
char buffer[32];
while (f_read(&file, buffer, sizeof(buffer), &bytes_read) == FR_OK) {
// reached EOF?
if (bytes_read == 0)
break;
if (yp.parse(buffer, bytes_read) != YamlParser::CONTINUE_PARSING)
break;
}
f_close(&file);
return NULL;
}
//
// Generic storage interface
//
#if defined(STORAGE_MODELSLIST)
void storageCreateModelsList()
{
modelslist.clear();
ModelsCategory* cat = modelslist.createCategory(DEFAULT_CATEGORY, false);
ModelCell* model = nullptr;
if (g_eeGeneral.currModelFilename[0] != '\0') {
model = modelslist.addModel(cat, g_eeGeneral.currModelFilename, false);
} else {
model = modelslist.addModel(cat, DEFAULT_MODEL_FILENAME, false);
}
model->setModelName(g_model.header.name);
modelslist.save();
}
#endif
//
// SDCARD storage interface
//
const char * loadRadioSettingsYaml()
{
// YAML reader
TRACE("YAML radio settings reader");
YamlTreeWalker tree;
tree.reset(get_radiodata_nodes(), (uint8_t*)&g_eeGeneral);
return readYamlFile(RADIO_SETTINGS_YAML_PATH, YamlTreeWalker::get_parser_calls(), &tree);
}
const char * loadRadioSettings()
{
FILINFO fno;
if (f_stat(RADIO_SETTINGS_YAML_PATH, &fno) != FR_OK) {
#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";
}
g_eeGeneral.version = versions[0];
g_eeGeneral.variant = *variant;
eeConvert();
#else
#error "Unsupported conversion format"
#endif
}
const char* error = loadRadioSettingsYaml();
if (!error) {
g_eeGeneral.chkSum = evalChkSum();
}
return error;
}
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;
TRACE_NOCRLF("%.*s",len,str);
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)
{
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;
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");
return writeFileYaml(RADIO_SETTINGS_YAML_PATH, get_radiodata_nodes(),
(uint8_t*)&g_eeGeneral);
}
const char * readModelYaml(const char * filename, uint8_t * buffer, uint32_t size, uint8_t * version)
{
// 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);
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;
}
*version = 255; // max version number
return readYamlFile(path, YamlTreeWalker::get_parser_calls(), &tree);
}
const char* readModel(const char* filename, uint8_t* buffer, uint32_t size,
uint8_t* version)
{
const char* ext = strrchr(filename, '.');
if (ext != nullptr) {
if (!strncmp(ext, YAML_EXT, 4)) {
return readModelYaml(filename, buffer, size, version);
}
#if !defined(EEPROM_RLC)
return readModelBin(filename, buffer, size, version);
#endif
}
return nullptr;
}
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);
}
#if !defined(STORAGE_MODELSLIST)
// EEPROM slot simulation based on file names:
// - /MODELS/model[00-99].yml
constexpr uint8_t MODELIDX_STRLEN = sizeof(MODEL_FILENAME_PREFIX "00");
static 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)) {
uint8_t dummy;
char fname[MODELIDX_STRLEN + sizeof(YAML_EXT)];
getModelNumberStr(id, fname);
strcat(fname, YAML_EXT);
readModelYaml(fname, reinterpret_cast<uint8_t*>(&partial), sizeof(partial), &dummy);
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