esp32tool 1.6.1 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esp32tool",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "type": "module",
5
5
  "description": "Flash & Read ESP devices using WebSerial, Electron, and also Android mobile via WebUSB",
6
6
  "main": "electron/main.cjs",
@@ -49,27 +49,27 @@
49
49
  "@electron-forge/maker-zip": "^7.11.1",
50
50
  "@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
51
51
  "@electron/fuses": "^2.0.0",
52
- "@eslint/js": "^9.39.2",
52
+ "@eslint/js": "^9.39.3",
53
53
  "@rollup/plugin-json": "^6.1.0",
54
54
  "@rollup/plugin-node-resolve": "^16.0.0",
55
55
  "@rollup/plugin-terser": "^0.4.4",
56
56
  "@rollup/plugin-typescript": "^12.3.0",
57
- "@types/node": "^25.2.3",
57
+ "@types/node": "^25.3.0",
58
58
  "@types/pako": "^2.0.4",
59
59
  "@types/serialport": "^10.2.0",
60
60
  "@types/w3c-web-serial": "^1.0.7",
61
61
  "archiver": "^7.0.1",
62
- "electron": "^40.4.1",
62
+ "electron": "^40.6.0",
63
63
  "electron-squirrel-startup": "^1.0.1",
64
- "eslint": "^9.39.2",
64
+ "eslint": "^10.0.2",
65
65
  "eslint-config-prettier": "^10.1.8",
66
66
  "eslint-plugin-prettier": "^5.5.5",
67
67
  "npm-run-all": "^4.1.5",
68
68
  "prettier": "^3.8.1",
69
- "rollup": "^4.57.0",
69
+ "rollup": "^4.59.0",
70
70
  "serve": "^14.2.5",
71
71
  "typescript": "^5.7.3",
72
- "typescript-eslint": "^8.56.0"
72
+ "typescript-eslint": "^8.56.1"
73
73
  },
74
74
  "dependencies": {
75
75
  "pako": "^2.1.0",
@@ -79,8 +79,10 @@
79
79
  "overrides": {
80
80
  "tmp": "^0.2.4",
81
81
  "tar": "^7.5.6",
82
- "ajv": "^8.18.0",
83
82
  "minimatch": "^10.2.1",
83
+ "serve": {
84
+ "ajv": "^8.18.0"
85
+ },
84
86
  "@electron/asar": "^4.0.1"
85
87
  }
86
88
  }
Binary file
Binary file
package/src/esp_loader.ts CHANGED
@@ -1161,14 +1161,11 @@ export class ESPLoader extends EventTarget {
1161
1161
  * Classic reset with shorter delays for WebUSB (Android)
1162
1162
  */
1163
1163
  async hardResetClassicShortDelayWebUSB() {
1164
- await this.setDTRWebUSB(false); // IO0=HIGH
1165
- await this.setRTSWebUSB(true); // EN=LOW, chip in reset
1166
- await sleep(50);
1167
- await this.setDTRWebUSB(true); // IO0=LOW
1168
- await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset
1169
- await sleep(25);
1170
- await this.setDTRWebUSB(false); // IO0=HIGH, done
1171
- await sleep(100);
1164
+ await this.runSignalSequence([
1165
+ { dtr: false, rts: true, delayMs: 50 },
1166
+ { dtr: true, rts: false, delayMs: 25 },
1167
+ { dtr: false, delayMs: 100 },
1168
+ ]);
1172
1169
  }
1173
1170
 
1174
1171
  /**
@@ -1176,14 +1173,11 @@ export class ESPLoader extends EventTarget {
1176
1173
  * Inverted reset sequence for WebUSB (Android) - both signals inverted
1177
1174
  */
1178
1175
  async hardResetInvertedWebUSB() {
1179
- await this.setDTRWebUSB(true); // IO0=HIGH (inverted)
1180
- await this.setRTSWebUSB(false); // EN=LOW, chip in reset (inverted)
1181
- await sleep(100);
1182
- await this.setDTRWebUSB(false); // IO0=LOW (inverted)
1183
- await this.setRTSWebUSB(true); // EN=HIGH, chip out of reset (inverted)
1184
- await sleep(50);
1185
- await this.setDTRWebUSB(true); // IO0=HIGH, done (inverted)
1186
- await sleep(200);
1176
+ await this.runSignalSequence([
1177
+ { dtr: true, rts: false, delayMs: 100 },
1178
+ { dtr: false, rts: true, delayMs: 50 },
1179
+ { dtr: true, delayMs: 200 },
1180
+ ]);
1187
1181
  }
1188
1182
 
1189
1183
  /**
@@ -1191,14 +1185,11 @@ export class ESPLoader extends EventTarget {
1191
1185
  * Only DTR inverted for WebUSB (Android)
1192
1186
  */
1193
1187
  async hardResetInvertedDTRWebUSB() {
1194
- await this.setDTRWebUSB(true); // IO0=HIGH (DTR inverted)
1195
- await this.setRTSWebUSB(true); // EN=LOW, chip in reset (RTS normal)
1196
- await sleep(100);
1197
- await this.setDTRWebUSB(false); // IO0=LOW (DTR inverted)
1198
- await this.setRTSWebUSB(false); // EN=HIGH, chip out of reset (RTS normal)
1199
- await sleep(50);
1200
- await this.setDTRWebUSB(true); // IO0=HIGH, done (DTR inverted)
1201
- await sleep(200);
1188
+ await this.runSignalSequence([
1189
+ { dtr: true, rts: true, delayMs: 100 },
1190
+ { dtr: false, rts: false, delayMs: 50 },
1191
+ { dtr: true, delayMs: 200 },
1192
+ ]);
1202
1193
  }
1203
1194
 
1204
1195
  /**
@@ -1206,14 +1197,11 @@ export class ESPLoader extends EventTarget {
1206
1197
  * Only RTS inverted for WebUSB (Android)
1207
1198
  */
1208
1199
  async hardResetInvertedRTSWebUSB() {
1209
- await this.setDTRWebUSB(false); // IO0=HIGH (DTR normal)
1210
- await this.setRTSWebUSB(false); // EN=LOW, chip in reset (RTS inverted)
1211
- await sleep(100);
1212
- await this.setDTRWebUSB(true); // IO0=LOW (DTR normal)
1213
- await this.setRTSWebUSB(true); // EN=HIGH, chip out of reset (RTS inverted)
1214
- await sleep(50);
1215
- await this.setDTRWebUSB(false); // IO0=HIGH, done (DTR normal)
1216
- await sleep(200);
1200
+ await this.runSignalSequence([
1201
+ { dtr: false, rts: false, delayMs: 100 },
1202
+ { dtr: true, rts: true, delayMs: 50 },
1203
+ { dtr: false, delayMs: 200 },
1204
+ ]);
1217
1205
  }
1218
1206
 
1219
1207
  /**
package/sw.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Service Worker for ESP32Tool PWA
2
- const CACHE_NAME = 'esp32tool-v1.6.1';
2
+ const CACHE_NAME = 'esp32tool-v1.6.2';
3
3
  const RUNTIME_CACHE = 'esp32tool-runtime';
4
4
 
5
5
  // Core files to cache on install (relative paths work for any deployment path)
package/electron/main.js DELETED
@@ -1,338 +0,0 @@
1
- const { app, BrowserWindow, Menu, session, ipcMain, dialog } = require('electron');
2
- const path = require('path');
3
- const fs = require('fs');
4
-
5
- // Handle creating/removing shortcuts on Windows when installing/uninstalling.
6
- // Only required for Windows Squirrel installer
7
- if (process.platform === 'win32') {
8
- try {
9
- if (require('electron-squirrel-startup')) {
10
- app.quit();
11
- }
12
- } catch (e) {
13
- // Module not available, ignore
14
- }
15
- }
16
-
17
- let mainWindow;
18
-
19
- // Store granted serial port devices
20
- const grantedDevices = new Map();
21
-
22
- function createWindow() {
23
- mainWindow = new BrowserWindow({
24
- width: 1200,
25
- height: 800,
26
- minWidth: 800,
27
- minHeight: 600,
28
- webPreferences: {
29
- nodeIntegration: false,
30
- contextIsolation: true,
31
- preload: path.join(__dirname, 'preload.js'),
32
- },
33
- icon: path.join(__dirname, 'icons', 'icon.png'),
34
- title: 'ESP32Tool',
35
- autoHideMenuBar: false,
36
- });
37
-
38
- // Load the index.html of the app
39
- mainWindow.loadFile(path.join(__dirname, '..', 'index.html'));
40
-
41
- // Open DevTools in development
42
- if (process.env.NODE_ENV === 'development') {
43
- mainWindow.webContents.openDevTools();
44
- }
45
-
46
- mainWindow.on('closed', () => {
47
- mainWindow = null;
48
- });
49
-
50
- // Setup serial port handlers for this window's session
51
- setupSerialPortHandlers(mainWindow.webContents.session);
52
- }
53
-
54
- function setupSerialPortHandlers(ses) {
55
- let lastSelectedPort = null;
56
- let esp32s2ReconnectPending = false;
57
- let portSelectionQueue = [];
58
-
59
- // Handle serial port selection - shows when navigator.serial.requestPort() is called
60
- ses.on('select-serial-port', (event, portList, webContents, callback) => {
61
- event.preventDefault();
62
-
63
- console.log('Available serial ports:', portList.map(p => ({
64
- portId: p.portId,
65
- portName: p.portName,
66
- displayName: p.displayName
67
- })));
68
-
69
- if (portList && portList.length > 0) {
70
- // Try to find ESP-compatible port
71
- const espPort = portList.find(port => {
72
- const name = (port.displayName || port.portName || '').toLowerCase();
73
- return name.includes('cp2102') ||
74
- name.includes('cp2103') ||
75
- name.includes('cp2104') ||
76
- name.includes('cp2105') ||
77
- name.includes('cp2108') ||
78
- name.includes('ch9102') ||
79
- name.includes('ch9104') ||
80
- name.includes('ch340') ||
81
- name.includes('ch341') ||
82
- name.includes('ch343') ||
83
- name.includes('ftdi') ||
84
- name.includes('usb') ||
85
- name.includes('uart') ||
86
- name.includes('silicon labs') ||
87
- name.includes('esp');
88
- });
89
-
90
- // Select ESP-compatible port or first available
91
- const selectedPort = espPort || portList[0];
92
- console.log('Selected port:', selectedPort.portId, selectedPort.displayName || selectedPort.portName);
93
- lastSelectedPort = selectedPort;
94
-
95
- callback(selectedPort.portId);
96
- } else {
97
- console.log('No serial ports available - queuing selection');
98
- // No ports available yet - queue this callback for when a port appears
99
- portSelectionQueue.push(callback);
100
- }
101
- });
102
-
103
- // Track port additions - handle ESP32-S2 reconnect
104
- ses.on('serial-port-added', (event, port) => {
105
- console.log('Serial port added:', port);
106
-
107
- // If we have queued port selections, handle them now
108
- if (portSelectionQueue.length > 0) {
109
- console.log('Processing queued port selection');
110
- const callback = portSelectionQueue.shift();
111
- callback(port.portId);
112
- lastSelectedPort = port;
113
- }
114
-
115
- // Check if this looks like an ESP32-S2 CDC port appearing after ROM port disappeared
116
- if (lastSelectedPort && port.portName !== lastSelectedPort.portName) {
117
- const name = (port.displayName || port.portName || '').toLowerCase();
118
- if (name.includes('esp') || name.includes('usb') || name.includes('uart')) {
119
- console.log('ESP32-S2 reconnect detected - new CDC port available');
120
- esp32s2ReconnectPending = true;
121
- }
122
- }
123
- });
124
-
125
- // Track port removals - detect ESP32-S2 disconnect
126
- ses.on('serial-port-removed', (event, port) => {
127
- console.log('Serial port removed:', port);
128
-
129
- // If the last selected port was removed, prepare for reconnect
130
- if (lastSelectedPort && port.portId === lastSelectedPort.portId) {
131
- console.log('Last selected port removed - may be ESP32-S2 mode switch');
132
- // Don't clear lastSelectedPort yet, we might need it for comparison
133
- }
134
- });
135
-
136
- // Grant permission for serial port access checks
137
- ses.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
138
- if (permission === 'serial') {
139
- return true;
140
- }
141
- return true;
142
- });
143
-
144
- // Handle device permission requests
145
- ses.setDevicePermissionHandler((details) => {
146
- if (details.deviceType === 'serial') {
147
- if (details.device) {
148
- grantedDevices.set(details.device.deviceId, details.device);
149
- }
150
- return true;
151
- }
152
- return true;
153
- });
154
- }
155
-
156
- // Create application menu
157
- function createMenu() {
158
- const template = [
159
- {
160
- label: 'File',
161
- submenu: [
162
- { role: 'quit' }
163
- ]
164
- },
165
- {
166
- label: 'Edit',
167
- submenu: [
168
- { role: 'undo' },
169
- { role: 'redo' },
170
- { type: 'separator' },
171
- { role: 'cut' },
172
- { role: 'copy' },
173
- { role: 'paste' },
174
- { role: 'selectAll' }
175
- ]
176
- },
177
- {
178
- label: 'View',
179
- submenu: [
180
- { role: 'reload' },
181
- { role: 'forceReload' },
182
- { role: 'toggleDevTools' },
183
- { type: 'separator' },
184
- { role: 'resetZoom' },
185
- { role: 'zoomIn' },
186
- { role: 'zoomOut' },
187
- { type: 'separator' },
188
- { role: 'togglefullscreen' }
189
- ]
190
- },
191
- {
192
- label: 'Help',
193
- submenu: [
194
- {
195
- label: 'About ESP32Tool',
196
- click: async () => {
197
- const { shell } = require('electron');
198
- await shell.openExternal('https://github.com/Jason2866/esp32tool');
199
- }
200
- },
201
- {
202
- label: 'ESP32 Documentation',
203
- click: async () => {
204
- const { shell } = require('electron');
205
- await shell.openExternal('https://docs.espressif.com/');
206
- }
207
- }
208
- ]
209
- }
210
- ];
211
-
212
- // macOS specific menu adjustments
213
- if (process.platform === 'darwin') {
214
- template.unshift({
215
- label: app.getName(),
216
- submenu: [
217
- { role: 'about' },
218
- { type: 'separator' },
219
- { role: 'services' },
220
- { type: 'separator' },
221
- { role: 'hide' },
222
- { role: 'hideOthers' },
223
- { role: 'unhide' },
224
- { type: 'separator' },
225
- { role: 'quit' }
226
- ]
227
- });
228
- }
229
-
230
- const menu = Menu.buildFromTemplate(template);
231
- Menu.setApplicationMenu(menu);
232
- }
233
-
234
- // This method will be called when Electron has finished initialization
235
- app.whenReady().then(() => {
236
- createMenu();
237
- createWindow();
238
-
239
- app.on('activate', () => {
240
- // On macOS re-create a window when dock icon is clicked and no windows are open
241
- if (BrowserWindow.getAllWindows().length === 0) {
242
- createWindow();
243
- }
244
- });
245
- });
246
-
247
- // Quit when all windows are closed, except on macOS
248
- app.on('window-all-closed', () => {
249
- if (process.platform !== 'darwin') {
250
- app.quit();
251
- }
252
- });
253
-
254
- // ============================================
255
- // IPC Handlers for File Operations
256
- // ============================================
257
-
258
- // Save file dialog and write data
259
- ipcMain.handle('save-file', async (event, { data, defaultFilename, filters }) => {
260
- const result = await dialog.showSaveDialog(mainWindow, {
261
- defaultPath: defaultFilename,
262
- filters: filters || [
263
- { name: 'Binary Files', extensions: ['bin'] },
264
- { name: 'All Files', extensions: ['*'] }
265
- ]
266
- });
267
-
268
- if (result.canceled || !result.filePath) {
269
- return { success: false, canceled: true };
270
- }
271
-
272
- try {
273
- // Convert data to Buffer if it's a Uint8Array
274
- const buffer = Buffer.from(data);
275
- fs.writeFileSync(result.filePath, buffer);
276
- return { success: true, filePath: result.filePath };
277
- } catch (error) {
278
- return { success: false, error: error.message };
279
- }
280
- });
281
-
282
- // Open file dialog and read data
283
- ipcMain.handle('open-file', async (event, { filters }) => {
284
- const result = await dialog.showOpenDialog(mainWindow, {
285
- properties: ['openFile'],
286
- filters: filters || [
287
- { name: 'Binary Files', extensions: ['bin'] },
288
- { name: 'All Files', extensions: ['*'] }
289
- ]
290
- });
291
-
292
- if (result.canceled || result.filePaths.length === 0) {
293
- return { success: false, canceled: true };
294
- }
295
-
296
- try {
297
- const filePath = result.filePaths[0];
298
- const data = fs.readFileSync(filePath);
299
- const filename = path.basename(filePath);
300
- return {
301
- success: true,
302
- filePath,
303
- filename,
304
- data: Array.from(data) // Convert Buffer to array for IPC transfer
305
- };
306
- } catch (error) {
307
- return { success: false, error: error.message };
308
- }
309
- });
310
-
311
- // Show input dialog (replacement for prompt())
312
- ipcMain.handle('show-prompt', async (event, { message, defaultValue }) => {
313
- // Use a simple approach - return the default value
314
- // In Electron, we use the save dialog instead of prompt
315
- return defaultValue;
316
- });
317
-
318
- // Show message box
319
- ipcMain.handle('show-message', async (event, { type, title, message, buttons }) => {
320
- const result = await dialog.showMessageBox(mainWindow, {
321
- type: type || 'info',
322
- title: title || 'ESP32Tool',
323
- message: message,
324
- buttons: buttons || ['OK']
325
- });
326
- return result.response;
327
- });
328
-
329
- // Show confirm dialog
330
- ipcMain.handle('show-confirm', async (event, { message }) => {
331
- const result = await dialog.showMessageBox(mainWindow, {
332
- type: 'question',
333
- title: 'Confirm',
334
- message: message,
335
- buttons: ['OK', 'Cancel']
336
- });
337
- return result.response === 0; // true if OK clicked
338
- });