/* * Authors (alphabetical order) * - Andre Bernet * - Andreas Weitl * - Bertrand Songis * - Bryan J. Rentoul (Gruvin) * - Cameron Weeks * - Erez Raviv * - Gabriel Birkus * - Jean-Pierre Parisy * - Karl Szmutny * - Michael Blandford * - Michal Hlavinka * - Pat Mackenzie * - Philip Moss * - Rob Thomson * - Romolo Manfredini * - Thomas Husterer * * opentx is based on code named * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, * er9x by Erez Raviv: http://code.google.com/p/er9x/, * and the original (and ongoing) project by * Thomas Husterer, th9x: http://code.google.com/p/th9x/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include "../opentx.h" const char * writeFile(const char * filename, const uint8_t * data, uint16_t size) { FIL file; char buf[8]; UINT written; FRESULT result = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE); if (result != FR_OK) { return SDCARD_ERROR(result); } *(uint32_t*)&buf[0] = O9X_FOURCC; buf[4] = EEPROM_VER; buf[5] = 'M'; *(uint16_t*)&buf[6] = size; result = f_write(&file, buf, 8, &written); if (result != FR_OK || written != 8) { f_close(&file); return SDCARD_ERROR(result); } result = f_write(&file, data, size, &written); if (result != FR_OK || written != size) { f_close(&file); return SDCARD_ERROR(result); } f_close(&file); return NULL; } const char * writeModel() { char path[256]; strcpy(path, STR_MODELS_PATH); path[sizeof(MODELS_PATH)-1] = '/'; strAppend(&path[sizeof(MODELS_PATH)], g_eeGeneral.currModelFilename, sizeof(g_eeGeneral.currModelFilename)); return writeFile(path, (uint8_t *)&g_model, sizeof(g_model)); } const char * loadFile(const char * filename, uint8_t * data, uint16_t maxsize) { FIL file; char buf[8]; UINT read; FRESULT result = f_open(&file, filename, FA_OPEN_EXISTING | FA_READ); if (result != FR_OK) { return SDCARD_ERROR(result); } 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); return SDCARD_ERROR(result); } uint8_t version = (uint8_t)buf[4]; if (*(uint32_t*)&buf[0] != O9X_FOURCC || version < FIRST_CONV_EEPROM_VER || version > EEPROM_VER || buf[5] != 'M') { f_close(&file); return STR_INCOMPATIBLE; } uint16_t size = min(maxsize, *(uint16_t*)&buf[6]); result = f_read(&file, data, size, &read); if (result != FR_OK || read != size) { f_close(&file); return SDCARD_ERROR(result); } f_close(&file); return NULL; } void getModelPath(char * path, const char * filename) { strcpy(path, STR_MODELS_PATH); path[sizeof(MODELS_PATH)-1] = '/'; strcpy(&path[sizeof(MODELS_PATH)], filename); } const char * readModel(const char * filename, uint8_t * buffer, uint32_t size) { char path[256]; getModelPath(path, filename); return loadFile(path, buffer, size); } const char * loadModel(const char * filename) { preModelLoad(); const char * error = readModel(filename, (uint8_t *)&g_model, sizeof(g_model)); if (error) { TRACE("loadModel error=%s", error); } bool newModel = false; if (error) { modelDefault(0) ; storageCheck(true); newModel = true; } postModelLoad(newModel); return error; } const char RADIO_SETTINGS_PATH[] = RADIO_PATH "/radio.bin"; const char RADIO_MODELSLIST_PATH[] = RADIO_PATH "/models.txt"; const char * loadGeneralSettings() { const char * error = loadFile(RADIO_SETTINGS_PATH, (uint8_t *)&g_eeGeneral, sizeof(g_eeGeneral)); if (error) { TRACE("loadGeneralSettings error=%s", error); } // TODO this is temporary, we only have one model for now return error; } const char * writeGeneralSettings() { return writeFile(RADIO_SETTINGS_PATH, (uint8_t *)&g_eeGeneral, sizeof(g_eeGeneral)); } void storageCheck(bool immediately) { if (storageDirtyMsk & EE_GENERAL) { TRACE("eeprom write general"); storageDirtyMsk -= EE_GENERAL; const char * error = writeGeneralSettings(); if (error) { TRACE("writeGeneralSettings error=%s", error); } } if (storageDirtyMsk & EE_MODEL) { TRACE("eeprom write model"); storageDirtyMsk -= EE_MODEL; const char * error = writeModel(); if (error) { TRACE("writeModel error=%s", error); } } } void storageReadAll() { sdInit(); if (loadGeneralSettings() != NULL) { storageEraseAll(true); } stickMode = g_eeGeneral.stickMode; #if defined(CPUARM) for (uint8_t i=0; languagePacks[i]!=NULL; i++) { if (!strncmp(g_eeGeneral.ttsLanguage, languagePacks[i]->id, 2)) { currentLanguagePackIdx = i; currentLanguagePack = languagePacks[i]; } } #endif loadModel(g_eeGeneral.currModelFilename); } void storageCreateModelsList() { FIL file; FRESULT result = f_open(&file, RADIO_MODELSLIST_PATH, FA_CREATE_ALWAYS | FA_WRITE); if (result == FR_OK) { f_puts("[" DEFAULT_CATEGORY "]\n" DEFAULT_MODEL_FILENAME "\n", &file); f_close(&file); } } void storageFormat() { sdCheckAndCreateDirectory(RADIO_PATH); sdCheckAndCreateDirectory(MODELS_PATH); storageCreateModelsList(); } struct StorageModelsList { FIL file; }; const char * storageOpenModelsList(StorageModelsList * storage) { FRESULT result = f_open(&storage->file, RADIO_MODELSLIST_PATH, FA_OPEN_EXISTING | FA_READ); if (result != FR_OK) { return SDCARD_ERROR(result); } return NULL; } bool storageReadNextLine(StorageModelsList * storage, char * line, int maxlen) { char c; unsigned int read; int len=0; while (1) { FRESULT result = f_read(&storage->file, (uint8_t *)&c, 1, &read); if (result != FR_OK || read != 1) { line[len] = '\0'; return false; } if (c == '\n') { if (len > 0) { // we skip empty lines line[len] = '\0'; return true; } } else if (c != '\r' && len < maxlen) { line[len++] = c; } } } int storageGetCategoryLength(const char * line) { int len = strlen(line); if (len > 2 && line[0] == '[' && line[len-1] == ']') { return len-2; } else { return 0; } } bool storageReadNextCategory(StorageModelsList * storage, char * line, int maxlen) { bool result = true; while (result) { result = storageReadNextLine(storage, line, maxlen); int len = storageGetCategoryLength(line); if (len > 0) { memmove(line, &line[1], len); line[len] = '\0'; return result; } } line[0] = '\0'; return false; } bool storageSeekCategory(StorageModelsList * storage, int category) { f_lseek(&storage->file, 0); char line[256] = ""; int result = true; for (int i=0; result && i<=category; i++) { result = storageReadNextCategory(storage, line, sizeof(line)-1); } return line[0] != '\0'; } bool storageReadNextModel(StorageModelsList * storage, char * line, int maxlen) { bool result = true; while (result) { result = storageReadNextLine(storage, line, maxlen); if (line[0] == '[') return false; else if (line[0] != '\0') return result; } line[0] = '\0'; return false; } enum StorageOperation { STORAGE_INSERT_MODEL, STORAGE_REMOVE_MODEL, STORAGE_RENAME_MODEL, STORAGE_INSERT_CATEGORY, STORAGE_RENAME_CATEGORY, STORAGE_REMOVE_CATEGORY }; const char * storageModifyLine(unsigned int operation, int category, int position, const char * name="") { StorageModelsList storage; FIL file; const char * error = storageOpenModelsList(&storage); if (error) { return error; } { FRESULT result = f_open(&file, RADIO_PATH "/models.tmp", FA_CREATE_ALWAYS | FA_WRITE); if (result != FR_OK) { return SDCARD_ERROR(result); } } bool operationdone = false; bool result = true; int categoryindex = -1; int modelindex = 0; while (result) { char line[256]; result = storageReadNextLine(&storage, line, sizeof(line)-1); if (!operationdone) { int len = storageGetCategoryLength(line); if (len > 0) { if (category >= 0) { if (categoryindex == category && operation == STORAGE_INSERT_MODEL) { f_puts(name, &file); f_putc('\n', &file); operationdone = true; } categoryindex++; if (categoryindex == category) { if (operation == STORAGE_RENAME_CATEGORY) { f_puts("[", &file); f_puts(name, &file); f_puts("]", &file); f_putc('\n', &file); operationdone = true; continue; } else if (operation == STORAGE_REMOVE_CATEGORY) { operationdone = true; continue; } } } } else if (categoryindex == category) { if (modelindex++ == position) { operationdone = true; if (operation == STORAGE_INSERT_MODEL) { f_puts(name, &file); f_putc('\n', &file); } else if (operation == STORAGE_RENAME_MODEL) { f_puts(name, &file); f_putc('\n', &file); continue; } else if (operation == STORAGE_REMOVE_MODEL) { TRACE("skip %s", line); continue; } } } } f_puts(line, &file); f_putc('\n', &file); } if (!operationdone) { if (categoryindex>=0 && operation==STORAGE_INSERT_MODEL) { f_puts(name, &file); f_putc('\n', &file); } else if (operation==STORAGE_INSERT_CATEGORY) { f_puts("[", &file); f_puts(name, &file); f_puts("]", &file); f_putc('\n', &file); } } f_close(&file); f_unlink(RADIO_PATH "/models.old"); f_rename(RADIO_MODELSLIST_PATH, RADIO_PATH "/models.old"); f_rename(RADIO_PATH "/models.tmp", RADIO_MODELSLIST_PATH); return NULL; } const char * storageInsertCategory(const char * name, int position) { return storageModifyLine(STORAGE_INSERT_CATEGORY, position, -1, name); } const char * storageRenameCategory(int category, const char * name) { return storageModifyLine(STORAGE_RENAME_CATEGORY, category, -1, name); } const char * storageRemoveCategory(int category) { return storageModifyLine(STORAGE_REMOVE_CATEGORY, category, -1); } const char * storageInsertModel(const char * name, int category, int position) { return storageModifyLine(STORAGE_INSERT_MODEL, category, position, name); } const char * storageRemoveModel(int category, int position) { return storageModifyLine(STORAGE_REMOVE_MODEL, category, position); } const char * storageRenameModel(const char * name, int category, int position) { return storageModifyLine(STORAGE_RENAME_MODEL, category, position, name); } const char * createModel(int category) { preModelLoad(); char filename[LEN_MODEL_FILENAME+1]; memset(filename, 0, sizeof(filename)); strcpy(filename, "Model.bin"); int index = findNextFileIndex(filename, MODELS_PATH); if (index > 0) { modelDefault(index); TRACE("filename=%s", filename); memcpy(g_eeGeneral.currModelFilename, filename, sizeof(g_eeGeneral.currModelFilename)); storageDirty(EE_GENERAL); storageDirty(EE_MODEL); storageCheck(true); storageInsertModel(filename, category, -1); } postModelLoad(true); return NULL; }