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/spi.js ADDED
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ /**
3
+ * CH347 SPI Interface
4
+ *
5
+ * Protocol based on official WCH SDK and flashrom implementation.
6
+ * Commands:
7
+ * 0xC0 - SPI_SET_CFG: Configure SPI
8
+ * 0xC1 - SPI_CS_CTRL: Chip select control
9
+ * 0xC2 - SPI_OUT_IN: Bidirectional transfer
10
+ * 0xC3 - SPI_IN: Read only
11
+ * 0xC4 - SPI_OUT: Write only
12
+ * 0xCA - SPI_GET_CFG: Get configuration
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.CH347SPI = void 0;
16
+ const constants_1 = require("./constants");
17
+ // Default SPI configuration
18
+ const DEFAULT_SPI_CONFIG = {
19
+ speed: constants_1.SPISpeed.CLK_15M,
20
+ mode: constants_1.SPIMode.MODE_0,
21
+ chipSelect: 0,
22
+ bitOrder: 'MSB',
23
+ };
24
+ class CH347SPI {
25
+ usb;
26
+ config;
27
+ isInitialized = false;
28
+ constructor(usb, config) {
29
+ this.usb = usb;
30
+ this.config = { ...DEFAULT_SPI_CONFIG, ...config };
31
+ }
32
+ /**
33
+ * Initialize SPI interface with configuration
34
+ */
35
+ async init(config) {
36
+ if (config) {
37
+ this.config = { ...this.config, ...config };
38
+ }
39
+ // Build SPI configuration packet (29 bytes total)
40
+ const cfgBuf = Buffer.alloc(29);
41
+ cfgBuf[0] = this.config.mode; // SPI Mode 0/1/2/3
42
+ cfgBuf[1] = this.config.speed; // Clock divisor
43
+ cfgBuf[2] = this.config.bitOrder === 'LSB' ? 0 : 1; // Byte order: 0=LSB, 1=MSB
44
+ cfgBuf[3] = 0; // SpiWriteReadInterval low byte
45
+ cfgBuf[4] = 0; // SpiWriteReadInterval high byte
46
+ cfgBuf[5] = 0xff; // SpiOutDefaultData
47
+ cfgBuf[6] = (this.config.chipSelect === 0 ? 0x80 : 0x81); // ChipSelect: bit7=1 means valid, bit0=CS selection
48
+ cfgBuf[7] = 0; // CS1 polarity (0=active low)
49
+ cfgBuf[8] = 0; // CS2 polarity (0=active low)
50
+ cfgBuf[9] = 0; // IsAutoDeactiveCS low byte
51
+ cfgBuf[10] = 0; // IsAutoDeactiveCS high byte
52
+ cfgBuf[11] = 0; // ActiveDelay low byte
53
+ cfgBuf[12] = 0; // ActiveDelay high byte
54
+ cfgBuf[13] = 0; // DelayDeactive byte 0
55
+ cfgBuf[14] = 0; // DelayDeactive byte 1
56
+ cfgBuf[15] = 0; // DelayDeactive byte 2
57
+ cfgBuf[16] = 0; // DelayDeactive byte 3
58
+ // Build command packet
59
+ const cmdLen = 3 + cfgBuf.length;
60
+ const cmdBuf = Buffer.alloc(cmdLen);
61
+ cmdBuf[0] = constants_1.CH347_CMD_SPI_SET_CFG;
62
+ cmdBuf[1] = cfgBuf.length & 0xff; // Length low byte
63
+ cmdBuf[2] = (cfgBuf.length >> 8) & 0xff; // Length high byte
64
+ cfgBuf.copy(cmdBuf, 3);
65
+ await this.usb.write(cmdBuf);
66
+ const response = await this.usb.read(4);
67
+ // Check response
68
+ if (response[0] !== constants_1.CH347_CMD_SPI_SET_CFG || response[3] !== 0) {
69
+ throw new Error('Failed to configure SPI');
70
+ }
71
+ this.isInitialized = true;
72
+ }
73
+ /**
74
+ * Control chip select
75
+ */
76
+ async setChipSelect(active) {
77
+ const cmdBuf = Buffer.alloc(4);
78
+ cmdBuf[0] = constants_1.CH347_CMD_SPI_CS_CTRL;
79
+ cmdBuf[1] = 1; // Length
80
+ cmdBuf[2] = 0;
81
+ cmdBuf[3] = constants_1.CH347_CS_CHANGE | (active ? constants_1.CH347_CS_ASSERT : constants_1.CH347_CS_DEASSERT);
82
+ await this.usb.write(cmdBuf);
83
+ await this.usb.read(4);
84
+ }
85
+ /**
86
+ * SPI write and read (full duplex transfer)
87
+ * @param data Data to write
88
+ * @param csControl If true, automatically control CS (default true)
89
+ * @returns Data read during transfer
90
+ */
91
+ async writeRead(data, csControl = true) {
92
+ if (!this.isInitialized) {
93
+ await this.init();
94
+ }
95
+ const result = Buffer.alloc(data.length);
96
+ let offset = 0;
97
+ const maxPayload = constants_1.CH347_PACKET_SIZE - 3; // Command header is 3 bytes
98
+ // Control CS
99
+ const csFlag = csControl ? 0x80 : 0x00;
100
+ while (offset < data.length) {
101
+ const chunkSize = Math.min(data.length - offset, maxPayload);
102
+ const chunk = data.subarray(offset, offset + chunkSize);
103
+ // Build command packet
104
+ const cmdBuf = Buffer.alloc(3 + chunkSize);
105
+ cmdBuf[0] = constants_1.CH347_CMD_SPI_OUT_IN;
106
+ cmdBuf[1] = chunkSize & 0xff;
107
+ cmdBuf[2] = ((chunkSize >> 8) & 0xff) | csFlag;
108
+ chunk.copy(cmdBuf, 3);
109
+ await this.usb.write(cmdBuf);
110
+ const response = await this.usb.read(constants_1.CH347_PACKET_SIZE);
111
+ // Extract data from response
112
+ if (response[0] === constants_1.CH347_CMD_SPI_OUT_IN) {
113
+ const responseLen = response[1] | (response[2] << 8);
114
+ response.copy(result, offset, 3, 3 + Math.min(responseLen, chunkSize));
115
+ }
116
+ offset += chunkSize;
117
+ }
118
+ return result;
119
+ }
120
+ /**
121
+ * SPI write only
122
+ * @param data Data to write
123
+ * @param csControl If true, automatically control CS (default true)
124
+ */
125
+ async write(data, csControl = true) {
126
+ if (!this.isInitialized) {
127
+ await this.init();
128
+ }
129
+ const maxPayload = constants_1.CH347_PACKET_SIZE - 3;
130
+ const csFlag = csControl ? 0x80 : 0x00;
131
+ let offset = 0;
132
+ while (offset < data.length) {
133
+ const chunkSize = Math.min(data.length - offset, maxPayload);
134
+ const chunk = data.subarray(offset, offset + chunkSize);
135
+ const cmdBuf = Buffer.alloc(3 + chunkSize);
136
+ cmdBuf[0] = constants_1.CH347_CMD_SPI_OUT;
137
+ cmdBuf[1] = chunkSize & 0xff;
138
+ cmdBuf[2] = ((chunkSize >> 8) & 0xff) | csFlag;
139
+ chunk.copy(cmdBuf, 3);
140
+ await this.usb.write(cmdBuf);
141
+ await this.usb.read(4); // Read acknowledgment
142
+ offset += chunkSize;
143
+ }
144
+ }
145
+ /**
146
+ * SPI read only
147
+ * @param length Number of bytes to read
148
+ * @param csControl If true, automatically control CS (default true)
149
+ * @returns Data read
150
+ */
151
+ async read(length, csControl = true) {
152
+ if (!this.isInitialized) {
153
+ await this.init();
154
+ }
155
+ const result = Buffer.alloc(length);
156
+ const maxPayload = constants_1.CH347_PACKET_SIZE - 3;
157
+ const csFlag = csControl ? 0x80 : 0x00;
158
+ let offset = 0;
159
+ while (offset < length) {
160
+ const chunkSize = Math.min(length - offset, maxPayload);
161
+ const cmdBuf = Buffer.alloc(7);
162
+ cmdBuf[0] = constants_1.CH347_CMD_SPI_IN;
163
+ cmdBuf[1] = 4; // Length of parameters
164
+ cmdBuf[2] = csFlag;
165
+ cmdBuf[3] = chunkSize & 0xff;
166
+ cmdBuf[4] = (chunkSize >> 8) & 0xff;
167
+ cmdBuf[5] = (chunkSize >> 16) & 0xff;
168
+ cmdBuf[6] = (chunkSize >> 24) & 0xff;
169
+ await this.usb.write(cmdBuf);
170
+ const response = await this.usb.read(constants_1.CH347_PACKET_SIZE);
171
+ if (response[0] === constants_1.CH347_CMD_SPI_IN) {
172
+ const responseLen = response[1] | (response[2] << 8);
173
+ response.copy(result, offset, 3, 3 + Math.min(responseLen, chunkSize));
174
+ }
175
+ offset += chunkSize;
176
+ }
177
+ return result;
178
+ }
179
+ /**
180
+ * Execute SPI command with CS control
181
+ * Useful for sending flash commands
182
+ */
183
+ async command(writeData, readLength = 0) {
184
+ await this.setChipSelect(true);
185
+ try {
186
+ if (readLength === 0) {
187
+ // Write only
188
+ await this.write(writeData, false);
189
+ return Buffer.alloc(0);
190
+ }
191
+ else {
192
+ // Write then read
193
+ await this.write(writeData, false);
194
+ return await this.read(readLength, false);
195
+ }
196
+ }
197
+ finally {
198
+ await this.setChipSelect(false);
199
+ }
200
+ }
201
+ /**
202
+ * Execute SPI command with full duplex transfer
203
+ */
204
+ async transfer(data) {
205
+ await this.setChipSelect(true);
206
+ try {
207
+ return await this.writeRead(data, false);
208
+ }
209
+ finally {
210
+ await this.setChipSelect(false);
211
+ }
212
+ }
213
+ /**
214
+ * Get current configuration
215
+ */
216
+ getConfig() {
217
+ return { ...this.config };
218
+ }
219
+ /**
220
+ * Check if SPI is initialized
221
+ */
222
+ isReady() {
223
+ return this.isInitialized;
224
+ }
225
+ }
226
+ exports.CH347SPI = CH347SPI;
227
+ //# sourceMappingURL=spi.js.map
@@ -0,0 +1,74 @@
1
+ /**
2
+ * CH347 Type Definitions
3
+ */
4
+ import { SPISpeed, SPIMode, I2CSpeed } from './constants';
5
+ export interface CH347DeviceInfo {
6
+ vendorId: number;
7
+ productId: number;
8
+ serialNumber?: string;
9
+ manufacturer?: string;
10
+ product?: string;
11
+ busNumber: number;
12
+ deviceAddress: number;
13
+ }
14
+ export interface GPIOConfig {
15
+ pin: number;
16
+ direction: 'input' | 'output';
17
+ value?: boolean;
18
+ }
19
+ export interface GPIOState {
20
+ pin: number;
21
+ direction: 'input' | 'output';
22
+ value: boolean;
23
+ }
24
+ export interface SPIConfig {
25
+ speed: SPISpeed;
26
+ mode: SPIMode;
27
+ chipSelect: 0 | 1;
28
+ bitOrder: 'MSB' | 'LSB';
29
+ }
30
+ export interface EraseType {
31
+ command: number;
32
+ size: number;
33
+ timeoutMs: number;
34
+ }
35
+ export interface FlashInfo {
36
+ manufacturerId: number;
37
+ memoryType: number;
38
+ capacity: number;
39
+ jedecId: number;
40
+ size: number;
41
+ name?: string;
42
+ sfdpSupported?: boolean;
43
+ pageSize?: number;
44
+ eraseTypes?: EraseType[];
45
+ }
46
+ export interface FlashProgress {
47
+ operation: 'read' | 'write' | 'erase' | 'verify';
48
+ current: number;
49
+ total: number;
50
+ percentage: number;
51
+ }
52
+ export type FlashProgressCallback = (progress: FlashProgress) => void;
53
+ export interface I2CConfig {
54
+ speed: I2CSpeed;
55
+ }
56
+ export interface UARTConfig {
57
+ baudRate: number;
58
+ dataBits: 5 | 6 | 7 | 8;
59
+ stopBits: 1 | 2;
60
+ parity: 'none' | 'odd' | 'even';
61
+ flowControl: 'none' | 'hardware';
62
+ }
63
+ export interface CH347Options {
64
+ gpio?: Partial<GPIOConfig>[];
65
+ spi?: Partial<SPIConfig>;
66
+ i2c?: Partial<I2CConfig>;
67
+ uart?: Partial<UARTConfig>;
68
+ }
69
+ export declare const FlashManufacturers: Record<number, string>;
70
+ export declare const FlashDatabase: Record<number, {
71
+ name: string;
72
+ size: number;
73
+ }>;
74
+ //# sourceMappingURL=types.d.ts.map
package/dist/types.js ADDED
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * CH347 Type Definitions
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FlashDatabase = exports.FlashManufacturers = void 0;
7
+ // Known flash manufacturers
8
+ exports.FlashManufacturers = {
9
+ 0xef: 'Winbond',
10
+ 0xc8: 'GigaDevice',
11
+ 0x20: 'Micron/Numonyx/ST',
12
+ 0x1f: 'Atmel/Adesto',
13
+ 0xbf: 'SST/Microchip',
14
+ 0xc2: 'Macronix',
15
+ 0x9d: 'ISSI',
16
+ 0x01: 'Spansion/Cypress',
17
+ 0x1c: 'EON',
18
+ 0xa1: 'Fudan',
19
+ 0x68: 'Boya',
20
+ 0x85: 'Puya',
21
+ 0x5e: 'Zbit',
22
+ };
23
+ // Common flash chips database
24
+ exports.FlashDatabase = {
25
+ // Winbond
26
+ 0xef4014: { name: 'W25Q80', size: 1024 * 1024 },
27
+ 0xef4015: { name: 'W25Q16', size: 2 * 1024 * 1024 },
28
+ 0xef4016: { name: 'W25Q32', size: 4 * 1024 * 1024 },
29
+ 0xef4017: { name: 'W25Q64', size: 8 * 1024 * 1024 },
30
+ 0xef4018: { name: 'W25Q128', size: 16 * 1024 * 1024 },
31
+ 0xef4019: { name: 'W25Q256', size: 32 * 1024 * 1024 },
32
+ // GigaDevice
33
+ 0xc84014: { name: 'GD25Q80', size: 1024 * 1024 },
34
+ 0xc84015: { name: 'GD25Q16', size: 2 * 1024 * 1024 },
35
+ 0xc84016: { name: 'GD25Q32', size: 4 * 1024 * 1024 },
36
+ 0xc84017: { name: 'GD25Q64', size: 8 * 1024 * 1024 },
37
+ 0xc84018: { name: 'GD25Q128', size: 16 * 1024 * 1024 },
38
+ // Macronix
39
+ 0xc22014: { name: 'MX25L8005', size: 1024 * 1024 },
40
+ 0xc22015: { name: 'MX25L1605', size: 2 * 1024 * 1024 },
41
+ 0xc22016: { name: 'MX25L3205', size: 4 * 1024 * 1024 },
42
+ 0xc22017: { name: 'MX25L6405', size: 8 * 1024 * 1024 },
43
+ 0xc22018: { name: 'MX25L12805', size: 16 * 1024 * 1024 },
44
+ };
45
+ //# sourceMappingURL=types.js.map
package/dist/uart.d.ts ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * CH347 UART Interface
3
+ *
4
+ * The CH347 UART interface appears as a virtual COM port (CDC ACM).
5
+ * On Linux/macOS, it typically shows up as /dev/ttyACM0 or /dev/tty.usbmodem*
6
+ *
7
+ * This module provides a cross-platform wrapper around the serial port.
8
+ */
9
+ import { EventEmitter } from 'events';
10
+ import { UARTConfig } from './types';
11
+ export interface CH347UARTEvents {
12
+ data: (data: Buffer) => void;
13
+ error: (error: Error) => void;
14
+ open: () => void;
15
+ close: () => void;
16
+ }
17
+ export declare class CH347UART extends EventEmitter {
18
+ private config;
19
+ private port;
20
+ private portPath;
21
+ private isOpen;
22
+ constructor(config?: Partial<UARTConfig>);
23
+ /**
24
+ * List available serial ports that might be CH347 UART
25
+ * Returns array of tty paths (e.g., '/dev/ttyACM0')
26
+ */
27
+ static listPorts(): Promise<string[]>;
28
+ /**
29
+ * Open UART port
30
+ */
31
+ open(portPath: string, config?: Partial<UARTConfig>): Promise<void>;
32
+ /**
33
+ * Close UART port
34
+ */
35
+ close(): Promise<void>;
36
+ /**
37
+ * Write data to UART
38
+ */
39
+ write(data: Buffer | string): Promise<void>;
40
+ /**
41
+ * Read data from UART (with timeout)
42
+ */
43
+ read(length: number, timeoutMs?: number): Promise<Buffer>;
44
+ /**
45
+ * Read line (until newline)
46
+ */
47
+ readLine(timeoutMs?: number): Promise<string>;
48
+ /**
49
+ * Write and wait for response
50
+ */
51
+ writeRead(data: Buffer | string, responseLength: number, timeoutMs?: number): Promise<Buffer>;
52
+ /**
53
+ * Set baud rate
54
+ */
55
+ setBaudRate(baudRate: number): Promise<void>;
56
+ /**
57
+ * Flush input/output buffers
58
+ */
59
+ flush(): Promise<void>;
60
+ /**
61
+ * Set DTR signal
62
+ */
63
+ setDTR(value: boolean): Promise<void>;
64
+ /**
65
+ * Set RTS signal
66
+ */
67
+ setRTS(value: boolean): Promise<void>;
68
+ /**
69
+ * Check if port is open
70
+ */
71
+ isConnected(): boolean;
72
+ /**
73
+ * Get current port path
74
+ */
75
+ getPortPath(): string | null;
76
+ /**
77
+ * Get current configuration
78
+ */
79
+ getConfig(): UARTConfig;
80
+ }
81
+ //# sourceMappingURL=uart.d.ts.map