diff --git a/README.md b/README.md index a49d545..f6b1651 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ Manifest definition: ```json { "name": "ESPHome", + "version": "2021.10.3", "builds": [ { "chipFamily": "ESP32", - "improv": true, "parts": [ { "path": "bootloader.bin", "offset": 4096 }, { "path": "partitions.bin", "offset": 32768 }, @@ -46,17 +46,6 @@ Manifest definition: } ``` -Allows for optionally passing an attribute to trigger an erase before installation. - -```html - -``` - -All attributes can also be set via properties (`manifest`, `eraseFirst`) - ## Styling ### Attributes @@ -67,14 +56,6 @@ The following attributes are automatically added to `` a | -- | -- | | `install-supported` | Added if installing firmware is supported | `install-unsupported` | Added if installing firmware is not supported -| `active` | Added when flashing is active - -You can add the following attributes or properties to change the UI elements: - -| Attribute | Property | Description | -| -- | -- | -- | -| `show-log` | `showLog` | Show a log style view of the progress instead of a progress bar -| `hide-progress` | `hideProgress` | Hides all progress UI elements ### CSS custom properties (variables) @@ -115,4 +96,4 @@ details | An optional extra field that is different [per state](https://github.c ## Development -Run `script/develop`. This starts a server. Open it on http://localhost:5000. +Run `script/develop`. This starts a server. Open it on http://localhost:5001. diff --git a/index.html b/index.html index 181d962..cefda66 100644 --- a/index.html +++ b/index.html @@ -90,13 +90,22 @@ padding: 8px; border-bottom: 1px solid #ccc; } + @media (prefers-color-scheme: dark) { + body { + background-color: #333; + color: #fff; + } + a { + color: #58a6ff; + } + } @@ -112,8 +121,8 @@

To try it out and install - the ESPHome firmware, connect an ESP to - your computer and hit the button: + ESPHome on an ESP, connect it to your + computer and hit the button:

<script type="module" - src="https://unpkg.com/esp-web-tools@3.6.0/dist/web/install-button.js?module" + src="https://unpkg.com/esp-web-tools@4.0.0/dist/web/install-button.js?module" ></script> <esp-web-install-button @@ -243,17 +252,17 @@ ESP Web Tools manifest describe the firmware that you want to install. It allows specifying different builds for the different types of ESP devices. Current supported chip families are ESP8266, - ESP32, ESP32-C3 and ESP32-S2. The + ESP32, ESP32C3 and ESP32S2. The correct build will be automatically selected based on the type of the ESP device we detect via the serial port.

 {
   "name": "ESPHome",
+  "version": "2021.11.0",
   "builds": [
     {
       "chipFamily": "ESP32",
-      "improv": true,
       "parts": [
         { "path": "bootloader.bin", "offset": 4096 },
         { "path": "partitions.bin", "offset": 32768 },
@@ -276,7 +285,22 @@
         where it should be installed. Part paths are resolved relative to the
         path of the manifest, but can also be URLs to other hosts.
       

+

Wi-Fi provisioning

+ ESP Web Tools has support for the + Improv Wi-Fi serial standard. This is an open standard to allow configuring Wi-Fi via the serial + port. +

+

+ If Improv is supported, a user will be guided to connect the device to + the network after installation. It also allows the user to connect + already installed devices and re-configure the wireless network + settings. +

+

TODO EXAMPLE VIDEO

+

Customizing the look and feel

diff --git a/package-lock.json b/package-lock.json index 0a45a63..847365e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,23 @@ { "name": "esp-web-tools", - "version": "3.6.0", + "version": "4.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "3.6.0", + "name": "esp-web-tools", + "version": "4.0.0", "license": "Apache-2.0", "dependencies": { + "@material/mwc-button": "^0.25.3", + "@material/mwc-checkbox": "^0.25.3", + "@material/mwc-circular-progress": "^0.25.3", + "@material/mwc-dialog": "^0.25.3", + "@material/mwc-icon-button": "^0.25.3", "@material/mwc-linear-progress": "^0.25.1", - "esp-web-flasher": "^3.2.0", + "@material/mwc-textfield": "^0.25.3", + "esp-web-flasher": "^4.0.0", + "improv-wifi-serial-sdk": "^1.0.0", "lit": "^2.0.0", "tslib": "^2.3.1" }, @@ -72,6 +80,69 @@ "tslib": "^2.1.0" } }, + "node_modules/@material/button": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-DB0MAvdIGWKuFwlQ57hjv7ZuHIioT2mnG7RWtL7ZoCWoY45nCrsbJirmX5zZFipm9gIOJ3YnIkIrUyMVSrDX+g==", + "dependencies": { + "@material/density": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/elevation": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/ripple": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/shape": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/tokens": "14.0.0-canary.261f2db59.0", + "@material/touch-target": "14.0.0-canary.261f2db59.0", + "@material/typography": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/circular-progress": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-Gi6Ika8MEZQOT3Qei2NfTj+sRWxCDFjchPM7szNjIKgL2DyH03bHmodQFVcyBFiPWEcWMc/mqVYgGf/XJXs85w==", + "dependencies": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/progress-indicator": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/density": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-zOR5wISqPVr8KS/ERNC1jdRV9O832lzclyS9Ea20rDrWfuOiYsQ9bbIk12xWlxpgsn7r9fxQJyd1O2SURoHdRA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dialog": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-NfQR0fmNS/y2iRAx5YeODLLywBAnSyZI/CL9GUq4NiNj+FeSxe+5bhG1p9NxHeGMjEVrl6fG5L9ql7lqtfQaYQ==", + "dependencies": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/button": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/elevation": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/icon-button": "14.0.0-canary.261f2db59.0", + "@material/ripple": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/shape": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/tokens": "14.0.0-canary.261f2db59.0", + "@material/touch-target": "14.0.0-canary.261f2db59.0", + "@material/typography": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "node_modules/@material/dom": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/dom/-/dom-14.0.0-canary.261f2db59.0.tgz", @@ -81,6 +152,19 @@ "tslib": "^2.1.0" } }, + "node_modules/@material/elevation": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-AqN/tsTGGyBzZ7CtoSMBY9bDYvCuUt98EUfiGjZGXcf4HgoHV3Cn/JSLrhru5Cq8Nx6HF6AmHh3dQCfNCQduew==", + "dependencies": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "node_modules/@material/feature-targeting": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-14.0.0-canary.261f2db59.0.tgz", @@ -89,6 +173,49 @@ "tslib": "^2.1.0" } }, + "node_modules/@material/floating-label": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-Cp0/LngkW6/uZWbEDTe3Ox143V4kYtxl9twiM3XLKd6a67JHCzneQWFzC0qSg90b3r5O+1zOkT3ZMF2Pbu2Vwg==", + "dependencies": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/typography": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/icon-button": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-9P6cjRqKtjE6ML+r5yz0ExU/f2KLdNabHQxmO6RpKd/FnjTyP1NcWqqj8dsvo/DZ7mOtT1MIThgkQDdiMqcYLg==", + "dependencies": { + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/density": "14.0.0-canary.261f2db59.0", + "@material/elevation": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/ripple": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/touch-target": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/line-ripple": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-LlyiyxpHNlFt0PZ8Q2tvOPbjNcgm3L7tUebXsM7iGyoKXfj0HwyDI31S0KgtU3Vs5DIK4U4mnRWtoAxtBW6Jfg==", + "dependencies": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "node_modules/@material/linear-progress": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-14.0.0-canary.261f2db59.0.tgz", @@ -115,6 +242,94 @@ "tslib": "^2.0.1" } }, + "node_modules/@material/mwc-button": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-button/-/mwc-button-0.25.3.tgz", + "integrity": "sha512-usHEKchj9hqetY7n0yebTz1Pk9Z+9W/sNZheFoSaiWQCv9XhtCdKkHH0MXTv8SpwxWuEKUf/XjtyvikGIcIn7w==", + "dependencies": { + "@material/mwc-icon": "^0.25.3", + "@material/mwc-ripple": "^0.25.3", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "node_modules/@material/mwc-checkbox": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-checkbox/-/mwc-checkbox-0.25.3.tgz", + "integrity": "sha512-PSh9IAgQK4XiDzBwgclheejkA4cbZ3K9V1JTTl/YVRDD/OLLM+Bh8tbnAg/1kGVlPWOUfDrYCcZ0gg472ca7gw==", + "dependencies": { + "@material/mwc-base": "^0.25.3", + "@material/mwc-ripple": "^0.25.3", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "node_modules/@material/mwc-circular-progress": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-circular-progress/-/mwc-circular-progress-0.25.3.tgz", + "integrity": "sha512-ajgSzfdRfq0/sZg0Z5W/ZpgZwD8Ioj59m5ScCPXXdkRoVHf7+8lsD/2Fh4095GfoYE4PWSkXYVlWsQCx+aJbcA==", + "dependencies": { + "@material/circular-progress": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/theme": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "node_modules/@material/mwc-dialog": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-dialog/-/mwc-dialog-0.25.3.tgz", + "integrity": "sha512-UpxAYAzKXO1MW4ezpiYfEQgov08p0J8KDVKqKrMwg7lsZRkAtUMk4YJkM6qmWGqGPqd/cN++42PMPHAISJH3yA==", + "dependencies": { + "@material/dialog": "=14.0.0-canary.261f2db59.0", + "@material/dom": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/mwc-button": "^0.25.3", + "blocking-elements": "^0.1.0", + "lit": "^2.0.0", + "tslib": "^2.0.1", + "wicg-inert": "^3.0.0" + } + }, + "node_modules/@material/mwc-floating-label": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-floating-label/-/mwc-floating-label-0.25.3.tgz", + "integrity": "sha512-3uFMi8Y680P0nzP5zih4YuOZJLl/C6Ux9G810Unwo44zblG/ckgJlFiM+T+oR+OH5KM8LbfNlV0ypo7FT5zYJA==", + "dependencies": { + "@material/floating-label": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "node_modules/@material/mwc-icon": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-icon/-/mwc-icon-0.25.3.tgz", + "integrity": "sha512-36076AWZIRSr8qYOLjuDDkxej/HA0XAosrj7TS1ZeLlUBnLUtbDtvc1S7KSa0hqez7ouzOqGaWK24yoNnTa2OA==", + "dependencies": { + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "node_modules/@material/mwc-icon-button": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-icon-button/-/mwc-icon-button-0.25.3.tgz", + "integrity": "sha512-FexkMpK3ZSHh7NF+PIqvVhvAbBOgFDYPck/lqnxIDC3VGJ0rjD/1MqevDy2fY6IcHGlc8Ai7VuYbdQ6Cvw8WcQ==", + "dependencies": { + "@material/mwc-ripple": "^0.25.3", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "node_modules/@material/mwc-line-ripple": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-line-ripple/-/mwc-line-ripple-0.25.3.tgz", + "integrity": "sha512-ANJzSyumb+shBVTIhqF1+YByPU/EpFXxI9CS26qThFqlUDpYXg5xcoZpkMSmZv3Wv/loF1rs2mJfFWOcC6nFnw==", + "dependencies": { + "@material/line-ripple": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, "node_modules/@material/mwc-linear-progress": { "version": "0.25.3", "resolved": "https://registry.npmjs.org/@material/mwc-linear-progress/-/mwc-linear-progress-0.25.3.tgz", @@ -127,6 +342,59 @@ "tslib": "^2.0.1" } }, + "node_modules/@material/mwc-notched-outline": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-notched-outline/-/mwc-notched-outline-0.25.3.tgz", + "integrity": "sha512-8jvU8GD0Pke+pfTQ0PdXpZmkU3XIHhMVY6AHM/2IQrXHkVZmAm9kbwL7ne3Ao+6f5n+DeXDGd+SG9U6ZZjD7gw==", + "dependencies": { + "@material/mwc-base": "^0.25.3", + "@material/notched-outline": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "node_modules/@material/mwc-ripple": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-ripple/-/mwc-ripple-0.25.3.tgz", + "integrity": "sha512-G/gt/csxgME6/sAku3GiuB0O2LLvoPWsRTLq/9iABpaGLJjqaKHvNg/IVzNDdF3YZT7EORgR9cBWWl7umA4i4Q==", + "dependencies": { + "@material/dom": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/ripple": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "node_modules/@material/mwc-textfield": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-textfield/-/mwc-textfield-0.25.3.tgz", + "integrity": "sha512-stpZ8sEyo2Mb9fG2XCoTc1Kom8oRXZiVI5rU88GtfcBU7nH0em8S4grq9X1mVfUG6Cfi1G/T+avCSIhzbYtr0w==", + "dependencies": { + "@material/floating-label": "=14.0.0-canary.261f2db59.0", + "@material/line-ripple": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/mwc-floating-label": "^0.25.3", + "@material/mwc-line-ripple": "^0.25.3", + "@material/mwc-notched-outline": "^0.25.3", + "@material/textfield": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "node_modules/@material/notched-outline": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-gtn+IKAiX2rbfbX3a9aDlfUoKCEYrlAPOZifKXUaZ4UJYMNLzZuAqy7l5Ds30emtqUE22mySTEWqhzK6dePKsA==", + "dependencies": { + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/floating-label": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/shape": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "node_modules/@material/progress-indicator": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-14.0.0-canary.261f2db59.0.tgz", @@ -135,6 +403,20 @@ "tslib": "^2.1.0" } }, + "node_modules/@material/ripple": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-3FLCLj8X7KrFfuYBHJg1b7Odb3V/AW7fxk3m1i1zhDnygKmlQ/abVucH1s2qbX3Y+JIiq+5/C5407h9BFtOf+A==", + "dependencies": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "node_modules/@material/rtl": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-14.0.0-canary.261f2db59.0.tgz", @@ -144,6 +426,38 @@ "tslib": "^2.1.0" } }, + "node_modules/@material/shape": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-VjcQltd1uF9ugvLExMy00SMISjy/370o8lsZlb1T+xHyhXHL3UxeuWYLW5Amq6mbx65+c9Df9WmlXXOdebpEkw==", + "dependencies": { + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/textfield": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-KBPgpvvVFBfLx9nc6+wWOS2hJ40JVwh5KBjMoYbiOEFLf0O7SgCAVREHaFAXrPsC8AeTyUipx6TReONIGfMCPQ==", + "dependencies": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/density": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/floating-label": "14.0.0-canary.261f2db59.0", + "@material/line-ripple": "14.0.0-canary.261f2db59.0", + "@material/notched-outline": "14.0.0-canary.261f2db59.0", + "@material/ripple": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/shape": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/typography": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "node_modules/@material/theme": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/theme/-/theme-14.0.0-canary.261f2db59.0.tgz", @@ -153,6 +467,35 @@ "tslib": "^2.1.0" } }, + "node_modules/@material/tokens": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-mgar9gsLv00HTvXIDvNR1vEEXpfKgeWhVTO8a7aWofSNyENNOVc5ImJwBgCAMb5SgLHBi6w8/c1tPzjOewBfCA==", + "dependencies": { + "@material/elevation": "14.0.0-canary.261f2db59.0" + } + }, + "node_modules/@material/touch-target": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-xA6TTHN7aOTXg/+c6mQJlogzTD+Sp8WPC5TK8RBXbQxEykGXGW15p+H9pG+rX/gzD5iehnHRBrDUFmAGoskhcQ==", + "dependencies": { + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/typography": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-WOCdcNkD5KBRAwICcRqWBRG3cDkyrwK5USTNmG0oxnwnZAN7daOpPTdLppVAhadE7faj8d67ON+V9pH7+T62FQ==", + "dependencies": { + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "node_modules/@rollup/plugin-json": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", @@ -337,6 +680,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/blocking-elements": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/blocking-elements/-/blocking-elements-0.1.1.tgz", + "integrity": "sha512-/SLWbEzMoVIMZACCyhD/4Ya2M1PWP1qMKuiymowPcI+PdWDARqeARBjhj73kbUBCxEmTZCUu5TAqxtwUO9C1Ig==" + }, "node_modules/boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -639,9 +987,9 @@ } }, "node_modules/esp-web-flasher": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/esp-web-flasher/-/esp-web-flasher-3.2.0.tgz", - "integrity": "sha512-jcJtWb5QuENWzeasfGYcJP/MV+XmRQelNRoOVCAKXcBJFh9h9NnfPXJtpoG+RsIMqb7hDdutomz/bBoBUH6urw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esp-web-flasher/-/esp-web-flasher-4.0.0.tgz", + "integrity": "sha512-7d23iEkEjvrYkywLZtvg69GAitRJVE73dN6nmyWNmTvCe55b0UTzndLJtTHANbAiNzpgmJ7/kYnt202A7BD75A==", "dependencies": { "pako": "^2.0.3", "tslib": "^2.2.0" @@ -760,6 +1108,19 @@ "node": ">=4" } }, + "node_modules/improv-wifi-serial-sdk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/improv-wifi-serial-sdk/-/improv-wifi-serial-sdk-1.0.0.tgz", + "integrity": "sha512-R3NM7Ry9DjTyT5B6iIIZjW5LMia64PwLEJnue5lfYlmqHyJuNMxkWrGomqG7AxQLLCul7CPN1qs52nkJglqYsg==", + "dependencies": { + "@material/mwc-button": "^0.25.3", + "@material/mwc-circular-progress": "^0.25.3", + "@material/mwc-dialog": "^0.25.3", + "@material/mwc-textfield": "^0.25.3", + "lit": "^2.0.0", + "tslib": "^2.3.1" + } + }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -1469,6 +1830,11 @@ "which": "bin/which" } }, + "node_modules/wicg-inert": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.1.tgz", + "integrity": "sha512-PhBaNh8ur9Xm4Ggy4umelwNIP6pPP1bv3EaWaKqfb/QNme2rdLjm7wIInvV4WhxVHhzA4Spgw9qNSqWtB/ca2A==" + }, "node_modules/widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -1586,6 +1952,69 @@ "tslib": "^2.1.0" } }, + "@material/button": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-DB0MAvdIGWKuFwlQ57hjv7ZuHIioT2mnG7RWtL7ZoCWoY45nCrsbJirmX5zZFipm9gIOJ3YnIkIrUyMVSrDX+g==", + "requires": { + "@material/density": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/elevation": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/ripple": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/shape": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/tokens": "14.0.0-canary.261f2db59.0", + "@material/touch-target": "14.0.0-canary.261f2db59.0", + "@material/typography": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "@material/circular-progress": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-Gi6Ika8MEZQOT3Qei2NfTj+sRWxCDFjchPM7szNjIKgL2DyH03bHmodQFVcyBFiPWEcWMc/mqVYgGf/XJXs85w==", + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/progress-indicator": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "@material/density": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-zOR5wISqPVr8KS/ERNC1jdRV9O832lzclyS9Ea20rDrWfuOiYsQ9bbIk12xWlxpgsn7r9fxQJyd1O2SURoHdRA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@material/dialog": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-NfQR0fmNS/y2iRAx5YeODLLywBAnSyZI/CL9GUq4NiNj+FeSxe+5bhG1p9NxHeGMjEVrl6fG5L9ql7lqtfQaYQ==", + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/button": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/elevation": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/icon-button": "14.0.0-canary.261f2db59.0", + "@material/ripple": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/shape": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/tokens": "14.0.0-canary.261f2db59.0", + "@material/touch-target": "14.0.0-canary.261f2db59.0", + "@material/typography": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "@material/dom": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/dom/-/dom-14.0.0-canary.261f2db59.0.tgz", @@ -1595,6 +2024,19 @@ "tslib": "^2.1.0" } }, + "@material/elevation": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-AqN/tsTGGyBzZ7CtoSMBY9bDYvCuUt98EUfiGjZGXcf4HgoHV3Cn/JSLrhru5Cq8Nx6HF6AmHh3dQCfNCQduew==", + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "@material/feature-targeting": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-14.0.0-canary.261f2db59.0.tgz", @@ -1603,6 +2045,49 @@ "tslib": "^2.1.0" } }, + "@material/floating-label": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-Cp0/LngkW6/uZWbEDTe3Ox143V4kYtxl9twiM3XLKd6a67JHCzneQWFzC0qSg90b3r5O+1zOkT3ZMF2Pbu2Vwg==", + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/typography": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "@material/icon-button": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-9P6cjRqKtjE6ML+r5yz0ExU/f2KLdNabHQxmO6RpKd/FnjTyP1NcWqqj8dsvo/DZ7mOtT1MIThgkQDdiMqcYLg==", + "requires": { + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/density": "14.0.0-canary.261f2db59.0", + "@material/elevation": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/ripple": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/touch-target": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "@material/line-ripple": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-LlyiyxpHNlFt0PZ8Q2tvOPbjNcgm3L7tUebXsM7iGyoKXfj0HwyDI31S0KgtU3Vs5DIK4U4mnRWtoAxtBW6Jfg==", + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "@material/linear-progress": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-14.0.0-canary.261f2db59.0.tgz", @@ -1629,6 +2114,94 @@ "tslib": "^2.0.1" } }, + "@material/mwc-button": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-button/-/mwc-button-0.25.3.tgz", + "integrity": "sha512-usHEKchj9hqetY7n0yebTz1Pk9Z+9W/sNZheFoSaiWQCv9XhtCdKkHH0MXTv8SpwxWuEKUf/XjtyvikGIcIn7w==", + "requires": { + "@material/mwc-icon": "^0.25.3", + "@material/mwc-ripple": "^0.25.3", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "@material/mwc-checkbox": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-checkbox/-/mwc-checkbox-0.25.3.tgz", + "integrity": "sha512-PSh9IAgQK4XiDzBwgclheejkA4cbZ3K9V1JTTl/YVRDD/OLLM+Bh8tbnAg/1kGVlPWOUfDrYCcZ0gg472ca7gw==", + "requires": { + "@material/mwc-base": "^0.25.3", + "@material/mwc-ripple": "^0.25.3", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "@material/mwc-circular-progress": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-circular-progress/-/mwc-circular-progress-0.25.3.tgz", + "integrity": "sha512-ajgSzfdRfq0/sZg0Z5W/ZpgZwD8Ioj59m5ScCPXXdkRoVHf7+8lsD/2Fh4095GfoYE4PWSkXYVlWsQCx+aJbcA==", + "requires": { + "@material/circular-progress": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/theme": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "@material/mwc-dialog": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-dialog/-/mwc-dialog-0.25.3.tgz", + "integrity": "sha512-UpxAYAzKXO1MW4ezpiYfEQgov08p0J8KDVKqKrMwg7lsZRkAtUMk4YJkM6qmWGqGPqd/cN++42PMPHAISJH3yA==", + "requires": { + "@material/dialog": "=14.0.0-canary.261f2db59.0", + "@material/dom": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/mwc-button": "^0.25.3", + "blocking-elements": "^0.1.0", + "lit": "^2.0.0", + "tslib": "^2.0.1", + "wicg-inert": "^3.0.0" + } + }, + "@material/mwc-floating-label": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-floating-label/-/mwc-floating-label-0.25.3.tgz", + "integrity": "sha512-3uFMi8Y680P0nzP5zih4YuOZJLl/C6Ux9G810Unwo44zblG/ckgJlFiM+T+oR+OH5KM8LbfNlV0ypo7FT5zYJA==", + "requires": { + "@material/floating-label": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "@material/mwc-icon": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-icon/-/mwc-icon-0.25.3.tgz", + "integrity": "sha512-36076AWZIRSr8qYOLjuDDkxej/HA0XAosrj7TS1ZeLlUBnLUtbDtvc1S7KSa0hqez7ouzOqGaWK24yoNnTa2OA==", + "requires": { + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "@material/mwc-icon-button": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-icon-button/-/mwc-icon-button-0.25.3.tgz", + "integrity": "sha512-FexkMpK3ZSHh7NF+PIqvVhvAbBOgFDYPck/lqnxIDC3VGJ0rjD/1MqevDy2fY6IcHGlc8Ai7VuYbdQ6Cvw8WcQ==", + "requires": { + "@material/mwc-ripple": "^0.25.3", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "@material/mwc-line-ripple": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-line-ripple/-/mwc-line-ripple-0.25.3.tgz", + "integrity": "sha512-ANJzSyumb+shBVTIhqF1+YByPU/EpFXxI9CS26qThFqlUDpYXg5xcoZpkMSmZv3Wv/loF1rs2mJfFWOcC6nFnw==", + "requires": { + "@material/line-ripple": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, "@material/mwc-linear-progress": { "version": "0.25.3", "resolved": "https://registry.npmjs.org/@material/mwc-linear-progress/-/mwc-linear-progress-0.25.3.tgz", @@ -1641,6 +2214,59 @@ "tslib": "^2.0.1" } }, + "@material/mwc-notched-outline": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-notched-outline/-/mwc-notched-outline-0.25.3.tgz", + "integrity": "sha512-8jvU8GD0Pke+pfTQ0PdXpZmkU3XIHhMVY6AHM/2IQrXHkVZmAm9kbwL7ne3Ao+6f5n+DeXDGd+SG9U6ZZjD7gw==", + "requires": { + "@material/mwc-base": "^0.25.3", + "@material/notched-outline": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "@material/mwc-ripple": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-ripple/-/mwc-ripple-0.25.3.tgz", + "integrity": "sha512-G/gt/csxgME6/sAku3GiuB0O2LLvoPWsRTLq/9iABpaGLJjqaKHvNg/IVzNDdF3YZT7EORgR9cBWWl7umA4i4Q==", + "requires": { + "@material/dom": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/ripple": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "@material/mwc-textfield": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@material/mwc-textfield/-/mwc-textfield-0.25.3.tgz", + "integrity": "sha512-stpZ8sEyo2Mb9fG2XCoTc1Kom8oRXZiVI5rU88GtfcBU7nH0em8S4grq9X1mVfUG6Cfi1G/T+avCSIhzbYtr0w==", + "requires": { + "@material/floating-label": "=14.0.0-canary.261f2db59.0", + "@material/line-ripple": "=14.0.0-canary.261f2db59.0", + "@material/mwc-base": "^0.25.3", + "@material/mwc-floating-label": "^0.25.3", + "@material/mwc-line-ripple": "^0.25.3", + "@material/mwc-notched-outline": "^0.25.3", + "@material/textfield": "=14.0.0-canary.261f2db59.0", + "lit": "^2.0.0", + "tslib": "^2.0.1" + } + }, + "@material/notched-outline": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-gtn+IKAiX2rbfbX3a9aDlfUoKCEYrlAPOZifKXUaZ4UJYMNLzZuAqy7l5Ds30emtqUE22mySTEWqhzK6dePKsA==", + "requires": { + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/floating-label": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/shape": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "@material/progress-indicator": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-14.0.0-canary.261f2db59.0.tgz", @@ -1649,6 +2275,20 @@ "tslib": "^2.1.0" } }, + "@material/ripple": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-3FLCLj8X7KrFfuYBHJg1b7Odb3V/AW7fxk3m1i1zhDnygKmlQ/abVucH1s2qbX3Y+JIiq+5/C5407h9BFtOf+A==", + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "@material/rtl": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-14.0.0-canary.261f2db59.0.tgz", @@ -1658,6 +2298,38 @@ "tslib": "^2.1.0" } }, + "@material/shape": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-VjcQltd1uF9ugvLExMy00SMISjy/370o8lsZlb1T+xHyhXHL3UxeuWYLW5Amq6mbx65+c9Df9WmlXXOdebpEkw==", + "requires": { + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "@material/textfield": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-KBPgpvvVFBfLx9nc6+wWOS2hJ40JVwh5KBjMoYbiOEFLf0O7SgCAVREHaFAXrPsC8AeTyUipx6TReONIGfMCPQ==", + "requires": { + "@material/animation": "14.0.0-canary.261f2db59.0", + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/density": "14.0.0-canary.261f2db59.0", + "@material/dom": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/floating-label": "14.0.0-canary.261f2db59.0", + "@material/line-ripple": "14.0.0-canary.261f2db59.0", + "@material/notched-outline": "14.0.0-canary.261f2db59.0", + "@material/ripple": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "@material/shape": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "@material/typography": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "@material/theme": { "version": "14.0.0-canary.261f2db59.0", "resolved": "https://registry.npmjs.org/@material/theme/-/theme-14.0.0-canary.261f2db59.0.tgz", @@ -1667,6 +2339,35 @@ "tslib": "^2.1.0" } }, + "@material/tokens": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-mgar9gsLv00HTvXIDvNR1vEEXpfKgeWhVTO8a7aWofSNyENNOVc5ImJwBgCAMb5SgLHBi6w8/c1tPzjOewBfCA==", + "requires": { + "@material/elevation": "14.0.0-canary.261f2db59.0" + } + }, + "@material/touch-target": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-xA6TTHN7aOTXg/+c6mQJlogzTD+Sp8WPC5TK8RBXbQxEykGXGW15p+H9pG+rX/gzD5iehnHRBrDUFmAGoskhcQ==", + "requires": { + "@material/base": "14.0.0-canary.261f2db59.0", + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/rtl": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, + "@material/typography": { + "version": "14.0.0-canary.261f2db59.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-14.0.0-canary.261f2db59.0.tgz", + "integrity": "sha512-WOCdcNkD5KBRAwICcRqWBRG3cDkyrwK5USTNmG0oxnwnZAN7daOpPTdLppVAhadE7faj8d67ON+V9pH7+T62FQ==", + "requires": { + "@material/feature-targeting": "14.0.0-canary.261f2db59.0", + "@material/theme": "14.0.0-canary.261f2db59.0", + "tslib": "^2.1.0" + } + }, "@rollup/plugin-json": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", @@ -1824,6 +2525,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "blocking-elements": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/blocking-elements/-/blocking-elements-0.1.1.tgz", + "integrity": "sha512-/SLWbEzMoVIMZACCyhD/4Ya2M1PWP1qMKuiymowPcI+PdWDARqeARBjhj73kbUBCxEmTZCUu5TAqxtwUO9C1Ig==" + }, "boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -2061,9 +2767,9 @@ "dev": true }, "esp-web-flasher": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/esp-web-flasher/-/esp-web-flasher-3.2.0.tgz", - "integrity": "sha512-jcJtWb5QuENWzeasfGYcJP/MV+XmRQelNRoOVCAKXcBJFh9h9NnfPXJtpoG+RsIMqb7hDdutomz/bBoBUH6urw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esp-web-flasher/-/esp-web-flasher-4.0.0.tgz", + "integrity": "sha512-7d23iEkEjvrYkywLZtvg69GAitRJVE73dN6nmyWNmTvCe55b0UTzndLJtTHANbAiNzpgmJ7/kYnt202A7BD75A==", "requires": { "pako": "^2.0.3", "tslib": "^2.2.0" @@ -2163,6 +2869,19 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "improv-wifi-serial-sdk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/improv-wifi-serial-sdk/-/improv-wifi-serial-sdk-1.0.0.tgz", + "integrity": "sha512-R3NM7Ry9DjTyT5B6iIIZjW5LMia64PwLEJnue5lfYlmqHyJuNMxkWrGomqG7AxQLLCul7CPN1qs52nkJglqYsg==", + "requires": { + "@material/mwc-button": "^0.25.3", + "@material/mwc-circular-progress": "^0.25.3", + "@material/mwc-dialog": "^0.25.3", + "@material/mwc-textfield": "^0.25.3", + "lit": "^2.0.0", + "tslib": "^2.3.1" + } + }, "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -2747,6 +3466,11 @@ "isexe": "^2.0.0" } }, + "wicg-inert": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.1.tgz", + "integrity": "sha512-PhBaNh8ur9Xm4Ggy4umelwNIP6pPP1bv3EaWaKqfb/QNme2rdLjm7wIInvV4WhxVHhzA4Spgw9qNSqWtB/ca2A==" + }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", diff --git a/package.json b/package.json index e8bda8a..35b3eb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "esp-web-tools", - "version": "3.6.0", + "version": "4.0.0", "description": "Web tools for ESP devices", "main": "dist/install-button.js", "repository": "https://github.com/esphome/web", @@ -21,8 +21,15 @@ "typescript": "^4.3.2" }, "dependencies": { + "@material/mwc-button": "^0.25.3", + "@material/mwc-checkbox": "^0.25.3", + "@material/mwc-circular-progress": "^0.25.3", + "@material/mwc-dialog": "^0.25.3", + "@material/mwc-icon-button": "^0.25.3", "@material/mwc-linear-progress": "^0.25.1", - "esp-web-flasher": "^3.2.0", + "@material/mwc-textfield": "^0.25.3", + "esp-web-flasher": "^4.0.0", + "improv-wifi-serial-sdk": "^1.0.0", "lit": "^2.0.0", "tslib": "^2.3.1" } diff --git a/script/develop b/script/develop index 665528c..2cba10b 100755 --- a/script/develop +++ b/script/develop @@ -11,7 +11,7 @@ trap "kill 0" EXIT # Run tsc once as rollup expects those files tsc || true -npm exec -- serve & +npm exec -- serve -p 5001 & npm exec -- tsc --watch & npm exec -- rollup -c --watch & wait diff --git a/src/components/ewt-button.ts b/src/components/ewt-button.ts new file mode 100644 index 0000000..f2600b0 --- /dev/null +++ b/src/components/ewt-button.ts @@ -0,0 +1,14 @@ +import { ButtonBase } from "@material/mwc-button/mwc-button-base"; +import { styles } from "@material/mwc-button/styles.css"; + +declare global { + interface HTMLElementTagNameMap { + "ewt-button": EwtButton; + } +} + +export class EwtButton extends ButtonBase { + static override styles = [styles]; +} + +customElements.define("ewt-button", EwtButton); diff --git a/src/components/ewt-circular-progress.ts b/src/components/ewt-circular-progress.ts new file mode 100644 index 0000000..13c7d7b --- /dev/null +++ b/src/components/ewt-circular-progress.ts @@ -0,0 +1,14 @@ +import { CircularProgressBase } from "@material/mwc-circular-progress/mwc-circular-progress-base"; +import { styles } from "@material/mwc-circular-progress/mwc-circular-progress.css"; + +declare global { + interface HTMLElementTagNameMap { + "ewt-circular-progress": EwtCircularProgress; + } +} + +export class EwtCircularProgress extends CircularProgressBase { + static override styles = [styles]; +} + +customElements.define("ewt-circular-progress", EwtCircularProgress); diff --git a/src/components/ewt-console.ts b/src/components/ewt-console.ts new file mode 100644 index 0000000..669c660 --- /dev/null +++ b/src/components/ewt-console.ts @@ -0,0 +1,227 @@ +import { ColoredConsole } from "../util/console-color"; +import { sleep } from "../util/sleep"; +import { LineBreakTransformer } from "../util/line-break-transformer"; +import { Logger } from "../const"; + +export class EwtConsole extends HTMLElement { + public port!: SerialPort; + public logger!: Logger; + + private _console?: ColoredConsole; + private _cancelConnection?: () => Promise; + + public connectedCallback() { + if (this._console) { + return; + } + const shadowRoot = this.attachShadow({ mode: "open" }); + + shadowRoot.innerHTML = ` + +

+
+ > + + +
+ `; + + this._console = new ColoredConsole(this.shadowRoot!.querySelector("div")!); + const input = this.shadowRoot!.querySelector("input")!; + + this.addEventListener("click", () => input.focus()); + + input.addEventListener("keydown", (ev) => { + if (ev.key === "Enter") { + ev.preventDefault(); + ev.stopPropagation(); + this._sendCommand(); + } + }); + + const abortController = new AbortController(); + const connection = this._connect(abortController.signal); + this._cancelConnection = () => { + abortController.abort(); + return connection; + }; + } + + private async _connect(abortSignal: AbortSignal) { + this.logger.debug("Starting console read loop"); + try { + await this.port + .readable!.pipeThrough(new TextDecoderStream(), { + signal: abortSignal, + }) + .pipeThrough(new TransformStream(new LineBreakTransformer())) + .pipeTo( + new WritableStream({ + write: (chunk) => { + this._console!.addLine(chunk); + }, + }) + ); + if (!abortSignal.aborted) { + this._console!.addLine(""); + this._console!.addLine(""); + this._console!.addLine("Terminal disconnected"); + } + } catch (e) { + this._console!.addLine(""); + this._console!.addLine(""); + this._console!.addLine(`Terminal disconnected: ${e}`); + } finally { + await sleep(100); + this.logger.debug("Finished console read loop"); + } + } + + private async _sendCommand() { + const input = this.shadowRoot!.querySelector("input")!; + const command = input.value; + const encoder = new TextEncoder(); + const writer = this.port.writable!.getWriter(); + await writer.write(encoder.encode(command)); + this._console!.addLine(`> ${command}\n`); + input.value = ""; + input.focus(); + try { + writer.releaseLock(); + } catch (err) { + console.error("Ignoring release lock error", err); + } + } + + public async disconnect() { + if (this._cancelConnection) { + await this._cancelConnection(); + this._cancelConnection = undefined; + } + } + + public async reset() { + this.logger.debug("Triggering reset."); + await this.port.setSignals({ + dataTerminalReady: false, + requestToSend: true, + }); + await this.port.setSignals({ + dataTerminalReady: false, + requestToSend: false, + }); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } +} + +customElements.define("ewt-console", EwtConsole); + +declare global { + interface HTMLElementTagNameMap { + "ewt-console": EwtConsole; + } +} diff --git a/src/components/ewt-dialog.ts b/src/components/ewt-dialog.ts new file mode 100644 index 0000000..74c3174 --- /dev/null +++ b/src/components/ewt-dialog.ts @@ -0,0 +1,22 @@ +import { DialogBase } from "@material/mwc-dialog/mwc-dialog-base"; +import { styles } from "@material/mwc-dialog/mwc-dialog.css"; +import { css } from "lit"; + +declare global { + interface HTMLElementTagNameMap { + "ewt-dialog": EwtDialog; + } +} + +export class EwtDialog extends DialogBase { + static override styles = [ + styles, + css` + .mdc-dialog__title { + padding-right: 52px; + } + `, + ]; +} + +customElements.define("ewt-dialog", EwtDialog); diff --git a/src/components/ewt-icon-button.ts b/src/components/ewt-icon-button.ts new file mode 100644 index 0000000..b50be76 --- /dev/null +++ b/src/components/ewt-icon-button.ts @@ -0,0 +1,14 @@ +import { IconButtonBase } from "@material/mwc-icon-button/mwc-icon-button-base"; +import { styles } from "@material/mwc-icon-button/mwc-icon-button.css"; + +declare global { + interface HTMLElementTagNameMap { + "ewt-icon-button": EwtIconButton; + } +} + +export class EwtIconButton extends IconButtonBase { + static override styles = [styles]; +} + +customElements.define("ewt-icon-button", EwtIconButton); diff --git a/src/components/ewt-textfield.ts b/src/components/ewt-textfield.ts new file mode 100644 index 0000000..dae1d73 --- /dev/null +++ b/src/components/ewt-textfield.ts @@ -0,0 +1,14 @@ +import { TextFieldBase } from "@material/mwc-textfield/mwc-textfield-base"; +import { styles } from "@material/mwc-textfield/mwc-textfield.css"; + +declare global { + interface HTMLElementTagNameMap { + "ewt-textfield": EwtTextfield; + } +} + +export class EwtTextfield extends TextFieldBase { + static override styles = [styles]; +} + +customElements.define("ewt-textfield", EwtTextfield); diff --git a/src/connect.ts b/src/connect.ts new file mode 100644 index 0000000..b61cbfe --- /dev/null +++ b/src/connect.ts @@ -0,0 +1,30 @@ +import type { InstallButton } from "./install-button.js"; +import "./install-dialog.js"; + +export const connect = async (button: InstallButton) => { + let port: SerialPort | undefined; + try { + port = await navigator.serial.requestPort(); + } catch (err) { + console.error("User cancelled request", err); + return; + } + + if (!port) { + return; + } + + await port.open({ baudRate: 115200 }); + + const el = document.createElement("ewt-install-dialog"); + el.port = port; + el.manifestPath = button.getAttribute("manifest")!; + el.addEventListener( + "closed", + () => { + port!.close(); + }, + { once: true } + ); + document.body.appendChild(el); +}; diff --git a/src/const.ts b/src/const.ts index 0ec91fe..7744e14 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,6 +1,11 @@ +export interface Logger { + log(msg: string, ...args: any[]): void; + error(msg: string, ...args: any[]): void; + debug(msg: string, ...args: any[]): void; +} + export interface Build { chipFamily: "ESP32" | "ESP8266" | "ESP32-S2" | "ESP32-C3"; - improv: boolean; parts: { path: string; offset: number; @@ -9,11 +14,12 @@ export interface Build { export interface Manifest { name: string; + version: string; builds: Build[]; } export interface BaseFlashState { - state: State; + state: FlashStateType; message: string; manifest?: Manifest; build?: Build; @@ -21,36 +27,36 @@ export interface BaseFlashState { } export interface InitializingState extends BaseFlashState { - state: State.INITIALIZING; + state: FlashStateType.INITIALIZING; details: { done: boolean }; } export interface ManifestState extends BaseFlashState { - state: State.MANIFEST; + state: FlashStateType.MANIFEST; details: { done: boolean }; } export interface PreparingState extends BaseFlashState { - state: State.PREPARING; + state: FlashStateType.PREPARING; details: { done: boolean }; } export interface ErasingState extends BaseFlashState { - state: State.ERASING; + state: FlashStateType.ERASING; details: { done: boolean }; } export interface WritingState extends BaseFlashState { - state: State.WRITING; + state: FlashStateType.WRITING; details: { bytesTotal: number; bytesWritten: number; percentage: number }; } export interface FinishedState extends BaseFlashState { - state: State.FINISHED; + state: FlashStateType.FINISHED; } export interface ErrorState extends BaseFlashState { - state: State.ERROR; + state: FlashStateType.ERROR; details: { error: FlashError; details: string | Error }; } @@ -63,7 +69,7 @@ export type FlashState = | FinishedState | ErrorState; -export const enum State { +export const enum FlashStateType { INITIALIZING = "initializing", MANIFEST = "manifest", PREPARING = "preparing", diff --git a/src/flash-log.ts b/src/flash-log.ts deleted file mode 100644 index 6e5985a..0000000 --- a/src/flash-log.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { css, html, HTMLTemplateResult, LitElement } from "lit"; -import { customElement, state } from "lit/decorators.js"; -import { classMap } from "lit/directives/class-map.js"; -import { FlashState, State } from "./const"; - -interface Row { - state?: State; - message: HTMLTemplateResult | string; - error?: boolean; - action?: boolean; -} - -@customElement("esp-web-flash-log") -export class FlashLog extends LitElement { - @state() private _rows: Row[] = []; - - protected render() { - return html`${this._rows.map( - (row) => - html`
- ${row.message} -
` - )}`; - } - - public willUpdate() { - this.toggleAttribute("hidden", !this._rows.length); - } - - public clear() { - this._rows = []; - } - - public processState(state: FlashState) { - if (state.state === State.ERROR) { - this.addError(state.message); - return; - } - this.addRow(state); - if (state.state === State.FINISHED) { - this.addAction( - html`` - ); - } - } - - /** - * Add or replace a row. - */ - public addRow(row: Row) { - // If last entry has same ID, replace it. - if ( - row.state && - this._rows.length > 0 && - this._rows[this._rows.length - 1].state === row.state - ) { - const newRows = this._rows.slice(0, -1); - newRows.push(row); - this._rows = newRows; - } else { - this._rows = [...this._rows, row]; - } - } - - /** - * Add an error row - */ - public addError(message: Row["message"]) { - this.addRow({ message, error: true }); - } - - /** - * Add an action row - */ - public addAction(message: Row["message"]) { - this.addRow({ message, action: true }); - } - - /** - * Remove last row if state matches - */ - public removeRow(state: string) { - if ( - this._rows.length > 0 && - this._rows[this._rows.length - 1].state === state - ) { - this._rows = this._rows.slice(0, -1); - } - } - - static styles = css` - :host { - display: block; - margin-top: 16px; - padding: 12px 16px; - font-family: monospace; - background: var(--esp-tools-log-background, black); - color: var(--esp-tools-log-text-color, greenyellow); - font-size: 14px; - line-height: 19px; - } - - :host([hidden]) { - display: none; - } - - button { - background: none; - color: inherit; - border: none; - padding: 0; - font: inherit; - text-align: left; - text-decoration: underline; - cursor: pointer; - } - - .error { - color: var(--esp-tools-error-color, #dc3545); - } - - .error, - .action { - margin-top: 1em; - } - `; -} - -declare global { - interface HTMLElementTagNameMap { - "esp-web-flash-log": FlashLog; - } -} diff --git a/src/flash-progress.ts b/src/flash-progress.ts deleted file mode 100644 index 4a151b5..0000000 --- a/src/flash-progress.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { css, html, LitElement } from "lit"; -import { customElement, state } from "lit/decorators.js"; -import { FlashState, State } from "./const"; -import "@material/mwc-linear-progress"; -import { classMap } from "lit/directives/class-map.js"; - -@customElement("esp-web-flash-progress") -export class FlashProgress extends LitElement { - @state() private _state?: FlashState; - - @state() private _indeterminate = true; - - @state() private _progress = 0; - - public processState(state: FlashState) { - this._state = state; - if (this._state.state === State.WRITING) { - this._indeterminate = false; - this._progress = this._state.details.percentage / 100; - } - if (this._state.state === State.ERROR) { - this._indeterminate = false; - } - } - - public clear() { - this._state = undefined; - this._progress = 0; - this._indeterminate = true; - } - - protected render() { - if (!this._state) { - return; - } - return html`

- ${this._state.message} -

-

- ${this._state.manifest - ? html`${this._state.manifest.name}: ${this._state.chipFamily}` - : html` `} -

- `; - } - - static styles = css` - :host { - display: block; - --mdc-theme-primary: var(--esp-tools-progress-color, #03a9f4); - } - .error { - color: var(--esp-tools-error-color, #dc3545); - --mdc-theme-primary: var(--esp-tools-error-color, #dc3545); - } - .done { - color: var(--esp-tools-success-color, #28a745); - --mdc-theme-primary: var(--esp-tools-success-color, #28a745); - } - mwc-linear-progress { - text-align: left; - } - h2 { - margin: 16px 0 0; - } - p { - margin: 4px 0; - } - `; -} - -declare global { - interface HTMLElementTagNameMap { - "esp-web-flash-progress": FlashProgress; - } -} diff --git a/src/flash.ts b/src/flash.ts index 9d12fab..720d4f0 100644 --- a/src/flash.ts +++ b/src/flash.ts @@ -1,9 +1,17 @@ -import { connect, ESPLoader, Logger } from "esp-web-flasher"; -import { Build, FlashError, FlashState, Manifest, State } from "./const"; -import { fireEvent, getChipFamilyName, sleep } from "./util"; +import { ESPLoader, Logger } from "esp-web-flasher"; +import { + Build, + FlashError, + FlashState, + Manifest, + FlashStateType, +} from "./const"; +import { getChipFamilyName } from "./util/chip-family-name"; +import { sleep } from "./util/sleep"; export const flash = async ( - eventTarget: EventTarget, + onEvent: (state: FlashState) => void, + port: SerialPort, logger: Logger, manifestPath: string, eraseFirst: boolean @@ -12,47 +20,39 @@ export const flash = async ( let build: Build | undefined; let chipFamily: ReturnType; - const fireStateEvent = (stateUpdate: FlashState) => { - fireEvent(eventTarget, "state-changed", { + const fireStateEvent = (stateUpdate: FlashState) => + onEvent({ ...stateUpdate, manifest, build, chipFamily, }); - }; const manifestURL = new URL(manifestPath, location.toString()).toString(); const manifestProm = fetch(manifestURL).then( (resp): Promise => resp.json() ); - let esploader: ESPLoader | undefined; - - try { - esploader = await connect(logger); - } catch (err) { - // User pressed cancel on web serial - return; - } + const esploader = new ESPLoader(port, logger); // For debugging (window as any).esploader = esploader; fireStateEvent({ - state: State.INITIALIZING, + state: FlashStateType.INITIALIZING, message: "Initializing...", details: { done: false }, }); try { await esploader.initialize(); - } catch (err) { + } catch (err: any) { logger.error(err); if (esploader.connected) { fireStateEvent({ - state: State.ERROR, + state: FlashStateType.ERROR, message: - "Failed to initialize. Try resetting your device or holding the BOOT button while selecting your serial port.", + "Failed to initialize. Try resetting your device or holding the BOOT button while clicking INSTALL.", details: { error: FlashError.FAILED_INITIALIZING, details: err }, }); await esploader.disconnect(); @@ -63,22 +63,22 @@ export const flash = async ( chipFamily = getChipFamilyName(esploader); fireStateEvent({ - state: State.INITIALIZING, + state: FlashStateType.INITIALIZING, message: `Initialized. Found ${chipFamily}`, details: { done: true }, }); fireStateEvent({ - state: State.MANIFEST, + state: FlashStateType.MANIFEST, message: "Fetching manifest...", details: { done: false }, }); try { manifest = await manifestProm; - } catch (err) { + } catch (err: any) { fireStateEvent({ - state: State.ERROR, - message: `Unable to fetch manifest: ${err.message}`, + state: FlashStateType.ERROR, + message: `Unable to fetch manifest: ${err}`, details: { error: FlashError.FAILED_MANIFEST_FETCH, details: err }, }); await esploader.disconnect(); @@ -88,14 +88,14 @@ export const flash = async ( build = manifest.builds.find((b) => b.chipFamily === chipFamily); fireStateEvent({ - state: State.MANIFEST, + state: FlashStateType.MANIFEST, message: `Found manifest for ${manifest.name}`, details: { done: true }, }); if (!build) { fireStateEvent({ - state: State.ERROR, + state: FlashStateType.ERROR, message: `Your ${chipFamily} board is not supported.`, details: { error: FlashError.NOT_SUPPORTED, details: chipFamily }, }); @@ -104,7 +104,7 @@ export const flash = async ( } fireStateEvent({ - state: State.PREPARING, + state: FlashStateType.PREPARING, message: "Preparing installation...", details: { done: false }, }); @@ -131,11 +131,14 @@ export const flash = async ( const data = await prom; files.push(data); totalSize += data.byteLength; - } catch (err) { + } catch (err: any) { fireStateEvent({ - state: State.ERROR, - message: err, - details: { error: FlashError.FAILED_FIRMWARE_DOWNLOAD, details: err }, + state: FlashStateType.ERROR, + message: err.message, + details: { + error: FlashError.FAILED_FIRMWARE_DOWNLOAD, + details: err.message, + }, }); await esploader.disconnect(); return; @@ -143,20 +146,20 @@ export const flash = async ( } fireStateEvent({ - state: State.PREPARING, + state: FlashStateType.PREPARING, message: "Installation prepared", details: { done: true }, }); if (eraseFirst) { fireStateEvent({ - state: State.ERASING, + state: FlashStateType.ERASING, message: "Erasing device...", details: { done: false }, }); await espStub.eraseFlash(); fireStateEvent({ - state: State.ERASING, + state: FlashStateType.ERASING, message: "Device erased", details: { done: true }, }); @@ -165,7 +168,7 @@ export const flash = async ( let lastPct = 0; fireStateEvent({ - state: State.WRITING, + state: FlashStateType.WRITING, message: `Writing progress: ${lastPct}%`, details: { bytesTotal: totalSize, @@ -190,7 +193,7 @@ export const flash = async ( } lastPct = newPct; fireStateEvent({ - state: State.WRITING, + state: FlashStateType.WRITING, message: `Writing progress: ${newPct}%`, details: { bytesTotal: totalSize, @@ -202,10 +205,10 @@ export const flash = async ( part.offset, true ); - } catch (err) { + } catch (err: any) { fireStateEvent({ - state: State.ERROR, - message: err, + state: FlashStateType.ERROR, + message: err.message, details: { error: FlashError.WRITE_FAILED, details: err }, }); await esploader.disconnect(); @@ -215,7 +218,7 @@ export const flash = async ( } fireStateEvent({ - state: State.WRITING, + state: FlashStateType.WRITING, message: "Writing complete", details: { bytesTotal: totalSize, @@ -225,11 +228,13 @@ export const flash = async ( }); await sleep(100); - await esploader.hardReset(); + console.log("DISCONNECT"); await esploader.disconnect(); + console.log("HARD RESET"); + await esploader.hardReset(); fireStateEvent({ - state: State.FINISHED, + state: FlashStateType.FINISHED, message: "All done!", }); }; diff --git a/src/install-button.ts b/src/install-button.ts index ef7ca21..1abc54c 100644 --- a/src/install-button.ts +++ b/src/install-button.ts @@ -72,7 +72,7 @@ export class InstallButton extends HTMLElement { public renderRoot?: ShadowRoot; public static preload() { - import("./start-flash"); + import("./connect"); } public connectedCallback() { @@ -98,8 +98,8 @@ export class InstallButton extends HTMLElement { slot.addEventListener("click", async (ev) => { ev.preventDefault(); - const mod = await import("./start-flash"); - mod.startFlash(this); + const mod = await import("./connect"); + mod.connect(this); }); slot.name = "activate"; diff --git a/src/install-dialog.ts b/src/install-dialog.ts new file mode 100644 index 0000000..f29eccb --- /dev/null +++ b/src/install-dialog.ts @@ -0,0 +1,718 @@ +import { LitElement, html, PropertyValues, css, TemplateResult } from "lit"; +import { state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import "./components/ewt-dialog"; +import "./components/ewt-textfield"; +import "./components/ewt-button"; +import "./components/ewt-icon-button"; +import "./components/ewt-circular-progress"; +import type { EwtTextfield } from "./components/ewt-textfield"; +import { Logger, Manifest, FlashStateType, FlashState } from "./const.js"; +import { ImprovSerial } from "improv-wifi-serial-sdk/dist/serial"; +import { + ImprovSerialCurrentState, + ImprovSerialErrorState, + PortNotReady, +} from "improv-wifi-serial-sdk/dist/const"; +import { fireEvent } from "./util/fire-event"; +import { flash } from "./flash"; +import "./components/ewt-console"; +import { sleep } from "./util/sleep"; + +const ERROR_ICON = "⚠️"; +const OK_ICON = "🎉"; + +const messageTemplate = (icon: string, label: string) => html` +
+
${icon}
+ ${label} +
+`; + +class EwtInstallDialog extends LitElement { + public port!: SerialPort; + + public manifestPath!: string; + + public logger: Logger = console; + + private _manifest!: Manifest; + + private _info?: ImprovSerial["info"]; + + // null = NOT_SUPPORTED + @state() private _client?: ImprovSerial | null; + + @state() private _state: + | "ERROR" + | "DASHBOARD" + | "PROVISION" + | "INSTALL" + | "LOGS" = "DASHBOARD"; + + @state() private _installErase = false; + @state() private _installConfirmed = false; + @state() private _installState?: FlashState; + + @state() private _provisionForce = false; + + @state() private _error?: string; + + @state() private _busy = false; + + private _progressFeedback?: { + resolve: (_: unknown) => void; + reject: () => void; + }; + + 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) { + content = this._renderMessage(ERROR_ICON, this._error, true); + } else { + content = this._renderProgress("Connecting"); + hideActions = true; + } + } else if (this._state === "INSTALL") { + [heading, content, hideActions, allowClosing] = this._renderInstall(); + } else if (this._state === "ERROR") { + heading = "Error"; + content = this._renderMessage(ERROR_ICON, this._error!, true); + } else if (this._state === "DASHBOARD") { + [heading, content, hideActions, allowClosing] = this._renderDashboard(); + } else if (this._state === "PROVISION") { + [heading, content, hideActions] = this._renderProvision(); + } else if (this._state === "LOGS") { + [heading, content, hideActions] = this._renderLogs(); + } + + return html` + + ${heading && allowClosing + ? html` + + + + + + ` + : ""} + ${content!} + + `; + } + + _renderProgress(label: string | TemplateResult, progress?: number) { + return html` +
+
+ + ${progress !== undefined + ? html`
${progress}%
` + : ""} +
+ ${label} +
+ `; + } + _renderMessage(icon: string, label: string, showClose: boolean) { + return html` + ${messageTemplate(icon, label)} + ${showClose && + html` + + `} + `; + } + + _renderDashboard(): [string, TemplateResult, boolean, boolean] { + const heading = this._info!.name; + let content: TemplateResult; + let hideActions = true; + let allowClosing = true; + + const isSameFirmware = this._info!.firmware === this._manifest!.name; + const isSameVersion = + isSameFirmware && this._info!.version === this._manifest!.version; + + content = html` +
+ ${this._info!.firmware} ${this._info!.version} +
+
+ ${this._client!.nextUrl === undefined + ? "" + : html` +
+ + + +
+ `} +
+ { + this._state = "PROVISION"; + if ( + this._client!.state === ImprovSerialCurrentState.PROVISIONED + ) { + this._provisionForce = true; + } + }} + > +
+
+ this._startInstall(!isSameFirmware)} + .disabled=${isSameVersion} + > +
+
+ { + 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"; + }} + > +
+
+ `; + + 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("Trying to connect"), true]; + } + + if ( + !this._provisionForce && + this._client!.state === ImprovSerialCurrentState.PROVISIONED + ) { + heading = undefined; + content = html` + ${messageTemplate(OK_ICON, "Device connected to the network!")} + ${ + // If we went to provision after installing the firmware with a full erase, + // there is nothing left for the user, let them go to the device dashboard + // if available + this._installState?.state === FlashStateType.FINISHED && + this._installErase && + this._client!.nextUrl !== undefined + ? html` + { + this._state = "DASHBOARD"; + }} + > + + + { + this._state = "DASHBOARD"; + this._installState = undefined; + }} + > + ` + : html` + { + this._state = "DASHBOARD"; + }} + > + ` + } + `; + } else { + let error: string | undefined; + + switch (this._client!.error) { + case ImprovSerialErrorState.UNABLE_TO_CONNECT: + error = "Unable to connect"; + break; + + case ImprovSerialErrorState.NO_ERROR: + break; + + default: + error = `Unknown error (${this._client!.error})`; + } + content = html` +
+ Enter the credentials of the Wi-Fi network that you want your device + to connect to. +
+ ${error ? html`

${error}

` : ""} + + + + { + this._installState = undefined; + this._state = "DASHBOARD"; + }} + > + `; + } + return [heading, content, hideActions]; + } + + _renderInstall(): [string | undefined, TemplateResult, boolean, boolean] { + let heading: string | undefined = `Install ${this._manifest!.name}`; + let content: TemplateResult; + let hideActions = false; + let allowClosing = false; + + const isUpdate = !this._installErase && this._isUpdate; + + if (!this._installConfirmed) { + const action = isUpdate ? "update to" : "install"; + content = html` + ${isUpdate + ? html`Your device is running + ${this._info!.firmware} ${this._info!.version}.

` + : ""} + Do you want to ${action} + ${this._manifest!.name} ${this._manifest!.version}? + ${this._installErase + ? "All existing data will be erased from your device." + : ""} + + ${this._client + ? html` + { + this._state = "DASHBOARD"; + }} + > + ` + : html` + { + // In case it was null + this._client = undefined; + this._state = "LOGS"; + }} + > + `} + `; + allowClosing = !this._client; + } else if ( + !this._installState || + this._installState.state === FlashStateType.INITIALIZING || + this._installState.state === FlashStateType.MANIFEST || + this._installState.state === FlashStateType.PREPARING + ) { + content = this._renderProgress("Preparing installation"); + hideActions = true; + } else if (this._installState.state === FlashStateType.ERASING) { + content = this._renderProgress("Erasing"); + hideActions = true; + } else if (this._installState.state === FlashStateType.WRITING) { + content = this._renderProgress( + html` + ${this._installState.details.percentage > 3 + ? "" + : html`Installing
`} +
+ This will take + ${this._installState.chipFamily === "ESP8266" + ? "a minute" + : "2 minutes"}.
+ Keep this page visible to prevent slow down + `, + // Show as undeterminate under 3% or else we don't show any pixels + this._installState.details.percentage > 3 + ? this._installState.details.percentage + : undefined + ); + hideActions = true; + } else if (this._installState.state === FlashStateType.FINISHED) { + heading = undefined; + const supportsImprov = this._client !== null; + content = html` + ${messageTemplate(OK_ICON, "Installation complete!")} + { + this._state = this._installErase ? "PROVISION" : "DASHBOARD"; + }} + > + `; + } else if (this._installState.state === FlashStateType.ERROR) { + content = html` + ${messageTemplate(ERROR_ICON, this._installState.message)} + { + this._initialize(); + this._state = "DASHBOARD"; + this._installState = undefined; + }} + > + `; + } + return [heading, content!, hideActions, allowClosing]; + } + + _renderLogs(): [string | undefined, TemplateResult, boolean] { + let heading: string | undefined = `Logs`; + let content: TemplateResult; + let hideActions = false; + + content = html` + + { + await this.shadowRoot!.querySelector("ewt-console")!.disconnect(); + this._state = "DASHBOARD"; + this._initialize(); + }} + > + { + await this.shadowRoot!.querySelector("ewt-console")!.reset(); + }} + > + `; + + 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; + } + if (this._state !== "PROVISION") { + this._provisionForce = false; + } + } + + protected override firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + this._initialize(); + } + + protected override updated(changedProps: PropertyValues) { + super.updated(changedProps); + + if (!changedProps.has("_state")) { + return; + } + + this.setAttribute("state", this._state); + + if (this._state === "PROVISION") { + const textfield = this.shadowRoot!.querySelector("ewt-textfield"); + if (textfield) { + textfield.updateComplete.then(() => textfield.focus()); + } + } else if (this._state === "INSTALL") { + this._installConfirmed = false; + this._installState = undefined; + } + } + + private async _fetchManifest() { + if (this._manifest) { + return; + } + + const manifestURL = new URL( + this.manifestPath, + location.toString() + ).toString(); + this._manifest = await fetch(manifestURL).then( + (resp): Promise => resp.json() + ); + } + + private async _initialize() { + 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."; + } + + const manifestProm = this._fetchManifest(); + + const client = new ImprovSerial(this.port!, this.logger); + client.addEventListener("state-changed", () => { + this.requestUpdate(); + }); + client.addEventListener("error-changed", () => this.requestUpdate()); + try { + this._info = await client.initialize(); + 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); + // initialize is also called at the end of an installation + // When it can't detect improv (ie because install failed) + // We shouldn't reset settings but instead show the error + if (this._state !== "INSTALL") { + this._startInstall(true); + } + } + } + + try { + await manifestProm; + } catch (err: any) { + this._state = "ERROR"; + this._error = "Failed to download manifest"; + } + } + + 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; + + flash( + (state) => { + this._installState = state; + + if (state.state === FlashStateType.FINISHED) { + this._initialize().then(() => this.requestUpdate()); + } + }, + this.port, + this.logger, + this.manifestPath, + this._installErase + ); + } + + private async _doProvision() { + this._busy = true; + const ssid = ( + this.shadowRoot!.querySelector("ewt-textfield[name=ssid]") as EwtTextfield + ).value; + const password = ( + this.shadowRoot!.querySelector( + "ewt-textfield[name=password]" + ) as EwtTextfield + ).value; + try { + await this._client!.provision(ssid, password); + } catch (err: any) { + return; + } finally { + this._busy = false; + this._provisionForce = false; + } + } + + private _handleDisconnect = () => { + this._state = "ERROR"; + this._error = "Disconnected"; + }; + + private async _handleClose() { + if (this._progressFeedback) { + this._progressFeedback.reject(); + } + if (this._client) { + await this._closeClientWithoutEvents(this._client); + } + fireEvent(this, "closed" as any); + this.parentNode!.removeChild(this); + } + + private get _isUpdate() { + return this._info?.firmware === this._manifest!.name; + } + + private async _closeClientWithoutEvents(client: ImprovSerial) { + client.removeEventListener("disconnect", this._handleDisconnect); + await client.close(); + } + + static styles = css` + :host { + --mdc-dialog-max-width: 390px; + --mdc-theme-primary: var(--improv-primary-color, #03a9f4); + --mdc-theme-on-primary: var(--improv-on-primary-color, #fff); + } + ewt-icon-button { + position: absolute; + right: 4px; + top: 10px; + } + ewt-textfield { + display: block; + margin-top: 16px; + } + .center { + text-align: center; + } + .flash { + font-weight: bold; + margin-bottom: 1em; + background-color: var(--mdc-theme-primary); + padding: 8px 4px; + color: var(--mdc-theme-on-primary); + border-radius: 4px; + text-align: center; + } + .dashboard-buttons { + margin: 16px 0 -16px -8px; + } + .dashboard-buttons div { + display: block; + margin: 4px 0; + } + ewt-circular-progress { + margin-bottom: 16px; + } + a.has-button { + text-decoration: none; + } + .icon { + font-size: 50px; + line-height: 80px; + color: black; + } + .error { + color: #db4437; + } + 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 { + display: block; + width: calc(80vw - 48px); + height: 80vh; + } + `; +} + +customElements.define("ewt-install-dialog", EwtInstallDialog); + +declare global { + interface HTMLElementTagNameMap { + "ewt-install-dialog": EwtInstallDialog; + } +} diff --git a/src/start-flash.ts b/src/start-flash.ts deleted file mode 100644 index 309a361..0000000 --- a/src/start-flash.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { flash } from "./flash"; -import "./flash-log"; -import "./flash-progress"; -import type { FlashLog } from "./flash-log"; -import type { FlashProgress } from "./flash-progress"; -import type { InstallButton } from "./install-button"; -import { State } from "./const"; - -interface FlashData { - stateListenerAdded: boolean; - logEl: FlashLog | undefined; - progressEl: FlashProgress | undefined; - improvEl: HTMLElement | undefined; -} - -const getData = (button: InstallButton): FlashData => { - if (!("_flashData" in button)) { - (button as any)._flashData = { - stateListenerAdded: false, - logEl: undefined, - progressEl: undefined, - improvEl: undefined, - } as FlashData; - } - - return (button as any)._flashData as FlashData; -}; - -const addElement = ( - button: InstallButton, - element: T -): T => { - button.renderRoot!.append(element); - return element; -}; - -export const startFlash = async (button: InstallButton) => { - if (button.hasAttribute("active")) { - return; - } - - const manifest = button.manifest || button.getAttribute("manifest"); - if (!manifest) { - alert("No manifest defined!"); - return; - } - - const data = getData(button); - - let hasImprov = false; - - if (!data.stateListenerAdded) { - data.stateListenerAdded = true; - button.addEventListener("state-changed", (ev) => { - const state = (button.state = ev.detail); - if (state.state === State.INITIALIZING) { - button.toggleAttribute("active", true); - } else if (state.state === State.MANIFEST && state.build?.improv) { - hasImprov = true; - // @ts-ignore - // preload improv button - import("https://www.improv-wifi.com/sdk-js/launch-button.js"); - } else if (state.state === State.FINISHED) { - button.toggleAttribute("active", false); - if (hasImprov) { - startImprov(button); - } - } else if (state.state === State.ERROR) { - button.toggleAttribute("active", false); - } - data.progressEl?.processState(ev.detail); - data.logEl?.processState(ev.detail); - }); - } - - const logConsole = button.logConsole || button.hasAttribute("log-console"); - const showLog = button.showLog || button.hasAttribute("show-log"); - const showProgress = - !showLog && - button.hideProgress !== true && - !button.hasAttribute("hide-progress"); - - if (showLog && !data.logEl) { - data.logEl = addElement( - button, - document.createElement("esp-web-flash-log") - ); - } else if (!showLog && data.logEl) { - data.logEl.remove(); - data.logEl = undefined; - } - - if (showProgress && !data.progressEl) { - data.progressEl = addElement( - button, - document.createElement("esp-web-flash-progress") - ); - } else if (!showProgress && data.progressEl) { - data.progressEl.remove(); - data.progressEl = undefined; - } - - data.logEl?.clear(); - data.progressEl?.clear(); - data.improvEl?.classList.toggle("hidden", true); - - flash( - button, - logConsole - ? console - : { - log: () => {}, - error: () => {}, - debug: () => {}, - }, - manifest, - button.eraseFirst !== undefined - ? button.eraseFirst - : button.hasAttribute("erase-first") - ); -}; - -const startImprov = async (button: InstallButton) => { - // @ts-ignore - await import("https://www.improv-wifi.com/sdk-js/launch-button.js"); - - const improvButtonConstructor = customElements.get( - "improv-wifi-launch-button" - ); - - if ( - !improvButtonConstructor.isSupported || - !improvButtonConstructor.isAllowed - ) { - return; - } - - const data = getData(button); - - if (!data.improvEl) { - data.improvEl = document.createElement("improv-wifi-launch-button"); - data.improvEl.addEventListener("state-changed", (ev: any) => { - if (ev.detail.state === "PROVISIONED") { - data.improvEl!.classList.toggle("hidden", true); - } - }); - const improvButton = document.createElement("button"); - improvButton.slot = "activate"; - improvButton.textContent = "CLICK HERE TO FINISH SETTING UP YOUR DEVICE"; - data.improvEl.appendChild(improvButton); - addElement(button, data.improvEl); - } - data.improvEl.classList.toggle("hidden", false); -}; diff --git a/src/util.ts b/src/util.ts deleted file mode 100644 index c6cdebd..0000000 --- a/src/util.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - CHIP_FAMILY_ESP32, - CHIP_FAMILY_ESP32S2, - CHIP_FAMILY_ESP8266, - CHIP_FAMILY_ESP32C3, - ESPLoader, -} from "esp-web-flasher"; -import type { BaseFlashState } from "./const"; - -export const getChipFamilyName = ( - esploader: ESPLoader -): NonNullable => { - switch (esploader.chipFamily) { - case CHIP_FAMILY_ESP32: - return "ESP32"; - case CHIP_FAMILY_ESP8266: - return "ESP8266"; - case CHIP_FAMILY_ESP32S2: - return "ESP32-S2"; - case CHIP_FAMILY_ESP32C3: - return "ESP32-C3"; - default: - return "Unknown Chip"; - } -}; - -export const sleep = (time: number) => - new Promise((resolve) => setTimeout(resolve, time)); - -export const fireEvent = ( - eventTarget: EventTarget, - type: Event, - // @ts-ignore - detail?: HTMLElementEventMap[Event]["detail"], - options?: { - bubbles?: boolean; - cancelable?: boolean; - composed?: boolean; - } -): void => { - options = options || {}; - const event = new CustomEvent(type, { - bubbles: options.bubbles === undefined ? true : options.bubbles, - cancelable: Boolean(options.cancelable), - composed: options.composed === undefined ? true : options.composed, - detail, - }); - eventTarget.dispatchEvent(event); -}; diff --git a/src/util/chip-family-name.ts b/src/util/chip-family-name.ts new file mode 100644 index 0000000..53131b0 --- /dev/null +++ b/src/util/chip-family-name.ts @@ -0,0 +1,25 @@ +import { + CHIP_FAMILY_ESP32, + CHIP_FAMILY_ESP32S2, + CHIP_FAMILY_ESP8266, + CHIP_FAMILY_ESP32C3, + ESPLoader, +} from "esp-web-flasher"; +import type { BaseFlashState } from "../const"; + +export const getChipFamilyName = ( + esploader: ESPLoader +): NonNullable => { + switch (esploader.chipFamily) { + case CHIP_FAMILY_ESP32: + return "ESP32"; + case CHIP_FAMILY_ESP8266: + return "ESP8266"; + case CHIP_FAMILY_ESP32S2: + return "ESP32-S2"; + case CHIP_FAMILY_ESP32C3: + return "ESP32-C3"; + default: + return "Unknown Chip"; + } +}; diff --git a/src/util/console-color.ts b/src/util/console-color.ts new file mode 100644 index 0000000..5f02443 --- /dev/null +++ b/src/util/console-color.ts @@ -0,0 +1,188 @@ +interface ConsoleState { + bold: boolean; + italic: boolean; + underline: boolean; + strikethrough: boolean; + foregroundColor: string | null; + backgroundColor: string | null; + // carriageReturn: boolean; + secret: boolean; +} + +export class ColoredConsole { + public state: ConsoleState = { + bold: false, + italic: false, + underline: false, + strikethrough: false, + foregroundColor: null, + backgroundColor: null, + // carriageReturn: false, + secret: false, + }; + + constructor(public targetElement: HTMLElement) {} + + addLine(line: string) { + const re = /(?:\033|\\033)(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g; + let i = 0; + + // This doesn't work for some reason + // if (this.state.carriageReturn) { + // if (line !== "\n") { + // // don't remove if \r\n + // this.targetElement.removeChild(this.targetElement.lastChild!); + // } + // this.state.carriageReturn = false; + // } + + // if (line.includes("\r")) { + // this.state.carriageReturn = true; + // } + + const lineSpan = document.createElement("span"); + lineSpan.classList.add("line"); + this.targetElement.appendChild(lineSpan); + + const addSpan = (content: string) => { + if (content === "") return; + + const span = document.createElement("span"); + if (this.state.bold) span.classList.add("log-bold"); + if (this.state.italic) span.classList.add("log-italic"); + if (this.state.underline) span.classList.add("log-underline"); + if (this.state.strikethrough) span.classList.add("log-strikethrough"); + if (this.state.secret) span.classList.add("log-secret"); + if (this.state.foregroundColor !== null) + span.classList.add(`log-fg-${this.state.foregroundColor}`); + if (this.state.backgroundColor !== null) + span.classList.add(`log-bg-${this.state.backgroundColor}`); + span.appendChild(document.createTextNode(content)); + lineSpan.appendChild(span); + + if (this.state.secret) { + const redacted = document.createElement("span"); + redacted.classList.add("log-secret-redacted"); + redacted.appendChild(document.createTextNode("[redacted]")); + lineSpan.appendChild(redacted); + } + }; + + while (true) { + const match = re.exec(line); + if (match === null) break; + + const j = match.index; + addSpan(line.substring(i, j)); + i = j + match[0].length; + + if (match[1] === undefined) continue; + + for (const colorCode of match[1].split(";")) { + switch (parseInt(colorCode)) { + case 0: + // reset + this.state.bold = false; + this.state.italic = false; + this.state.underline = false; + this.state.strikethrough = false; + this.state.foregroundColor = null; + this.state.backgroundColor = null; + this.state.secret = false; + break; + case 1: + this.state.bold = true; + break; + case 3: + this.state.italic = true; + break; + case 4: + this.state.underline = true; + break; + case 5: + this.state.secret = true; + break; + case 6: + this.state.secret = false; + break; + case 9: + this.state.strikethrough = true; + break; + case 22: + this.state.bold = false; + break; + case 23: + this.state.italic = false; + break; + case 24: + this.state.underline = false; + break; + case 29: + this.state.strikethrough = false; + break; + case 30: + this.state.foregroundColor = "black"; + break; + case 31: + this.state.foregroundColor = "red"; + break; + case 32: + this.state.foregroundColor = "green"; + break; + case 33: + this.state.foregroundColor = "yellow"; + break; + case 34: + this.state.foregroundColor = "blue"; + break; + case 35: + this.state.foregroundColor = "magenta"; + break; + case 36: + this.state.foregroundColor = "cyan"; + break; + case 37: + this.state.foregroundColor = "white"; + break; + case 39: + this.state.foregroundColor = null; + break; + case 41: + this.state.backgroundColor = "red"; + break; + case 42: + this.state.backgroundColor = "green"; + break; + case 43: + this.state.backgroundColor = "yellow"; + break; + case 44: + this.state.backgroundColor = "blue"; + break; + case 45: + this.state.backgroundColor = "magenta"; + break; + case 46: + this.state.backgroundColor = "cyan"; + break; + case 47: + this.state.backgroundColor = "white"; + break; + case 40: + case 49: + this.state.backgroundColor = null; + break; + } + } + } + addSpan(line.substring(i)); + + if ( + this.targetElement.scrollTop + 56 >= + this.targetElement.scrollHeight - this.targetElement.offsetHeight + ) { + // at bottom + this.targetElement.scrollTop = this.targetElement.scrollHeight; + } + } +} diff --git a/src/util/fire-event.ts b/src/util/fire-event.ts new file mode 100644 index 0000000..184a38b --- /dev/null +++ b/src/util/fire-event.ts @@ -0,0 +1,20 @@ +export const fireEvent = ( + eventTarget: EventTarget, + type: Event, + // @ts-ignore + detail?: HTMLElementEventMap[Event]["detail"], + options?: { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; + } +): void => { + options = options || {}; + const event = new CustomEvent(type, { + bubbles: options.bubbles === undefined ? true : options.bubbles, + cancelable: Boolean(options.cancelable), + composed: options.composed === undefined ? true : options.composed, + detail, + }); + eventTarget.dispatchEvent(event); +}; diff --git a/src/util/line-break-transformer.ts b/src/util/line-break-transformer.ts new file mode 100644 index 0000000..71a9323 --- /dev/null +++ b/src/util/line-break-transformer.ts @@ -0,0 +1,20 @@ +export class LineBreakTransformer implements Transformer { + private chunks = ""; + + transform( + chunk: string, + controller: TransformStreamDefaultController + ) { + // Append new chunks to existing chunks. + this.chunks += chunk; + // For each line breaks in chunks, send the parsed lines out. + const lines = this.chunks.split("\r\n"); + this.chunks = lines.pop()!; + lines.forEach((line) => controller.enqueue(line + "\r\n")); + } + + flush(controller: TransformStreamDefaultController) { + // When the stream is closed, flush any remaining chunks out. + controller.enqueue(this.chunks); + } +} diff --git a/src/util/sleep.ts b/src/util/sleep.ts new file mode 100644 index 0000000..0d01adb --- /dev/null +++ b/src/util/sleep.ts @@ -0,0 +1,2 @@ +export const sleep = (time: number) => + new Promise((resolve) => setTimeout(resolve, time)); diff --git a/static/firmware_build/esp8266.bin b/static/firmware_build/esp8266.bin index 5ee57f3..7040b45 100644 Binary files a/static/firmware_build/esp8266.bin and b/static/firmware_build/esp8266.bin differ diff --git a/static/firmware_build/firmware.bin b/static/firmware_build/firmware.bin index 3b9ff9d..3f3e092 100644 Binary files a/static/firmware_build/firmware.bin and b/static/firmware_build/firmware.bin differ diff --git a/static/firmware_build/manifest.json b/static/firmware_build/manifest.json index 054248d..56b1752 100644 --- a/static/firmware_build/manifest.json +++ b/static/firmware_build/manifest.json @@ -1,9 +1,9 @@ { - "name": "ESP Web Tools demo powered by ESPHome", + "name": "ESPHome", + "version": "2021.11.0-dev", "builds": [ { "chipFamily": "ESP32", - "improv": true, "parts": [ { "path": "bootloader.bin", "offset": 4096 }, { "path": "partitions.bin", "offset": 32768 }, diff --git a/static/firmware_build/partitions.bin b/static/firmware_build/partitions.bin index e648fa3..711da8c 100644 Binary files a/static/firmware_build/partitions.bin and b/static/firmware_build/partitions.bin differ