diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f86100..53ef092 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Changelog -## [0.26.5] - 2026-05-xx +## [0.27.0] - 2026-05-18 -### New Feature** - - **Filter save** - operations now provide visual feedback to users, confirming successful saves with "Saved ✓" messages or displaying error details if an issue occurs. - - Save button shows a disabled state while processing and displays timed feedback that clearly indicates whether the operation succeeded or failed. +### New Feature + - **Reset button** - new toolbar button performs a hard-reset of the connected ESP chip by toggling the RTS/EN line, mirroring `esptool reset_chip("hard-reset")`. Enabled while connected; disabled otherwise. + - **Filter save** - operations now provide visual feedback to users, confirming successful saves with "Saved ✓" messages or displaying error details if an issue occurs. Save button shows a disabled state while processing and displays timed feedback that clearly indicates whether the operation succeeded or failed. ## [0.26.4] - 2026-05-18 diff --git a/package-lock.json b/package-lock.json index 35893dc..7e3b359 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "esp-decoder", - "version": "0.26.4", + "version": "0.27.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "esp-decoder", - "version": "0.26.4", + "version": "0.27.0", "license": "GPL-3.0", "dependencies": { "serialport": "^13.0.0" diff --git a/package.json b/package.json index 120aaa3..e8f2794 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "esp-decoder", "displayName": "ESP Crash Decoder", "description": "Decode ESP32 / ESP8266 crash dumps from serial port", - "version": "0.26.4", + "version": "0.27.0", "publisher": "Jason2866", "license": "GPL-3.0", "icon": "assets/icon_large.png", diff --git a/src/serialPortManager.ts b/src/serialPortManager.ts index 1c0afdc..63a67ad 100644 --- a/src/serialPortManager.ts +++ b/src/serialPortManager.ts @@ -459,6 +459,47 @@ export class SerialPortManager extends vscode.Disposable { }); } + /** + * Perform a hard reset of the connected ESP chip by toggling the RTS line + * (which is wired to the chip's EN/RESET pin on the standard auto-reset + * circuit used by virtually every ESP dev-board). + * + * Mirrors esptool's `HardReset` strategy + * (https://github.com/espressif/esptool/blob/af0787da2cccaf68080d8032cfaf4ce918c3037d/esptool/reset.py) + * and is invoked by `esptool reset_chip("hard-reset")`: + * RTS = True → EN low (chip held in reset) + * sleep 100 ms + * RTS = False → EN high (chip released, boots normally) + * + * DTR is explicitly held LOW (false) the whole time so the two-transistor + * auto-reset circuit only pulls EN — pulling DTR high (the node-serialport + * default for unspecified flags is `dtr: true`) would assert IO0 LOW and + * drop the chip into the ROM bootloader instead of doing a normal boot. + * + * Note: on chips that talk via native USB-CDC (ESP32-S2/S3/C3/P4 when no + * USB-UART bridge is involved) the reset may not work. The port disappears when + * the chip resets, and ESP-Decoder tries to auto-reconnect to the same port before the reset. + */ + async hardReset(): Promise { + if (!this.port || !this._isConnected) { + throw new Error('Serial port not connected'); + } + const port = this.port; + // Always pass BOTH dtr and rts: node-serialport's set() resets every + // unspecified flag to its default (dtr: true, rts: true). Letting dtr + // default to true would assert IO0 LOW via the auto-reset circuit and + // send the chip into download mode instead of resetting it. + const setSignals = (dtr: boolean, rts: boolean): Promise => + new Promise((resolve, reject) => { + port.set({ dtr, rts }, (err) => (err ? reject(err) : resolve())); + }); + this.log.appendLine('[ESP Decoder] hard-reset: RTS=1 DTR=0 (EN low, IO0 high)'); + await setSignals(false, true); + await new Promise((r) => setTimeout(r, 100)); + this.log.appendLine('[ESP Decoder] hard-reset: RTS=0 DTR=0 (EN high, IO0 high)'); + await setSignals(false, false); + } + async sendData(data: string): Promise { if (!this.port || !this._isConnected) { throw new Error('Serial port not connected'); diff --git a/src/webviewPanel.ts b/src/webviewPanel.ts index b030e78..8add8b7 100644 --- a/src/webviewPanel.ts +++ b/src/webviewPanel.ts @@ -433,6 +433,17 @@ export class EspDecoderWebviewPanel implements vscode.WebviewViewProvider { this.syncState(); break; } + case 'hardReset': { + try { + await this.serialManager.hardReset(); + } catch (err) { + this.postMessage({ + type: 'error', + message: `Reset error: ${err instanceof Error ? err.message : String(err)}`, + }); + } + break; + } case 'selectPort': { const port = await this.serialManager.selectPort(); this.syncState(); @@ -1669,6 +1680,7 @@ export class EspDecoderWebviewPanel implements vscode.WebviewViewProvider {
+
@@ -2103,6 +2115,18 @@ export class EspDecoderWebviewPanel implements vscode.WebviewViewProvider { vscode.postMessage({ type: 'disconnect' }); }); + document.getElementById('btn-reset').addEventListener('click', () => { + const btn = document.getElementById('btn-reset'); + vscode.postMessage({ type: 'hardReset' }); + const originalText = btn.textContent; + btn.classList.add('feedback-saved'); + btn.textContent = 'Reset \u2713'; + setTimeout(function() { + btn.classList.remove('feedback-saved'); + btn.textContent = originalText; + }, 600); + }); + document.getElementById('btn-elf').addEventListener('click', () => { vscode.postMessage({ type: 'selectElf' }); }); @@ -2584,6 +2608,7 @@ export class EspDecoderWebviewPanel implements vscode.WebviewViewProvider { const text = document.getElementById('status-text'); const btnConnect = document.getElementById('btn-connect'); const btnDisconnect = document.getElementById('btn-disconnect'); + const btnReset = document.getElementById('btn-reset'); if (isConnected) { dot.className = 'status-indicator connected'; @@ -2591,12 +2616,14 @@ export class EspDecoderWebviewPanel implements vscode.WebviewViewProvider { btnConnect.textContent = 'Connect'; btnConnect.disabled = true; btnDisconnect.disabled = false; + btnReset.disabled = false; } else { dot.className = 'status-indicator disconnected'; text.textContent = 'Disconnected'; btnConnect.textContent = 'Connect'; btnConnect.disabled = false; btnDisconnect.disabled = true; + btnReset.disabled = true; } if (port) {