import "./lib/beer.min.js"; import { createApp, reactive, ref, nextTick } from "./lib/vue.min.js"; import { Dfu } from "./lib/dfu.js"; import { ESPLoader, Transport, HardReset } from "./lib/esp32.js"; 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', }; function setup() { const consoleEditBox = ref(); const consoleWindow = ref(); 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 serialCon = reactive({ instance: null, opened: false, content: '', edit: '', }); window.app = { selected, flashing, serialCon }; const log = { clean() { flashing.log = '' }, write(data) { flashing.log += data }, writeLine(data) { flashing.log += data + '\n' } }; 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 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(); } 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 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({}); 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; } 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; } }, }; 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); 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; } } }; 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 }}
		{{ serialCon.content }}
		
>
`; createApp({ setup, template }).mount('#flasher');