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
package/dist/flash.js CHANGED
@@ -1,21 +1,12 @@
1
1
  import { ESPLoader } from "tasmota-webserial-esptool";
2
2
  import { getChipFamilyName } from "./util/chip-family-name";
3
3
  import { sleep } from "./util/sleep";
4
- /**
5
- * Check if a serial port is an ESP32-S2 Native USB device in TinyUSB CDC mode
6
- * VID 0x303a = Espressif
7
- * PID 0x0002 = ESP32-S2 TinyUSB CDC
8
- */
9
- const isESP32S2NativeUSB = (port) => {
10
- const info = port.getInfo();
11
- return info.usbVendorId === 0x303a && info.usbProductId === 0x0002;
12
- };
4
+ import { isESP32S2NativeUSB, isESP32S2ReconnectError, closeAndForgetPort, } from "./util/esp32s2-reconnect";
13
5
  export const flash = async (onEvent, port, logger, manifestPath, eraseFirst, firmwareBuffer, baudRate) => {
14
6
  let manifest;
15
7
  let build;
16
8
  let chipFamily;
17
9
  let chipVariant = null;
18
- let esp32s2ReconnectRequired = false;
19
10
  const isS2NativeUSB = isESP32S2NativeUSB(port);
20
11
  const fireStateEvent = (stateUpdate) => onEvent({
21
12
  ...stateUpdate,
@@ -38,7 +29,6 @@ export const flash = async (onEvent, port, logger, manifestPath, eraseFirst, fir
38
29
  window.esploader = esploader;
39
30
  // ESP32-S2 Native USB event handler - listen on ESPLoader instance
40
31
  const handleESP32S2Reconnect = () => {
41
- esp32s2ReconnectRequired = true;
42
32
  logger.log("ESP32-S2 Native USB disconnect detected - reconnection required");
43
33
  };
44
34
  // Register event listener for ESP32-S2 Native USB reconnect on ESPLoader
@@ -63,26 +53,10 @@ export const flash = async (onEvent, port, logger, manifestPath, eraseFirst, fir
63
53
  catch (err) {
64
54
  logger.error(err);
65
55
  // Check if this is an ESP32-S2 Native USB reconnect situation
66
- // This happens when:
67
- // 1. ESP32-S2 in TinyUSB CDC mode was reset to bootloader
68
- // 2. ESP32-S2 disconnected during initialization
69
- if (isS2NativeUSB &&
70
- (esp32s2ReconnectRequired || String(err).includes("reconnect required"))) {
56
+ if (isS2NativeUSB && isESP32S2ReconnectError(err)) {
71
57
  cleanup();
72
- // Close the old port if still accessible
73
- try {
74
- await port.close();
75
- }
76
- catch {
77
- // Port may already be closed
78
- }
79
- // Forget the old port to allow reselection
80
- try {
81
- await port.forget();
82
- }
83
- catch {
84
- // Forget may not be supported or port already released
85
- }
58
+ // Close and forget the old port
59
+ await closeAndForgetPort(port);
86
60
  // Fire reconnect event to trigger port reselection dialog
87
61
  fireStateEvent({
88
62
  state: "esp32_s2_usb_reconnect" /* FlashStateType.ESP32_S2_USB_RECONNECT */,
@@ -8,6 +8,7 @@ import "./components/ewt-icon-button";
8
8
  import "./components/ewt-textfield";
9
9
  import "./components/ewt-select";
10
10
  import "./components/ewt-list-item";
11
+ import "./components/ewt-littlefs-manager";
11
12
  import "./pages/ewt-page-progress";
12
13
  import "./pages/ewt-page-message";
13
14
  import { Logger, Manifest } from "./const.js";
@@ -35,6 +36,9 @@ export declare class EwtInstallDialog extends LitElement {
35
36
  private _esp32s2ReconnectInProgress;
36
37
  private _ssids?;
37
38
  private _selectedSsid;
39
+ private _partitions?;
40
+ private _selectedPartition?;
41
+ private _espStub?;
38
42
  protected render(): TemplateResult<1>;
39
43
  _renderProgress(label: string | TemplateResult, progress?: number): TemplateResult<1>;
40
44
  _renderError(label: string): [string, TemplateResult, boolean];
@@ -44,6 +48,13 @@ export declare class EwtInstallDialog extends LitElement {
44
48
  _renderAskErase(): [string | undefined, TemplateResult];
45
49
  _renderInstall(): [string | undefined, TemplateResult, boolean, boolean];
46
50
  _renderLogs(): [string | undefined, TemplateResult, boolean];
51
+ _renderPartitions(): [string | undefined, TemplateResult, boolean];
52
+ _renderLittleFS(): [string | undefined, TemplateResult, boolean, boolean];
53
+ _renderESP32S2Reconnect(): [string | undefined, TemplateResult, boolean];
54
+ private _handleESP32S2ReconnectClick;
55
+ private _readPartitionTable;
56
+ private _openFilesystem;
57
+ private _formatSize;
47
58
  willUpdate(changedProps: PropertyValues): void;
48
59
  protected firstUpdated(changedProps: PropertyValues): void;
49
60
  protected updated(changedProps: PropertyValues): void;
@@ -10,6 +10,7 @@ import "./components/ewt-icon-button";
10
10
  import "./components/ewt-textfield";
11
11
  import "./components/ewt-select";
12
12
  import "./components/ewt-list-item";
13
+ import "./components/ewt-littlefs-manager";
13
14
  import "./pages/ewt-page-progress";
14
15
  import "./pages/ewt-page-message";
15
16
  import { chipIcon, closeIcon, firmwareIcon } from "./components/svg";
@@ -21,6 +22,9 @@ import { fireEvent } from "./util/fire-event";
21
22
  import { sleep } from "./util/sleep";
22
23
  import { downloadManifest } from "./util/manifest";
23
24
  import { dialogStyles } from "./styles";
25
+ import { parsePartitionTable } from "./partition.js";
26
+ import { detectFilesystemType } from "./util/partition.js";
27
+ import { isESP32S2NativeUSB, isESP32S2ReconnectError, closeAndForgetPort, } from "./util/esp32s2-reconnect";
24
28
  const ERROR_ICON = "⚠️";
25
29
  const OK_ICON = "🎉";
26
30
  export class EwtInstallDialog extends LitElement {
@@ -82,6 +86,15 @@ export class EwtInstallDialog extends LitElement {
82
86
  else if (this._state === "LOGS") {
83
87
  [heading, content, hideActions] = this._renderLogs();
84
88
  }
89
+ else if (this._state === "PARTITIONS") {
90
+ [heading, content, hideActions] = this._renderPartitions();
91
+ }
92
+ else if (this._state === "LITTLEFS") {
93
+ [heading, content, hideActions, allowClosing] = this._renderLittleFS();
94
+ }
95
+ else if (this._state === "ESP32S2_RECONNECT") {
96
+ [heading, content, hideActions] = this._renderESP32S2Reconnect();
97
+ }
85
98
  return html `
86
99
  <ewt-dialog
87
100
  open
@@ -215,6 +228,15 @@ export class EwtInstallDialog extends LitElement {
215
228
  }}
216
229
  ></ewt-button>
217
230
  </div>
231
+ <div>
232
+ <ewt-button
233
+ label="Manage Filesystem"
234
+ @click=${() => {
235
+ this._state = "PARTITIONS";
236
+ this._readPartitionTable();
237
+ }}
238
+ ></ewt-button>
239
+ </div>
218
240
  ${this._isSameFirmware && this._manifest.funding_url
219
241
  ? html `
220
242
  <div>
@@ -276,6 +298,16 @@ export class EwtInstallDialog extends LitElement {
276
298
  }}
277
299
  ></ewt-button>
278
300
  </div>
301
+
302
+ <div>
303
+ <ewt-button
304
+ label="Manage Filesystem"
305
+ @click=${() => {
306
+ this._state = "PARTITIONS";
307
+ this._readPartitionTable();
308
+ }}
309
+ ></ewt-button>
310
+ </div>
279
311
  </div>
280
312
  `;
281
313
  return [heading, content, hideActions, allowClosing];
@@ -651,6 +683,263 @@ export class EwtInstallDialog extends LitElement {
651
683
  `;
652
684
  return [heading, content, hideActions];
653
685
  }
686
+ _renderPartitions() {
687
+ const heading = "Partition Table";
688
+ let content;
689
+ const hideActions = false;
690
+ if (this._busy) {
691
+ content = this._renderProgress("Reading partition table...");
692
+ }
693
+ else if (!this._partitions || this._partitions.length === 0) {
694
+ content = html `
695
+ <ewt-page-message
696
+ .icon=${ERROR_ICON}
697
+ label="No partitions found"
698
+ ></ewt-page-message>
699
+ <ewt-button
700
+ slot="primaryAction"
701
+ label="Back"
702
+ @click=${() => {
703
+ this._state = "DASHBOARD";
704
+ }}
705
+ ></ewt-button>
706
+ `;
707
+ }
708
+ else {
709
+ content = html `
710
+ <div class="partition-list">
711
+ <table class="partition-table">
712
+ <thead>
713
+ <tr>
714
+ <th>Name</th>
715
+ <th>Type</th>
716
+ <th>SubType</th>
717
+ <th>Offset</th>
718
+ <th>Size</th>
719
+ <th>Action</th>
720
+ </tr>
721
+ </thead>
722
+ <tbody>
723
+ ${this._partitions.map((partition) => html `
724
+ <tr>
725
+ <td>${partition.name}</td>
726
+ <td>${partition.typeName}</td>
727
+ <td>${partition.subtypeName}</td>
728
+ <td>0x${partition.offset.toString(16)}</td>
729
+ <td>${this._formatSize(partition.size)}</td>
730
+ <td>
731
+ ${partition.type === 0x01 && partition.subtype === 0x82
732
+ ? html `
733
+ <ewt-button
734
+ label="Open FS"
735
+ @click=${() => this._openFilesystem(partition)}
736
+ ></ewt-button>
737
+ `
738
+ : ""}
739
+ </td>
740
+ </tr>
741
+ `)}
742
+ </tbody>
743
+ </table>
744
+ </div>
745
+ <ewt-button
746
+ slot="primaryAction"
747
+ label="Back"
748
+ @click=${() => {
749
+ this._state = "DASHBOARD";
750
+ }}
751
+ ></ewt-button>
752
+ `;
753
+ }
754
+ return [heading, content, hideActions];
755
+ }
756
+ _renderLittleFS() {
757
+ const heading = undefined;
758
+ const hideActions = true;
759
+ const allowClosing = true;
760
+ const content = html `
761
+ <ewt-littlefs-manager
762
+ .partition=${this._selectedPartition}
763
+ .espStub=${this._espStub}
764
+ .logger=${this.logger}
765
+ .onClose=${() => {
766
+ this._state = "PARTITIONS";
767
+ }}
768
+ ></ewt-littlefs-manager>
769
+ `;
770
+ return [heading, content, hideActions, allowClosing];
771
+ }
772
+ _renderESP32S2Reconnect() {
773
+ const heading = "ESP32-S2 USB Port Changed";
774
+ const content = html `
775
+ <ewt-page-message
776
+ .icon=${"⚠️"}
777
+ .label=${"The ESP32-S2 has switched to USB CDC mode. Please select the new USB port to continue."}
778
+ ></ewt-page-message>
779
+ <ewt-button
780
+ slot="primaryAction"
781
+ label="Select New Port"
782
+ @click=${this._handleESP32S2ReconnectClick}
783
+ ></ewt-button>
784
+ <ewt-button
785
+ slot="secondaryAction"
786
+ label="Cancel"
787
+ @click=${() => {
788
+ this._state = "DASHBOARD";
789
+ }}
790
+ ></ewt-button>
791
+ `;
792
+ const hideActions = false;
793
+ return [heading, content, hideActions];
794
+ }
795
+ async _handleESP32S2ReconnectClick() {
796
+ try {
797
+ this._busy = true;
798
+ // Request new port (this is triggered by user click, so it works)
799
+ this.logger.log("Requesting new port selection...");
800
+ const newPort = await navigator.serial.requestPort();
801
+ await newPort.open({ baudRate: 115200 });
802
+ this.logger.log("New port selected, updating...");
803
+ this.port = newPort;
804
+ // Restart partition table reading with new port
805
+ this._state = "PARTITIONS";
806
+ await this._readPartitionTable();
807
+ }
808
+ catch (err) {
809
+ if (err.name === "NotFoundError") {
810
+ this.logger.log("Port selection cancelled");
811
+ this._state = "DASHBOARD";
812
+ }
813
+ else {
814
+ this._error = `Failed to reconnect: ${err.message}`;
815
+ this._state = "ERROR";
816
+ }
817
+ }
818
+ finally {
819
+ this._busy = false;
820
+ }
821
+ }
822
+ async _readPartitionTable() {
823
+ const PARTITION_TABLE_OFFSET = 0x8000;
824
+ const PARTITION_TABLE_SIZE = 0x1000;
825
+ this._busy = true;
826
+ this._partitions = undefined;
827
+ try {
828
+ this.logger.log("Reading partition table from 0x8000...");
829
+ // Import ESPLoader from the package
830
+ const { ESPLoader } = await import("tasmota-webserial-esptool");
831
+ let currentPort = this.port;
832
+ let esploader = new ESPLoader(currentPort, {
833
+ log: (msg, ...args) => this.logger.log(msg, ...args),
834
+ debug: (msg, ...args) => { var _a, _b; return (_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, msg, ...args); },
835
+ error: (msg, ...args) => this.logger.error(msg, ...args),
836
+ });
837
+ // Set up ESP32-S2 reconnect handler BEFORE initialize
838
+ const reconnectHandler = async (event) => {
839
+ this.logger.log("ESP32-S2 USB reconnect event:", event.detail.message);
840
+ await closeAndForgetPort(currentPort);
841
+ this.logger.log("Please select the new ESP32-S2 USB CDC port");
842
+ };
843
+ esploader.addEventListener("esp32s2-usb-reconnect", reconnectHandler, {
844
+ once: true,
845
+ });
846
+ // Initialize ESP loader
847
+ this.logger.log("Initializing ESP loader...");
848
+ try {
849
+ await esploader.initialize();
850
+ this.logger.log("ESP loader initialized successfully");
851
+ }
852
+ catch (err) {
853
+ // Check if this is an ESP32-S2 reconnect scenario
854
+ if (isESP32S2NativeUSB(currentPort) && isESP32S2ReconnectError(err)) {
855
+ this.logger.log("ESP32-S2 USB port changed - user needs to select new port");
856
+ // Close and forget old port
857
+ await closeAndForgetPort(currentPort);
858
+ // Show UI with button for user to click
859
+ this._busy = false;
860
+ this._state = "ESP32S2_RECONNECT";
861
+ return;
862
+ }
863
+ else {
864
+ throw err;
865
+ }
866
+ }
867
+ this.logger.log("Running stub...");
868
+ const espStub = await esploader.runStub();
869
+ this._espStub = espStub;
870
+ // Add a small delay after stub is running
871
+ await sleep(500);
872
+ this.logger.log("Reading flash data...");
873
+ const data = await espStub.readFlash(PARTITION_TABLE_OFFSET, PARTITION_TABLE_SIZE);
874
+ const partitions = parsePartitionTable(data);
875
+ if (partitions.length === 0) {
876
+ this.logger.error("No valid partition table found");
877
+ this._partitions = [];
878
+ }
879
+ else {
880
+ this.logger.log(`Found ${partitions.length} partition(s)`);
881
+ this._partitions = partitions;
882
+ }
883
+ }
884
+ catch (e) {
885
+ this.logger.error(`Failed to read partition table: ${e.message || e}`);
886
+ if (e.message === "Port selection cancelled") {
887
+ this._error = "Port selection cancelled";
888
+ }
889
+ else {
890
+ this._error = `Failed to read partition table: ${e.message || e}. Try resetting your device.`;
891
+ }
892
+ this._state = "ERROR";
893
+ }
894
+ finally {
895
+ this._busy = false;
896
+ }
897
+ }
898
+ async _openFilesystem(partition) {
899
+ try {
900
+ this._busy = true;
901
+ this.logger.log(`Detecting filesystem type for partition "${partition.name}"...`);
902
+ // Check if ESP stub is still available
903
+ if (!this._espStub) {
904
+ throw new Error("ESP stub not available. Please reconnect.");
905
+ }
906
+ const fsType = await detectFilesystemType(this._espStub, partition.offset, partition.size, this.logger);
907
+ this.logger.log(`Detected filesystem: ${fsType}`);
908
+ if (fsType === "littlefs") {
909
+ this._selectedPartition = partition;
910
+ this._state = "LITTLEFS";
911
+ }
912
+ else if (fsType === "spiffs") {
913
+ this.logger.error("SPIFFS support not yet implemented. Use LittleFS partitions.");
914
+ this._error = "SPIFFS support not yet implemented";
915
+ this._state = "ERROR";
916
+ }
917
+ else {
918
+ this.logger.error("Unknown filesystem type. Cannot open partition.");
919
+ this._error = "Unknown filesystem type";
920
+ this._state = "ERROR";
921
+ }
922
+ }
923
+ catch (e) {
924
+ this.logger.error(`Failed to open filesystem: ${e.message || e}`);
925
+ this._error = `Failed to open filesystem: ${e.message || e}`;
926
+ this._state = "ERROR";
927
+ }
928
+ finally {
929
+ this._busy = false;
930
+ }
931
+ }
932
+ _formatSize(bytes) {
933
+ if (bytes < 1024) {
934
+ return `${bytes} B`;
935
+ }
936
+ else if (bytes < 1024 * 1024) {
937
+ return `${(bytes / 1024).toFixed(2)} KB`;
938
+ }
939
+ else {
940
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
941
+ }
942
+ }
654
943
  willUpdate(changedProps) {
655
944
  if (!changedProps.has("_state")) {
656
945
  return;
@@ -972,6 +1261,44 @@ EwtInstallDialog.styles = [
972
1261
  width: calc(80vw - 48px);
973
1262
  height: 80vh;
974
1263
  }
1264
+ :host([state="PARTITIONS"]) ewt-dialog {
1265
+ --mdc-dialog-max-width: 800px;
1266
+ }
1267
+ :host([state="LITTLEFS"]) ewt-dialog {
1268
+ --mdc-dialog-max-width: 95vw;
1269
+ --mdc-dialog-max-height: 90vh;
1270
+ }
1271
+ :host([state="LITTLEFS"]) .mdc-dialog__content {
1272
+ padding: 10px 20px;
1273
+ }
1274
+ :host([state="LITTLEFS"]) ewt-littlefs-manager {
1275
+ display: block;
1276
+ max-width: 100%;
1277
+ }
1278
+ .partition-list {
1279
+ max-height: 60vh;
1280
+ overflow-y: auto;
1281
+ }
1282
+ .partition-table {
1283
+ width: 100%;
1284
+ border-collapse: collapse;
1285
+ margin: 16px 0;
1286
+ }
1287
+ .partition-table th,
1288
+ .partition-table td {
1289
+ padding: 8px 12px;
1290
+ text-align: left;
1291
+ border: 1px solid #ccc;
1292
+ }
1293
+ .partition-table th {
1294
+ font-weight: 600;
1295
+ background-color: #f0f0f0;
1296
+ position: sticky;
1297
+ top: 0;
1298
+ }
1299
+ .partition-table tbody tr:hover {
1300
+ background-color: rgba(3, 169, 244, 0.1);
1301
+ }
975
1302
  `,
976
1303
  ];
977
1304
  __decorate([
@@ -1004,4 +1331,13 @@ __decorate([
1004
1331
  __decorate([
1005
1332
  state()
1006
1333
  ], EwtInstallDialog.prototype, "_selectedSsid", void 0);
1334
+ __decorate([
1335
+ state()
1336
+ ], EwtInstallDialog.prototype, "_partitions", void 0);
1337
+ __decorate([
1338
+ state()
1339
+ ], EwtInstallDialog.prototype, "_selectedPartition", void 0);
1340
+ __decorate([
1341
+ state()
1342
+ ], EwtInstallDialog.prototype, "_espStub", void 0);
1007
1343
  customElements.define("ewt-install-dialog", EwtInstallDialog);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ESP32 Partition Table Parser
3
+ * Based on ESP-IDF partition table format
4
+ */
5
+ export interface Partition {
6
+ name: string;
7
+ type: number;
8
+ subtype: number;
9
+ offset: number;
10
+ size: number;
11
+ flags: number;
12
+ typeName: string;
13
+ subtypeName: string;
14
+ }
15
+ /**
16
+ * Parse the entire partition table
17
+ */
18
+ export declare function parsePartitionTable(data: Uint8Array): Partition[];
19
+ /**
20
+ * Get the default partition table offset
21
+ */
22
+ export declare function getPartitionTableOffset(): number;
23
+ /**
24
+ * Format size in human-readable format
25
+ */
26
+ export declare function formatSize(bytes: number): string;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * ESP32 Partition Table Parser
3
+ * Based on ESP-IDF partition table format
4
+ */
5
+ // Partition types
6
+ const PARTITION_TYPES = {
7
+ 0x00: "app",
8
+ 0x01: "data",
9
+ };
10
+ // App subtypes
11
+ const APP_SUBTYPES = {
12
+ 0x00: "factory",
13
+ 0x10: "ota_0",
14
+ 0x11: "ota_1",
15
+ 0x12: "ota_2",
16
+ 0x13: "ota_3",
17
+ 0x14: "ota_4",
18
+ 0x15: "ota_5",
19
+ 0x16: "ota_6",
20
+ 0x17: "ota_7",
21
+ 0x18: "ota_8",
22
+ 0x19: "ota_9",
23
+ 0x1a: "ota_10",
24
+ 0x1b: "ota_11",
25
+ 0x1c: "ota_12",
26
+ 0x1d: "ota_13",
27
+ 0x1e: "ota_14",
28
+ 0x1f: "ota_15",
29
+ 0x20: "test",
30
+ };
31
+ // Data subtypes
32
+ const DATA_SUBTYPES = {
33
+ 0x00: "ota",
34
+ 0x01: "phy",
35
+ 0x02: "nvs",
36
+ 0x03: "coredump",
37
+ 0x04: "nvs_keys",
38
+ 0x05: "efuse",
39
+ 0x80: "esphttpd",
40
+ 0x81: "fat",
41
+ 0x82: "spiffs",
42
+ };
43
+ const PARTITION_TABLE_OFFSET = 0x8000; // Default partition table offset
44
+ const PARTITION_ENTRY_SIZE = 32;
45
+ const PARTITION_MAGIC = 0x50aa;
46
+ /**
47
+ * Parse a single partition entry from binary data
48
+ */
49
+ function parsePartitionEntry(data) {
50
+ if (data.length < PARTITION_ENTRY_SIZE) {
51
+ return null;
52
+ }
53
+ // Check magic bytes
54
+ const magic = (data[0] | (data[1] << 8)) & 0xffff;
55
+ if (magic !== PARTITION_MAGIC) {
56
+ return null;
57
+ }
58
+ const type = data[2];
59
+ const subtype = data[3];
60
+ const offset = data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24);
61
+ const size = data[8] | (data[9] << 8) | (data[10] << 16) | (data[11] << 24);
62
+ // Name is at offset 12, max 16 bytes, null-terminated
63
+ let name = "";
64
+ for (let i = 12; i < 28; i++) {
65
+ if (data[i] === 0)
66
+ break;
67
+ name += String.fromCharCode(data[i]);
68
+ }
69
+ const flags = data[28] | (data[29] << 8) | (data[30] << 16) | (data[31] << 24);
70
+ // Get type and subtype names
71
+ const typeName = PARTITION_TYPES[type] || `unknown(0x${type.toString(16)})`;
72
+ let subtypeName = "";
73
+ if (type === 0x00) {
74
+ subtypeName = APP_SUBTYPES[subtype] || `unknown(0x${subtype.toString(16)})`;
75
+ }
76
+ else if (type === 0x01) {
77
+ subtypeName =
78
+ DATA_SUBTYPES[subtype] || `unknown(0x${subtype.toString(16)})`;
79
+ }
80
+ else {
81
+ subtypeName = `0x${subtype.toString(16)}`;
82
+ }
83
+ return {
84
+ name,
85
+ type,
86
+ subtype,
87
+ offset,
88
+ size,
89
+ flags,
90
+ typeName,
91
+ subtypeName,
92
+ };
93
+ }
94
+ /**
95
+ * Parse the entire partition table
96
+ */
97
+ export function parsePartitionTable(data) {
98
+ const partitions = [];
99
+ for (let i = 0; i < data.length; i += PARTITION_ENTRY_SIZE) {
100
+ const entryData = data.slice(i, i + PARTITION_ENTRY_SIZE);
101
+ const partition = parsePartitionEntry(entryData);
102
+ if (partition === null) {
103
+ // End of partition table or invalid entry
104
+ break;
105
+ }
106
+ partitions.push(partition);
107
+ }
108
+ return partitions;
109
+ }
110
+ /**
111
+ * Get the default partition table offset
112
+ */
113
+ export function getPartitionTableOffset() {
114
+ return PARTITION_TABLE_OFFSET;
115
+ }
116
+ /**
117
+ * Format size in human-readable format
118
+ */
119
+ export function formatSize(bytes) {
120
+ if (bytes < 1024) {
121
+ return `${bytes} B`;
122
+ }
123
+ else if (bytes < 1024 * 1024) {
124
+ return `${(bytes / 1024).toFixed(2)} KB`;
125
+ }
126
+ else {
127
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
128
+ }
129
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * ESP32-S2 Native USB Reconnect Utilities
3
+ *
4
+ * The ESP32-S2 can switch between ROM bootloader mode and USB CDC mode,
5
+ * causing the serial port to change. These utilities help detect and handle
6
+ * this scenario consistently across the application.
7
+ */
8
+ /**
9
+ * Check if a serial port is an ESP32-S2 Native USB device
10
+ * VID 0x303a = Espressif
11
+ * PID 0x0002 = ESP32-S2 Native USB (ROM bootloader)
12
+ */
13
+ export declare const isESP32S2NativeUSB: (port: SerialPort) => boolean;
14
+ /**
15
+ * Check if an error is likely caused by ESP32-S2 port switching
16
+ */
17
+ export declare const isESP32S2ReconnectError: (error: any) => boolean;
18
+ /**
19
+ * Close and forget a serial port
20
+ * This releases the port so a new one can be selected
21
+ */
22
+ export declare const closeAndForgetPort: (port: SerialPort) => Promise<void>;