diff --git a/config.json b/config.json index 7d86dac..f5e4e30 100644 --- a/config.json +++ b/config.json @@ -1,375 +1,375 @@ { - "basePath": "./firmware", - "role": { - "gui": { - "icon": "gradient", - "title": "Client GUI", - "tooltip": "all your device settings are saved on internal flash" - }, - "guiSD": { - "icon": "gradient", - "title": "Client GUI: data on SD card", - "tooltip": "all your device settings are saved on SD card" - }, - "companionBle": { - "icon": "smartphone", - "class": "primary-text", - "title": "Companion radio: Bluetooth", - "tooltip": "Chat via mobile phone App or Web Client" - }, - "companionUsb": { - "icon": "usb", - "title": "Companion radio: USB", - "tooltip": "Chat via Web client or command line client" - }, - "repeater": { - "icon": "cell_tower", - "title": "Repeater", - "tooltip": "Special role just for routing packets. Configured via Console on flasher main page" - }, - "roomServer": { - "icon": "forum", - "title": "Room Server", - "tooltip": "Special role for local room and routing packets. Configured via Console on flasher main page" - } - }, - "device": [ - { - "name": "Lilygo T-Deck", - "tooltip": "", - "type": "esp32", - "firmware": [ - { - "role": "gui", - "files": [ - { - "type": "flash", - "name": "RippleUltra-TDeck-v6.0-beta21-merged.bin", - "title": "Combined app+partition+bootloader firmware bin" - }, - { - "type": "download", - "name": "RippleUltra-TDeck-v6.0-beta21.bin", - "title": "App firmware bin (use with m5 booloader)" - } - ] - }, - { - "role": "guiSD", - "files": [ - { - "type": "flash", - "name": "RippleUltra-TDeck-SD-v6.0-beta21-merged.bin", - "title": "Combined app+partition+bootloader firmware bin" - }, - { - "type": "download", - "name": "RippleUltra-TDeck-SD-v6.0-beta21.bin", - "title": "App firmware bin (use with m5 booloader)" - } - ] - } - ] - }, - { - "name": "Lilygo T3 S3", - "type": "esp32", - "tooltip": "", - "firmware": [ - { - "role": "companionUsb", - "files": [ - { - "type": "flash", - "name": "LilyGo_T3S3_sx1262_companion_radio_usb.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - }, - { - "role": "companionBle", - "files": [ - { - "type": "flash", - "name": "LilyGo_T3S3_sx1262_companion_radio_ble.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - }, - { - "role": "repeater", - "files": [ - { - "type": "flash", - "name": "LilyGo_T3S3_sx1262_Repeater.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - } - ] - }, - { - "name": "Heltec v2", - "type": "esp32", - "tooltip": "", - "firmware": [ - { - "role": "companionUsb", - "files": [ - { - "type": "flash", - "name": "Heltec_v2_companion_radio_usb.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - }, - { - "role": "repeater", - "files": [ - { - "type": "flash", - "name": "Heltec_v2_repeater.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - } - ] - }, - { - "name": "Heltec v3", - "type": "esp32", - "tooltip": "", - "firmware": [ - { - "role": "companionUsb", - "files": [ - { - "type": "flash", - "name": "Heltec_v3_companion_radio_usb.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - }, - { - "role": "companionBle", - "files": [ - { - "type": "flash", - "name": "Heltec_v3_companion_radio_ble.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - }, - { - "role": "repeater", - "files": [ - { - "type": "flash", - "name": "Heltec_v3_repeater.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - }, - { - "role": "roomServer", - "files": [ - { - "type": "flash", - "name": "Heltec_v3_room_server.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - } - ] - }, - { - "name": "Heltec T114", - "type": "nrf52", - "tooltip": "", - "firmware": [ - { - "role": "repeater", - "files": [ - { - "type": "flash", - "name": "Heltec_T114_repeater.zip", - "title": "firmware OTA zip" - }, - { - "type": "download", - "name": "Heltec_T114_repeater.uf2", - "title": "firmware uf2" - } - ] - }, - { - "role": "roomServer", - "files": [ - { - "type": "flash", - "name": "Heltec_T114_room_server.zip", - "title": "firmware OTA zip" - }, - { - "type": "download", - "name": "Heltec_T114_room_server.uf2", - "title": "firmware uf2" - } - ] - } - ] - }, - { - "name": "RAK Wireless WisBlock / WisMesh (RAK 4631)", - "type": "nrf52", - "tooltip": "", - "firmware": [ - { - "role": "companionUsb", - "files": [ - { - "type": "flash", - "name": "RAK_4631_companion_radio_usb.zip", - "title": "firmware OTA zip" - }, - { - "type": "download", - "name": "RAK_4631_companion_radio_usb.uf2", - "title": "firmware uf2" - } - ] - }, - { - "role": "companionBle", - "files": [ - { - "type": "flash", - "name": "RAK_4631_companion_radio_ble.zip", - "title": "firmware OTA zip" - }, - { - "type": "download", - "name": "RAK_4631_companion_radio_ble.uf2", - "title": "firmware uf2" - } - ] - }, - { - "role": "repeater", - "files": [ - { - "type": "flash", - "name": "RAK_4631_Repeater.zip", - "title": "firmware OTA zip" - }, - { - "type": "download", - "name": "RAK_4631_Repeater.uf2", - "title": "firmware uf2" - } - ] - }, - { - "role": "roomServer", - "files": [ - { - "type": "flash", - "name": "RAK_4631_room_server.zip", - "title": "firmware OTA zip" - }, - { - "type": "download", - "name": "RAK_4631_room_server.uf2", - "title": "firmware uf2" - } - ] - } - ] - }, - { - "name": "Seeed Studio SenseCAP T1000-E", - "tooltip": "", - "type": "nrf52", - "firmware": [ - { - "role": "companionBle", - "files": [ - { - "type": "flash", - "name": "Seeed_T1000e_companion_radio_ble.zip", - "title": "firmware OTA zip" - }, - { - "type": "download", - "name": "Seeed_T1000e_companion_radio_ble.uf2", - "title": "firmware uf2" - } - ] - } - ] - }, - { - "name": "Seeed Studio Xiao C3", - "tooltip": "", - "type": "esp32", - "firmware": [ - { - "role": "repeater", - "title": "Repeater (Semtech SX1262)", - "files": [ - { - "type": "flash", - "name": "Xiao_C3_Repeater_sx1262.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - }, - { - "role": "repeater", - "title": "Repeater (Semtech SX1268)", - "files": [ - { - "type": "flash", - "name": "Xiao_C3_Repeater_sx1268.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - } - ] - }, - { - "name": "Seeed Studio Xiao S3 WIO", - "tooltip": "", - "type": "esp32", - "firmware": [ - { - "role": "repeater", - "files": [ - { - "type": "flash", - "name": "Xiao_S3_WIO_Repeater.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - } - ] - }, - { - "name": "UnitEng Station G2", - "tooltip": "", - "type": "esp32", - "firmware": [ - { - "role": "repeater", - "files": [ - { - "type": "flash", - "name": "Station_G2_repeater.bin", - "title": "Combined app+partition+bootloader firmware bin" - } - ] - } - ] - } - ] + "basePath": "./firmware", + "role": { + "gui": { + "icon": "gradient", + "title": "Client GUI", + "tooltip": "all your device settings are saved on internal flash" + }, + "guiSD": { + "icon": "gradient", + "title": "Client GUI: data on SD card", + "tooltip": "all your device settings are saved on SD card" + }, + "companionBle": { + "icon": "smartphone", + "class": "primary-text", + "title": "Companion radio: Bluetooth", + "tooltip": "Chat via mobile phone App or Web Client" + }, + "companionUsb": { + "icon": "usb", + "title": "Companion radio: USB", + "tooltip": "Chat via Web client or command line client" + }, + "repeater": { + "icon": "cell_tower", + "title": "Repeater", + "tooltip": "Special role just for routing packets. Configured via Console on flasher main page" + }, + "roomServer": { + "icon": "forum", + "title": "Room Server", + "tooltip": "Special role for local room and routing packets. Configured via Console on flasher main page" + } + }, + "device": [ + { + "name": "Lilygo T-Deck", + "tooltip": "", + "type": "esp32", + "firmware": [ + { + "role": "gui", + "files": [ + { + "type": "flash", + "name": "RippleUltra-TDeck-v6.0-beta21-merged.bin", + "title": "Combined app+partition+bootloader firmware bin" + }, + { + "type": "download", + "name": "RippleUltra-TDeck-v6.0-beta21.bin", + "title": "App firmware bin (use with m5 booloader)" + } + ] + }, + { + "role": "guiSD", + "files": [ + { + "type": "flash", + "name": "RippleUltra-TDeck-SD-v6.0-beta21-merged.bin", + "title": "Combined app+partition+bootloader firmware bin" + }, + { + "type": "download", + "name": "RippleUltra-TDeck-SD-v6.0-beta21.bin", + "title": "App firmware bin (use with m5 booloader)" + } + ] + } + ] + }, + { + "name": "Lilygo T3 S3", + "type": "esp32", + "tooltip": "", + "firmware": [ + { + "role": "companionUsb", + "files": [ + { + "type": "flash", + "name": "LilyGo_T3S3_sx1262_companion_radio_usb.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + }, + { + "role": "companionBle", + "files": [ + { + "type": "flash", + "name": "LilyGo_T3S3_sx1262_companion_radio_ble.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + }, + { + "role": "repeater", + "files": [ + { + "type": "flash", + "name": "LilyGo_T3S3_sx1262_Repeater.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + } + ] + }, + { + "name": "Heltec v2", + "type": "esp32", + "tooltip": "", + "firmware": [ + { + "role": "companionUsb", + "files": [ + { + "type": "flash", + "name": "Heltec_v2_companion_radio_usb.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + }, + { + "role": "repeater", + "files": [ + { + "type": "flash", + "name": "Heltec_v2_repeater.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + } + ] + }, + { + "name": "Heltec v3", + "type": "esp32", + "tooltip": "", + "firmware": [ + { + "role": "companionUsb", + "files": [ + { + "type": "flash", + "name": "Heltec_v3_companion_radio_usb.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + }, + { + "role": "companionBle", + "files": [ + { + "type": "flash", + "name": "Heltec_v3_companion_radio_ble.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + }, + { + "role": "repeater", + "files": [ + { + "type": "flash", + "name": "Heltec_v3_repeater.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + }, + { + "role": "roomServer", + "files": [ + { + "type": "flash", + "name": "Heltec_v3_room_server.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + } + ] + }, + { + "name": "Heltec T114", + "type": "nrf52", + "tooltip": "", + "firmware": [ + { + "role": "repeater", + "files": [ + { + "type": "flash", + "name": "Heltec_T114_repeater.zip", + "title": "firmware OTA zip" + }, + { + "type": "download", + "name": "Heltec_T114_repeater.uf2", + "title": "firmware uf2" + } + ] + }, + { + "role": "roomServer", + "files": [ + { + "type": "flash", + "name": "Heltec_T114_room_server.zip", + "title": "firmware OTA zip" + }, + { + "type": "download", + "name": "Heltec_T114_room_server.uf2", + "title": "firmware uf2" + } + ] + } + ] + }, + { + "name": "RAK Wireless WisBlock / WisMesh (RAK 4631)", + "type": "nrf52", + "tooltip": "", + "firmware": [ + { + "role": "companionUsb", + "files": [ + { + "type": "flash", + "name": "RAK_4631_companion_radio_usb.zip", + "title": "firmware OTA zip" + }, + { + "type": "download", + "name": "RAK_4631_companion_radio_usb.uf2", + "title": "firmware uf2" + } + ] + }, + { + "role": "companionBle", + "files": [ + { + "type": "flash", + "name": "RAK_4631_companion_radio_ble.zip", + "title": "firmware OTA zip" + }, + { + "type": "download", + "name": "RAK_4631_companion_radio_ble.uf2", + "title": "firmware uf2" + } + ] + }, + { + "role": "repeater", + "files": [ + { + "type": "flash", + "name": "RAK_4631_Repeater.zip", + "title": "firmware OTA zip" + }, + { + "type": "download", + "name": "RAK_4631_Repeater.uf2", + "title": "firmware uf2" + } + ] + }, + { + "role": "roomServer", + "files": [ + { + "type": "flash", + "name": "RAK_4631_room_server.zip", + "title": "firmware OTA zip" + }, + { + "type": "download", + "name": "RAK_4631_room_server.uf2", + "title": "firmware uf2" + } + ] + } + ] + }, + { + "name": "Seeed Studio SenseCAP T1000-E", + "tooltip": "", + "type": "nrf52", + "firmware": [ + { + "role": "companionBle", + "files": [ + { + "type": "flash", + "name": "Seeed_T1000e_companion_radio_ble.zip", + "title": "firmware OTA zip" + }, + { + "type": "download", + "name": "Seeed_T1000e_companion_radio_ble.uf2", + "title": "firmware uf2" + } + ] + } + ] + }, + { + "name": "Seeed Studio Xiao C3", + "tooltip": "", + "type": "esp32", + "firmware": [ + { + "role": "repeater", + "title": "Repeater (Semtech SX1262)", + "files": [ + { + "type": "flash", + "name": "Xiao_C3_Repeater_sx1262.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + }, + { + "role": "repeater", + "title": "Repeater (Semtech SX1268)", + "files": [ + { + "type": "flash", + "name": "Xiao_C3_Repeater_sx1268.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + } + ] + }, + { + "name": "Seeed Studio Xiao S3 WIO", + "tooltip": "", + "type": "esp32", + "firmware": [ + { + "role": "repeater", + "files": [ + { + "type": "flash", + "name": "Xiao_S3_WIO_Repeater.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + } + ] + }, + { + "name": "UnitEng Station G2", + "tooltip": "", + "type": "esp32", + "firmware": [ + { + "role": "repeater", + "files": [ + { + "type": "flash", + "name": "Station_G2_repeater.bin", + "title": "Combined app+partition+bootloader firmware bin" + } + ] + } + ] + } + ] } \ No newline at end of file diff --git a/css/flasher.css b/css/flasher.css index e69de29..7e35a9f 100644 --- a/css/flasher.css +++ b/css/flasher.css @@ -0,0 +1,48 @@ +body { + display: flex; + height: 100vh; +} + +#flasher { + flex-grow: 1; +} +#flasher img.device { + width: 300px; +} +#flasher div.autoscroller { + overflow: auto; + max-height: 300px; + display: flex; + flex-direction: column-reverse; +} +#flasher pre.term { + font-family: monospace; +} +#flasher .overlay { + display: flex !important; + flex-direction: column; +} +#flasher .console { + overflow: auto; + display: flex; + flex-direction: column; + flex-grow: 1; + margin: 0; +} +#flasher .console .holder { + display: flex; + flex-direction: row; + gap: 10px; +} +#flasher .console-input { + flex-grow: 1; + display: block; + appearance: none; + background: transparent; + border: 0; + font-family: monospace; + font-size: .875rem; +} +#flasher .console-input:focus, #flasher console-input:focus{ + outline: none; +} \ No newline at end of file diff --git a/flasher.js b/flasher.js index 4550726..6fec1e1 100644 --- a/flasher.js +++ b/flasher.js @@ -7,390 +7,390 @@ import { SerialConsole } from './lib/console.js'; const res = await fetch('./config.json'); const config = await res.json(); const commandReference = { - 'set freq ': 'Set frequency {Mhz}', - 'time ': 'Set time {epoch-secs}', - 'erase': 'Erase filesystem', - 'advert': 'Send Advertisment packet', - 'reboot': 'Reboot device', - 'clock': 'Display current time', - 'password ': 'Set new password', - 'log': 'Ouput log', - 'log start': 'Start packet logging to file system', - 'log stop': 'Stop packet logging to file system', - 'log erase': 'Erase the packet logs from file system', - 'ver': 'Show device version', - 'set af ': 'Set Air-time factor', - 'set tx ': 'Set Tx power {dBm}', - 'set repeat ': 'Set repeater mode {on|off}', - 'set advert.interval ': 'Set advert rebroadcast interval {minutes}', - 'set guest.password ': 'Set guest password', - 'set name ': 'Set advertisement name', - 'set lat': 'Set the advertisement map latitude', - 'set lon': 'Set the advertisement map longitude', + 'set freq ': 'Set frequency {Mhz}', + 'time ': 'Set time {epoch-secs}', + 'erase': 'Erase filesystem', + 'advert': 'Send Advertisment packet', + 'reboot': 'Reboot device', + 'clock': 'Display current time', + 'password ': 'Set new password', + 'log': 'Ouput log', + 'log start': 'Start packet logging to file system', + 'log stop': 'Stop packet logging to file system', + 'log erase': 'Erase the packet logs from file system', + 'ver': 'Show device version', + 'set af ': 'Set Air-time factor', + 'set tx ': 'Set Tx power {dBm}', + 'set repeat ': 'Set repeater mode {on|off}', + 'set advert.interval ': 'Set advert rebroadcast interval {minutes}', + 'set guest.password ': 'Set guest password', + 'set name ': 'Set advertisement name', + 'set lat': 'Set the advertisement map latitude', + 'set lon': 'Set the advertisement map longitude', }; function setup() { - const consoleEditBox = ref(); - const consoleWindow = ref(); + const consoleEditBox = ref(); + const consoleWindow = ref(); - const selected = reactive({ - device: null, - firmware: null, - wipe: false, - port: null - }); + const selected = reactive({ + device: null, + firmware: null, + wipe: false, + port: null + }); - const flashing = reactive({ - instance: null, - active: false, - percentage: 0, - log: '', - error: '', - dfuComplete: false, - }); + const flashing = reactive({ + instance: null, + active: false, + percentage: 0, + log: '', + error: '', + dfuComplete: false, + }); - const serialCon = reactive({ - instance: null, - opened: false, - content: '', - edit: '', - }); + const serialCon = reactive({ + instance: null, + opened: false, + content: '', + edit: '', + }); - window.app = { selected, flashing, serialCon }; + window.app = { selected, flashing, serialCon }; - const log = { - clean() { flashing.log = '' }, - write(data) { flashing.log += data }, - writeLine(data) { flashing.log += data + '\n' } - }; + const log = { + clean() { flashing.log = '' }, + write(data) { flashing.log += data }, + writeLine(data) { flashing.log += data + '\n' } + }; - const refresh = () => { - location.reload(); - } + const refresh = () => { + location.reload(); + } - const flasherCleanup = async () => { - const port = selected.port; - flashing.active = false; - flashing.log = ''; - flashing.error = ''; - flashing.dfuComplete = false; - flashing.percentage = 0; - selected.firmware = null; - selected.wipe = false; - selected.device = null; - if(flashing.instance instanceof ESPLoader) { - await flashing.instance?.hr.reset(); - await flashing.instance?.transport?.disconnect(); - } - flashing.instance = null; - } + const flasherCleanup = async () => { + const port = selected.port; + flashing.active = false; + flashing.log = ''; + flashing.error = ''; + flashing.dfuComplete = false; + flashing.percentage = 0; + selected.firmware = null; + selected.wipe = false; + selected.device = null; + if(flashing.instance instanceof ESPLoader) { + await flashing.instance?.hr.reset(); + await flashing.instance?.transport?.disconnect(); + } + flashing.instance = null; + } - const openSerialCon = async() => { - const port = selected.port = await navigator.serial.requestPort(); - const serialConsole = serialCon.instance = new SerialConsole(port); - serialCon.content = 'Welcome to MeshCore serial console.\n'; - serialCon.content += 'If you came here right after flashing, please restart your device.\n'; - serialCon.content += 'Click on the cursor to get all supported commands.\n\n'; - serialConsole.onOutput = (text) => { - serialCon.content += text; - }; - serialConsole.connect(); - serialCon.opened = true; - await nextTick(); + const openSerialCon = async() => { + const port = selected.port = await navigator.serial.requestPort(); + const serialConsole = serialCon.instance = new SerialConsole(port); + serialCon.content = 'Welcome to MeshCore serial console.\n'; + serialCon.content += 'If you came here right after flashing, please restart your device.\n'; + serialCon.content += 'Click on the cursor to get all supported commands.\n\n'; + serialConsole.onOutput = (text) => { + serialCon.content += text; + }; + serialConsole.connect(); + serialCon.opened = true; + await nextTick(); - consoleEditBox.value.focus(); - } + consoleEditBox.value.focus(); + } - const closeSerialCon = async() => { - serialCon.opened = false; - await serialCon.instance.disconnect(); - } + const closeSerialCon = async() => { + serialCon.opened = false; + await serialCon.instance.disconnect(); + } - const sendCommand = async(text) => { - const consoleEl = consoleWindow.value; - serialCon.edit = ''; - await serialCon.instance.sendCommand(text); - setTimeout(() => consoleEl.scrollTop = consoleEl.scrollHeight, 100); - } + const sendCommand = async(text) => { + const consoleEl = consoleWindow.value; + serialCon.edit = ''; + await serialCon.instance.sendCommand(text); + setTimeout(() => consoleEl.scrollTop = consoleEl.scrollHeight, 100); + } - const dfuMode = async() => { - await Dfu.forceDfuMode(await navigator.serial.requestPort({})) - flashing.dfuComplete = true; - } + const dfuMode = async() => { + await Dfu.forceDfuMode(await navigator.serial.requestPort({})) + flashing.dfuComplete = true; + } - const flashDevice = async() => { - const device = selected.device; - const firmware = selected.firmware; - const flashFile = firmware.files.find(f => f.type === 'flash'); - if(!flashFile) { - alert('Cannot find configuration for flash file! please report this to Discord.') - flasherCleanup(); - return; - } - const url = `${config.basePath}/${flashFile.name}`; - const resp = await fetch(url); - const port = selected.port = await navigator.serial.requestPort({}); + const flashDevice = async() => { + const device = selected.device; + const firmware = selected.firmware; + const flashFile = firmware.files.find(f => f.type === 'flash'); + if(!flashFile) { + alert('Cannot find configuration for flash file! please report this to Discord.') + flasherCleanup(); + return; + } + const url = `${config.basePath}/${flashFile.name}`; + const resp = await fetch(url); + const port = selected.port = await navigator.serial.requestPort({}); - if(device.type === 'esp32') { - let esploader; - let fileData; - let transport; + if(device.type === 'esp32') { + let esploader; + let fileData; + let transport; - try { - const reader = new FileReader(); - fileData = await new Promise(async (resolve) => { - reader.addEventListener('load', () => resolve(reader.result)); - reader.readAsBinaryString(await resp.blob()); - }); - } - catch(e) { - console.error(e); - flashing.error = `Cannot read flash file: ${e}`; - return; - } + try { + const reader = new FileReader(); + fileData = await new Promise(async (resolve) => { + reader.addEventListener('load', () => resolve(reader.result)); + reader.readAsBinaryString(await resp.blob()); + }); + } + catch(e) { + console.error(e); + flashing.error = `Cannot read flash file: ${e}`; + return; + } - const flashOptions = { - terminal: log, - compress: true, - eraseAll: selected.wipe, - flashSize: 'keep', - flashMode: 'keep', - flashFreq: 'keep', - baudrate: 115200, - romBaudrate: 115200, - enableTracing: false, - fileArray: [{ - data: fileData, - address: 0 - }], - reportProgress: async (fileIndex, written, total) => { - flashing.percentage = (written / total) * 100; + const flashOptions = { + terminal: log, + compress: true, + eraseAll: selected.wipe, + flashSize: 'keep', + flashMode: 'keep', + flashFreq: 'keep', + baudrate: 115200, + romBaudrate: 115200, + enableTracing: false, + fileArray: [{ + data: fileData, + address: 0 + }], + reportProgress: async (fileIndex, written, total) => { + flashing.percentage = (written / total) * 100; - // we're done with this file - if (written === total) { - return; - } - }, - }; + // we're done with this file + if (written === total) { + return; + } + }, + }; - try { - flashing.active = true; - transport = new Transport(port, true); - flashOptions.transport = transport - flashing.instance = esploader = new ESPLoader(flashOptions); - esploader.hr = new HardReset(transport); - await esploader.main(); - await esploader.flashId(); - } - catch(e) { - console.error(e); - flashing.error = `Failed to initialize. Did you place the device into firmware download mode? Detail: ${e}`; - esploader = null; - return; - } + try { + flashing.active = true; + transport = new Transport(port, true); + flashOptions.transport = transport + flashing.instance = esploader = new ESPLoader(flashOptions); + esploader.hr = new HardReset(transport); + await esploader.main(); + await esploader.flashId(); + } + catch(e) { + console.error(e); + flashing.error = `Failed to initialize. Did you place the device into firmware download mode? Detail: ${e}`; + esploader = null; + return; + } - try { - await esploader.writeFlash(flashOptions); - await esploader.after(); - } - catch(e) { - console.error(e); - flashing.error = `ESP32 flashing failed: ${e}`; - await esploader.hardReset(); - await transport.disconnect(); - return; - } - } - else if(device.type === 'nrf52') { - const dfu = this.flashing.instance = new Dfu(port, selected.wipe); + try { + await esploader.writeFlash(flashOptions); + await esploader.after(); + } + catch(e) { + console.error(e); + flashing.error = `ESP32 flashing failed: ${e}`; + await esploader.hardReset(); + await transport.disconnect(); + return; + } + } + else if(device.type === 'nrf52') { + const dfu = this.flashing.instance = new Dfu(port, selected.wipe); - const zipFile = await resp.blob(); - flashing.active = true; + const zipFile = await resp.blob(); + flashing.active = true; - try { - await dfu.dfuUpdate(zipFile, async (progress) => { - flashing.percentage = progress; - }); - } - catch(e) { - console.error(e); - flashing.error = `nRF flashing failed: ${e}`; - return; - } - } - }; + try { + await dfu.dfuUpdate(zipFile, async (progress) => { + flashing.percentage = progress; + }); + } + catch(e) { + console.error(e); + flashing.error = `nRF flashing failed: ${e}`; + return; + } + } + }; - return { - consoleEditBox, consoleWindow, - config, selected, flashing, - flashDevice, flasherCleanup, dfuMode, - serialCon, openSerialCon, sendCommand, closeSerialCon, - refresh, commandReference - } + return { + consoleEditBox, consoleWindow, + config, selected, flashing, + flashDevice, flasherCleanup, dfuMode, + serialCon, openSerialCon, sendCommand, closeSerialCon, + refresh, commandReference + } } const template = `
-
-
- -
-
-
-
-
Flashing failed!
-

{{ flashing.error }}

-

-
-
-
-
-
-
-
Flashing...
-

Please do not disconnect the device

-
-
-
Flashing complete!
-

- -

-
-
-
-
{{ flashing.terminal }}
-
- -
-
-
-
- - -
- - -
- -
-
-
- - -
- -
-
-
- - -
- -
+
+
+ +
+
+
+
+
Flashing failed!
+

{{ flashing.error }}

+

+
+
+
+
+
+
+
Flashing...
+

Please do not disconnect the device

+
+
+
Flashing complete!
+

+ +

+
+
+
+
{{ flashing.terminal }}
+
+ +
+
+
+
+ + +
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
- - - -
- -
-
-		{{ serialCon.content }}
-		
- > - -
-
+ + + +
+ +
+
+    {{ serialCon.content }}
+    
+ > + +
+
`; diff --git a/lib/dfu.js b/lib/dfu.js index 11ef35d..2d4c133 100644 --- a/lib/dfu.js +++ b/lib/dfu.js @@ -27,409 +27,409 @@ const DFU_UPDATE_MODE_APP = 4; // --- Utility Functions (adapted from dfu/util.py) --- function int32ToBytes(value) { - const buffer = new ArrayBuffer(4); - const view = new DataView(buffer); - view.setUint32(0, value, true); // Little-endian - return new Uint8Array(buffer); + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + view.setUint32(0, value, true); // Little-endian + return new Uint8Array(buffer); } function int16ToBytes(value) { - const buffer = new ArrayBuffer(2); - const view = new DataView(buffer); - view.setUint16(0, value, true); // Little-endian - return new Uint8Array(buffer); + const buffer = new ArrayBuffer(2); + const view = new DataView(buffer); + view.setUint16(0, value, true); // Little-endian + return new Uint8Array(buffer); } function slipPartsToFourBytes(seq, dip, rp, pktType, pktLen) { - const ints = new Uint8Array(4); - ints[0] = seq | (((seq + 1) % 8) << 3) | (dip << 6) | (rp << 7); - ints[1] = pktType | ((pktLen & 0x000F) << 4); - ints[2] = (pktLen & 0x0FF0) >> 4; - ints[3] = (~(ints[0] + ints[1] + ints[2]) + 1) & 0xFF; - return ints; + const ints = new Uint8Array(4); + ints[0] = seq | (((seq + 1) % 8) << 3) | (dip << 6) | (rp << 7); + ints[1] = pktType | ((pktLen & 0x000F) << 4); + ints[2] = (pktLen & 0x0FF0) >> 4; + ints[3] = (~(ints[0] + ints[1] + ints[2]) + 1) & 0xFF; + return ints; } function slipEncodeEscChars(data) { - const result = []; - for (const byte of data) { - if (byte === 0xC0) { - result.push(0xDB, 0xDC); - } else if (byte === 0xDB) { - result.push(0xDB, 0xDD); - } else { - result.push(byte); - } + const result = []; + for (const byte of data) { + if (byte === 0xC0) { + result.push(0xDB, 0xDC); + } else if (byte === 0xDB) { + result.push(0xDB, 0xDD); + } else { + result.push(byte); } - return new Uint8Array(result); + } + return new Uint8Array(result); } // --- CRC16 Calculation (adapted from dfu/crc16.py) --- function calcCrc16(data, crc = 0xFFFF) { - if (!(data instanceof Uint8Array)) { - throw new Error("calcCrc16 requires Uint8Array input"); - } - for (let i = 0; i < data.length; i++) { - crc = ((crc >> 8) & 0x00FF) | ((crc << 8) & 0xFF00); - crc ^= data[i]; - crc ^= (crc & 0x00FF) >> 4; - crc ^= (crc << 8) << 4; - crc ^= ((crc & 0x00FF) << 4) << 1; - } - return crc & 0xFFFF; + if (!(data instanceof Uint8Array)) { + throw new Error("calcCrc16 requires Uint8Array input"); + } + for (let i = 0; i < data.length; i++) { + crc = ((crc >> 8) & 0x00FF) | ((crc << 8) & 0xFF00); + crc ^= data[i]; + crc ^= (crc & 0x00FF) >> 4; + crc ^= (crc << 8) << 4; + crc ^= ((crc & 0x00FF) << 4) << 1; + } + return crc & 0xFFFF; } function sleep(milliseconds) { - return new Promise((resolve) => setTimeout(resolve, milliseconds)) + return new Promise((resolve) => setTimeout(resolve, milliseconds)) } // --- HciPacket Class (adapted from dfu/dfu_transport_serial.py) --- class HciPacket { - static sequenceNumber = 0; + static sequenceNumber = 0; - constructor(data) { - HciPacket.sequenceNumber = (HciPacket.sequenceNumber + 1) % 8; - let tempData = []; + constructor(data) { + HciPacket.sequenceNumber = (HciPacket.sequenceNumber + 1) % 8; + let tempData = []; - const slipBytes = slipPartsToFourBytes( - HciPacket.sequenceNumber, - DATA_INTEGRITY_CHECK_PRESENT, - RELIABLE_PACKET, - HCI_PACKET_TYPE, - data.length - ); - tempData = tempData.concat(Array.from(slipBytes)); + const slipBytes = slipPartsToFourBytes( + HciPacket.sequenceNumber, + DATA_INTEGRITY_CHECK_PRESENT, + RELIABLE_PACKET, + HCI_PACKET_TYPE, + data.length + ); + tempData = tempData.concat(Array.from(slipBytes)); - tempData = tempData.concat(Array.from(data)); + tempData = tempData.concat(Array.from(data)); - // Add CRC - const crc = calcCrc16(new Uint8Array(tempData)); - tempData.push(crc & 0xFF); - tempData.push((crc & 0xFF00) >> 8); + // Add CRC + const crc = calcCrc16(new Uint8Array(tempData)); + tempData.push(crc & 0xFF); + tempData.push((crc & 0xFF00) >> 8); - const encoded = slipEncodeEscChars(new Uint8Array(tempData)); - this.data = new Uint8Array([0xC0, ...encoded, 0xC0]); - } + const encoded = slipEncodeEscChars(new Uint8Array(tempData)); + this.data = new Uint8Array([0xC0, ...encoded, 0xC0]); + } } // --- Main DFU Class --- export class Dfu { - /** - * @param {SerialPort} port - The Web Serial API port object. - * @param {boolean} [eraseBeforeUpdate=false] - Whether to erase the entire flash before updating. - */ - constructor(port, eraseBeforeUpdate = false) { - this.port = port; - this.transferInProgress = false; - this.lastAck = -1; - this.eraseBeforeUpdate = eraseBeforeUpdate; // Store the erase flag + /** + * @param {SerialPort} port - The Web Serial API port object. + * @param {boolean} [eraseBeforeUpdate=false] - Whether to erase the entire flash before updating. + */ + constructor(port, eraseBeforeUpdate = false) { + this.port = port; + this.transferInProgress = false; + this.lastAck = -1; + this.eraseBeforeUpdate = eraseBeforeUpdate; // Store the erase flag + } + + async sendPacket(pkt) { + if (!this.port || !this.port.writable) { + throw new Error("Serial port not open or not writable."); } - async sendPacket(pkt) { - if (!this.port || !this.port.writable) { - throw new Error("Serial port not open or not writable."); - } - - const writer = this.port.writable.getWriter(); - try { - await writer.write(pkt.data); - console.debug("Sent packet:", pkt.data); - } finally { - writer.releaseLock(); - } - - await this.getAck(); // Wait for ACK after sending + const writer = this.port.writable.getWriter(); + try { + await writer.write(pkt.data); + console.debug("Sent packet:", pkt.data); + } finally { + writer.releaseLock(); } - async getAck() { - if (!this.port || !this.port.readable) { - throw new Error("Serial port not open or not readable."); + await this.getAck(); // Wait for ACK after sending + } + + async getAck() { + if (!this.port || !this.port.readable) { + throw new Error("Serial port not open or not readable."); + } + + const reader = this.port.readable.getReader(); + let buffer = []; + let c0Count = 0; + + try { + const startTime = Date.now(); + while (c0Count < 2) { + const { value, done } = await reader.read(); + if (done) { + throw new Error("Stream closed before receiving full ACK."); } - const reader = this.port.readable.getReader(); - let buffer = []; - let c0Count = 0; - - try { - const startTime = Date.now(); - while (c0Count < 2) { - const { value, done } = await reader.read(); - if (done) { - throw new Error("Stream closed before receiving full ACK."); - } - - if (value) { - for (const byte of value) { - buffer.push(byte); - if (byte === 0xC0) { - c0Count++; - } - } - } - - if (Date.now() - startTime > DEFAULT_SERIAL_PORT_TIMEOUT * 1000 * 5) { // Increased timeout for safety - HciPacket.sequenceNumber = 0; // Reset sequence number on timeout. - throw new Error("Timeout waiting for ACK."); - } + if (value) { + for (const byte of value) { + buffer.push(byte); + if (byte === 0xC0) { + c0Count++; } - } finally { - reader.releaseLock(); - } - // Decode SLIP - const decodedData = this.decodeSlip(buffer); - - // Extract ACK number (assuming it's in the decoded data) - if (decodedData.length < 2) { // Check for sufficient length - throw new Error("Received incomplete ACK."); - } - const ack = (decodedData[0] >> 3) & 0x07; - - // Check for valid ACK sequence - if (this.lastAck !== -1 && ack !== (this.lastAck + 1) % 8) { - HciPacket.sequenceNumber = 0; // Reset on bad ack - throw new Error(`Invalid ACK sequence. Expected ${(this.lastAck + 1) % 8}, got ${ack}`); - } - this.lastAck = ack; - - return ack; - } - - decodeSlip(data) { - const result = []; - let i = 0; - while (i < data.length) { - if (data[i] === 0xDB) { - i++; - if (i >= data.length) { - throw new Error("Invalid SLIP escape sequence: incomplete."); - } - if (data[i] === 0xDC) { - result.push(0xC0); - } else if (data[i] === 0xDD) { - result.push(0xDB); - } else { - throw new Error(`Invalid SLIP escape sequence: DB followed by ${data[i].toString(16)}`); - } - } else if (data[i] === 0xC0) { - // Ignore 0xC0 (start/end of packet) - } - else { - result.push(data[i]); - } - i++; - } - return new Uint8Array(result); - } - - async sendInitPacket(initPacket) { - const frame = new Uint8Array([ - ...int32ToBytes(DFU_INIT_PACKET), - ...initPacket, - ...int16ToBytes(0x0000), // Padding - ]); - const packet = new HciPacket(frame); - await this.sendPacket(packet); - } - - // THANKS Liam!!! - static async forceDfuMode(port) { - // open port - await port.open({ - baudRate: DFU_TOUCH_BAUD, - }); - - // wait SERIAL_PORT_OPEN_WAIT_TIME before closing port - await sleep(SERIAL_PORT_OPEN_WAIT_TIME * 1000); - - // close port - await port.close(); - - // wait TOUCH_RESET_WAIT_TIME for device to enter into DFU mode - await sleep(TOUCH_RESET_WAIT_TIME * 1000); - } - - async sendStartDfu(mode, softdeviceSize = 0, bootloaderSize = 0, appSize = 0) { - const frame = new Uint8Array([ - ...int32ToBytes(DFU_START_PACKET), - ...int32ToBytes(mode), - ...int32ToBytes(softdeviceSize), - ...int32ToBytes(bootloaderSize), - ...int32ToBytes(appSize), - ]); - - const packet = new HciPacket(frame); - await this.sendPacket(packet); - - // Calculate and apply erase wait time. - const totalSize = softdeviceSize + bootloaderSize + appSize; - const eraseWaitTime = Math.max(0.5, ((totalSize / FLASH_PAGE_SIZE) + 1) * FLASH_PAGE_ERASE_TIME); - await sleep(eraseWaitTime * 1000); - } - - - async sendErasePage(pageAddress) { - const frame = new Uint8Array([ - ...int32ToBytes(DFU_ERASE_PAGE), - ...int32ToBytes(pageAddress), - ]); - const packet = new HciPacket(frame); - await this.sendPacket(packet); - await sleep(FLASH_PAGE_ERASE_TIME * 1000); // Wait for page erase - } - - - async eraseFlash(appSize) { - console.log("Erasing flash..."); - const numPages = Math.ceil(appSize / FLASH_PAGE_SIZE); - - // Assuming application starts at address 0x00000000 - let startAddress = 0x00000000; - - for (let i = 0; i < numPages; i++) { - const pageAddress = startAddress + (i * FLASH_PAGE_SIZE); - console.log(`Erasing page ${i} at address 0x${pageAddress.toString(16)}`); - await this.sendErasePage(pageAddress); - } - console.log("Flash erase complete."); - } - - - async sendFirmware(firmware, progressCallback) { - const frames = []; - let totalBytes = firmware.length; - - // Chunk firmware into DFU packets - for (let i = 0; i < firmware.length; i += DFU_PACKET_MAX_SIZE) { - const chunk = firmware.subarray(i, i + DFU_PACKET_MAX_SIZE); - const frame = new Uint8Array([ - ...int32ToBytes(DFU_DATA_PACKET), - ...chunk, - ]); - const dataPacket = new HciPacket(frame); - frames.push(dataPacket); + } } - let bytesSent = 0; - // Send firmware packets - for (const [index, pkt] of frames.entries()) { - await this.sendPacket(pkt); - bytesSent += pkt.data.length - 6; // Correctly calculate sent bytes, excluding SLIP overhead - - if (progressCallback) { - const progress = Math.min(100, Math.round((bytesSent / totalBytes) * 100)); // Ensure progress doesn't exceed 100 - progressCallback(progress); - } - - // Wait after every 8 frames (one flash page) - if ((index + 1) % 8 === 0) { - await sleep(FLASH_PAGE_WRITE_TIME * 1000); - } + if (Date.now() - startTime > DEFAULT_SERIAL_PORT_TIMEOUT * 1000 * 5) { // Increased timeout for safety + HciPacket.sequenceNumber = 0; // Reset sequence number on timeout. + throw new Error("Timeout waiting for ACK."); } + } + } finally { + reader.releaseLock(); + } + // Decode SLIP + const decodedData = this.decodeSlip(buffer); - // Wait for the last page to be written + // Extract ACK number (assuming it's in the decoded data) + if (decodedData.length < 2) { // Check for sufficient length + throw new Error("Received incomplete ACK."); + } + const ack = (decodedData[0] >> 3) & 0x07; + + // Check for valid ACK sequence + if (this.lastAck !== -1 && ack !== (this.lastAck + 1) % 8) { + HciPacket.sequenceNumber = 0; // Reset on bad ack + throw new Error(`Invalid ACK sequence. Expected ${(this.lastAck + 1) % 8}, got ${ack}`); + } + this.lastAck = ack; + + return ack; + } + + decodeSlip(data) { + const result = []; + let i = 0; + while (i < data.length) { + if (data[i] === 0xDB) { + i++; + if (i >= data.length) { + throw new Error("Invalid SLIP escape sequence: incomplete."); + } + if (data[i] === 0xDC) { + result.push(0xC0); + } else if (data[i] === 0xDD) { + result.push(0xDB); + } else { + throw new Error(`Invalid SLIP escape sequence: DB followed by ${data[i].toString(16)}`); + } + } else if (data[i] === 0xC0) { + // Ignore 0xC0 (start/end of packet) + } + else { + result.push(data[i]); + } + i++; + } + return new Uint8Array(result); + } + + async sendInitPacket(initPacket) { + const frame = new Uint8Array([ + ...int32ToBytes(DFU_INIT_PACKET), + ...initPacket, + ...int16ToBytes(0x0000), // Padding + ]); + const packet = new HciPacket(frame); + await this.sendPacket(packet); + } + + // THANKS Liam!!! + static async forceDfuMode(port) { + // open port + await port.open({ + baudRate: DFU_TOUCH_BAUD, + }); + + // wait SERIAL_PORT_OPEN_WAIT_TIME before closing port + await sleep(SERIAL_PORT_OPEN_WAIT_TIME * 1000); + + // close port + await port.close(); + + // wait TOUCH_RESET_WAIT_TIME for device to enter into DFU mode + await sleep(TOUCH_RESET_WAIT_TIME * 1000); + } + + async sendStartDfu(mode, softdeviceSize = 0, bootloaderSize = 0, appSize = 0) { + const frame = new Uint8Array([ + ...int32ToBytes(DFU_START_PACKET), + ...int32ToBytes(mode), + ...int32ToBytes(softdeviceSize), + ...int32ToBytes(bootloaderSize), + ...int32ToBytes(appSize), + ]); + + const packet = new HciPacket(frame); + await this.sendPacket(packet); + + // Calculate and apply erase wait time. + const totalSize = softdeviceSize + bootloaderSize + appSize; + const eraseWaitTime = Math.max(0.5, ((totalSize / FLASH_PAGE_SIZE) + 1) * FLASH_PAGE_ERASE_TIME); + await sleep(eraseWaitTime * 1000); + } + + + async sendErasePage(pageAddress) { + const frame = new Uint8Array([ + ...int32ToBytes(DFU_ERASE_PAGE), + ...int32ToBytes(pageAddress), + ]); + const packet = new HciPacket(frame); + await this.sendPacket(packet); + await sleep(FLASH_PAGE_ERASE_TIME * 1000); // Wait for page erase + } + + + async eraseFlash(appSize) { + console.log("Erasing flash..."); + const numPages = Math.ceil(appSize / FLASH_PAGE_SIZE); + + // Assuming application starts at address 0x00000000 + let startAddress = 0x00000000; + + for (let i = 0; i < numPages; i++) { + const pageAddress = startAddress + (i * FLASH_PAGE_SIZE); + console.log(`Erasing page ${i} at address 0x${pageAddress.toString(16)}`); + await this.sendErasePage(pageAddress); + } + console.log("Flash erase complete."); + } + + + async sendFirmware(firmware, progressCallback) { + const frames = []; + let totalBytes = firmware.length; + + // Chunk firmware into DFU packets + for (let i = 0; i < firmware.length; i += DFU_PACKET_MAX_SIZE) { + const chunk = firmware.subarray(i, i + DFU_PACKET_MAX_SIZE); + const frame = new Uint8Array([ + ...int32ToBytes(DFU_DATA_PACKET), + ...chunk, + ]); + const dataPacket = new HciPacket(frame); + frames.push(dataPacket); + } + + let bytesSent = 0; + // Send firmware packets + for (const [index, pkt] of frames.entries()) { + await this.sendPacket(pkt); + bytesSent += pkt.data.length - 6; // Correctly calculate sent bytes, excluding SLIP overhead + + if (progressCallback) { + const progress = Math.min(100, Math.round((bytesSent / totalBytes) * 100)); // Ensure progress doesn't exceed 100 + progressCallback(progress); + } + + // Wait after every 8 frames (one flash page) + if ((index + 1) % 8 === 0) { await sleep(FLASH_PAGE_WRITE_TIME * 1000); - - // Send stop packet - const stopPacket = new HciPacket(int32ToBytes(DFU_STOP_DATA_PACKET)); - await this.sendPacket(stopPacket); + } } - async dfuUpdate(zipFile, progressCallback) { - if (this.transferInProgress) { - throw new Error("DFU update already in progress."); + // Wait for the last page to be written + await sleep(FLASH_PAGE_WRITE_TIME * 1000); + + // Send stop packet + const stopPacket = new HciPacket(int32ToBytes(DFU_STOP_DATA_PACKET)); + await this.sendPacket(stopPacket); + } + + async dfuUpdate(zipFile, progressCallback) { + if (this.transferInProgress) { + throw new Error("DFU update already in progress."); + } + this.transferInProgress = true; + this.lastAck = -1; // Reset last ACK + const decoder = new TextDecoder(); + try { + await this.port.open({ baudRate: 115200 }); // Open with correct baudrate + + const reader = new zip.ZipReader(new zip.BlobReader(zipFile)); + const entries = await reader.getEntries(); + + let manifest = null; + let firmwareFiles = {}; + + for (const entry of entries) { + const filename = decoder.decode(entry.rawFilename); + console.debug('Found zip filename: ', filename); + if (filename === 'manifest.json') { + const text = await entry.getData(new zip.TextWriter()); + manifest = JSON.parse(text); + } else if (filename.endsWith('.bin') || filename.endsWith('.dat')) { + firmwareFiles[filename] = await entry.getData(new zip.Uint8ArrayWriter()); } - this.transferInProgress = true; - this.lastAck = -1; // Reset last ACK - const decoder = new TextDecoder(); + } + + await reader.close(); + + if (!manifest) { + throw new Error("manifest.json not found in the ZIP file."); + } + if (!firmwareFiles[manifest.manifest.application.bin_file] || + !firmwareFiles[manifest.manifest.application.dat_file]) + { + throw new Error("Application .bin or .dat file not found."); + } + + const appBin = firmwareFiles[manifest.manifest.application.bin_file]; + const initPacket = firmwareFiles[manifest.manifest.application.dat_file]; + const appSize = appBin.length; + + // Erase flash if requested + if (this.eraseBeforeUpdate) { + await this.eraseFlash(appSize); + } + + // Start DFU + await this.sendStartDfu(DFU_UPDATE_MODE_APP, 0, 0, appSize); + + // Send Init Packet + await this.sendInitPacket(initPacket); + + // Send Firmware + await this.sendFirmware(appBin, progressCallback); + + console.log("DFU update complete."); + + } catch (error) { + console.error("DFU Update failed:", error); + throw error; // Re-throw the error for handling by the caller + } finally { + this.transferInProgress = false; + if (this.port && this.port.readable) { try { - await this.port.open({ baudRate: 115200 }); // Open with correct baudrate - - const reader = new zip.ZipReader(new zip.BlobReader(zipFile)); - const entries = await reader.getEntries(); - - let manifest = null; - let firmwareFiles = {}; - - for (const entry of entries) { - const filename = decoder.decode(entry.rawFilename); - console.debug('Found zip filename: ', filename); - if (filename === 'manifest.json') { - const text = await entry.getData(new zip.TextWriter()); - manifest = JSON.parse(text); - } else if (filename.endsWith('.bin') || filename.endsWith('.dat')) { - firmwareFiles[filename] = await entry.getData(new zip.Uint8ArrayWriter()); - } - } - - await reader.close(); - - if (!manifest) { - throw new Error("manifest.json not found in the ZIP file."); - } - if (!firmwareFiles[manifest.manifest.application.bin_file] || - !firmwareFiles[manifest.manifest.application.dat_file]) - { - throw new Error("Application .bin or .dat file not found."); - } - - const appBin = firmwareFiles[manifest.manifest.application.bin_file]; - const initPacket = firmwareFiles[manifest.manifest.application.dat_file]; - const appSize = appBin.length; - - // Erase flash if requested - if (this.eraseBeforeUpdate) { - await this.eraseFlash(appSize); - } - - // Start DFU - await this.sendStartDfu(DFU_UPDATE_MODE_APP, 0, 0, appSize); - - // Send Init Packet - await this.sendInitPacket(initPacket); - - // Send Firmware - await this.sendFirmware(appBin, progressCallback); - - console.log("DFU update complete."); + const reader = this.port.readable.getReader(); + await reader.cancel(); + reader.releaseLock(); } catch (error) { - console.error("DFU Update failed:", error); - throw error; // Re-throw the error for handling by the caller - } finally { - this.transferInProgress = false; - if (this.port && this.port.readable) { - try { - const reader = this.port.readable.getReader(); - await reader.cancel(); - reader.releaseLock(); - - } catch (error) { - // Ignore errors when trying to cancel the reader - console.debug(`Error: closing reader: ${error}`); - } - } - if (this.port && this.port.writable) { - try { - const writer = this.port.writable.getWriter(); - await writer.close(); - writer.releaseLock(); - } catch(error) { - // Ignore errors when trying to close the writer - console.debug(`Error: closing writer: ${error}`); - } - } - if (this.port) { - try { - await this.port.close(); - } - catch (error) { - // Ignore errors when trying to close the port - console.debug(`Error: closing port: ${error}`); - } - } + // Ignore errors when trying to cancel the reader + console.debug(`Error: closing reader: ${error}`); } + } + if (this.port && this.port.writable) { + try { + const writer = this.port.writable.getWriter(); + await writer.close(); + writer.releaseLock(); + } catch(error) { + // Ignore errors when trying to close the writer + console.debug(`Error: closing writer: ${error}`); + } + } + if (this.port) { + try { + await this.port.close(); + } + catch (error) { + // Ignore errors when trying to close the port + console.debug(`Error: closing port: ${error}`); + } + } } + } } \ No newline at end of file