diff --git a/locales/en/messages.json b/locales/en/messages.json index 729d575b..5f263e09 100644 --- a/locales/en/messages.json +++ b/locales/en/messages.json @@ -289,6 +289,14 @@ "message": "Device -
" }, + "backupFileIncompatible": { + "message": "Backup file provided was generated for previous version of the configurator and is incompatible with this version of configurator. Sorry" + }, + + "backupFileUnmigratable": { + "message": "Backup file provided was generated by a previous version of the configurator and is not migratable. Sorry." + }, + "configMigrationFrom": { "message": "Migrating configuration file generated by configurator: $1" }, @@ -797,6 +805,12 @@ "initialSetupResetText": { "message": "Restore settings to default" }, + "initialSetupButtonBackup": { + "message": "Backup" + }, + "initialSetupButtonRestore": { + "message": "Restore" + }, "initialSetupButtonRebootBootloader": { "message": "Activate Boot Loader / DFU" }, @@ -806,6 +820,12 @@ "initialSetupRebootBootloaderText": { "message": "Reboot into boot loader / DFU mode." }, + "initialSetupBackupSuccess": { + "message": "Backup saved " + }, + "initialSetupRestoreSuccess": { + "message": "Configuration restored " + }, "initialSetupButtonResetZaxis": { "message": "Reset Z axis, offset: 0 deg" }, diff --git a/src/js/backup_restore.js b/src/js/backup_restore.js new file mode 100644 index 00000000..0217a3cb --- /dev/null +++ b/src/js/backup_restore.js @@ -0,0 +1,929 @@ +'use strict'; + +// code below is highly experimental, although it runs fine on latest firmware +// the data inside nested objects needs to be verified if deep copy works properly +function configuration_backup(callback) { + let activeProfile = null; + + let version = CONFIGURATOR.version; + + if (version.indexOf(".") === -1) { + version = version + ".0.0"; + } + + const configuration = { + 'generatedBy': version, + 'apiVersion': FC.CONFIG.apiVersion, + 'profiles': [], + }; + + const profileSpecificData = [ + MSPCodes.MSP_PID_CONTROLLER, + MSPCodes.MSP_PID, + MSPCodes.MSP_RC_TUNING, + MSPCodes.MSP_ACC_TRIM, + MSPCodes.MSP_SERVO_CONFIGURATIONS, + MSPCodes.MSP_MODE_RANGES, + MSPCodes.MSP_ADJUSTMENT_RANGES, + ]; + + function update_profile_specific_data_list() { + if (semver.gt(FC.CONFIG.apiVersion, "1.12.0")) { + profileSpecificData.push(MSPCodes.MSP_SERVO_MIX_RULES); + } + if (semver.gte(FC.CONFIG.apiVersion, "1.15.0")) { + profileSpecificData.push(MSPCodes.MSP_RC_DEADBAND); + } + } + + update_profile_specific_data_list(); + + MSP.send_message(MSPCodes.MSP_STATUS, false, false, function () { + activeProfile = FC.CONFIG.profile; + select_profile(); + }); + + function select_profile() { + if (activeProfile > 0) { + MSP.send_message(MSPCodes.MSP_SELECT_SETTING, [0], false, fetch_specific_data); + } else { + fetch_specific_data(); + } + } + + function fetch_specific_data() { + let fetchingProfile = 0; + let codeKey = 0; + + function fetch_specific_data_item() { + if (fetchingProfile < FC.CONFIG.numProfiles) { + MSP.send_message(profileSpecificData[codeKey], false, false, function () { + codeKey++; + + if (codeKey < profileSpecificData.length) { + fetch_specific_data_item(); + } else { + configuration.profiles.push({ + 'PID': jQuery.extend(true, {}, FC.PID), + 'PIDs': jQuery.extend(true, [], FC.PIDS), + 'RC': jQuery.extend(true, {}, FC.RC_TUNING), + 'AccTrim': jQuery.extend(true, [], FC.CONFIG.accelerometerTrims), + 'ServoConfig': jQuery.extend(true, [], FC.SERVO_CONFIG), + 'ServoRules': jQuery.extend(true, [], FC.SERVO_RULES), + 'ModeRanges': jQuery.extend(true, [], FC.MODE_RANGES), + 'AdjustmentRanges': jQuery.extend(true, [], FC.ADJUSTMENT_RANGES), + }); + + if (semver.gte(FC.CONFIG.apiVersion, "1.15.0")) { + configuration.profiles[fetchingProfile].RCdeadband = jQuery.extend(true, {}, FC.RC_DEADBAND_CONFIG); + } + codeKey = 0; + fetchingProfile++; + + MSP.send_message(MSPCodes.MSP_SELECT_SETTING, [fetchingProfile], false, fetch_specific_data_item); + } + }); + } else { + MSP.send_message(MSPCodes.MSP_SELECT_SETTING, [activeProfile], false, fetch_unique_data); + } + } + + // start fetching + fetch_specific_data_item(); + } + + const uniqueData = [ + MSPCodes.MSP_RX_MAP, + MSPCodes.MSP_CF_SERIAL_CONFIG, + MSPCodes.MSP_LED_STRIP_CONFIG, + MSPCodes.MSP_LED_COLORS, + ]; + + function update_unique_data_list() { + if (semver.gte(FC.CONFIG.apiVersion, "1.8.0")) { + uniqueData.push(MSPCodes.MSP_LOOP_TIME); + uniqueData.push(MSPCodes.MSP_ARMING_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, "1.14.0")) { + uniqueData.push(MSPCodes.MSP_MOTOR_3D_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, "1.15.0")) { + uniqueData.push(MSPCodes.MSP_SENSOR_ALIGNMENT); + uniqueData.push(MSPCodes.MSP_RX_CONFIG); + uniqueData.push(MSPCodes.MSP_FAILSAFE_CONFIG); + uniqueData.push(MSPCodes.MSP_RXFAIL_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, "1.19.0")) { + uniqueData.push(MSPCodes.MSP_LED_STRIP_MODECOLOR); + } + if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_33)) { + uniqueData.push(MSPCodes.MSP_MOTOR_CONFIG); + uniqueData.push(MSPCodes.MSP_RSSI_CONFIG); + uniqueData.push(MSPCodes.MSP_GPS_CONFIG); + uniqueData.push(MSPCodes.MSP_FEATURE_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_41)) { + uniqueData.push(MSPCodes.MSP_MODE_RANGES_EXTRA); + } + } + + update_unique_data_list(); + + function fetch_unique_data() { + let codeKey = 0; + + function fetch_unique_data_item() { + if (codeKey < uniqueData.length) { + MSP.send_message(uniqueData[codeKey], false, false, function () { + codeKey++; + fetch_unique_data_item(); + }); + } else { + configuration.RCMAP = jQuery.extend(true, [], FC.RC_MAP); + configuration.SERIAL_CONFIG = jQuery.extend(true, {}, FC.SERIAL_CONFIG); + configuration.LED_STRIP = jQuery.extend(true, [], FC.LED_STRIP); + configuration.LED_COLORS = jQuery.extend(true, [], FC.LED_COLORS); + configuration.BOARD_ALIGNMENT_CONFIG = jQuery.extend(true, {}, FC.BOARD_ALIGNMENT_CONFIG); + configuration.CRAFT_NAME = FC.CONFIG.name; + configuration.DISPLAY_NAME = FC.CONFIG.displayName; + configuration.MIXER_CONFIG = jQuery.extend(true, {}, FC.MIXER_CONFIG); + configuration.SENSOR_CONFIG = jQuery.extend(true, {}, FC.SENSOR_CONFIG); + configuration.PID_ADVANCED_CONFIG = jQuery.extend(true, {}, FC.PID_ADVANCED_CONFIG); + + if (semver.gte(FC.CONFIG.apiVersion, "1.19.0")) { + configuration.LED_MODE_COLORS = jQuery.extend(true, [], FC.LED_MODE_COLORS); + } + if (semver.gte(FC.CONFIG.apiVersion, "1.8.0")) { + configuration.FC_CONFIG = jQuery.extend(true, {}, FC.FC_CONFIG); + configuration.ARMING_CONFIG = jQuery.extend(true, {}, FC.ARMING_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, "1.14.0")) { + configuration.MOTOR_3D_CONFIG = jQuery.extend(true, {}, FC.MOTOR_3D_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, "1.15.0")) { + configuration.SENSOR_ALIGNMENT = jQuery.extend(true, {}, FC.SENSOR_ALIGNMENT); + configuration.RX_CONFIG = jQuery.extend(true, {}, FC.RX_CONFIG); + configuration.FAILSAFE_CONFIG = jQuery.extend(true, {}, FC.FAILSAFE_CONFIG); + configuration.RXFAIL_CONFIG = jQuery.extend(true, [], FC.RXFAIL_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_33)) { + configuration.RSSI_CONFIG = jQuery.extend(true, {}, FC.RSSI_CONFIG); + configuration.FEATURE_CONFIG = jQuery.extend(true, {}, FC.FEATURE_CONFIG); + configuration.MOTOR_CONFIG = jQuery.extend(true, {}, FC.MOTOR_CONFIG); + configuration.GPS_CONFIG = jQuery.extend(true, {}, FC.GPS_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_36)) { + configuration.BEEPER_CONFIG = jQuery.extend(true, {}, FC.BEEPER_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_41)) { + configuration.MODE_RANGES_EXTRA = jQuery.extend(true, [], FC.MODE_RANGES_EXTRA); + } + + save(); + } + } + + if (GUI.configuration_loaded === true) { + return fetch_unique_data_item(); + } + + MSP.promise(MSPCodes.MSP_ADVANCED_CONFIG).then(function() { + return MSP.promise(MSPCodes.MSP_SENSOR_CONFIG); + }).then(function() { + return MSP.promise(MSPCodes.MSP_NAME); + }).then(function() { + return MSP.promise(MSPCodes.MSP_BOARD_ALIGNMENT_CONFIG); + }).then(function() { + return MSP.promise(MSPCodes.MSP_MIXER_CONFIG); + }).then(function() { + return MSP.promise(MSPCodes.MSP_BEEPER_CONFIG); + }).then(function() { + return fetch_unique_data_item(); + }); + } + + function save() { + let chosenFileEntry = null; + + const prefix = 'backup'; + const suffix = 'json'; + + const filename = generateFilename(prefix, suffix); + + const accepts = [{ + description: suffix.toUpperCase() + ' files', extensions: [suffix], + }]; + + // create or load the file + chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename, accepts: accepts}, function (fileEntry) { + if (checkChromeRuntimeError()) { + return; + } + + if (!fileEntry) { + console.log('No file selected, backup aborted.'); + return; + } + + chosenFileEntry = fileEntry; + + // echo/console log path specified + chrome.fileSystem.getDisplayPath(chosenFileEntry, function (path) { + console.log('Backup file path: ' + path); + }); + + // change file entry from read only to read/write + chrome.fileSystem.getWritableEntry(chosenFileEntry, function (fileEntryWritable) { + // check if file is writable + chrome.fileSystem.isWritableEntry(fileEntryWritable, function (isWritable) { + if (isWritable) { + chosenFileEntry = fileEntryWritable; + + // crunch the config object + const serializedConfigObject = JSON.stringify(configuration, null, '\t'); + const blob = new Blob([serializedConfigObject], {type: 'text/plain'}); // first parameter for Blob needs to be an array + + chosenFileEntry.createWriter(function (writer) { + writer.onerror = function (e) { + console.error(e); + }; + + let truncated = false; + writer.onwriteend = function () { + if (!truncated) { + // onwriteend will be fired again when truncation is finished + truncated = true; + writer.truncate(blob.size); + + return; + } + + analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'Backup'); + console.log('Write SUCCESSFUL'); + if (callback) callback(); + }; + + writer.write(blob); + }, function (e) { + console.error(e); + }); + } else { + // Something went wrong or file is set to read only and cannot be changed + console.log('File appears to be read only, sorry.'); + } + }); + }); + }); + } + +} + +function configuration_restore(callback) { + let chosenFileEntry = null; + + const accepts = [{ + description: 'JSON files', extensions: ['json'], + }]; + + // load up the file + chrome.fileSystem.chooseEntry({type: 'openFile', accepts: accepts}, function (fileEntry) { + if (checkChromeRuntimeError()) { + return; + } + + if (!fileEntry) { + console.log('No file selected, restore aborted.'); + return; + } + + chosenFileEntry = fileEntry; + + // echo/console log path specified + chrome.fileSystem.getDisplayPath(chosenFileEntry, function (path) { + console.log('Restore file path: ' + path); + }); + + // read contents into variable + chosenFileEntry.file(function (file) { + const reader = new FileReader(); + + reader.onprogress = function (e) { + if (e.total > 1048576) { // 1 MB + // dont allow reading files bigger then 1 MB + console.log('File limit (1 MB) exceeded, aborting'); + reader.abort(); + } + }; + + reader.onloadend = function (e) { + if ((e.total != 0 && e.total == e.loaded) || GUI.isCordova()) { + // Cordova: Ignore verification : seem to have a bug with progressEvent returned + console.log('Read SUCCESSFUL'); + let configuration; + try { // check if string provided is a valid JSON + configuration = JSON.parse(e.target.result); + } catch (err) { + // data provided != valid json object + console.log(`Data provided != valid JSON string, restore aborted: ${err}`); + + return; + } + + // validate + if (typeof configuration.generatedBy !== 'undefined' && compareVersions(configuration.generatedBy, CONFIGURATOR.BACKUP_FILE_VERSION_MIN_SUPPORTED)) { + if (!compareVersions(configuration.generatedBy, "1.14.0") && !migrate(configuration)) { + GUI.log(i18n.getMessage('backupFileUnmigratable')); + return; + } + if (configuration.FEATURE_CONFIG.features._featureMask) { + const features = new Features(FC.CONFIG); + features.setMask(configuration.FEATURE_CONFIG.features._featureMask); + configuration.FEATURE_CONFIG.features = features; + } + + analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'Restore'); + + configuration_upload(configuration, callback); + } else { + GUI.log(i18n.getMessage('backupFileIncompatible')); + } + } + }; + + reader.readAsText(file); + }); + }); + + function compareVersions(generated, required) { + if (generated == undefined) { + return false; + } + return semver.gte(generated, required); + } + + + function migrate(configuration) { + let appliedMigrationsCount = 0; + let migratedVersion = configuration.generatedBy; + GUI.log(i18n.getMessage('configMigrationFrom', [migratedVersion])); + + if (!compareVersions(migratedVersion, '0.59.1')) { + + // variable was renamed + configuration.RSSI_CONFIG.channel = configuration.MISC.rssi_aux_channel; + configuration.MISC.rssi_aux_channel = undefined; + + migratedVersion = '0.59.1'; + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (!compareVersions(migratedVersion, '0.60.1')) { + + // LED_STRIP support was added. + if (!configuration.LED_STRIP) { + configuration.LED_STRIP = []; + } + + migratedVersion = '0.60.1'; + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (!compareVersions(migratedVersion, '0.61.0')) { + + // Changing PID controller via UI was added. + if (!configuration.PIDs && configuration.PID) { + configuration.PIDs = configuration.PID; + configuration.PID = { + controller: 0, // assume pid controller 0 was used. + }; + } + + migratedVersion = '0.61.0'; + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (!compareVersions(migratedVersion, '0.63.0')) { + + // LED Strip was saved as object instead of array. + if (typeof(configuration.LED_STRIP) == 'object') { + const fixedLedStrip = []; + + let index = 0; + while (configuration.LED_STRIP[index]) { + fixedLedStrip.push(configuration.LED_STRIP[index++]); + } + configuration.LED_STRIP = fixedLedStrip; + } + + for (let profileIndex = 0; profileIndex < 3; profileIndex++) { + const RC = configuration.profiles[profileIndex].RC; + // TPA breakpoint was added + if (!RC.dynamic_THR_breakpoint) { + RC.dynamic_THR_breakpoint = 1500; // firmware default + } + + // Roll and pitch rates were split + RC.roll_rate = RC.roll_pitch_rate; + RC.pitch_rate = RC.roll_pitch_rate; + } + + migratedVersion = '0.63.0'; + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (configuration.apiVersion == undefined) { + configuration.apiVersion = "1.0.0"; // a guess that will satisfy the rest of the code + } + // apiVersion previously stored without patchlevel + if (!semver.parse(configuration.apiVersion)) { + configuration.apiVersion += ".0"; + if (!semver.parse(configuration.apiVersion)) { + return false; + } + } + if (compareVersions(migratedVersion, '0.63.0') && !compareVersions(configuration.apiVersion, '1.7.0')) { + // Serial configuation redesigned, 0.63.0 saves old and new configurations. + const ports = []; + for (const port of configuration.SERIAL_CONFIG.ports) { + const oldPort = port; + + const newPort = { + identifier: oldPort.identifier, + functions: [], + msp_baudrate: String(configuration.SERIAL_CONFIG.mspBaudRate), + gps_baudrate: String(configuration.SERIAL_CONFIG.gpsBaudRate), + telemetry_baudrate: 'AUTO', + blackbox_baudrate: '115200', + }; + + switch(oldPort.scenario) { + case 1: // MSP, CLI, TELEMETRY, SMARTPORT TELEMETRY, GPS-PASSTHROUGH + case 5: // MSP, CLI, GPS-PASSTHROUGH + case 8: // MSP ONLY + newPort.functions.push('MSP'); + break; + case 2: // GPS + newPort.functions.push('GPS'); + break; + case 3: // RX_SERIAL + newPort.functions.push('RX_SERIAL'); + break; + case 10: // BLACKBOX ONLY + newPort.functions.push('BLACKBOX'); + break; + case 11: // MSP, CLI, BLACKBOX, GPS-PASSTHROUGH + newPort.functions.push('MSP'); + newPort.functions.push('BLACKBOX'); + break; + } + + ports.push(newPort); + } + configuration.SERIAL_CONFIG = { + ports: ports, + }; + + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (compareVersions(migratedVersion, '0.63.0') && !compareVersions(configuration.apiVersion, '1.8.0')) { + // api 1.8 exposes looptime and arming config + + if (configuration.FC_CONFIG == undefined) { + configuration.FC_CONFIG = { + loopTime: 3500, + }; + } + + if (configuration.ARMING_CONFIG == undefined) { + configuration.ARMING_CONFIG = { + auto_disarm_delay: 5, + disarm_kill_switch: 1, + }; + } + + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (compareVersions(migratedVersion, '0.63.0')) { + // backups created with 0.63.0 for firmwares with api < 1.8 were saved with incorrect looptime + if (configuration.FC_CONFIG.loopTime == 0) { + //reset it to the default + configuration.FC_CONFIG.loopTime = 3500; + } + } + + if (semver.lt(migratedVersion, '0.66.0')) { + // api 1.12 updated servo configuration protocol and added servo mixer rules + for (let profileIndex = 0; profileIndex < configuration.profiles.length; profileIndex++) { + if (semver.eq(configuration.apiVersion, '1.10.0')) { + // drop two unused servo configurations + while (configuration.profiles[profileIndex].ServoConfig.length > 8) { + configuration.profiles[profileIndex].ServoConfig.pop(); + } + } + + for (let i = 0; i < configuration.profiles[profileIndex].ServoConfig.length; i++) { + const servoConfig = profiles[profileIndex].ServoConfig; + + servoConfig[i].angleAtMin = 45; + servoConfig[i].angleAtMax = 45; + servoConfig[i].reversedInputSources = 0; + + // set the rate to 0 if an invalid value is detected. + if (servoConfig[i].rate < -100 || servoConfig[i].rate > 100) { + servoConfig[i].rate = 0; + } + } + + configuration.profiles[profileIndex].ServoRules = []; + } + + migratedVersion = '0.66.0'; + + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (semver.lt(configuration.apiVersion, '1.14.0') && semver.gte(FC.CONFIG.apiVersion, "1.14.0")) { + // api 1.14 removed old pid controllers + for (let profileIndex = 0; profileIndex < configuration.profiles.length; profileIndex++) { + let newPidControllerIndex = configuration.profiles[profileIndex].PID.controller; + switch (newPidControllerIndex) { + case 3: + case 4: + case 5: + newPidControllerIndex = 0; + break; + } + configuration.profiles[profileIndex].PID.controller = newPidControllerIndex; + } + + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + + if (compareVersions(migratedVersion, '0.66.0') && !compareVersions(configuration.apiVersion, '1.14.0')) { + // api 1.14 exposes 3D configuration + + if (configuration.MOTOR_3D_CONFIG == undefined) { + configuration.MOTOR_3D_CONFIG = { + deadband3d_low: 1406, + deadband3d_high: 1514, + neutral: 1460, + deadband3d_throttle: 50, + }; + } + + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + + if (compareVersions(migratedVersion, '0.66.0') && !compareVersions(configuration.apiVersion, '1.15.0')) { + // api 1.15 exposes RCdeadband and sensor alignment + + for (let profileIndex = 0; profileIndex < configuration.profiles.length; profileIndex++) { + if (configuration.profiles[profileIndex].RCdeadband == undefined) { + configuration.profiles[profileIndex].RCdeadband = { + deadband: 0, + yaw_deadband: 0, + alt_hold_deadband: 40, + }; + } + } + if (configuration.SENSOR_ALIGNMENT == undefined) { + configuration.SENSOR_ALIGNMENT = { + align_gyro: 0, + align_acc: 0, + align_mag: 0, + }; + } + + // api 1.15 exposes RX_CONFIG, FAILSAFE_CONFIG and RXFAIL_CONFIG configuration + + if (configuration.RX_CONFIG == undefined) { + configuration.RX_CONFIG = { + serialrx_provider: 0, + spektrum_sat_bind: 0, + stick_center: 1500, + stick_min: 1100, + stick_max: 1900, + rx_min_usec: 885, + rx_max_usec: 2115, + }; + } + + if (configuration.FAILSAFE_CONFIG == undefined) { + configuration.FAILSAFE_CONFIG = { + failsafe_delay: 10, + failsafe_off_delay: 200, + failsafe_throttle: 1000, + failsafe_switch_mode: 0, + failsafe_throttle_low_delay: 100, + failsafe_procedure: 0, + }; + } + + if (configuration.RXFAIL_CONFIG == undefined) { + configuration.RXFAIL_CONFIG = [ + {mode: 0, value: 1500}, + {mode: 0, value: 1500}, + {mode: 0, value: 1500}, + {mode: 0, value: 875}, + ]; + + for (let i = 0; i < 14; i++) { + const rxfailChannel = { + mode: 1, + value: 1500, + }; + configuration.RXFAIL_CONFIG.push(rxfailChannel); + } + } + + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (compareVersions(migratedVersion, '1.2.0')) { + // old version of the configurator incorrectly had a 'disabled' option for GPS SBAS mode. + if (FC.GPS_CONFIG.ublox_sbas < 0) { + FC.GPS_CONFIG.ublox_sbas = 0; + } + migratedVersion = '1.2.0'; + + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (compareVersions(migratedVersion, '1.3.1')) { + + // LED_COLORS & LED_MODE_COLORS support was added. + if (!configuration.LED_COLORS) { + configuration.LED_COLORS = []; + } + if (!configuration.LED_MODE_COLORS) { + configuration.LED_MODE_COLORS = []; + } + + migratedVersion = '1.3.1'; + + GUI.log(i18n.getMessage('configMigratedTo', [migratedVersion])); + appliedMigrationsCount++; + } + + if (appliedMigrationsCount > 0) { + GUI.log(i18n.getMessage('configMigrationSuccessful', [appliedMigrationsCount])); + } + return true; + } + + function configuration_upload(configuration, _callback) { + function upload() { + let activeProfile = null; + let numProfiles = FC.CONFIG.numProfiles; + if (configuration.profiles.length < numProfiles) { + numProfiles = configuration.profiles.length; + } + + const profileSpecificData = [ + MSPCodes.MSP_SET_PID_CONTROLLER, + MSPCodes.MSP_SET_PID, + MSPCodes.MSP_SET_RC_TUNING, + MSPCodes.MSP_SET_ACC_TRIM, + ]; + + if (semver.gte(FC.CONFIG.apiVersion, "1.15.0")) { + profileSpecificData.push(MSPCodes.MSP_SET_RC_DEADBAND); + } + + MSP.send_message(MSPCodes.MSP_STATUS, false, false, function () { + activeProfile = FC.CONFIG.profile; + select_profile(); + }); + + function select_profile() { + if (activeProfile > 0) { + MSP.send_message(MSPCodes.MSP_SELECT_SETTING, [0], false, upload_specific_data); + } else { + upload_specific_data(); + } + } + + function upload_specific_data() { + let savingProfile = 0; + let codeKey = 0; + + function load_objects(profile) { + FC.PID = configuration.profiles[profile].PID; + FC.PIDS = configuration.profiles[profile].PIDs; + FC.RC_TUNING = configuration.profiles[profile].RC; + FC.CONFIG.accelerometerTrims = configuration.profiles[profile].AccTrim; + FC.SERVO_CONFIG = configuration.profiles[profile].ServoConfig; + FC.SERVO_RULES = configuration.profiles[profile].ServoRules; + FC.MODE_RANGES = configuration.profiles[profile].ModeRanges; + FC.ADJUSTMENT_RANGES = configuration.profiles[profile].AdjustmentRanges; + FC.RC_DEADBAND_CONFIG = configuration.profiles[profile].RCdeadband; + } + + function upload_using_specific_commands() { + MSP.send_message(profileSpecificData[codeKey], mspHelper.crunch(profileSpecificData[codeKey]), false, function () { + codeKey++; + + if (codeKey < profileSpecificData.length) { + upload_using_specific_commands(); + } else { + codeKey = 0; + savingProfile++; + + if (savingProfile < numProfiles) { + load_objects(savingProfile); + + MSP.send_message(MSPCodes.MSP_EEPROM_WRITE, false, false, function () { + MSP.send_message(MSPCodes.MSP_SELECT_SETTING, [savingProfile], false, upload_using_specific_commands); + }); + } else { + MSP.send_message(MSPCodes.MSP_EEPROM_WRITE, false, false, function () { + MSP.send_message(MSPCodes.MSP_SELECT_SETTING, [activeProfile], false, upload_unique_data); + }); + } + } + }); + } + + function upload_servo_configuration() { + mspHelper.sendServoConfigurations(upload_mode_ranges); + } + + function upload_mode_ranges() { + if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_41)) { + if (configuration.MODE_RANGES_EXTRA == undefined) { + FC.MODE_RANGES_EXTRA = []; + + for (let modeIndex = 0; modeIndex < FC.MODE_RANGES.length; modeIndex++) { + const defaultModeRangeExtra = { + modeId: FC.MODE_RANGES[modeIndex].modeId, + modeLogic: 0, + linkedTo: 0, + }; + FC.MODE_RANGES_EXTRA.push(defaultModeRangeExtra); + } + } else { + FC.MODE_RANGES_EXTRA = configuration.MODE_RANGES_EXTRA; + } + } + + mspHelper.sendModeRanges(upload_adjustment_ranges); + } + + function upload_adjustment_ranges() { + mspHelper.sendAdjustmentRanges(upload_using_specific_commands); + } + // start uploading + load_objects(0); + upload_servo_configuration(); + } + + function upload_unique_data() { + let codeKey = 0; + + const uniqueData = [ + MSPCodes.MSP_SET_RX_MAP, + MSPCodes.MSP_SET_CF_SERIAL_CONFIG, + ]; + + function update_unique_data_list() { + uniqueData.push(MSPCodes.MSP_SET_NAME); + uniqueData.push(MSPCodes.MSP_SET_SENSOR_CONFIG); + uniqueData.push(MSPCodes.MSP_SET_MIXER_CONFIG); + uniqueData.push(MSPCodes.MSP_SET_BEEPER_CONFIG); + uniqueData.push(MSPCodes.MSP_SET_BOARD_ALIGNMENT_CONFIG); + uniqueData.push(MSPCodes.MSP_SET_ADVANCED_CONFIG); + + if (semver.gte(FC.CONFIG.apiVersion, "1.8.0")) { + uniqueData.push(MSPCodes.MSP_SET_LOOP_TIME); + uniqueData.push(MSPCodes.MSP_SET_ARMING_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, "1.14.0")) { + uniqueData.push(MSPCodes.MSP_SET_MOTOR_3D_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, "1.15.0")) { + uniqueData.push(MSPCodes.MSP_SET_SENSOR_ALIGNMENT); + uniqueData.push(MSPCodes.MSP_SET_RX_CONFIG); + uniqueData.push(MSPCodes.MSP_SET_FAILSAFE_CONFIG); + } + if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_33)) { + uniqueData.push(MSPCodes.MSP_SET_FEATURE_CONFIG); + uniqueData.push(MSPCodes.MSP_SET_MOTOR_CONFIG); + uniqueData.push(MSPCodes.MSP_SET_GPS_CONFIG); + uniqueData.push(MSPCodes.MSP_SET_RSSI_CONFIG); + } + } + + function load_objects() { + FC.MISC = configuration.MISC; + FC.RC_MAP = configuration.RCMAP; + FC.SERIAL_CONFIG = configuration.SERIAL_CONFIG; + FC.LED_STRIP = configuration.LED_STRIP; + FC.LED_COLORS = configuration.LED_COLORS; + FC.LED_MODE_COLORS = configuration.LED_MODE_COLORS; + FC.ARMING_CONFIG = configuration.ARMING_CONFIG; + FC.FC_CONFIG = configuration.FC_CONFIG; + FC.MOTOR_3D_CONFIG = configuration.MOTOR_3D_CONFIG; + FC.SENSOR_ALIGNMENT = configuration.SENSOR_ALIGNMENT; + FC.RX_CONFIG = configuration.RX_CONFIG; + FC.FAILSAFE_CONFIG = configuration.FAILSAFE_CONFIG; + FC.RXFAIL_CONFIG = configuration.RXFAIL_CONFIG; + FC.FEATURE_CONFIG = configuration.FEATURE_CONFIG; + FC.MOTOR_CONFIG = configuration.MOTOR_CONFIG; + FC.GPS_CONFIG = configuration.GPS_CONFIG; + FC.RSSI_CONFIG = configuration.RSSI_CONFIG; + FC.BOARD_ALIGNMENT_CONFIG = configuration.BOARD_ALIGNMENT_CONFIG; + FC.CONFIG.name = configuration.CRAFT_NAME; + FC.CONFIG.displayName = configuration.DISPLAY_NAME; + FC.MIXER_CONFIG = configuration.MIXER_CONFIG; + FC.SENSOR_CONFIG = configuration.SENSOR_CONFIG; + FC.PID_ADVANCED_CONFIG = configuration.PID_ADVANCED_CONFIG; + + FC.BEEPER_CONFIG.beepers = new Beepers(FC.CONFIG); + FC.BEEPER_CONFIG.beepers.setDisabledMask(configuration.BEEPER_CONFIG.beepers._beeperDisabledMask); + FC.BEEPER_CONFIG.dshotBeaconTone = configuration.BEEPER_CONFIG.dshotBeaconTone; + FC.BEEPER_CONFIG.dshotBeaconConditions = new Beepers(FC.CONFIG, [ "RX_LOST", "RX_SET" ]); + FC.BEEPER_CONFIG.dshotBeaconConditions.setDisabledMask(configuration.BEEPER_CONFIG.dshotBeaconConditions._beeperDisabledMask); + } + + function send_unique_data_item() { + if (codeKey < uniqueData.length) { + MSP.send_message(uniqueData[codeKey], mspHelper.crunch(uniqueData[codeKey]), false, function () { + codeKey++; + send_unique_data_item(); + }); + } else { + send_led_strip_config(); + } + } + + load_objects(); + + update_unique_data_list(); + + // start uploading + send_unique_data_item(); + } + + function send_led_strip_config() { + mspHelper.sendLedStripConfig(send_led_strip_colors); + } + + function send_led_strip_colors() { + mspHelper.sendLedStripColors(send_led_strip_mode_colors); + } + + function send_led_strip_mode_colors() { + if (semver.gte(FC.CONFIG.apiVersion, "1.19.0")) { + mspHelper.sendLedStripModeColors(send_rxfail_config); + } else { + send_rxfail_config(); + } + } + + function send_rxfail_config() { + if (semver.gte(FC.CONFIG.apiVersion, "1.15.0")) { + mspHelper.sendRxFailConfig(save_to_eeprom); + } else { + save_to_eeprom(); + } + } + + function save_to_eeprom() { + MSP.send_message(MSPCodes.MSP_EEPROM_WRITE, false, false, reboot); + } + + function reboot() { + GUI.log(i18n.getMessage('eeprom_saved_ok')); + + GUI.tab_switch_cleanup(function() { + MSP.send_message(MSPCodes.MSP_SET_REBOOT, false, false, reinitialiseConnection('setup', _callback)); + }); + } + } + + if (CONFIGURATOR.virtualMode) { + FC.resetState(); + FC.CONFIG.apiVersion = CONFIGURATOR.virtualApiVersion; + + sensor_status(FC.CONFIG.activeSensors); + update_dataflash_global(); + } + + upload(); + } +} diff --git a/src/js/tabs/setup.js b/src/js/tabs/setup.js index d0a184e9..3a2beace 100644 --- a/src/js/tabs/setup.js +++ b/src/js/tabs/setup.js @@ -29,6 +29,36 @@ TABS.setup.initialize = function (callback) { // translate to user-selected language i18n.localizePage(); + if (CONFIGURATOR.virtualMode || semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_44)) { + const backupButton = $('#content .backup'); + + if (semver.lt(FC.CONFIG.apiVersion, CONFIGURATOR.API_VERSION_MIN_SUPPORTED_BACKUP_RESTORE)) { + backupButton.addClass('disabled'); + $('#content .restore').addClass('disabled'); + + GUI.log(i18n.getMessage('initialSetupBackupAndRestoreApiVersion', [FC.CONFIG.apiVersion, CONFIGURATOR.API_VERSION_MIN_SUPPORTED_BACKUP_RESTORE])); + } + + if (CONFIGURATOR.virtualMode) { + // saving and uploading an imaginary config to hardware is a bad idea + backupButton.addClass('disabled'); + } + + backupButton.on('click', () => configuration_backup(() => GUI.log(i18n.getMessage('initialSetupBackupSuccess')))); + + $('#content .restore').on('click', () => { + configuration_restore(() => { + // get latest settings + TABS.setup.initialize(); + + GUI.log(i18n.getMessage('initialSetupRestoreSuccess')); + }); + }); + + } else { + $('.backupRestore').hide(); + } + // initialize 3D Model self.initModel(); diff --git a/src/tabs/setup.html b/src/tabs/setup.html index 1aa05956..aac1c5ba 100644 --- a/src/tabs/setup.html +++ b/src/tabs/setup.html @@ -55,6 +55,27 @@ +