add support for flashing esp32 devices
This commit is contained in:
305
index.html
305
index.html
@@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
<div id="app" class="space-y-2 p-3">
|
<div id="app" class="space-y-2 p-3">
|
||||||
|
|
||||||
|
<!-- header -->
|
||||||
<div class="flex border bg-gray-50 p-3 rounded shadow">
|
<div class="flex border bg-gray-50 p-3 rounded shadow">
|
||||||
<div class="mr-3">
|
<div class="mr-3">
|
||||||
<img src="reticulum_logo_512.png" class="w-14 h-14"/>
|
<img src="reticulum_logo_512.png" class="w-14 h-14"/>
|
||||||
@@ -39,15 +40,43 @@
|
|||||||
<div class="border bg-gray-50 rounded shadow">
|
<div class="border bg-gray-50 rounded shadow">
|
||||||
|
|
||||||
<div class="border-b px-2 py-1">
|
<div class="border-b px-2 py-1">
|
||||||
1. Put device into DFU Mode
|
1. Select your device
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
|
|
||||||
|
<div class="flex mb-1 space-x-1">
|
||||||
|
<div class="min-w-[70px] my-auto text-right">Product</div>
|
||||||
|
<select v-model="selectedProduct" class="min-w-[250px] bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block pl-2 pr-8">
|
||||||
|
<option :value="null" disabled>Select a Product</option>
|
||||||
|
<option v-for="product of products" :value="product">{{ product.name }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1">
|
||||||
|
<div class="min-w-[70px] my-auto text-right">Model</div>
|
||||||
|
<select v-model="selectedModel" class="min-w-[250px] bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block pl-2 pr-8">
|
||||||
|
<option :value="null" disabled>Select a Model</option>
|
||||||
|
<option v-if="selectedProduct" v-for="model of selectedProduct.models" :value="model">{{ model.name }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- button to enter dfu mode on PLATFORM_NRF52 -->
|
||||||
|
<div v-if="selectedProduct?.platform === 0x70" class="p-3 border-t">
|
||||||
<button @click="enterDfuMode" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
|
<button @click="enterDfuMode" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
|
||||||
Enter DFU Mode
|
Enter DFU Mode
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t px-2 py-1">
|
||||||
|
<div class="text-sm space-x-1">
|
||||||
|
<span>Can't find your device? Open an issue on</span>
|
||||||
|
<a target="_blank" href="https://github.com/liamcottle/rnode-flasher" class="text-blue-500 hover:underline">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border bg-gray-50 rounded shadow">
|
<div class="border bg-gray-50 rounded shadow">
|
||||||
@@ -58,18 +87,11 @@
|
|||||||
|
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
|
|
||||||
<div class="flex mb-2">
|
|
||||||
<div class="text-sm bg-red-100 px-2 py-1 border rounded">
|
|
||||||
<span class="text-red-500">*</span>
|
|
||||||
<span>Only RAK4631 can be flashed at this time, ESP32 support is coming later.</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<input ref="file" type="file"/>
|
<input ref="file" type="file"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!isFlashing">
|
<div v-if="!isFlashing" class="space-x-1">
|
||||||
<button @click="flash" :disabled="isFlashing" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
|
<button @click="flash" :disabled="isFlashing" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
|
||||||
Flash Now
|
Flash Now
|
||||||
</button>
|
</button>
|
||||||
@@ -103,27 +125,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
|
<button @click="provision" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
|
||||||
<div class="flex mb-1 space-x-1">
|
Provision
|
||||||
<div class="min-w-[70px] my-auto text-right">Product</div>
|
</button>
|
||||||
<select v-model="selectedProduct" class="min-w-[250px] bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block pl-2 pr-8">
|
|
||||||
<option :value="null" disabled>Select a Product</option>
|
|
||||||
<option v-for="product of products" :value="product">{{ product.name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="flex mb-1 space-x-1">
|
|
||||||
<div class="min-w-[70px] my-auto text-right">Model</div>
|
|
||||||
<select v-model="selectedModel" class="min-w-[250px] bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block pl-2 pr-8">
|
|
||||||
<option :value="null" disabled>Select a Model</option>
|
|
||||||
<option v-if="selectedProduct" v-for="model of selectedProduct.models" :value="model">{{ model.name }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button @click="provision" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
|
|
||||||
Provision
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -220,6 +224,13 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- setup esptool-js -->
|
||||||
|
<script type="module">
|
||||||
|
import { ESPLoader, Transport } from "https://unpkg.com/esptool-js@0.4.5/bundle.js";
|
||||||
|
window.ESPLoader = ESPLoader;
|
||||||
|
window.Transport = Transport;
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
@@ -231,9 +242,34 @@
|
|||||||
selectedProduct: null,
|
selectedProduct: null,
|
||||||
selectedModel: null,
|
selectedModel: null,
|
||||||
products: [
|
products: [
|
||||||
|
{
|
||||||
|
name: "Heltec LoRa32 v3",
|
||||||
|
id: ROM.PRODUCT_H32_V3,
|
||||||
|
platform: ROM.PLATFORM_ESP32,
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: ROM.MODEL_C5,
|
||||||
|
name: "433 MHz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ROM.MODEL_CA,
|
||||||
|
name: "868 MHz / 915 MHz / 923 MHz",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
flash_config: {
|
||||||
|
flash_size: "8MB",
|
||||||
|
flash_files: {
|
||||||
|
"0xe000": "rnode_firmware_heltec32v3.boot_app0",
|
||||||
|
"0x0": "rnode_firmware_heltec32v3.bootloader",
|
||||||
|
"0x10000": "rnode_firmware_heltec32v3.bin",
|
||||||
|
"0x8000": "rnode_firmware_heltec32v3.partitions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "RAK4631",
|
name: "RAK4631",
|
||||||
id: ROM.PRODUCT_RAK4631,
|
id: ROM.PRODUCT_RAK4631,
|
||||||
|
platform: ROM.PLATFORM_NRF52,
|
||||||
models: [
|
models: [
|
||||||
{
|
{
|
||||||
id: ROM.MODEL_11,
|
id: ROM.MODEL_11,
|
||||||
@@ -245,39 +281,66 @@
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Heltec LoRa32 v3",
|
|
||||||
id: ROM.PRODUCT_H32_V3,
|
|
||||||
models: [
|
|
||||||
{
|
|
||||||
id: ROM.MODEL_C5,
|
|
||||||
name: "433 MHz",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: ROM.MODEL_CA,
|
|
||||||
name: "868 MHz / 915 MHz / 923 MHz",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "LilyGO T-Beam",
|
name: "LilyGO T-Beam",
|
||||||
id: ROM.PRODUCT_TBEAM,
|
id: ROM.PRODUCT_TBEAM,
|
||||||
|
platform: ROM.PLATFORM_ESP32,
|
||||||
models: [
|
models: [
|
||||||
{
|
{
|
||||||
id: ROM.MODEL_E4,
|
id: ROM.MODEL_E4,
|
||||||
name: "433 MHz (with SX1278 chip)",
|
name: "433 MHz (with SX1278 chip)",
|
||||||
|
flash_config: {
|
||||||
|
flash_size: "4MB",
|
||||||
|
flash_files: {
|
||||||
|
"0xe000": "rnode_firmware_tbeam.boot_app0",
|
||||||
|
"0x1000": "rnode_firmware_tbeam.bootloader",
|
||||||
|
"0x10000": "rnode_firmware_tbeam.bin",
|
||||||
|
"0x210000": "console_image.bin",
|
||||||
|
"0x8000": "rnode_firmware_tbeam.partitions",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ROM.MODEL_E9,
|
id: ROM.MODEL_E9,
|
||||||
name: "868/915/923 MHz (with SX1276 chip)",
|
name: "868/915/923 MHz (with SX1276 chip)",
|
||||||
|
flash_config: {
|
||||||
|
flash_size: "4MB",
|
||||||
|
flash_files: {
|
||||||
|
"0xe000": "rnode_firmware_tbeam.boot_app0",
|
||||||
|
"0x1000": "rnode_firmware_tbeam.bootloader",
|
||||||
|
"0x10000": "rnode_firmware_tbeam.bin",
|
||||||
|
"0x210000": "console_image.bin",
|
||||||
|
"0x8000": "rnode_firmware_tbeam.partitions",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ROM.MODEL_E3,
|
id: ROM.MODEL_E3,
|
||||||
name: "433 MHz (with SX1268 chip)",
|
name: "433 MHz (with SX1268 chip)",
|
||||||
|
flash_config: {
|
||||||
|
flash_size: "4MB",
|
||||||
|
flash_files: {
|
||||||
|
"0xe000": "rnode_firmware_tbeam_sx1262.boot_app0",
|
||||||
|
"0x1000": "rnode_firmware_tbeam_sx1262.bootloader",
|
||||||
|
"0x10000": "rnode_firmware_tbeam_sx1262.bin",
|
||||||
|
"0x210000": "console_image.bin",
|
||||||
|
"0x8000": "rnode_firmware_tbeam_sx1262.partitions",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ROM.MODEL_E8,
|
id: ROM.MODEL_E8,
|
||||||
name: "868/915/923 MHz (with SX1262 chip)",
|
name: "868/915/923 MHz (with SX1262 chip)",
|
||||||
|
flash_config: {
|
||||||
|
flash_size: "4MB",
|
||||||
|
flash_files: {
|
||||||
|
"0xe000": "rnode_firmware_tbeam_sx1262.boot_app0",
|
||||||
|
"0x1000": "rnode_firmware_tbeam_sx1262.bootloader",
|
||||||
|
"0x10000": "rnode_firmware_tbeam_sx1262.bin",
|
||||||
|
"0x210000": "console_image.bin",
|
||||||
|
"0x8000": "rnode_firmware_tbeam_sx1262.partitions",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -352,6 +415,22 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
async flash() {
|
async flash() {
|
||||||
|
switch(this.selectedProduct?.platform){
|
||||||
|
case ROM.PLATFORM_ESP32: {
|
||||||
|
await this.flashEsp32();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ROM.PLATFORM_NRF52: {
|
||||||
|
await this.flashNrf52();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
alert("Please select a device first");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async flashNrf52() {
|
||||||
|
|
||||||
// ensure firmware file selected
|
// ensure firmware file selected
|
||||||
const file = this.$refs["file"].files[0];
|
const file = this.$refs["file"].files[0];
|
||||||
@@ -388,6 +467,129 @@
|
|||||||
this.isFlashing = false;
|
this.isFlashing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// close port
|
||||||
|
console.log("Closing serial port");
|
||||||
|
await this.serialPort.close();
|
||||||
|
|
||||||
|
},
|
||||||
|
async flashEsp32() {
|
||||||
|
|
||||||
|
// ensure ESPLoader is available
|
||||||
|
if(!window.ESPLoader){
|
||||||
|
alert("esptool-js could not be loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure flash config is known, use from selected model, or fallback to selected product
|
||||||
|
const flashConfig = this.selectedModel?.flash_config ?? this.selectedProduct?.flash_config;
|
||||||
|
if(!flashConfig){
|
||||||
|
alert("Flash config is not available for the selected device.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure firmware file selected
|
||||||
|
const file = this.$refs["file"].files[0];
|
||||||
|
if(!file){
|
||||||
|
alert("Select a firmware file first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ask for serial port
|
||||||
|
const serialPort = await this.askForSerialPort();
|
||||||
|
if(!serialPort){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update progress
|
||||||
|
this.isFlashing = true;
|
||||||
|
this.flashingProgress = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// read zip file
|
||||||
|
const blobReader = new window.zip.BlobReader(file);
|
||||||
|
const zipReader = new window.zip.ZipReader(blobReader);
|
||||||
|
const zipEntries = await zipReader.getEntries();
|
||||||
|
|
||||||
|
// get files to flash
|
||||||
|
const filesToFlash = [];
|
||||||
|
for(const [address, filename] of Object.entries(flashConfig.flash_files)){
|
||||||
|
|
||||||
|
// find file inside zip
|
||||||
|
const file = zipEntries.find((zipEntry) => zipEntry.filename === filename);
|
||||||
|
if(!file){
|
||||||
|
throw filename + " not found in firmware file!";
|
||||||
|
}
|
||||||
|
|
||||||
|
// get file data as binary string
|
||||||
|
const blob = await file.getData(new window.zip.BlobWriter());
|
||||||
|
const data = await this.readAsBinaryString(blob); // fixme: deprecated, but works for now
|
||||||
|
|
||||||
|
// add to files to flash
|
||||||
|
filesToFlash.push({
|
||||||
|
"address": parseInt(address),
|
||||||
|
"data": data,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// init transport and esploader
|
||||||
|
const transport = new window.Transport(serialPort, true);
|
||||||
|
const esploader = new window.ESPLoader({
|
||||||
|
transport: transport,
|
||||||
|
baudrate: 921600,
|
||||||
|
debugLogging: false,
|
||||||
|
enableTracing: false,
|
||||||
|
terminal: {
|
||||||
|
clean() {
|
||||||
|
|
||||||
|
},
|
||||||
|
writeLine(data) {
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
write(data) {
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// load device info
|
||||||
|
await esploader.main();
|
||||||
|
|
||||||
|
// flash device
|
||||||
|
await esploader.writeFlash({
|
||||||
|
fileArray: filesToFlash,
|
||||||
|
flashSize: flashConfig.flash_size,
|
||||||
|
flashMode: "DIO",
|
||||||
|
flashFreq: "80MHz",
|
||||||
|
eraseAll: false,
|
||||||
|
compress: true,
|
||||||
|
calculateMD5Hash: (image) => CryptoJS.MD5(CryptoJS.enc.Latin1.parse(image)),
|
||||||
|
reportProgress: (fileIndex, written, total) => {
|
||||||
|
const currentFilePercentage = (written / total) * 100;
|
||||||
|
this.flashingProgress = Math.floor(currentFilePercentage);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// reboot device
|
||||||
|
await transport.setDTR(false);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
await transport.setDTR(true);
|
||||||
|
|
||||||
|
// flashing successful
|
||||||
|
alert("Firmware has been flashed!");
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
alert("Firmware flashing failed: " + e);
|
||||||
|
console.log(e);
|
||||||
|
} finally {
|
||||||
|
this.isFlashing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close port
|
||||||
|
console.log("Closing serial port");
|
||||||
|
await serialPort.close();
|
||||||
|
|
||||||
},
|
},
|
||||||
async detect() {
|
async detect() {
|
||||||
|
|
||||||
@@ -849,6 +1051,21 @@
|
|||||||
alert("TNC mode has been disabled!");
|
alert("TNC mode has been disabled!");
|
||||||
|
|
||||||
},
|
},
|
||||||
|
async readAsBinaryString(blob) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
resolve(reader.result);
|
||||||
|
};
|
||||||
|
reader.readAsBinaryString(blob);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedProduct() {
|
||||||
|
// reset selected model when changing selected product
|
||||||
|
this.selectedModel = null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}).mount('#app');
|
}).mount('#app');
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -178,10 +178,6 @@ class Nrf52DfuFlasher {
|
|||||||
console.log("Sending firmware");
|
console.log("Sending firmware");
|
||||||
await this.sendFirmware(firmware, progressCallback);
|
await this.sendFirmware(firmware, progressCallback);
|
||||||
|
|
||||||
// close port
|
|
||||||
console.log("Closing serial port");
|
|
||||||
await this.serialPort.close();
|
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
// sleep(self.dfu_transport.get_activate_wait_time())
|
// sleep(self.dfu_transport.get_activate_wait_time())
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user