From ba85feb548db894654a5f76ba300c1ec3e5ce2ec Mon Sep 17 00:00:00 2001
From: Paulus Schoutsen <balloob@gmail.com>
Date: Tue, 3 Jan 2023 15:58:37 -0500
Subject: [PATCH] Configure wifi improvements (#304)

* Add timeout to improv provisioning

* Add refresh SSIDs button

* Hide password if insecured wifi

* Bump serial sdk 2.4.0
---
 package-lock.json     |  16 +++---
 package.json          |   2 +-
 src/components/svg.ts |   9 +++
 src/install-dialog.ts | 124 ++++++++++++++++++++++++++++--------------
 4 files changed, 103 insertions(+), 48 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index e54ec2cb..211ad7ee 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,7 +18,7 @@
         "@material/mwc-icon-button": "^0.27.0",
         "@material/mwc-textfield": "^0.27.0",
         "esptool-js": "github:espressif/esptool-js#076af269f44daa5b7823031221f39bf22124c129",
-        "improv-wifi-serial-sdk": "^2.3.0",
+        "improv-wifi-serial-sdk": "^2.4.0",
         "lit": "^2.5.0",
         "pako": "^2.1.0",
         "tslib": "^2.4.1"
@@ -2050,13 +2050,14 @@
       }
     },
     "node_modules/improv-wifi-serial-sdk": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/improv-wifi-serial-sdk/-/improv-wifi-serial-sdk-2.3.0.tgz",
-      "integrity": "sha512-z5wiuM0uAiLL/Ifc2ZwdEBpGqpjJsUv0MS12kRqMgI01C8mn58ZSEVwoxExpOPUfX8dZL5kdrpYygqM+VfhCJQ==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/improv-wifi-serial-sdk/-/improv-wifi-serial-sdk-2.4.0.tgz",
+      "integrity": "sha512-QiWbGMZdXN4LsyiS0hml0NY9D5hpmje1092SrKcFzGTuAiHpOJOfNTalD/Zl3hAd1LuN4XJLS3JCfa6ntoV8KQ==",
       "dependencies": {
         "@material/mwc-button": "^0.27.0",
         "@material/mwc-circular-progress": "^0.27.0",
         "@material/mwc-dialog": "^0.27.0",
+        "@material/mwc-icon-button": "^0.27.0",
         "@material/mwc-list": "^0.27.0",
         "@material/mwc-select": "^0.27.0",
         "@material/mwc-textfield": "^0.27.0",
@@ -4525,13 +4526,14 @@
       "dev": true
     },
     "improv-wifi-serial-sdk": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/improv-wifi-serial-sdk/-/improv-wifi-serial-sdk-2.3.0.tgz",
-      "integrity": "sha512-z5wiuM0uAiLL/Ifc2ZwdEBpGqpjJsUv0MS12kRqMgI01C8mn58ZSEVwoxExpOPUfX8dZL5kdrpYygqM+VfhCJQ==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/improv-wifi-serial-sdk/-/improv-wifi-serial-sdk-2.4.0.tgz",
+      "integrity": "sha512-QiWbGMZdXN4LsyiS0hml0NY9D5hpmje1092SrKcFzGTuAiHpOJOfNTalD/Zl3hAd1LuN4XJLS3JCfa6ntoV8KQ==",
       "requires": {
         "@material/mwc-button": "^0.27.0",
         "@material/mwc-circular-progress": "^0.27.0",
         "@material/mwc-dialog": "^0.27.0",
+        "@material/mwc-icon-button": "^0.27.0",
         "@material/mwc-list": "^0.27.0",
         "@material/mwc-select": "^0.27.0",
         "@material/mwc-textfield": "^0.27.0",
diff --git a/package.json b/package.json
index b818ca9f..46bae73a 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
     "@material/mwc-icon-button": "^0.27.0",
     "@material/mwc-textfield": "^0.27.0",
     "esptool-js": "github:espressif/esptool-js#076af269f44daa5b7823031221f39bf22124c129",
-    "improv-wifi-serial-sdk": "^2.3.0",
+    "improv-wifi-serial-sdk": "^2.4.0",
     "lit": "^2.5.0",
     "pako": "^2.1.0",
     "tslib": "^2.4.1"
diff --git a/src/components/svg.ts b/src/components/svg.ts
index a9cb1451..e7f23674 100644
--- a/src/components/svg.ts
+++ b/src/components/svg.ts
@@ -25,3 +25,12 @@ export const chipIcon = svg`
     />
   </svg>
 `;
+
+export const refreshIcon = svg`
+  <svg viewBox="0 0 24 24">
+    <path
+      fill="currentColor"
+      d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z"
+    />
+  </svg>
+`;
diff --git a/src/install-dialog.ts b/src/install-dialog.ts
index 36485a09..6e2c3d63 100644
--- a/src/install-dialog.ts
+++ b/src/install-dialog.ts
@@ -12,7 +12,12 @@ import "./components/ewt-select";
 import "./components/ewt-list-item";
 import "./pages/ewt-page-progress";
 import "./pages/ewt-page-message";
-import { chipIcon, closeIcon, firmwareIcon } from "./components/svg";
+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 {
@@ -74,8 +79,8 @@ export class EwtInstallDialog extends LitElement {
   // null = not available
   @state() private _ssids?: Ssid[] | null;
 
-  // -1 = custom
-  @state() private _selectedSsid = -1;
+  // Name of Ssid. Null = other
+  @state() private _selectedSsid: string | null = null;
 
   protected render() {
     if (!this.port) {
@@ -416,6 +421,10 @@ export class EwtInstallDialog extends LitElement {
           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:
@@ -424,6 +433,9 @@ export class EwtInstallDialog extends LitElement {
         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
@@ -439,42 +451,48 @@ export class EwtInstallDialog extends LitElement {
                   const index = ev.detail.index;
                   // The "Join Other" item is always the last item.
                   this._selectedSsid =
-                    index === this._ssids!.length ? -1 : index;
+                    index === this._ssids!.length
+                      ? null
+                      : this._ssids![index].name;
                 }}
                 @closed=${(ev: Event) => ev.stopPropagation()}
               >
                 ${this._ssids!.map(
-                  (info, idx) => html`
+                  (info) => html`
                     <ewt-list-item
-                      .selected=${this._selectedSsid === idx}
-                      value=${idx}
+                      .selected=${selectedSsid === info}
+                      .value=${info.name}
                     >
                       ${info.name}
                     </ewt-list-item>
                   `
                 )}
-                <ewt-list-item
-                  .selected=${this._selectedSsid === -1}
-                  value="-1"
-                >
+                <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
-          this._selectedSsid === -1
+          !selectedSsid
             ? html`
                 <ewt-textfield label="Network Name" name="ssid"></ewt-textfield>
               `
             : ""
         }
-        <ewt-textfield
-          label="Password"
-          name="password"
-          type="password"
-        ></ewt-textfield>
+        ${!selectedSsid || selectedSsid.secured
+          ? html`
+              <ewt-textfield
+                label="Password"
+                name="password"
+                type="password"
+              ></ewt-textfield>
+            `
+          : ""}
         <ewt-button
           slot="primaryAction"
           label="Connect"
@@ -701,20 +719,7 @@ export class EwtInstallDialog extends LitElement {
     }
     // Scan for SSIDs on provision
     if (this._state === "PROVISION") {
-      this._ssids = undefined;
-      this._busy = true;
-      this._client!.scan().then(
-        (ssids) => {
-          this._busy = false;
-          this._ssids = ssids;
-          this._selectedSsid = ssids.length ? 0 : -1;
-        },
-        () => {
-          this._busy = false;
-          this._ssids = null;
-          this._selectedSsid = -1;
-        }
-      );
+      this._updateSsids();
     } else {
       // Reset this value if we leave provisioning.
       this._provisionForce = false;
@@ -726,6 +731,41 @@ export class EwtInstallDialog extends LitElement {
     }
   }
 
+  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();
@@ -742,7 +782,7 @@ export class EwtInstallDialog extends LitElement {
       return;
     }
 
-    if (changedProps.has("_selectedSsid") && this._selectedSsid === -1) {
+    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")) {
@@ -855,20 +895,21 @@ export class EwtInstallDialog extends LitElement {
     this._wasProvisioned =
       this._client!.state === ImprovSerialCurrentState.PROVISIONED;
     const ssid =
-      this._selectedSsid === -1
+      this._selectedSsid === null
         ? (
             this.shadowRoot!.querySelector(
               "ewt-textfield[name=ssid]"
             ) as EwtTextfield
           ).value
-        : this._ssids![this._selectedSsid].name;
-    const password = (
-      this.shadowRoot!.querySelector(
-        "ewt-textfield[name=password]"
-      ) 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);
+      await this._client!.provision(ssid, password, 30000);
     } catch (err: any) {
       return;
     } finally {
@@ -975,6 +1016,9 @@ export class EwtInstallDialog extends LitElement {
         width: calc(80vw - 48px);
         height: 80vh;
       }
+      ewt-list-item[value="-1"] {
+        border-top: 1px solid #ccc;
+      }
     `,
   ];
 }