diff --git a/companion/src/firmwares/edgetx/edgetxinterface.cpp b/companion/src/firmwares/edgetx/edgetxinterface.cpp index ad97a2b793f..7a768d5c6df 100644 --- a/companion/src/firmwares/edgetx/edgetxinterface.cpp +++ b/companion/src/firmwares/edgetx/edgetxinterface.cpp @@ -20,12 +20,47 @@ #include "edgetxinterface.h" +#include "crc.h" +#include +#include #include "yaml_ops.h" #include "yaml_generalsettings.h" #include "yaml_modeldata.h" #include + +static uint16_t calculateChecksum(const QByteArray& data, uint16_t checksum) +{ + std::string fileContent = data.toStdString(); + + size_t startPos = 0; + size_t pos = 0; + uint8_t isCRLF = 0; + std::string newLine[2] = {"\n","\r\n"}; + + pos = fileContent.find("\n", 0); + if(pos == 0) { + return checksum; + } + + if(fileContent[pos-1] == '\r') { + isCRLF = 1; + qDebug() << "** File has CRLF line endings"; + } else { + isCRLF = 0; + qDebug() << "** File has LF line endings"; + } + + // The first line contains the "checksum" value - if present. Skip it + if ( fileContent.find("checksum:") == 0) { + startPos = fileContent.find(newLine[isCRLF]) + newLine[isCRLF].length(); + } + + checksum = crc16(0, (const uint8_t *) &fileContent[startPos], fileContent.length() - startPos, checksum); + return checksum; +} + static YAML::Node loadYamlFromByteArray(const QByteArray& data) { // TODO: use real streaming to avoid memory copies @@ -33,15 +68,25 @@ static YAML::Node loadYamlFromByteArray(const QByteArray& data) return YAML::Load(data_istream); } -static void writeYamlToByteArray(const YAML::Node& node, QByteArray& data) +static void writeYamlToByteArray(const YAML::Node& node, QByteArray& data, bool addChecksum = false) { // TODO: use real streaming to avoid memory copies std::stringstream data_ostream; + data_ostream << node; data = QByteArray::fromStdString(data_ostream.str()); qDebug() << "Saving YAML:"; - qDebug() << data_ostream.str().c_str(); + + if(addChecksum) { + uint16_t checksum = 0xFFFF; + checksum = calculateChecksum(data, checksum); + std::stringstream checksum_ostream; + checksum_ostream << "checksum: " << checksum << std::endl; + data.prepend(checksum_ostream.str().c_str()); + } + + qDebug() << data.toStdString().c_str(); } bool loadModelsListFromYaml(std::vector& categories, @@ -87,6 +132,28 @@ bool loadModelFromYaml(ModelData& model, const QByteArray& data) bool loadRadioSettingsFromYaml(GeneralSettings& settings, const QByteArray& data) { + if(data.indexOf("checksum:") == 0) { + int startPos = strlen("checksum:"); + int endPos = data.indexOf("\n"); + if (endPos > 0) { + while( ((const char)data[startPos] == ' ') && (startPos < data.size())) { + startPos++; + } + if (startPos < data.size()) { + QByteArray checksumStr = data.mid(startPos, endPos - startPos); + uint16_t fileChecksum = std::stoi(checksumStr.toStdString()); + uint16_t calculatedChecksum = calculateChecksum(data, 0xFFFF); + qDebug() << "File checksum:" << fileChecksum; + qDebug() << "Calculated checksum:" << calculatedChecksum; + if (fileChecksum != calculatedChecksum) { + qDebug() << "File checksum mismatch! Got: " << fileChecksum << ", expected: " << calculatedChecksum; + QMessageBox::critical(NULL, CPN_STR_APP_NAME, QCoreApplication::translate("EdgeTXInterface", "Radio settings file checksum error. You are advised to review the settings")); + //return false; + } + } + } + } + YAML::Node node = loadYamlFromByteArray(data); node >> settings; if (settings.version < CPN_CURRENT_SETTINGS_VERSION) { @@ -159,7 +226,7 @@ bool writeRadioSettingsToYaml(const GeneralSettings& settings, QByteArray& data) YAML::Node node; node = settings; - writeYamlToByteArray(node, data); + writeYamlToByteArray(node, data, true); return true; } diff --git a/companion/src/storage/CMakeLists.txt b/companion/src/storage/CMakeLists.txt index dc9eb1195ee..f39b82475ce 100644 --- a/companion/src/storage/CMakeLists.txt +++ b/companion/src/storage/CMakeLists.txt @@ -15,6 +15,7 @@ set(storage_NAMES etx otx yaml + crc ) set(storage_SRCS diff --git a/companion/src/storage/crc.cpp b/companion/src/storage/crc.cpp new file mode 100644 index 00000000000..485851b2d43 --- /dev/null +++ b/companion/src/storage/crc.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "crc.h" + +static const unsigned short * crc16tab[] = { + crc16tab_1021, + crc16tab_1189 +}; + +uint16_t crc16(uint8_t index, const uint8_t * buf, uint32_t len, uint16_t start) +{ + uint16_t crc = start; + const unsigned short * tab = crc16tab[index]; + for (uint32_t i=0; i>8) ^ *buf++) & 0x00FF]; + } + return crc; +} + +// CRC8 implementation with polynom = x^8+x^7+x^6+x^4+x^2+1 (0xD5) +const unsigned char crc8tab[256] = { + 0x00, 0xD5, 0x7F, 0xAA, 0xFE, 0x2B, 0x81, 0x54, + 0x29, 0xFC, 0x56, 0x83, 0xD7, 0x02, 0xA8, 0x7D, + 0x52, 0x87, 0x2D, 0xF8, 0xAC, 0x79, 0xD3, 0x06, + 0x7B, 0xAE, 0x04, 0xD1, 0x85, 0x50, 0xFA, 0x2F, + 0xA4, 0x71, 0xDB, 0x0E, 0x5A, 0x8F, 0x25, 0xF0, + 0x8D, 0x58, 0xF2, 0x27, 0x73, 0xA6, 0x0C, 0xD9, + 0xF6, 0x23, 0x89, 0x5C, 0x08, 0xDD, 0x77, 0xA2, + 0xDF, 0x0A, 0xA0, 0x75, 0x21, 0xF4, 0x5E, 0x8B, + 0x9D, 0x48, 0xE2, 0x37, 0x63, 0xB6, 0x1C, 0xC9, + 0xB4, 0x61, 0xCB, 0x1E, 0x4A, 0x9F, 0x35, 0xE0, + 0xCF, 0x1A, 0xB0, 0x65, 0x31, 0xE4, 0x4E, 0x9B, + 0xE6, 0x33, 0x99, 0x4C, 0x18, 0xCD, 0x67, 0xB2, + 0x39, 0xEC, 0x46, 0x93, 0xC7, 0x12, 0xB8, 0x6D, + 0x10, 0xC5, 0x6F, 0xBA, 0xEE, 0x3B, 0x91, 0x44, + 0x6B, 0xBE, 0x14, 0xC1, 0x95, 0x40, 0xEA, 0x3F, + 0x42, 0x97, 0x3D, 0xE8, 0xBC, 0x69, 0xC3, 0x16, + 0xEF, 0x3A, 0x90, 0x45, 0x11, 0xC4, 0x6E, 0xBB, + 0xC6, 0x13, 0xB9, 0x6C, 0x38, 0xED, 0x47, 0x92, + 0xBD, 0x68, 0xC2, 0x17, 0x43, 0x96, 0x3C, 0xE9, + 0x94, 0x41, 0xEB, 0x3E, 0x6A, 0xBF, 0x15, 0xC0, + 0x4B, 0x9E, 0x34, 0xE1, 0xB5, 0x60, 0xCA, 0x1F, + 0x62, 0xB7, 0x1D, 0xC8, 0x9C, 0x49, 0xE3, 0x36, + 0x19, 0xCC, 0x66, 0xB3, 0xE7, 0x32, 0x98, 0x4D, + 0x30, 0xE5, 0x4F, 0x9A, 0xCE, 0x1B, 0xB1, 0x64, + 0x72, 0xA7, 0x0D, 0xD8, 0x8C, 0x59, 0xF3, 0x26, + 0x5B, 0x8E, 0x24, 0xF1, 0xA5, 0x70, 0xDA, 0x0F, + 0x20, 0xF5, 0x5F, 0x8A, 0xDE, 0x0B, 0xA1, 0x74, + 0x09, 0xDC, 0x76, 0xA3, 0xF7, 0x22, 0x88, 0x5D, + 0xD6, 0x03, 0xA9, 0x7C, 0x28, 0xFD, 0x57, 0x82, + 0xFF, 0x2A, 0x80, 0x55, 0x01, 0xD4, 0x7E, 0xAB, + 0x84, 0x51, 0xFB, 0x2E, 0x7A, 0xAF, 0x05, 0xD0, + 0xAD, 0x78, 0xD2, 0x07, 0x53, 0x86, 0x2C, 0xF9 +}; + +uint8_t crc8(const uint8_t * ptr, uint32_t len) +{ + uint8_t crc = 0; + for (uint32_t i=0; i + +enum { + CRC_1021, + CRC_1189, +}; + +uint8_t crc8(const uint8_t * ptr, uint32_t len); +uint8_t crc8_BA(const uint8_t * ptr, uint32_t len); +uint16_t crc16(uint8_t index, const uint8_t * buf, uint32_t len, uint16_t start = 0); + +// CRC16 implementation according to CCITT standards +static const unsigned short crc16tab_1021[256] = { + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, + 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, + 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, + 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, + 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, + 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, + 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, + 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, + 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, + 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, + 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, + 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, + 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, + 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, + 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, + 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, + 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, + 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, + 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, + 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, + 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, + 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, + 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, + 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, + 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, + 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, + 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, + 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, + 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, + 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, + 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, + 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 +}; + +static const unsigned short crc16tab_1189[256] = { + 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf, + 0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7, + 0x1081,0x0108,0x3393,0x221a,0x56a5,0x472c,0x75b7,0x643e, + 0x9cc9,0x8d40,0xbfdb,0xae52,0xdaed,0xcb64,0xf9ff,0xe876, + 0x2102,0x308b,0x0210,0x1399,0x6726,0x76af,0x4434,0x55bd, + 0xad4a,0xbcc3,0x8e58,0x9fd1,0xeb6e,0xfae7,0xc87c,0xd9f5, + 0x3183,0x200a,0x1291,0x0318,0x77a7,0x662e,0x54b5,0x453c, + 0xbdcb,0xac42,0x9ed9,0x8f50,0xfbef,0xea66,0xd8fd,0xc974, + 0x4204,0x538d,0x6116,0x709f,0x0420,0x15a9,0x2732,0x36bb, + 0xce4c,0xdfc5,0xed5e,0xfcd7,0x8868,0x99e1,0xab7a,0xbaf3, + 0x5285,0x430c,0x7197,0x601e,0x14a1,0x0528,0x37b3,0x263a, + 0xdecd,0xcf44,0xfddf,0xec56,0x98e9,0x8960,0xbbfb,0xaa72, + 0x6306,0x728f,0x4014,0x519d,0x2522,0x34ab,0x0630,0x17b9, + 0xef4e,0xfec7,0xcc5c,0xddd5,0xa96a,0xb8e3,0x8a78,0x9bf1, + 0x7387,0x620e,0x5095,0x411c,0x35a3,0x242a,0x16b1,0x0738, + 0xffcf,0xee46,0xdcdd,0xcd54,0xb9eb,0xa862,0x9af9,0x8b70, + 0x8408,0x9581,0xa71a,0xb693,0xc22c,0xd3a5,0xe13e,0xf0b7, + 0x0840,0x19c9,0x2b52,0x3adb,0x4e64,0x5fed,0x6d76,0x7cff, + 0x9489,0x8500,0xb79b,0xa612,0xd2ad,0xc324,0xf1bf,0xe036, + 0x18c1,0x0948,0x3bd3,0x2a5a,0x5ee5,0x4f6c,0x7df7,0x6c7e, + 0xa50a,0xb483,0x8618,0x9791,0xe32e,0xf2a7,0xc03c,0xd1b5, + 0x2942,0x38cb,0x0a50,0x1bd9,0x6f66,0x7eef,0x4c74,0x5dfd, + 0xb58b,0xa402,0x9699,0x8710,0xf3af,0xe226,0xd0bd,0xc134, + 0x39c3,0x284a,0x1ad1,0x0b58,0x7fe7,0x6e6e,0x5cf5,0x4d7c, + 0xc60c,0xd785,0xe51e,0xf497,0x8028,0x91a1,0xa33a,0xb2b3, + 0x4a44,0x5bcd,0x6956,0x78df,0x0c60,0x1de9,0x2f72,0x3efb, + 0xd68d,0xc704,0xf59f,0xe416,0x90a9,0x8120,0xb3bb,0xa232, + 0x5ac5,0x4b4c,0x79d7,0x685e,0x1ce1,0x0d68,0x3ff3,0x2e7a, + 0xe70e,0xf687,0xc41c,0xd595,0xa12a,0xb0a3,0x8238,0x93b1, + 0x6b46,0x7acf,0x4854,0x59dd,0x2d62,0x3ceb,0x0e70,0x1ff9, + 0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330, + 0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 +}; + +#endif diff --git a/radio/src/datastructs.h b/radio/src/datastructs.h index 2736e3c2f93..25fe13ed584 100644 --- a/radio/src/datastructs.h +++ b/radio/src/datastructs.h @@ -105,7 +105,7 @@ static inline void check_struct() CHKSIZE(TimerData, 3); CHKSIZE(FlightModeData, 30); - CHKSIZE(RadioData, 85); + CHKSIZE(RadioData, 86); #endif /* board specific ifdefs*/ @@ -117,33 +117,33 @@ static inline void check_struct() CHKSIZE(TrainerData, 16); #if defined(PCBXLITES) - CHKSIZE(RadioData, 861); + CHKSIZE(RadioData, 862); CHKSIZE(ModelData, 6164); #elif defined(PCBXLITE) - CHKSIZE(RadioData, 859); + CHKSIZE(RadioData, 860); CHKSIZE(ModelData, 6164); #elif defined(RADIO_TPRO) - CHKSIZE(RadioData, 842); + CHKSIZE(RadioData, 843); CHKSIZE(ModelData, 6189); #elif defined(PCBX7) - CHKSIZE(RadioData, 865); + CHKSIZE(RadioData, 866); CHKSIZE(ModelData, 6164); #elif defined(PCBX9E) - CHKSIZE(RadioData, 955); + CHKSIZE(RadioData, 956); CHKSIZE(ModelData, 6616); #elif defined(PCBX9D) || defined(PCBX9DP) - CHKSIZE(RadioData, 897); + CHKSIZE(RadioData, 898); CHKSIZE(ModelData, 6608); #elif defined(PCBHORUS) #if defined(PCBX10) - CHKSIZE(RadioData, 923); + CHKSIZE(RadioData, 924); CHKSIZE(ModelData, 11028); #else - CHKSIZE(RadioData, 905); + CHKSIZE(RadioData, 906); CHKSIZE(ModelData, 11026); #endif #elif defined(PCBNV14) - CHKSIZE(RadioData, 851); + CHKSIZE(RadioData, 852); CHKSIZE(ModelData, 10842); #endif diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index e4578c6dbeb..22a3657d947 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -804,6 +804,8 @@ PACK(struct TrainerData { PACK(struct RadioData { // Real attributes + NOBACKUP(uint8_t manuallyEdited:1); + NOBACKUP(int8_t spare0:7 SKIP); CUST_ATTR(semver,nullptr,w_semver); CUST_ATTR(board,nullptr,w_board); CalibData calib[NUM_STICKS + STORAGE_NUM_POTS + STORAGE_NUM_SLIDERS + STORAGE_NUM_MOUSE_ANALOGS] NO_IDX; diff --git a/radio/src/gui/colorlcd/menu_radio.cpp b/radio/src/gui/colorlcd/menu_radio.cpp index 691f72b405e..5ff80bce725 100644 --- a/radio/src/gui/colorlcd/menu_radio.cpp +++ b/radio/src/gui/colorlcd/menu_radio.cpp @@ -44,3 +44,8 @@ RadioMenu::RadioMenu(): addTab(new RadioHardwarePage()); addTab(new RadioVersionPage()); } + +RadioMenu::~RadioMenu() +{ + storageCheck(true); +} diff --git a/radio/src/gui/colorlcd/menu_radio.h b/radio/src/gui/colorlcd/menu_radio.h index 1e5048308fd..df92a2a0ee1 100644 --- a/radio/src/gui/colorlcd/menu_radio.h +++ b/radio/src/gui/colorlcd/menu_radio.h @@ -27,6 +27,7 @@ class RadioMenu: public TabsGroup { public: RadioMenu(); + ~RadioMenu(); }; #endif // _MENU_RADIO_H_ diff --git a/radio/src/sdcard.h b/radio/src/sdcard.h index 3665bca2acd..fd550081481 100644 --- a/radio/src/sdcard.h +++ b/radio/src/sdcard.h @@ -68,6 +68,10 @@ const char RADIO_SETTINGS_PATH[] = RADIO_PATH PATH_SEPARATOR RADIO_FILENAME; const char MODELSLIST_YAML_PATH[] = MODELS_PATH PATH_SEPARATOR "models.yml"; const char FALLBACK_MODELSLIST_YAML_PATH[] = RADIO_PATH PATH_SEPARATOR "models.yml"; const char RADIO_SETTINGS_YAML_PATH[] = RADIO_PATH PATH_SEPARATOR "radio.yml"; +const char RADIO_SETTINGS_TMPFILE_YAML_PATH[] = RADIO_PATH PATH_SEPARATOR "radio_new.yml"; +const char RADIO_SETTINGS_ERRORFILE_YAML_PATH[] = RADIO_PATH PATH_SEPARATOR "radio_error.yml"; + +const char YAMLFILE_CHECKSUM_TAG_NAME[] = "checksum"; #endif #define SPLASH_FILE "splash.png" #endif diff --git a/radio/src/storage/conversions/conversions_220_221.cpp b/radio/src/storage/conversions/conversions_220_221.cpp index fdde3b458f8..68f79ce5907 100644 --- a/radio/src/storage/conversions/conversions_220_221.cpp +++ b/radio/src/storage/conversions/conversions_220_221.cpp @@ -58,6 +58,7 @@ static const char* convertData_220_to_221( memset(data, 0, size); uint8_t version; + const char* error = loadFileBin(path, data, size, &version); if (!error) { if (patchBinary) patchBinary(data); @@ -65,9 +66,11 @@ static const char* convertData_220_to_221( char output_fname[FF_MAX_LFN+1]; strncpy(output_fname, path, FF_MAX_LFN); output_fname[FF_MAX_LFN] = '\0'; - + + uint16_t file_checksum = 0; + YamlFileChecksum(root_node, data, &file_checksum); patchFilenameToYaml(output_fname); - error = writeFileYaml(output_fname, root_node, data); + error = writeFileYaml(output_fname, root_node, data, file_checksum); } free(data); @@ -157,7 +160,7 @@ const char* convertModelData_220_to_221(uint8_t id) char model_idx[MODELIDX_STRLEN + sizeof(YAML_EXT)]; getModelNumberStr(id, model_idx); GET_FILENAME(path, MODELS_PATH, model_idx, YAML_EXT); - error = writeFileYaml(path, yaml_conv_220::get_modeldata_nodes(), data); + error = writeFileYaml(path, yaml_conv_220::get_modeldata_nodes(), data, 0); } else { // ERROR: size mismatch !!! } @@ -174,9 +177,13 @@ const char* convertRadioData_220_to_221() const char* error = nullptr; uint16_t read = eeLoadGeneralSettingsData(data, size); + uint16_t file_checksum = 0; + + YamlFileChecksum(yaml_conv_220::get_radiodata_nodes(), data, &file_checksum); + if (read == size) { error = writeFileYaml(RADIO_SETTINGS_YAML_PATH, - yaml_conv_220::get_radiodata_nodes(), data); + yaml_conv_220::get_radiodata_nodes(), data, file_checksum); } else { // ERROR: size mismatch !!! } diff --git a/radio/src/storage/sdcard_common.h b/radio/src/storage/sdcard_common.h index e55ab68ebc5..a64ec516740 100644 --- a/radio/src/storage/sdcard_common.h +++ b/radio/src/storage/sdcard_common.h @@ -53,7 +53,7 @@ const char *loadFileBin(const char *fullpath, uint8_t *data, // writes a complete YAML file struct YamlNode; -const char* writeFileYaml(const char* path, const YamlNode* root_node, uint8_t* data); +const char* writeFileYaml(const char* path, const YamlNode* root_node, uint8_t* data, uint16_t checksum); void getModelPath(char * path, const char * filename, const char* pathName = STR_MODELS_PATH); diff --git a/radio/src/storage/sdcard_yaml.cpp b/radio/src/storage/sdcard_yaml.cpp index 6dc48c86fed..bf827ca2e6b 100644 --- a/radio/src/storage/sdcard_yaml.cpp +++ b/radio/src/storage/sdcard_yaml.cpp @@ -30,6 +30,8 @@ #include "yaml/yaml_tree_walker.h" #include "yaml/yaml_parser.h" #include "yaml/yaml_datastructs.h" +#include "yaml/yaml_bits.h" + #if defined(EEPROM_RLC) #include "storage/eeprom_common.h" @@ -38,32 +40,85 @@ #include "storage/conversions/conversions.h" -const char * readYamlFile(const char* fullpath, const YamlParserCalls* calls, void* parser_ctx) +const char * readYamlFile(const char* fullpath, const YamlParserCalls* calls, void* parser_ctx, ChecksumResult* checksum_result) { FIL file; UINT bytes_read; + UINT total_bytes = 0; FRESULT result = f_open(&file, fullpath, FA_OPEN_EXISTING | FA_READ); if (result != FR_OK) { return SDCARD_ERROR(result); } - + YamlParser yp; //TODO: move to re-usable buffer yp.init(calls, parser_ctx); - char buffer[32]; - while (f_read(&file, buffer, sizeof(buffer), &bytes_read) == FR_OK) { + uint16_t calculated_checksum = 0xFFFF; + uint16_t file_checksum = 0; - // reached EOF? - if (bytes_read == 0) + bool first_block = true; + char buffer[32]; + while (f_read(&file, buffer, sizeof(buffer)-1, &bytes_read) == FR_OK) { + if (bytes_read == 0) // EOF break; - + total_bytes += bytes_read; + + uint16_t skip = 0; + if(first_block) { + // Get the 'checksum' value and skip from further YAML processing + // The checksum must be first in the first buffer read from file + first_block = false; + const char *skipValue = "checksum: "; + if(strncmp(buffer, skipValue, strlen(skipValue)) == 0) { + skip = 10; + char* startPos = buffer + strlen(skipValue); + char* endPos = startPos; + // Advance through the value + while((*endPos != '\r') && (*endPos != '\n')) { + if (endPos > buffer + bytes_read) { + return SDCARD_ERROR( FR_INT_ERR ); + } + endPos++; + } + // Skip trailing newline + while((*endPos == '\r') || (*endPos == '\n')) { + *endPos = 0; + endPos++; + } + + file_checksum = atoi(startPos); + skip = endPos - buffer; + } + } + + // Calculate checksum on read block only if we are called with a pointer to write the resulting checksum + if (checksum_result != NULL) { + calculated_checksum = crc16(0, (const uint8_t *)buffer + skip, bytes_read - skip, calculated_checksum); + } + if (f_eof(&file)) yp.set_eof(); - if (yp.parse(buffer, bytes_read) != YamlParser::CONTINUE_PARSING) + if (yp.parse(buffer + skip, bytes_read - skip) != YamlParser::CONTINUE_PARSING) break; } - f_close(&file); + + if (checksum_result != NULL) { + // Special case to handle "old" files with no checksum field + // 25 was arbitrarily chosen as the minimum realistic file size + // - The issue is to allow old files to pass, while still detecting garbled files + if ( (file_checksum == 0) && (total_bytes > 25) ) { + *checksum_result = ChecksumResult::Success; + } else { + // Normal case - compare read and calculated checksum + if (calculated_checksum == file_checksum) { + *checksum_result = ChecksumResult::Success; + } else { + *checksum_result = ChecksumResult::Failed; + } + } + } + return NULL; } @@ -92,21 +147,57 @@ void storageCreateModelsList() // SDCARD storage interface // +static const char * attemptLoad(const char *filename, ChecksumResult* checksum_status) +{ + YamlTreeWalker tree; + tree.reset(get_radiodata_nodes(), (uint8_t*)&g_eeGeneral); + return readYamlFile(filename, YamlTreeWalker::get_parser_calls(), &tree, checksum_status); +} + const char * loadRadioSettingsYaml() { // YAML reader TRACE("YAML radio settings reader"); - YamlTreeWalker tree; - tree.reset(get_radiodata_nodes(), (uint8_t*)&g_eeGeneral); - - return readYamlFile(RADIO_SETTINGS_YAML_PATH, YamlTreeWalker::get_parser_calls(), &tree); + ChecksumResult checksum_status; + const char* p = attemptLoad(RADIO_SETTINGS_YAML_PATH, &checksum_status); + + if((p != NULL) || (checksum_status != ChecksumResult::Success) ) { + // Read failed or checksum check failed + FRESULT result = FR_OK; + TRACE("radio settings: Reading failed"); + if ( (p == NULL) && g_eeGeneral.manuallyEdited) { + // Read sussessfull, checksum failed, manuallyEdited set + TRACE("File has been manually edited - ignoring checksum mismatch"); + g_eeGeneral.manuallyEdited = 0; + storageDirty(EE_GENERAL); // Trigger a save on sucessfull recovery + } else { + TRACE("File is corrupted, attempting alternative file"); + f_unlink(RADIO_SETTINGS_ERRORFILE_YAML_PATH); + result = f_rename(RADIO_SETTINGS_YAML_PATH, RADIO_SETTINGS_ERRORFILE_YAML_PATH); // Save corrupted file for later analysis + p = attemptLoad(RADIO_SETTINGS_TMPFILE_YAML_PATH, &checksum_status); + if (p == NULL && (checksum_status == ChecksumResult::Success)) { + f_unlink(RADIO_SETTINGS_YAML_PATH); + result = f_rename(RADIO_SETTINGS_TMPFILE_YAML_PATH, RADIO_SETTINGS_YAML_PATH); // Rename previously saved file to active file + if (result != FR_OK) { + ALERT(STR_STORAGE_WARNING, TR_RADIO_DATA_UNRECOVERABLE, AU_BAD_RADIODATA); + return SDCARD_ERROR(result); + } + } + TRACE("Unable to recover radio data"); + ALERT(STR_STORAGE_WARNING, p == NULL ? TR_RADIO_DATA_RECOVERED : TR_RADIO_DATA_UNRECOVERABLE, AU_BAD_RADIODATA); + } + } + return p; } const char * loadRadioSettings() { FILINFO fno; - if (f_stat(RADIO_SETTINGS_YAML_PATH, &fno) != FR_OK) { + + if ( (f_stat(RADIO_SETTINGS_YAML_PATH, &fno) != FR_OK) && ((f_stat(RADIO_SETTINGS_TMPFILE_YAML_PATH, &fno) != FR_OK)) ) { + // If neither the radio configuraion YAML file or the temporary file generated on write exist, this must be a first run with YAML support. + // - thus requiring a conversion from binary to YAML. #if STORAGE_CONVERSIONS < 221 #if defined(STORAGE_MODELSLIST) uint8_t version; @@ -142,6 +233,47 @@ const char * loadRadioSettings() return error; } + + +struct yaml_checksummer_ctx { + FRESULT result; + uint16_t checksum; + bool checksum_invalid; +}; + +static bool yaml_checksummer(void* opaque, const char* str, size_t len) +{ + yaml_checksummer_ctx* ctx = (yaml_checksummer_ctx*)opaque; + + ctx->checksum = crc16(0, (const uint8_t *) str, len, ctx->checksum); + return true; +} + +bool YamlFileChecksum(const YamlNode* root_node, uint8_t* data, uint16_t* checksum) +{ + YamlTreeWalker tree; + tree.reset(root_node, data); + + yaml_checksummer_ctx ctx; + ctx.result = FR_OK; + ctx.checksum = 0xFFFF; + ctx.checksum_invalid = false; + + if (!tree.generate(yaml_checksummer, &ctx)) { + if (ctx.result != FR_OK) { + ctx.checksum_invalid = true; + return false; + } + } + + if(checksum != NULL) { + *checksum = ctx.checksum; + } + + return true; +} + + struct yaml_writer_ctx { FIL* file; FRESULT result; @@ -160,7 +292,7 @@ static bool yaml_writer(void* opaque, const char* str, size_t len) return (ctx->result == FR_OK) && (bytes_written == len); } -const char* writeFileYaml(const char* path, const YamlNode* root_node, uint8_t* data) +const char* writeFileYaml(const char* path, const YamlNode* root_node, uint8_t* data, uint16_t checksum) { FIL file; @@ -168,14 +300,24 @@ const char* writeFileYaml(const char* path, const YamlNode* root_node, uint8_t* if (result != FR_OK) { return SDCARD_ERROR(result); } - YamlTreeWalker tree; tree.reset(root_node, data); yaml_writer_ctx ctx; ctx.file = &file; ctx.result = FR_OK; - + + // Try to add CRC + if (checksum != 0) { + if (!yaml_writer(&ctx, YAMLFILE_CHECKSUM_TAG_NAME, strlen(YAMLFILE_CHECKSUM_TAG_NAME))) return NULL; + if (!yaml_writer(&ctx, ": ", 2)) return SDCARD_ERROR(FR_INVALID_PARAMETER); + const char* p_out = NULL; + p_out = yaml_unsigned2str((int)checksum); + if (p_out && !yaml_writer(&ctx, p_out, strlen(p_out))) return SDCARD_ERROR(FR_INVALID_PARAMETER); + yaml_writer(&ctx, "\r\n", 2); + } + + if (!tree.generate(yaml_writer, &ctx)) { if (ctx.result != FR_OK) { f_close(&file); @@ -190,8 +332,25 @@ const char* writeFileYaml(const char* path, const YamlNode* root_node, uint8_t* const char * writeGeneralSettings() { TRACE("YAML radio settings writer"); - return writeFileYaml(RADIO_SETTINGS_YAML_PATH, get_radiodata_nodes(), - (uint8_t*)&g_eeGeneral); + uint16_t file_checksum = 0; + + YamlFileChecksum(get_radiodata_nodes(), (uint8_t*)&g_eeGeneral, &file_checksum); + g_eeGeneral.manuallyEdited = false; + + const char *p = writeFileYaml(RADIO_SETTINGS_TMPFILE_YAML_PATH, get_radiodata_nodes(), + (uint8_t*)&g_eeGeneral, file_checksum); + TRACE("generalSettings written with checksum %u", file_checksum); + + if (p != NULL) { + return p; + } + f_unlink(RADIO_SETTINGS_YAML_PATH); + + FRESULT result = f_rename(RADIO_SETTINGS_TMPFILE_YAML_PATH, RADIO_SETTINGS_YAML_PATH); + if(result != FR_OK) + return SDCARD_ERROR(result); + + return nullptr; } @@ -213,7 +372,7 @@ const char * readModelYaml(const char * filename, uint8_t * buffer, uint32_t siz TRACE("cannot find YAML data nodes for object size (size=%d)", size); return "YAML size error"; } - + char path[256]; getModelPath(path, filename, pathName); @@ -241,7 +400,7 @@ const char * readModelYaml(const char * filename, uint8_t * buffer, uint32_t siz // md->swashR.elevatorWeight = 100; } - return readYamlFile(path, YamlTreeWalker::get_parser_calls(), &tree); + return readYamlFile(path, YamlTreeWalker::get_parser_calls(), &tree, NULL); } static const char _wrongExtentionError[] = "wrong file extension"; @@ -261,7 +420,7 @@ const char * writeModelYaml(const char* filename) TRACE("YAML model writer"); char path[256]; getModelPath(path, filename); - return writeFileYaml(path, get_modeldata_nodes(), (uint8_t*)&g_model); + return writeFileYaml(path, get_modeldata_nodes(), (uint8_t*)&g_model,0 ); } #if !defined(STORAGE_MODELSLIST) @@ -329,7 +488,7 @@ bool copyModel(uint8_t dst, uint8_t src) char model_idx_dst[MODELIDX_STRLEN]; getModelNumberStr(src, model_idx_src); getModelNumberStr(dst, model_idx_dst); - + GET_FILENAME(fname_src, MODELS_PATH, model_idx_src, YAML_EXT); GET_FILENAME(fname_dst, MODELS_PATH, model_idx_dst, YAML_EXT); @@ -350,7 +509,7 @@ void swapModels(uint8_t id1, uint8_t id2) char model_idx_2[MODELIDX_STRLEN]; getModelNumberStr(id1, model_idx_1); getModelNumberStr(id2, model_idx_2); - + GET_FILENAME(fname1, MODELS_PATH, model_idx_1, YAML_EXT); GET_FILENAME(fname1_tmp, MODELS_PATH, model_idx_1, ".tmp"); GET_FILENAME(fname2, MODELS_PATH, model_idx_2, YAML_EXT); @@ -451,7 +610,7 @@ const char * backupModel(uint8_t idx) char model_idx[MODELIDX_STRLEN + sizeof(YAML_EXT)]; getModelNumberStr(idx, model_idx); strcat(model_idx, STR_YAML_EXT); - + return sdCopyFile(model_idx, STR_MODELS_PATH, buf, STR_BACKUP_PATH); } diff --git a/radio/src/storage/sdcard_yaml.h b/radio/src/storage/sdcard_yaml.h index 884cebb4455..5f94fbee9a8 100644 --- a/radio/src/storage/sdcard_yaml.h +++ b/radio/src/storage/sdcard_yaml.h @@ -20,9 +20,14 @@ */ #pragma once + +enum class ChecksumResult {Success, Failed, None}; + constexpr uint8_t MODELIDX_STRLEN = sizeof(MODEL_FILENAME_PREFIX "00"); const char * loadRadioSettingsYaml(); const char * writeModelYaml(const char* filename); const char * readModelYaml(const char * filename, uint8_t * buffer, uint32_t size, const char* pathName = STR_MODELS_PATH); +bool YamlFileChecksum(const YamlNode* root_node, uint8_t* data, uint16_t* checksum); + void getModelNumberStr(uint8_t idx, char* model_idx); diff --git a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp index 28c49f31c2b..9a7d04ec325 100644 --- a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp @@ -355,6 +355,8 @@ static const struct YamlNode struct_OpenTxTheme__PersistentData[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 9, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_t12.cpp b/radio/src/storage/yaml/yaml_datastructs_t12.cpp index 795342ab85d..69f884a3d63 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t12.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t12.cpp @@ -326,6 +326,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_t8.cpp b/radio/src/storage/yaml/yaml_datastructs_t8.cpp index aaab3f771db..ae84738d9a3 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t8.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t8.cpp @@ -326,6 +326,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_tlite.cpp b/radio/src/storage/yaml/yaml_datastructs_tlite.cpp index aaab3f771db..ae84738d9a3 100644 --- a/radio/src/storage/yaml/yaml_datastructs_tlite.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tlite.cpp @@ -326,6 +326,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_tpro.cpp b/radio/src/storage/yaml/yaml_datastructs_tpro.cpp index cfc074e5ccc..c3aa06dc388 100644 --- a/radio/src/storage/yaml/yaml_datastructs_tpro.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tpro.cpp @@ -334,6 +334,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_tx12.cpp b/radio/src/storage/yaml/yaml_datastructs_tx12.cpp index efd9145afb0..23a2e0c7478 100644 --- a/radio/src/storage/yaml/yaml_datastructs_tx12.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tx12.cpp @@ -326,6 +326,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_tx12mk2.cpp b/radio/src/storage/yaml/yaml_datastructs_tx12mk2.cpp index fea5586b921..459d94c6663 100755 --- a/radio/src/storage/yaml/yaml_datastructs_tx12mk2.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tx12mk2.cpp @@ -326,6 +326,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_x10.cpp b/radio/src/storage/yaml/yaml_datastructs_x10.cpp index f6f5f0286c0..d2afb8c3687 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x10.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x10.cpp @@ -380,6 +380,8 @@ static const struct YamlNode struct_OpenTxTheme__PersistentData[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 17, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_x12s.cpp b/radio/src/storage/yaml/yaml_datastructs_x12s.cpp index 853d844577b..85f697c12b8 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x12s.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x12s.cpp @@ -378,6 +378,8 @@ static const struct YamlNode struct_OpenTxTheme__PersistentData[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 15, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_x7.cpp b/radio/src/storage/yaml/yaml_datastructs_x7.cpp index aaab3f771db..ae84738d9a3 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x7.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x7.cpp @@ -326,6 +326,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_x9d.cpp b/radio/src/storage/yaml/yaml_datastructs_x9d.cpp index 074edef05ac..13fc1a99a29 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9d.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9d.cpp @@ -333,6 +333,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 9, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_x9e.cpp b/radio/src/storage/yaml/yaml_datastructs_x9e.cpp index 6cf360546e7..4c0079eb41b 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9e.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9e.cpp @@ -372,6 +372,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 12, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_x9lite.cpp b/radio/src/storage/yaml/yaml_datastructs_x9lite.cpp index 1b7e0be4d6c..99626027997 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9lite.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9lite.cpp @@ -313,6 +313,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 5, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_x9lites.cpp b/radio/src/storage/yaml/yaml_datastructs_x9lites.cpp index a0367a0ec78..f18ba9a9e1b 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9lites.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9lites.cpp @@ -321,6 +321,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 5, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_xlite.cpp b/radio/src/storage/yaml/yaml_datastructs_xlite.cpp index 5062527fc9c..a7d5755e740 100644 --- a/radio/src/storage/yaml/yaml_datastructs_xlite.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_xlite.cpp @@ -318,6 +318,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_xlites.cpp b/radio/src/storage/yaml/yaml_datastructs_xlites.cpp index f6932433ea1..98005e13a24 100644 --- a/radio/src/storage/yaml/yaml_datastructs_xlites.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_xlites.cpp @@ -320,6 +320,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_zorro.cpp b/radio/src/storage/yaml/yaml_datastructs_zorro.cpp index fea5586b921..459d94c6663 100644 --- a/radio/src/storage/yaml/yaml_datastructs_zorro.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_zorro.cpp @@ -326,6 +326,8 @@ static const struct YamlNode struct_string_24[] = { YAML_END }; static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_PADDING( 7 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 6, struct_CalibData, NULL), diff --git a/radio/src/targets/simu/simufatfs.cpp b/radio/src/targets/simu/simufatfs.cpp index e7551adc8b4..6df8322345e 100644 --- a/radio/src/targets/simu/simufatfs.cpp +++ b/radio/src/targets/simu/simufatfs.cpp @@ -134,7 +134,7 @@ bool redirectToSettingsDirectory(const std::string & path) #endif #endif #if defined(SDCARD_YAML) - if (path == MODELSLIST_YAML_PATH || path == RADIO_SETTINGS_YAML_PATH) + if (path == MODELSLIST_YAML_PATH || path == RADIO_SETTINGS_YAML_PATH || path == RADIO_SETTINGS_TMPFILE_YAML_PATH || path == RADIO_SETTINGS_ERRORFILE_YAML_PATH) return true; if (startsWith(path, MODELS_PATH) && endsWith(path, YAML_EXT)) return true; #endif diff --git a/radio/src/translations.cpp b/radio/src/translations.cpp index 5a121163e94..38ffee712a6 100644 --- a/radio/src/translations.cpp +++ b/radio/src/translations.cpp @@ -859,6 +859,8 @@ const char STR_STORAGE_WARNING[] = TR_STORAGE_WARNING; const char STR_STORAGE_FORMAT[] = TR_STORAGE_FORMAT; const char STR_SDCARD_CONVERSION_REQUIRE[] = TR_SDCARD_CONVERSION_REQUIRE; const char STR_CONVERTING[] = TR_CONVERTING; +const char STR_RADIO_DATA_UNRECOVERABLE[] = TR_RADIO_DATA_UNRECOVERABLE; +const char STR_RADIO_DATA_RECOVERED[] = TR_RADIO_DATA_RECOVERED; #endif const char STR_EEPROMOVERFLOW[] = TR_EEPROMOVERFLOW; const char STR_EEPROM_CONVERTING[] = TR_EEPROM_CONVERTING; diff --git a/radio/src/translations.h b/radio/src/translations.h index 4105336fa0e..9d65971ac0b 100644 --- a/radio/src/translations.h +++ b/radio/src/translations.h @@ -403,6 +403,8 @@ extern const char STR_THROTTLE_NOT_IDLE[]; extern const char STR_ALARMSDISABLED[]; extern const char STR_PRESSANYKEY[]; extern const char STR_BAD_RADIO_DATA[]; +extern const char STR_RADIO_DATA_UNRECOVERABLE[]; +extern const char STR_RADIO_DATA_RECOVERED[]; extern const char STR_STORAGE_FORMAT[]; extern const char STR_EEPROMOVERFLOW[]; extern const char STR_TRIMS2OFFSETS[]; diff --git a/radio/src/translations/cn.h b/radio/src/translations/cn.h index e2b3ce71e72..07ce50a8213 100644 --- a/radio/src/translations/cn.h +++ b/radio/src/translations/cn.h @@ -517,6 +517,8 @@ #define TR_PRESSANYKEY TR("\010按任意键", "按任意键") #define TR_BADEEPROMDATA "存储数据错误" #define TR_BAD_RADIO_DATA "系统数据错误" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "格式化存储" #define TR_STORAGE_FORMAT "存储准备" #define TR_EEPROMOVERFLOW "存储超限" diff --git a/radio/src/translations/cz.h b/radio/src/translations/cz.h index b0cc47452f7..8ddbc528ec3 100644 --- a/radio/src/translations/cz.h +++ b/radio/src/translations/cz.h @@ -538,6 +538,8 @@ #define TR_PRESSANYKEY TR("\006Stiskni klávesu", "Stiskni klávesu") #define TR_BADEEPROMDATA TR("\006Chyba dat EEPROM", "Chyba dat EEPROM") #define TR_BAD_RADIO_DATA "Chybná data rádia" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING TR("\004Formatování EEPROM", "Formatování EEPROM") #define TR_STORAGE_FORMAT "Storage Preparation" #define TR_EEPROMOVERFLOW "Přetekla EEPROM" diff --git a/radio/src/translations/da.h b/radio/src/translations/da.h index 7952d4ddf7b..f0d37f4e738 100644 --- a/radio/src/translations/da.h +++ b/radio/src/translations/da.h @@ -518,6 +518,8 @@ #define TR_PRESSANYKEY TR("\010Tryk en tast", "Tryk en tast") #define TR_BADEEPROMDATA "Dårlig EEprom data" #define TR_BAD_RADIO_DATA "Dårlig radio data" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "Formaterer EEPROM" #define TR_STORAGE_FORMAT "Lager klargøres" #define TR_EEPROMOVERFLOW "EEPROM overflow" diff --git a/radio/src/translations/de.h b/radio/src/translations/de.h index da66cb62f90..56ed24a8d69 100644 --- a/radio/src/translations/de.h +++ b/radio/src/translations/de.h @@ -523,6 +523,8 @@ #define TR_PRESSANYKEY TR("Taste drücken",CENTER"Taste drücken") #define TR_BADEEPROMDATA "EEPROM ungültig" #define TR_BAD_RADIO_DATA "Bad Radio Data" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "EEPROM Initialisieren" #define TR_STORAGE_FORMAT "Speicher Vorbereiten" #define TR_EEPROMOVERFLOW "EEPROM Überlauf" diff --git a/radio/src/translations/en.h b/radio/src/translations/en.h index 1ff98e2be0c..80e6c1a6538 100644 --- a/radio/src/translations/en.h +++ b/radio/src/translations/en.h @@ -517,6 +517,8 @@ #define TR_PRESSANYKEY TR("\010Press any Key", "Press any key") #define TR_BADEEPROMDATA "Bad EEprom data" #define TR_BAD_RADIO_DATA "Bad radio data" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "Formatting EEPROM" #define TR_STORAGE_FORMAT "Storage preparation" #define TR_EEPROMOVERFLOW "EEPROM overflow" diff --git a/radio/src/translations/es.h b/radio/src/translations/es.h index 426f372302e..db83f066c70 100644 --- a/radio/src/translations/es.h +++ b/radio/src/translations/es.h @@ -513,6 +513,8 @@ #define TR_PRESSANYKEY TR("\010Pulsa una tecla", "Pulsa una tecla") #define TR_BADEEPROMDATA "Error datos EEPROM" #define TR_BAD_RADIO_DATA "Error datos radio" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "Formateo EEPROM" #define TR_STORAGE_FORMAT "Preparación alamacenamiento" #define TR_EEPROMOVERFLOW "Desborde EEPROM" diff --git a/radio/src/translations/fi.h b/radio/src/translations/fi.h index 554514e934c..986360f3176 100644 --- a/radio/src/translations/fi.h +++ b/radio/src/translations/fi.h @@ -528,6 +528,8 @@ #define TR_PRESSANYKEY TR("\010Press any Key", "Press any Key") #define TR_BADEEPROMDATA "Bad EEPROM Data" #define TR_BAD_RADIO_DATA "Bad Radio Data" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "Formatting EEPROM" #define TR_STORAGE_FORMAT "Storage Preparation" #define TR_EEPROMOVERFLOW "EEPROM overflow" diff --git a/radio/src/translations/fr.h b/radio/src/translations/fr.h index 01cbc0e012f..008122ea647 100644 --- a/radio/src/translations/fr.h +++ b/radio/src/translations/fr.h @@ -542,6 +542,8 @@ #define TR_PRESSANYKEY TR("Touche pour continuer", "Touche pour continuer") #define TR_BADEEPROMDATA "EEPROM corrompue" #define TR_BAD_RADIO_DATA "Réglages radio corrompus" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "Formatage EEPROM" #define TR_STORAGE_FORMAT "Préparation stockage" #define TR_EEPROMOVERFLOW "Dépassement EEPROM" diff --git a/radio/src/translations/it.h b/radio/src/translations/it.h index 40f929fea8f..5c5b221812b 100644 --- a/radio/src/translations/it.h +++ b/radio/src/translations/it.h @@ -522,6 +522,8 @@ #define TR_PRESSANYKEY "\010Premi un tasto" #define TR_BADEEPROMDATA "Dati corrotti!" #define TR_BAD_RADIO_DATA "Dati radio errati" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "Formatto EEPROM..." #define TR_STORAGE_FORMAT "Preparazione storage" #define TR_EEPROMOVERFLOW "EEPROM Piena" diff --git a/radio/src/translations/nl.h b/radio/src/translations/nl.h index e5007a83174..4d7708e6abc 100644 --- a/radio/src/translations/nl.h +++ b/radio/src/translations/nl.h @@ -524,6 +524,8 @@ #define TR_PRESSANYKEY TR("\010Druk een Toets", "Druk een Toets") #define TR_BADEEPROMDATA "EEPROM Ongeldig" #define TR_BAD_RADIO_DATA "Bad Radio Data" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "EEPROM Initialiseren" #define TR_STORAGE_FORMAT "Storage Preparation" #define TR_EEPROMOVERFLOW "EEPROM Overflow" diff --git a/radio/src/translations/pl.h b/radio/src/translations/pl.h index 426177a2146..50a30859550 100644 --- a/radio/src/translations/pl.h +++ b/radio/src/translations/pl.h @@ -518,6 +518,8 @@ #define TR_PRESSANYKEY TR("\010Wciśnij jakiś klawisz","Wciśnij jakiś klawisz") #define TR_BADEEPROMDATA "\006Błąd danych EEPROM" #define TR_BAD_RADIO_DATA "Bad Radio Data" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "\004Formatowanie EEPROM" #define TR_STORAGE_FORMAT "Storage Preparation" #define TR_EEPROMOVERFLOW "Przeładowany EEPROM" diff --git a/radio/src/translations/pt.h b/radio/src/translations/pt.h index 105ae2bcc4a..066ce9e2617 100644 --- a/radio/src/translations/pt.h +++ b/radio/src/translations/pt.h @@ -515,6 +515,8 @@ #define TR_PRESSANYKEY "\010Pressione Tecla" #define TR_BADEEPROMDATA "EEPRON INVALIDA" #define TR_BAD_RADIO_DATA "Bad Radio Data" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "Formatando EEPROM" #define TR_STORAGE_FORMAT "Storage Preparation" #define TR_EEPROMOVERFLOW "EEPROM CHEIA" diff --git a/radio/src/translations/se.h b/radio/src/translations/se.h index 86d224dce9a..2e2a6c20495 100644 --- a/radio/src/translations/se.h +++ b/radio/src/translations/se.h @@ -549,6 +549,8 @@ #define TR_PRESSANYKEY TR("\010Tryck på en knapp", "Tryck på valfri knapp") #define TR_BADEEPROMDATA "Minnet kan inte tolkas" #define TR_BAD_RADIO_DATA "Data från radion kan inte tolkas" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "Minnet formateras" #define TR_STORAGE_FORMAT "SD-Lagring förbereds" #define TR_EEPROMOVERFLOW "Minnesfel" diff --git a/radio/src/translations/tw.h b/radio/src/translations/tw.h index b3fffa24997..3220f65eb62 100644 --- a/radio/src/translations/tw.h +++ b/radio/src/translations/tw.h @@ -517,6 +517,8 @@ #define TR_PRESSANYKEY TR("\010按任意鍵", "按任意鍵") #define TR_BADEEPROMDATA "存儲數據錯誤" #define TR_BAD_RADIO_DATA "系統數據錯誤" +#define TR_RADIO_DATA_RECOVERED TR3("Using backup radio data","Using backup radio settings","Radio settings recovered from backup") +#define TR_RADIO_DATA_UNRECOVERABLE TR3("Radio settings invalid","Radio settings not valid", "Unable to read valid radio settings") #define TR_EEPROMFORMATTING "格式化存儲" #define TR_STORAGE_FORMAT "存儲準備" #define TR_EEPROMOVERFLOW "存儲超限"