Skip to content

Refactor auto-backup in flashing tab for PWA #4005

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

Merged
merged 3 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 3 additions & 184 deletions src/js/tabs/firmware_flasher.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import { API_VERSION_1_39, API_VERSION_1_45, API_VERSION_1_46 } from '../data_st
import { gui_log } from '../gui_log';
import semver from 'semver';
import { urlExists } from '../utils/common';
import { generateFilename } from '../utils/generate_filename';
import read_hex_file from '../workers/hex_parser.js';
import Sponsor from '../Sponsor';
import FileSystem from '../FileSystem';
import STM32 from '../protocols/webstm32';
import DFU from '../protocols/webusbdfu';
import serial from '../webSerial';
import AutoBackup from '../utils/AutoBackup.js';

const firmware_flasher = {
targets: null,
Expand Down Expand Up @@ -984,7 +984,8 @@ firmware_flasher.initialize = function (callback) {
text: i18n.getMessage('firmwareFlasherRemindBackup'),
buttonYesText: i18n.getMessage('firmwareFlasherBackup'),
buttonNoText: i18n.getMessage('firmwareFlasherBackupIgnore'),
buttonYesCallback: () => firmware_flasher.backupConfig(initiateFlashing),
// buttonYesCallback: () => firmware_flasher.backupConfig(initiateFlashing),
buttonYesCallback: AutoBackup.execute(initiateFlashing),
buttonNoCallback: initiateFlashing,
},
);
Expand Down Expand Up @@ -1313,188 +1314,6 @@ firmware_flasher.verifyBoard = function() {
serial.connect(port, { baudRate: 115200 });
};

firmware_flasher.getPort = function () {
return String($('div#port-picker #port').val());
};

/**
*
* Bacup the current configuration to a file before flashing in serial mode
*/

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.portAvailable) {
console.log(`Connection ready for flashing in ${count / 10} seconds`);
clearInterval(disconnect);
if (callback) {
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() {

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

FileSystem.pickSaveFile(filename, i18n.getMessage('fileSystemPickerFiles', {typeof: suffix.toUpperCase()}), `.${suffix}`)
.then((file) => {
console.log("Saving config to:", file.name);
FileSystem.writeFile(file, text);
})
.catch((error) => {
console.error("Error saving config:", error);
});
})
.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 port = this.getPort();

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
137 changes: 137 additions & 0 deletions src/js/utils/AutoBackup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import PortHandler from '../port_handler';
import FileSystem from '../FileSystem';
import { generateFilename } from './generate_filename';
import { gui_log } from '../gui_log';
import { i18n } from "../localization";
import serial from '../webSerial';

/**
*
* Bacup the current configuration to a file before flashing in serial mode
*/

class AutoBackup {
constructor() {
this.outputHistory = '';
this.callback = null;
}

handleConnect(openInfo) {
console.log('Connected to serial port:', openInfo);
if (openInfo) {
serial.removeEventListener('receive', this.readSerialAdapter);
serial.addEventListener('receive', this.readSerialAdapter.bind(this));

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

handleDisconnect(event) {
if (event.detail) {
gui_log(i18n.getMessage('serialPortClosedOk'));
} else {
gui_log(i18n.getMessage('serialPortClosedFail'));
}

serial.removeEventListener('receive', this.readSerialAdapter);
serial.removeEventListener('connect', this.handleConnect);
serial.removeEventListener('disconnect', this.handleDisconnect);
}

readSerialAdapter(info) {
const data = new Uint8Array(info.detail.buffer);

for (const charCode of data) {
const currentChar = String.fromCharCode(charCode);
this.outputHistory += currentChar;
}
}

onClose() {
serial.addEventListener('disconnect', this.handleDisconnect.bind(this), { once: true });
serial.disconnect();
}

async save() {
console.log('Saving backup');
const prefix = 'cli_backup';
const suffix = 'txt';
const text = this.outputHistory;
const filename = generateFilename(prefix, suffix);

FileSystem.pickSaveFile(filename, i18n.getMessage('fileSystemPickerFiles', { types: suffix.toUpperCase() }), `.${suffix}`)
.then((file) => {
console.log("Saving config to:", file.name);
FileSystem.writeFile(file, text);
})
.catch((error) => {
console.error("Error saving config:", error);
})
.finally(() => {
if (this.callback) {
this.callback();
}
});
}

async run() {
console.log('Running backup');

await this.activateCliMode();
await this.sendCommand("dump all");

setTimeout(async () => {
this.sendCommand("exit", this.onClose);
await this.save(this.outputHistory);
}, 1500);
}

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

bufView[0] = 0x23;

serial.send(bufferOut);

setTimeout(() => {
this.outputHistory = '';
resolve();
}, 500);
});
}

async 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);
}

async sendCommand(line, callback) {
this.sendSerial(`${line}\n`, callback);
}

execute(callback) {
this.callback = callback;

const port = PortHandler.portPicker.selectedPort;

if (port.startsWith('serial')) {
const baud = parseInt($('#flash_manual_baud_rate').val()) || 115200;
serial.addEventListener('connect', this.handleConnect.bind(this), { once: true });
serial.connect(port, { baudRate: baud });
} else {
gui_log(i18n.getMessage('firmwareFlasherNoPortSelected'));
}
}
}

export default new AutoBackup();