1 | import { __decorate } from "tslib";
|
2 | import { LitElement, html, css } from "lit";
|
3 | import { state } from "lit/decorators.js";
|
4 | import "./components/ew-text-button";
|
5 | import "./components/ew-list";
|
6 | import "./components/ew-list-item";
|
7 | import "./components/ew-divider";
|
8 | import "./components/ew-checkbox";
|
9 | import "./components/ewt-console";
|
10 | import "./components/ew-dialog";
|
11 | import "./components/ew-icon-button";
|
12 | import "./components/ew-filled-text-field";
|
13 | import "./components/ew-filled-select";
|
14 | import "./components/ew-select-option";
|
15 | import "./pages/ewt-page-progress";
|
16 | import "./pages/ewt-page-message";
|
17 | import { closeIcon, listItemConsole, listItemEraseUserData, listItemFundDevelopment, listItemHomeAssistant, listItemInstallIcon, listItemVisitDevice, listItemWifi, refreshIcon, } from "./components/svg";
|
18 | import { ImprovSerial } from "improv-wifi-serial-sdk/dist/serial";
|
19 | import { ImprovSerialCurrentState, PortNotReady, } from "improv-wifi-serial-sdk/dist/const";
|
20 | import { flash } from "./flash";
|
21 | import { textDownload } from "./util/file-download";
|
22 | import { fireEvent } from "./util/fire-event";
|
23 | import { sleep } from "./util/sleep";
|
24 | import { downloadManifest } from "./util/manifest";
|
25 | import { dialogStyles } from "./styles";
|
26 | import { version } from "./version";
|
27 | console.log(`ESP Web Tools ${version} by Open Home Foundation; https://esphome.github.io/esp-web-tools/`);
|
28 | const ERROR_ICON = "⚠️";
|
29 | const OK_ICON = "🎉";
|
30 | export class EwtInstallDialog extends LitElement {
|
31 | constructor() {
|
32 | super(...arguments);
|
33 | this.logger = console;
|
34 | this._state = "DASHBOARD";
|
35 | this._installErase = false;
|
36 | this._installConfirmed = false;
|
37 | this._provisionForce = false;
|
38 | this._wasProvisioned = false;
|
39 | this._busy = false;
|
40 |
|
41 | this._selectedSsid = null;
|
42 | this._bodyOverflow = null;
|
43 | this._handleDisconnect = () => {
|
44 | this._state = "ERROR";
|
45 | this._error = "Disconnected";
|
46 | };
|
47 | }
|
48 | render() {
|
49 | if (!this.port) {
|
50 | return html ``;
|
51 | }
|
52 | let heading;
|
53 | let content;
|
54 | let allowClosing = false;
|
55 |
|
56 | if (this._client === undefined &&
|
57 | this._state !== "INSTALL" &&
|
58 | this._state !== "LOGS") {
|
59 | if (this._error) {
|
60 | [heading, content] = this._renderError(this._error);
|
61 | }
|
62 | else {
|
63 | content = this._renderProgress("Connecting");
|
64 | }
|
65 | }
|
66 | else if (this._state === "INSTALL") {
|
67 | [heading, content, allowClosing] = this._renderInstall();
|
68 | }
|
69 | else if (this._state === "ASK_ERASE") {
|
70 | [heading, content] = this._renderAskErase();
|
71 | }
|
72 | else if (this._state === "ERROR") {
|
73 | [heading, content] = this._renderError(this._error);
|
74 | }
|
75 | else if (this._state === "DASHBOARD") {
|
76 | [heading, content, allowClosing] = this._client
|
77 | ? this._renderDashboard()
|
78 | : this._renderDashboardNoImprov();
|
79 | }
|
80 | else if (this._state === "PROVISION") {
|
81 | [heading, content] = this._renderProvision();
|
82 | }
|
83 | else if (this._state === "LOGS") {
|
84 | [heading, content] = this._renderLogs();
|
85 | }
|
86 | return html `
|
87 | <ew-dialog
|
88 | open
|
89 | .heading=${heading}
|
90 | @cancel=${this._preventDefault}
|
91 | @closed=${this._handleClose}
|
92 | >
|
93 | ${heading ? html `<div slot="headline">${heading}</div>` : ""}
|
94 | ${allowClosing
|
95 | ? html `
|
96 | <ew-icon-button slot="headline" @click=${this._closeDialog}>
|
97 | ${closeIcon}
|
98 | </ew-icon-button>
|
99 | `
|
100 | : ""}
|
101 | ${content}
|
102 | </ew-dialog>
|
103 | `;
|
104 | }
|
105 | _renderProgress(label, progress) {
|
106 | return html `
|
107 | <ewt-page-progress
|
108 | slot="content"
|
109 | .label=${label}
|
110 | .progress=${progress}
|
111 | ></ewt-page-progress>
|
112 | `;
|
113 | }
|
114 | _renderError(label) {
|
115 | const heading = "Error";
|
116 | const content = html `
|
117 | <ewt-page-message
|
118 | slot="content"
|
119 | .icon=${ERROR_ICON}
|
120 | .label=${label}
|
121 | ></ewt-page-message>
|
122 | <div slot="actions">
|
123 | <ew-text-button @click=${this._closeDialog}>Close</ew-text-button>
|
124 | </div>
|
125 | `;
|
126 | return [heading, content];
|
127 | }
|
128 | _renderDashboard() {
|
129 | const heading = this._manifest.name;
|
130 | let content;
|
131 | let allowClosing = true;
|
132 | content = html `
|
133 | <div slot="content">
|
134 | <ew-list>
|
135 | <ew-list-item>
|
136 | <div slot="headline">Connected to ${this._info.name}</div>
|
137 | <div slot="supporting-text">
|
138 | ${this._info.firmware} ${this._info.version}
|
139 | (${this._info.chipFamily})
|
140 | </div>
|
141 | </ew-list-item>
|
142 | ${!this._isSameVersion
|
143 | ? html `
|
144 | <ew-list-item
|
145 | type="button"
|
146 | @click=${() => {
|
147 | if (this._isSameFirmware) {
|
148 | this._startInstall(false);
|
149 | }
|
150 | else if (this._manifest.new_install_prompt_erase) {
|
151 | this._state = "ASK_ERASE";
|
152 | }
|
153 | else {
|
154 | this._startInstall(true);
|
155 | }
|
156 | }}
|
157 | >
|
158 | ${listItemInstallIcon}
|
159 | <div slot="headline">
|
160 | ${!this._isSameFirmware
|
161 | ? `Install ${this._manifest.name}`
|
162 | : `Update ${this._manifest.name}`}
|
163 | </div>
|
164 | </ew-list-item>
|
165 | `
|
166 | : ""}
|
167 | ${this._client.nextUrl === undefined
|
168 | ? ""
|
169 | : html `
|
170 | <ew-list-item
|
171 | type="link"
|
172 | href=${this._client.nextUrl}
|
173 | target="_blank"
|
174 | >
|
175 | ${listItemVisitDevice}
|
176 | <div slot="headline">Visit Device</div>
|
177 | </ew-list-item>
|
178 | `}
|
179 | ${!this._manifest.home_assistant_domain ||
|
180 | this._client.state !== ImprovSerialCurrentState.PROVISIONED
|
181 | ? ""
|
182 | : html `
|
183 | <ew-list-item
|
184 | type="link"
|
185 | href=${`https://my.home-assistant.io/redirect/config_flow_start/?domain=${this._manifest.home_assistant_domain}`}
|
186 | target="_blank"
|
187 | >
|
188 | ${listItemHomeAssistant}
|
189 | <div slot="headline">Add to Home Assistant</div>
|
190 | </ew-list-item>
|
191 | `}
|
192 | <ew-list-item
|
193 | type="button"
|
194 | @click=${() => {
|
195 | this._state = "PROVISION";
|
196 | if (this._client.state === ImprovSerialCurrentState.PROVISIONED) {
|
197 | this._provisionForce = true;
|
198 | }
|
199 | }}
|
200 | >
|
201 | ${listItemWifi}
|
202 | <div slot="headline">
|
203 | ${this._client.state === ImprovSerialCurrentState.READY
|
204 | ? "Connect to Wi-Fi"
|
205 | : "Change Wi-Fi"}
|
206 | </div>
|
207 | </ew-list-item>
|
208 | <ew-list-item
|
209 | type="button"
|
210 | @click=${async () => {
|
211 | const client = this._client;
|
212 | if (client) {
|
213 | await this._closeClientWithoutEvents(client);
|
214 | await sleep(100);
|
215 | }
|
216 | // Also set `null` back to undefined.
|
217 | this._client = undefined;
|
218 | this._state = "LOGS";
|
219 | }}
|
220 | >
|
221 | ${listItemConsole}
|
222 | <div slot="headline">Logs & Console</div>
|
223 | </ew-list-item>
|
224 | ${this._isSameFirmware && this._manifest.funding_url
|
225 | ? html `
|
226 | <ew-list-item
|
227 | type="link"
|
228 | href=${this._manifest.funding_url}
|
229 | target="_blank"
|
230 | >
|
231 | ${listItemFundDevelopment}
|
232 | <div slot="headline">Fund Development</div>
|
233 | </ew-list-item>
|
234 | `
|
235 | : ""}
|
236 | ${this._isSameVersion
|
237 | ? html `
|
238 | <ew-list-item
|
239 | type="button"
|
240 | class="danger"
|
241 | @click=${() => this._startInstall(true)}
|
242 | >
|
243 | ${listItemEraseUserData}
|
244 | <div slot="headline">Erase User Data</div>
|
245 | </ew-list-item>
|
246 | `
|
247 | : ""}
|
248 | </ew-list>
|
249 | </div>
|
250 | `;
|
251 | return [heading, content, allowClosing];
|
252 | }
|
253 | _renderDashboardNoImprov() {
|
254 | const heading = this._manifest.name;
|
255 | let content;
|
256 | let allowClosing = true;
|
257 | content = html `
|
258 | <div slot="content">
|
259 | <ew-list>
|
260 | <ew-list-item
|
261 | type="button"
|
262 | @click=${() => {
|
263 | if (this._manifest.new_install_prompt_erase) {
|
264 | this._state = "ASK_ERASE";
|
265 | }
|
266 | else {
|
267 | // Default is to erase a device that does not support Improv Serial
|
268 | this._startInstall(true);
|
269 | }
|
270 | }}
|
271 | >
|
272 | ${listItemInstallIcon}
|
273 | <div slot="headline">${`Install ${this._manifest.name}`}</div>
|
274 | </ew-list-item>
|
275 | <ew-list-item
|
276 | type="button"
|
277 | @click=${async () => {
|
278 | // Also set `null` back to undefined.
|
279 | this._client = undefined;
|
280 | this._state = "LOGS";
|
281 | }}
|
282 | >
|
283 | ${listItemConsole}
|
284 | <div slot="headline">Logs & Console</div>
|
285 | </ew-list-item>
|
286 | </ew-list>
|
287 | </div>
|
288 | `;
|
289 | return [heading, content, allowClosing];
|
290 | }
|
291 | _renderProvision() {
|
292 | var _a;
|
293 | let heading = "Configure Wi-Fi";
|
294 | let content;
|
295 | if (this._busy) {
|
296 | return [
|
297 | heading,
|
298 | this._renderProgress(this._ssids === undefined
|
299 | ? "Scanning for networks"
|
300 | : "Trying to connect"),
|
301 | ];
|
302 | }
|
303 | if (!this._provisionForce &&
|
304 | this._client.state === ImprovSerialCurrentState.PROVISIONED) {
|
305 | heading = undefined;
|
306 | const showSetupLinks = !this._wasProvisioned &&
|
307 | (this._client.nextUrl !== undefined ||
|
308 | "home_assistant_domain" in this._manifest);
|
309 | content = html `
|
310 | <div slot="content">
|
311 | <ewt-page-message
|
312 | .icon=${OK_ICON}
|
313 | label="Device connected to the network!"
|
314 | ></ewt-page-message>
|
315 | ${showSetupLinks
|
316 | ? html `
|
317 | <ew-list>
|
318 | ${this._client.nextUrl === undefined
|
319 | ? ""
|
320 | : html `
|
321 | <ew-list-item
|
322 | type="link"
|
323 | href=${this._client.nextUrl}
|
324 | target="_blank"
|
325 | @click=${() => {
|
326 | this._state = "DASHBOARD";
|
327 | }}
|
328 | >
|
329 | ${listItemVisitDevice}
|
330 | <div slot="headline">Visit Device</div>
|
331 | </ew-list-item>
|
332 | `}
|
333 | ${!this._manifest.home_assistant_domain
|
334 | ? ""
|
335 | : html `
|
336 | <ew-list-item
|
337 | type="link"
|
338 | href=${`https://my.home-assistant.io/redirect/config_flow_start/?domain=${this._manifest.home_assistant_domain}`}
|
339 | target="_blank"
|
340 | @click=${() => {
|
341 | this._state = "DASHBOARD";
|
342 | }}
|
343 | >
|
344 | ${listItemHomeAssistant}
|
345 | <div slot="headline">Add to Home Assistant</div>
|
346 | </ew-list-item>
|
347 | `}
|
348 | <ew-list-item
|
349 | type="button"
|
350 | @click=${() => {
|
351 | this._state = "DASHBOARD";
|
352 | }}
|
353 | >
|
354 | <div slot="start" class="fake-icon"></div>
|
355 | <div slot="headline">Skip</div>
|
356 | </ew-list-item>
|
357 | </ew-list>
|
358 | `
|
359 | : ""}
|
360 | </div>
|
361 |
|
362 | ${!showSetupLinks
|
363 | ? html `
|
364 | <div slot="actions">
|
365 | <ew-text-button
|
366 | @click=${() => {
|
367 | this._state = "DASHBOARD";
|
368 | }}
|
369 | >
|
370 | Continue
|
371 | </ew-text-button>
|
372 | </div>
|
373 | `
|
374 | : ""}
|
375 | `;
|
376 | }
|
377 | else {
|
378 | let error;
|
379 | switch (this._client.error) {
|
380 | case 3 :
|
381 | error = "Unable to connect";
|
382 | break;
|
383 | case 254 :
|
384 | error = "Timeout";
|
385 | break;
|
386 | case 0 :
|
387 |
|
388 | case 2 :
|
389 | break;
|
390 | default:
|
391 | error = `Unknown error (${this._client.error})`;
|
392 | }
|
393 | const selectedSsid = (_a = this._ssids) === null || _a === void 0 ? void 0 : _a.find((info) => info.name === this._selectedSsid);
|
394 | content = html `
|
395 | <ew-icon-button slot="headline" @click=${this._updateSsids}>
|
396 | ${refreshIcon}
|
397 | </ew-icon-button>
|
398 | <div slot="content">
|
399 | <div>Connect your device to the network to start using it.</div>
|
400 | ${error ? html `<p class="error">${error}</p>` : ""}
|
401 | ${this._ssids !== null
|
402 | ? html `
|
403 | <ew-filled-select
|
404 | menu-positioning="fixed"
|
405 | label="Network"
|
406 | @change=${(ev) => {
|
407 | const index = ev.target.selectedIndex;
|
408 | // The "Join Other" item is always the last item.
|
409 | this._selectedSsid =
|
410 | index === this._ssids.length
|
411 | ? null
|
412 | : this._ssids[index].name;
|
413 | }}
|
414 | >
|
415 | ${this._ssids.map((info) => html `
|
416 | <ew-select-option
|
417 | .selected=${selectedSsid === info}
|
418 | .value=${info.name}
|
419 | >
|
420 | ${info.name}
|
421 | </ew-select-option>
|
422 | `)}
|
423 | <ew-divider></ew-divider>
|
424 | <ew-select-option .selected=${!selectedSsid}>
|
425 | Join other…
|
426 | </ew-select-option>
|
427 | </ew-filled-select>
|
428 | `
|
429 | : ""}
|
430 | ${
|
431 | // Show input box if command not supported or "Join Other" selected
|
432 | !selectedSsid
|
433 | ? html `
|
434 | <ew-filled-text-field
|
435 | label="Network Name"
|
436 | name="ssid"
|
437 | ></ew-filled-text-field>
|
438 | `
|
439 | : ""}
|
440 | ${!selectedSsid || selectedSsid.secured
|
441 | ? html `
|
442 | <ew-filled-text-field
|
443 | label="Password"
|
444 | name="password"
|
445 | type="password"
|
446 | ></ew-filled-text-field>
|
447 | `
|
448 | : ""}
|
449 | </div>
|
450 | <div slot="actions">
|
451 | <ew-text-button
|
452 | @click=${() => {
|
453 | this._state = "DASHBOARD";
|
454 | }}
|
455 | >
|
456 | ${this._installState && this._installErase ? "Skip" : "Back"}
|
457 | </ew-text-button>
|
458 | <ew-text-button @click=${this._doProvision}>Connect</ew-text-button>
|
459 | </div>
|
460 | `;
|
461 | }
|
462 | return [heading, content];
|
463 | }
|
464 | _renderAskErase() {
|
465 | const heading = "Erase device";
|
466 | const content = html `
|
467 | <div slot="content">
|
468 | <div>
|
469 | Do you want to erase the device before installing
|
470 | ${this._manifest.name}? All data on the device will be lost.
|
471 | </div>
|
472 | <label class="formfield">
|
473 | <ew-checkbox touch-target="wrapper" class="danger"></ew-checkbox>
|
474 | Erase device
|
475 | </label>
|
476 | </div>
|
477 | <div slot="actions">
|
478 | <ew-text-button
|
479 | @click=${() => {
|
480 | this._state = "DASHBOARD";
|
481 | }}
|
482 | >
|
483 | Back
|
484 | </ew-text-button>
|
485 | <ew-text-button
|
486 | @click=${() => {
|
487 | const checkbox = this.shadowRoot.querySelector("ew-checkbox");
|
488 | this._startInstall(checkbox.checked);
|
489 | }}
|
490 | >
|
491 | Next
|
492 | </ew-text-button>
|
493 | </div>
|
494 | `;
|
495 | return [heading, content];
|
496 | }
|
497 | _renderInstall() {
|
498 | let heading;
|
499 | let content;
|
500 | const allowClosing = false;
|
501 | const isUpdate = !this._installErase && this._isSameFirmware;
|
502 | if (!this._installConfirmed && this._isSameVersion) {
|
503 | heading = "Erase User Data";
|
504 | content = html `
|
505 | <div slot="content">
|
506 | Do you want to reset your device and erase all user data from your
|
507 | device?
|
508 | </div>
|
509 | <div slot="actions">
|
510 | <ew-text-button class="danger" @click=${this._confirmInstall}>
|
511 | Erase User Data
|
512 | </ew-text-button>
|
513 | </div>
|
514 | `;
|
515 | }
|
516 | else if (!this._installConfirmed) {
|
517 | heading = "Confirm Installation";
|
518 | const action = isUpdate ? "update to" : "install";
|
519 | content = html `
|
520 | <div slot="content">
|
521 | ${isUpdate
|
522 | ? html `Your device is running
|
523 | ${this._info.firmware} ${this._info.version}.<br /><br />`
|
524 | : ""}
|
525 | Do you want to ${action}
|
526 | ${this._manifest.name} ${this._manifest.version}?
|
527 | ${this._installErase
|
528 | ? html `<br /><br />All data on the device will be erased.`
|
529 | : ""}
|
530 | </div>
|
531 | <div slot="actions">
|
532 | <ew-text-button
|
533 | @click=${() => {
|
534 | this._state = "DASHBOARD";
|
535 | }}
|
536 | >
|
537 | Back
|
538 | </ew-text-button>
|
539 | <ew-text-button @click=${this._confirmInstall}>
|
540 | Install
|
541 | </ew-text-button>
|
542 | </div>
|
543 | `;
|
544 | }
|
545 | else if (!this._installState ||
|
546 | this._installState.state === "initializing" ||
|
547 | this._installState.state === "preparing" ) {
|
548 | heading = "Installing";
|
549 | content = this._renderProgress("Preparing installation");
|
550 | }
|
551 | else if (this._installState.state === "erasing" ) {
|
552 | heading = "Installing";
|
553 | content = this._renderProgress("Erasing");
|
554 | }
|
555 | else if (this._installState.state === "writing" ||
|
556 |
|
557 |
|
558 | (this._installState.state === "finished" &&
|
559 | this._client === undefined)) {
|
560 | heading = "Installing";
|
561 | let percentage;
|
562 | let undeterminateLabel;
|
563 | if (this._installState.state === "finished" ) {
|
564 |
|
565 | undeterminateLabel = "Wrapping up";
|
566 | }
|
567 | else if (this._installState.details.percentage < 4) {
|
568 |
|
569 | undeterminateLabel = "Installing";
|
570 | }
|
571 | else {
|
572 |
|
573 | percentage = this._installState.details.percentage;
|
574 | }
|
575 | content = this._renderProgress(html `
|
576 | ${undeterminateLabel ? html `${undeterminateLabel}<br />` : ""}
|
577 | <br />
|
578 | This will take
|
579 | ${this._installState.chipFamily === "ESP8266"
|
580 | ? "a minute"
|
581 | : "2 minutes"}.<br />
|
582 | Keep this page visible to prevent slow down
|
583 | `, percentage);
|
584 | }
|
585 | else if (this._installState.state === "finished" ) {
|
586 | heading = undefined;
|
587 | const supportsImprov = this._client !== null;
|
588 | content = html `
|
589 | <ewt-page-message
|
590 | slot="content"
|
591 | .icon=${OK_ICON}
|
592 | label="Installation complete!"
|
593 | ></ewt-page-message>
|
594 |
|
595 | <div slot="actions">
|
596 | <ew-text-button
|
597 | @click=${() => {
|
598 | this._state =
|
599 | supportsImprov && this._installErase
|
600 | ? "PROVISION"
|
601 | : "DASHBOARD";
|
602 | }}
|
603 | >
|
604 | Next
|
605 | </ew-text-button>
|
606 | </div>
|
607 | `;
|
608 | }
|
609 | else if (this._installState.state === "error" ) {
|
610 | heading = "Installation failed";
|
611 | content = html `
|
612 | <ewt-page-message
|
613 | slot="content"
|
614 | .icon=${ERROR_ICON}
|
615 | .label=${this._installState.message}
|
616 | ></ewt-page-message>
|
617 | <div slot="actions">
|
618 | <ew-text-button
|
619 | @click=${async () => {
|
620 | this._initialize();
|
621 | this._state = "DASHBOARD";
|
622 | }}
|
623 | >
|
624 | Back
|
625 | </ew-text-button>
|
626 | </div>
|
627 | `;
|
628 | }
|
629 | return [heading, content, allowClosing];
|
630 | }
|
631 | _renderLogs() {
|
632 | let heading = `Logs`;
|
633 | let content;
|
634 | content = html `
|
635 | <div slot="content">
|
636 | <ewt-console .port=${this.port} .logger=${this.logger}></ewt-console>
|
637 | </div>
|
638 | <div slot="actions">
|
639 | <ew-text-button
|
640 | @click=${async () => {
|
641 | await this.shadowRoot.querySelector("ewt-console").reset();
|
642 | }}
|
643 | >
|
644 | Reset Device
|
645 | </ew-text-button>
|
646 | <ew-text-button
|
647 | @click=${() => {
|
648 | textDownload(this.shadowRoot.querySelector("ewt-console").logs(), `esp-web-tools-logs.txt`);
|
649 | this.shadowRoot.querySelector("ewt-console").reset();
|
650 | }}
|
651 | >
|
652 | Download Logs
|
653 | </ew-text-button>
|
654 | <ew-text-button
|
655 | @click=${async () => {
|
656 | await this.shadowRoot.querySelector("ewt-console").disconnect();
|
657 | this._state = "DASHBOARD";
|
658 | this._initialize();
|
659 | }}
|
660 | >
|
661 | Back
|
662 | </ew-text-button>
|
663 | </div>
|
664 | `;
|
665 | return [heading, content];
|
666 | }
|
667 | willUpdate(changedProps) {
|
668 | if (!changedProps.has("_state")) {
|
669 | return;
|
670 | }
|
671 |
|
672 |
|
673 | if (this._state !== "ERROR") {
|
674 | this._error = undefined;
|
675 | }
|
676 |
|
677 | if (this._state === "PROVISION") {
|
678 | this._updateSsids();
|
679 | }
|
680 | else {
|
681 |
|
682 | this._provisionForce = false;
|
683 | }
|
684 | if (this._state === "INSTALL") {
|
685 | this._installConfirmed = false;
|
686 | this._installState = undefined;
|
687 | }
|
688 | }
|
689 | async _updateSsids(tries = 0) {
|
690 | const oldSsids = this._ssids;
|
691 | this._ssids = undefined;
|
692 | this._busy = true;
|
693 | let ssids;
|
694 | try {
|
695 | ssids = await this._client.scan();
|
696 | }
|
697 | catch (err) {
|
698 |
|
699 | if (this._ssids === undefined) {
|
700 | this._ssids = null;
|
701 | this._selectedSsid = null;
|
702 | }
|
703 | this._busy = false;
|
704 | return;
|
705 | }
|
706 |
|
707 | if (ssids.length === 0 && tries < 3) {
|
708 | console.log("SCHEDULE RETRY", tries);
|
709 | setTimeout(() => this._updateSsids(tries + 1), 1000);
|
710 | return;
|
711 | }
|
712 | if (oldSsids) {
|
713 |
|
714 | if (this._selectedSsid &&
|
715 | !ssids.find((s) => s.name === this._selectedSsid)) {
|
716 | this._selectedSsid = ssids[0].name;
|
717 | }
|
718 | }
|
719 | else {
|
720 | this._selectedSsid = ssids.length ? ssids[0].name : null;
|
721 | }
|
722 | this._ssids = ssids;
|
723 | this._busy = false;
|
724 | }
|
725 | firstUpdated(changedProps) {
|
726 | super.firstUpdated(changedProps);
|
727 | this._bodyOverflow = document.body.style.overflow;
|
728 | document.body.style.overflow = "hidden";
|
729 | this._initialize();
|
730 | }
|
731 | updated(changedProps) {
|
732 | super.updated(changedProps);
|
733 | if (changedProps.has("_state")) {
|
734 | this.setAttribute("state", this._state);
|
735 | }
|
736 | if (this._state !== "PROVISION") {
|
737 | return;
|
738 | }
|
739 | if (changedProps.has("_selectedSsid") && this._selectedSsid === null) {
|
740 |
|
741 | this._focusFormElement("ew-filled-text-field[name=ssid]");
|
742 | }
|
743 | else if (changedProps.has("_ssids")) {
|
744 |
|
745 | this._focusFormElement();
|
746 | }
|
747 | }
|
748 | _focusFormElement(selector = "ew-filled-text-field, ew-filled-select") {
|
749 | const formEl = this.shadowRoot.querySelector(selector);
|
750 | if (formEl) {
|
751 | formEl.updateComplete.then(() => setTimeout(() => formEl.focus(), 100));
|
752 | }
|
753 | }
|
754 | async _initialize(justInstalled = false) {
|
755 | if (this.port.readable === null || this.port.writable === null) {
|
756 | this._state = "ERROR";
|
757 | this._error =
|
758 | "Serial port is not readable/writable. Close any other application using it and try again.";
|
759 | return;
|
760 | }
|
761 | try {
|
762 | this._manifest = await downloadManifest(this.manifestPath);
|
763 | }
|
764 | catch (err) {
|
765 | this._state = "ERROR";
|
766 | this._error = "Failed to download manifest";
|
767 | return;
|
768 | }
|
769 | if (this._manifest.new_install_improv_wait_time === 0) {
|
770 | this._client = null;
|
771 | return;
|
772 | }
|
773 | const client = new ImprovSerial(this.port, this.logger);
|
774 | client.addEventListener("state-changed", () => {
|
775 | this.requestUpdate();
|
776 | });
|
777 | client.addEventListener("error-changed", () => this.requestUpdate());
|
778 | try {
|
779 |
|
780 |
|
781 | const timeout = !justInstalled
|
782 | ? 1000
|
783 | : this._manifest.new_install_improv_wait_time !== undefined
|
784 | ? this._manifest.new_install_improv_wait_time * 1000
|
785 | : 10000;
|
786 | this._info = await client.initialize(timeout);
|
787 | this._client = client;
|
788 | client.addEventListener("disconnect", this._handleDisconnect);
|
789 | }
|
790 | catch (err) {
|
791 |
|
792 | this._info = undefined;
|
793 | if (err instanceof PortNotReady) {
|
794 | this._state = "ERROR";
|
795 | this._error =
|
796 | "Serial port is not ready. Close any other application using it and try again.";
|
797 | }
|
798 | else {
|
799 | this._client = null;
|
800 | this.logger.error("Improv initialization failed.", err);
|
801 | }
|
802 | }
|
803 | }
|
804 | _startInstall(erase) {
|
805 | this._state = "INSTALL";
|
806 | this._installErase = erase;
|
807 | this._installConfirmed = false;
|
808 | }
|
809 | async _confirmInstall() {
|
810 | this._installConfirmed = true;
|
811 | this._installState = undefined;
|
812 | if (this._client) {
|
813 | await this._closeClientWithoutEvents(this._client);
|
814 | }
|
815 | this._client = undefined;
|
816 |
|
817 | await this.port.close();
|
818 | flash((state) => {
|
819 | this._installState = state;
|
820 | if (state.state === "finished" ) {
|
821 | sleep(100)
|
822 |
|
823 | .then(() => this.port.open({ baudRate: 115200 }))
|
824 | .then(() => this._initialize(true))
|
825 | .then(() => this.requestUpdate());
|
826 | }
|
827 | else if (state.state === "error" ) {
|
828 | sleep(100)
|
829 |
|
830 | .then(() => this.port.open({ baudRate: 115200 }));
|
831 | }
|
832 | }, this.port, this.manifestPath, this._manifest, this._installErase);
|
833 | }
|
834 | async _doProvision() {
|
835 | var _a;
|
836 | this._busy = true;
|
837 | this._wasProvisioned =
|
838 | this._client.state === ImprovSerialCurrentState.PROVISIONED;
|
839 | const ssid = this._selectedSsid === null
|
840 | ? this.shadowRoot.querySelector("ew-filled-text-field[name=ssid]").value
|
841 | : this._selectedSsid;
|
842 | const password = ((_a = this.shadowRoot.querySelector("ew-filled-text-field[name=password]")) === null || _a === void 0 ? void 0 : _a.value) || "";
|
843 | try {
|
844 | await this._client.provision(ssid, password, 30000);
|
845 | }
|
846 | catch (err) {
|
847 | return;
|
848 | }
|
849 | finally {
|
850 | this._busy = false;
|
851 | this._provisionForce = false;
|
852 | }
|
853 | }
|
854 | _closeDialog() {
|
855 | this.shadowRoot.querySelector("ew-dialog").close();
|
856 | }
|
857 | async _handleClose() {
|
858 | if (this._client) {
|
859 | await this._closeClientWithoutEvents(this._client);
|
860 | }
|
861 | fireEvent(this, "closed");
|
862 | document.body.style.overflow = this._bodyOverflow;
|
863 | this.parentNode.removeChild(this);
|
864 | }
|
865 | |
866 |
|
867 |
|
868 | get _isSameFirmware() {
|
869 | var _a;
|
870 | return !this._info
|
871 | ? false
|
872 | : ((_a = this.overrides) === null || _a === void 0 ? void 0 : _a.checkSameFirmware)
|
873 | ? this.overrides.checkSameFirmware(this._manifest, this._info)
|
874 | : this._info.firmware === this._manifest.name;
|
875 | }
|
876 | |
877 |
|
878 |
|
879 | get _isSameVersion() {
|
880 | return (this._isSameFirmware && this._info.version === this._manifest.version);
|
881 | }
|
882 | async _closeClientWithoutEvents(client) {
|
883 | client.removeEventListener("disconnect", this._handleDisconnect);
|
884 | await client.close();
|
885 | }
|
886 | _preventDefault(ev) {
|
887 | ev.preventDefault();
|
888 | }
|
889 | }
|
890 | EwtInstallDialog.styles = [
|
891 | dialogStyles,
|
892 | css `
|
893 | :host {
|
894 | --mdc-dialog-max-width: 390px;
|
895 | }
|
896 | div[slot="headline"] {
|
897 | padding-right: 48px;
|
898 | }
|
899 | ew-icon-button[slot="headline"] {
|
900 | position: absolute;
|
901 | right: 4px;
|
902 | top: 8px;
|
903 | }
|
904 | ew-icon-button[slot="headline"] svg {
|
905 | padding: 8px;
|
906 | color: var(--text-color);
|
907 | }
|
908 | .dialog-nav svg {
|
909 | color: var(--text-color);
|
910 | }
|
911 | .table-row {
|
912 | display: flex;
|
913 | }
|
914 | .table-row.last {
|
915 | margin-bottom: 16px;
|
916 | }
|
917 | .table-row svg {
|
918 | width: 20px;
|
919 | margin-right: 8px;
|
920 | }
|
921 | ew-filled-text-field,
|
922 | ew-filled-select {
|
923 | display: block;
|
924 | margin-top: 16px;
|
925 | }
|
926 | label.formfield {
|
927 | display: inline-flex;
|
928 | align-items: center;
|
929 | padding-right: 8px;
|
930 | }
|
931 | ew-list {
|
932 | margin: 0 -24px;
|
933 | padding: 0;
|
934 | }
|
935 | ew-list-item svg {
|
936 | height: 24px;
|
937 | }
|
938 | ewt-page-message + ew-list {
|
939 | padding-top: 16px;
|
940 | }
|
941 | .fake-icon {
|
942 | width: 24px;
|
943 | }
|
944 | .error {
|
945 | color: var(--danger-color);
|
946 | }
|
947 | .danger {
|
948 | --mdc-theme-primary: var(--danger-color);
|
949 | --mdc-theme-secondary: var(--danger-color);
|
950 | --md-sys-color-primary: var(--danger-color);
|
951 | --md-sys-color-on-surface: var(--danger-color);
|
952 | }
|
953 | button.link {
|
954 | background: none;
|
955 | color: inherit;
|
956 | border: none;
|
957 | padding: 0;
|
958 | font: inherit;
|
959 | text-align: left;
|
960 | text-decoration: underline;
|
961 | cursor: pointer;
|
962 | }
|
963 | :host([state="LOGS"]) ew-dialog {
|
964 | max-width: 90vw;
|
965 | max-height: 90vh;
|
966 | }
|
967 | ewt-console {
|
968 | width: calc(80vw - 48px);
|
969 | height: calc(90vh - 168px);
|
970 | }
|
971 | `,
|
972 | ];
|
973 | __decorate([
|
974 | state()
|
975 | ], EwtInstallDialog.prototype, "_client", void 0);
|
976 | __decorate([
|
977 | state()
|
978 | ], EwtInstallDialog.prototype, "_state", void 0);
|
979 | __decorate([
|
980 | state()
|
981 | ], EwtInstallDialog.prototype, "_installErase", void 0);
|
982 | __decorate([
|
983 | state()
|
984 | ], EwtInstallDialog.prototype, "_installConfirmed", void 0);
|
985 | __decorate([
|
986 | state()
|
987 | ], EwtInstallDialog.prototype, "_installState", void 0);
|
988 | __decorate([
|
989 | state()
|
990 | ], EwtInstallDialog.prototype, "_provisionForce", void 0);
|
991 | __decorate([
|
992 | state()
|
993 | ], EwtInstallDialog.prototype, "_error", void 0);
|
994 | __decorate([
|
995 | state()
|
996 | ], EwtInstallDialog.prototype, "_busy", void 0);
|
997 | __decorate([
|
998 | state()
|
999 | ], EwtInstallDialog.prototype, "_ssids", void 0);
|
1000 | __decorate([
|
1001 | state()
|
1002 | ], EwtInstallDialog.prototype, "_selectedSsid", void 0);
|
1003 | customElements.define("ewt-install-dialog", EwtInstallDialog);
|