Merge pull request #2 from Huibean/pr-add-firmware-upgrade-hex-support
firmware upgrade add .hex support
This commit is contained in:
@@ -58,14 +58,119 @@ class FileServer {
|
|||||||
* @param {string} path - The virtual path to register the file under
|
* @param {string} path - The virtual path to register the file under
|
||||||
* @return {Promise} - Resolves when file is loaded
|
* @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) {
|
loadFile(file, path = null) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Use the file name as the path if no path is provided
|
// Use the file name as the path if no path is provided
|
||||||
const filePath = path || file.name;
|
const filePath = path || file.name;
|
||||||
|
|
||||||
|
// Check if this is a .hex file
|
||||||
|
const isHexFile = file.name.toLowerCase().endsWith('.hex');
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
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] = {
|
this.files[filePath] = {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: buffer.byteLength,
|
size: buffer.byteLength,
|
||||||
@@ -82,7 +187,12 @@ class FileServer {
|
|||||||
reject(error);
|
reject(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.readAsArrayBuffer(file);
|
// Read as text for .hex files, as binary for others
|
||||||
|
if (isHexFile) {
|
||||||
|
reader.readAsText(file);
|
||||||
|
} else {
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
|
|||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
// Validate file extension is .bin
|
// Validate file extension is .bin or .hex
|
||||||
const fileExtension = file.name.split('.').pop().toLowerCase();
|
const fileExtension = file.name.split('.').pop().toLowerCase();
|
||||||
if (fileExtension !== 'bin') {
|
if (fileExtension !== 'bin' && fileExtension !== 'hex') {
|
||||||
setUpdateStatus('error');
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
|
|||||||
<DialogTitle>Firmware Update</DialogTitle>
|
<DialogTitle>Firmware Update</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Typography variant="body2" gutterBottom>
|
<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>
|
</Typography>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -156,7 +156,7 @@ const FirmwareUpdateModal = ({ open, onClose, targetNodeId }) => {
|
|||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
hidden
|
hidden
|
||||||
accept=".bin"
|
accept=".bin,.hex"
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user