Skip to content

Commit

Permalink
Refactor auto-backup in flashing tab for PWA (#4005)
Browse files Browse the repository at this point in the history
* Make auto-backup while flashing work for PWA

* Fixes per review

* Prevent caching of buttonYesCallback
  • Loading branch information
haslinghuis authored Jun 5, 2024
1 parent 2f4337b commit 0ada2ce
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 184 deletions.
186 changes: 2 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,7 @@ firmware_flasher.initialize = function (callback) {
text: i18n.getMessage('firmwareFlasherRemindBackup'),
buttonYesText: i18n.getMessage('firmwareFlasherBackup'),
buttonNoText: i18n.getMessage('firmwareFlasherBackupIgnore'),
buttonYesCallback: () => firmware_flasher.backupConfig(initiateFlashing),
buttonYesCallback: () => AutoBackup.execute(initiateFlashing),
buttonNoCallback: initiateFlashing,
},
);
Expand Down Expand Up @@ -1313,188 +1313,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
134 changes: 134 additions & 0 deletions src/js/utils/AutoBackup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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) {
gui_log(i18n.getMessage(event.detail ? 'serialPortClosedOk' : '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;
const baud = PortHandler.portPicker.selectedBauds;

if (port.startsWith('serial')) {
serial.addEventListener('connect', this.handleConnect.bind(this), { once: true });
serial.connect(port, { baudRate: baud });
} else {
gui_log(i18n.getMessage('firmwareFlasherNoPortSelected'));
}
}
}

export default new AutoBackup();

0 comments on commit 0ada2ce

Please sign in to comment.