Merge pull request #2 from Huibean/pr-add-firmware-upgrade-hex-support

firmware upgrade add .hex support
This commit is contained in:
Huibean Luo
2025-08-14 10:08:57 +08:00
committed by GitHub
2 changed files with 117 additions and 7 deletions

View File

@@ -58,14 +58,119 @@ class FileServer {
* @param {string} path - The virtual path to register the file under
* @return {Promise} - Resolves when file is loaded
*/
/**
* Parse Intel HEX format and convert to binary ArrayBuffer
* @param {string} hexContent - The Intel HEX file content as string
* @returns {ArrayBuffer} - Binary data
*/
parseIntelHex(hexContent) {
const lines = hexContent.split(/\r?\n/);
let binaryData = [];
let minAddress = Infinity;
let maxAddress = 0;
let addressMap = {};
let baseAddress = 0; // For extended address records
for (let line of lines) {
line = line.trim();
if (!line || !line.startsWith(':')) continue;
// Parse Intel HEX record
const byteCount = parseInt(line.substring(1, 3), 16);
const address = parseInt(line.substring(3, 7), 16);
const recordType = parseInt(line.substring(7, 9), 16);
const data = line.substring(9, 9 + (byteCount * 2));
const checksum = parseInt(line.substring(9 + (byteCount * 2), 9 + (byteCount * 2) + 2), 16);
// Verify checksum
let sum = byteCount + ((address >> 8) & 0xFF) + (address & 0xFF) + recordType;
for (let i = 0; i < data.length; i += 2) {
sum += parseInt(data.substring(i, i + 2), 16);
}
sum = (~sum + 1) & 0xFF;
if (sum !== checksum) {
throw new Error(`Checksum mismatch in line: ${line}`);
}
if (recordType === 0x00) { // Data record
const bytes = [];
for (let i = 0; i < data.length; i += 2) {
bytes.push(parseInt(data.substring(i, i + 2), 16));
}
// Store data at address (with base address offset)
for (let i = 0; i < bytes.length; i++) {
const addr = baseAddress + address + i;
addressMap[addr] = bytes[i];
minAddress = Math.min(minAddress, addr);
maxAddress = Math.max(maxAddress, addr);
}
} else if (recordType === 0x04) { // Extended Linear Address
// Upper 16 bits of address
const upperAddress = parseInt(data, 16);
baseAddress = upperAddress << 16;
console.log(`Extended Linear Address: base = 0x${baseAddress.toString(16)}`);
} else if (recordType === 0x02) { // Extended Segment Address
// Segment address (multiply by 16)
const segmentAddress = parseInt(data, 16);
baseAddress = segmentAddress << 4;
console.log(`Extended Segment Address: base = 0x${baseAddress.toString(16)}`);
} else if (recordType === 0x01) { // End of file
console.log('End of Intel HEX file');
break;
}
}
if (minAddress === Infinity) {
throw new Error('No data found in Intel HEX file');
}
// Convert to contiguous binary array
const size = maxAddress - minAddress + 1;
const buffer = new ArrayBuffer(size);
const view = new Uint8Array(buffer);
// Fill with 0xFF (typical for flash memory)
view.fill(0xFF);
// Copy data
for (let addr = minAddress; addr <= maxAddress; addr++) {
if (addressMap[addr] !== undefined) {
view[addr - minAddress] = addressMap[addr];
}
}
console.log(`Intel HEX parsed: ${lines.length} lines, address range 0x${minAddress.toString(16)}-0x${maxAddress.toString(16)}, binary size: ${size} bytes`);
return buffer;
}
loadFile(file, path = null) {
return new Promise((resolve, reject) => {
// Use the file name as the path if no path is provided
const filePath = path || file.name;
// Check if this is a .hex file
const isHexFile = file.name.toLowerCase().endsWith('.hex');
const reader = new FileReader();
reader.onload = (e) => {
const buffer = e.target.result;
let buffer;
if (isHexFile) {
// Parse Intel HEX format
const hexContent = e.target.result;
try {
buffer = this.parseIntelHex(hexContent);
} catch (error) {
console.error('Error parsing Intel HEX file:', error);
reject(new Error('Invalid Intel HEX format'));
return;
}
} else {
// Use raw binary data for .bin files
buffer = e.target.result;
}
this.files[filePath] = {
name: file.name,
size: buffer.byteLength,
@@ -82,7 +187,12 @@ class FileServer {
reject(error);
};
// Read as text for .hex files, as binary for others
if (isHexFile) {
reader.readAsText(file);
} else {
reader.readAsArrayBuffer(file);
}
});
}

View File

@@ -17,11 +17,11 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
const file = event.target.files[0];
if (!file) return;
// Validate file extension is .bin
// Validate file extension is .bin or .hex
const fileExtension = file.name.split('.').pop().toLowerCase();
if (fileExtension !== 'bin') {
if (fileExtension !== 'bin' && fileExtension !== 'hex') {
setUpdateStatus('error');
setStatusMessage('Invalid file type. Please select a .bin firmware file.');
setStatusMessage('Invalid file type. Please select a .bin or .hex firmware file.');
return;
}
@@ -143,7 +143,7 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
<DialogTitle>Firmware Update</DialogTitle>
<DialogContent>
<Typography variant="body2" gutterBottom>
Please select the firmware file (.bin) to upload to node {targetNodeId}.
Please select the firmware file (.bin|.hex) to upload to node {targetNodeId}.
</Typography>
<Button
@@ -156,7 +156,7 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
<input
type="file"
hidden
accept=".bin"
accept=".bin,.hex"
onChange={handleFileChange}
/>
</Button>