fix models list reload after USB mass storage connection (#5963)
[opentx.git] / radio / src / storage / modelslist.cpp
blob3e842b7d5f32be61b0cc0df9e385697672b12a7c
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "modelslist.h"
22 using std::list;
24 ModelsList modelslist;
26 ModelCell::ModelCell(const char * name)
27 : buffer(NULL), valid_rfData(false)
29 strncpy(modelFilename, name, sizeof(modelFilename));
30 memset(modelName, 0, sizeof(modelName));
33 ModelCell::~ModelCell()
35 resetBuffer();
38 void ModelCell::setModelName(char* name)
40 zchar2str(modelName, name, LEN_MODEL_NAME);
41 if (modelName[0] == 0) {
42 char * tmp;
43 strncpy(modelName, modelFilename, LEN_MODEL_NAME);
44 tmp = (char *) memchr(modelName, '.', LEN_MODEL_NAME);
45 if (tmp != NULL)
46 *tmp = 0;
49 resetBuffer();
52 void ModelCell::setModelId(uint8_t moduleIdx, uint8_t id)
54 modelId[moduleIdx] = id;
57 void ModelCell::resetBuffer()
59 if (buffer) {
60 delete buffer;
61 buffer = NULL;
65 const BitmapBuffer * ModelCell::getBuffer()
67 if (!buffer) {
68 loadBitmap();
70 return buffer;
73 void ModelCell::loadBitmap()
75 PACK(struct {
76 ModelHeader header;
77 TimerData timers[MAX_TIMERS];
78 }) partialmodel;
79 const char * error = NULL;
81 buffer = new BitmapBuffer(BMP_RGB565, MODELCELL_WIDTH, MODELCELL_HEIGHT);
82 if (buffer == NULL) {
83 return;
86 if (strncmp(modelFilename, g_eeGeneral.currModelFilename, LEN_MODEL_FILENAME) == 0) {
87 memcpy(&partialmodel.header, &g_model.header, sizeof(partialmodel));
89 else {
90 error = readModel(modelFilename, (uint8_t *)&partialmodel.header, sizeof(partialmodel));
93 buffer->clear(TEXT_BGCOLOR);
95 if (error) {
96 buffer->drawText(5, 2, "(Invalid Model)", TEXT_COLOR);
97 buffer->drawBitmapPattern(5, 23, LBM_LIBRARY_SLOT, TEXT_COLOR);
99 else {
100 if (modelName[0] == 0)
101 setModelName(partialmodel.header.name);
103 char timer[LEN_TIMER_STRING];
104 buffer->drawSizedText(5, 2, modelName, LEN_MODEL_NAME, SMLSIZE|TEXT_COLOR);
105 getTimerString(timer, 0);
106 for (uint8_t i = 0; i < MAX_TIMERS; i++) {
107 if (partialmodel.timers[i].mode > 0 && partialmodel.timers[i].persistent) {
108 getTimerString(timer, partialmodel.timers[i].value);
109 break;
112 buffer->drawText(101, 40, timer, TEXT_COLOR);
113 for (int i=0; i<4; i++) {
114 buffer->drawBitmapPattern(104+i*11, 25, LBM_SCORE0, TITLE_BGCOLOR);
116 GET_FILENAME(filename, BITMAPS_PATH, partialmodel.header.bitmap, "");
117 const BitmapBuffer * bitmap = BitmapBuffer::load(filename);
118 if (bitmap) {
119 buffer->drawScaledBitmap(bitmap, 5, 24, 56, 32);
120 delete bitmap;
122 else {
123 buffer->drawBitmapPattern(5, 23, LBM_LIBRARY_SLOT, TEXT_COLOR);
126 buffer->drawSolidHorizontalLine(5, 19, 143, LINE_COLOR);
129 void ModelCell::save(FIL* file)
131 f_puts(modelFilename, file);
132 f_putc('\n', file);
135 void ModelCell::setRfData(ModelData* model)
137 for (uint8_t i = 0; i < NUM_MODULES; i++) {
138 modelId[i] = model->header.modelId[i];
139 setRfModuleData(i, &(model->moduleData[i]));
140 TRACE("<%s/%i> : %X,%X,%X",
141 strlen(modelName) ? modelName : modelFilename,
142 i, moduleData[i].type, moduleData[i].rfProtocol, modelId[i]);
144 valid_rfData = true;
147 void ModelCell::setRfModuleData(uint8_t moduleIdx, ModuleData* modData)
149 moduleData[moduleIdx].type = modData->type;
150 if (modData->type != MODULE_TYPE_MULTIMODULE) {
151 moduleData[moduleIdx].rfProtocol = (uint8_t)modData->rfProtocol;
153 else {
154 // do we care here about MM_RF_CUSTOM_SELECTED? probably not...
155 moduleData[moduleIdx].rfProtocol = modData->getMultiProtocol(false);
159 bool ModelCell::fetchRfData()
161 //TODO: use g_model in case fetching data for current model
163 char buf[256];
164 getModelPath(buf, modelFilename);
166 FIL file;
167 uint16_t file_size;
169 const char* err = openFile(buf,&file,&file_size);
170 if (err) return false;
172 FSIZE_t start_offset = f_tell(&file);
175 UINT read;
176 if ((f_read(&file, buf, LEN_MODEL_NAME, &read) != FR_OK) || (read != LEN_MODEL_NAME))
177 goto error;
179 setModelName(buf);
181 // 1. fetch modelId: NUM_MODULES @ offsetof(ModelHeader, modelId)
182 // if (f_lseek(&file, start_offset + offsetof(ModelHeader, modelId)) != FR_OK)
183 // goto error;
184 if ((f_read(&file, modelId, NUM_MODULES, &read) != FR_OK) || (read != NUM_MODULES))
185 goto error;
187 // 2. fetch ModuleData: sizeof(ModuleData)*NUM_MODULES @ offsetof(ModelData, moduleData)
188 if (f_lseek(&file, start_offset + offsetof(ModelData, moduleData)) != FR_OK)
189 goto error;
191 for(uint8_t i=0; i<NUM_MODULES; i++) {
192 ModuleData modData;
193 if ((f_read(&file, &modData, NUM_MODULES, &read) != FR_OK) || (read != NUM_MODULES))
194 goto error;
196 setRfModuleData(i, &modData);
199 valid_rfData = true;
200 f_close(&file);
201 return true;
203 error:
204 f_close(&file);
205 return false;
208 ModelsCategory::ModelsCategory(const char * name)
210 strncpy(this->name, name, sizeof(this->name));
213 ModelsCategory::~ModelsCategory()
215 for (list<ModelCell *>::iterator it = begin(); it != end(); ++it) {
216 delete *it;
221 ModelCell * ModelsCategory::addModel(const char * name)
223 ModelCell * result = new ModelCell(name);
224 push_back(result);
225 return result;
228 void ModelsCategory::removeModel(ModelCell * model)
230 delete model;
231 remove(model);
234 void ModelsCategory::moveModel(ModelCell * model, int8_t step)
236 ModelsCategory::iterator current = begin();
237 for (; current != end(); current++) {
238 if (*current == model) {
239 break;
243 ModelsCategory::iterator new_position = current;
244 if (step > 0) {
245 while (step >= 0 && new_position != end()) {
246 new_position++;
247 step--;
250 else {
251 while (step < 0 && new_position != begin()) {
252 new_position--;
253 step++;
257 insert(new_position, 1, *current);
258 erase(current);
261 void ModelsCategory::save(FIL * file)
263 f_puts("[", file);
264 f_puts(name, file);
265 f_puts("]", file);
266 f_putc('\n', file);
267 for (list<ModelCell *>::iterator it = begin(); it != end(); ++it) {
268 (*it)->save(file);
272 ModelsList::ModelsList()
274 init();
277 ModelsList::~ModelsList()
279 clear();
282 void ModelsList::init()
284 loaded = false;
285 currentCategory = NULL;
286 currentModel = NULL;
287 modelsCount = 0;
290 void ModelsList::clear()
292 for (list<ModelsCategory *>::iterator it = categories.begin(); it != categories.end(); ++it) {
293 delete *it;
295 categories.clear();
296 init();
299 bool ModelsList::load()
301 char line[LEN_MODELS_IDX_LINE+1];
302 ModelsCategory * category = NULL;
304 if (loaded)
305 return true;
307 FRESULT result = f_open(&file, RADIO_MODELSLIST_PATH, FA_OPEN_EXISTING | FA_READ);
308 if (result == FR_OK) {
309 while (readNextLine(line, LEN_MODELS_IDX_LINE)) {
310 int len = strlen(line); // TODO could be returned by readNextLine
311 if (len > 2 && line[0] == '[' && line[len-1] == ']') {
312 line[len-1] = '\0';
313 category = new ModelsCategory(&line[1]);
314 categories.push_back(category);
316 else if (len > 0) {
318 //char* rf_data_str = cutModelFilename(line);
319 ModelCell * model = new ModelCell(line);
320 if (!category) {
321 category = new ModelsCategory("Models");
322 categories.push_back(category);
324 category->push_back(model);
325 if (!strncmp(line, g_eeGeneral.currModelFilename, LEN_MODEL_FILENAME)) {
326 currentCategory = category;
327 currentModel = model;
329 //parseModulesData(model, rf_data_str);
330 //TRACE("model=<%s>, valid_rfData=<%i>",model->modelFilename,model->valid_rfData);
331 model->fetchRfData();
332 modelsCount += 1;
335 f_close(&file);
337 if (!getCurrentModel()) {
338 TRACE("currentModel is NULL");
342 if (categories.size() == 0) {
343 category = new ModelsCategory("Models");
344 categories.push_back(category);
347 loaded = true;
348 return true;
351 void ModelsList::save()
353 FRESULT result = f_open(&file, RADIO_MODELSLIST_PATH, FA_CREATE_ALWAYS | FA_WRITE);
354 if (result != FR_OK) {
355 return;
358 for (list<ModelsCategory *>::iterator it = categories.begin(); it != categories.end(); ++it) {
359 (*it)->save(&file);
362 f_close(&file);
365 void ModelsList::setCurrentCategorie(ModelsCategory* cat)
367 currentCategory = cat;
370 void ModelsList::setCurrentModel(ModelCell* cell)
372 currentModel = cell;
373 if (!currentModel->valid_rfData)
374 currentModel->fetchRfData();
377 bool ModelsList::readNextLine(char * line, int maxlen)
379 if (f_gets(line, maxlen, &file) != NULL) {
380 int curlen = strlen(line) - 1;
381 if (line[curlen] == '\n') { // remove unwanted chars if file was edited using windows
382 if (line[curlen - 1] == '\r') {
383 line[curlen - 1] = 0;
385 else {
386 line[curlen] = 0;
389 return true;
391 return false;
394 ModelsCategory * ModelsList::createCategory()
396 ModelsCategory * result = new ModelsCategory("Category");
397 categories.push_back(result);
398 save();
399 return result;
402 ModelCell * ModelsList::addModel(ModelsCategory * category, const char * name)
404 ModelCell * result = category->addModel(name);
405 modelsCount++;
406 save();
407 return result;
410 void ModelsList::removeCategory(ModelsCategory * category)
412 modelsCount -= category->size();
413 delete category;
414 categories.remove(category);
417 void ModelsList::removeModel(ModelsCategory * category, ModelCell * model)
419 category->removeModel(model);
420 modelsCount--;
421 save();
424 void ModelsList::moveModel(ModelsCategory * category, ModelCell * model, int8_t step)
426 category->moveModel(model, step);
427 save();
430 void ModelsList::moveModel(ModelCell * model, ModelsCategory * previous_category, ModelsCategory * new_category)
432 previous_category->remove(model);
433 new_category->push_back(model);
434 save();
437 bool ModelsList::isModelIdUnique(uint8_t moduleIdx, char* warn_buf, size_t warn_buf_len)
439 ModelCell* mod_cell = modelslist.getCurrentModel();
440 if (!mod_cell || !mod_cell->valid_rfData) {
441 // in doubt, pretend it's unique
442 return true;
445 uint8_t modelId = mod_cell->modelId[moduleIdx];
446 uint8_t type = mod_cell->moduleData[moduleIdx].type;
447 uint8_t rfProtocol = mod_cell->moduleData[moduleIdx].rfProtocol;
449 uint8_t additionalOnes = 0;
450 char* curr = warn_buf;
451 curr[0] = 0;
453 bool hit_found = false;
454 const std::list<ModelsCategory*>& cats = modelslist.getCategories();
455 std::list<ModelsCategory*>::const_iterator cat_it = cats.begin();
456 for (;cat_it != cats.end(); cat_it++) {
457 for (ModelsCategory::const_iterator it = (*cat_it)->begin(); it != (*cat_it)->end(); it++) {
458 if (mod_cell == *it)
459 continue;
461 if (!(*it)->valid_rfData)
462 continue;
464 if ((type != MODULE_TYPE_NONE) &&
465 (type == (*it)->moduleData[moduleIdx].type) &&
466 (rfProtocol == (*it)->moduleData[moduleIdx].rfProtocol) &&
467 (modelId == (*it)->modelId[moduleIdx])) {
469 // Hit found!
470 hit_found = true;
472 const char* modelName = (*it)->modelName;
473 const char* modelFilename = (*it)->modelFilename;
475 // you cannot rely exactly on WARNING_LINE_LEN so using WARNING_LINE_LEN-2 (-2 for the ",")
476 if ((warn_buf_len - 2 - (curr - warn_buf)) > LEN_MODEL_NAME) {
477 if (warn_buf[0] != 0)
478 curr = strAppend(curr, ", ");
479 if (modelName[0] == 0) {
480 size_t len = min<size_t>(strlen(modelFilename),LEN_MODEL_NAME);
481 curr = strAppendFilename(curr, modelFilename, len);
483 else
484 curr = strAppend(curr, modelName, LEN_MODEL_NAME);
486 else {
487 additionalOnes++;
493 if (additionalOnes && (warn_buf_len - (curr - warn_buf) >= 7)) {
494 curr = strAppend(curr, " (+");
495 curr = strAppendUnsigned(curr, additionalOnes);
496 curr = strAppend(curr, ")");
499 return !hit_found;
502 uint8_t ModelsList::findNextUnusedModelId(uint8_t moduleIdx)
504 ModelCell* mod_cell = modelslist.getCurrentModel();
505 if (!mod_cell || !mod_cell->valid_rfData) {
506 return 0;
509 uint8_t type = mod_cell->moduleData[moduleIdx].type;
510 uint8_t rfProtocol = mod_cell->moduleData[moduleIdx].rfProtocol;
512 // assume 63 is the highest Model ID
513 // and use 64 bits
514 uint8_t usedModelIds[8];
515 memset(usedModelIds, 0, sizeof(usedModelIds));
517 const std::list<ModelsCategory*>& cats = modelslist.getCategories();
518 std::list<ModelsCategory*>::const_iterator cat_it = cats.begin();
519 for (;cat_it != cats.end(); cat_it++) {
520 for (ModelsCategory::const_iterator it = (*cat_it)->begin(); it != (*cat_it)->end(); it++) {
521 if (mod_cell == *it)
522 continue;
524 if (!(*it)->valid_rfData)
525 continue;
527 // match module type and RF protocol
528 if ((type != MODULE_TYPE_NONE) &&
529 (type == (*it)->moduleData[moduleIdx].type) &&
530 (rfProtocol == (*it)->moduleData[moduleIdx].rfProtocol)) {
532 uint8_t id = (*it)->modelId[moduleIdx];
534 uint8_t mask = 1;
535 for (uint8_t i = 1; i < (id & 7); i++)
536 mask <<= 1;
538 usedModelIds[id >> 3] |= mask;
543 uint8_t new_id = 1;
544 uint8_t tst_mask = 1;
545 for (;new_id < MAX_RX_NUM(moduleIdx); new_id++) {
546 if (!(usedModelIds[new_id >> 3] & tst_mask)) {
547 // found free ID
548 return new_id;
550 if ((tst_mask <<= 1) == 0)
551 tst_mask = 1;
554 // failed finding something...
555 return 0;
558 void ModelsList::onNewModelCreated(ModelCell* cell, ModelData* model)
560 cell->setModelName(model->header.name);
561 cell->setRfData(model);
563 uint8_t new_id = findNextUnusedModelId(INTERNAL_MODULE);
564 model->header.modelId[INTERNAL_MODULE] = new_id;
565 cell->setModelId(INTERNAL_MODULE, new_id);