Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
41 changes: 41 additions & 0 deletions src/serialPortManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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<void> =>
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<void>((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<void> {
if (!this.port || !this._isConnected) {
throw new Error('Serial port not connected');
Expand Down
27 changes: 27 additions & 0 deletions src/webviewPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -1669,6 +1680,7 @@ export class EspDecoderWebviewPanel implements vscode.WebviewViewProvider {
<div class="toolbar-group">
<button id="btn-connect" title="Connect to serial port">Connect</button>
<button id="btn-disconnect" title="Disconnect from serial port" disabled>Disconnect</button>
<button id="btn-reset" class="secondary" title="Hard-reset the chip (toggles RTS/EN, same as esptool reset_chip hard-reset)" disabled>Reset</button>
</div>
<div class="toolbar-separator"></div>
<div class="toolbar-group">
Expand Down Expand Up @@ -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' });
});
Expand Down Expand Up @@ -2584,19 +2608,22 @@ 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';
text.textContent = 'Connected: ' + (port || '?') + ' @ ' + (baudRate || '?');
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) {
Expand Down