From 5d3ed846789bfa38aff8bc8a33819e20fa3268d7 Mon Sep 17 00:00:00 2001 From: makermelissa Date: Fri, 3 Jan 2025 23:21:31 +0000 Subject: [PATCH] Github Action: Updated dist files --- dist/base_installer.js | 93 ++++++++++++++++++++-------- dist/base_installer.min.js | 4 +- dist/cpinstaller.js | 124 +++++++++++++++++++------------------ dist/cpinstaller.min.js | 6 +- 4 files changed, 135 insertions(+), 92 deletions(-) diff --git a/dist/base_installer.js b/dist/base_installer.js index a2f21d9..bbaf823 100644 --- a/dist/base_installer.js +++ b/dist/base_installer.js @@ -5,8 +5,7 @@ 'use strict'; import {html, render} from 'https://cdn.jsdelivr.net/npm/lit-html/+esm'; import {asyncAppend} from 'https://cdn.jsdelivr.net/npm/lit-html/directives/async-append/+esm'; -import * as esptoolPackage from "https://cdn.jsdelivr.net/npm/esp-web-flasher@5.1.2/dist/web/index.js/+esm" - +import { ESPLoader, Transport } from "https://unpkg.com/esptool-js@0.5.3/bundle.js"; export const ESP_ROM_BAUD = 115200; export class InstallButton extends HTMLButtonElement { @@ -15,12 +14,15 @@ export class InstallButton extends HTMLButtonElement { constructor() { super(); + this.baudRate = ESP_ROM_BAUD; this.dialogElements = {}; this.currentFlow = null; this.currentStep = 0; this.currentDialogElement = null; - this.port = null; - this.espStub = null; + this.device = null; + this.transport = null; + this.esploader = null; + this.chip = null; this.dialogCssClass = "install-dialog"; this.connected = this.connectionStates.DISCONNECTED; this.menuTitle = "Installer Menu"; @@ -343,13 +345,17 @@ export class InstallButton extends HTMLButtonElement { return macAddr.map((value) => value.toString(16).toUpperCase().padStart(2, "0")).join(":"); } - async disconnect() { - if (this.espStub) { - await espStub.disconnect(); - await espStub.port.close(); - this.updateUIConnected(this.connectionStates.DISCONNECTED); - this.espStub = null; + async espDisconnect() { + if (this.transport) { + await this.transport.disconnect(); + await this.transport.waitForUnlock(1500); + this.updateEspConnected(this.connectionStates.DISCONNECTED); + this.transport = null; + this.device = null; + this.chip = null; + return true; } + return false; } async runFlow(flow) { @@ -390,6 +396,17 @@ export class InstallButton extends HTMLButtonElement { } } + async advanceSteps(stepCount) { + if (!this.currentFlow) { + return; + } + + if (this.currentStep <= this.currentFlow.steps.length + stepCount) { + this.currentStep += stepCount; + await this.currentFlow.steps[this.currentStep].bind(this)(); + } + } + async showMenu() { // Display Menu this.showDialog(this.dialogs.menu); @@ -405,38 +422,60 @@ export class InstallButton extends HTMLButtonElement { this.showDialog(this.dialogs.error, {message: message}); } - async setBaudRateIfChipSupports(chipType, baud) { - if (baud == ESP_ROM_BAUD) { return } // already the default - - if (chipType == esptoolPackage.CHIP_FAMILY_ESP32) { // only supports the default - this.logMsg(`ESP32 Chip only works at 115200 instead of the preferred ${baud}. Staying at 115200...`); - return - } + async setBaudRateIfChipSupports(baud) { + if (baud == this.baudRate) { return } // already the current setting await this.changeBaudRate(baud); } async changeBaudRate(baud) { - if (this.espStub && this.baudRates.includes(baud)) { - await this.espStub.setBaudrate(baud); + if (this.baudRates.includes(baud)) { + if (this.transport == null) { + this.baudRate = baud; + } else { + this.errorMsg("Cannot change baud rate while connected."); + } } } - async espHardReset(bootloader = false) { - if (this.espStub) { - await this.espStub.hardReset(bootloader); + async espHardReset() { + if (this.esploader) { + await this.esploader.hardReset(); } } async espConnect(logger) { - // - Request a port and open a connection. - this.port = await navigator.serial.requestPort(); - logger.log("Connecting..."); - await this.port.open({ baudRate: ESP_ROM_BAUD }); + + if (this.device === null) { + this.device = await navigator.serial.requestPort({}); + this.transport = new Transport(this.device, true); + } + + const espLoaderTerminal = { + clean() { + // Clear the terminal + }, + writeLine(data) { + logger.log(data); + }, + write(data) { + logger.log(data); + }, + }; + + const loaderOptions = { + transport: this.transport, + baudrate: this.baudRate, + terminal: espLoaderTerminal, + debugLogging: false, + }; + + this.esploader = new ESPLoader(loaderOptions); + this.chip = await this.esploader.main(); logger.log("Connected successfully."); - return new esptoolPackage.ESPLoader(this.port, logger); + return this.esploader; }; } \ No newline at end of file diff --git a/dist/base_installer.min.js b/dist/base_installer.min.js index 935713c..3b65721 100644 --- a/dist/base_installer.min.js +++ b/dist/base_installer.min.js @@ -1,4 +1,4 @@ -"use strict";import{html,render}from"https://cdn.jsdelivr.net/npm/lit-html/+esm";import{asyncAppend}from"https://cdn.jsdelivr.net/npm/lit-html/directives/async-append/+esm";import*as esptoolPackage from"https://cdn.jsdelivr.net/npm/esp-web-flasher@5.1.2/dist/web/index.js/+esm";const ESP_ROM_BAUD=115200;class InstallButton extends HTMLButtonElement{static isSupported="serial"in navigator;static isAllowed=window.isSecureContext;constructor(){super(),this.dialogElements={},this.currentFlow=null,this.currentStep=0,this.currentDialogElement=null,this.port=null,this.espStub=null,this.dialogCssClass="install-dialog",this.connected=this.connectionStates.DISCONNECTED,this.menuTitle="Installer Menu"}init(){this.preloadDialogs()}previousButton={label:"Previous",onClick:this.prevStep,isEnabled:async()=>0this.currentStep{this.closeDialog()}};defaultButtons=[this.previousButton,this.nextButton];connectionStates={DISCONNECTED:"Connect",CONNECTING:"Connecting...",CONNECTED:"Disconnect"};dialogs={notSupported:{preload:!1,closeable:!0,template:t=>html` +import{html,render}from"https://cdn.jsdelivr.net/npm/lit-html/+esm";import{asyncAppend}from"https://cdn.jsdelivr.net/npm/lit-html/directives/async-append/+esm";import{ESPLoader,Transport}from"https://unpkg.com/esptool-js@0.5.3/bundle.js";let ESP_ROM_BAUD=115200;class InstallButton extends HTMLButtonElement{static isSupported="serial"in navigator;static isAllowed=window.isSecureContext;constructor(){super(),this.baudRate=ESP_ROM_BAUD,this.dialogElements={},this.currentFlow=null,this.currentStep=0,this.currentDialogElement=null,this.device=null,this.transport=null,this.esploader=null,this.chip=null,this.dialogCssClass="install-dialog",this.connected=this.connectionStates.DISCONNECTED,this.menuTitle="Installer Menu"}init(){this.preloadDialogs()}previousButton={label:"Previous",onClick:this.prevStep,isEnabled:async()=>0this.currentStep{this.closeDialog()}};defaultButtons=[this.previousButton,this.nextButton];connectionStates={DISCONNECTED:"Connect",CONNECTING:"Connecting...",CONNECTED:"Disconnect"};dialogs={notSupported:{preload:!1,closeable:!0,template:t=>html` Sorry, Web Serial is not supported on your browser at this time. Browsers we expect to work:
  • Google Chrome 89 (and higher)
  • @@ -9,4 +9,4 @@

    ${this.menuTitle}

    `,buttons:[this.closeButton]}};flows={};baudRates=[115200,128e3,153600,230400,460800,921600,15e5,2e6];connectedCallback(){InstallButton.isSupported&&InstallButton.isAllowed?this.toggleAttribute("install-supported",!0):this.toggleAttribute("install-unsupported",!0),this.addEventListener("click",async t=>{t.preventDefault(),InstallButton.isSupported?await this.buttonClickHandler(t):await this.showNotSupported()})}async buttonClickHandler(t){await this.showMenu()}getUrlParams(){var e={};return location.hash&&location.hash.substr(1).split("&").forEach(function(t){e[t.split("=")[0]]=t.split("=")[1]}),e}getUrlParam(t){var e=this.getUrlParams();let s=null;return s=t in e?e[t]:s}async enabledFlowCount(){let t=0;for(var[e,s]of Object.entries(this.flows))await s.isEnabled()&&t++;return t}async*generateMenu(t){0==await this.enabledFlowCount()&&(yield html`
  • No installable options available for this board.
  • `);for(var[e,s]of Object.entries(this.flows))await s.isEnabled()&&(yield t(e,s))}preloadDialogs(){for(var[t,e]of Object.entries(this.dialogs))"preload"in e&&!e.preload||(this.dialogElements[t]=this.getDialogElement(e))}createIdFromLabel(t){return t.replace(/^[^a-z]+|[^\w:.-]+/gi,"")}createDialogElement(t,e){var s=this.querySelector("#cp-installer-"+t);s&&this.remove(s);let n=document.createElement("dialog");n.id=t,n.classList.add(this.dialogCssClass);s=document.createElement("button"),s.href="#",s.classList.add("close-button"),s.addEventListener("click",t=>{t.preventDefault(),n.close()}),n.appendChild(s),t=document.createElement("div");t.classList.add("dialog-body"),n.appendChild(t);let i=this.defaultButtons;return e&&e.buttons&&(i=e.buttons),n.appendChild(this.createNavigation(i)),document.body.appendChild(n),n}createNavigation(t){var e=document.createElement("div");e.classList.add("dialog-navigation");for(const n of t){var s=document.createElement("button");s.innerText=n.label,s.id=this.createIdFromLabel(n.label),s.addEventListener("click",async t=>{t.preventDefault(),await n.onClick.bind(this)()}),s.addEventListener("update",async t=>{"onUpdate"in n&&await n.onUpdate.bind(this)(t),"isEnabled"in n&&(t.target.disabled=!await n.isEnabled.bind(this)())}),e.appendChild(s)}return e}getDialogElement(t,e=!1){s=this.dialogs,n=t;var s,n,i=Object.keys(s).find(t=>s[t]===n);return i?i in this.dialogElements&&!e?this.dialogElements[i]:this.createDialogElement(i,t):null}updateButtons(){if(this.currentDialogElement)for(const t of this.currentDialogElement.querySelectorAll(".dialog-navigation button"))t.dispatchEvent(new Event("update"))}showDialog(t,e={}){var s;this.currentDialogElement&&this.closeDialog(),this.currentDialogElement=this.getDialogElement(t),this.currentDialogElement||console.error("Dialog not found"),this.currentDialogElement&&(s=this.currentDialogElement.querySelector(".dialog-body"),"template"in t&&render(t.template(e),s),"closeable"in t&&t.closeable?this.currentDialogElement.querySelector(".close-button").style.display="block":this.currentDialogElement.querySelector(".close-button").style.display="none",this.defaultButtons,"buttons"in t&&t.buttons,this.updateButtons(),this.currentDialogElement.showModal())}closeDialog(){this.currentDialogElement.close(),this.currentDialogElement=null}errorMsg(t){t=this.stripHtml(t),console.error(t),this.showError(t)}logMsg(t,e=!1){console.info(this.stripHtml(t)),e&&console.trace()}updateEspConnected(t){Object.values(this.connectionStates).includes(t)&&(this.connected=t,this.updateButtons())}stripHtml(t){var e=document.createElement("div");return e.innerHTML=t,e.textContent||e.innerText||""}formatMacAddr(t){return t.map(t=>t.toString(16).toUpperCase().padStart(2,"0")).join(":")}async disconnect(){this.espStub&&(await espStub.disconnect(),await espStub.port.close(),this.updateUIConnected(this.connectionStates.DISCONNECTED),this.espStub=null)}async runFlow(t){if(t instanceof Event){if(t.preventDefault(),t.stopImmediatePropagation(),!(t.target.id in this.flows))return;t=this.flows[t.target.id]}this.currentFlow=t,this.currentStep=0,await this.currentFlow.steps[this.currentStep].bind(this)()}async nextStep(){this.currentFlow&&this.currentStep`,buttons:[this.closeButton]}};flows={};baudRates=[115200,128e3,153600,230400,460800,921600,15e5,2e6];connectedCallback(){InstallButton.isSupported&&InstallButton.isAllowed?this.toggleAttribute("install-supported",!0):this.toggleAttribute("install-unsupported",!0),this.addEventListener("click",async t=>{t.preventDefault(),InstallButton.isSupported?await this.buttonClickHandler(t):await this.showNotSupported()})}async buttonClickHandler(t){await this.showMenu()}getUrlParams(){var e={};return location.hash&&location.hash.substr(1).split("&").forEach(function(t){e[t.split("=")[0]]=t.split("=")[1]}),e}getUrlParam(t){var e=this.getUrlParams();let s=null;return s=t in e?e[t]:s}async enabledFlowCount(){let t=0;for(var[e,s]of Object.entries(this.flows))await s.isEnabled()&&t++;return t}async*generateMenu(t){0==await this.enabledFlowCount()&&(yield html`
  • No installable options available for this board.
  • `);for(var[e,s]of Object.entries(this.flows))await s.isEnabled()&&(yield t(e,s))}preloadDialogs(){for(var[t,e]of Object.entries(this.dialogs))"preload"in e&&!e.preload||(this.dialogElements[t]=this.getDialogElement(e))}createIdFromLabel(t){return t.replace(/^[^a-z]+|[^\w:.-]+/gi,"")}createDialogElement(t,e){var s=this.querySelector("#cp-installer-"+t);s&&this.remove(s);let i=document.createElement("dialog");i.id=t,i.classList.add(this.dialogCssClass);s=document.createElement("button"),s.href="#",s.classList.add("close-button"),s.addEventListener("click",t=>{t.preventDefault(),i.close()}),i.appendChild(s),t=document.createElement("div");t.classList.add("dialog-body"),i.appendChild(t);let n=this.defaultButtons;return e&&e.buttons&&(n=e.buttons),i.appendChild(this.createNavigation(n)),document.body.appendChild(i),i}createNavigation(t){var s=document.createElement("div");s.classList.add("dialog-navigation");for(let e of t){var i=document.createElement("button");i.innerText=e.label,i.id=this.createIdFromLabel(e.label),i.addEventListener("click",async t=>{t.preventDefault(),await e.onClick.bind(this)()}),i.addEventListener("update",async t=>{"onUpdate"in e&&await e.onUpdate.bind(this)(t),"isEnabled"in e&&(t.target.disabled=!await e.isEnabled.bind(this)())}),s.appendChild(i)}return s}getDialogElement(t,e=!1){s=this.dialogs,i=t;var s,i,n=Object.keys(s).find(t=>s[t]===i);return n?n in this.dialogElements&&!e?this.dialogElements[n]:this.createDialogElement(n,t):null}updateButtons(){var t;if(this.currentDialogElement)for(t of this.currentDialogElement.querySelectorAll(".dialog-navigation button"))t.dispatchEvent(new Event("update"))}showDialog(t,e={}){var s;this.currentDialogElement&&this.closeDialog(),this.currentDialogElement=this.getDialogElement(t),this.currentDialogElement||console.error("Dialog not found"),this.currentDialogElement&&(s=this.currentDialogElement.querySelector(".dialog-body"),"template"in t&&render(t.template(e),s),"closeable"in t&&t.closeable?this.currentDialogElement.querySelector(".close-button").style.display="block":this.currentDialogElement.querySelector(".close-button").style.display="none",this.defaultButtons,"buttons"in t&&t.buttons,this.updateButtons(),this.currentDialogElement.showModal())}closeDialog(){this.currentDialogElement.close(),this.currentDialogElement=null}errorMsg(t){t=this.stripHtml(t),console.error(t),this.showError(t)}logMsg(t,e=!1){console.info(this.stripHtml(t)),e&&console.trace()}updateEspConnected(t){Object.values(this.connectionStates).includes(t)&&(this.connected=t,this.updateButtons())}stripHtml(t){var e=document.createElement("div");return e.innerHTML=t,e.textContent||e.innerText||""}formatMacAddr(t){return t.map(t=>t.toString(16).toUpperCase().padStart(2,"0")).join(":")}async espDisconnect(){return!(!this.transport||(await this.transport.disconnect(),await this.transport.waitForUnlock(1500),this.updateEspConnected(this.connectionStates.DISCONNECTED),this.transport=null,this.device=null,this.chip=null))}async runFlow(t){if(t instanceof Event){if(t.preventDefault(),t.stopImmediatePropagation(),!(t.target.id in this.flows))return;t=this.flows[t.target.id]}this.currentFlow=t,this.currentStep=0,await this.currentFlow.steps[this.currentStep].bind(this)()}async nextStep(){this.currentFlow&&this.currentStep { return !this.hasNativeUsb() && !!this.binFileUrl }, }, uf2Only: { // Upgrade when Bootloader is already installer - label: `Upgrade/Install CircuitPython [version] UF2 Only`, + label: `Install CircuitPython [version] UF2 Only`, steps: [this.stepWelcome, this.stepSelectBootDrive, this.stepCopyUf2, this.stepSelectCpyDrive, this.stepCredentials, this.stepSuccess], isEnabled: async () => { return this.hasNativeUsb() && !!this.uf2FileUrl }, }, binOnly: { - label: `Upgrade CircuitPython [version] Bin Only`, + label: `Install CircuitPython [version] Bin Only`, steps: [this.stepWelcome, this.stepSerialConnect, this.stepConfirm, this.stepEraseAll, this.stepFlashBin, this.stepSuccess], isEnabled: async () => { return !!this.binFileUrl }, }, @@ -311,6 +307,10 @@ export class CPInstallButton extends InstallButton { `, buttons: [ this.previousButton, + { + label: "Skip Erase", + onClick: async (e) => { if (confirm("Skipping the erase step may cause issues and is not recommended. Continue?")) { await this.advanceSteps(2); }}, + }, { label: "Continue", onClick: this.nextStep, @@ -481,7 +481,7 @@ export class CPInstallButton extends InstallButton { action: "Erasing Flash", }); try { - await this.espStub.eraseFlash(); + await this.esploader.eraseFlash(); } catch (err) { this.errorMsg("Unable to finish erasing Flash memory. Please try again."); } @@ -550,8 +550,8 @@ export class CPInstallButton extends InstallButton { async stepSetupRepl() { // TODO: Try and reuse the existing connection so user doesn't need to select it again - /*if (this.port) { - this.replSerialDevice = this.port; + /*if (this.device) { + this.replSerialDevice = this.device; await this.setupRepl(); }*/ const serialPortName = await this.getSerialPortName(); @@ -579,6 +579,7 @@ export class CPInstallButton extends InstallButton { // TODO: Currently the control is just disabled and not used because we don't have anything to modify boot.py in place. // Setting mass_storage_disabled to true/false will display the checkbox with the appropriately checked state. //parameters.mass_storage_disabled = true; + // This can be updated to use FileOps for ease of implementation } // Display Credentials Request Dialog @@ -658,45 +659,34 @@ export class CPInstallButton extends InstallButton { async espToolConnectHandler(e) { await this.onReplDisconnected(e); await this.espDisconnect(); - let esploader; + await this.setBaudRateIfChipSupports(PREFERRED_BAUDRATE); try { - esploader = await this.espConnect({ + this.updateEspConnected(this.connectionStates.CONNECTING); + await this.espConnect({ log: (...args) => this.logMsg(...args), debug: (...args) => {}, error: (...args) => this.errorMsg(...args), }); + this.updateEspConnected(this.connectionStates.CONNECTED); } catch (err) { // It's possible the dialog was also canceled here + this.updateEspConnected(this.connectionStates.DISCONNECTED); this.errorMsg("Unable to open Serial connection to board. Make sure the port is not already in use by another application or in another browser tab."); return; } try { - this.updateEspConnected(this.connectionStates.CONNECTING); - await esploader.initialize(); - this.updateEspConnected(this.connectionStates.CONNECTED); - } catch (err) { - await esploader.disconnect(); - // Disconnection before complete - this.updateEspConnected(this.connectionStates.DISCONNECTED); - this.errorMsg("Unable to connect to the board. Make sure it is in bootloader mode by holding the boot0 button when powering on and try again.") - return; - } - - try { - this.logMsg(`Connected to ${esploader.chipName}`); - this.logMsg(`MAC Address: ${this.formatMacAddr(esploader.macAddr())}`); + this.logMsg(`Connected to ${this.esploader.chip.CHIP_NAME}`); // check chip compatibility - if (FAMILY_TO_CHIP_MAP[this.chipFamily] == esploader.chipFamily) { + if (this.chipFamily == `${this.esploader.chip.CHIP_NAME}`.toLowerCase().replaceAll("-", "")) { this.logMsg("This chip checks out"); - this.espStub = await esploader.runStub(); - this.espStub.addEventListener("disconnect", () => { - this.updateEspConnected(this.connectionStates.DISCONNECTED); - this.espStub = null; - }); - await this.setBaudRateIfChipSupports(esploader.chipFamily, PREFERRED_BAUDRATE); + // esploader-js doesn't have a disconnect event, so we can't use this + //this.esploader.addEventListener("disconnect", () => { + // this.updateEspConnected(this.connectionStates.DISCONNECTED); + //}); + await this.nextStep(); return; } @@ -706,7 +696,9 @@ export class CPInstallButton extends InstallButton { await this.espDisconnect(); } catch (err) { - await esploader.disconnect(); + if (this.transport) { + await this.transport.disconnect(); + } // Disconnection before complete this.updateEspConnected(this.connectionStates.DISCONNECTED); this.errorMsg("Oops, we lost connection to your board before completing the install. Please check your USB connection and click Connect again. Refresh the browser if it becomes unresponsive.") @@ -1024,10 +1016,28 @@ export class CPInstallButton extends InstallButton { async downloadAndInstall(url, fileToExtract = null, cacheFile = false) { let [filename, fileBlob] = await this.downloadAndExtract(url, fileToExtract, cacheFile); + const fileArray = []; + + const readBlobAsBinaryString = (inputFile) => { + const reader = new FileReader(); + + return new Promise((resolve, reject) => { + reader.onerror = () => { + reader.abort(); + reject(new DOMException("Problem parsing input file.")); + }; + + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsBinaryString(inputFile); + }); + }; // Update the Progress dialog if (fileBlob) { - const fileContents = (new Uint8Array(await fileBlob.arrayBuffer())).buffer; + fileArray.push({ data: await readBlobAsBinaryString(fileBlob), address: 0 }); + let lastPercent = 0; this.showDialog(this.dialogs.actionProgress, { action: `Flashing ${filename}` @@ -1037,14 +1047,22 @@ export class CPInstallButton extends InstallButton { progressElement.value = 0; try { - await this.espStub.flashData(fileContents, (bytesWritten, totalBytes) => { - let percentage = Math.round((bytesWritten / totalBytes) * 100); - if (percentage > lastPercent) { - progressElement.value = percentage; - this.logMsg(`${percentage}% (${bytesWritten}/${totalBytes})...`); - lastPercent = percentage; - } - }, 0, 0); + const flashOptions = { + fileArray: fileArray, + flashSize: "keep", + eraseAll: false, + compress: true, + reportProgress: (fileIndex, written, total) => { + let percentage = Math.round((written / total) * 100); + if (percentage > lastPercent) { + progressElement.value = percentage; + this.logMsg(`${percentage}% (${written}/${total})...`); + lastPercent = percentage; + } + }, + calculateMD5Hash: (image) => CryptoJS.MD5(CryptoJS.enc.Latin1.parse(image)), + }; + await this.esploader.writeFlash(flashOptions); } catch (err) { this.errorMsg(`Unable to flash file: ${filename}. Error Message: ${err}`); } @@ -1256,20 +1274,6 @@ export class CPInstallButton extends InstallButton { return {}; } - async espDisconnect() { - // Disconnect the ESPTool - if (this.espStub) { - await this.espStub.disconnect(); - this.espStub.removeEventListener("disconnect", this.espDisconnect.bind(this)); - this.updateEspConnected(this.connectionStates.DISCONNECTED); - this.espStub = null; - } - if (this.port) { - await this.port.close(); - this.port = null; - } - } - async serialTransmit(msg) { const encoder = new TextEncoder(); if (this.writer) { diff --git a/dist/cpinstaller.min.js b/dist/cpinstaller.min.js index d76a95f..153b120 100644 --- a/dist/cpinstaller.min.js +++ b/dist/cpinstaller.min.js @@ -1,4 +1,4 @@ -"use strict";import{html}from"https://cdn.jsdelivr.net/npm/lit-html/+esm";import{map}from"https://cdn.jsdelivr.net/npm/lit-html/directives/map/+esm";import*as toml from"https://cdn.jsdelivr.net/npm/iarna-toml-esm@3.0.5/+esm";import*as zip from"https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.6.65/+esm";import*as esptoolPackage from"https://cdn.jsdelivr.net/npm/esp-web-flasher@5.1.2/dist/web/index.js/+esm";import{REPL}from"https://cdn.jsdelivr.net/gh/adafruit/circuitpython-repl-js/repl.js";import{InstallButton,ESP_ROM_BAUD}from"./base_installer.min.js";const PREFERRED_BAUDRATE=921600,COPY_CHUNK_SIZE=65536,DEFAULT_RELEASE_LATEST=!1,BOARD_DEFS="https://adafruit-circuit-python.s3.amazonaws.com/esp32_boards.json",CSS_DIALOG_CLASS="cp-installer-dialog",FAMILY_TO_CHIP_MAP={esp32s2:esptoolPackage.CHIP_FAMILY_ESP32S2,esp32s3:esptoolPackage.CHIP_FAMILY_ESP32S3,esp32c3:esptoolPackage.CHIP_FAMILY_ESP32C3,esp32:esptoolPackage.CHIP_FAMILY_ESP32},attrMap={bootloader:"bootloaderUrl",uf2file:"uf2FileUrl",binfile:"binFileUrl"};class CPInstallButton extends InstallButton{constructor(){super(),this.releaseVersion="[version]",this.boardName="ESP32-based device",this.boardIds=null,this.selectedBoardId=null,this.bootloaderUrl=null,this.boardDefs=null,this.uf2FileUrl=null,this.binFileUrl=null,this.releaseVersion=0,this.chipFamily=null,this.dialogCssClass=CSS_DIALOG_CLASS,this.dialogs={...this.dialogs,...this.cpDialogs},this.bootDriveHandle=null,this.circuitpyDriveHandle=null,this._bootDriveName=null,this._serialPortName=null,this.replSerialDevice=null,this.repl=null,this.fileCache=[],this.reader=null,this.writer=null,this.tomlSettings=null,this.init()}static get observedAttributes(){return Object.keys(attrMap)}parseVersion(e){var t={},e=e.match(/(\d+)\.(\d+)\.(\d+)(?:-([a-z]+)\.(\d+))?/);return e&&4<=e.length&&(t.major=e[1],t.minor=e[2],t.patch=e[3],e[4]&&e[5]?(t.suffix=e[4],t.suffixVersion=e[5]):(t.suffix="stable",t.suffixVersion=0)),t}sortReleases(e){const r=["major","minor","patch","suffix","suffixVersion"];return e.sort((e,t)=>{var i,s=this.parseVersion(e.version),a=this.parseVersion(t.version);for(i of r){if(s[i]a[i])return 1}return 0}),e}async connectedCallback(){var e=await fetch(BOARD_DEFS),e=(this.boardDefs=await e.json(),this.getAttribute("boardid"));e&&0!==e.trim().length?this.boardIds=e.split(","):this.boardIds=Object.keys(this.boardDefs),1===this.boardIds.length&&(this.selectedBoardId=this.boardIds[0]),this.getAttribute("version")&&(this.releaseVersion=this.getAttribute("version")),super.connectedCallback()}async loadBoard(e){let t=null;if(Object.keys(this.boardDefs).includes(e)){e=this.boardDefs[e],e=(this.chipFamily=e.chipfamily,e.name&&(this.boardName=e.name),e.bootloader&&(this.bootloaderUrl=this.updateBinaryUrl(e.bootloader)),this.sortReleases(e.releases));if(this.releaseVersion)for(var i of e)if(i.version==this.releaseVersion){t=i;break}t||(t=DEFAULT_RELEASE_LATEST?e[e.length-1]:e[0],this.releaseVersion=t.version),t.uf2file&&(this.uf2FileUrl=this.updateBinaryUrl(t.uf2file)),t.binfile&&(this.binFileUrl=this.updateBinaryUrl(t.binfile))}this.getAttribute("chipfamily")&&(this.chipFamily=this.getAttribute("chipfamily")),this.getAttribute("boardname")&&(this.boardName=this.getAttribute("boardname")),this.menuTitle="CircuitPython Installer for "+this.boardName}attributeChangedCallback(e,t,i){this[attrMap[e]]=i?this.updateBinaryUrl(i):null}updateBinaryUrl(e){return e=e&&e.replace("https://downloads.circuitpython.org/","https://adafruit-circuit-python.s3.amazonaws.com/")}flows={uf2FullProgram:{label:"Full CircuitPython [version] Install",steps:[this.stepWelcome,this.stepSerialConnect,this.stepConfirm,this.stepEraseAll,this.stepBootloader,this.stepSelectBootDrive,this.stepCopyUf2,this.stepSelectCpyDrive,this.stepCredentials,this.stepSuccess],isEnabled:async()=>this.hasNativeUsb()&&!!this.bootloaderUrl&&!!this.uf2FileUrl},binFullProgram:{label:"Full CircuitPython [version] Install",steps:[this.stepWelcome,this.stepSerialConnect,this.stepConfirm,this.stepEraseAll,this.stepFlashBin,this.stepSetupRepl,this.stepCredentials,this.stepSuccess],isEnabled:async()=>!this.hasNativeUsb()&&!!this.binFileUrl},uf2Only:{label:"Upgrade/Install CircuitPython [version] UF2 Only",steps:[this.stepWelcome,this.stepSelectBootDrive,this.stepCopyUf2,this.stepSelectCpyDrive,this.stepCredentials,this.stepSuccess],isEnabled:async()=>this.hasNativeUsb()&&!!this.uf2FileUrl},binOnly:{label:"Upgrade CircuitPython [version] Bin Only",steps:[this.stepWelcome,this.stepSerialConnect,this.stepConfirm,this.stepEraseAll,this.stepFlashBin,this.stepSuccess],isEnabled:async()=>!!this.binFileUrl},bootloaderOnly:{label:"Install Bootloader Only",steps:[this.stepWelcome,this.stepSerialConnect,this.stepConfirm,this.stepEraseAll,this.stepBootloader,this.stepSuccess],isEnabled:async()=>this.hasNativeUsb()&&!!this.bootloaderUrl},credentialsOnlyRepl:{label:"Update WiFi credentials",steps:[this.stepWelcome,this.stepSetupRepl,this.stepCredentials,this.stepSuccess],isEnabled:async()=>!this.hasNativeUsb()},credentialsOnlyDrive:{label:"Update WiFi credentials",steps:[this.stepWelcome,this.stepSelectCpyDrive,this.stepCredentials,this.stepSuccess],isEnabled:async()=>this.hasNativeUsb()}};cpDialogs={boardSelect:{closeable:!0,template:i=>html` +import{html}from"https://cdn.jsdelivr.net/npm/lit-html/+esm";import{map}from"https://cdn.jsdelivr.net/npm/lit-html/directives/map/+esm";import*as toml from"https://cdn.jsdelivr.net/npm/iarna-toml-esm@3.0.5/+esm";import*as zip from"https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.6.65/+esm";import{default as CryptoJS}from"https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/+esm";import{REPL}from"https://cdn.jsdelivr.net/gh/adafruit/circuitpython-repl-js@3.2.1/repl.js";import{InstallButton,ESP_ROM_BAUD}from"./base_installer.min.js";let PREFERRED_BAUDRATE=921600,COPY_CHUNK_SIZE=65536,DEFAULT_RELEASE_LATEST=!1,BOARD_DEFS="https://adafruit-circuit-python.s3.amazonaws.com/esp32_boards.json",CSS_DIALOG_CLASS="cp-installer-dialog",attrMap={bootloader:"bootloaderUrl",uf2file:"uf2FileUrl",binfile:"binFileUrl"};class CPInstallButton extends InstallButton{constructor(){super(),this.releaseVersion="[version]",this.boardName="ESP32-based device",this.boardIds=null,this.selectedBoardId=null,this.bootloaderUrl=null,this.boardDefs=null,this.uf2FileUrl=null,this.binFileUrl=null,this.releaseVersion=0,this.chipFamily=null,this.dialogCssClass=CSS_DIALOG_CLASS,this.dialogs={...this.dialogs,...this.cpDialogs},this.bootDriveHandle=null,this.circuitpyDriveHandle=null,this._bootDriveName=null,this._serialPortName=null,this.replSerialDevice=null,this.repl=null,this.fileCache=[],this.reader=null,this.writer=null,this.tomlSettings=null,this.init()}static get observedAttributes(){return Object.keys(attrMap)}parseVersion(e){var t={},e=e.match(/(\d+)\.(\d+)\.(\d+)(?:-([a-z]+)\.(\d+))?/);return e&&4<=e.length&&(t.major=e[1],t.minor=e[2],t.patch=e[3],e[4]&&e[5]?(t.suffix=e[4],t.suffixVersion=e[5]):(t.suffix="stable",t.suffixVersion=0)),t}sortReleases(e){let r=["major","minor","patch","suffix","suffixVersion"];return e.sort((e,t)=>{var i,s=this.parseVersion(e.version),a=this.parseVersion(t.version);for(i of r){if(s[i]a[i])return 1}return 0}),e}async connectedCallback(){var e=await fetch(BOARD_DEFS),e=(this.boardDefs=await e.json(),this.getAttribute("boardid"));e&&0!==e.trim().length?this.boardIds=e.split(","):this.boardIds=Object.keys(this.boardDefs),1===this.boardIds.length&&(this.selectedBoardId=this.boardIds[0]),this.getAttribute("version")&&(this.releaseVersion=this.getAttribute("version")),super.connectedCallback()}async loadBoard(e){let t=null;if(Object.keys(this.boardDefs).includes(e)){e=this.boardDefs[e],e=(this.chipFamily=e.chipfamily,e.name&&(this.boardName=e.name),e.bootloader&&(this.bootloaderUrl=this.updateBinaryUrl(e.bootloader)),this.sortReleases(e.releases));if(this.releaseVersion)for(var i of e)if(i.version==this.releaseVersion){t=i;break}t||(t=DEFAULT_RELEASE_LATEST?e[e.length-1]:e[0],this.releaseVersion=t.version),t.uf2file&&(this.uf2FileUrl=this.updateBinaryUrl(t.uf2file)),t.binfile&&(this.binFileUrl=this.updateBinaryUrl(t.binfile))}this.getAttribute("chipfamily")&&(this.chipFamily=this.getAttribute("chipfamily")),this.getAttribute("boardname")&&(this.boardName=this.getAttribute("boardname")),this.menuTitle="CircuitPython Installer for "+this.boardName}attributeChangedCallback(e,t,i){this[attrMap[e]]=i?this.updateBinaryUrl(i):null}updateBinaryUrl(e){return e=e&&e.replace("https://downloads.circuitpython.org/","https://adafruit-circuit-python.s3.amazonaws.com/")}flows={uf2FullProgram:{label:"Full CircuitPython [version] Install",steps:[this.stepWelcome,this.stepSerialConnect,this.stepConfirm,this.stepEraseAll,this.stepBootloader,this.stepSelectBootDrive,this.stepCopyUf2,this.stepSelectCpyDrive,this.stepCredentials,this.stepSuccess],isEnabled:async()=>this.hasNativeUsb()&&!!this.bootloaderUrl&&!!this.uf2FileUrl},binFullProgram:{label:"Full CircuitPython [version] Install",steps:[this.stepWelcome,this.stepSerialConnect,this.stepConfirm,this.stepEraseAll,this.stepFlashBin,this.stepSetupRepl,this.stepCredentials,this.stepSuccess],isEnabled:async()=>!this.hasNativeUsb()&&!!this.binFileUrl},uf2Only:{label:"Install CircuitPython [version] UF2 Only",steps:[this.stepWelcome,this.stepSelectBootDrive,this.stepCopyUf2,this.stepSelectCpyDrive,this.stepCredentials,this.stepSuccess],isEnabled:async()=>this.hasNativeUsb()&&!!this.uf2FileUrl},binOnly:{label:"Install CircuitPython [version] Bin Only",steps:[this.stepWelcome,this.stepSerialConnect,this.stepConfirm,this.stepEraseAll,this.stepFlashBin,this.stepSuccess],isEnabled:async()=>!!this.binFileUrl},bootloaderOnly:{label:"Install Bootloader Only",steps:[this.stepWelcome,this.stepSerialConnect,this.stepConfirm,this.stepEraseAll,this.stepBootloader,this.stepSuccess],isEnabled:async()=>this.hasNativeUsb()&&!!this.bootloaderUrl},credentialsOnlyRepl:{label:"Update WiFi credentials",steps:[this.stepWelcome,this.stepSetupRepl,this.stepCredentials,this.stepSuccess],isEnabled:async()=>!this.hasNativeUsb()},credentialsOnlyDrive:{label:"Update WiFi credentials",steps:[this.stepWelcome,this.stepSelectCpyDrive,this.stepCredentials,this.stepSuccess],isEnabled:async()=>this.hasNativeUsb()}};cpDialogs={boardSelect:{closeable:!0,template:i=>html`

    There are multiple boards are available. Select the board you have:

    @@ -40,7 +40,7 @@

    `,buttons:[this.previousButton,{label:"Next",onClick:this.nextStep,isEnabled:async()=>this.currentStep{this.currentDialogElement.querySelector("#butConnect").innerText=this.connected}}]},confirm:{template:e=>html`

    This will overwrite everything on the ${e.boardName}.

    - `,buttons:[this.previousButton,{label:"Continue",onClick:this.nextStep}]},bootDriveSelect:{closeable:!0,template:e=>html` + `,buttons:[this.previousButton,{label:"Skip Erase",onClick:async e=>{confirm("Skipping the erase step may cause issues and is not recommended. Continue?")&&await this.advanceSteps(2)}},{label:"Continue",onClick:this.nextStep}]},bootDriveSelect:{closeable:!0,template:e=>html`

    Please select the ${e.drivename} Drive where the UF2 file will be copied.

    @@ -103,4 +103,4 @@

    `:""} `,buttons:[this.closeButton]},error:{closeable:!0,template:e=>html`

    Installation Error: ${e.message}

    - `,buttons:[this.closeButton]}};getBoardName(e){return Object.keys(this.boardDefs).includes(e)?this.boardDefs[e].name:null}getBoardOptions(){var e,t=[];for(e of this.boardIds)t.push({id:e,name:this.getBoardName(e)});return t.sort((e,t)=>{e=e.name.trim().toLowerCase(),t=t.name.trim().toLowerCase();return ethis.logMsg(...e),debug:()=>{},error:(...e)=>this.errorMsg(...e)})}catch(e){return void this.errorMsg("Unable to open Serial connection to board. Make sure the port is not already in use by another application or in another browser tab.")}try{this.updateEspConnected(this.connectionStates.CONNECTING),await t.initialize(),this.updateEspConnected(this.connectionStates.CONNECTED)}catch(e){return await t.disconnect(),this.updateEspConnected(this.connectionStates.DISCONNECTED),void this.errorMsg("Unable to connect to the board. Make sure it is in bootloader mode by holding the boot0 button when powering on and try again.")}try{this.logMsg("Connected to "+t.chipName),this.logMsg("MAC Address: "+this.formatMacAddr(t.macAddr())),FAMILY_TO_CHIP_MAP[this.chipFamily]==t.chipFamily?(this.logMsg("This chip checks out"),this.espStub=await t.runStub(),this.espStub.addEventListener("disconnect",()=>{this.updateEspConnected(this.connectionStates.DISCONNECTED),this.espStub=null}),await this.setBaudRateIfChipSupports(t.chipFamily,PREFERRED_BAUDRATE),await this.nextStep()):(this.errorMsg("Oops, this is the wrong firmware for your board."),await this.espDisconnect())}catch(e){await t.disconnect(),this.updateEspConnected(this.connectionStates.DISCONNECTED),this.errorMsg("Oops, we lost connection to your board before completing the install. Please check your USB connection and click Connect again. Refresh the browser if it becomes unresponsive.")}}async onSerialReceive(e){await this.repl.onSerialReceive(e)}async cpSerialConnectHandler(e){await this.espDisconnect(),await this.onReplDisconnected(e);try{this.replSerialDevice=await navigator.serial.requestPort()}catch(e){return}try{await this.replSerialDevice.open({baudRate:ESP_ROM_BAUD})}catch(e){console.error("Error. Unable to open Serial Port. Make sure it isn't already in use in another tab or application.")}await this.setupRepl(),this.nextStep()}async setupRepl(){this.replSerialDevice&&(this.repl=new REPL,this.repl.serialTransmit=this.serialTransmit.bind(this),this.replSerialDevice.addEventListener("message",this.onSerialReceive.bind(this)),this._readLoopPromise=this._readSerialLoop().catch(async function(e){await this.onReplDisconnected()}.bind(this)),this.replSerialDevice.writable)&&(this.writer=this.replSerialDevice.writable.getWriter(),await this.writer.ready)}async onReplDisconnected(e){if(this.reader){try{await this.reader.cancel()}catch(e){}this.reader=null}if(this.writer&&(await this.writer.releaseLock(),this.writer=null),this.replSerialDevice){try{await this.replSerialDevice.close()}catch(e){}this.replSerialDevice=null}}async buttonClickHandler(e,t=!1){!(1{var i=Math.round(e/t*100);i>s&&(a.value=i,this.logMsg(i+`% (${e}/${t})...`),s=i)},0,0)}catch(e){this.errorMsg(`Unable to flash file: ${t}. Error Message: `+e)}}}async downloadAndCopy(t,i=null){if(i=i||this.bootDriveHandle){var s,a=this.currentDialogElement.querySelector("#stepProgress"),[t,r]=(a.value=0,await this.downloadAndExtract(t)),o=await(await i.getFileHandle(t,{create:!0})).createWritable(),l=r.size;let e=0;for(;e{let e={};for(;0==Object.entries(e).length||null===e.ip;)e=await this.getDeviceHostInfo(),await this.sleep(300)},1e4)}catch(e){return console.warn("Unable to get IP Address. Network Credentials may be incorrect"),null}}else{if(!this.circuitpyDriveHandle)return this.errorMsg("Connect to the CIRCUITPY drive or the REPL first"),null;e=toml.stringify(e);await this.writeFile("settings.toml",e)}}async getCurrentSettings(){let e;if(this.repl)e=await this.runCode(["f = open('settings.toml', 'r')","print(f.read())","f.close()"]);else{if(!this.circuitpyDriveHandle)return this.errorMsg("Connect to the CIRCUITPY drive or the REPL first"),{};e=await this.readFile("settings.toml")}return e?toml.parse(e):(this.logMsg("Unable to read settings.toml from CircuitPython. It may not exist. Continuing..."),{})}async espDisconnect(){this.espStub&&(await this.espStub.disconnect(),this.espStub.removeEventListener("disconnect",this.espDisconnect.bind(this)),this.updateEspConnected(this.connectionStates.DISCONNECTED),this.espStub=null),this.port&&(await this.port.close(),this.port=null)}async serialTransmit(e){var t=new TextEncoder;this.writer&&(t=t.encode(e),await this.writer.ready.catch(e=>{this.errorMsg("Ready error: "+e)}),await this.writer.write(t).catch(e=>{this.errorMsg("Chunk error: "+e)}),await this.writer.ready)}async _readSerialLoop(){if(this.replSerialDevice){var e=new Event("message"),t=new TextDecoder;if(this.replSerialDevice.readable)for(this.reader=this.replSerialDevice.readable.getReader();;){var{value:i,done:s}=await this.reader.read();if(i&&(e.data=t.decode(i),this.replSerialDevice.dispatchEvent(e)),s){this.reader.releaseLock(),await this.onReplDisconnected();break}}this.logMsg("Read Loop Stopped. Closing Serial Port.")}}async getDeviceHostInfo(){return this.repl?{ip:this.repl.getIpAddress(),version:this.repl.getVersion()}:{}}hasNativeUsb(){return!(!this.chipFamily||"esp32c3".includes(this.chipFamily))}sleep(t){return new Promise(e=>setTimeout(e,t))}timeout(e,t){return Promise.race([e(),this.sleep(t).then(()=>{throw Error("Timed Out")})])}}customElements.define("cp-install-button",CPInstallButton,{extends:"button"});export{CPInstallButton}; \ No newline at end of file + `,buttons:[this.closeButton]}};getBoardName(e){return Object.keys(this.boardDefs).includes(e)?this.boardDefs[e].name:null}getBoardOptions(){var e,t=[];for(e of this.boardIds)t.push({id:e,name:this.getBoardName(e)});return t.sort((e,t)=>{e=e.name.trim().toLowerCase(),t=t.name.trim().toLowerCase();return ethis.logMsg(...e),debug:()=>{},error:(...e)=>this.errorMsg(...e)}),this.updateEspConnected(this.connectionStates.CONNECTED)}catch(e){return this.updateEspConnected(this.connectionStates.DISCONNECTED),void this.errorMsg("Unable to open Serial connection to board. Make sure the port is not already in use by another application or in another browser tab.")}try{this.logMsg("Connected to "+this.esploader.chip.CHIP_NAME),this.chipFamily==(""+this.esploader.chip.CHIP_NAME).toLowerCase().replaceAll("-","")?(this.logMsg("This chip checks out"),await this.nextStep()):(this.errorMsg("Oops, this is the wrong firmware for your board."),await this.espDisconnect())}catch(e){this.transport&&await this.transport.disconnect(),this.updateEspConnected(this.connectionStates.DISCONNECTED),this.errorMsg("Oops, we lost connection to your board before completing the install. Please check your USB connection and click Connect again. Refresh the browser if it becomes unresponsive.")}}async onSerialReceive(e){await this.repl.onSerialReceive(e)}async cpSerialConnectHandler(e){await this.espDisconnect(),await this.onReplDisconnected(e);try{this.replSerialDevice=await navigator.serial.requestPort()}catch(e){return}try{await this.replSerialDevice.open({baudRate:ESP_ROM_BAUD})}catch(e){console.error("Error. Unable to open Serial Port. Make sure it isn't already in use in another tab or application.")}await this.setupRepl(),this.nextStep()}async setupRepl(){this.replSerialDevice&&(this.repl=new REPL,this.repl.serialTransmit=this.serialTransmit.bind(this),this.replSerialDevice.addEventListener("message",this.onSerialReceive.bind(this)),this._readLoopPromise=this._readSerialLoop().catch(async function(e){await this.onReplDisconnected()}.bind(this)),this.replSerialDevice.writable)&&(this.writer=this.replSerialDevice.writable.getWriter(),await this.writer.ready)}async onReplDisconnected(e){if(this.reader){try{await this.reader.cancel()}catch(e){}this.reader=null}if(this.writer&&(await this.writer.releaseLock(),this.writer=null),this.replSerialDevice){try{await this.replSerialDevice.close()}catch(e){}this.replSerialDevice=null}}async buttonClickHandler(e,t=!1){!(1{let s=new FileReader;return new Promise((e,t)=>{s.onerror=()=>{s.abort(),t(new DOMException("Problem parsing input file."))},s.onload=()=>{e(s.result)},s.readAsBinaryString(i)})})(e),address:0});let a=0,r=(this.showDialog(this.dialogs.actionProgress,{action:"Flashing "+t}),this.currentDialogElement.querySelector("#stepProgress"));r.value=0;try{var s={fileArray:i,flashSize:"keep",eraseAll:!1,compress:!0,reportProgress:(e,t,i)=>{var s=Math.round(t/i*100);s>a&&(r.value=s,this.logMsg(s+`% (${t}/${i})...`),a=s)},calculateMD5Hash:e=>CryptoJS.MD5(CryptoJS.enc.Latin1.parse(e))};await this.esploader.writeFlash(s)}catch(e){this.errorMsg(`Unable to flash file: ${t}. Error Message: `+e)}}}async downloadAndCopy(t,i=null){if(i=i||this.bootDriveHandle){var s,a=this.currentDialogElement.querySelector("#stepProgress"),[t,r]=(a.value=0,await this.downloadAndExtract(t)),o=await(await i.getFileHandle(t,{create:!0})).createWritable(),l=r.size;let e=0;for(;e{let e={};for(;0==Object.entries(e).length||null===e.ip;)e=await this.getDeviceHostInfo(),await this.sleep(300)},1e4)}catch(e){return console.warn("Unable to get IP Address. Network Credentials may be incorrect"),null}}else{if(!this.circuitpyDriveHandle)return this.errorMsg("Connect to the CIRCUITPY drive or the REPL first"),null;e=toml.stringify(e);await this.writeFile("settings.toml",e)}}async getCurrentSettings(){let e;if(this.repl)e=await this.runCode(["f = open('settings.toml', 'r')","print(f.read())","f.close()"]);else{if(!this.circuitpyDriveHandle)return this.errorMsg("Connect to the CIRCUITPY drive or the REPL first"),{};e=await this.readFile("settings.toml")}return e?toml.parse(e):(this.logMsg("Unable to read settings.toml from CircuitPython. It may not exist. Continuing..."),{})}async serialTransmit(e){var t=new TextEncoder;this.writer&&(t=t.encode(e),await this.writer.ready.catch(e=>{this.errorMsg("Ready error: "+e)}),await this.writer.write(t).catch(e=>{this.errorMsg("Chunk error: "+e)}),await this.writer.ready)}async _readSerialLoop(){if(this.replSerialDevice){var e=new Event("message"),t=new TextDecoder;if(this.replSerialDevice.readable)for(this.reader=this.replSerialDevice.readable.getReader();;){var{value:i,done:s}=await this.reader.read();if(i&&(e.data=t.decode(i),this.replSerialDevice.dispatchEvent(e)),s){this.reader.releaseLock(),await this.onReplDisconnected();break}}this.logMsg("Read Loop Stopped. Closing Serial Port.")}}async getDeviceHostInfo(){return this.repl?{ip:this.repl.getIpAddress(),version:this.repl.getVersion()}:{}}hasNativeUsb(){return!(!this.chipFamily||"esp32c3".includes(this.chipFamily))}sleep(t){return new Promise(e=>setTimeout(e,t))}timeout(e,t){return Promise.race([e(),this.sleep(t).then(()=>{throw Error("Timed Out")})])}}customElements.define("cp-install-button",CPInstallButton,{extends:"button"});export{CPInstallButton}; \ No newline at end of file