node-ch347 0.0.1

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.js ADDED
@@ -0,0 +1,597 @@
1
+ "use strict";
2
+ /**
3
+ * CH347 SPI Flash Programmer
4
+ *
5
+ * Supports common SPI flash chips (W25Qxx, GD25Qxx, MX25Lxx, etc.)
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.CH347Flash = void 0;
42
+ const fs = __importStar(require("fs/promises"));
43
+ const constants_1 = require("./constants");
44
+ const types_1 = require("./types");
45
+ const DEFAULT_TIMEOUT_MS = 30000; // 30 seconds for chip erase
46
+ const POLL_INTERVAL_MS = 1; // 1ms polling
47
+ class CH347Flash {
48
+ spi;
49
+ flashInfo = null;
50
+ constructor(spi) {
51
+ this.spi = spi;
52
+ }
53
+ /**
54
+ * Read JEDEC ID from flash chip
55
+ */
56
+ async readJedecId() {
57
+ const cmd = Buffer.from([constants_1.FLASH_CMD_READ_JEDEC_ID, 0, 0, 0]);
58
+ const response = await this.spi.transfer(cmd);
59
+ const manufacturerId = response[1];
60
+ const memoryType = response[2];
61
+ const capacity = response[3];
62
+ const jedecId = (manufacturerId << 16) | (memoryType << 8) | capacity;
63
+ // Look up in database
64
+ const dbEntry = types_1.FlashDatabase[jedecId];
65
+ let size = 0;
66
+ let name;
67
+ if (dbEntry) {
68
+ size = dbEntry.size;
69
+ name = dbEntry.name;
70
+ }
71
+ else {
72
+ // Calculate size from capacity byte (2^capacity bytes)
73
+ size = capacity > 0 ? 1 << capacity : 0;
74
+ }
75
+ const manufacturerName = types_1.FlashManufacturers[manufacturerId];
76
+ if (!name && manufacturerName) {
77
+ name = `${manufacturerName} (0x${jedecId.toString(16)})`;
78
+ }
79
+ this.flashInfo = {
80
+ manufacturerId,
81
+ memoryType,
82
+ capacity,
83
+ jedecId,
84
+ size,
85
+ name,
86
+ };
87
+ // Try to read SFDP parameters
88
+ await this.readSFDPBasicParams();
89
+ return this.flashInfo;
90
+ }
91
+ /**
92
+ * Read SFDP (Serial Flash Discoverable Parameters) data
93
+ * @param address 24-bit address in SFDP space
94
+ * @param length Number of bytes to read
95
+ */
96
+ async readSFDP(address, length) {
97
+ // Command: 0x5A + 24-bit address + 1 dummy byte + read data
98
+ const cmd = Buffer.alloc(5 + length);
99
+ cmd[0] = constants_1.FLASH_CMD_READ_SFDP;
100
+ cmd[1] = (address >> 16) & 0xff;
101
+ cmd[2] = (address >> 8) & 0xff;
102
+ cmd[3] = address & 0xff;
103
+ cmd[4] = 0x00; // dummy byte
104
+ const response = await this.spi.transfer(cmd);
105
+ return response.subarray(5);
106
+ }
107
+ /**
108
+ * Estimate erase timeout based on block size
109
+ */
110
+ estimateEraseTimeout(size) {
111
+ if (size <= 4096)
112
+ return 3000; // 4KB: 3s
113
+ if (size <= 32768)
114
+ return 5000; // 32KB: 5s
115
+ if (size <= 65536)
116
+ return 5000; // 64KB: 5s
117
+ return 10000; // Larger: 10s
118
+ }
119
+ /**
120
+ * Read and parse SFDP Basic Flash Parameter Table
121
+ * Populates flashInfo with discovered erase types and page size
122
+ */
123
+ async readSFDPBasicParams() {
124
+ if (!this.flashInfo)
125
+ return;
126
+ try {
127
+ // Read SFDP header (8 bytes at address 0x00)
128
+ const header = await this.readSFDP(0, 8);
129
+ // Validate signature "SFDP" (little-endian: 0x50444653)
130
+ const signature = header.readUInt32LE(0);
131
+ if (signature !== 0x50444653) {
132
+ return; // SFDP not supported
133
+ }
134
+ // Read first parameter header (8 bytes at address 0x08)
135
+ const paramHeader = await this.readSFDP(8, 8);
136
+ const tableLength = paramHeader[3] * 4; // length in DWORDs -> bytes
137
+ const tableAddress = paramHeader[4] | (paramHeader[5] << 8) | (paramHeader[6] << 16);
138
+ // Read Basic Flash Parameter Table (BFPT)
139
+ const bfpt = await this.readSFDP(tableAddress, Math.min(tableLength, 64));
140
+ // Parse erase types from DWORDs 8-9 (bytes 28-35)
141
+ // Each erase type is 2 bytes: [size_code, command]
142
+ // size_code: 2^N bytes, command: erase opcode
143
+ const eraseTypes = [];
144
+ for (let i = 0; i < 4; i++) {
145
+ const offset = 28 + i * 2;
146
+ if (offset + 1 >= bfpt.length)
147
+ break;
148
+ const sizeCode = bfpt[offset]; // 2^N bytes
149
+ const command = bfpt[offset + 1];
150
+ if (sizeCode > 0 && command > 0) {
151
+ const size = 1 << sizeCode;
152
+ eraseTypes.push({
153
+ command,
154
+ size,
155
+ timeoutMs: this.estimateEraseTimeout(size),
156
+ });
157
+ }
158
+ }
159
+ // Parse page size from DWORD 11 (byte 40, bits 7-4)
160
+ let pageSize = constants_1.FLASH_PAGE_SIZE; // default 256
161
+ if (bfpt.length > 40) {
162
+ const pageSizeCode = (bfpt[40] >> 4) & 0x0f;
163
+ if (pageSizeCode > 0) {
164
+ pageSize = 1 << pageSizeCode;
165
+ }
166
+ }
167
+ // Update flash info with SFDP data
168
+ this.flashInfo.sfdpSupported = true;
169
+ this.flashInfo.pageSize = pageSize;
170
+ this.flashInfo.eraseTypes = eraseTypes.sort((a, b) => b.size - a.size);
171
+ }
172
+ catch {
173
+ // SFDP read failed - chip may not support it, use defaults
174
+ this.flashInfo.sfdpSupported = false;
175
+ }
176
+ }
177
+ /**
178
+ * Read status register
179
+ */
180
+ async readStatus() {
181
+ const cmd = Buffer.from([constants_1.FLASH_CMD_READ_STATUS, 0]);
182
+ const response = await this.spi.transfer(cmd);
183
+ return response[1];
184
+ }
185
+ /**
186
+ * Wait for flash to be ready (WIP bit cleared)
187
+ */
188
+ async waitReady(timeoutMs = DEFAULT_TIMEOUT_MS) {
189
+ const startTime = Date.now();
190
+ while (true) {
191
+ const status = await this.readStatus();
192
+ if ((status & constants_1.FLASH_STATUS_WIP) === 0) {
193
+ return;
194
+ }
195
+ if (Date.now() - startTime > timeoutMs) {
196
+ throw new Error('Flash operation timeout');
197
+ }
198
+ await this.delay(POLL_INTERVAL_MS);
199
+ }
200
+ }
201
+ /**
202
+ * Enable write operations
203
+ */
204
+ async writeEnable() {
205
+ await this.spi.command(Buffer.from([constants_1.FLASH_CMD_WRITE_ENABLE]));
206
+ // Verify write enable latch is set
207
+ const status = await this.readStatus();
208
+ if ((status & constants_1.FLASH_STATUS_WEL) === 0) {
209
+ throw new Error('Failed to enable write');
210
+ }
211
+ }
212
+ /**
213
+ * Disable write operations
214
+ */
215
+ async writeDisable() {
216
+ await this.spi.command(Buffer.from([constants_1.FLASH_CMD_WRITE_DISABLE]));
217
+ }
218
+ /**
219
+ * Read data from flash
220
+ */
221
+ async read(address, length, onProgress) {
222
+ const result = Buffer.alloc(length);
223
+ const chunkSize = 4096; // Read in 4KB chunks for progress reporting
224
+ let offset = 0;
225
+ while (offset < length) {
226
+ const readLen = Math.min(chunkSize, length - offset);
227
+ const addr = address + offset;
228
+ // Build read command
229
+ const cmd = Buffer.alloc(4 + readLen);
230
+ cmd[0] = constants_1.FLASH_CMD_READ_DATA;
231
+ cmd[1] = (addr >> 16) & 0xff;
232
+ cmd[2] = (addr >> 8) & 0xff;
233
+ cmd[3] = addr & 0xff;
234
+ const response = await this.spi.transfer(cmd);
235
+ response.copy(result, offset, 4, 4 + readLen);
236
+ offset += readLen;
237
+ if (onProgress) {
238
+ onProgress({
239
+ operation: 'read',
240
+ current: offset,
241
+ total: length,
242
+ percentage: Math.round((offset / length) * 100),
243
+ });
244
+ }
245
+ }
246
+ return result;
247
+ }
248
+ /**
249
+ * Write a single page (up to 256 bytes)
250
+ */
251
+ async writePage(address, data) {
252
+ if (data.length > constants_1.FLASH_PAGE_SIZE) {
253
+ throw new Error(`Data exceeds page size: ${data.length} > ${constants_1.FLASH_PAGE_SIZE}`);
254
+ }
255
+ await this.writeEnable();
256
+ // Build page program command
257
+ const cmd = Buffer.alloc(4 + data.length);
258
+ cmd[0] = constants_1.FLASH_CMD_PAGE_PROGRAM;
259
+ cmd[1] = (address >> 16) & 0xff;
260
+ cmd[2] = (address >> 8) & 0xff;
261
+ cmd[3] = address & 0xff;
262
+ data.copy(cmd, 4);
263
+ await this.spi.command(cmd);
264
+ await this.waitReady(1000); // Page program timeout 1 second
265
+ }
266
+ /**
267
+ * Write data to flash (handles page boundaries)
268
+ */
269
+ async write(address, data, onProgress) {
270
+ let offset = 0;
271
+ while (offset < data.length) {
272
+ // Calculate bytes remaining in current page
273
+ const pageOffset = (address + offset) % constants_1.FLASH_PAGE_SIZE;
274
+ const pageRemaining = constants_1.FLASH_PAGE_SIZE - pageOffset;
275
+ const writeLen = Math.min(pageRemaining, data.length - offset);
276
+ // Write page
277
+ const chunk = data.subarray(offset, offset + writeLen);
278
+ await this.writePage(address + offset, chunk);
279
+ offset += writeLen;
280
+ if (onProgress) {
281
+ onProgress({
282
+ operation: 'write',
283
+ current: offset,
284
+ total: data.length,
285
+ percentage: Math.round((offset / data.length) * 100),
286
+ });
287
+ }
288
+ }
289
+ }
290
+ /**
291
+ * Erase a 4KB sector
292
+ */
293
+ async eraseSector(address) {
294
+ // Align to sector boundary
295
+ const sectorAddress = address & ~(constants_1.FLASH_SECTOR_SIZE - 1);
296
+ await this.writeEnable();
297
+ const cmd = Buffer.from([
298
+ constants_1.FLASH_CMD_SECTOR_ERASE,
299
+ (sectorAddress >> 16) & 0xff,
300
+ (sectorAddress >> 8) & 0xff,
301
+ sectorAddress & 0xff,
302
+ ]);
303
+ await this.spi.command(cmd);
304
+ await this.waitReady(3000); // Sector erase timeout 3 seconds
305
+ }
306
+ /**
307
+ * Erase a 32KB block
308
+ */
309
+ async eraseBlock32K(address) {
310
+ const blockAddress = address & ~(constants_1.FLASH_BLOCK_SIZE_32K - 1);
311
+ await this.writeEnable();
312
+ const cmd = Buffer.from([
313
+ constants_1.FLASH_CMD_BLOCK_ERASE_32K,
314
+ (blockAddress >> 16) & 0xff,
315
+ (blockAddress >> 8) & 0xff,
316
+ blockAddress & 0xff,
317
+ ]);
318
+ await this.spi.command(cmd);
319
+ await this.waitReady(5000); // Block erase timeout 5 seconds
320
+ }
321
+ /**
322
+ * Erase a 64KB block
323
+ */
324
+ async eraseBlock64K(address) {
325
+ const blockAddress = address & ~(constants_1.FLASH_BLOCK_SIZE_64K - 1);
326
+ await this.writeEnable();
327
+ const cmd = Buffer.from([
328
+ constants_1.FLASH_CMD_BLOCK_ERASE_64K,
329
+ (blockAddress >> 16) & 0xff,
330
+ (blockAddress >> 8) & 0xff,
331
+ blockAddress & 0xff,
332
+ ]);
333
+ await this.spi.command(cmd);
334
+ await this.waitReady(5000); // Block erase timeout 5 seconds
335
+ }
336
+ /**
337
+ * Erase entire chip
338
+ */
339
+ async eraseChip(onProgress) {
340
+ await this.writeEnable();
341
+ await this.spi.command(Buffer.from([constants_1.FLASH_CMD_CHIP_ERASE]));
342
+ // Poll for completion with progress
343
+ const startTime = Date.now();
344
+ const timeout = DEFAULT_TIMEOUT_MS;
345
+ while (true) {
346
+ const status = await this.readStatus();
347
+ if ((status & constants_1.FLASH_STATUS_WIP) === 0) {
348
+ if (onProgress) {
349
+ onProgress({
350
+ operation: 'erase',
351
+ current: 100,
352
+ total: 100,
353
+ percentage: 100,
354
+ });
355
+ }
356
+ return;
357
+ }
358
+ const elapsed = Date.now() - startTime;
359
+ if (elapsed > timeout) {
360
+ throw new Error('Chip erase timeout');
361
+ }
362
+ if (onProgress) {
363
+ // Estimate progress based on typical chip erase time
364
+ const estimatedTime = 30000; // 30 seconds typical
365
+ const progress = Math.min(99, Math.round((elapsed / estimatedTime) * 100));
366
+ onProgress({
367
+ operation: 'erase',
368
+ current: progress,
369
+ total: 100,
370
+ percentage: progress,
371
+ });
372
+ }
373
+ await this.delay(100);
374
+ }
375
+ }
376
+ /**
377
+ * Erase using a specific erase type (from SFDP discovery)
378
+ */
379
+ async eraseWithType(address, eraseType) {
380
+ // Align to erase boundary
381
+ const alignedAddress = address & ~(eraseType.size - 1);
382
+ await this.writeEnable();
383
+ const cmd = Buffer.from([
384
+ eraseType.command,
385
+ (alignedAddress >> 16) & 0xff,
386
+ (alignedAddress >> 8) & 0xff,
387
+ alignedAddress & 0xff,
388
+ ]);
389
+ await this.spi.command(cmd);
390
+ await this.waitReady(eraseType.timeoutMs);
391
+ }
392
+ /**
393
+ * Get default erase types (fallback when SFDP not available)
394
+ */
395
+ getDefaultEraseTypes() {
396
+ return [
397
+ { command: constants_1.FLASH_CMD_BLOCK_ERASE_64K, size: constants_1.FLASH_BLOCK_SIZE_64K, timeoutMs: 5000 },
398
+ { command: constants_1.FLASH_CMD_BLOCK_ERASE_32K, size: constants_1.FLASH_BLOCK_SIZE_32K, timeoutMs: 5000 },
399
+ { command: constants_1.FLASH_CMD_SECTOR_ERASE, size: constants_1.FLASH_SECTOR_SIZE, timeoutMs: 3000 },
400
+ ];
401
+ }
402
+ /**
403
+ * Erase range (uses optimal erase commands from SFDP or defaults)
404
+ */
405
+ async eraseRange(address, length, onProgress) {
406
+ const endAddress = address + length;
407
+ let currentAddress = address;
408
+ let erased = 0;
409
+ // Use SFDP-discovered erase types or fall back to defaults
410
+ const eraseTypes = this.flashInfo?.eraseTypes ?? this.getDefaultEraseTypes();
411
+ while (currentAddress < endAddress) {
412
+ const remaining = endAddress - currentAddress;
413
+ // Find the largest erase type that fits
414
+ let usedEraseType = null;
415
+ for (const eraseType of eraseTypes) {
416
+ if ((currentAddress % eraseType.size === 0) &&
417
+ remaining >= eraseType.size) {
418
+ usedEraseType = eraseType;
419
+ break; // eraseTypes are sorted by size descending
420
+ }
421
+ }
422
+ if (usedEraseType) {
423
+ await this.eraseWithType(currentAddress, usedEraseType);
424
+ currentAddress += usedEraseType.size;
425
+ erased += usedEraseType.size;
426
+ }
427
+ else {
428
+ // Fallback to smallest erase type available
429
+ const smallestErase = eraseTypes[eraseTypes.length - 1];
430
+ await this.eraseWithType(currentAddress, smallestErase);
431
+ currentAddress += smallestErase.size;
432
+ erased += smallestErase.size;
433
+ }
434
+ if (onProgress) {
435
+ onProgress({
436
+ operation: 'erase',
437
+ current: Math.min(erased, length),
438
+ total: length,
439
+ percentage: Math.round((Math.min(erased, length) / length) * 100),
440
+ });
441
+ }
442
+ }
443
+ }
444
+ /**
445
+ * Verify data matches flash contents
446
+ */
447
+ async verify(address, data, onProgress) {
448
+ const readData = await this.read(address, data.length, (progress) => {
449
+ if (onProgress) {
450
+ onProgress({
451
+ ...progress,
452
+ operation: 'verify',
453
+ });
454
+ }
455
+ });
456
+ return data.equals(readData);
457
+ }
458
+ /**
459
+ * Check if a sector needs to be erased to write new data
460
+ * Flash can only program 1→0, so we need to erase if any bit needs to go 0→1
461
+ */
462
+ sectorNeedsErase(original, newData) {
463
+ for (let i = 0; i < newData.length; i++) {
464
+ // If (original & new) !== new, some bits need to go from 0 to 1, requiring erase
465
+ if ((original[i] & newData[i]) !== newData[i]) {
466
+ return true;
467
+ }
468
+ }
469
+ return false;
470
+ }
471
+ /**
472
+ * Check if a sector has any changes
473
+ */
474
+ sectorHasChanges(original, newData) {
475
+ return !original.equals(newData);
476
+ }
477
+ /**
478
+ * Program flash (smart erase + write + verify)
479
+ * Only erases and writes sectors that have actual changes
480
+ */
481
+ async program(address, data, options = {}) {
482
+ const { erase = true, verify = true, onProgress } = options;
483
+ // Read original content to compare
484
+ const original = await this.read(address, data.length);
485
+ // Analyze which sectors need erasing and which need writing
486
+ const sectorsToErase = [];
487
+ const sectorsToWrite = [];
488
+ const startSector = Math.floor(address / constants_1.FLASH_SECTOR_SIZE);
489
+ const endAddress = address + data.length;
490
+ const endSector = Math.ceil(endAddress / constants_1.FLASH_SECTOR_SIZE);
491
+ for (let sector = startSector; sector < endSector; sector++) {
492
+ const sectorStart = sector * constants_1.FLASH_SECTOR_SIZE;
493
+ const sectorEnd = sectorStart + constants_1.FLASH_SECTOR_SIZE;
494
+ // Calculate overlap between this sector and our data range
495
+ const overlapStart = Math.max(sectorStart, address);
496
+ const overlapEnd = Math.min(sectorEnd, endAddress);
497
+ // Get the corresponding slices
498
+ const dataOffset = overlapStart - address;
499
+ const dataSlice = data.subarray(dataOffset, dataOffset + (overlapEnd - overlapStart));
500
+ const originalSlice = original.subarray(dataOffset, dataOffset + (overlapEnd - overlapStart));
501
+ if (this.sectorHasChanges(originalSlice, dataSlice)) {
502
+ sectorsToWrite.push(sector);
503
+ if (erase && this.sectorNeedsErase(originalSlice, dataSlice)) {
504
+ sectorsToErase.push(sector);
505
+ }
506
+ }
507
+ }
508
+ // Calculate progress steps
509
+ const totalWork = sectorsToErase.length + sectorsToWrite.length + (verify ? 1 : 0);
510
+ let completedWork = 0;
511
+ const reportProgress = (operation) => {
512
+ if (onProgress) {
513
+ onProgress({
514
+ operation,
515
+ current: completedWork,
516
+ total: totalWork,
517
+ percentage: totalWork > 0 ? Math.round((completedWork / totalWork) * 100) : 100,
518
+ });
519
+ }
520
+ };
521
+ // Erase only the sectors that need it
522
+ for (const sector of sectorsToErase) {
523
+ await this.eraseSector(sector * constants_1.FLASH_SECTOR_SIZE);
524
+ completedWork++;
525
+ reportProgress('erase');
526
+ }
527
+ // Write only the sectors that have changes
528
+ for (const sector of sectorsToWrite) {
529
+ const sectorStart = sector * constants_1.FLASH_SECTOR_SIZE;
530
+ const sectorEnd = sectorStart + constants_1.FLASH_SECTOR_SIZE;
531
+ const overlapStart = Math.max(sectorStart, address);
532
+ const overlapEnd = Math.min(sectorEnd, endAddress);
533
+ const dataOffset = overlapStart - address;
534
+ const dataSlice = data.subarray(dataOffset, dataOffset + (overlapEnd - overlapStart));
535
+ await this.write(overlapStart, dataSlice);
536
+ completedWork++;
537
+ reportProgress('write');
538
+ }
539
+ // Verify
540
+ if (verify) {
541
+ const verified = await this.verify(address, data);
542
+ completedWork++;
543
+ reportProgress('verify');
544
+ if (!verified) {
545
+ throw new Error('Verification failed');
546
+ }
547
+ }
548
+ return true;
549
+ }
550
+ /**
551
+ * Program flash from a binary file
552
+ * @param filePath Path to the binary file to write
553
+ * @param address Starting address in flash (default: 0, requires file size to match flash size)
554
+ * @param options Programming options (erase, verify, progress callback)
555
+ */
556
+ async programFile(filePath, address, options = {}) {
557
+ const data = await fs.readFile(filePath);
558
+ // If no address specified, default to 0 but require file size to match flash size
559
+ if (address === undefined) {
560
+ if (!this.flashInfo?.size) {
561
+ throw new Error('Flash size unknown. Call readJedecId() first or specify an address.');
562
+ }
563
+ if (data.length !== this.flashInfo.size) {
564
+ throw new Error(`File size (${data.length} bytes) does not match flash size (${this.flashInfo.size} bytes). ` +
565
+ `Specify an address explicitly to write partial data.`);
566
+ }
567
+ address = 0;
568
+ }
569
+ return this.program(address, data, options);
570
+ }
571
+ /**
572
+ * Read flash contents and save to a binary file
573
+ * @param filePath Path to save the binary file
574
+ * @param address Starting address in flash (default: 0)
575
+ * @param length Number of bytes to read (if not specified, reads entire flash based on JEDEC ID)
576
+ * @param onProgress Progress callback
577
+ */
578
+ async readToFile(filePath, address = 0, length, onProgress) {
579
+ const readLength = length ?? this.flashInfo?.size;
580
+ if (!readLength) {
581
+ throw new Error('Length not specified and flash size unknown. Call readJedecId() first or specify length.');
582
+ }
583
+ const data = await this.read(address, readLength, onProgress);
584
+ await fs.writeFile(filePath, data);
585
+ }
586
+ /**
587
+ * Get flash info (call readJedecId first)
588
+ */
589
+ getFlashInfo() {
590
+ return this.flashInfo;
591
+ }
592
+ delay(ms) {
593
+ return new Promise((resolve) => setTimeout(resolve, ms));
594
+ }
595
+ }
596
+ exports.CH347Flash = CH347Flash;
597
+ //# sourceMappingURL=flash.js.map
package/dist/gpio.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * CH347 GPIO Control
3
+ *
4
+ * Protocol from ch347-gpio.c:
5
+ * - GPIO buffer is 11 bytes: [0xCC, length(8), 0x00, pin0, pin1, pin2, pin3, pin4, pin5, pin6, pin7]
6
+ * - Each pin byte has control bits:
7
+ * - Bit 7 (0x80): In response, indicates output direction
8
+ * - Bit 6 (0x40): Enable pin change (out) / Current value (in)
9
+ * - Bit 5-4 (0x30): Set direction to output
10
+ * - Bit 3 (0x08): Set pin value high
11
+ */
12
+ import { CH347USB } from './usb';
13
+ import { GPIOConfig, GPIOState } from './types';
14
+ export declare class CH347GPIO {
15
+ private usb;
16
+ private pinStates;
17
+ constructor(usb: CH347USB);
18
+ /**
19
+ * Build GPIO command buffer
20
+ */
21
+ private buildCommandBuffer;
22
+ /**
23
+ * Parse GPIO response buffer
24
+ */
25
+ private parseResponse;
26
+ /**
27
+ * Read all GPIO states
28
+ */
29
+ readAll(): Promise<GPIOState[]>;
30
+ /**
31
+ * Read single GPIO pin state
32
+ */
33
+ read(pin: number): Promise<GPIOState>;
34
+ /**
35
+ * Configure GPIO pin direction
36
+ */
37
+ setDirection(pin: number, direction: 'input' | 'output'): Promise<void>;
38
+ /**
39
+ * Set GPIO output value
40
+ */
41
+ write(pin: number, value: boolean): Promise<void>;
42
+ /**
43
+ * Set multiple GPIO pins at once
44
+ */
45
+ writeMultiple(pins: {
46
+ pin: number;
47
+ value: boolean;
48
+ }[]): Promise<void>;
49
+ /**
50
+ * Configure multiple GPIO pins
51
+ */
52
+ configure(configs: GPIOConfig[]): Promise<void>;
53
+ /**
54
+ * Pulse a GPIO pin (useful for buttons)
55
+ */
56
+ pulse(pin: number, durationMs?: number, activeHigh?: boolean): Promise<void>;
57
+ /**
58
+ * Toggle GPIO pin
59
+ */
60
+ toggle(pin: number): Promise<boolean>;
61
+ /**
62
+ * Get cached pin states (call readAll() to refresh)
63
+ */
64
+ getPinStates(): GPIOState[];
65
+ private delay;
66
+ }
67
+ //# sourceMappingURL=gpio.d.ts.map