From 752cac9b7a4baa3e56f01d9cd8d8b51c90a5f997 Mon Sep 17 00:00:00 2001 From: blckmn Date: Sat, 5 Nov 2022 11:49:13 +1100 Subject: Restoring ability to load local configuration for a target --- locales/en/messages.json | 3 ++ src/js/ConfigInserter.js | 107 ++++++++++++++++++++++++++++++++++++++++ src/js/tabs/firmware_flasher.js | 76 +++++++++++++++++++++++++--- src/main.html | 1 + src/tabs/firmware_flasher.html | 3 ++ 5 files changed, 182 insertions(+), 8 deletions(-) create mode 100644 src/js/ConfigInserter.js diff --git a/locales/en/messages.json b/locales/en/messages.json index 9b6d4c7b..2adabf70 100644 --- a/locales/en/messages.json +++ b/locales/en/messages.json @@ -6725,5 +6725,8 @@ }, "firmwareFlasherBuildMotorProtocols": { "message": "Motor Protocols" + }, + "firmwareFlasherConfigurationFile": { + "message": "Configuration Filename:" } } diff --git a/src/js/ConfigInserter.js b/src/js/ConfigInserter.js new file mode 100644 index 00000000..8103df64 --- /dev/null +++ b/src/js/ConfigInserter.js @@ -0,0 +1,107 @@ +'use strict'; + +const ConfigInserter = function () { +}; + +const CUSTOM_DEFAULTS_POINTER_ADDRESS = 0x08002800; +const BLOCK_SIZE = 16384; + +function seek(firmware, address) { + let index = 0; + for (; index < firmware.data.length && address >= firmware.data[index].address + firmware.data[index].bytes; index++) { + // empty for loop to increment index + } + + const result = { + lineIndex: index, + }; + + if (firmware.data[index] && address >= firmware.data[index].address) { + result.byteIndex = address - firmware.data[index].address; + } + + return result; +} + +function readUint32(firmware, index) { + let result = 0; + for (let position = 0; position < 4; position++) { + result += firmware.data[index.lineIndex].data[index.byteIndex++] << (8 * position); + if (index.byteIndex >= firmware.data[index.lineIndex].bytes) { + index.lineIndex++; + index.byteIndex = 0; + } + } + + return result; +} + +function getCustomDefaultsArea(firmware) { + const result = {}; + + const index = seek(firmware, CUSTOM_DEFAULTS_POINTER_ADDRESS); + + if (index.byteIndex === undefined) { + return; + } + + result.startAddress = readUint32(firmware, index); + result.endAddress = readUint32(firmware, index); + + return result; +} + +function generateData(firmware, input, startAddress) { + let address = startAddress; + + const index = seek(firmware, address); + + if (index.byteIndex !== undefined) { + throw new Error('Configuration area in firmware not free.'); + } + + let inputIndex = 0; + while (inputIndex < input.length) { + const remaining = input.length - inputIndex; + const line = { + address: address, + bytes: BLOCK_SIZE > remaining ? remaining : BLOCK_SIZE, + data: [], + }; + + if (firmware.data[index.lineIndex] && (line.address + line.bytes) > firmware.data[index.lineIndex].address) { + throw new Error("Aborting data generation, free area too small."); + } + + for (let i = 0; i < line.bytes; i++) { + line.data.push(input.charCodeAt(inputIndex++)); + } + + address = address + line.bytes; + + firmware.data.splice(index.lineIndex++, 0, line); + } + + firmware.bytes_total += input.length; +} + +const CONFIG_LABEL = `Custom defaults inserted in`; + +ConfigInserter.prototype.insertConfig = function (firmware, config) { + console.time(CONFIG_LABEL); + + const input = `# Betaflight\n${config}\0`; + const customDefaultsArea = getCustomDefaultsArea(firmware); + + if (!customDefaultsArea || customDefaultsArea.endAddress - customDefaultsArea.startAddress === 0) { + return false; + } else if (input.length >= customDefaultsArea.endAddress - customDefaultsArea.startAddress) { + throw new Error(`Custom defaults area too small (${customDefaultsArea.endAddress - customDefaultsArea.startAddress} bytes), ${input.length + 1} bytes needed.`); + } + + generateData(firmware, input, customDefaultsArea.startAddress); + + console.timeEnd(CONFIG_LABEL); + + return true; +}; diff --git a/src/js/tabs/firmware_flasher.js b/src/js/tabs/firmware_flasher.js index 0645b3b9..278cc0f4 100644 --- a/src/js/tabs/firmware_flasher.js +++ b/src/js/tabs/firmware_flasher.js @@ -9,6 +9,8 @@ const firmware_flasher = { 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 + configFilename: null, + config: {}, developmentFirmwareLoaded: false, // Is the firmware to be flashed from the development branch? }; @@ -61,6 +63,7 @@ firmware_flasher.initialize = function (callback) { $('div.release_info .name').text(summary.release).prop('href', summary.releaseUrl); $('div.release_info .date').text(summary.date); $('div.release_info #targetMCU').text(summary.mcu); + $('div.release_info .configFilename').text(self.isConfigLocal ? self.configFilename : "[default]"); if (summary.cloudBuild) { $('div.release_info #cloudTargetInfo').show(); @@ -86,6 +89,18 @@ firmware_flasher.initialize = function (callback) { } } + function clearBoardConfig() { + self.config = {}; + self.isConfigLocal = false; + self.configFilename = null; + } + + function setBoardConfig(config, filename) { + self.config = config; + self.isConfigLocal = filename !== undefined; + self.configFilename = filename !== undefined ? filename : null; + } + function processHex(data, key) { self.intel_hex = data; @@ -284,7 +299,7 @@ firmware_flasher.initialize = function (callback) { } function clearBufferedFirmware() { - self.isConfigLocal = false; + clearBoardConfig(); self.intel_hex = undefined; self.parsed_hex = undefined; self.localFirmwareLoaded = false; @@ -356,6 +371,33 @@ firmware_flasher.initialize = function (callback) { } }); + function cleanUnifiedConfigFile(input) { + let output = []; + let inComment = false; + for (let i=0; i < input.length; i++) { + if (input.charAt(i) === "\n" || input.charAt(i) === "\r") { + inComment = false; + } + + if (input.charAt(i) === "#") { + inComment = true; + } + + if (!inComment && input.charCodeAt(i) > 255) { + self.flashingMessage(i18n.getMessage('firmwareFlasherConfigCorrupted'), self.FLASH_MESSAGE_TYPES.INVALID); + GUI.log(i18n.getMessage('firmwareFlasherConfigCorruptedLogMessage')); + return null; + } + + if (input.charCodeAt(i) > 255) { + output.push('_'); + } else { + output.push(input.charAt(i)); + } + } + return output.join(''); + } + const portPickerElement = $('div#port-picker #port'); function flashFirmware(firmware) { const options = {}; @@ -594,7 +636,7 @@ firmware_flasher.initialize = function (callback) { accepts: [ { description: 'target files', - extensions: ['hex'], + extensions: ['hex', 'config'], }, ], }, function (fileEntry) { @@ -633,7 +675,13 @@ firmware_flasher.initialize = function (callback) { } }); } else { - self.flashingMessage(i18n.getMessage('firmwareFlasherHexCorrupted'), self.FLASH_MESSAGE_TYPES.INVALID); + clearBufferedFirmware(); + + let config = cleanUnifiedConfigFile(e.target.result); + if (config !== null) { + setBoardConfig(config, file.name); + flashingMessageLocal(file.name); + } } } }; @@ -687,6 +735,10 @@ firmware_flasher.initialize = function (callback) { }); } + if (summary.configuration && !self.isConfigLocal) { + setBoardConfig(summary.configuration.join('\n')); + } + $("a.load_remote_file").removeClass('disabled'); } @@ -794,9 +846,6 @@ firmware_flasher.initialize = function (callback) { self.releaseLoader.loadTargetHex(summary.url, (hex) => onLoadSuccess(hex, fileName), onLoadFailed); } - const target = $('select[name="board"] option:selected').val(); - const release = $('select[name="firmware_version"] option:selected').val(); - if (self.summary) { // undefined while list is loading or while running offline $("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonDownloading')); $("a.load_remote_file").addClass('disabled'); @@ -804,9 +853,9 @@ firmware_flasher.initialize = function (callback) { showReleaseNotes(self.summary); if (self.summary.cloudBuild === true) { - self.releaseLoader.loadTarget(target, release, requestCloudBuild, onLoadFailed); + requestCloudBuild(self.summary); } else { - self.releaseLoader.loadTarget(target, release, requestLegacyBuild, onLoadFailed); + requestLegacyBuild(self.summary); } } else { $('span.progressLabel').attr('i18n','firmwareFlasherFailedToLoadOnlineFirmware').removeClass('i18n-replaced'); @@ -926,6 +975,17 @@ firmware_flasher.initialize = function (callback) { if (!GUI.connect_lock) { // button disabled while flashing is in progress if (self.parsed_hex) { try { + if (self.config && !self.parsed_hex.configInserted) { + const configInserter = new ConfigInserter(); + + if (configInserter.insertConfig(self.parsed_hex, self.config)) { + self.parsed_hex.configInserted = true; + } else { + console.log('Firmware does not support custom defaults.'); + clearBoardConfig(); + } + } + flashFirmware(self.parsed_hex); } catch (e) { console.log(`Flashing failed: ${e.message}`); diff --git a/src/main.html b/src/main.html index 1a9b3cfe..cccddec1 100644 --- a/src/main.html +++ b/src/main.html @@ -111,6 +111,7 @@ + diff --git a/src/tabs/firmware_flasher.html b/src/tabs/firmware_flasher.html index 96fdc7ad..d03d2f99 100644 --- a/src/tabs/firmware_flasher.html +++ b/src/tabs/firmware_flasher.html @@ -223,6 +223,9 @@
+ + +
-- cgit v1.2.3