123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032 |
- import { LitElement, html, PropertyValues, css, TemplateResult } from "lit";
- import { state } from "lit/decorators.js";
- import "./components/ewt-button";
- import "./components/ewt-checkbox";
- import "./components/ewt-console";
- import "./components/ewt-dialog";
- import "./components/ewt-formfield";
- import "./components/ewt-icon-button";
- import "./components/ewt-textfield";
- import type { EwtTextfield } from "./components/ewt-textfield";
- import "./components/ewt-select";
- import "./components/ewt-list-item";
- import "./pages/ewt-page-progress";
- import "./pages/ewt-page-message";
- import {
- chipIcon,
- closeIcon,
- firmwareIcon,
- refreshIcon,
- } from "./components/svg";
- import { Logger, Manifest, FlashStateType, FlashState } from "./const.js";
- import { ImprovSerial, Ssid } from "improv-wifi-serial-sdk/dist/serial";
- import {
- ImprovSerialCurrentState,
- ImprovSerialErrorState,
- PortNotReady,
- } from "improv-wifi-serial-sdk/dist/const";
- import { flash } from "./flash";
- import { textDownload } from "./util/file-download";
- import { fireEvent } from "./util/fire-event";
- import { sleep } from "./util/sleep";
- import { downloadManifest } from "./util/manifest";
- import { dialogStyles } from "./styles";
- const ERROR_ICON = "⚠️";
- const OK_ICON = "🎉";
- export class EwtInstallDialog extends LitElement {
- public port!: SerialPort;
- public manifestPath!: string;
- public logger: Logger = console;
- public overrides?: {
- checkSameFirmware?: (
- manifest: Manifest,
- deviceImprov: ImprovSerial["info"]
- ) => boolean;
- };
- private _manifest!: Manifest;
- private _info?: ImprovSerial["info"];
- // null = NOT_SUPPORTED
- @state() private _client?: ImprovSerial | null;
- @state() private _state:
- | "ERROR"
- | "DASHBOARD"
- | "PROVISION"
- | "INSTALL"
- | "ASK_ERASE"
- | "LOGS" = "DASHBOARD";
- @state() private _installErase = false;
- @state() private _installConfirmed = false;
- @state() private _installState?: FlashState;
- @state() private _provisionForce = false;
- private _wasProvisioned = false;
- @state() private _error?: string;
- @state() private _busy = false;
- // undefined = not loaded
- // null = not available
- @state() private _ssids?: Ssid[] | null;
- // Name of Ssid. Null = other
- @state() private _selectedSsid: string | null = null;
- protected render() {
- if (!this.port) {
- return html``;
- }
- let heading: string | undefined;
- let content: TemplateResult;
- let hideActions = false;
- let allowClosing = false;
- // During installation phase we temporarily remove the client
- if (
- this._client === undefined &&
- this._state !== "INSTALL" &&
- this._state !== "LOGS"
- ) {
- if (this._error) {
- [heading, content, hideActions] = this._renderError(this._error);
- } else {
- content = this._renderProgress("Connecting");
- hideActions = true;
- }
- } else if (this._state === "INSTALL") {
- [heading, content, hideActions, allowClosing] = this._renderInstall();
- } else if (this._state === "ASK_ERASE") {
- [heading, content] = this._renderAskErase();
- } else if (this._state === "ERROR") {
- [heading, content, hideActions] = this._renderError(this._error!);
- } else if (this._state === "DASHBOARD") {
- [heading, content, hideActions, allowClosing] = this._client
- ? this._renderDashboard()
- : this._renderDashboardNoImprov();
- } else if (this._state === "PROVISION") {
- [heading, content, hideActions] = this._renderProvision();
- } else if (this._state === "LOGS") {
- [heading, content, hideActions] = this._renderLogs();
- }
- return html`
- <ewt-dialog
- open
- .heading=${heading!}
- scrimClickAction
- @closed=${this._handleClose}
- .hideActions=${hideActions}
- >
- ${heading && allowClosing
- ? html`
- <ewt-icon-button dialogAction="close">
- ${closeIcon}
- </ewt-icon-button>
- `
- : ""}
- ${content!}
- </ewt-dialog>
- `;
- }
- _renderProgress(label: string | TemplateResult, progress?: number) {
- return html`
- <ewt-page-progress
- .label=${label}
- .progress=${progress}
- ></ewt-page-progress>
- `;
- }
- _renderError(label: string): [string, TemplateResult, boolean] {
- const heading = "Error";
- const content = html`
- <ewt-page-message .icon=${ERROR_ICON} .label=${label}></ewt-page-message>
- <ewt-button
- slot="primaryAction"
- dialogAction="ok"
- label="Close"
- ></ewt-button>
- `;
- const hideActions = false;
- return [heading, content, hideActions];
- }
- _renderDashboard(): [string, TemplateResult, boolean, boolean] {
- const heading = this._info!.name;
- let content: TemplateResult;
- let hideActions = true;
- let allowClosing = true;
- content = html`
- <div class="table-row">
- ${firmwareIcon}
- <div>${this._info!.firmware} ${this._info!.version}</div>
- </div>
- <div class="table-row last">
- ${chipIcon}
- <div>${this._info!.chipFamily}</div>
- </div>
- <div class="dashboard-buttons">
- ${!this._isSameVersion
- ? html`
- <div>
- <ewt-button
- text-left
- .label=${!this._isSameFirmware
- ? `Install ${this._manifest.name}`
- : `Update ${this._manifest.name}`}
- @click=${() => {
- if (this._isSameFirmware) {
- this._startInstall(false);
- } else if (this._manifest.new_install_prompt_erase) {
- this._state = "ASK_ERASE";
- } else {
- this._startInstall(true);
- }
- }}
- ></ewt-button>
- </div>
- `
- : ""}
- ${this._client!.nextUrl === undefined
- ? ""
- : html`
- <div>
- <a
- href=${this._client!.nextUrl}
- class="has-button"
- target="_blank"
- >
- <ewt-button label="Visit Device"></ewt-button>
- </a>
- </div>
- `}
- ${!this._manifest.home_assistant_domain ||
- this._client!.state !== ImprovSerialCurrentState.PROVISIONED
- ? ""
- : html`
- <div>
- <a
- href=${`https://my.home-assistant.io/redirect/config_flow_start/?domain=${this._manifest.home_assistant_domain}`}
- class="has-button"
- target="_blank"
- >
- <ewt-button label="Add to Home Assistant"></ewt-button>
- </a>
- </div>
- `}
- <div>
- <ewt-button
- .label=${this._client!.state === ImprovSerialCurrentState.READY
- ? "Connect to Wi-Fi"
- : "Change Wi-Fi"}
- @click=${() => {
- this._state = "PROVISION";
- if (
- this._client!.state === ImprovSerialCurrentState.PROVISIONED
- ) {
- this._provisionForce = true;
- }
- }}
- ></ewt-button>
- </div>
- <div>
- <ewt-button
- label="Logs & Console"
- @click=${async () => {
- const client = this._client;
- if (client) {
- await this._closeClientWithoutEvents(client);
- await sleep(100);
- }
- // Also set `null` back to undefined.
- this._client = undefined;
- this._state = "LOGS";
- }}
- ></ewt-button>
- </div>
- ${this._isSameFirmware && this._manifest.funding_url
- ? html`
- <div>
- <a
- class="button"
- href=${this._manifest.funding_url}
- target="_blank"
- >
- <ewt-button label="Fund Development"></ewt-button>
- </a>
- </div>
- `
- : ""}
- ${this._isSameVersion
- ? html`
- <div>
- <ewt-button
- class="danger"
- label="Erase User Data"
- @click=${() => this._startInstall(true)}
- ></ewt-button>
- </div>
- `
- : ""}
- </div>
- `;
- return [heading, content, hideActions, allowClosing];
- }
- _renderDashboardNoImprov(): [string, TemplateResult, boolean, boolean] {
- const heading = "Device Dashboard";
- let content: TemplateResult;
- let hideActions = true;
- let allowClosing = true;
- content = html`
- <div class="dashboard-buttons">
- <div>
- <ewt-button
- text-left
- .label=${`Install ${this._manifest.name}`}
- @click=${() => {
- if (this._manifest.new_install_prompt_erase) {
- this._state = "ASK_ERASE";
- } else {
- // Default is to erase a device that does not support Improv Serial
- this._startInstall(true);
- }
- }}
- ></ewt-button>
- </div>
- <div>
- <ewt-button
- label="Logs & Console"
- @click=${async () => {
- // Also set `null` back to undefined.
- this._client = undefined;
- this._state = "LOGS";
- }}
- ></ewt-button>
- </div>
- </div>
- `;
- return [heading, content, hideActions, allowClosing];
- }
- _renderProvision(): [string | undefined, TemplateResult, boolean] {
- let heading: string | undefined = "Configure Wi-Fi";
- let content: TemplateResult;
- let hideActions = false;
- if (this._busy) {
- return [
- heading,
- this._renderProgress(
- this._ssids === undefined
- ? "Scanning for networks"
- : "Trying to connect"
- ),
- true,
- ];
- }
- if (
- !this._provisionForce &&
- this._client!.state === ImprovSerialCurrentState.PROVISIONED
- ) {
- heading = undefined;
- const showSetupLinks =
- !this._wasProvisioned &&
- (this._client!.nextUrl !== undefined ||
- "home_assistant_domain" in this._manifest);
- hideActions = showSetupLinks;
- content = html`
- <ewt-page-message
- .icon=${OK_ICON}
- label="Device connected to the network!"
- ></ewt-page-message>
- ${showSetupLinks
- ? html`
- <div class="dashboard-buttons">
- ${this._client!.nextUrl === undefined
- ? ""
- : html`
- <div>
- <a
- href=${this._client!.nextUrl}
- class="has-button"
- target="_blank"
- @click=${() => {
- this._state = "DASHBOARD";
- }}
- >
- <ewt-button label="Visit Device"></ewt-button>
- </a>
- </div>
- `}
- ${!this._manifest.home_assistant_domain
- ? ""
- : html`
- <div>
- <a
- href=${`https://my.home-assistant.io/redirect/config_flow_start/?domain=${this._manifest.home_assistant_domain}`}
- class="has-button"
- target="_blank"
- @click=${() => {
- this._state = "DASHBOARD";
- }}
- >
- <ewt-button
- label="Add to Home Assistant"
- ></ewt-button>
- </a>
- </div>
- `}
- <div>
- <ewt-button
- label="Skip"
- @click=${() => {
- this._state = "DASHBOARD";
- }}
- ></ewt-button>
- </div>
- </div>
- `
- : html`
- <ewt-button
- slot="primaryAction"
- label="Continue"
- @click=${() => {
- this._state = "DASHBOARD";
- }}
- ></ewt-button>
- `}
- `;
- } else {
- let error: string | undefined;
- switch (this._client!.error) {
- case ImprovSerialErrorState.UNABLE_TO_CONNECT:
- error = "Unable to connect";
- break;
- case ImprovSerialErrorState.TIMEOUT:
- error = "Timeout";
- break;
- case ImprovSerialErrorState.NO_ERROR:
- // Happens when list SSIDs not supported.
- case ImprovSerialErrorState.UNKNOWN_RPC_COMMAND:
- break;
- default:
- error = `Unknown error (${this._client!.error})`;
- }
- const selectedSsid = this._ssids?.find(
- (info) => info.name === this._selectedSsid
- );
- content = html`
- <div>
- Enter the credentials of the Wi-Fi network that you want your device
- to connect to.
- </div>
- ${error ? html`<p class="error">${error}</p>` : ""}
- ${this._ssids !== null
- ? html`
- <ewt-select
- fixedMenuPosition
- label="Network"
- @selected=${(ev: { detail: { index: number } }) => {
- const index = ev.detail.index;
- // The "Join Other" item is always the last item.
- this._selectedSsid =
- index === this._ssids!.length
- ? null
- : this._ssids![index].name;
- }}
- @closed=${(ev: Event) => ev.stopPropagation()}
- >
- ${this._ssids!.map(
- (info) => html`
- <ewt-list-item
- .selected=${selectedSsid === info}
- .value=${info.name}
- >
- ${info.name}
- </ewt-list-item>
- `
- )}
- <ewt-list-item .selected=${!selectedSsid} value="-1">
- Join other…
- </ewt-list-item>
- </ewt-select>
- <ewt-icon-button @click=${this._updateSsids}>
- ${refreshIcon}
- </ewt-icon-button>
- `
- : ""}
- ${
- // Show input box if command not supported or "Join Other" selected
- !selectedSsid
- ? html`
- <ewt-textfield label="Network Name" name="ssid"></ewt-textfield>
- `
- : ""
- }
- ${!selectedSsid || selectedSsid.secured
- ? html`
- <ewt-textfield
- label="Password"
- name="password"
- type="password"
- ></ewt-textfield>
- `
- : ""}
- <ewt-button
- slot="primaryAction"
- label="Connect"
- @click=${this._doProvision}
- ></ewt-button>
- <ewt-button
- slot="secondaryAction"
- .label=${this._installState && this._installErase ? "Skip" : "Back"}
- @click=${() => {
- this._state = "DASHBOARD";
- }}
- ></ewt-button>
- `;
- }
- return [heading, content, hideActions];
- }
- _renderAskErase(): [string | undefined, TemplateResult] {
- const heading = "Erase device";
- const content = html`
- <div>
- Do you want to erase the device before installing
- ${this._manifest.name}? All data on the device will be lost.
- </div>
- <ewt-formfield label="Erase device" class="danger">
- <ewt-checkbox></ewt-checkbox>
- </ewt-formfield>
- <ewt-button
- slot="primaryAction"
- label="Next"
- @click=${() => {
- const checkbox = this.shadowRoot!.querySelector("ewt-checkbox")!;
- this._startInstall(checkbox.checked);
- }}
- ></ewt-button>
- <ewt-button
- slot="secondaryAction"
- label="Back"
- @click=${() => {
- this._state = "DASHBOARD";
- }}
- ></ewt-button>
- `;
- return [heading, content];
- }
- _renderInstall(): [string | undefined, TemplateResult, boolean, boolean] {
- let heading: string | undefined;
- let content: TemplateResult;
- let hideActions = false;
- const allowClosing = false;
- const isUpdate = !this._installErase && this._isSameFirmware;
- if (!this._installConfirmed && this._isSameVersion) {
- heading = "Erase User Data";
- content = html`
- Do you want to reset your device and erase all user data from your
- device?
- <ewt-button
- class="danger"
- slot="primaryAction"
- label="Erase User Data"
- @click=${this._confirmInstall}
- ></ewt-button>
- `;
- } else if (!this._installConfirmed) {
- heading = "Confirm Installation";
- const action = isUpdate ? "update to" : "install";
- content = html`
- ${isUpdate
- ? html`Your device is running
- ${this._info!.firmware} ${this._info!.version}.<br /><br />`
- : ""}
- Do you want to ${action}
- ${this._manifest.name} ${this._manifest.version}?
- ${this._installErase
- ? html`<br /><br />All data on the device will be erased.`
- : ""}
- <ewt-button
- slot="primaryAction"
- label="Install"
- @click=${this._confirmInstall}
- ></ewt-button>
- <ewt-button
- slot="secondaryAction"
- label="Back"
- @click=${() => {
- this._state = "DASHBOARD";
- }}
- ></ewt-button>
- `;
- } else if (
- !this._installState ||
- this._installState.state === FlashStateType.INITIALIZING ||
- this._installState.state === FlashStateType.PREPARING
- ) {
- heading = "Installing";
- content = this._renderProgress("Preparing installation");
- hideActions = true;
- } else if (this._installState.state === FlashStateType.ERASING) {
- heading = "Installing";
- content = this._renderProgress("Erasing");
- hideActions = true;
- } else if (
- this._installState.state === FlashStateType.WRITING ||
- // When we're finished, keep showing this screen with 100% written
- // until Improv is initialized / not detected.
- (this._installState.state === FlashStateType.FINISHED &&
- this._client === undefined)
- ) {
- heading = "Installing";
- let percentage: number | undefined;
- let undeterminateLabel: string | undefined;
- if (this._installState.state === FlashStateType.FINISHED) {
- // We're done writing and detecting improv, show spinner
- undeterminateLabel = "Wrapping up";
- } else if (this._installState.details.percentage < 4) {
- // We're writing the firmware under 4%, show spinner or else we don't show any pixels
- undeterminateLabel = "Installing";
- } else {
- // We're writing the firmware over 4%, show progress bar
- percentage = this._installState.details.percentage;
- }
- content = this._renderProgress(
- html`
- ${undeterminateLabel ? html`${undeterminateLabel}<br />` : ""}
- <br />
- This will take
- ${this._installState.chipFamily === "ESP8266"
- ? "a minute"
- : "2 minutes"}.<br />
- Keep this page visible to prevent slow down
- `,
- percentage
- );
- hideActions = true;
- } else if (this._installState.state === FlashStateType.FINISHED) {
- heading = undefined;
- const supportsImprov = this._client !== null;
- content = html`
- <ewt-page-message
- .icon=${OK_ICON}
- label="Installation complete!"
- ></ewt-page-message>
- <ewt-button
- slot="primaryAction"
- label="Next"
- @click=${() => {
- this._state =
- supportsImprov && this._installErase ? "PROVISION" : "DASHBOARD";
- }}
- ></ewt-button>
- `;
- } else if (this._installState.state === FlashStateType.ERROR) {
- heading = "Installation failed";
- content = html`
- <ewt-page-message
- .icon=${ERROR_ICON}
- .label=${this._installState.message}
- ></ewt-page-message>
- <ewt-button
- slot="primaryAction"
- label="Back"
- @click=${async () => {
- this._initialize();
- this._state = "DASHBOARD";
- }}
- ></ewt-button>
- `;
- }
- return [heading, content!, hideActions, allowClosing];
- }
- _renderLogs(): [string | undefined, TemplateResult, boolean] {
- let heading: string | undefined = `Logs`;
- let content: TemplateResult;
- let hideActions = false;
- content = html`
- <ewt-console .port=${this.port} .logger=${this.logger}></ewt-console>
- <ewt-button
- slot="primaryAction"
- label="Back"
- @click=${async () => {
- await this.shadowRoot!.querySelector("ewt-console")!.disconnect();
- this._state = "DASHBOARD";
- this._initialize();
- }}
- ></ewt-button>
- <ewt-button
- slot="secondaryAction"
- label="Download Logs"
- @click=${() => {
- textDownload(
- this.shadowRoot!.querySelector("ewt-console")!.logs(),
- `esp-web-tools-logs.txt`
- );
- this.shadowRoot!.querySelector("ewt-console")!.reset();
- }}
- ></ewt-button>
- <ewt-button
- slot="secondaryAction"
- label="Reset Device"
- @click=${async () => {
- await this.shadowRoot!.querySelector("ewt-console")!.reset();
- }}
- ></ewt-button>
- `;
- return [heading, content!, hideActions];
- }
- public override willUpdate(changedProps: PropertyValues) {
- if (!changedProps.has("_state")) {
- return;
- }
- // Clear errors when changing between pages unless we change
- // to the error page.
- if (this._state !== "ERROR") {
- this._error = undefined;
- }
- // Scan for SSIDs on provision
- if (this._state === "PROVISION") {
- this._updateSsids();
- } else {
- // Reset this value if we leave provisioning.
- this._provisionForce = false;
- }
- if (this._state === "INSTALL") {
- this._installConfirmed = false;
- this._installState = undefined;
- }
- }
- private async _updateSsids() {
- const oldSsids = this._ssids;
- this._ssids = undefined;
- this._busy = true;
- let ssids: Ssid[];
- try {
- ssids = await this._client!.scan();
- } catch (err) {
- // When we fail on first load, pick "Join other"
- if (this._ssids === undefined) {
- this._ssids = null;
- this._selectedSsid = null;
- }
- this._busy = false;
- return;
- }
- if (oldSsids) {
- // If we had a previous list, ensure the selection is still valid
- if (
- this._selectedSsid &&
- !ssids.find((s) => s.name === this._selectedSsid)
- ) {
- this._selectedSsid = ssids[0].name;
- }
- } else {
- this._selectedSsid = ssids.length ? ssids[0].name : null;
- }
- this._ssids = ssids;
- this._busy = false;
- }
- protected override firstUpdated(changedProps: PropertyValues) {
- super.firstUpdated(changedProps);
- this._initialize();
- }
- protected override updated(changedProps: PropertyValues) {
- super.updated(changedProps);
- if (changedProps.has("_state")) {
- this.setAttribute("state", this._state);
- }
- if (this._state !== "PROVISION") {
- return;
- }
- if (changedProps.has("_selectedSsid") && this._selectedSsid === null) {
- // If we pick "Join other", select SSID input.
- this._focusFormElement("ewt-textfield[name=ssid]");
- } else if (changedProps.has("_ssids")) {
- // Form is shown when SSIDs are loaded/marked not supported
- this._focusFormElement();
- }
- }
- private _focusFormElement(selector = "ewt-textfield, ewt-select") {
- const formEl = this.shadowRoot!.querySelector(
- selector
- ) as LitElement | null;
- if (formEl) {
- formEl.updateComplete.then(() => setTimeout(() => formEl.focus(), 100));
- }
- }
- private async _initialize(justInstalled = false) {
- if (this.port.readable === null || this.port.writable === null) {
- this._state = "ERROR";
- this._error =
- "Serial port is not readable/writable. Close any other application using it and try again.";
- return;
- }
- try {
- this._manifest = await downloadManifest(this.manifestPath);
- } catch (err: any) {
- this._state = "ERROR";
- this._error = "Failed to download manifest";
- return;
- }
- if (this._manifest.new_install_improv_wait_time === 0) {
- this._client = null;
- return;
- }
- const client = new ImprovSerial(this.port!, this.logger);
- client.addEventListener("state-changed", () => {
- this.requestUpdate();
- });
- client.addEventListener("error-changed", () => this.requestUpdate());
- try {
- // If a device was just installed, give new firmware 10 seconds (overridable) to
- // format the rest of the flash and do other stuff.
- const timeout = !justInstalled
- ? 1000
- : this._manifest.new_install_improv_wait_time !== undefined
- ? this._manifest.new_install_improv_wait_time * 1000
- : 10000;
- this._info = await client.initialize(timeout);
- this._client = client;
- client.addEventListener("disconnect", this._handleDisconnect);
- } catch (err: any) {
- // Clear old value
- this._info = undefined;
- if (err instanceof PortNotReady) {
- this._state = "ERROR";
- this._error =
- "Serial port is not ready. Close any other application using it and try again.";
- } else {
- this._client = null; // not supported
- this.logger.error("Improv initialization failed.", err);
- }
- }
- }
- private _startInstall(erase: boolean) {
- this._state = "INSTALL";
- this._installErase = erase;
- this._installConfirmed = false;
- }
- private async _confirmInstall() {
- this._installConfirmed = true;
- this._installState = undefined;
- if (this._client) {
- await this._closeClientWithoutEvents(this._client);
- }
- this._client = undefined;
- // Close port. ESPLoader likes opening it.
- await this.port.close();
- flash(
- (state) => {
- this._installState = state;
- if (state.state === FlashStateType.FINISHED) {
- sleep(100)
- // Flashing closes the port
- .then(() => this.port.open({ baudRate: 115200 }))
- .then(() => this._initialize(true))
- .then(() => this.requestUpdate());
- } else if (state.state === FlashStateType.ERROR) {
- sleep(100)
- // Flashing closes the port
- .then(() => this.port.open({ baudRate: 115200 }));
- }
- },
- this.port,
- this.manifestPath,
- this._manifest,
- this._installErase
- );
- }
- private async _doProvision() {
- this._busy = true;
- this._wasProvisioned =
- this._client!.state === ImprovSerialCurrentState.PROVISIONED;
- const ssid =
- this._selectedSsid === null
- ? (
- this.shadowRoot!.querySelector(
- "ewt-textfield[name=ssid]"
- ) as EwtTextfield
- ).value
- : this._selectedSsid;
- const password =
- (
- this.shadowRoot!.querySelector(
- "ewt-textfield[name=password]"
- ) as EwtTextfield | null
- )?.value || "";
- try {
- await this._client!.provision(ssid, password, 30000);
- } catch (err: any) {
- return;
- } finally {
- this._busy = false;
- this._provisionForce = false;
- }
- }
- private _handleDisconnect = () => {
- this._state = "ERROR";
- this._error = "Disconnected";
- };
- private async _handleClose() {
- if (this._client) {
- await this._closeClientWithoutEvents(this._client);
- }
- fireEvent(this, "closed" as any);
- this.parentNode!.removeChild(this);
- }
- /**
- * Return if the device runs same firmware as manifest.
- */
- private get _isSameFirmware() {
- return !this._info
- ? false
- : this.overrides?.checkSameFirmware
- ? this.overrides.checkSameFirmware(this._manifest, this._info)
- : this._info.firmware === this._manifest.name;
- }
- /**
- * Return if the device runs same firmware and version as manifest.
- */
- private get _isSameVersion() {
- return (
- this._isSameFirmware && this._info!.version === this._manifest.version
- );
- }
- private async _closeClientWithoutEvents(client: ImprovSerial) {
- client.removeEventListener("disconnect", this._handleDisconnect);
- await client.close();
- }
- static styles = [
- dialogStyles,
- css`
- :host {
- --mdc-dialog-max-width: 390px;
- }
- ewt-icon-button {
- position: absolute;
- right: 4px;
- top: 10px;
- }
- .table-row {
- display: flex;
- }
- .table-row.last {
- margin-bottom: 16px;
- }
- .table-row svg {
- width: 20px;
- margin-right: 8px;
- }
- ewt-textfield,
- ewt-select {
- display: block;
- margin-top: 16px;
- }
- .dashboard-buttons {
- margin: 0 0 -16px -8px;
- }
- .dashboard-buttons div {
- display: block;
- margin: 4px 0;
- }
- a.has-button {
- text-decoration: none;
- }
- .error {
- color: var(--improv-danger-color);
- }
- .danger {
- --mdc-theme-primary: var(--improv-danger-color);
- --mdc-theme-secondary: var(--improv-danger-color);
- }
- button.link {
- background: none;
- color: inherit;
- border: none;
- padding: 0;
- font: inherit;
- text-align: left;
- text-decoration: underline;
- cursor: pointer;
- }
- :host([state="LOGS"]) ewt-dialog {
- --mdc-dialog-max-width: 90vw;
- }
- ewt-console {
- width: calc(80vw - 48px);
- height: 80vh;
- }
- ewt-list-item[value="-1"] {
- border-top: 1px solid #ccc;
- }
- `,
- ];
- }
- customElements.define("ewt-install-dialog", EwtInstallDialog);
- declare global {
- interface HTMLElementTagNameMap {
- "ewt-install-dialog": EwtInstallDialog;
- }
- }
|