mirror of
https://github.com/opentx/opentx.git
synced 2025-07-15 12:25:12 +03:00
Horus: add receiver number check (#5841)
* add receiver number check for Horus * find the first free model ID on LONG ENTER * use the next free model ID for new models * improve free model ID support for std LCD as well * fixed “duplicate model” * fixed ModelsList::isModelIdUnique()
This commit is contained in:
parent
ea898e5122
commit
7379f6064e
16 changed files with 918 additions and 325 deletions
|
@ -157,7 +157,7 @@ endforeach()
|
||||||
set(SRC ${SRC} debug.cpp)
|
set(SRC ${SRC} debug.cpp)
|
||||||
|
|
||||||
if(${EEPROM} STREQUAL SDCARD)
|
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)
|
elseif(${EEPROM} STREQUAL EEPROM_RLC)
|
||||||
set(SRC ${SRC} storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_rlc.cpp)
|
set(SRC ${SRC} storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_rlc.cpp)
|
||||||
add_definitions(-DEEPROM -DEEPROM_RLC)
|
add_definitions(-DEEPROM -DEEPROM_RLC)
|
||||||
|
|
|
@ -634,6 +634,46 @@ int cliTestMemorySpeed()
|
||||||
|
|
||||||
return 0;
|
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<ModelsCategory*>& cats = modList.getCategories();
|
||||||
|
while(1) {
|
||||||
|
for (list<ModelsCategory*>::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)
|
#endif // #if defined(COLORLCD)
|
||||||
|
|
||||||
int cliTest(const char ** argv)
|
int cliTest(const char ** argv)
|
||||||
|
@ -651,6 +691,9 @@ int cliTest(const char ** argv)
|
||||||
else if (!strcmp(argv[1], "memspd")) {
|
else if (!strcmp(argv[1], "memspd")) {
|
||||||
return cliTestMemorySpeed();
|
return cliTestMemorySpeed();
|
||||||
}
|
}
|
||||||
|
else if (!strcmp(argv[1], "modelslist")) {
|
||||||
|
return cliTestModelsList();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
else {
|
else {
|
||||||
serialPrint("%s: Invalid argument \"%s\"", argv[0], argv[1]);
|
serialPrint("%s: Invalid argument \"%s\"", argv[0], argv[1]);
|
||||||
|
|
|
@ -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
|
static uint8_t selectedPxxPower = g_model.moduleData[EXTERNAL_MODULE].pxx.power; //TODO could go to the reusable struct
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int8_t old_editMode = s_editMode;
|
||||||
|
|
||||||
#if defined(PCBTARANIS)
|
#if defined(PCBTARANIS)
|
||||||
MENU_TAB({
|
MENU_TAB({
|
||||||
HEADER_LINE_COLUMNS
|
HEADER_LINE_COLUMNS
|
||||||
|
@ -1109,9 +1111,14 @@ void menuModelSetup(event_t event)
|
||||||
if (checkIncDec_Ret) {
|
if (checkIncDec_Ret) {
|
||||||
modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[moduleIdx];
|
modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (editMode==0 && event==EVT_KEY_BREAK(KEY_ENTER)) {
|
|
||||||
checkModelIdUnique(g_eeGeneral.currModel, moduleIdx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lcdDrawText(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0);
|
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);
|
lcdDrawNumber(16+4*FW, 5*FH, TELEMETRY_RSSI(), BOLD);
|
||||||
}
|
}
|
||||||
#endif
|
#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)
|
#if defined(CPUARM)
|
||||||
|
|
|
@ -279,6 +279,7 @@ void menuModelSetup(event_t event)
|
||||||
bool CURSOR_ON_CELL = (menuHorizontalPosition >= 0);
|
bool CURSOR_ON_CELL = (menuHorizontalPosition >= 0);
|
||||||
static uint8_t selectedPxxPower = g_model.moduleData[EXTERNAL_MODULE].pxx.power; //TODO could go to the reusable struct
|
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,
|
MENU_TAB({ 0, 0, TIMERS_ROWS, TOPLCD_ROWS 0, 1, 0, 0,
|
||||||
LABEL(Throttle), 0, 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,
|
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) {
|
if (checkIncDec_Ret) {
|
||||||
modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[moduleIdx];
|
modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (s_editMode==0 && event==EVT_KEY_BREAK(KEY_ENTER)) {
|
|
||||||
checkModelIdUnique(g_eeGeneral.currModel, moduleIdx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lcdDrawText(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0);
|
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);
|
lcdDrawNumber(16+4*FW, 5*FH, TELEMETRY_RSSI(), BOLD);
|
||||||
}
|
}
|
||||||
#endif
|
#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)
|
void menuModelFailsafe(event_t event)
|
||||||
|
|
|
@ -37,7 +37,6 @@ enum ModelDeleteMode {
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t selectMode, deleteMode;
|
uint8_t selectMode, deleteMode;
|
||||||
ModelsList modelslist;
|
|
||||||
|
|
||||||
ModelsCategory * currentCategory;
|
ModelsCategory * currentCategory;
|
||||||
int currentCategoryIndex;
|
int currentCategoryIndex;
|
||||||
|
@ -87,11 +86,12 @@ void setCurrentModel(unsigned int index)
|
||||||
void setCurrentCategory(unsigned int index)
|
void setCurrentCategory(unsigned int index)
|
||||||
{
|
{
|
||||||
currentCategoryIndex = index;
|
currentCategoryIndex = index;
|
||||||
std::list<ModelsCategory *>::iterator it = modelslist.categories.begin();
|
const std::list<ModelsCategory *>& cats = modelslist.getCategories();
|
||||||
|
std::list<ModelsCategory *>::const_iterator it = cats.begin();
|
||||||
std::advance(it, index);
|
std::advance(it, index);
|
||||||
currentCategory = *it;
|
currentCategory = *it;
|
||||||
categoriesVerticalPosition = index;
|
categoriesVerticalPosition = index;
|
||||||
categoriesVerticalOffset = limit<int>(categoriesVerticalPosition-4, categoriesVerticalOffset, min<int>(categoriesVerticalPosition, max<int>(0, modelslist.categories.size()-5)));
|
categoriesVerticalOffset = limit<int>(categoriesVerticalPosition-4, categoriesVerticalOffset, min<int>(categoriesVerticalPosition, max<int>(0, cats.size()-5)));
|
||||||
if (currentCategory->size() > 0)
|
if (currentCategory->size() > 0)
|
||||||
setCurrentModel(0);
|
setCurrentModel(0);
|
||||||
else
|
else
|
||||||
|
@ -226,6 +226,7 @@ void onModelSelectMenu(const char * result)
|
||||||
storageFlushCurrentModel();
|
storageFlushCurrentModel();
|
||||||
storageCheck(true);
|
storageCheck(true);
|
||||||
memcpy(g_eeGeneral.currModelFilename, currentModel->modelFilename, LEN_MODEL_FILENAME);
|
memcpy(g_eeGeneral.currModelFilename, currentModel->modelFilename, LEN_MODEL_FILENAME);
|
||||||
|
modelslist.setCurrentModel(currentModel);
|
||||||
loadModel(g_eeGeneral.currModelFilename, false);
|
loadModel(g_eeGeneral.currModelFilename, false);
|
||||||
storageDirty(EE_GENERAL);
|
storageDirty(EE_GENERAL);
|
||||||
storageCheck(true);
|
storageCheck(true);
|
||||||
|
@ -239,9 +240,11 @@ void onModelSelectMenu(const char * result)
|
||||||
}
|
}
|
||||||
else if (result == STR_CREATE_MODEL) {
|
else if (result == STR_CREATE_MODEL) {
|
||||||
storageCheck(true);
|
storageCheck(true);
|
||||||
currentModel = modelslist.currentModel = modelslist.addModel(currentCategory, createModel());
|
modelslist.addModel(currentCategory, createModel());
|
||||||
selectMode = MODE_SELECT_MODEL;
|
selectMode = MODE_SELECT_MODEL;
|
||||||
setCurrentModel(currentCategory->size() - 1);
|
setCurrentModel(currentCategory->size() - 1);
|
||||||
|
modelslist.setCurrentModel(currentModel);
|
||||||
|
modelslist.onNewModelCreated(currentModel, &g_model);
|
||||||
#if defined(LUA)
|
#if defined(LUA)
|
||||||
chainMenu(menuModelWizard);
|
chainMenu(menuModelWizard);
|
||||||
#endif
|
#endif
|
||||||
|
@ -251,9 +254,9 @@ void onModelSelectMenu(const char * result)
|
||||||
memcpy(duplicatedFilename, currentModel->modelFilename, sizeof(duplicatedFilename));
|
memcpy(duplicatedFilename, currentModel->modelFilename, sizeof(duplicatedFilename));
|
||||||
if (findNextFileIndex(duplicatedFilename, LEN_MODEL_FILENAME, MODELS_PATH)) {
|
if (findNextFileIndex(duplicatedFilename, LEN_MODEL_FILENAME, MODELS_PATH)) {
|
||||||
sdCopyFile(currentModel->modelFilename, MODELS_PATH, duplicatedFilename, MODELS_PATH);
|
sdCopyFile(currentModel->modelFilename, MODELS_PATH, duplicatedFilename, MODELS_PATH);
|
||||||
modelslist.addModel(currentCategory, duplicatedFilename);
|
ModelCell* dup_model = modelslist.addModel(currentCategory, duplicatedFilename);
|
||||||
unsigned int index = currentCategory->size() - 1;
|
dup_model->fetchRfData();
|
||||||
setCurrentModel(index);
|
setCurrentModel(currentCategory->size() - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
POPUP_WARNING("Invalid File");
|
POPUP_WARNING("Invalid File");
|
||||||
|
@ -264,7 +267,7 @@ void onModelSelectMenu(const char * result)
|
||||||
}
|
}
|
||||||
else if (result == STR_CREATE_CATEGORY) {
|
else if (result == STR_CREATE_CATEGORY) {
|
||||||
currentCategory = modelslist.createCategory();
|
currentCategory = modelslist.createCategory();
|
||||||
setCurrentCategory(modelslist.categories.size() - 1);
|
setCurrentCategory(modelslist.getCategories().size() - 1);
|
||||||
}
|
}
|
||||||
else if (result == STR_RENAME_CATEGORY) {
|
else if (result == STR_RENAME_CATEGORY) {
|
||||||
selectMode = MODE_RENAME_CATEGORY;
|
selectMode = MODE_RENAME_CATEGORY;
|
||||||
|
@ -291,8 +294,9 @@ void initModelsList()
|
||||||
categoriesVerticalOffset = 0;
|
categoriesVerticalOffset = 0;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (std::list<ModelsCategory *>::iterator it = modelslist.categories.begin(); it != modelslist.categories.end(); ++it, ++index) {
|
const std::list<ModelsCategory *>& cats = modelslist.getCategories();
|
||||||
if (*it == modelslist.currentCategory) {
|
for (std::list<ModelsCategory *>::const_iterator it = cats.begin(); it != cats.end(); ++it, ++index) {
|
||||||
|
if (*it == modelslist.getCurrentCategory()) {
|
||||||
setCurrentCategory(index);
|
setCurrentCategory(index);
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
|
@ -306,7 +310,7 @@ void initModelsList()
|
||||||
found = false;
|
found = false;
|
||||||
index = 0;
|
index = 0;
|
||||||
for (ModelsCategory::iterator it = currentCategory->begin(); it != currentCategory->end(); ++it, ++index) {
|
for (ModelsCategory::iterator it = currentCategory->begin(); it != currentCategory->end(); ++it, ++index) {
|
||||||
if (*it == modelslist.currentModel) {
|
if (*it == modelslist.getCurrentModel()) {
|
||||||
setCurrentModel(index);
|
setCurrentModel(index);
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
|
@ -339,6 +343,7 @@ bool menuModelSelect(event_t event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::list<ModelsCategory*>& cats = modelslist.getCategories();
|
||||||
switch(event) {
|
switch(event) {
|
||||||
case 0:
|
case 0:
|
||||||
// no need to refresh the screen
|
// no need to refresh the screen
|
||||||
|
@ -371,7 +376,7 @@ bool menuModelSelect(event_t event)
|
||||||
#endif
|
#endif
|
||||||
if (selectMode == MODE_SELECT_MODEL) {
|
if (selectMode == MODE_SELECT_MODEL) {
|
||||||
if (categoriesVerticalPosition == 0)
|
if (categoriesVerticalPosition == 0)
|
||||||
categoriesVerticalPosition = modelslist.categories.size() - 1;
|
categoriesVerticalPosition = cats.size() - 1;
|
||||||
else
|
else
|
||||||
categoriesVerticalPosition -= 1;
|
categoriesVerticalPosition -= 1;
|
||||||
setCurrentCategory(categoriesVerticalPosition);
|
setCurrentCategory(categoriesVerticalPosition);
|
||||||
|
@ -394,11 +399,11 @@ bool menuModelSelect(event_t event)
|
||||||
#endif
|
#endif
|
||||||
if (selectMode == MODE_SELECT_MODEL) {
|
if (selectMode == MODE_SELECT_MODEL) {
|
||||||
categoriesVerticalPosition += 1;
|
categoriesVerticalPosition += 1;
|
||||||
if (categoriesVerticalPosition >= modelslist.categories.size())
|
if (categoriesVerticalPosition >= cats.size())
|
||||||
categoriesVerticalPosition = 0;
|
categoriesVerticalPosition = 0;
|
||||||
setCurrentCategory(categoriesVerticalPosition);
|
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;
|
ModelsCategory * previous_category = currentCategory;
|
||||||
ModelCell * model = currentModel;
|
ModelCell * model = currentModel;
|
||||||
categoriesVerticalPosition += 1;
|
categoriesVerticalPosition += 1;
|
||||||
|
@ -411,7 +416,7 @@ bool menuModelSelect(event_t event)
|
||||||
case EVT_KEY_LONG(KEY_ENTER):
|
case EVT_KEY_LONG(KEY_ENTER):
|
||||||
if (selectMode == MODE_SELECT_MODEL) {
|
if (selectMode == MODE_SELECT_MODEL) {
|
||||||
killEvents(event);
|
killEvents(event);
|
||||||
if (currentModel && currentModel != modelslist.currentModel) {
|
if (currentModel && currentModel != modelslist.getCurrentModel()) {
|
||||||
POPUP_MENU_ADD_ITEM(STR_SELECT_MODEL);
|
POPUP_MENU_ADD_ITEM(STR_SELECT_MODEL);
|
||||||
}
|
}
|
||||||
POPUP_MENU_ADD_ITEM(STR_CREATE_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_ITEM(STR_MOVE_MODEL);
|
||||||
}
|
}
|
||||||
// POPUP_MENU_ADD_SD_ITEM(STR_BACKUP_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_DELETE_MODEL);
|
||||||
}
|
}
|
||||||
// POPUP_MENU_ADD_ITEM(STR_RESTORE_MODEL);
|
// POPUP_MENU_ADD_ITEM(STR_RESTORE_MODEL);
|
||||||
POPUP_MENU_ADD_ITEM(STR_CREATE_CATEGORY);
|
POPUP_MENU_ADD_ITEM(STR_CREATE_CATEGORY);
|
||||||
POPUP_MENU_ADD_ITEM(STR_RENAME_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_ADD_ITEM(STR_DELETE_CATEGORY);
|
||||||
}
|
}
|
||||||
POPUP_MENU_START(onModelSelectMenu);
|
POPUP_MENU_START(onModelSelectMenu);
|
||||||
|
@ -441,8 +446,8 @@ bool menuModelSelect(event_t event)
|
||||||
// Categories
|
// Categories
|
||||||
int index = 0;
|
int index = 0;
|
||||||
coord_t y = 97;
|
coord_t y = 97;
|
||||||
drawVerticalScrollbar(CATEGORIES_WIDTH-1, y-1, 5*(FH+7)-5, categoriesVerticalOffset, modelslist.categories.size(), 5);
|
drawVerticalScrollbar(CATEGORIES_WIDTH-1, y-1, 5*(FH+7)-5, categoriesVerticalOffset, cats.size(), 5);
|
||||||
for (std::list<ModelsCategory *>::iterator it = modelslist.categories.begin(); it != modelslist.categories.end(); ++it, ++index) {
|
for (std::list<ModelsCategory *>::const_iterator it = cats.begin(); it != cats.end(); ++it, ++index) {
|
||||||
if (index >= categoriesVerticalOffset && index < categoriesVerticalOffset+5) {
|
if (index >= categoriesVerticalOffset && index < categoriesVerticalOffset+5) {
|
||||||
if (index != categoriesVerticalOffset) {
|
if (index != categoriesVerticalOffset) {
|
||||||
lcdDrawSolidHorizontalLine(1, y-4, CATEGORIES_WIDTH-10, LINE_COLOR);
|
lcdDrawSolidHorizontalLine(1, y-4, CATEGORIES_WIDTH-10, LINE_COLOR);
|
||||||
|
@ -508,7 +513,7 @@ bool menuModelSelect(event_t event)
|
||||||
uint32_t size = sdGetSize() / 100;
|
uint32_t size = sdGetSize() / 100;
|
||||||
lcdDrawNumber(22, LCD_H-FH-21, size, PREC1|SMLSIZE, 0, NULL, "GB");
|
lcdDrawNumber(22, LCD_H-FH-21, size, PREC1|SMLSIZE, 0, NULL, "GB");
|
||||||
lcd->drawBitmap(70, LCD_H-FH-20, modelselModelQtyBitmap);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "opentx.h"
|
#include "opentx.h"
|
||||||
|
#include "storage/modelslist.h"
|
||||||
|
|
||||||
uint8_t g_moduleIdx;
|
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))
|
#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)
|
void onBindMenu(const char * result)
|
||||||
{
|
{
|
||||||
uint8_t moduleIdx = (menuVerticalPosition >= ITEM_MODEL_EXTERNAL_MODULE_LABEL ? EXTERNAL_MODULE : INTERNAL_MODULE);
|
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
|
// The user choosed a bmp file in the list
|
||||||
copySelection(g_model.header.bitmap, result, sizeof(g_model.header.bitmap));
|
copySelection(g_model.header.bitmap, result, sizeof(g_model.header.bitmap));
|
||||||
storageDirty(EE_MODEL);
|
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;
|
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,
|
MENU(STR_MENUSETUP, MODEL_ICONS, menuTabModel, MENU_MODEL_SETUP, ITEM_MODEL_SETUP_MAX,
|
||||||
{ 0, 0, TIMERS_ROWS, 0, 1, 0, 0,
|
{ 0, 0, TIMERS_ROWS, 0, 1, 0, 0,
|
||||||
LABEL(Throttle), 0, 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)
|
if (g_model.moduleData[INTERNAL_MODULE].rfProtocol == RF_PROTO_OFF)
|
||||||
g_model.moduleData[INTERNAL_MODULE].type = MODULE_TYPE_NONE;
|
g_model.moduleData[INTERNAL_MODULE].type = MODULE_TYPE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -891,8 +908,19 @@ bool menuModelSetup(event_t event)
|
||||||
if (IS_MODULE_PXX(moduleIdx) || IS_MODULE_DSM2(moduleIdx) || IS_MODULE_MULTIMODULE(moduleIdx)) {
|
if (IS_MODULE_PXX(moduleIdx) || IS_MODULE_DSM2(moduleIdx) || IS_MODULE_MULTIMODULE(moduleIdx)) {
|
||||||
if (xOffsetBind)
|
if (xOffsetBind)
|
||||||
lcdDrawNumber(MODEL_SETUP_2ND_COLUMN, y, g_model.header.modelId[moduleIdx], (l_posHorz==0 ? attr : 0) | LEADING0 | LEFT, 2);
|
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)
|
if (attr && l_posHorz==0) {
|
||||||
|
if (s_editMode>0) {
|
||||||
CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.modelId[moduleIdx], MAX_RX_NUM(moduleIdx));
|
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+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));
|
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;
|
uint8_t newFlag = 0;
|
||||||
|
@ -1110,6 +1138,35 @@ bool menuModelSetup(event_t event)
|
||||||
lcdDrawNumber(WARNING_LINE_X, WARNING_INFOLINE_Y, TELEMETRY_RSSI(), DBLSIZE|LEFT);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "opentx.h"
|
#include "opentx.h"
|
||||||
|
#include "storage/modelslist.h"
|
||||||
|
|
||||||
#define REFRESH_FILES() do { reusableBuffer.sdmanager.offset = 65535; currentBitmapIndex = -1; } while (0)
|
#define REFRESH_FILES() do { reusableBuffer.sdmanager.offset = 65535; currentBitmapIndex = -1; } while (0)
|
||||||
#define NODE_TYPE(fname) fname[SD_SCREEN_FILE_LENGTH+1]
|
#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) {
|
else if (result == STR_ASSIGN_BITMAP) {
|
||||||
memcpy(g_model.header.bitmap, line, sizeof(g_model.header.bitmap));
|
memcpy(g_model.header.bitmap, line, sizeof(g_model.header.bitmap));
|
||||||
|
if(modelslist.getCurrentModel())
|
||||||
|
modelslist.getCurrentModel()->resetBuffer();
|
||||||
storageDirty(EE_MODEL);
|
storageDirty(EE_MODEL);
|
||||||
}
|
}
|
||||||
else if (result == STR_ASSIGN_SPLASH) {
|
else if (result == STR_ASSIGN_SPLASH) {
|
||||||
|
|
|
@ -446,6 +446,44 @@ void checkModelIdUnique(uint8_t index, uint8_t module)
|
||||||
SET_WARNING_INFO(reusableBuffer.msgbuf.msg, sizeof(reusableBuffer.msgbuf.msg), 0);
|
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
|
#endif
|
||||||
|
|
||||||
void modelDefault(uint8_t id)
|
void modelDefault(uint8_t id)
|
||||||
|
|
|
@ -939,8 +939,9 @@ inline void resumeMixerCalculations()
|
||||||
void generalDefault();
|
void generalDefault();
|
||||||
void modelDefault(uint8_t id);
|
void modelDefault(uint8_t id);
|
||||||
|
|
||||||
#if defined(CPUARM)
|
#if defined(CPUARM) && defined(EEPROM)
|
||||||
void checkModelIdUnique(uint8_t index, uint8_t module);
|
void checkModelIdUnique(uint8_t index, uint8_t module);
|
||||||
|
uint8_t findNextUnusedModelId(uint8_t index, uint8_t module);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(CPUARM)
|
#if defined(CPUARM)
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#define _SDCARD_H_
|
#define _SDCARD_H_
|
||||||
|
|
||||||
#include "ff.h"
|
#include "ff.h"
|
||||||
|
#include "opentx.h"
|
||||||
|
|
||||||
#define ROOT_PATH "/"
|
#define ROOT_PATH "/"
|
||||||
#define MODELS_PATH ROOT_PATH "MODELS" // no trailing slash = important
|
#define MODELS_PATH ROOT_PATH "MODELS" // no trailing slash = important
|
||||||
|
|
565
radio/src/storage/modelslist.cpp
Normal file
565
radio/src/storage/modelslist.cpp
Normal file
|
@ -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; i<NUM_MODULES; i++) {
|
||||||
|
ModuleData modData;
|
||||||
|
if ((f_read(&file, &modData, NUM_MODULES, &read) != FR_OK) || (read != NUM_MODULES))
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
setRfModuleData(i, &modData);
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_rfData = true;
|
||||||
|
f_close(&file);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error:
|
||||||
|
f_close(&file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelsCategory::ModelsCategory(const char * name)
|
||||||
|
{
|
||||||
|
strncpy(this->name, name, sizeof(this->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelsCategory::~ModelsCategory()
|
||||||
|
{
|
||||||
|
for (list<ModelCell *>::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<ModelCell *>::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<ModelsCategory *>::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<ModelsCategory *>::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<ModelsCategory*>& cats = modelslist.getCategories();
|
||||||
|
std::list<ModelsCategory*>::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<size_t>(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<ModelsCategory*>& cats = modelslist.getCategories();
|
||||||
|
std::list<ModelsCategory*>::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);
|
||||||
|
}
|
|
@ -21,318 +21,121 @@
|
||||||
#ifndef _MODELSLIST_H_
|
#ifndef _MODELSLIST_H_
|
||||||
#define _MODELSLIST_H_
|
#define _MODELSLIST_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include "sdcard.h"
|
#include "sdcard.h"
|
||||||
|
|
||||||
#define MODELCELL_WIDTH 172
|
#define MODELCELL_WIDTH 172
|
||||||
#define MODELCELL_HEIGHT 59
|
#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
|
class ModelCell
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ModelCell(const char * name):
|
|
||||||
buffer(NULL)
|
|
||||||
{
|
|
||||||
strncpy(this->modelFilename, name, sizeof(this->modelFilename));
|
|
||||||
}
|
|
||||||
|
|
||||||
~ModelCell()
|
|
||||||
{
|
|
||||||
if (buffer) {
|
|
||||||
delete buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const BitmapBuffer * getBuffer()
|
|
||||||
{
|
|
||||||
if (!buffer) {
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void load()
|
|
||||||
{
|
|
||||||
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 {
|
|
||||||
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 modelFilename[LEN_MODEL_FILENAME+1];
|
||||||
char modelName[LEN_MODEL_NAME+1];
|
char modelName[LEN_MODEL_NAME+1];
|
||||||
BitmapBuffer * buffer;
|
BitmapBuffer * buffer;
|
||||||
|
|
||||||
|
bool valid_rfData;
|
||||||
|
uint8_t modelId[NUM_MODULES];
|
||||||
|
SimpleModuleData moduleData[NUM_MODULES];
|
||||||
|
|
||||||
|
ModelCell(const char * name);
|
||||||
|
~ModelCell();
|
||||||
|
|
||||||
|
void save(FIL* file);
|
||||||
|
|
||||||
|
void setModelName(char* name);
|
||||||
|
void setRfData(ModelData* model);
|
||||||
|
|
||||||
|
void setModelId(uint8_t moduleIdx, uint8_t id);
|
||||||
|
void setRfModuleData(uint8_t moduleIdx, ModuleData* modData);
|
||||||
|
|
||||||
|
bool fetchRfData();
|
||||||
|
void loadBitmap();
|
||||||
|
const BitmapBuffer * getBuffer();
|
||||||
|
void resetBuffer();
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModelsCategory: public std::list<ModelCell *>
|
class ModelsCategory: public std::list<ModelCell *>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ModelsCategory(const char * name)
|
|
||||||
{
|
|
||||||
strncpy(this->name, name, sizeof(this->name));
|
|
||||||
}
|
|
||||||
|
|
||||||
~ModelsCategory()
|
|
||||||
{
|
|
||||||
for (std::list<ModelCell *>::iterator it = begin(); it != end(); ++it) {
|
|
||||||
delete *it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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<ModelCell *>::iterator it = begin(); it != end(); ++it) {
|
|
||||||
f_puts((*it)->modelFilename, file);
|
|
||||||
f_putc('\n', file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char name[LEN_MODEL_FILENAME+1];
|
char name[LEN_MODEL_FILENAME+1];
|
||||||
|
|
||||||
|
ModelsCategory(const char * name);
|
||||||
|
~ModelsCategory();
|
||||||
|
|
||||||
|
ModelCell * addModel(const char * name);
|
||||||
|
void removeModel(ModelCell * model);
|
||||||
|
void moveModel(ModelCell * model, int8_t step);
|
||||||
|
void save(FIL * file);
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModelsList
|
class ModelsList
|
||||||
{
|
{
|
||||||
public:
|
bool loaded;
|
||||||
ModelsList()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~ModelsList()
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear()
|
|
||||||
{
|
|
||||||
for (std::list<ModelsCategory *>::iterator it = categories.begin(); it != categories.end(); ++it) {
|
|
||||||
delete *it;
|
|
||||||
}
|
|
||||||
categories.clear();
|
|
||||||
currentCategory = NULL;
|
|
||||||
currentModel = NULL;
|
|
||||||
modelsCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load()
|
|
||||||
{
|
|
||||||
char line[LEN_MODEL_FILENAME+1];
|
|
||||||
ModelsCategory * category = NULL;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (categories.size() == 0) {
|
|
||||||
category = new ModelsCategory("Models");
|
|
||||||
categories.push_back(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void save()
|
|
||||||
{
|
|
||||||
FRESULT result = f_open(&file, RADIO_MODELSLIST_PATH, FA_CREATE_ALWAYS | FA_WRITE);
|
|
||||||
if (result != FR_OK) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::list<ModelsCategory *>::iterator it = categories.begin(); it != categories.end(); ++it) {
|
|
||||||
(*it)->save(&file);
|
|
||||||
}
|
|
||||||
|
|
||||||
f_close(&file);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModelsCategory * createCategory()
|
|
||||||
{
|
|
||||||
ModelsCategory * result = new ModelsCategory("Category");
|
|
||||||
categories.push_back(result);
|
|
||||||
save();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<ModelsCategory *> categories;
|
std::list<ModelsCategory *> categories;
|
||||||
ModelsCategory * currentCategory;
|
ModelsCategory * currentCategory;
|
||||||
ModelCell * currentModel;
|
ModelCell * currentModel;
|
||||||
unsigned int modelsCount;
|
unsigned int modelsCount;
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ModelsList();
|
||||||
|
~ModelsList();
|
||||||
|
|
||||||
|
bool load();
|
||||||
|
void save();
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
const std::list<ModelsCategory *>& getCategories() const {
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCurrentCategorie(ModelsCategory* cat);
|
||||||
|
ModelsCategory* getCurrentCategory() const {
|
||||||
|
return currentCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCurrentModel(ModelCell* cell);
|
||||||
|
ModelCell* getCurrentModel() const {
|
||||||
|
return currentModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int getModelsCount() const {
|
||||||
|
return modelsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readNextLine(char * line, int maxlen);
|
||||||
|
|
||||||
|
ModelsCategory * createCategory();
|
||||||
|
void removeCategory(ModelsCategory * category);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
bool isModelIdUnique(uint8_t moduleIdx, char* warn_buf, size_t warn_buf_len);
|
||||||
|
uint8_t findNextUnusedModelId(uint8_t moduleIdx);
|
||||||
|
|
||||||
|
void onNewModelCreated(ModelCell* cell, ModelData* model);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FIL file;
|
FIL file;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern ModelsList modelslist;
|
||||||
|
|
||||||
#endif // _MODELSLIST_H_
|
#endif // _MODELSLIST_H_
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "opentx.h"
|
#include "opentx.h"
|
||||||
|
#include "modelslist.h"
|
||||||
|
|
||||||
void getModelPath(char * path, const char * filename)
|
void getModelPath(char * path, const char * filename)
|
||||||
{
|
{
|
||||||
|
@ -68,38 +69,50 @@ const char * writeModel()
|
||||||
return writeFile(path, (uint8_t *)&g_model, sizeof(g_model));
|
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);
|
FRESULT result = f_open(file, fullpath, FA_OPEN_EXISTING | FA_READ);
|
||||||
|
|
||||||
FIL file;
|
|
||||||
char buf[8];
|
|
||||||
UINT read;
|
|
||||||
|
|
||||||
FRESULT result = f_open(&file, filename, FA_OPEN_EXISTING | FA_READ);
|
|
||||||
if (result != FR_OK) {
|
if (result != FR_OK) {
|
||||||
return SDCARD_ERROR(result);
|
return SDCARD_ERROR(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f_size(&file) < 8) {
|
if (f_size(file) < 8) {
|
||||||
f_close(&file);
|
f_close(file);
|
||||||
return STR_INCOMPATIBLE;
|
return STR_INCOMPATIBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = f_read(&file, (uint8_t *)buf, 8, &read);
|
UINT read;
|
||||||
if (result != FR_OK || read != 8) {
|
char buf[8];
|
||||||
f_close(&file);
|
|
||||||
|
result = f_read(file, (uint8_t *)buf, sizeof(buf), &read);
|
||||||
|
if ((result != FR_OK) || (read != sizeof(buf))) {
|
||||||
|
f_close(file);
|
||||||
return SDCARD_ERROR(result);
|
return SDCARD_ERROR(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t version = (uint8_t)buf[4];
|
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') {
|
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;
|
return STR_INCOMPATIBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t size = min<uint16_t>(maxsize, *(uint16_t*)&buf[6]);
|
*size = *(uint16_t*)&buf[6];
|
||||||
result = f_read(&file, data, size, &read);
|
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<uint16_t>(maxsize, size);
|
||||||
|
FRESULT result = f_read(&file, data, size, &read);
|
||||||
if (result != FR_OK || read != size) {
|
if (result != FR_OK || read != size) {
|
||||||
f_close(&file);
|
f_close(&file);
|
||||||
return SDCARD_ERROR(result);
|
return SDCARD_ERROR(result);
|
||||||
|
@ -142,7 +155,7 @@ const char * loadRadioSettingsSettings()
|
||||||
if (error) {
|
if (error) {
|
||||||
TRACE("loadRadioSettingsSettings error=%s", error);
|
TRACE("loadRadioSettingsSettings error=%s", error);
|
||||||
}
|
}
|
||||||
// TODO this is temporary, we only have one model for now
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,6 +206,8 @@ void storageReadAll()
|
||||||
sdCheckAndCreateDirectory(MODELS_PATH);
|
sdCheckAndCreateDirectory(MODELS_PATH);
|
||||||
createModel();
|
createModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modelslist.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
void storageCreateModelsList()
|
void storageCreateModelsList()
|
||||||
|
|
|
@ -26,6 +26,11 @@
|
||||||
#define DEFAULT_CATEGORY "Models"
|
#define DEFAULT_CATEGORY "Models"
|
||||||
#define DEFAULT_MODEL_FILENAME "model1.bin"
|
#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 * readModel(const char * filename, uint8_t * buffer, uint32_t size);
|
||||||
const char * loadModel(const char * filename, bool alarms=true);
|
const char * loadModel(const char * filename, bool alarms=true);
|
||||||
const char * createModel();
|
const char * createModel();
|
||||||
|
|
|
@ -463,8 +463,10 @@ char * strAppendFilename(char * dest, const char * filename, const int size)
|
||||||
memset(dest, 0, size);
|
memset(dest, 0, size);
|
||||||
for (int i=0; i<size; i++) {
|
for (int i=0; i<size; i++) {
|
||||||
char c = *filename++;
|
char c = *filename++;
|
||||||
if (c == '\0' || c == '.')
|
if (c == '\0' || c == '.') {
|
||||||
|
*dest = 0;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
*dest++ = c;
|
*dest++ = c;
|
||||||
}
|
}
|
||||||
return dest;
|
return dest;
|
||||||
|
|
|
@ -1056,7 +1056,11 @@
|
||||||
#define TR_SET BUTTON("Set")
|
#define TR_SET BUTTON("Set")
|
||||||
#define TR_TRAINER "Trainer"
|
#define TR_TRAINER "Trainer"
|
||||||
#define TR_ANTENNAPROBLEM CENTER "TX antenna problem!"
|
#define TR_ANTENNAPROBLEM CENTER "TX antenna problem!"
|
||||||
|
#if defined(COLORLCD)
|
||||||
|
#define TR_MODELIDUSED "ID used in:"
|
||||||
|
#else
|
||||||
#define TR_MODELIDUSED TR("ID used in:","Receiver ID used in:")
|
#define TR_MODELIDUSED TR("ID used in:","Receiver ID used in:")
|
||||||
|
#endif
|
||||||
#define TR_MODULE INDENT "Module"
|
#define TR_MODULE INDENT "Module"
|
||||||
#define TR_TELEMETRY_TYPE TR("Type", "Telemetry type")
|
#define TR_TELEMETRY_TYPE TR("Type", "Telemetry type")
|
||||||
#define TR_TELEMETRY_SENSORS "Sensors"
|
#define TR_TELEMETRY_SENSORS "Sensors"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue