Restoring ability to load local configuration for a target (#3066)
Based on device auto detect, and the use of local flash files.10.9-maintenance
commit
6bb248a9e3
|
@ -6722,5 +6722,8 @@
|
|||
},
|
||||
"firmwareFlasherBuildMotorProtocols": {
|
||||
"message": "Motor Protocols"
|
||||
},
|
||||
"firmwareFlasherConfigurationFile": {
|
||||
"message": "Configuration Filename:"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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}`);
|
||||
|
|
|
@ -111,6 +111,7 @@
|
|||
<script type="text/javascript" src="./js/Beepers.js"></script>
|
||||
<script type="text/javascript" src="./js/release_checker.js"></script>
|
||||
<script type="text/javascript" src="./js/release_loader.js"></script>
|
||||
<script type="text/javascript" src="./js/ConfigInserter.js"></script>
|
||||
<script type="text/javascript" src="./js/Analytics.js"></script>
|
||||
<script type="text/javascript" src="./js/GitHubApi.js"></script>
|
||||
<script type="module" src="./js/main.js"></script>
|
||||
|
|
|
@ -223,6 +223,9 @@
|
|||
<strong i18n="firmwareFlasherReleaseDate"></strong>
|
||||
<span class="date"></span>
|
||||
<br />
|
||||
<strong i18n="firmwareFlasherConfigurationFile"></strong>
|
||||
<span class="configFilename"></span>
|
||||
<br />
|
||||
</div>
|
||||
<div class="margin-bottom" id="cloudTargetInfo">
|
||||
<strong i18n="firmwareFlasherCloudBuildDetails"></strong>
|
||||
|
|
Loading…
Reference in New Issue