Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backup config on flash #3459

Merged
merged 4 commits into from Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions locales/en/messages.json
Expand Up @@ -3545,6 +3545,22 @@
"firmwareFlasherPreviousDevice": {
"message": "Detected: <strong>$1</strong> - previous device still flashing, please replug to try again"
},
"firmwareFlasherRemindBackupTitle": {
"message": "Wipe out settings",
"description": "Warning message title before actual flashing takes place"
},
"firmwareFlasherRemindBackup": {
"message": "Flashing new firmware will wipe out all settings. We strongly recommend to save a backup before continuing.",
"description": "Warning message before actual flashing takes place"
},
"firmwareFlasherBackup": {
"message": "Create Backup",
"description": "Create a backup before actual flashing takes place"
},
"firmwareFlasherBackupIgnore": {
"message": "Ignore the risk",
"description": "Ignore creating a backup before actual flashing takes place"
},
"ledStripHelp": {
"message": "The flight controller can control colors and effects of individual LEDs on a strip.<br />Configure LEDs on the grid, configure wiring order then attach LEDs on your aircraft according to grid positions. LEDs without wire ordering number will not be saved.<br />Double-click on a color to edit the HSV values."
},
Expand Down
2 changes: 1 addition & 1 deletion src/js/port_handler.js
Expand Up @@ -216,7 +216,7 @@ PortHandler.detectPort = function(currentPorts) {

self.port_available = true;
// Signal board verification
if (GUI.active_tab === 'firmware_flasher') {
if (GUI.active_tab === 'firmware_flasher' && TABS.firmware_flasher.allowBoardDetection) {
TABS.firmware_flasher.boardNeedsVerification = true;
}

Expand Down
198 changes: 193 additions & 5 deletions src/js/tabs/firmware_flasher.js
Expand Up @@ -17,13 +17,15 @@ import STM32DFU from '../protocols/stm32usbdfu';
import { gui_log } from '../gui_log';
import semver from 'semver';
import { checkChromeRuntimeError } from '../utils/common';
import { generateFilename } from '../utils/generate_filename';

const firmware_flasher = {
targets: null,
releaseLoader: new BuildApi(),
localFirmwareLoaded: false,
selectedBoard: undefined,
boardNeedsVerification: false,
allowBoardDetection: true,
intel_hex: undefined, // standard intel hex in string format
parsed_hex: undefined, // parsed raw hex in array format
isConfigLocal: false, // Set to true if the user loads one locally
Expand Down Expand Up @@ -597,7 +599,7 @@ firmware_flasher.initialize = function (callback) {
}

function getBuildInfo() {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45) && navigator.onLine) {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45) && navigator.onLine && FC.CONFIG.flightControllerIdentifier === 'BTFL') {

function onLoadCloudBuild(options) {
FC.CONFIG.buildOptions = options.Request.Options;
Expand Down Expand Up @@ -625,7 +627,7 @@ firmware_flasher.initialize = function (callback) {
} else if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_39)) {
onClose(false); // not supported
} else {
MSP.send_message(MSPCodes.MSP_UID, false, false, getBuildInfo);
MSP.send_message(MSPCodes.MSP_FC_VARIANT, false, false, getBuildInfo);
}
});
}
Expand Down Expand Up @@ -1025,11 +1027,30 @@ firmware_flasher.initialize = function (callback) {
}).trigger('change');

$('a.flash_firmware').click(function () {

if (!$(this).hasClass('disabled')) {
if (self.developmentFirmwareLoaded) {
checkShowAcknowledgementDialog();
function goFlashing() {
if (self.developmentFirmwareLoaded) {
checkShowAcknowledgementDialog();
} else {
startFlashing();
}
}

// Backup not available in DFU mode
if (!$('option:selected', portPickerElement).data().isDFU) {
GUI.showYesNoDialog(
{
title: i18n.getMessage('firmwareFlasherRemindBackupTitle'),
text: i18n.getMessage('firmwareFlasherRemindBackup'),
buttonYesText: i18n.getMessage('firmwareFlasherBackup'),
buttonNoText: i18n.getMessage('firmwareFlasherBackupIgnore'),
buttonYesCallback: () => firmware_flasher.backupConfig(goFlashing),
buttonNoCallback: goFlashing,
},
);
} else {
startFlashing();
goFlashing();
}
}
});
Expand Down Expand Up @@ -1216,6 +1237,173 @@ firmware_flasher.initialize = function (callback) {
});
};

firmware_flasher.backupConfig = function (callback) {
let mspHelper;
let cliBuffer = '';
let catchOutputCallback = null;

function readOutput(callback) {
catchOutputCallback = callback;
}

function writeOutput(text) {
if (catchOutputCallback) {
catchOutputCallback(text);
}
}

function readSerial(readInfo) {
const data = new Uint8Array(readInfo.data);

for (const charCode of data) {
const currentChar = String.fromCharCode(charCode);

switch (charCode) {
case 10:
if (GUI.operating_system === "Windows") {
writeOutput(cliBuffer);
cliBuffer = '';
}
break;
case 13:
if (GUI.operating_system !== "Windows") {
writeOutput(cliBuffer);
cliBuffer = '';
}
break;
default:
cliBuffer += currentChar;
}
}
}

function activateCliMode() {
return new Promise(resolve => {
const bufferOut = new ArrayBuffer(1);
const bufView = new Uint8Array(bufferOut);

cliBuffer = '';
bufView[0] = 0x23;

serial.send(bufferOut);

GUI.timeout_add('enter_cli_mode_done', () => {
resolve();
}, 500);
});
}

function sendSerial(line, callback) {
const bufferOut = new ArrayBuffer(line.length);
const bufView = new Uint8Array(bufferOut);

for (let cKey = 0; cKey < line.length; cKey++) {
bufView[cKey] = line.charCodeAt(cKey);
}

serial.send(bufferOut, callback);
}

function sendCommand(line, callback) {
sendSerial(`${line}\n`, callback);
}

function readCommand() {
let timeStamp = performance.now();
const output = [];
const commandInterval = "COMMAND_INTERVAL";

readOutput(str => {
timeStamp = performance.now();
output.push(str);
});

sendCommand("diff all defaults");

return new Promise(resolve => {
GUI.interval_add(commandInterval, () => {
const currentTime = performance.now();
if (currentTime - timeStamp > 500) {
catchOutputCallback = null;
GUI.interval_remove(commandInterval);
resolve(output);
}
}, 500, false);
});
}

function onFinishClose() {
MSP.clearListeners();

// Include timeout in count
let count = 15;
// Allow reboot after CLI exit
const waitOnReboot = () => {
const disconnect = setInterval(function() {
if (PortHandler.port_available) {
console.log(`Connection ready for flashing in ${count / 10} seconds`);
clearInterval(disconnect);
// Re-activate auto-detection. (Seems not be needed) :)
TABS.firmware_flasher.allowBoardDetection = true;
callback();
}
count++;
}, 100);
};

// PortHandler has a 500ms timer - so triple for safety
setTimeout(waitOnReboot, 1500);
}

function onClose() {
serial.disconnect(onFinishClose);
MSP.disconnect_cleanup();
}

function onSaveConfig() {
// Prevent auto-detect after CLI reset
TABS.firmware_flasher.allowBoardDetection = false;

activateCliMode()
.then(readCommand)
.then(output => {
const prefix = 'cli_backup';
const suffix = 'txt';
const text = output.join("\n");
const filename = generateFilename(prefix, suffix);

return GUI.saveToTextFileDialog(text, filename, suffix);
})
.then(() => sendCommand("exit", onClose));
}

function onConnect(openInfo) {
if (openInfo) {
mspHelper = new MspHelper();
serial.onReceive.addListener(readSerial);
MSP.listen(mspHelper.process_data.bind(mspHelper));

onSaveConfig();
} else {
gui_log(i18n.getMessage('serialPortOpenFail'));

if (callback) {
callback();
}
}
}

const portPickerElement = $('div#port-picker #port');
const port = String(portPickerElement.val());

if (port !== '0') {
const baud = parseInt($('#flash_manual_baud_rate').val()) || 115200;
serial.connect(port, {bitrate: baud}, onConnect);
} else {
gui_log(i18n.getMessage('firmwareFlasherNoPortSelected'));
}
};

firmware_flasher.cleanup = function (callback) {
PortHandler.flush_callbacks();

Expand Down
28 changes: 8 additions & 20 deletions src/js/utils/generate_filename.js
Expand Up @@ -14,29 +14,17 @@ function zeroPad(value, width) {

export function generateFilename(prefix, suffix) {
const date = new Date();
let filename = prefix;
const yyyymmdd = `${date.getFullYear()}${zeroPad(date.getMonth() + 1, 2)}${zeroPad(date.getDate(), 2)}`;
const hhmmss = `${zeroPad(date.getHours(), 2)}${zeroPad(date.getMinutes(), 2)}${zeroPad(date.getSeconds(), 2)}`;

let filename = `${FC.CONFIG.flightControllerIdentifier || 'UNKNOWN'}_${prefix}_${yyyymmdd}_${hhmmss}`;

if (FC.CONFIG) {
if (FC.CONFIG.flightControllerIdentifier) {
filename = `${FC.CONFIG.flightControllerIdentifier}_${filename}`;
}
const craftName = semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45)
? FC.CONFIG.craftName
: FC.CONFIG.name;
if (craftName.trim() !== "") {
filename = `${filename}_${craftName.trim().replace(" ", "_")}`;
}
}
const craftName = semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_45) ? FC.CONFIG.craftName : FC.CONFIG.name;
const boardName = FC.CONFIG.boardName || craftName.trim().replace(" ", "_");

const yyyymmdd = `${date.getFullYear()}${zeroPad(
date.getMonth() + 1,
2,
)}${zeroPad(date.getDate(), 2)}`;
const hhmmss = `${zeroPad(date.getHours(), 2)}${zeroPad(
date.getMinutes(),
2,
)}${zeroPad(date.getSeconds(), 2)}`;
filename = `${filename}_${yyyymmdd}_${hhmmss}`;
filename = `${filename}_${boardName}`;
}

return `${filename}.${suffix}`;
}