Skip to content

Commit 8b653f1

Browse files
committed
feat: esp-idf flasher_args.json support
esp-idf projects can now specify `builder/flasher_args.json` as the firmware, to automatically load firmwares compiled by `idf.py build`. e.g. ```toml [wokwi] version = 1 firmware = 'build/flasher_args.json' elf = 'build/example_app.elf' ```
1 parent 99fe63d commit 8b653f1

File tree

3 files changed

+86
-3
lines changed

3 files changed

+86
-3
lines changed

src/esp32.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export interface IESP32FlasherJSON {
2+
write_flash_args: string[];
3+
flash_settings: FlashSettings;
4+
flash_files: Record<string, string>;
5+
bootloader: IFlashFile;
6+
app: IFlashFile;
7+
'partition-table': IFlashFile;
8+
extra_esptool_args: ExtraEsptoolArgs;
9+
}
10+
11+
export interface IFlashFile {
12+
offset: string;
13+
file: string;
14+
encrypted: string;
15+
}
16+
17+
export interface ExtraEsptoolArgs {
18+
after: string;
19+
before: string;
20+
stub: boolean;
21+
chip: string;
22+
}
23+
24+
export interface FlashSettings {
25+
flash_mode: string;
26+
flash_size: string;
27+
flash_freq: string;
28+
}

src/main.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { DelayCommand } from './scenario/DelayCommand.js';
1616
import { ExpectPinCommand } from './scenario/ExpectPinCommand.js';
1717
import { SetControlCommand } from './scenario/SetControlCommand.js';
1818
import { WaitSerialCommand } from './scenario/WaitSerialCommand.js';
19+
import { uploadFirmware } from './uploadFirmware.js';
1920

2021
const millis = 1_000_000;
2122

@@ -169,9 +170,7 @@ async function main() {
169170
try {
170171
await client.connected;
171172
await client.fileUpload('diagram.json', diagram);
172-
const extension = firmwarePath.split('.').pop();
173-
const firmwareName = `firmware.${extension}`;
174-
await client.fileUpload(firmwareName, readFileSync(firmwarePath));
173+
const firmwareName = await uploadFirmware(client, firmwarePath);
175174
await client.fileUpload('firmware.elf', readFileSync(elfPath));
176175

177176
for (const chip of chips) {

src/uploadFirmware.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { readFileSync } from 'fs';
2+
import { basename, resolve, dirname } from 'path';
3+
import { type APIClient } from './APIClient.js';
4+
import { type IESP32FlasherJSON } from './esp32.js';
5+
6+
interface IFirmwarePiece {
7+
offset: number;
8+
data: ArrayBuffer;
9+
}
10+
11+
const MAX_FIRMWARE_SIZE = 4 * 1024 * 1024;
12+
13+
export async function uploadESP32Firmware(client: APIClient, firmwarePath: string) {
14+
const flasherArgs = JSON.parse(readFileSync(firmwarePath, 'utf-8')) as IESP32FlasherJSON;
15+
if (!('flash_files' in flasherArgs)) {
16+
throw new Error('flash_files is not defined in flasher_args.json');
17+
}
18+
19+
const firmwareParts: IFirmwarePiece[] = [];
20+
let firmwareSize = 0;
21+
for (const [offset, file] of Object.entries(flasherArgs.flash_files)) {
22+
const offsetNum = parseInt(offset, 16);
23+
if (isNaN(offsetNum)) {
24+
throw new Error(`Invalid offset in flasher_args.json flash_files: ${offset}`);
25+
}
26+
27+
const data = readFileSync(resolve(dirname(firmwarePath), file));
28+
firmwareParts.push({ offset: offsetNum, data });
29+
firmwareSize = Math.max(firmwareSize, offsetNum + data.byteLength);
30+
}
31+
32+
if (firmwareSize > MAX_FIRMWARE_SIZE) {
33+
throw new Error(
34+
`Firmware size (${firmwareSize} bytes) exceeds the maximum supported size (${MAX_FIRMWARE_SIZE} bytes)`,
35+
);
36+
}
37+
38+
const firmwareData = new Uint8Array(firmwareSize);
39+
for (const { offset, data } of firmwareParts) {
40+
firmwareData.set(new Uint8Array(data), offset);
41+
}
42+
await client.fileUpload('firmware.bin', firmwareData);
43+
44+
return 'firmware.bin';
45+
}
46+
47+
export async function uploadFirmware(client: APIClient, firmwarePath: string) {
48+
if (basename(firmwarePath) === 'flasher_args.json') {
49+
return await uploadESP32Firmware(client, firmwarePath);
50+
}
51+
52+
const extension = firmwarePath.split('.').pop();
53+
const firmwareName = `firmware.${extension}`;
54+
await client.fileUpload(firmwareName, readFileSync(firmwarePath));
55+
return firmwareName;
56+
}

0 commit comments

Comments
 (0)