tasmota-esp-web-tools 9.0.2 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/components/ewt-littlefs-manager.d.ts +43 -0
  2. package/dist/components/ewt-littlefs-manager.js +819 -0
  3. package/dist/flash.js +4 -30
  4. package/dist/install-dialog.d.ts +11 -0
  5. package/dist/install-dialog.js +336 -0
  6. package/dist/partition.d.ts +26 -0
  7. package/dist/partition.js +129 -0
  8. package/dist/util/esp32s2-reconnect.d.ts +22 -0
  9. package/dist/util/esp32s2-reconnect.js +45 -0
  10. package/dist/util/partition.d.ts +4 -0
  11. package/dist/util/partition.js +59 -0
  12. package/{js/modules/index-BK6drzlv.js → dist/web/index-t2Vsxqjj.js} +1 -1
  13. package/dist/web/install-button.js +1 -1
  14. package/dist/web/{install-dialog-Xo7JovVA.js → install-dialog-CVebVk1R.js} +515 -36
  15. package/dist/web/{styles-ntwzvT31.js → styles-CWxbqh-J.js} +1 -1
  16. package/dist/web/wasm/littlefs/index.d.ts +91 -0
  17. package/dist/web/wasm/littlefs/index.js +611 -0
  18. package/dist/web/wasm/littlefs/littlefs.js +505 -0
  19. package/dist/web/wasm/littlefs/littlefs.wasm +0 -0
  20. package/{dist/web/index-BK6drzlv.js → js/modules/index-t2Vsxqjj.js} +1 -1
  21. package/js/modules/install-button.js +1 -1
  22. package/js/modules/{install-dialog-Xo7JovVA.js → install-dialog-CVebVk1R.js} +515 -36
  23. package/js/modules/{styles-ntwzvT31.js → styles-CWxbqh-J.js} +1 -1
  24. package/js/modules/wasm/littlefs/index.d.ts +91 -0
  25. package/js/modules/wasm/littlefs/index.js +611 -0
  26. package/js/modules/wasm/littlefs/littlefs.js +505 -0
  27. package/js/modules/wasm/littlefs/littlefs.wasm +0 -0
  28. package/package.json +3 -2
  29. package/.devcontainer/Dockerfile +0 -16
  30. package/.devcontainer/devcontainer.json +0 -21
  31. package/.github/dependabot.yml +0 -10
  32. package/.github/workflows/build_upload.yml +0 -78
  33. package/.github/workflows/ci.yml +0 -24
  34. package/.prettierignore +0 -1
  35. package/BAUD_RATE_CONFIGURATION.md +0 -291
  36. package/BAUD_RATE_IMPROVEMENT.md +0 -195
  37. package/CHANGELOG_IMPROVEMENTS.md +0 -246
  38. package/ESP32_S2_USB_RECONNECT.md +0 -108
  39. package/FINAL_SUMMARY.md +0 -153
  40. package/example-baud-rate.html +0 -276
  41. package/manifest-example-p4-variants.json +0 -61
  42. package/rollup.config.mjs +0 -36
  43. package/script/build +0 -8
  44. package/script/develop +0 -17
  45. package/src/components/ewt-button.ts +0 -25
  46. package/src/components/ewt-checkbox.ts +0 -14
  47. package/src/components/ewt-circular-progress.ts +0 -14
  48. package/src/components/ewt-console.ts +0 -166
  49. package/src/components/ewt-dialog.ts +0 -22
  50. package/src/components/ewt-formfield.ts +0 -14
  51. package/src/components/ewt-icon-button.ts +0 -14
  52. package/src/components/ewt-list-item.ts +0 -14
  53. package/src/components/ewt-select.ts +0 -23
  54. package/src/components/ewt-textfield.ts +0 -23
  55. package/src/components/svg.ts +0 -27
  56. package/src/connect.ts +0 -55
  57. package/src/const.ts +0 -122
  58. package/src/flash.ts +0 -361
  59. package/src/install-button.ts +0 -126
  60. package/src/install-dialog.ts +0 -1114
  61. package/src/no-port-picked/index.ts +0 -10
  62. package/src/no-port-picked/no-port-picked-dialog.ts +0 -167
  63. package/src/pages/ewt-page-message.ts +0 -39
  64. package/src/pages/ewt-page-progress.ts +0 -44
  65. package/src/styles.ts +0 -34
  66. package/src/util/chip-family-name.ts +0 -46
  67. package/src/util/console-color.ts +0 -284
  68. package/src/util/file-download.ts +0 -17
  69. package/src/util/fire-event.ts +0 -20
  70. package/src/util/line-break-transformer.ts +0 -20
  71. package/src/util/manifest.ts +0 -18
  72. package/src/util/sleep.ts +0 -2
  73. package/static/logos/2smart.png +0 -0
  74. package/static/logos/canairio.png +0 -0
  75. package/static/logos/clockwise.png +0 -0
  76. package/static/logos/espeasy.png +0 -0
  77. package/static/logos/esphome.svg +0 -1
  78. package/static/logos/luciferin_logo.png +0 -0
  79. package/static/logos/squeezelite-esp32.png +0 -0
  80. package/static/logos/tasmota.svg +0 -1
  81. package/static/logos/wled.png +0 -0
  82. package/static/screenshots/dashboard.png +0 -0
  83. package/static/screenshots/logs.png +0 -0
  84. package/static/social.png +0 -0
  85. package/tsconfig.json +0 -20
@@ -1,1114 +0,0 @@
1
- import { LitElement, html, PropertyValues, css, TemplateResult } from "lit";
2
- import { state } from "lit/decorators.js";
3
- import "./components/ewt-button";
4
- import "./components/ewt-checkbox";
5
- import "./components/ewt-console";
6
- import "./components/ewt-dialog";
7
- import "./components/ewt-formfield";
8
- import "./components/ewt-icon-button";
9
- import "./components/ewt-textfield";
10
- import type { EwtTextfield } from "./components/ewt-textfield";
11
- import "./components/ewt-select";
12
- import "./components/ewt-list-item";
13
- import "./pages/ewt-page-progress";
14
- import "./pages/ewt-page-message";
15
- import { chipIcon, closeIcon, firmwareIcon } from "./components/svg";
16
- import { Logger, Manifest, FlashStateType, FlashState } from "./const.js";
17
- import { ImprovSerial, Ssid } from "improv-wifi-serial-sdk/dist/serial";
18
- import {
19
- ImprovSerialCurrentState,
20
- ImprovSerialErrorState,
21
- PortNotReady,
22
- } from "improv-wifi-serial-sdk/dist/const";
23
- import { flash } from "./flash";
24
- import { textDownload } from "./util/file-download";
25
- import { fireEvent } from "./util/fire-event";
26
- import { sleep } from "./util/sleep";
27
- import { downloadManifest } from "./util/manifest";
28
- import { dialogStyles } from "./styles";
29
-
30
- const ERROR_ICON = "⚠️";
31
- const OK_ICON = "🎉";
32
-
33
- export class EwtInstallDialog extends LitElement {
34
- public port!: SerialPort;
35
-
36
- public manifestPath!: string;
37
-
38
- public firmwareFile?: File;
39
-
40
- public baudRate?: number;
41
-
42
- public logger: Logger = console;
43
-
44
- public overrides?: {
45
- checkSameFirmware?: (
46
- manifest: Manifest,
47
- deviceImprov: ImprovSerial["info"],
48
- ) => boolean;
49
- };
50
-
51
- private _manifest!: Manifest;
52
-
53
- private _info?: ImprovSerial["info"];
54
-
55
- // null = NOT_SUPPORTED
56
- @state() private _client?: ImprovSerial | null;
57
-
58
- @state() private _state:
59
- | "ERROR"
60
- | "DASHBOARD"
61
- | "PROVISION"
62
- | "INSTALL"
63
- | "ASK_ERASE"
64
- | "LOGS" = "DASHBOARD";
65
-
66
- @state() private _installErase = false;
67
- @state() private _installConfirmed = false;
68
- @state() private _installState?: FlashState;
69
-
70
- @state() private _provisionForce = false;
71
- private _wasProvisioned = false;
72
-
73
- @state() private _error?: string;
74
-
75
- @state() private _busy = false;
76
-
77
- // Prevent recursive ESP32-S2 reconnect attempts
78
- private _esp32s2ReconnectInProgress = false;
79
-
80
- // undefined = not loaded
81
- // null = not available
82
- @state() private _ssids?: Ssid[] | null;
83
-
84
- // -1 = custom
85
- @state() private _selectedSsid = -1;
86
-
87
- protected render() {
88
- if (!this.port) {
89
- return html``;
90
- }
91
- let heading: string | undefined;
92
- let content: TemplateResult;
93
- let hideActions = false;
94
- let allowClosing = false;
95
-
96
- // During installation phase we temporarily remove the client
97
- if (
98
- this._client === undefined &&
99
- this._state !== "INSTALL" &&
100
- this._state !== "LOGS"
101
- ) {
102
- if (this._error) {
103
- [heading, content, hideActions] = this._renderError(this._error);
104
- } else {
105
- content = this._renderProgress("Connecting");
106
- hideActions = true;
107
- }
108
- } else if (this._state === "INSTALL") {
109
- [heading, content, hideActions, allowClosing] = this._renderInstall();
110
- } else if (this._state === "ASK_ERASE") {
111
- [heading, content] = this._renderAskErase();
112
- } else if (this._state === "ERROR") {
113
- [heading, content, hideActions] = this._renderError(this._error!);
114
- } else if (this._state === "DASHBOARD") {
115
- [heading, content, hideActions, allowClosing] = this._client
116
- ? this._renderDashboard()
117
- : this._renderDashboardNoImprov();
118
- } else if (this._state === "PROVISION") {
119
- [heading, content, hideActions] = this._renderProvision();
120
- } else if (this._state === "LOGS") {
121
- [heading, content, hideActions] = this._renderLogs();
122
- }
123
-
124
- return html`
125
- <ewt-dialog
126
- open
127
- .heading=${heading!}
128
- scrimClickAction
129
- @closed=${this._handleClose}
130
- .hideActions=${hideActions}
131
- >
132
- ${heading && allowClosing
133
- ? html`
134
- <ewt-icon-button dialogAction="close">
135
- ${closeIcon}
136
- </ewt-icon-button>
137
- `
138
- : ""}
139
- ${content!}
140
- </ewt-dialog>
141
- `;
142
- }
143
-
144
- _renderProgress(label: string | TemplateResult, progress?: number) {
145
- return html`
146
- <ewt-page-progress
147
- .label=${label}
148
- .progress=${progress}
149
- ></ewt-page-progress>
150
- `;
151
- }
152
-
153
- _renderError(label: string): [string, TemplateResult, boolean] {
154
- const heading = "Error";
155
- const content = html`
156
- <ewt-page-message .icon=${ERROR_ICON} .label=${label}></ewt-page-message>
157
- <ewt-button
158
- slot="primaryAction"
159
- dialogAction="ok"
160
- label="Close"
161
- ></ewt-button>
162
- `;
163
- const hideActions = false;
164
- return [heading, content, hideActions];
165
- }
166
-
167
- _renderDashboard(): [string, TemplateResult, boolean, boolean] {
168
- const heading = this._info!.name;
169
- let content: TemplateResult;
170
- let hideActions = true;
171
- let allowClosing = true;
172
-
173
- content = html`
174
- <div class="table-row">
175
- ${firmwareIcon}
176
- <div>${this._info!.firmware}&nbsp;${this._info!.version}</div>
177
- </div>
178
- <div class="table-row last">
179
- ${chipIcon}
180
- <div>${this._info!.chipFamily}</div>
181
- </div>
182
- <div class="dashboard-buttons">
183
- ${!this._isSameVersion
184
- ? html`
185
- <div>
186
- <ewt-button
187
- text-left
188
- .label=${!this._isSameFirmware
189
- ? `Install ${this._manifest.name}`
190
- : `Update ${this._manifest.name}`}
191
- @click=${() => {
192
- if (this._isSameFirmware) {
193
- this._startInstall(false);
194
- } else if (this._manifest.new_install_prompt_erase) {
195
- this._state = "ASK_ERASE";
196
- } else {
197
- this._startInstall(true);
198
- }
199
- }}
200
- ></ewt-button>
201
- </div>
202
- `
203
- : ""}
204
- ${this._client!.nextUrl === undefined
205
- ? ""
206
- : html`
207
- <div>
208
- <a
209
- href=${this._client!.nextUrl}
210
- class="has-button"
211
- target="_blank"
212
- >
213
- <ewt-button label="Visit Device"></ewt-button>
214
- </a>
215
- </div>
216
- `}
217
- ${!this._manifest.home_assistant_domain ||
218
- this._client!.state !== ImprovSerialCurrentState.PROVISIONED
219
- ? ""
220
- : html`
221
- <div>
222
- <a
223
- href=${`https://my.home-assistant.io/redirect/config_flow_start/?domain=${this._manifest.home_assistant_domain}`}
224
- class="has-button"
225
- target="_blank"
226
- >
227
- <ewt-button label="Add to Home Assistant"></ewt-button>
228
- </a>
229
- </div>
230
- `}
231
- <div>
232
- <ewt-button
233
- .label=${this._client!.state === ImprovSerialCurrentState.READY
234
- ? "Connect to Wi-Fi"
235
- : "Change Wi-Fi"}
236
- @click=${() => {
237
- this._state = "PROVISION";
238
- if (
239
- this._client!.state === ImprovSerialCurrentState.PROVISIONED
240
- ) {
241
- this._provisionForce = true;
242
- }
243
- }}
244
- ></ewt-button>
245
- </div>
246
- <div>
247
- <ewt-button
248
- label="Logs & Console"
249
- @click=${async () => {
250
- const client = this._client;
251
- if (client) {
252
- await this._closeClientWithoutEvents(client);
253
- await sleep(100);
254
- }
255
- // Also set `null` back to undefined.
256
- this._client = undefined;
257
- this._state = "LOGS";
258
- }}
259
- ></ewt-button>
260
- </div>
261
- ${this._isSameFirmware && this._manifest.funding_url
262
- ? html`
263
- <div>
264
- <a
265
- class="button"
266
- href=${this._manifest.funding_url}
267
- target="_blank"
268
- >
269
- <ewt-button label="Fund Development"></ewt-button>
270
- </a>
271
- </div>
272
- `
273
- : ""}
274
- ${this._isSameVersion
275
- ? html`
276
- <div>
277
- <ewt-button
278
- class="danger"
279
- label="Erase User Data"
280
- @click=${() => this._startInstall(true)}
281
- ></ewt-button>
282
- </div>
283
- `
284
- : ""}
285
- </div>
286
- `;
287
-
288
- return [heading, content, hideActions, allowClosing];
289
- }
290
- _renderDashboardNoImprov(): [string, TemplateResult, boolean, boolean] {
291
- const heading = "Device Dashboard";
292
- let content: TemplateResult;
293
- let hideActions = true;
294
- let allowClosing = true;
295
-
296
- content = html`
297
- <div class="dashboard-buttons">
298
- <div>
299
- <ewt-button
300
- text-left
301
- .label=${`Install ${this._manifest.name}`}
302
- @click=${() => {
303
- if (this._manifest.new_install_prompt_erase) {
304
- this._state = "ASK_ERASE";
305
- } else {
306
- // Default is to erase a device that does not support Improv Serial
307
- this._startInstall(true);
308
- }
309
- }}
310
- ></ewt-button>
311
- </div>
312
-
313
- <div>
314
- <ewt-button
315
- label="Logs & Console"
316
- @click=${async () => {
317
- // Also set `null` back to undefined.
318
- this._client = undefined;
319
- this._state = "LOGS";
320
- }}
321
- ></ewt-button>
322
- </div>
323
- </div>
324
- `;
325
-
326
- return [heading, content, hideActions, allowClosing];
327
- }
328
-
329
- _renderProvision(): [string | undefined, TemplateResult, boolean] {
330
- let heading: string | undefined = "Configure Wi-Fi";
331
- let content: TemplateResult;
332
- let hideActions = false;
333
-
334
- if (this._busy) {
335
- return [
336
- heading,
337
- this._renderProgress(
338
- this._ssids === undefined
339
- ? "Scanning for networks"
340
- : "Trying to connect",
341
- ),
342
- true,
343
- ];
344
- }
345
-
346
- if (
347
- !this._provisionForce &&
348
- this._client!.state === ImprovSerialCurrentState.PROVISIONED
349
- ) {
350
- heading = undefined;
351
- const showSetupLinks =
352
- !this._wasProvisioned &&
353
- (this._client!.nextUrl !== undefined ||
354
- "home_assistant_domain" in this._manifest);
355
- hideActions = showSetupLinks;
356
- content = html`
357
- <ewt-page-message
358
- .icon=${OK_ICON}
359
- label="Device connected to the network!"
360
- ></ewt-page-message>
361
- ${showSetupLinks
362
- ? html`
363
- <div class="dashboard-buttons">
364
- ${this._client!.nextUrl === undefined
365
- ? ""
366
- : html`
367
- <div>
368
- <a
369
- href=${this._client!.nextUrl}
370
- class="has-button"
371
- target="_blank"
372
- @click=${() => {
373
- this._state = "DASHBOARD";
374
- }}
375
- >
376
- <ewt-button label="Visit Device"></ewt-button>
377
- </a>
378
- </div>
379
- `}
380
- ${!this._manifest.home_assistant_domain
381
- ? ""
382
- : html`
383
- <div>
384
- <a
385
- href=${`https://my.home-assistant.io/redirect/config_flow_start/?domain=${this._manifest.home_assistant_domain}`}
386
- class="has-button"
387
- target="_blank"
388
- @click=${() => {
389
- this._state = "DASHBOARD";
390
- }}
391
- >
392
- <ewt-button
393
- label="Add to Home Assistant"
394
- ></ewt-button>
395
- </a>
396
- </div>
397
- `}
398
- <div>
399
- <ewt-button
400
- label="Skip"
401
- @click=${() => {
402
- this._state = "DASHBOARD";
403
- }}
404
- ></ewt-button>
405
- </div>
406
- </div>
407
- `
408
- : html`
409
- <ewt-button
410
- slot="primaryAction"
411
- label="Continue"
412
- @click=${() => {
413
- this._state = "DASHBOARD";
414
- }}
415
- ></ewt-button>
416
- `}
417
- `;
418
- } else {
419
- let error: string | undefined;
420
-
421
- switch (this._client!.error) {
422
- case ImprovSerialErrorState.UNABLE_TO_CONNECT:
423
- error = "Unable to connect";
424
- break;
425
-
426
- case ImprovSerialErrorState.NO_ERROR:
427
- // Happens when list SSIDs not supported.
428
- case ImprovSerialErrorState.UNKNOWN_RPC_COMMAND:
429
- break;
430
-
431
- default:
432
- error = `Unknown error (${this._client!.error})`;
433
- }
434
- content = html`
435
- <div>
436
- Enter the credentials of the Wi-Fi network that you want your device
437
- to connect to.
438
- </div>
439
- ${error ? html`<p class="error">${error}</p>` : ""}
440
- ${this._ssids !== null
441
- ? html`
442
- <ewt-select
443
- fixedMenuPosition
444
- label="Network"
445
- @selected=${(ev: { detail: { index: number } }) => {
446
- const index = ev.detail.index;
447
- // The "Join Other" item is always the last item.
448
- this._selectedSsid =
449
- index === this._ssids!.length ? -1 : index;
450
- }}
451
- @closed=${(ev: Event) => ev.stopPropagation()}
452
- >
453
- ${this._ssids!.map(
454
- (info, idx) => html`
455
- <ewt-list-item
456
- .selected=${this._selectedSsid === idx}
457
- value=${idx}
458
- >
459
- ${info.name}
460
- </ewt-list-item>
461
- `,
462
- )}
463
- <ewt-list-item
464
- .selected=${this._selectedSsid === -1}
465
- value="-1"
466
- >
467
- Join other…
468
- </ewt-list-item>
469
- </ewt-select>
470
- `
471
- : ""}
472
- ${
473
- // Show input box if command not supported or "Join Other" selected
474
- this._selectedSsid === -1
475
- ? html`
476
- <ewt-textfield label="Network Name" name="ssid"></ewt-textfield>
477
- `
478
- : ""
479
- }
480
- <ewt-textfield
481
- label="Password"
482
- name="password"
483
- type="password"
484
- ></ewt-textfield>
485
- <ewt-button
486
- slot="primaryAction"
487
- label="Connect"
488
- @click=${this._doProvision}
489
- ></ewt-button>
490
- <ewt-button
491
- slot="secondaryAction"
492
- .label=${this._installState && this._installErase ? "Skip" : "Back"}
493
- @click=${() => {
494
- this._state = "DASHBOARD";
495
- }}
496
- ></ewt-button>
497
- `;
498
- }
499
- return [heading, content, hideActions];
500
- }
501
-
502
- _renderAskErase(): [string | undefined, TemplateResult] {
503
- const heading = "Erase device";
504
- const content = html`
505
- <div>
506
- Do you want to erase the device before installing
507
- ${this._manifest.name}? All data on the device will be lost.
508
- </div>
509
- <ewt-formfield label="Erase device" class="danger">
510
- <ewt-checkbox></ewt-checkbox>
511
- </ewt-formfield>
512
- <ewt-button
513
- slot="primaryAction"
514
- label="Next"
515
- @click=${() => {
516
- const checkbox = this.shadowRoot!.querySelector("ewt-checkbox")!;
517
- this._startInstall(checkbox.checked);
518
- }}
519
- ></ewt-button>
520
- <ewt-button
521
- slot="secondaryAction"
522
- label="Back"
523
- @click=${() => {
524
- this._state = "DASHBOARD";
525
- }}
526
- ></ewt-button>
527
- `;
528
-
529
- return [heading, content];
530
- }
531
-
532
- _renderInstall(): [string | undefined, TemplateResult, boolean, boolean] {
533
- let heading: string | undefined;
534
- let content: TemplateResult;
535
- let hideActions = false;
536
- const allowClosing = false;
537
-
538
- const isUpdate = !this._installErase && this._isSameFirmware;
539
-
540
- if (!this._installConfirmed && this._isSameVersion) {
541
- heading = "Erase User Data";
542
- content = html`
543
- Do you want to reset your device and erase all user data from your
544
- device?
545
- <ewt-button
546
- class="danger"
547
- slot="primaryAction"
548
- label="Erase User Data"
549
- @click=${this._confirmInstall}
550
- ></ewt-button>
551
- `;
552
- } else if (!this._installConfirmed) {
553
- heading = "Confirm Installation";
554
- const action = isUpdate ? "update to" : "install";
555
- content = html`
556
- ${isUpdate
557
- ? html`Your device is running
558
- ${this._info!.firmware}&nbsp;${this._info!.version}.<br /><br />`
559
- : ""}
560
- Do you want to ${action}
561
- ${this._manifest.name}&nbsp;${this._manifest.version}?
562
- ${this._installErase
563
- ? html`<br /><br />All data on the device will be erased.`
564
- : ""}
565
- <ewt-button
566
- slot="primaryAction"
567
- label="Install"
568
- @click=${this._confirmInstall}
569
- ></ewt-button>
570
- <ewt-button
571
- slot="secondaryAction"
572
- label="Back"
573
- @click=${() => {
574
- this._state = "DASHBOARD";
575
- }}
576
- ></ewt-button>
577
- `;
578
- } else if (
579
- !this._installState ||
580
- this._installState.state === FlashStateType.INITIALIZING ||
581
- this._installState.state === FlashStateType.MANIFEST ||
582
- this._installState.state === FlashStateType.PREPARING
583
- ) {
584
- heading = "Installing";
585
- content = this._renderProgress("Preparing installation");
586
- hideActions = true;
587
- } else if (this._installState.state === FlashStateType.ERASING) {
588
- heading = "Installing";
589
- content = this._renderProgress("Erasing");
590
- hideActions = true;
591
- } else if (
592
- this._installState.state === FlashStateType.WRITING ||
593
- // When we're finished, keep showing this screen with 100% written
594
- // until Improv is initialized / not detected.
595
- (this._installState.state === FlashStateType.FINISHED &&
596
- this._client === undefined)
597
- ) {
598
- heading = "Installing";
599
- let percentage: number | undefined;
600
- let undeterminateLabel: string | undefined;
601
- if (this._installState.state === FlashStateType.FINISHED) {
602
- // We're done writing and detecting improv, show spinner
603
- undeterminateLabel = "Wrapping up";
604
- } else if (this._installState.details.percentage < 4) {
605
- // We're writing the firmware under 4%, show spinner or else we don't show any pixels
606
- undeterminateLabel = "Installing";
607
- } else {
608
- // We're writing the firmware over 4%, show progress bar
609
- percentage = this._installState.details.percentage;
610
- }
611
- content = this._renderProgress(
612
- html`
613
- ${undeterminateLabel ? html`${undeterminateLabel}<br />` : ""}
614
- <br />
615
- This will take
616
- ${this._installState.chipFamily === "ESP8266"
617
- ? "a minute"
618
- : "2 minutes"}.<br />
619
- Keep this page visible to prevent slow down
620
- `,
621
- percentage,
622
- );
623
- hideActions = true;
624
- } else if (this._installState.state === FlashStateType.FINISHED) {
625
- heading = undefined;
626
- const supportsImprov = this._client !== null;
627
- content = html`
628
- <ewt-page-message
629
- .icon=${OK_ICON}
630
- label="Installation complete!"
631
- ></ewt-page-message>
632
- <ewt-button
633
- slot="primaryAction"
634
- label="Next"
635
- @click=${() => {
636
- this._state =
637
- supportsImprov && this._installErase ? "PROVISION" : "DASHBOARD";
638
- }}
639
- ></ewt-button>
640
- `;
641
- } else if (this._installState.state === FlashStateType.ERROR) {
642
- heading = "Installation failed";
643
- content = html`
644
- <ewt-page-message
645
- .icon=${ERROR_ICON}
646
- .label=${this._installState.message}
647
- ></ewt-page-message>
648
- <ewt-button
649
- slot="primaryAction"
650
- label="Back"
651
- @click=${async () => {
652
- this._initialize();
653
- this._state = "DASHBOARD";
654
- }}
655
- ></ewt-button>
656
- `;
657
- } else if (
658
- this._installState.state === FlashStateType.ESP32_S2_USB_RECONNECT
659
- ) {
660
- // ESP32-S2 Native USB has switched to CDC mode - need to select new port
661
- heading = "ESP32-S2 USB Port Changed";
662
- content = html`
663
- <ewt-page-message
664
- .icon=${"⚠️"}
665
- .label=${"The ESP32-S2 has switched from ROM bootloader to USB CDC mode. Please select the new USB port to continue."}
666
- ></ewt-page-message>
667
- <ewt-button
668
- slot="primaryAction"
669
- label="Select New Port"
670
- @click=${this._handleESP32S2Reconnect}
671
- ></ewt-button>
672
- <ewt-button
673
- slot="secondaryAction"
674
- label="Cancel"
675
- @click=${() => {
676
- this._state = "DASHBOARD";
677
- this._installState = undefined;
678
- }}
679
- ></ewt-button>
680
- `;
681
- }
682
- return [heading, content!, hideActions, allowClosing];
683
- }
684
-
685
- _renderLogs(): [string | undefined, TemplateResult, boolean] {
686
- let heading: string | undefined = `Logs`;
687
- let content: TemplateResult;
688
- let hideActions = false;
689
-
690
- content = html`
691
- <ewt-console .port=${this.port} .logger=${this.logger}></ewt-console>
692
- <ewt-button
693
- slot="primaryAction"
694
- label="Back"
695
- @click=${async () => {
696
- await this.shadowRoot!.querySelector("ewt-console")!.disconnect();
697
- this._state = "DASHBOARD";
698
- this._initialize();
699
- }}
700
- ></ewt-button>
701
- <ewt-button
702
- slot="secondaryAction"
703
- label="Download Logs"
704
- @click=${() => {
705
- textDownload(
706
- this.shadowRoot!.querySelector("ewt-console")!.logs(),
707
- `esp-web-tools-logs.txt`,
708
- );
709
-
710
- this.shadowRoot!.querySelector("ewt-console")!.reset();
711
- }}
712
- ></ewt-button>
713
- <ewt-button
714
- slot="secondaryAction"
715
- label="Reset Device"
716
- @click=${async () => {
717
- await this.shadowRoot!.querySelector("ewt-console")!.reset();
718
- }}
719
- ></ewt-button>
720
- `;
721
-
722
- return [heading, content!, hideActions];
723
- }
724
-
725
- public override willUpdate(changedProps: PropertyValues) {
726
- if (!changedProps.has("_state")) {
727
- return;
728
- }
729
- // Clear errors when changing between pages unless we change
730
- // to the error page.
731
- if (this._state !== "ERROR") {
732
- this._error = undefined;
733
- }
734
- // Scan for SSIDs on provision
735
- if (this._state === "PROVISION") {
736
- this._ssids = undefined;
737
- this._busy = true;
738
- this._client!.scan().then(
739
- (ssids) => {
740
- this._busy = false;
741
- this._ssids = ssids;
742
- this._selectedSsid = ssids.length ? 0 : -1;
743
- },
744
- () => {
745
- this._busy = false;
746
- this._ssids = null;
747
- this._selectedSsid = -1;
748
- },
749
- );
750
- } else {
751
- // Reset this value if we leave provisioning.
752
- this._provisionForce = false;
753
- }
754
-
755
- if (this._state === "INSTALL") {
756
- this._installConfirmed = false;
757
- this._installState = undefined;
758
- }
759
- }
760
-
761
- protected override firstUpdated(changedProps: PropertyValues) {
762
- super.firstUpdated(changedProps);
763
- this._initialize();
764
- }
765
-
766
- protected override updated(changedProps: PropertyValues) {
767
- super.updated(changedProps);
768
-
769
- if (changedProps.has("_state")) {
770
- this.setAttribute("state", this._state);
771
- }
772
-
773
- if (this._state !== "PROVISION") {
774
- return;
775
- }
776
-
777
- if (changedProps.has("_selectedSsid") && this._selectedSsid === -1) {
778
- // If we pick "Join other", select SSID input.
779
- this._focusFormElement("ewt-textfield[name=ssid]");
780
- } else if (changedProps.has("_ssids")) {
781
- // Form is shown when SSIDs are loaded/marked not supported
782
- this._focusFormElement();
783
- }
784
- }
785
-
786
- private _focusFormElement(selector = "ewt-textfield, ewt-select") {
787
- const formEl = this.shadowRoot!.querySelector(
788
- selector,
789
- ) as LitElement | null;
790
- if (formEl) {
791
- formEl.updateComplete.then(() => setTimeout(() => formEl.focus(), 100));
792
- }
793
- }
794
-
795
- private async _initialize(justInstalled = false) {
796
- if (this.port.readable === null || this.port.writable === null) {
797
- this._state = "ERROR";
798
- this._error =
799
- "Serial port is not readable/writable. Close any other application using it and try again.";
800
- return;
801
- }
802
-
803
- try {
804
- // If local file upload via browser is used, we already provide a manifest as a JSON string and not a URL to it
805
- this._manifest = JSON.parse(this.manifestPath);
806
- } catch {
807
- // Standard procedure - download manifest.json with provided URL
808
- try {
809
- this._manifest = await downloadManifest(this.manifestPath);
810
- } catch (err: any) {
811
- this._state = "ERROR";
812
- this._error = "Failed to download manifest";
813
- return;
814
- }
815
- }
816
-
817
- if (this._manifest.new_install_improv_wait_time === 0) {
818
- this._client = null;
819
- return;
820
- }
821
-
822
- const client = new ImprovSerial(this.port!, this.logger);
823
- client.addEventListener("state-changed", () => {
824
- this.requestUpdate();
825
- });
826
- client.addEventListener("error-changed", () => this.requestUpdate());
827
- try {
828
- // If a device was just installed, give new firmware 10 seconds (overridable) to
829
- // format the rest of the flash and do other stuff.
830
- const timeout = !justInstalled
831
- ? 1000
832
- : this._manifest.new_install_improv_wait_time !== undefined
833
- ? this._manifest.new_install_improv_wait_time * 1000
834
- : 10000;
835
- this._info = await client.initialize(timeout);
836
- this._client = client;
837
- client.addEventListener("disconnect", this._handleDisconnect);
838
- } catch (err: any) {
839
- // Clear old value
840
- this._info = undefined;
841
- if (err instanceof PortNotReady) {
842
- this._state = "ERROR";
843
- this._error =
844
- "Serial port is not ready. Close any other application using it and try again.";
845
- } else {
846
- this._client = null; // not supported
847
- this.logger.error("Improv initialization failed.", err);
848
- }
849
- }
850
- }
851
-
852
- private _startInstall(erase: boolean) {
853
- this._state = "INSTALL";
854
- this._installErase = erase;
855
- this._installConfirmed = false;
856
- // Reset reconnect flag when starting a fresh installation
857
- this._esp32s2ReconnectInProgress = false;
858
- }
859
-
860
- /**
861
- * Handle ESP32-S2 Native USB reconnect.
862
- * When the ESP32-S2 switches from ROM bootloader to USB CDC mode,
863
- * the USB port changes and we need to let the user select the new port.
864
- */
865
- private async _handleESP32S2Reconnect() {
866
- // Prevent recursive reconnect attempts
867
- if (this._esp32s2ReconnectInProgress) {
868
- this.logger.log("ESP32-S2 reconnect already in progress, ignoring");
869
- this._error = "Reconnection failed. Please try again manually.";
870
- this._state = "ERROR";
871
- return;
872
- }
873
-
874
- this._esp32s2ReconnectInProgress = true;
875
-
876
- try {
877
- // Close the old port if still accessible
878
- try {
879
- await this.port.close();
880
- } catch {
881
- // Port may already be closed
882
- }
883
-
884
- // Forget the old port to allow reselection
885
- try {
886
- await this.port.forget();
887
- } catch {
888
- // Forget may not be supported or port already released
889
- }
890
-
891
- // Request new port from user
892
- const newPort = await navigator.serial.requestPort();
893
-
894
- // Open the new port
895
- await newPort.open({ baudRate: 115200 });
896
-
897
- // Update the port reference
898
- this.port = newPort;
899
-
900
- // Reset install state and restart installation
901
- this._installState = undefined;
902
- this._installConfirmed = false;
903
-
904
- // Restart the install process with the same erase setting
905
- this._confirmInstall();
906
- } catch (err: any) {
907
- this._esp32s2ReconnectInProgress = false;
908
-
909
- if ((err as DOMException).name === "NotFoundError") {
910
- // User cancelled port selection - stay on reconnect screen
911
- this.logger.log("User cancelled port selection");
912
- return;
913
- }
914
-
915
- // Show error and go back to dashboard
916
- this._error = `Failed to reconnect: ${err.message}`;
917
- this._state = "ERROR";
918
- }
919
- }
920
-
921
- private async _confirmInstall() {
922
- this._installConfirmed = true;
923
- this._installState = undefined;
924
- // Reset reconnect flag when starting a new installation
925
- this._esp32s2ReconnectInProgress = false;
926
-
927
- if (this._client) {
928
- await this._closeClientWithoutEvents(this._client);
929
- }
930
- this._client = undefined;
931
-
932
- if (this.firmwareFile != undefined) {
933
- // If a uploaded File was provided -> create Uint8Array of content
934
- new Blob([this.firmwareFile])
935
- .arrayBuffer()
936
- .then((b) => this._flashFilebuffer(new Uint8Array(b)));
937
- } else {
938
- // Use "standard way" with URL to manifest and firmware binary
939
- flash(
940
- (state) => {
941
- this._installState = state;
942
-
943
- if (state.state === FlashStateType.FINISHED) {
944
- sleep(100)
945
- .then(() => this._initialize(true))
946
- .then(() => this.requestUpdate());
947
- }
948
- },
949
- this.port,
950
- this.logger,
951
- this.manifestPath,
952
- this._installErase,
953
- new Uint8Array(0),
954
- this.baudRate,
955
- );
956
- }
957
- }
958
-
959
- async _flashFilebuffer(fileBuffer: Uint8Array) {
960
- flash(
961
- (state) => {
962
- this._installState = state;
963
-
964
- if (state.state === FlashStateType.FINISHED) {
965
- sleep(100)
966
- .then(() => this._initialize(true))
967
- .then(() => this.requestUpdate());
968
- }
969
- },
970
- this.port,
971
- this.logger,
972
- this.manifestPath,
973
- this._installErase,
974
- fileBuffer,
975
- this.baudRate,
976
- );
977
- }
978
-
979
- private async _doProvision() {
980
- this._busy = true;
981
- this._wasProvisioned =
982
- this._client!.state === ImprovSerialCurrentState.PROVISIONED;
983
- const ssid =
984
- this._selectedSsid === -1
985
- ? (
986
- this.shadowRoot!.querySelector(
987
- "ewt-textfield[name=ssid]",
988
- ) as EwtTextfield
989
- ).value
990
- : this._ssids![this._selectedSsid].name;
991
- const password = (
992
- this.shadowRoot!.querySelector(
993
- "ewt-textfield[name=password]",
994
- ) as EwtTextfield
995
- ).value;
996
- try {
997
- await this._client!.provision(ssid, password);
998
- } catch (err: any) {
999
- return;
1000
- } finally {
1001
- this._busy = false;
1002
- this._provisionForce = false;
1003
- }
1004
- }
1005
-
1006
- private _handleDisconnect = () => {
1007
- this._state = "ERROR";
1008
- this._error = "Disconnected";
1009
- };
1010
-
1011
- private async _handleClose() {
1012
- if (this._client) {
1013
- await this._closeClientWithoutEvents(this._client);
1014
- }
1015
- fireEvent(this, "closed" as any);
1016
- this.parentNode!.removeChild(this);
1017
- }
1018
-
1019
- /**
1020
- * Return if the device runs same firmware as manifest.
1021
- */
1022
- private get _isSameFirmware() {
1023
- return !this._info
1024
- ? false
1025
- : this.overrides?.checkSameFirmware
1026
- ? this.overrides.checkSameFirmware(this._manifest, this._info)
1027
- : this._info.firmware === this._manifest.name;
1028
- }
1029
-
1030
- /**
1031
- * Return if the device runs same firmware and version as manifest.
1032
- */
1033
- private get _isSameVersion() {
1034
- return (
1035
- this._isSameFirmware && this._info!.version === this._manifest.version
1036
- );
1037
- }
1038
-
1039
- private async _closeClientWithoutEvents(client: ImprovSerial) {
1040
- client.removeEventListener("disconnect", this._handleDisconnect);
1041
- await client.close();
1042
- }
1043
-
1044
- static styles = [
1045
- dialogStyles,
1046
- css`
1047
- :host {
1048
- --mdc-dialog-max-width: 390px;
1049
- }
1050
- ewt-icon-button {
1051
- position: absolute;
1052
- right: 4px;
1053
- top: 10px;
1054
- }
1055
- .table-row {
1056
- display: flex;
1057
- }
1058
- .table-row.last {
1059
- margin-bottom: 16px;
1060
- }
1061
- .table-row svg {
1062
- width: 20px;
1063
- margin-right: 8px;
1064
- }
1065
- ewt-textfield,
1066
- ewt-select {
1067
- display: block;
1068
- margin-top: 16px;
1069
- }
1070
- .dashboard-buttons {
1071
- margin: 0 0 -16px -8px;
1072
- }
1073
- .dashboard-buttons div {
1074
- display: block;
1075
- margin: 4px 0;
1076
- }
1077
- a.has-button {
1078
- text-decoration: none;
1079
- }
1080
- .error {
1081
- color: var(--improv-danger-color);
1082
- }
1083
- .danger {
1084
- --mdc-theme-primary: var(--improv-danger-color);
1085
- --mdc-theme-secondary: var(--improv-danger-color);
1086
- }
1087
- button.link {
1088
- background: none;
1089
- color: inherit;
1090
- border: none;
1091
- padding: 0;
1092
- font: inherit;
1093
- text-align: left;
1094
- text-decoration: underline;
1095
- cursor: pointer;
1096
- }
1097
- :host([state="LOGS"]) ewt-dialog {
1098
- --mdc-dialog-max-width: 90vw;
1099
- }
1100
- ewt-console {
1101
- width: calc(80vw - 48px);
1102
- height: 80vh;
1103
- }
1104
- `,
1105
- ];
1106
- }
1107
-
1108
- customElements.define("ewt-install-dialog", EwtInstallDialog);
1109
-
1110
- declare global {
1111
- interface HTMLElementTagNameMap {
1112
- "ewt-install-dialog": EwtInstallDialog;
1113
- }
1114
- }