tasmota-webserial-esptool 9.2.9 → 9.2.11

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/src/esp_loader.ts CHANGED
@@ -141,9 +141,13 @@ export class ESPLoader extends EventTarget {
141
141
  __inputBuffer?: number[];
142
142
  __inputBufferReadIndex?: number;
143
143
  __totalBytesRead?: number;
144
- private __currentBaudRate: number = ESP_ROM_BAUD;
144
+ public currentBaudRate: number = ESP_ROM_BAUD;
145
145
  private _maxUSBSerialBaudrate?: number;
146
146
  public __reader?: ReadableStreamDefaultReader<Uint8Array>;
147
+ private SLIP_END = 0xc0;
148
+ private SLIP_ESC = 0xdb;
149
+ private SLIP_ESC_END = 0xdc;
150
+ private SLIP_ESC_ESC = 0xdd;
147
151
  private _isESP32S2NativeUSB: boolean = false;
148
152
  private _initializationSucceeded: boolean = false;
149
153
  private __commandLock: Promise<[number, number[]]> = Promise.resolve([0, []]);
@@ -757,17 +761,14 @@ export class ESPLoader extends EventTarget {
757
761
 
758
762
  // Always read from browser's serial buffer immediately
759
763
  // to prevent browser buffer overflow. Don't apply back-pressure here.
760
- const chunk = Array.from(value);
764
+ const chunk = Array.from(value as Uint8Array);
761
765
  Array.prototype.push.apply(this._inputBuffer, chunk);
762
766
 
763
767
  // Track total bytes read from serial port
764
768
  this._totalBytesRead += value.length;
765
769
  }
766
770
  } catch {
767
- // Don't log error if this is an expected disconnect during console mode transition
768
- if (!this._consoleMode) {
769
- this.logger.error("Read loop got disconnected");
770
- }
771
+ // this.logger.error("Read loop got disconnected");
771
772
  } finally {
772
773
  // Always reset reconfiguring flag when read loop ends
773
774
  // This prevents "Cannot write during port reconfiguration" errors
@@ -1462,9 +1463,9 @@ export class ESPLoader extends EventTarget {
1462
1463
  }
1463
1464
  } catch (error) {
1464
1465
  lastError = error as Error;
1465
- this.logger.debug(
1466
- `${strategy.name} reset failed: ${(error as Error).message}`,
1467
- );
1466
+ // this.logger.debug(
1467
+ // `${strategy.name} reset failed: ${(error as Error).message}`,
1468
+ // );
1468
1469
 
1469
1470
  // Set abandon flag to stop any in-flight operations
1470
1471
  this._abandonCurrentOperation = true;
@@ -2129,47 +2130,47 @@ export class ESPLoader extends EventTarget {
2129
2130
  "Timed out waiting for packet " + waitingFor,
2130
2131
  );
2131
2132
  }
2132
- const b = this._readByte()!;
2133
+ const byte = this._readByte()!;
2133
2134
 
2134
2135
  if (partialPacket === null) {
2135
2136
  // waiting for packet header
2136
- if (b == 0xc0) {
2137
+ if (byte == this.SLIP_END) {
2137
2138
  partialPacket = [];
2138
2139
  } else {
2139
2140
  if (this.debug) {
2140
- this.logger.debug("Read invalid data: " + toHex(b));
2141
+ this.logger.debug("Read invalid data: " + toHex(byte));
2141
2142
  this.logger.debug(
2142
2143
  "Remaining data in serial buffer: " +
2143
2144
  hexFormatter(this._inputBuffer),
2144
2145
  );
2145
2146
  }
2146
2147
  throw new SlipReadError(
2147
- "Invalid head of packet (" + toHex(b) + ")",
2148
+ "Invalid head of packet (" + toHex(byte) + ")",
2148
2149
  );
2149
2150
  }
2150
2151
  } else if (inEscape) {
2151
2152
  // part-way through escape sequence
2152
2153
  inEscape = false;
2153
- if (b == 0xdc) {
2154
- partialPacket.push(0xc0);
2155
- } else if (b == 0xdd) {
2156
- partialPacket.push(0xdb);
2154
+ if (byte == this.SLIP_ESC_END) {
2155
+ partialPacket.push(this.SLIP_END);
2156
+ } else if (byte == this.SLIP_ESC_ESC) {
2157
+ partialPacket.push(this.SLIP_ESC);
2157
2158
  } else {
2158
2159
  if (this.debug) {
2159
- this.logger.debug("Read invalid data: " + toHex(b));
2160
+ this.logger.debug("Read invalid data: " + toHex(byte));
2160
2161
  this.logger.debug(
2161
2162
  "Remaining data in serial buffer: " +
2162
2163
  hexFormatter(this._inputBuffer),
2163
2164
  );
2164
2165
  }
2165
2166
  throw new SlipReadError(
2166
- "Invalid SLIP escape (0xdb, " + toHex(b) + ")",
2167
+ "Invalid SLIP escape (0xdb, " + toHex(byte) + ")",
2167
2168
  );
2168
2169
  }
2169
- } else if (b == 0xdb) {
2170
+ } else if (byte == this.SLIP_ESC) {
2170
2171
  // start of escape sequence
2171
2172
  inEscape = true;
2172
- } else if (b == 0xc0) {
2173
+ } else if (byte == this.SLIP_END) {
2173
2174
  // end of packet
2174
2175
  if (this.debug)
2175
2176
  this.logger.debug(
@@ -2180,7 +2181,7 @@ export class ESPLoader extends EventTarget {
2180
2181
  return partialPacket;
2181
2182
  } else {
2182
2183
  // normal byte in packet
2183
- partialPacket.push(b);
2184
+ partialPacket.push(byte);
2184
2185
  }
2185
2186
  }
2186
2187
  }
@@ -2214,46 +2215,46 @@ export class ESPLoader extends EventTarget {
2214
2215
  this.logger.debug(
2215
2216
  "Read " + readBytes.length + " bytes: " + hexFormatter(readBytes),
2216
2217
  );
2217
- for (const b of readBytes) {
2218
+ for (const byte of readBytes) {
2218
2219
  if (partialPacket === null) {
2219
2220
  // waiting for packet header
2220
- if (b == 0xc0) {
2221
+ if (byte == this.SLIP_END) {
2221
2222
  partialPacket = [];
2222
2223
  } else {
2223
2224
  if (this.debug) {
2224
- this.logger.debug("Read invalid data: " + toHex(b));
2225
+ this.logger.debug("Read invalid data: " + toHex(byte));
2225
2226
  this.logger.debug(
2226
2227
  "Remaining data in serial buffer: " +
2227
2228
  hexFormatter(this._inputBuffer),
2228
2229
  );
2229
2230
  }
2230
2231
  throw new SlipReadError(
2231
- "Invalid head of packet (" + toHex(b) + ")",
2232
+ "Invalid head of packet (" + toHex(byte) + ")",
2232
2233
  );
2233
2234
  }
2234
2235
  } else if (inEscape) {
2235
2236
  // part-way through escape sequence
2236
2237
  inEscape = false;
2237
- if (b == 0xdc) {
2238
- partialPacket.push(0xc0);
2239
- } else if (b == 0xdd) {
2240
- partialPacket.push(0xdb);
2238
+ if (byte == this.SLIP_ESC_END) {
2239
+ partialPacket.push(this.SLIP_END);
2240
+ } else if (byte == this.SLIP_ESC_ESC) {
2241
+ partialPacket.push(this.SLIP_ESC);
2241
2242
  } else {
2242
2243
  if (this.debug) {
2243
- this.logger.debug("Read invalid data: " + toHex(b));
2244
+ this.logger.debug("Read invalid data: " + toHex(byte));
2244
2245
  this.logger.debug(
2245
2246
  "Remaining data in serial buffer: " +
2246
2247
  hexFormatter(this._inputBuffer),
2247
2248
  );
2248
2249
  }
2249
2250
  throw new SlipReadError(
2250
- "Invalid SLIP escape (0xdb, " + toHex(b) + ")",
2251
+ "Invalid SLIP escape (0xdb, " + toHex(byte) + ")",
2251
2252
  );
2252
2253
  }
2253
- } else if (b == 0xdb) {
2254
+ } else if (byte == this.SLIP_ESC) {
2254
2255
  // start of escape sequence
2255
2256
  inEscape = true;
2256
- } else if (b == 0xc0) {
2257
+ } else if (byte == this.SLIP_END) {
2257
2258
  // end of packet
2258
2259
  if (this.debug)
2259
2260
  this.logger.debug(
@@ -2264,7 +2265,7 @@ export class ESPLoader extends EventTarget {
2264
2265
  return partialPacket;
2265
2266
  } else {
2266
2267
  // normal byte in packet
2267
- partialPacket.push(b);
2268
+ partialPacket.push(byte);
2268
2269
  }
2269
2270
  }
2270
2271
  }
@@ -2341,9 +2342,9 @@ export class ESPLoader extends EventTarget {
2341
2342
 
2342
2343
  // Track current baudrate for reconnect
2343
2344
  if (this._parent) {
2344
- this._parent._currentBaudRate = baud;
2345
+ this._parent.currentBaudRate = baud;
2345
2346
  } else {
2346
- this._currentBaudRate = baud;
2347
+ this.currentBaudRate = baud;
2347
2348
  }
2348
2349
 
2349
2350
  // Warn if baudrate exceeds USB-Serial chip capability
@@ -2429,8 +2430,8 @@ export class ESPLoader extends EventTarget {
2429
2430
  // Restart Readloop
2430
2431
  this.readLoop();
2431
2432
  } catch (e) {
2432
- this.logger.error(`Reconfigure port error: ${e}`);
2433
- throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
2433
+ // this.logger.error(`Reconfigure port error: ${e}`);
2434
+ // throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
2434
2435
  } finally {
2435
2436
  // Always reset flag, even on error or early return
2436
2437
  this._isReconfiguring = false;
@@ -3138,20 +3139,6 @@ export class ESPLoader extends EventTarget {
3138
3139
  }
3139
3140
  }
3140
3141
 
3141
- private get _currentBaudRate(): number {
3142
- return this._parent
3143
- ? this._parent._currentBaudRate
3144
- : this.__currentBaudRate;
3145
- }
3146
-
3147
- private set _currentBaudRate(value: number) {
3148
- if (this._parent) {
3149
- this._parent._currentBaudRate = value;
3150
- } else {
3151
- this.__currentBaudRate = value;
3152
- }
3153
- }
3154
-
3155
3142
  async writeToStream(data: number[]) {
3156
3143
  if (!this.port.writable) {
3157
3144
  this.logger.debug("Port writable stream not available, skipping write");
@@ -3231,7 +3218,7 @@ export class ESPLoader extends EventTarget {
3231
3218
  return;
3232
3219
  }
3233
3220
  if (!this.port.writable) {
3234
- this.logger.debug("Port already closed, skipping disconnect");
3221
+ // this.logger.debug("Port already closed, skipping disconnect");
3235
3222
  return;
3236
3223
  }
3237
3224
 
@@ -3239,7 +3226,7 @@ export class ESPLoader extends EventTarget {
3239
3226
  try {
3240
3227
  await this._writeChain;
3241
3228
  } catch (err) {
3242
- this.logger.debug(`Pending write error during disconnect: ${err}`);
3229
+ // this.logger.debug(`Pending write error during disconnect: ${err}`);
3243
3230
  }
3244
3231
 
3245
3232
  // Release persistent writer before closing
@@ -3248,7 +3235,7 @@ export class ESPLoader extends EventTarget {
3248
3235
  await this._writer.close();
3249
3236
  this._writer.releaseLock();
3250
3237
  } catch (err) {
3251
- this.logger.debug(`Writer close/release error: ${err}`);
3238
+ // this.logger.debug(`Writer close/release error: ${err}`);
3252
3239
  }
3253
3240
  this._writer = undefined;
3254
3241
  } else {
@@ -3259,7 +3246,7 @@ export class ESPLoader extends EventTarget {
3259
3246
  await writer.close();
3260
3247
  writer.releaseLock();
3261
3248
  } catch (err) {
3262
- this.logger.debug(`Direct writer close error: ${err}`);
3249
+ // this.logger.debug(`Direct writer close error: ${err}`);
3263
3250
  }
3264
3251
  }
3265
3252
 
@@ -3288,7 +3275,7 @@ export class ESPLoader extends EventTarget {
3288
3275
  try {
3289
3276
  this._reader.cancel();
3290
3277
  } catch (err) {
3291
- this.logger.debug(`Reader cancel error: ${err}`);
3278
+ // this.logger.debug(`Reader cancel error: ${err}`);
3292
3279
  // Reader already released, resolve immediately
3293
3280
  clearTimeout(timeout);
3294
3281
  resolve(undefined);
@@ -3328,7 +3315,7 @@ export class ESPLoader extends EventTarget {
3328
3315
  try {
3329
3316
  await this._writeChain;
3330
3317
  } catch (err) {
3331
- this.logger.debug(`Pending write error during release: ${err}`);
3318
+ // this.logger.debug(`Pending write error during release: ${err}`);
3332
3319
  }
3333
3320
 
3334
3321
  // Release writer
@@ -3378,37 +3365,46 @@ export class ESPLoader extends EventTarget {
3378
3365
  /**
3379
3366
  * @name detectUsbConnectionType
3380
3367
  * Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
3381
- * This helper extracts the detection logic from initialize() for reuse
3368
+ * Uses USB PID (Product ID) for reliable detection
3382
3369
  * @returns true if USB-JTAG or USB-OTG, false if external serial chip
3383
- * @throws Error if detection fails and chipFamily is not set
3370
+ * @throws Error if chipFamily is not set
3384
3371
  */
3385
3372
  private async detectUsbConnectionType(): Promise<boolean> {
3386
3373
  if (!this.chipFamily) {
3387
3374
  throw new Error("Cannot detect USB connection type: chipFamily not set");
3388
3375
  }
3389
3376
 
3390
- if (
3391
- this.chipFamily === CHIP_FAMILY_ESP32S2 ||
3392
- this.chipFamily === CHIP_FAMILY_ESP32S3
3393
- ) {
3394
- const isUsingUsbOtg = await this.usingUsbOtg();
3395
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
3396
- return isUsingUsbOtg || isUsingUsbJtagSerial;
3397
- } else if (
3398
- this.chipFamily === CHIP_FAMILY_ESP32C3 ||
3399
- this.chipFamily === CHIP_FAMILY_ESP32C5 ||
3400
- this.chipFamily === CHIP_FAMILY_ESP32C6
3401
- ) {
3402
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
3403
- return isUsingUsbJtagSerial;
3404
- } else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
3405
- const isUsingUsbOtg = await this.usingUsbOtg();
3406
- const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
3407
- return isUsingUsbOtg || isUsingUsbJtagSerial;
3408
- } else {
3409
- // Other chips don't have USB-JTAG/OTG
3377
+ // Use PID-based detection (most reliable method)
3378
+ const portInfo = this.port.getInfo();
3379
+ const pid = portInfo.usbProductId;
3380
+ const vid = portInfo.usbVendorId;
3381
+
3382
+ this.logger.debug(
3383
+ `USB detection: VID=0x${vid?.toString(16)}, PID=0x${pid?.toString(16)}`,
3384
+ );
3385
+
3386
+ // Check if this is an Espressif device
3387
+ const isEspressif = vid === 0x303a;
3388
+
3389
+ if (!isEspressif) {
3390
+ this.logger.debug("Not Espressif VID - external serial chip");
3410
3391
  return false;
3411
3392
  }
3393
+
3394
+ // ESP32-S2/S3/C3/C6/H2 USB-JTAG/OTG PIDs
3395
+ // 0x1001 = ESP32-S3 USB-JTAG
3396
+ // 0x0002 = ESP32-S2 USB-JTAG
3397
+ // 0x1000 = ESP32-C3 USB-JTAG
3398
+ // 0x4008 = ESP32-C6 USB-JTAG
3399
+ // 0x4009 = ESP32-H2 USB-JTAG
3400
+ const usbJtagPids = [0x1001, 0x0002, 0x1000, 0x4008, 0x4009];
3401
+ const isUsbJtag = usbJtagPids.includes(pid || 0);
3402
+
3403
+ this.logger.debug(
3404
+ `USB-JTAG/OTG detection: ${isUsbJtag ? "YES" : "NO"} (PID=0x${pid?.toString(16)})`,
3405
+ );
3406
+
3407
+ return isUsbJtag;
3412
3408
  }
3413
3409
 
3414
3410
  /**
@@ -3442,6 +3438,8 @@ export class ESPLoader extends EventTarget {
3442
3438
  isUsbJtag = this.isUsbJtagOrOtg;
3443
3439
  }
3444
3440
 
3441
+ // Release reader/writer so console can create new ones
3442
+ // This is needed for Desktop (Web Serial) to unlock streams
3445
3443
  if (isUsbJtag) {
3446
3444
  // USB-JTAG/OTG devices: Use watchdog reset which closes port
3447
3445
  const wasReset = await this._resetToFirmwareIfNeeded();
@@ -3463,6 +3461,18 @@ export class ESPLoader extends EventTarget {
3463
3461
  this.logger.debug(`Could not reset device: ${err}`);
3464
3462
  }
3465
3463
 
3464
+ // For WebUSB (Android), recreate streams after hardware reset
3465
+ if (this.isWebUSB()) {
3466
+ try {
3467
+ // Use the public recreateStreams() method to safely recreate streams
3468
+ // without closing the port (important after hardware reset)
3469
+ await (this.port as any).recreateStreams();
3470
+ this.logger.debug("WebUSB streams recreated for console mode");
3471
+ } catch (err) {
3472
+ this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
3473
+ }
3474
+ }
3475
+
3466
3476
  return false; // Port stays open
3467
3477
  }
3468
3478
  }
@@ -3488,7 +3498,7 @@ export class ESPLoader extends EventTarget {
3488
3498
  : "USB-JTAG/Serial";
3489
3499
 
3490
3500
  this.logger.log(
3491
- `Resetting ${this.chipFamily} (${resetMethod}) to boot into firmware...`,
3501
+ `Resetting ${this.chipName || "device"} (${resetMethod}) to boot into firmware...`,
3492
3502
  );
3493
3503
 
3494
3504
  // Set console mode flag before reset to prevent subsequent hardReset calls
@@ -3567,7 +3577,7 @@ export class ESPLoader extends EventTarget {
3567
3577
 
3568
3578
  try {
3569
3579
  this.logger.log("Reconnecting serial port...");
3570
- const savedBaudRate = this._currentBaudRate;
3580
+ const savedBaudRate = this.currentBaudRate;
3571
3581
 
3572
3582
  this.connected = false;
3573
3583
  this.__inputBuffer = [];
@@ -3616,7 +3626,7 @@ export class ESPLoader extends EventTarget {
3616
3626
  try {
3617
3627
  await this.port.open({ baudRate: ESP_ROM_BAUD });
3618
3628
  this.connected = true;
3619
- this._currentBaudRate = ESP_ROM_BAUD;
3629
+ this.currentBaudRate = ESP_ROM_BAUD;
3620
3630
  } catch (err) {
3621
3631
  throw new Error(`Failed to open port: ${err}`);
3622
3632
  }
@@ -3758,7 +3768,7 @@ export class ESPLoader extends EventTarget {
3758
3768
  try {
3759
3769
  await this.port.open({ baudRate: ESP_ROM_BAUD });
3760
3770
  this.connected = true;
3761
- this._currentBaudRate = ESP_ROM_BAUD;
3771
+ this.currentBaudRate = ESP_ROM_BAUD;
3762
3772
  } catch (err) {
3763
3773
  throw new Error(`Failed to open port: ${err}`);
3764
3774
  }
@@ -3805,6 +3815,115 @@ export class ESPLoader extends EventTarget {
3805
3815
  }
3806
3816
  }
3807
3817
 
3818
+ /**
3819
+ * @name exitConsoleMode
3820
+ * Exit console mode and return to bootloader
3821
+ * For ESP32-S2, uses reconnectToBootloader which will trigger port change
3822
+ * @returns true if manual reconnection is needed (ESP32-S2), false otherwise
3823
+ */
3824
+ async exitConsoleMode(): Promise<boolean> {
3825
+ if (this._parent) {
3826
+ return await this._parent.exitConsoleMode();
3827
+ }
3828
+
3829
+ // Clear console mode flag
3830
+ this._consoleMode = false;
3831
+
3832
+ // Check if this is ESP32-S2 with USB-JTAG/OTG
3833
+ const isESP32S2 = this.chipFamily === CHIP_FAMILY_ESP32S2;
3834
+
3835
+ // For ESP32-S2: if _isUsbJtagOrOtg is undefined, try to detect it
3836
+ // If detection fails or is undefined, assume USB-JTAG/OTG (conservative/safe path)
3837
+ let isUsbJtagOrOtg = this._isUsbJtagOrOtg;
3838
+ if (isESP32S2 && isUsbJtagOrOtg === undefined) {
3839
+ try {
3840
+ isUsbJtagOrOtg = await this.detectUsbConnectionType();
3841
+ } catch (err) {
3842
+ this.logger.debug(
3843
+ `USB detection failed, assuming USB-JTAG/OTG for ESP32-S2: ${err}`,
3844
+ );
3845
+ isUsbJtagOrOtg = true; // Conservative fallback for ESP32-S2
3846
+ }
3847
+ }
3848
+
3849
+ if (isESP32S2 && isUsbJtagOrOtg) {
3850
+ // ESP32-S2 USB: Use reconnectToBootloader which handles the mode switch
3851
+ // This will close the port and the device will reboot to bootloader
3852
+ this.logger.log("ESP32-S2 USB detected - reconnecting to bootloader");
3853
+
3854
+ try {
3855
+ await this.reconnectToBootloader();
3856
+ } catch (err) {
3857
+ this.logger.debug(`Reconnect error (expected for ESP32-S2): ${err}`);
3858
+ }
3859
+
3860
+ // For ESP32-S2, port will change, so return true to indicate manual reconnection needed
3861
+ return true;
3862
+ }
3863
+
3864
+ // For other devices, use standard reconnectToBootloader
3865
+ await this.reconnectToBootloader();
3866
+ return false; // No manual reconnection needed
3867
+ }
3868
+
3869
+ /**
3870
+ * @name isConsoleResetSupported
3871
+ * Check if console reset is supported for this device
3872
+ * ESP32-S2 USB-JTAG/CDC does not support reset in console mode
3873
+ * because any reset causes USB port to be lost (hardware limitation)
3874
+ */
3875
+ isConsoleResetSupported(): boolean {
3876
+ if (this._parent) {
3877
+ return this._parent.isConsoleResetSupported();
3878
+ }
3879
+
3880
+ // For ESP32-S2: if _isUsbJtagOrOtg is undefined, assume USB-JTAG/OTG (conservative)
3881
+ // This means console reset is NOT supported (safer default)
3882
+ const isS2UsbJtag =
3883
+ this.chipFamily === CHIP_FAMILY_ESP32S2 &&
3884
+ (this._isUsbJtagOrOtg === true || this._isUsbJtagOrOtg === undefined);
3885
+ return !isS2UsbJtag; // Not supported for ESP32-S2 USB-JTAG/CDC
3886
+ }
3887
+
3888
+ /**
3889
+ * @name resetInConsoleMode
3890
+ * Reset device while in console mode (firmware mode)
3891
+ *
3892
+ * NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
3893
+ * the USB port to be lost because the device switches USB modes during reset.
3894
+ * This is a hardware limitation - use isConsoleResetSupported() to check first.
3895
+ */
3896
+ async resetInConsoleMode(): Promise<void> {
3897
+ if (this._parent) {
3898
+ return await this._parent.resetInConsoleMode();
3899
+ }
3900
+
3901
+ if (!this.isConsoleResetSupported()) {
3902
+ this.logger.debug(
3903
+ "Console reset not supported for ESP32-S2 USB-JTAG/CDC",
3904
+ );
3905
+ return; // Do nothing
3906
+ }
3907
+
3908
+ // For other devices: Use standard firmware reset
3909
+ const isWebUSB = (this.port as any).isWebUSB === true;
3910
+
3911
+ try {
3912
+ this.logger.debug("Resetting device in console mode");
3913
+
3914
+ if (isWebUSB) {
3915
+ await this.hardResetToFirmwareWebUSB();
3916
+ } else {
3917
+ await this.hardResetToFirmware();
3918
+ }
3919
+
3920
+ this.logger.debug("Device reset complete");
3921
+ } catch (err) {
3922
+ this.logger.error(`Reset failed: ${err}`);
3923
+ throw err;
3924
+ }
3925
+ }
3926
+
3808
3927
  /**
3809
3928
  * @name drainInputBuffer
3810
3929
  * Actively drain the input buffer by reading data for a specified time.
@@ -3820,7 +3939,7 @@ export class ESPLoader extends EventTarget {
3820
3939
  await sleep(bufferingTime);
3821
3940
 
3822
3941
  // Unsupported command response is sent 8 times and has
3823
- // 14 bytes length including delimiter 0xC0 bytes.
3942
+ // 14 bytes length including delimiter SLIP_END (0xC0) bytes.
3824
3943
  // At least part of it is read as a command response,
3825
3944
  // but to be safe, read it all.
3826
3945
  const bytesToDrain = 14 * 8;
@@ -4040,7 +4159,7 @@ export class ESPLoader extends EventTarget {
4040
4159
  // The stub expects 4 bytes (ACK), if we send less it will break out
4041
4160
  try {
4042
4161
  // Send SLIP frame with no data (just delimiters)
4043
- const abortFrame = [0xc0, 0xc0]; // Empty SLIP frame
4162
+ const abortFrame = [this.SLIP_END, this.SLIP_END]; // Empty SLIP frame
4044
4163
  await this.writeToStream(abortFrame);
4045
4164
  this.logger.debug(`Sent abort frame to stub`);
4046
4165
 
package/src/index.ts CHANGED
@@ -99,3 +99,6 @@ export const connectWithPort = async (port: SerialPort, logger: Logger) => {
99
99
 
100
100
  return new ESPLoader(port, logger);
101
101
  };
102
+
103
+ // Export utility functions for use in UI code
104
+ export { toHex, sleep, hexFormatter, formatMacAddr } from "./util";
package/src/util.ts CHANGED
@@ -45,5 +45,14 @@ export const toHex = (value: number, size = 2) => {
45
45
  }
46
46
  };
47
47
 
48
+ /**
49
+ * Format MAC address array to string (e.g., [0xAA, 0xBB, 0xCC] -> "AA:BB:CC:DD:EE:FF")
50
+ */
51
+ export const formatMacAddr = (macAddr: number[]): string => {
52
+ return macAddr
53
+ .map((value) => value.toString(16).toUpperCase().padStart(2, "0"))
54
+ .join(":");
55
+ };
56
+
48
57
  export const sleep = (ms: number) =>
49
58
  new Promise((resolve) => setTimeout(resolve, ms));