tasmota-esp-web-tools 12.2.1 → 12.2.2

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.
package/dist/flash.d.ts CHANGED
@@ -1,4 +1,32 @@
1
1
  import { Logger } from "tasmota-webserial-esptool";
2
- import { FlashState } from "./const";
2
+ import { Build, FlashState, Manifest } from "./const";
3
+ /**
4
+ * Parse flash size string (e.g., "4MB", "8MB", "16MB") to megabytes number
5
+ */
6
+ export declare function parseFlashSizeToMB(flashSize: string): number | undefined;
7
+ /**
8
+ * Extract the most relevant firmware filename from a build's parts.
9
+ * Prefers parts with "factory" in the name.
10
+ * Ignores parts with "bootloader" or "partition" in the name.
11
+ * Returns just the basename (no path prefix).
12
+ */
13
+ export declare function getFirmwareFileName(build: Build): string | undefined;
14
+ /**
15
+ * Find the best matching build for a given chip configuration.
16
+ * This is the single source of truth for build selection used by both
17
+ * the flash routine and the install dialog UI.
18
+ */
19
+ export declare function findMatchingBuild(manifest: Manifest, chipFamily: string, chipVariant: string | null, flashSizeMB: number | undefined, usbInterface: "UART" | "CDC" | undefined): Build | undefined;
20
+ /**
21
+ * Detect the matching build for the currently connected device.
22
+ * Extracts chip info directly from the esploader instance.
23
+ *
24
+ * @param manifest - The loaded firmware manifest
25
+ * @param esploader - ESPLoader instance (or stub) with chip info
26
+ * @param flashSize - Detected flash size string (e.g. "4MB"), or undefined
27
+ * @param isUsbJtagOrOtg - Whether the device uses native USB (CDC) instead of external serial
28
+ * @param improvChipFamily - Optional chipFamily from Improv Serial info (takes precedence)
29
+ */
30
+ export declare function detectMatchingBuild(manifest: Manifest, esploader: any, flashSize: string | undefined, isUsbJtagOrOtg: boolean, improvChipFamily?: string): Build | undefined;
3
31
  export declare const flash: (onEvent: (state: FlashState) => void, esploader: any, // ESPLoader instance from tasmota-webserial-esptool
4
32
  logger: Logger, manifestPath: string, eraseFirst: boolean, firmwareBuffer: Uint8Array, _baudRate?: number) => Promise<void>;
package/dist/flash.js CHANGED
@@ -4,7 +4,7 @@ import { corsProxyFetch } from "./util/cors-proxy";
4
4
  /**
5
5
  * Parse flash size string (e.g., "4MB", "8MB", "16MB") to megabytes number
6
6
  */
7
- function parseFlashSizeToMB(flashSize) {
7
+ export function parseFlashSizeToMB(flashSize) {
8
8
  if (!flashSize)
9
9
  return undefined;
10
10
  const match = flashSize.match(/^(\d+)(MB|GB)$/);
@@ -66,9 +66,71 @@ function selectBestBuild(builds, detectedFlashSizeMB, detectedUsbInterface) {
66
66
  }
67
67
  return bestScore >= 0 ? bestBuild : undefined;
68
68
  }
69
+ /**
70
+ * Extract the most relevant firmware filename from a build's parts.
71
+ * Prefers parts with "factory" in the name.
72
+ * Ignores parts with "bootloader" or "partition" in the name.
73
+ * Returns just the basename (no path prefix).
74
+ */
75
+ export function getFirmwareFileName(build) {
76
+ const candidates = build.parts
77
+ .map((p) => p.path)
78
+ .filter((path) => {
79
+ const lower = path.toLowerCase();
80
+ return !lower.includes("bootloader") && !lower.includes("partition");
81
+ });
82
+ if (candidates.length === 0)
83
+ return undefined;
84
+ const factory = candidates.find((p) => p.toLowerCase().includes("factory"));
85
+ const chosen = factory !== null && factory !== void 0 ? factory : candidates[0];
86
+ // Return only the basename
87
+ return chosen.split("/").pop().split("\\").pop();
88
+ }
89
+ /**
90
+ * Find the best matching build for a given chip configuration.
91
+ * This is the single source of truth for build selection used by both
92
+ * the flash routine and the install dialog UI.
93
+ */
94
+ export function findMatchingBuild(manifest, chipFamily, chipVariant, flashSizeMB, usbInterface) {
95
+ const compatible = manifest.builds.filter((b) => {
96
+ if (b.chipFamily !== chipFamily)
97
+ return false;
98
+ if (b.chipVariant && b.chipVariant !== chipVariant)
99
+ return false;
100
+ return true;
101
+ });
102
+ const exactVariant = compatible.filter((b) => b.chipVariant !== undefined && b.chipVariant === chipVariant);
103
+ const variantAgnostic = compatible.filter((b) => b.chipVariant === undefined);
104
+ return (selectBestBuild(exactVariant, flashSizeMB, usbInterface) ||
105
+ selectBestBuild(variantAgnostic, flashSizeMB, usbInterface));
106
+ }
107
+ /**
108
+ * Detect the matching build for the currently connected device.
109
+ * Extracts chip info directly from the esploader instance.
110
+ *
111
+ * @param manifest - The loaded firmware manifest
112
+ * @param esploader - ESPLoader instance (or stub) with chip info
113
+ * @param flashSize - Detected flash size string (e.g. "4MB"), or undefined
114
+ * @param isUsbJtagOrOtg - Whether the device uses native USB (CDC) instead of external serial
115
+ * @param improvChipFamily - Optional chipFamily from Improv Serial info (takes precedence)
116
+ */
117
+ export function detectMatchingBuild(manifest, esploader, flashSize, isUsbJtagOrOtg, improvChipFamily) {
118
+ var _a;
119
+ const chipFamily = improvChipFamily ||
120
+ (esploader.chipFamily ? getChipFamilyName(esploader) : null);
121
+ if (!chipFamily)
122
+ return undefined;
123
+ const chipVariant = (_a = esploader.chipVariant) !== null && _a !== void 0 ? _a : null;
124
+ const flashSizeMB = flashSize ? parseFlashSizeToMB(flashSize) : undefined;
125
+ const usbInterface = isUsbJtagOrOtg
126
+ ? "CDC"
127
+ : "UART";
128
+ return findMatchingBuild(manifest, chipFamily, chipVariant, flashSizeMB, usbInterface);
129
+ }
69
130
  export const flash = async (onEvent, esploader, // ESPLoader instance from tasmota-webserial-esptool
70
131
  logger, manifestPath, eraseFirst, firmwareBuffer, _baudRate) => {
71
132
  let manifest;
133
+ // eslint-disable-next-line prefer-const
72
134
  let build;
73
135
  // eslint-disable-next-line prefer-const
74
136
  let chipFamily;
@@ -167,24 +229,7 @@ logger, manifestPath, eraseFirst, firmwareBuffer, _baudRate) => {
167
229
  await esploader.disconnect();
168
230
  return;
169
231
  }
170
- // Filter builds by chipFamily and chipVariant
171
- const compatibleBuilds = manifest.builds.filter((b) => {
172
- if (b.chipFamily !== chipFamily) {
173
- return false;
174
- }
175
- if (b.chipVariant && b.chipVariant !== chipVariant) {
176
- return false;
177
- }
178
- return true;
179
- });
180
- // Select the best build using most-specific-matching algorithm
181
- // Prefer builds with more matching qualifiers (flashSizeMB)
182
- const exactVariantBuilds = compatibleBuilds.filter((b) => b.chipVariant !== undefined && b.chipVariant === chipVariant);
183
- const variantAgnosticBuilds = compatibleBuilds.filter((b) => b.chipVariant === undefined);
184
- build = selectBestBuild(exactVariantBuilds, flashSizeMB, detectedUsbInterface);
185
- if (!build) {
186
- build = selectBestBuild(variantAgnosticBuilds, flashSizeMB, detectedUsbInterface);
187
- }
232
+ build = findMatchingBuild(manifest, chipFamily, chipVariant, flashSizeMB, detectedUsbInterface);
188
233
  if (!build) {
189
234
  fireStateEvent({
190
235
  state: "error" /* FlashStateType.ERROR */,
@@ -46,6 +46,7 @@ export declare class EwtInstallDialog extends LitElement {
46
46
  private _improvSupported;
47
47
  private _isUsbJtagOrOtgDevice;
48
48
  private _flashSize?;
49
+ private _detectedBuild?;
49
50
  private _openConsoleAfterReconnect;
50
51
  private _visitDeviceAfterReconnect;
51
52
  private _addToHAAfterReconnect;
@@ -100,6 +101,12 @@ export declare class EwtInstallDialog extends LitElement {
100
101
  private _preventDefault;
101
102
  private _closeDialog;
102
103
  private _handleClose;
104
+ /**
105
+ * Return a human-readable label for the differentiating properties of a build.
106
+ * Shows the firmware filename from parts (factory preferred, no bootloader/partition).
107
+ * Falls back to chipVariant/flashSizeMB/usbInterface if no filename found.
108
+ */
109
+ private _buildVariantLabel;
103
110
  /**
104
111
  * Return if the device runs same firmware as manifest.
105
112
  */
@@ -18,7 +18,7 @@ import "./pages/ew-page-message";
18
18
  import { closeIcon, warningIcon, checkCircleIcon, listItemInstallIcon, listItemWifi, listItemConsole, listItemVisitDevice, listItemHomeAssistant, listItemEraseUserData, listItemFundDevelopment, firmwareIcon, downloadIcon, } from "./components/svg";
19
19
  import { ImprovSerial } from "improv-wifi-serial-sdk/dist/serial";
20
20
  import { ImprovSerialCurrentState, } from "improv-wifi-serial-sdk/dist/const";
21
- import { flash } from "./flash";
21
+ import { flash, detectMatchingBuild, getFirmwareFileName } from "./flash";
22
22
  import { textDownload } from "./util/file-download";
23
23
  import { fireEvent } from "./util/fire-event";
24
24
  import { sleep } from "./util/sleep";
@@ -57,8 +57,9 @@ export class EwtInstallDialog extends LitElement {
57
57
  this._handleDisconnect = () => {
58
58
  this._state = "ERROR";
59
59
  this._error = "Disconnected";
60
- // Reset flash size when device is actually disconnected
60
+ // Reset flash size and detected build when device is actually disconnected
61
61
  this._flashSize = undefined;
62
+ this._detectedBuild = undefined;
62
63
  };
63
64
  }
64
65
  // Ensure stub is initialized (called before any operation that needs it)
@@ -144,6 +145,7 @@ export class EwtInstallDialog extends LitElement {
144
145
  }
145
146
  // Helper to probe flash size from a running stub (only available after stub)
146
147
  async _probeFlashSize(espStub) {
148
+ var _a;
147
149
  if (espStub.detectFlashSize && !this._flashSize) {
148
150
  try {
149
151
  await espStub.detectFlashSize();
@@ -154,6 +156,7 @@ export class EwtInstallDialog extends LitElement {
154
156
  this.logger.debug("Failed to detect flash size:", err);
155
157
  }
156
158
  }
159
+ this._detectedBuild = detectMatchingBuild(this._manifest, espStub, this._flashSize, this._isUsbJtagOrOtgDevice, (_a = this._info) === null || _a === void 0 ? void 0 : _a.chipFamily);
157
160
  }
158
161
  // Helper to check if device is using USB-JTAG or USB-OTG (not external serial chip)
159
162
  async _isUsbJtagOrOtg() {
@@ -557,6 +560,14 @@ export class EwtInstallDialog extends LitElement {
557
560
  ? `Install ${this._manifest.name}`
558
561
  : `Update ${this._manifest.name}`}
559
562
  </div>
563
+ ${(() => {
564
+ const label = this._detectedBuild
565
+ ? this._buildVariantLabel(this._detectedBuild)
566
+ : undefined;
567
+ return label
568
+ ? html `<div slot="supporting-text">Variant: ${label}</div>`
569
+ : "";
570
+ })()}
560
571
  </ew-list-item>
561
572
  `
562
573
  : ""}
@@ -879,6 +890,14 @@ export class EwtInstallDialog extends LitElement {
879
890
  >
880
891
  ${listItemInstallIcon}
881
892
  <div slot="headline">Install ${this._manifest.name}</div>
893
+ ${(() => {
894
+ const label = this._detectedBuild
895
+ ? this._buildVariantLabel(this._detectedBuild)
896
+ : undefined;
897
+ return label
898
+ ? html `<div slot="supporting-text">Variant: ${label}</div>`
899
+ : "";
900
+ })()}
882
901
  </ew-list-item>
883
902
 
884
903
  ${!this._isUsbJtagOrOtgDevice
@@ -1251,7 +1270,7 @@ export class EwtInstallDialog extends LitElement {
1251
1270
  return [heading, content];
1252
1271
  }
1253
1272
  _renderInstall() {
1254
- var _a, _b, _c;
1273
+ var _a, _b, _c, _d, _e;
1255
1274
  let heading;
1256
1275
  let content;
1257
1276
  let hideActions = false;
@@ -1286,6 +1305,10 @@ export class EwtInstallDialog extends LitElement {
1286
1305
  ? `, ${this._flashSize}`
1287
1306
  : this._flashSize})`
1288
1307
  : "";
1308
+ const variantBuild = (_d = (_c = this._installState) === null || _c === void 0 ? void 0 : _c.build) !== null && _d !== void 0 ? _d : this._detectedBuild;
1309
+ const variantLabel = variantBuild
1310
+ ? this._buildVariantLabel(variantBuild)
1311
+ : undefined;
1289
1312
  content = html `
1290
1313
  ${isUpdate
1291
1314
  ? html `Your device is running
@@ -1295,7 +1318,9 @@ export class EwtInstallDialog extends LitElement {
1295
1318
  ? html `Device detected: ${deviceInfo}<br /><br />`
1296
1319
  : ""}
1297
1320
  Do you want to ${action}
1298
- ${this._manifest.name}&nbsp;${this._manifest.version}?
1321
+ ${this._manifest.name}&nbsp;${this._manifest.version}${variantLabel
1322
+ ? html `&nbsp;<em>(${variantLabel})</em>`
1323
+ : ""}?
1299
1324
  ${this._installErase
1300
1325
  ? html `<br /><br />All data on the device will be erased.`
1301
1326
  : ""}
@@ -1317,7 +1342,7 @@ export class EwtInstallDialog extends LitElement {
1317
1342
  this._installState.state === "preparing" /* FlashStateType.PREPARING */) {
1318
1343
  heading = "Installing";
1319
1344
  // Show flash size in preparing message if available
1320
- const preparingMsg = ((_c = this._installState) === null || _c === void 0 ? void 0 : _c.flashSize)
1345
+ const preparingMsg = ((_e = this._installState) === null || _e === void 0 ? void 0 : _e.flashSize)
1321
1346
  ? `Preparing installation (${this._installState.flashSize})`
1322
1347
  : "Preparing installation";
1323
1348
  content = this._renderProgress(preparingMsg);
@@ -2562,11 +2587,31 @@ export class EwtInstallDialog extends LitElement {
2562
2587
  await this._closeClientWithoutEvents(this._client);
2563
2588
  }
2564
2589
  document.body.style.overflow = (_a = this._bodyOverflow) !== null && _a !== void 0 ? _a : "";
2565
- // Reset flash size when dialog is closed
2590
+ // Reset flash size and detected build when dialog is closed
2566
2591
  this._flashSize = undefined;
2592
+ this._detectedBuild = undefined;
2567
2593
  fireEvent(this, "closed");
2568
2594
  this.parentNode.removeChild(this);
2569
2595
  }
2596
+ /**
2597
+ * Return a human-readable label for the differentiating properties of a build.
2598
+ * Shows the firmware filename from parts (factory preferred, no bootloader/partition).
2599
+ * Falls back to chipVariant/flashSizeMB/usbInterface if no filename found.
2600
+ */
2601
+ _buildVariantLabel(build) {
2602
+ const filename = getFirmwareFileName(build);
2603
+ if (filename)
2604
+ return filename;
2605
+ // Fallback: structural qualifiers
2606
+ const parts = [];
2607
+ if (build.chipVariant)
2608
+ parts.push(build.chipVariant);
2609
+ if (build.flashSizeMB)
2610
+ parts.push(`${build.flashSizeMB} MB`);
2611
+ if (build.usbInterface)
2612
+ parts.push(build.usbInterface);
2613
+ return parts.length > 0 ? parts.join(", ") : undefined;
2614
+ }
2570
2615
  /**
2571
2616
  * Return if the device runs same firmware as manifest.
2572
2617
  */
@@ -2756,4 +2801,7 @@ __decorate([
2756
2801
  __decorate([
2757
2802
  state()
2758
2803
  ], EwtInstallDialog.prototype, "_flashSize", void 0);
2804
+ __decorate([
2805
+ state()
2806
+ ], EwtInstallDialog.prototype, "_detectedBuild", void 0);
2759
2807
  customElements.define("ewt-install-dialog", EwtInstallDialog);