diff --git a/src/install-dialog.ts b/src/install-dialog.ts index 8baac08b..80f2fd60 100644 --- a/src/install-dialog.ts +++ b/src/install-dialog.ts @@ -31,7 +31,11 @@ import { networkWifiFull, } from "./components/svg"; import { Logger, Manifest, FlashStateType, FlashState } from "./const.js"; -import { ImprovSerial, Ssid } from "improv-wifi-serial-sdk/dist/serial"; +import { + ImprovSerial, + NetworkState, + Ssid, +} from "improv-wifi-serial-sdk/dist/serial"; import { ImprovSerialCurrentState, ImprovSerialErrorState, @@ -53,6 +57,11 @@ console.log( const ERROR_ICON = "⚠️"; const OK_ICON = "🎉"; +// Network state is polled in the background, so a failed request can recover +// on a later tick; keep the timeout small to avoid queueing up requests. +const NETWORK_STATE_TIMEOUT = 500; +const NETWORK_STATE_POLL_INTERVAL = 2500; + // A device that just booted can come back from its first scan with no networks // at all. Keep looking (the SDK scans every 3s, so this covers four scans) // before giving up and showing the form, or we'd tell the user we found nothing @@ -95,6 +104,12 @@ export class EwtInstallDialog extends LitElement { // null = NOT_SUPPORTED @state() private _client?: ImprovSerial | null; + // undefined = not yet known + // null = the device doesn't support the network state command + @state() private _networkState?: NetworkState | null; + + private _networkStatePollInterval?: ReturnType; + @state() private _state: | "ERROR" | "DASHBOARD" @@ -249,20 +264,19 @@ export class EwtInstallDialog extends LitElement { ` : ""} - ${this._client!.nextUrl === undefined + ${this._deviceUrl === undefined ? "" : html` ${listItemVisitDevice}
Visit Device
`} - ${!this._manifest.home_assistant_domain || - this._client!.state !== ImprovSerialCurrentState.PROVISIONED + ${!this._manifest.home_assistant_domain || !this._isOnline ? "" : html` { + const client = this._client; + if (!client) { + return; + } + try { + this._networkState = await client.requestNetworkState( + NETWORK_STATE_TIMEOUT, + ); + } catch (err) { + if (client.error === ImprovSerialErrorState.UNKNOWN_RPC_COMMAND) { + // The device predates the command; stop asking. + this._networkState = null; + this._stopNetworkStatePolling(); + } + this.logger.debug(`Failed to fetch network state: ${err}`); + } + }; + refresh(); + this._networkStatePollInterval = setInterval( + refresh, + NETWORK_STATE_POLL_INTERVAL, + ); + } + + private _stopNetworkStatePolling() { + clearInterval(this._networkStatePollInterval); + this._networkStatePollInterval = undefined; + } + /** * Return if the provision page shows the network form (and not a message). */ @@ -950,6 +1026,7 @@ export class EwtInstallDialog extends LitElement { } this._syncScanning(); + this._setupNetworkStatePolling(); if (this._state !== "PROVISION") { return; @@ -1001,6 +1078,8 @@ export class EwtInstallDialog extends LitElement { } const client = new ImprovSerial(this.port!, this.logger); + // Don't carry network state over from a previous client (e.g. pre-install). + this._networkState = undefined; client.addEventListener("state-changed", () => { this.requestUpdate(); }); @@ -1132,6 +1211,12 @@ export class EwtInstallDialog extends LitElement { this.parentNode!.removeChild(this); } + public override disconnectedCallback() { + super.disconnectedCallback(); + // The interval would otherwise keep firing against the closed client. + this._stopNetworkStatePolling(); + } + /** * Return if the device runs same firmware as manifest. */