diff --git a/radio/src/CMakeLists.txt b/radio/src/CMakeLists.txt index e7a9301fa..bfca8706e 100644 --- a/radio/src/CMakeLists.txt +++ b/radio/src/CMakeLists.txt @@ -157,7 +157,7 @@ endforeach() set(SRC ${SRC} debug.cpp) if(${EEPROM} STREQUAL SDCARD) - set(SRC ${SRC} storage/storage_common.cpp storage/sdcard_raw.cpp) + set(SRC ${SRC} storage/storage_common.cpp storage/sdcard_raw.cpp storage/modelslist.cpp) elseif(${EEPROM} STREQUAL EEPROM_RLC) set(SRC ${SRC} storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_rlc.cpp) add_definitions(-DEEPROM -DEEPROM_RLC) diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index 66fecaf44..69d948e17 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -634,6 +634,46 @@ int cliTestMemorySpeed() return 0; } + +#include "storage/modelslist.h" +using std::list; + +int cliTestModelsList() +{ + ModelsList modList; + modList.load(); + + int count=0; + + serialPrint("Starting fetching RF data 100x..."); + uint32_t start = (uint32_t)CoGetOSTime(); + + const list& cats = modList.getCategories(); + while(1) { + for (list::const_iterator cat_it = cats.begin(); + cat_it != cats.end(); ++cat_it) { + + for (ModelsCategory::iterator mod_it = (*cat_it)->begin(); + mod_it != (*cat_it)->end(); mod_it++) { + + if (!(*mod_it)->fetchRfData()) { + serialPrint("Error while fetching RF data..."); + return 0; + } + + if (++count >= 100) + goto done; + } + } + } + + done: + uint32_t actualRuntime = (uint32_t)CoGetOSTime() - start; + serialPrint("Done fetching %ix RF data: %d ms", count, actualRuntime*2); + + return 0; +} + #endif // #if defined(COLORLCD) int cliTest(const char ** argv) @@ -651,6 +691,9 @@ int cliTest(const char ** argv) else if (!strcmp(argv[1], "memspd")) { return cliTestMemorySpeed(); } + else if (!strcmp(argv[1], "modelslist")) { + return cliTestModelsList(); + } #endif else { serialPrint("%s: Invalid argument \"%s\"", argv[0], argv[1]); diff --git a/radio/src/gui/128x64/model_setup.cpp b/radio/src/gui/128x64/model_setup.cpp index 426c35771..e24849270 100644 --- a/radio/src/gui/128x64/model_setup.cpp +++ b/radio/src/gui/128x64/model_setup.cpp @@ -258,6 +258,8 @@ void menuModelSetup(event_t event) static uint8_t selectedPxxPower = g_model.moduleData[EXTERNAL_MODULE].pxx.power; //TODO could go to the reusable struct #endif + int8_t old_editMode = s_editMode; + #if defined(PCBTARANIS) MENU_TAB({ HEADER_LINE_COLUMNS @@ -1109,9 +1111,14 @@ void menuModelSetup(event_t event) if (checkIncDec_Ret) { modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[moduleIdx]; } - } - if (editMode==0 && event==EVT_KEY_BREAK(KEY_ENTER)) { - checkModelIdUnique(g_eeGeneral.currModel, moduleIdx); + else if (event == EVT_KEY_LONG(KEY_ENTER)) { + killEvents(event); + uint8_t newVal = findNextUnusedModelId(g_eeGeneral.currModel, moduleIdx); + if (newVal != g_model.header.modelId[moduleIdx]) { + modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[moduleIdx] = newVal; + storageDirty(EE_MODEL); + } + } } } lcdDrawText(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); @@ -1507,6 +1514,30 @@ void menuModelSetup(event_t event) lcdDrawNumber(16+4*FW, 5*FH, TELEMETRY_RSSI(), BOLD); } #endif + + // some field just finished being edited + if (old_editMode > 0 && s_editMode == 0) { + switch(menuVerticalPosition) { +#if defined(PCBTARANIS) + case ITEM_MODEL_INTERNAL_MODULE_BIND: + if (menuHorizontalPosition == 0) + checkModelIdUnique(g_eeGeneral.currModel, INTERNAL_MODULE); + break; +#endif +#if defined(PCBSKY9X) + case ITEM_MODEL_EXTRA_MODULE_BIND: + if (menuHorizontalPosition == 0) + checkModelIdUnique(g_eeGeneral.currModel, EXTRA_MODULE); + break; +#endif +#if defined(CPUARM) + case ITEM_MODEL_EXTERNAL_MODULE_BIND: + if (menuHorizontalPosition == 0) + checkModelIdUnique(g_eeGeneral.currModel, EXTERNAL_MODULE); + break; +#endif + } + } } #if defined(CPUARM) diff --git a/radio/src/gui/212x64/model_setup.cpp b/radio/src/gui/212x64/model_setup.cpp index 2c1d8ff92..bf061fb0e 100644 --- a/radio/src/gui/212x64/model_setup.cpp +++ b/radio/src/gui/212x64/model_setup.cpp @@ -279,6 +279,7 @@ void menuModelSetup(event_t event) bool CURSOR_ON_CELL = (menuHorizontalPosition >= 0); static uint8_t selectedPxxPower = g_model.moduleData[EXTERNAL_MODULE].pxx.power; //TODO could go to the reusable struct + int8_t old_editMode = s_editMode; MENU_TAB({ 0, 0, TIMERS_ROWS, TOPLCD_ROWS 0, 1, 0, 0, LABEL(Throttle), 0, 0, 0, LABEL(PreflightCheck), 0, 0, SW_WARN_ITEMS(), POT_WARN_ITEMS(), NAVIGATION_LINE_BY_LINE|(NUM_STICKS+NUM_POTS+NUM_SLIDERS+NUM_ROTARY_ENCODERS-1), 0, @@ -969,9 +970,14 @@ void menuModelSetup(event_t event) if (checkIncDec_Ret) { modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[moduleIdx]; } - } - if (s_editMode==0 && event==EVT_KEY_BREAK(KEY_ENTER)) { - checkModelIdUnique(g_eeGeneral.currModel, moduleIdx); + else if (event == EVT_KEY_LONG(KEY_ENTER)) { + killEvents(event); + uint8_t newVal = findNextUnusedModelId(g_eeGeneral.currModel, moduleIdx); + if (newVal != g_model.header.modelId[moduleIdx]) { + modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[moduleIdx] = newVal; + storageDirty(EE_MODEL); + } + } } } lcdDrawText(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); @@ -1190,6 +1196,20 @@ void menuModelSetup(event_t event) lcdDrawNumber(16+4*FW, 5*FH, TELEMETRY_RSSI(), BOLD); } #endif + + if (old_editMode > 0 && s_editMode == 0) { + switch(menuVerticalPosition) { + case ITEM_MODEL_INTERNAL_MODULE_BIND: + if (menuHorizontalPosition == 0) + checkModelIdUnique(g_eeGeneral.currModel, INTERNAL_MODULE); + break; + + case ITEM_MODEL_EXTERNAL_MODULE_BIND: + if (menuHorizontalPosition == 0) + checkModelIdUnique(g_eeGeneral.currModel, EXTERNAL_MODULE); + break; + } + } } void menuModelFailsafe(event_t event) diff --git a/radio/src/gui/480x272/model_select.cpp b/radio/src/gui/480x272/model_select.cpp index 9998d32ac..4aa22b166 100644 --- a/radio/src/gui/480x272/model_select.cpp +++ b/radio/src/gui/480x272/model_select.cpp @@ -37,7 +37,6 @@ enum ModelDeleteMode { }; uint8_t selectMode, deleteMode; -ModelsList modelslist; ModelsCategory * currentCategory; int currentCategoryIndex; @@ -87,11 +86,12 @@ void setCurrentModel(unsigned int index) void setCurrentCategory(unsigned int index) { currentCategoryIndex = index; - std::list::iterator it = modelslist.categories.begin(); + const std::list& cats = modelslist.getCategories(); + std::list::const_iterator it = cats.begin(); std::advance(it, index); currentCategory = *it; categoriesVerticalPosition = index; - categoriesVerticalOffset = limit(categoriesVerticalPosition-4, categoriesVerticalOffset, min(categoriesVerticalPosition, max(0, modelslist.categories.size()-5))); + categoriesVerticalOffset = limit(categoriesVerticalPosition-4, categoriesVerticalOffset, min(categoriesVerticalPosition, max(0, cats.size()-5))); if (currentCategory->size() > 0) setCurrentModel(0); else @@ -226,6 +226,7 @@ void onModelSelectMenu(const char * result) storageFlushCurrentModel(); storageCheck(true); memcpy(g_eeGeneral.currModelFilename, currentModel->modelFilename, LEN_MODEL_FILENAME); + modelslist.setCurrentModel(currentModel); loadModel(g_eeGeneral.currModelFilename, false); storageDirty(EE_GENERAL); storageCheck(true); @@ -239,9 +240,11 @@ void onModelSelectMenu(const char * result) } else if (result == STR_CREATE_MODEL) { storageCheck(true); - currentModel = modelslist.currentModel = modelslist.addModel(currentCategory, createModel()); + modelslist.addModel(currentCategory, createModel()); selectMode = MODE_SELECT_MODEL; setCurrentModel(currentCategory->size() - 1); + modelslist.setCurrentModel(currentModel); + modelslist.onNewModelCreated(currentModel, &g_model); #if defined(LUA) chainMenu(menuModelWizard); #endif @@ -251,9 +254,9 @@ void onModelSelectMenu(const char * result) memcpy(duplicatedFilename, currentModel->modelFilename, sizeof(duplicatedFilename)); if (findNextFileIndex(duplicatedFilename, LEN_MODEL_FILENAME, MODELS_PATH)) { sdCopyFile(currentModel->modelFilename, MODELS_PATH, duplicatedFilename, MODELS_PATH); - modelslist.addModel(currentCategory, duplicatedFilename); - unsigned int index = currentCategory->size() - 1; - setCurrentModel(index); + ModelCell* dup_model = modelslist.addModel(currentCategory, duplicatedFilename); + dup_model->fetchRfData(); + setCurrentModel(currentCategory->size() - 1); } else { POPUP_WARNING("Invalid File"); @@ -264,7 +267,7 @@ void onModelSelectMenu(const char * result) } else if (result == STR_CREATE_CATEGORY) { currentCategory = modelslist.createCategory(); - setCurrentCategory(modelslist.categories.size() - 1); + setCurrentCategory(modelslist.getCategories().size() - 1); } else if (result == STR_RENAME_CATEGORY) { selectMode = MODE_RENAME_CATEGORY; @@ -291,8 +294,9 @@ void initModelsList() categoriesVerticalOffset = 0; bool found = false; int index = 0; - for (std::list::iterator it = modelslist.categories.begin(); it != modelslist.categories.end(); ++it, ++index) { - if (*it == modelslist.currentCategory) { + const std::list& cats = modelslist.getCategories(); + for (std::list::const_iterator it = cats.begin(); it != cats.end(); ++it, ++index) { + if (*it == modelslist.getCurrentCategory()) { setCurrentCategory(index); found = true; break; @@ -306,7 +310,7 @@ void initModelsList() found = false; index = 0; for (ModelsCategory::iterator it = currentCategory->begin(); it != currentCategory->end(); ++it, ++index) { - if (*it == modelslist.currentModel) { + if (*it == modelslist.getCurrentModel()) { setCurrentModel(index); found = true; break; @@ -339,6 +343,7 @@ bool menuModelSelect(event_t event) } } + const std::list& cats = modelslist.getCategories(); switch(event) { case 0: // no need to refresh the screen @@ -371,7 +376,7 @@ bool menuModelSelect(event_t event) #endif if (selectMode == MODE_SELECT_MODEL) { if (categoriesVerticalPosition == 0) - categoriesVerticalPosition = modelslist.categories.size() - 1; + categoriesVerticalPosition = cats.size() - 1; else categoriesVerticalPosition -= 1; setCurrentCategory(categoriesVerticalPosition); @@ -394,11 +399,11 @@ bool menuModelSelect(event_t event) #endif if (selectMode == MODE_SELECT_MODEL) { categoriesVerticalPosition += 1; - if (categoriesVerticalPosition >= modelslist.categories.size()) + if (categoriesVerticalPosition >= cats.size()) categoriesVerticalPosition = 0; setCurrentCategory(categoriesVerticalPosition); } - else if (selectMode == MODE_MOVE_MODEL && categoriesVerticalPosition < modelslist.categories.size()-1) { + else if (selectMode == MODE_MOVE_MODEL && categoriesVerticalPosition < cats.size()-1) { ModelsCategory * previous_category = currentCategory; ModelCell * model = currentModel; categoriesVerticalPosition += 1; @@ -411,7 +416,7 @@ bool menuModelSelect(event_t event) case EVT_KEY_LONG(KEY_ENTER): if (selectMode == MODE_SELECT_MODEL) { killEvents(event); - if (currentModel && currentModel != modelslist.currentModel) { + if (currentModel && currentModel != modelslist.getCurrentModel()) { POPUP_MENU_ADD_ITEM(STR_SELECT_MODEL); } POPUP_MENU_ADD_ITEM(STR_CREATE_MODEL); @@ -420,13 +425,13 @@ bool menuModelSelect(event_t event) POPUP_MENU_ADD_ITEM(STR_MOVE_MODEL); } // POPUP_MENU_ADD_SD_ITEM(STR_BACKUP_MODEL); - if (currentModel && currentModel != modelslist.currentModel) { + if (currentModel && currentModel != modelslist.getCurrentModel()) { POPUP_MENU_ADD_ITEM(STR_DELETE_MODEL); } // POPUP_MENU_ADD_ITEM(STR_RESTORE_MODEL); POPUP_MENU_ADD_ITEM(STR_CREATE_CATEGORY); POPUP_MENU_ADD_ITEM(STR_RENAME_CATEGORY); - if (modelslist.categories.size() > 1) { + if (cats.size() > 1) { POPUP_MENU_ADD_ITEM(STR_DELETE_CATEGORY); } POPUP_MENU_START(onModelSelectMenu); @@ -441,8 +446,8 @@ bool menuModelSelect(event_t event) // Categories int index = 0; coord_t y = 97; - drawVerticalScrollbar(CATEGORIES_WIDTH-1, y-1, 5*(FH+7)-5, categoriesVerticalOffset, modelslist.categories.size(), 5); - for (std::list::iterator it = modelslist.categories.begin(); it != modelslist.categories.end(); ++it, ++index) { + drawVerticalScrollbar(CATEGORIES_WIDTH-1, y-1, 5*(FH+7)-5, categoriesVerticalOffset, cats.size(), 5); + for (std::list::const_iterator it = cats.begin(); it != cats.end(); ++it, ++index) { if (index >= categoriesVerticalOffset && index < categoriesVerticalOffset+5) { if (index != categoriesVerticalOffset) { lcdDrawSolidHorizontalLine(1, y-4, CATEGORIES_WIDTH-10, LINE_COLOR); @@ -508,7 +513,7 @@ bool menuModelSelect(event_t event) uint32_t size = sdGetSize() / 100; lcdDrawNumber(22, LCD_H-FH-21, size, PREC1|SMLSIZE, 0, NULL, "GB"); lcd->drawBitmap(70, LCD_H-FH-20, modelselModelQtyBitmap); - lcdDrawNumber(92, LCD_H-FH-21, modelslist.modelsCount,SMLSIZE); + lcdDrawNumber(92, LCD_H-FH-21, modelslist.getModelsCount(),SMLSIZE); return true; } diff --git a/radio/src/gui/480x272/model_setup.cpp b/radio/src/gui/480x272/model_setup.cpp index f87e341fd..105a0661c 100644 --- a/radio/src/gui/480x272/model_setup.cpp +++ b/radio/src/gui/480x272/model_setup.cpp @@ -19,6 +19,7 @@ */ #include "opentx.h" +#include "storage/modelslist.h" uint8_t g_moduleIdx; @@ -98,6 +99,20 @@ enum MenuModelSetupItems { #define CURRENT_MODULE_EDITED(k) (k>=ITEM_MODEL_TRAINER_LABEL ? TRAINER_MODULE : (k>=ITEM_MODEL_EXTERNAL_MODULE_LABEL ? EXTERNAL_MODULE : INTERNAL_MODULE)) +void checkModelIdUnique(uint8_t moduleIdx) +{ + char* warn_buf = reusableBuffer.msgbuf.msg; + + // cannot rely exactly on WARNING_LINE_LEN so using WARNING_LINE_LEN-2 + size_t warn_buf_len = sizeof(reusableBuffer.msgbuf.msg) - WARNING_LINE_LEN - 2; + if (!modelslist.isModelIdUnique(moduleIdx,warn_buf,warn_buf_len)) { + if (warn_buf[0] != 0) { + POPUP_WARNING(STR_MODELIDUSED); + SET_WARNING_INFO(warn_buf, sizeof(reusableBuffer.msgbuf.msg), 0); + } + } +} + void onBindMenu(const char * result) { uint8_t moduleIdx = (menuVerticalPosition >= ITEM_MODEL_EXTERNAL_MODULE_LABEL ? EXTERNAL_MODULE : INTERNAL_MODULE); @@ -136,6 +151,8 @@ void onModelSetupBitmapMenu(const char * result) // The user choosed a bmp file in the list copySelection(g_model.header.bitmap, result, sizeof(g_model.header.bitmap)); storageDirty(EE_MODEL); + if (modelslist.getCurrentModel()) + modelslist.getCurrentModel()->resetBuffer(); } } @@ -249,6 +266,7 @@ bool menuModelSetup(event_t event) g_model.moduleData[INTERNAL_MODULE].pxx.external_antenna = XJT_EXTERNAL_ANTENNA; } + int8_t old_editMode = s_editMode; MENU(STR_MENUSETUP, MODEL_ICONS, menuTabModel, MENU_MODEL_SETUP, ITEM_MODEL_SETUP_MAX, { 0, 0, TIMERS_ROWS, 0, 1, 0, 0, LABEL(Throttle), 0, 0, 0, @@ -634,7 +652,6 @@ bool menuModelSetup(event_t event) if (g_model.moduleData[INTERNAL_MODULE].rfProtocol == RF_PROTO_OFF) g_model.moduleData[INTERNAL_MODULE].type = MODULE_TYPE_NONE; } - } break; @@ -891,8 +908,19 @@ bool menuModelSetup(event_t event) if (IS_MODULE_PXX(moduleIdx) || IS_MODULE_DSM2(moduleIdx) || IS_MODULE_MULTIMODULE(moduleIdx)) { if (xOffsetBind) lcdDrawNumber(MODEL_SETUP_2ND_COLUMN, y, g_model.header.modelId[moduleIdx], (l_posHorz==0 ? attr : 0) | LEADING0 | LEFT, 2); - if (attr && l_posHorz==0 && s_editMode>0) - CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.modelId[moduleIdx], MAX_RX_NUM(moduleIdx)); + if (attr && l_posHorz==0) { + if (s_editMode>0) { + CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.modelId[moduleIdx], MAX_RX_NUM(moduleIdx)); + if (event == EVT_KEY_LONG(KEY_ENTER)) { + killEvents(event); + uint8_t newVal = modelslist.findNextUnusedModelId(moduleIdx); + if (newVal != g_model.header.modelId[moduleIdx]) { + g_model.header.modelId[moduleIdx] = newVal; + storageDirty(EE_MODEL); + } + } + } + } drawButton(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, (moduleFlag[moduleIdx] == MODULE_BIND ? BUTTON_ON : BUTTON_OFF) | (l_posHorz==1 ? attr : 0)); drawButton(MODEL_SETUP_2ND_COLUMN+MODEL_SETUP_RANGE_OFS+xOffsetBind, y, STR_MODULE_RANGE, (moduleFlag[moduleIdx] == MODULE_RANGECHECK ? BUTTON_ON : BUTTON_OFF) | (l_posHorz==2 ? attr : 0)); uint8_t newFlag = 0; @@ -1110,6 +1138,35 @@ bool menuModelSetup(event_t event) lcdDrawNumber(WARNING_LINE_X, WARNING_INFOLINE_Y, TELEMETRY_RSSI(), DBLSIZE|LEFT); } + // some field just finished being edited + if (old_editMode > 0 && s_editMode == 0) { + ModelCell* mod_cell = modelslist.getCurrentModel(); + if (mod_cell) { + + switch(menuVerticalPosition) { + case ITEM_MODEL_NAME: + mod_cell->setModelName(g_model.header.name); + break; + + case ITEM_MODEL_INTERNAL_MODULE_BIND: + if (menuHorizontalPosition != 0) + break; + case ITEM_MODEL_INTERNAL_MODULE_MODE: + mod_cell->setRfData(&g_model); + checkModelIdUnique(INTERNAL_MODULE); + break; + + case ITEM_MODEL_EXTERNAL_MODULE_BIND: + if (menuHorizontalPosition != 0) + break; + case ITEM_MODEL_EXTERNAL_MODULE_MODE: + mod_cell->setRfData(&g_model); + if (g_model.moduleData[EXTERNAL_MODULE].type != MODULE_TYPE_NONE) + checkModelIdUnique(EXTERNAL_MODULE); + } + } + } + return true; } diff --git a/radio/src/gui/480x272/radio_sdmanager.cpp b/radio/src/gui/480x272/radio_sdmanager.cpp index 99f3dd6e8..6a851d9c9 100644 --- a/radio/src/gui/480x272/radio_sdmanager.cpp +++ b/radio/src/gui/480x272/radio_sdmanager.cpp @@ -20,6 +20,7 @@ #include #include "opentx.h" +#include "storage/modelslist.h" #define REFRESH_FILES() do { reusableBuffer.sdmanager.offset = 65535; currentBitmapIndex = -1; } while (0) #define NODE_TYPE(fname) fname[SD_SCREEN_FILE_LENGTH+1] @@ -126,6 +127,8 @@ void onSdManagerMenu(const char * result) } else if (result == STR_ASSIGN_BITMAP) { memcpy(g_model.header.bitmap, line, sizeof(g_model.header.bitmap)); + if(modelslist.getCurrentModel()) + modelslist.getCurrentModel()->resetBuffer(); storageDirty(EE_MODEL); } else if (result == STR_ASSIGN_SPLASH) { diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index 86766ac11..1011467e1 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -446,6 +446,44 @@ void checkModelIdUnique(uint8_t index, uint8_t module) SET_WARNING_INFO(reusableBuffer.msgbuf.msg, sizeof(reusableBuffer.msgbuf.msg), 0); } } + +uint8_t findNextUnusedModelId(uint8_t index, uint8_t module) +{ + // assume 63 is the highest Model ID + // and use 64 bits + uint8_t usedModelIds[8]; + memset(usedModelIds, 0, sizeof(usedModelIds)); + + for (uint8_t mod_i = 0; mod_i < MAX_MODELS; mod_i++) { + + if (mod_i == index) + continue; + + uint8_t id = modelHeaders[mod_i].modelId[module]; + if (id == 0) + continue; + + uint8_t mask = 1; + for (uint8_t i = 1; i < (id & 7); i++) + mask <<= 1; + + usedModelIds[id >> 3] |= mask; + } + + uint8_t new_id = 1; + uint8_t tst_mask = 1; + for (;new_id < MAX_RX_NUM(module); new_id++) { + if (!(usedModelIds[new_id >> 3] & tst_mask)) { + // found free ID + return new_id; + } + if ((tst_mask <<= 1) == 0) + tst_mask = 1; + } + + // failed finding something... + return 0; +} #endif void modelDefault(uint8_t id) diff --git a/radio/src/opentx.h b/radio/src/opentx.h index 6328e1082..3be78156a 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -939,8 +939,9 @@ inline void resumeMixerCalculations() void generalDefault(); void modelDefault(uint8_t id); -#if defined(CPUARM) +#if defined(CPUARM) && defined(EEPROM) void checkModelIdUnique(uint8_t index, uint8_t module); +uint8_t findNextUnusedModelId(uint8_t index, uint8_t module); #endif #if defined(CPUARM) diff --git a/radio/src/sdcard.h b/radio/src/sdcard.h index a636e21c9..ffaf49043 100644 --- a/radio/src/sdcard.h +++ b/radio/src/sdcard.h @@ -22,6 +22,7 @@ #define _SDCARD_H_ #include "ff.h" +#include "opentx.h" #define ROOT_PATH "/" #define MODELS_PATH ROOT_PATH "MODELS" // no trailing slash = important diff --git a/radio/src/storage/modelslist.cpp b/radio/src/storage/modelslist.cpp new file mode 100644 index 000000000..acd47dd69 --- /dev/null +++ b/radio/src/storage/modelslist.cpp @@ -0,0 +1,565 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * 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 "modelslist.h" +using std::list; + +ModelsList modelslist; + +ModelCell::ModelCell(const char * name) + : buffer(NULL), valid_rfData(false) +{ + strncpy(modelFilename, name, sizeof(modelFilename)); + memset(modelName, 0, sizeof(modelName)); +} + +ModelCell::~ModelCell() +{ + resetBuffer(); +} + +void ModelCell::setModelName(char* name) +{ + zchar2str(modelName, name, LEN_MODEL_NAME); + if (modelName[0] == 0) { + char * tmp; + strncpy(modelName, modelFilename, LEN_MODEL_NAME); + tmp = (char *) memchr(modelName, '.', LEN_MODEL_NAME); + if (tmp != NULL) + *tmp = 0; + } + + resetBuffer(); +} + +void ModelCell::setModelId(uint8_t moduleIdx, uint8_t id) +{ + modelId[moduleIdx] = id; +} + +void ModelCell::resetBuffer() +{ + if (buffer) { + delete buffer; + buffer = NULL; + } +} + +const BitmapBuffer * ModelCell::getBuffer() +{ + if (!buffer) { + loadBitmap(); + } + return buffer; +} + +void ModelCell::loadBitmap() +{ + PACK(struct { + ModelHeader header; + TimerData timers[MAX_TIMERS]; + }) partialmodel; + const char * error = NULL; + + buffer = new BitmapBuffer(BMP_RGB565, MODELCELL_WIDTH, MODELCELL_HEIGHT); + if (buffer == NULL) { + return; + } + + if (strncmp(modelFilename, g_eeGeneral.currModelFilename, LEN_MODEL_FILENAME) == 0) { + memcpy(&partialmodel.header, &g_model.header, sizeof(partialmodel)); + } + else { + error = readModel(modelFilename, (uint8_t *)&partialmodel.header, sizeof(partialmodel)); + } + + buffer->clear(TEXT_BGCOLOR); + + if (error) { + buffer->drawText(5, 2, "(Invalid Model)", TEXT_COLOR); + buffer->drawBitmapPattern(5, 23, LBM_LIBRARY_SLOT, TEXT_COLOR); + } + else { + if (modelName[0] == 0) + setModelName(partialmodel.header.name); + + char timer[LEN_TIMER_STRING]; + buffer->drawSizedText(5, 2, modelName, LEN_MODEL_NAME, SMLSIZE|TEXT_COLOR); + getTimerString(timer, 0); + for (uint8_t i = 0; i < MAX_TIMERS; i++) { + if (partialmodel.timers[i].mode > 0 && partialmodel.timers[i].persistent) { + getTimerString(timer, partialmodel.timers[i].value); + break; + } + } + buffer->drawText(101, 40, timer, TEXT_COLOR); + for (int i=0; i<4; i++) { + buffer->drawBitmapPattern(104+i*11, 25, LBM_SCORE0, TITLE_BGCOLOR); + } + GET_FILENAME(filename, BITMAPS_PATH, partialmodel.header.bitmap, ""); + const BitmapBuffer * bitmap = BitmapBuffer::load(filename); + if (bitmap) { + buffer->drawScaledBitmap(bitmap, 5, 24, 56, 32); + delete bitmap; + } + else { + buffer->drawBitmapPattern(5, 23, LBM_LIBRARY_SLOT, TEXT_COLOR); + } + } + buffer->drawSolidHorizontalLine(5, 19, 143, LINE_COLOR); +} + +void ModelCell::save(FIL* file) +{ + f_puts(modelFilename, file); + f_putc('\n', file); +} + +void ModelCell::setRfData(ModelData* model) +{ + for (uint8_t i = 0; i < NUM_MODULES; i++) { + modelId[i] = model->header.modelId[i]; + setRfModuleData(i, &(model->moduleData[i])); + TRACE("<%s/%i> : %X,%X,%X", + strlen(modelName) ? modelName : modelFilename, + i, moduleData[i].type, moduleData[i].rfProtocol, modelId[i]); + } + valid_rfData = true; +} + +void ModelCell::setRfModuleData(uint8_t moduleIdx, ModuleData* modData) +{ + moduleData[moduleIdx].type = modData->type; + if (modData->type != MODULE_TYPE_MULTIMODULE) { + moduleData[moduleIdx].rfProtocol = (uint8_t)modData->rfProtocol; + } + else { + // do we care here about MM_RF_CUSTOM_SELECTED? probably not... + moduleData[moduleIdx].rfProtocol = modData->getMultiProtocol(false); + } +} + +bool ModelCell::fetchRfData() +{ + //TODO: use g_model in case fetching data for current model + // + char buf[256]; + getModelPath(buf, modelFilename); + + FIL file; + uint16_t file_size; + + const char* err = openFile(buf,&file,&file_size); + if (err) return false; + + FSIZE_t start_offset = f_tell(&file); + + + UINT read; + if ((f_read(&file, buf, LEN_MODEL_NAME, &read) != FR_OK) || (read != LEN_MODEL_NAME)) + goto error; + + setModelName(buf); + + // 1. fetch modelId: NUM_MODULES @ offsetof(ModelHeader, modelId) + // if (f_lseek(&file, start_offset + offsetof(ModelHeader, modelId)) != FR_OK) + // goto error; + if ((f_read(&file, modelId, NUM_MODULES, &read) != FR_OK) || (read != NUM_MODULES)) + goto error; + + // 2. fetch ModuleData: sizeof(ModuleData)*NUM_MODULES @ offsetof(ModelData, moduleData) + if (f_lseek(&file, start_offset + offsetof(ModelData, moduleData)) != FR_OK) + goto error; + + for(uint8_t i=0; iname, name, sizeof(this->name)); +} + +ModelsCategory::~ModelsCategory() +{ + for (list::iterator it = begin(); it != end(); ++it) { + delete *it; + } +} + + +ModelCell * ModelsCategory::addModel(const char * name) +{ + ModelCell * result = new ModelCell(name); + push_back(result); + return result; +} + +void ModelsCategory::removeModel(ModelCell * model) +{ + delete model; + remove(model); +} + +void ModelsCategory::moveModel(ModelCell * model, int8_t step) +{ + ModelsCategory::iterator current = begin(); + for (; current != end(); current++) { + if (*current == model) { + break; + } + } + + ModelsCategory::iterator new_position = current; + if (step > 0) { + while (step >= 0 && new_position != end()) { + new_position++; + step--; + } + } + else { + while (step < 0 && new_position != begin()) { + new_position--; + step++; + } + } + + insert(new_position, 1, *current); + erase(current); +} + +void ModelsCategory::save(FIL * file) +{ + f_puts("[", file); + f_puts(name, file); + f_puts("]", file); + f_putc('\n', file); + for (list::iterator it = begin(); it != end(); ++it) { + (*it)->save(file); + } +} + +ModelsList::ModelsList() +{ + init(); +} + +ModelsList::~ModelsList() +{ + clear(); +} + +void ModelsList::init() +{ + loaded = false; + currentCategory = NULL; + currentModel = NULL; + modelsCount = 0; +} + +void ModelsList::clear() +{ + for (list::iterator it = categories.begin(); it != categories.end(); ++it) { + delete *it; + } + categories.clear(); +} + +bool ModelsList::load() +{ + char line[LEN_MODELS_IDX_LINE+1]; + ModelsCategory * category = NULL; + + if (loaded) + return true; + + FRESULT result = f_open(&file, RADIO_MODELSLIST_PATH, FA_OPEN_EXISTING | FA_READ); + if (result == FR_OK) { + while (readNextLine(line, LEN_MODELS_IDX_LINE)) { + int len = strlen(line); // TODO could be returned by readNextLine + if (len > 2 && line[0] == '[' && line[len-1] == ']') { + line[len-1] = '\0'; + category = new ModelsCategory(&line[1]); + categories.push_back(category); + } + else if (len > 0) { + + //char* rf_data_str = cutModelFilename(line); + ModelCell * model = new ModelCell(line); + if (!category) { + category = new ModelsCategory("Models"); + categories.push_back(category); + } + category->push_back(model); + if (!strncmp(line, g_eeGeneral.currModelFilename, LEN_MODEL_FILENAME)) { + currentCategory = category; + currentModel = model; + } + //parseModulesData(model, rf_data_str); + //TRACE("model=<%s>, valid_rfData=<%i>",model->modelFilename,model->valid_rfData); + model->fetchRfData(); + modelsCount += 1; + } + } + f_close(&file); + + if (!getCurrentModel()) { + TRACE("currentModel is NULL"); + } + } + + if (categories.size() == 0) { + category = new ModelsCategory("Models"); + categories.push_back(category); + } + + loaded = true; + return true; +} + +void ModelsList::save() +{ + FRESULT result = f_open(&file, RADIO_MODELSLIST_PATH, FA_CREATE_ALWAYS | FA_WRITE); + if (result != FR_OK) { + return; + } + + for (list::iterator it = categories.begin(); it != categories.end(); ++it) { + (*it)->save(&file); + } + + f_close(&file); +} + +void ModelsList::setCurrentCategorie(ModelsCategory* cat) +{ + currentCategory = cat; +} + +void ModelsList::setCurrentModel(ModelCell* cell) +{ + currentModel = cell; + if (!currentModel->valid_rfData) + currentModel->fetchRfData(); +} + +bool ModelsList::readNextLine(char * line, int maxlen) +{ + if (f_gets(line, maxlen, &file) != NULL) { + int curlen = strlen(line) - 1; + if (line[curlen] == '\n') { // remove unwanted chars if file was edited using windows + if (line[curlen - 1] == '\r') { + line[curlen - 1] = 0; + } + else { + line[curlen] = 0; + } + } + return true; + } + return false; +} + +ModelsCategory * ModelsList::createCategory() +{ + ModelsCategory * result = new ModelsCategory("Category"); + categories.push_back(result); + save(); + return result; +} + +ModelCell * ModelsList::addModel(ModelsCategory * category, const char * name) +{ + ModelCell * result = category->addModel(name); + modelsCount++; + save(); + return result; +} + +void ModelsList::removeCategory(ModelsCategory * category) +{ + modelsCount -= category->size(); + delete category; + categories.remove(category); +} + +void ModelsList::removeModel(ModelsCategory * category, ModelCell * model) +{ + category->removeModel(model); + modelsCount--; + save(); +} + +void ModelsList::moveModel(ModelsCategory * category, ModelCell * model, int8_t step) +{ + category->moveModel(model, step); + save(); +} + +void ModelsList::moveModel(ModelCell * model, ModelsCategory * previous_category, ModelsCategory * new_category) +{ + previous_category->remove(model); + new_category->push_back(model); + save(); +} + +bool ModelsList::isModelIdUnique(uint8_t moduleIdx, char* warn_buf, size_t warn_buf_len) +{ + ModelCell* mod_cell = modelslist.getCurrentModel(); + if (!mod_cell || !mod_cell->valid_rfData) { + // in doubt, pretend it's unique + return true; + } + + uint8_t modelId = mod_cell->modelId[moduleIdx]; + uint8_t type = mod_cell->moduleData[moduleIdx].type; + uint8_t rfProtocol = mod_cell->moduleData[moduleIdx].rfProtocol; + + uint8_t additionalOnes = 0; + char* curr = warn_buf; + curr[0] = 0; + + bool hit_found = false; + const std::list& cats = modelslist.getCategories(); + std::list::const_iterator cat_it = cats.begin(); + for (;cat_it != cats.end(); cat_it++) { + for (ModelsCategory::const_iterator it = (*cat_it)->begin(); it != (*cat_it)->end(); it++) { + if (mod_cell == *it) + continue; + + if (!(*it)->valid_rfData) + continue; + + if ((type != MODULE_TYPE_NONE) && + (type == (*it)->moduleData[moduleIdx].type) && + (rfProtocol == (*it)->moduleData[moduleIdx].rfProtocol) && + (modelId == (*it)->modelId[moduleIdx])) { + + // Hit found! + hit_found = true; + + const char* modelName = (*it)->modelName; + const char* modelFilename = (*it)->modelFilename; + + // you cannot rely exactly on WARNING_LINE_LEN so using WARNING_LINE_LEN-2 (-2 for the ",") + if ((warn_buf_len - 2 - (curr - warn_buf)) > LEN_MODEL_NAME) { + if (warn_buf[0] != 0) + curr = strAppend(curr, ", "); + if (modelName[0] == 0) { + size_t len = min(strlen(modelFilename),LEN_MODEL_NAME); + curr = strAppendFilename(curr, modelFilename, len); + } + else + curr = strAppend(curr, modelName, LEN_MODEL_NAME); + } + else { + additionalOnes++; + } + } + } + } + + if (additionalOnes && (warn_buf_len - (curr - warn_buf) >= 7)) { + curr = strAppend(curr, " (+"); + curr = strAppendUnsigned(curr, additionalOnes); + curr = strAppend(curr, ")"); + } + + return !hit_found; +} + +uint8_t ModelsList::findNextUnusedModelId(uint8_t moduleIdx) +{ + ModelCell* mod_cell = modelslist.getCurrentModel(); + if (!mod_cell || !mod_cell->valid_rfData) { + return 0; + } + + uint8_t type = mod_cell->moduleData[moduleIdx].type; + uint8_t rfProtocol = mod_cell->moduleData[moduleIdx].rfProtocol; + + // assume 63 is the highest Model ID + // and use 64 bits + uint8_t usedModelIds[8]; + memset(usedModelIds, 0, sizeof(usedModelIds)); + + const std::list& cats = modelslist.getCategories(); + std::list::const_iterator cat_it = cats.begin(); + for (;cat_it != cats.end(); cat_it++) { + for (ModelsCategory::const_iterator it = (*cat_it)->begin(); it != (*cat_it)->end(); it++) { + if (mod_cell == *it) + continue; + + if (!(*it)->valid_rfData) + continue; + + // match module type and RF protocol + if ((type != MODULE_TYPE_NONE) && + (type == (*it)->moduleData[moduleIdx].type) && + (rfProtocol == (*it)->moduleData[moduleIdx].rfProtocol)) { + + uint8_t id = (*it)->modelId[moduleIdx]; + + uint8_t mask = 1; + for (uint8_t i = 1; i < (id & 7); i++) + mask <<= 1; + + usedModelIds[id >> 3] |= mask; + } + } + } + + uint8_t new_id = 1; + uint8_t tst_mask = 1; + for (;new_id < MAX_RX_NUM(moduleIdx); new_id++) { + if (!(usedModelIds[new_id >> 3] & tst_mask)) { + // found free ID + return new_id; + } + if ((tst_mask <<= 1) == 0) + tst_mask = 1; + } + + // failed finding something... + return 0; +} + +void ModelsList::onNewModelCreated(ModelCell* cell, ModelData* model) +{ + cell->setModelName(model->header.name); + cell->setRfData(model); + + uint8_t new_id = findNextUnusedModelId(INTERNAL_MODULE); + model->header.modelId[INTERNAL_MODULE] = new_id; + cell->setModelId(INTERNAL_MODULE, new_id); +} diff --git a/radio/src/storage/modelslist.h b/radio/src/storage/modelslist.h index 2944c335c..c5a157207 100644 --- a/radio/src/storage/modelslist.h +++ b/radio/src/storage/modelslist.h @@ -21,318 +21,121 @@ #ifndef _MODELSLIST_H_ #define _MODELSLIST_H_ +#include #include #include "sdcard.h" #define MODELCELL_WIDTH 172 #define MODELCELL_HEIGHT 59 +// modelXXXXXXX.bin F,FF F,3F,FF\r\n +#define LEN_MODELS_IDX_LINE (LEN_MODEL_FILENAME + sizeof(" F,FF F,3F,FF\r\n")-1) + +struct SimpleModuleData +{ + uint8_t type; + uint8_t rfProtocol; +}; + class ModelCell { - public: - ModelCell(const char * name): - buffer(NULL) - { - strncpy(this->modelFilename, name, sizeof(this->modelFilename)); - } +public: + char modelFilename[LEN_MODEL_FILENAME+1]; + char modelName[LEN_MODEL_NAME+1]; + BitmapBuffer * buffer; - ~ModelCell() - { - if (buffer) { - delete buffer; - } - } + bool valid_rfData; + uint8_t modelId[NUM_MODULES]; + SimpleModuleData moduleData[NUM_MODULES]; - const BitmapBuffer * getBuffer() - { - if (!buffer) { - load(); - } - return buffer; - } + ModelCell(const char * name); + ~ModelCell(); - void load() - { - PACK(struct { - ModelHeader header; - TimerData timers[MAX_TIMERS]; - }) partialmodel; - const char * error = NULL; + void save(FIL* file); - buffer = new BitmapBuffer(BMP_RGB565, MODELCELL_WIDTH, MODELCELL_HEIGHT); - if (buffer == NULL) { - return; - } + void setModelName(char* name); + void setRfData(ModelData* model); - if (strncmp(modelFilename, g_eeGeneral.currModelFilename, LEN_MODEL_FILENAME) == 0) { - memcpy(&partialmodel.header, &g_model.header, sizeof(partialmodel)); - } - else { - error = readModel(modelFilename, (uint8_t *)&partialmodel.header, sizeof(partialmodel)); - } + void setModelId(uint8_t moduleIdx, uint8_t id); + void setRfModuleData(uint8_t moduleIdx, ModuleData* modData); - buffer->clear(TEXT_BGCOLOR); - - if (error) { - buffer->drawText(5, 2, "(Invalid Model)", TEXT_COLOR); - buffer->drawBitmapPattern(5, 23, LBM_LIBRARY_SLOT, TEXT_COLOR); - } - else { - zchar2str(modelName, partialmodel.header.name, LEN_MODEL_NAME); - if (modelName[0] == 0) { - char * tmp; - strncpy(modelName, modelFilename, LEN_MODEL_NAME); - tmp = (char *) memchr(modelName, '.', LEN_MODEL_NAME); - if (tmp != NULL) - *tmp = 0; - } - char timer[LEN_TIMER_STRING]; - buffer->drawSizedText(5, 2, modelName, LEN_MODEL_NAME, SMLSIZE|TEXT_COLOR); - getTimerString(timer, 0); - for (uint8_t i = 0; i < MAX_TIMERS; i++) { - if (partialmodel.timers[i].mode > 0 && partialmodel.timers[i].persistent) { - getTimerString(timer, partialmodel.timers[i].value); - break; - } - } - buffer->drawText(101, 40, timer, TEXT_COLOR); - for (int i=0; i<4; i++) { - buffer->drawBitmapPattern(104+i*11, 25, LBM_SCORE0, TITLE_BGCOLOR); - } - GET_FILENAME(filename, BITMAPS_PATH, partialmodel.header.bitmap, ""); - const BitmapBuffer * bitmap = BitmapBuffer::load(filename); - if (bitmap) { - buffer->drawScaledBitmap(bitmap, 5, 24, 56, 32); - delete bitmap; - } - else { - buffer->drawBitmapPattern(5, 23, LBM_LIBRARY_SLOT, TEXT_COLOR); - } - } - buffer->drawSolidHorizontalLine(5, 19, 143, LINE_COLOR); - } - - char modelFilename[LEN_MODEL_FILENAME+1]; - char modelName[LEN_MODEL_NAME+1]; - BitmapBuffer * buffer; + bool fetchRfData(); + void loadBitmap(); + const BitmapBuffer * getBuffer(); + void resetBuffer(); }; class ModelsCategory: public std::list { - public: - ModelsCategory(const char * name) - { - strncpy(this->name, name, sizeof(this->name)); - } +public: + char name[LEN_MODEL_FILENAME+1]; - ~ModelsCategory() - { - for (std::list::iterator it = begin(); it != end(); ++it) { - delete *it; - } - } + ModelsCategory(const char * name); + ~ModelsCategory(); - - ModelCell * addModel(const char * name) - { - ModelCell * result = new ModelCell(name); - push_back(result); - return result; - } - - void removeModel(ModelCell * model) - { - delete model; - remove(model); - } - - void moveModel(ModelCell * model, int8_t step) - { - ModelsCategory::iterator current = begin(); - for (; current != end(); current++) { - if (*current == model) { - break; - } - } - - ModelsCategory::iterator new_position = current; - if (step > 0) { - while (step >= 0 && new_position != end()) { - new_position++; - step--; - } - } - else { - while (step < 0 && new_position != begin()) { - new_position--; - step++; - } - } - - insert(new_position, 1, *current); - erase(current); - } - - void save(FIL * file) - { - f_puts("[", file); - f_puts(name, file); - f_puts("]", file); - f_putc('\n', file); - for (std::list::iterator it = begin(); it != end(); ++it) { - f_puts((*it)->modelFilename, file); - f_putc('\n', file); - } - } - - char name[LEN_MODEL_FILENAME+1]; + ModelCell * addModel(const char * name); + void removeModel(ModelCell * model); + void moveModel(ModelCell * model, int8_t step); + void save(FIL * file); }; class ModelsList { - public: - ModelsList() - { - } + bool loaded; + std::list categories; + ModelsCategory * currentCategory; + ModelCell * currentModel; + unsigned int modelsCount; - ~ModelsList() - { - clear(); - } + void init(); - void clear() - { - for (std::list::iterator it = categories.begin(); it != categories.end(); ++it) { - delete *it; - } - categories.clear(); - currentCategory = NULL; - currentModel = NULL; - modelsCount = 0; - } +public: - bool load() - { - char line[LEN_MODEL_FILENAME+1]; - ModelsCategory * category = NULL; + ModelsList(); + ~ModelsList(); - clear(); + bool load(); + void save(); + void clear(); - FRESULT result = f_open(&file, RADIO_MODELSLIST_PATH, FA_OPEN_EXISTING | FA_READ); - if (result == FR_OK) { - while (readNextLine(line, LEN_MODEL_FILENAME)) { - int len = strlen(line); // TODO could be returned by readNextLine - if (len > 2 && line[0] == '[' && line[len-1] == ']') { - line[len-1] = '\0'; - category = new ModelsCategory(&line[1]); - categories.push_back(category); - } - else if (len > 0) { - ModelCell * model = new ModelCell(line); - if (!category) { - category = new ModelsCategory("Unknown"); - categories.push_back(category); - } - category->push_back(model); - if (!strncmp(line, g_eeGeneral.currModelFilename, LEN_MODEL_FILENAME)) { - currentCategory = category; - currentModel = model; - } - modelsCount += 1; - } - } - f_close(&file); - } + const std::list& getCategories() const { + return categories; + } + + void setCurrentCategorie(ModelsCategory* cat); + ModelsCategory* getCurrentCategory() const { + return currentCategory; + } - if (categories.size() == 0) { - category = new ModelsCategory("Models"); - categories.push_back(category); - } + void setCurrentModel(ModelCell* cell); + ModelCell* getCurrentModel() const { + return currentModel; + } - return true; - } + unsigned int getModelsCount() const { + return modelsCount; + } + + bool readNextLine(char * line, int maxlen); - void save() - { - FRESULT result = f_open(&file, RADIO_MODELSLIST_PATH, FA_CREATE_ALWAYS | FA_WRITE); - if (result != FR_OK) { - return; - } + ModelsCategory * createCategory(); + void removeCategory(ModelsCategory * category); - for (std::list::iterator it = categories.begin(); it != categories.end(); ++it) { - (*it)->save(&file); - } + ModelCell * addModel(ModelsCategory * category, const char * name); + void removeModel(ModelsCategory * category, ModelCell * model); + void moveModel(ModelsCategory * category, ModelCell * model, int8_t step); + void moveModel(ModelCell * model, ModelsCategory * previous_category, ModelsCategory * new_category); - f_close(&file); - } + bool isModelIdUnique(uint8_t moduleIdx, char* warn_buf, size_t warn_buf_len); + uint8_t findNextUnusedModelId(uint8_t moduleIdx); - bool readNextLine(char * line, int maxlen) - { - if (f_gets(line, maxlen, &file) != NULL) { - int curlen = strlen(line) - 1; - if (line[curlen] == '\n') { // remove unwanted chars if file was edited using windows - if (line[curlen - 1] == '\r') { - line[curlen - 1] = 0; - } - else { - line[curlen] = 0; - } - } - return true; - } - return false; - } + void onNewModelCreated(ModelCell* cell, ModelData* model); - ModelsCategory * createCategory() - { - ModelsCategory * result = new ModelsCategory("Category"); - categories.push_back(result); - save(); - return result; - } +protected: + FIL file; - ModelCell * addModel(ModelsCategory * category, const char * name) - { - ModelCell * result = category->addModel(name); - modelsCount++; - save(); - return result; - } - - void removeCategory(ModelsCategory * category) - { - modelsCount -= category->size(); - delete category; - categories.remove(category); - } - - void removeModel(ModelsCategory * category, ModelCell * model) - { - category->removeModel(model); - modelsCount--; - save(); - } - - void moveModel(ModelsCategory * category, ModelCell * model, int8_t step) - { - category->moveModel(model, step); - save(); - } - - void moveModel(ModelCell * model, ModelsCategory * previous_category, ModelsCategory * new_category) - { - previous_category->remove(model); - new_category->push_back(model); - save(); - } - - std::list categories; - ModelsCategory * currentCategory; - ModelCell * currentModel; - unsigned int modelsCount; - - protected: - FIL file; }; +extern ModelsList modelslist; + #endif // _MODELSLIST_H_ diff --git a/radio/src/storage/sdcard_raw.cpp b/radio/src/storage/sdcard_raw.cpp index ac5b70de2..877f9ee7c 100644 --- a/radio/src/storage/sdcard_raw.cpp +++ b/radio/src/storage/sdcard_raw.cpp @@ -19,6 +19,7 @@ */ #include "opentx.h" +#include "modelslist.h" void getModelPath(char * path, const char * filename) { @@ -68,38 +69,50 @@ const char * writeModel() return writeFile(path, (uint8_t *)&g_model, sizeof(g_model)); } -const char * loadFile(const char * filename, uint8_t * data, uint16_t maxsize) +const char * openFile(const char * fullpath, FIL* file, uint16_t* size) { - TRACE("loadFile(%s)", filename); - - FIL file; - char buf[8]; - UINT read; - - FRESULT result = f_open(&file, filename, FA_OPEN_EXISTING | FA_READ); + FRESULT result = f_open(file, fullpath, FA_OPEN_EXISTING | FA_READ); if (result != FR_OK) { return SDCARD_ERROR(result); } - if (f_size(&file) < 8) { - f_close(&file); + if (f_size(file) < 8) { + f_close(file); return STR_INCOMPATIBLE; } - result = f_read(&file, (uint8_t *)buf, 8, &read); - if (result != FR_OK || read != 8) { - f_close(&file); + UINT read; + char buf[8]; + + result = f_read(file, (uint8_t *)buf, sizeof(buf), &read); + if ((result != FR_OK) || (read != sizeof(buf))) { + f_close(file); return SDCARD_ERROR(result); } uint8_t version = (uint8_t)buf[4]; if ((*(uint32_t*)&buf[0] != OTX_FOURCC && *(uint32_t*)&buf[0] != O9X_FOURCC) || version < FIRST_CONV_EEPROM_VER || version > EEPROM_VER || buf[5] != 'M') { - f_close(&file); + f_close(file); return STR_INCOMPATIBLE; } - uint16_t size = min(maxsize, *(uint16_t*)&buf[6]); - result = f_read(&file, data, size, &read); + *size = *(uint16_t*)&buf[6]; + return NULL; +} + +const char * loadFile(const char * fullpath, uint8_t * data, uint16_t maxsize) +{ + FIL file; + UINT read; + uint16_t size; + + TRACE("loadFile(%s)", fullpath); + + const char* err = openFile(fullpath, &file, &size); + if (err) return err; + + size = min(maxsize, size); + FRESULT result = f_read(&file, data, size, &read); if (result != FR_OK || read != size) { f_close(&file); return SDCARD_ERROR(result); @@ -142,7 +155,7 @@ const char * loadRadioSettingsSettings() if (error) { TRACE("loadRadioSettingsSettings error=%s", error); } - // TODO this is temporary, we only have one model for now + return error; } @@ -193,6 +206,8 @@ void storageReadAll() sdCheckAndCreateDirectory(MODELS_PATH); createModel(); } + + modelslist.load(); } void storageCreateModelsList() diff --git a/radio/src/storage/sdcard_raw.h b/radio/src/storage/sdcard_raw.h index 37feef7d8..65c4a39ea 100644 --- a/radio/src/storage/sdcard_raw.h +++ b/radio/src/storage/sdcard_raw.h @@ -26,6 +26,11 @@ #define DEFAULT_CATEGORY "Models" #define DEFAULT_MODEL_FILENAME "model1.bin" +// opens radio.bin or model file +const char * openFile(const char * fullpath, FIL* file, uint16_t* size); + +void getModelPath(char * path, const char * filename); + const char * readModel(const char * filename, uint8_t * buffer, uint32_t size); const char * loadModel(const char * filename, bool alarms=true); const char * createModel(); diff --git a/radio/src/strhelpers.cpp b/radio/src/strhelpers.cpp index 6e7e76bb3..4e2c0f2eb 100644 --- a/radio/src/strhelpers.cpp +++ b/radio/src/strhelpers.cpp @@ -463,8 +463,10 @@ char * strAppendFilename(char * dest, const char * filename, const int size) memset(dest, 0, size); for (int i=0; i