serial-core 0.2.0-dev.5 → 0.2.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/SerialService.d.cts +40 -10
- package/dist/SerialService.d.ts +40 -10
- package/dist/core/PortRegistry.d.cts +10 -2
- package/dist/core/PortRegistry.d.ts +10 -2
- package/dist/core/PortScanner.d.cts +6 -2
- package/dist/core/PortScanner.d.ts +6 -2
- package/dist/core/QueueManager.d.cts +30 -10
- package/dist/core/QueueManager.d.ts +30 -10
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/buffers.d.cts +14 -2
- package/dist/utils/buffers.d.ts +14 -2
- package/package.json +1 -1
package/dist/SerialService.d.cts
CHANGED
|
@@ -14,28 +14,51 @@ export declare class SerialService extends EventEmitter {
|
|
|
14
14
|
protected config: SerialConfig;
|
|
15
15
|
protected intentionalDisconnect: boolean;
|
|
16
16
|
protected reconnectTimer: NodeJS.Timeout | null;
|
|
17
|
+
/**
|
|
18
|
+
* Initializes the SerialService with the provided configuration.
|
|
19
|
+
* @param config - Configuration object for serial connection
|
|
20
|
+
*/
|
|
17
21
|
constructor(config: SerialConfig);
|
|
22
|
+
/**
|
|
23
|
+
* Gets the autoConnect configuration value.
|
|
24
|
+
* @returns Whether the service should automatically connect on instantiation
|
|
25
|
+
*/
|
|
18
26
|
get autoConnect(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Gets the current connection status.
|
|
29
|
+
* @returns The current serial connection status
|
|
30
|
+
*/
|
|
19
31
|
get status(): SerialStatus;
|
|
20
32
|
/**
|
|
21
|
-
* Updates status and emits the corresponding event.
|
|
33
|
+
* Updates the service status and emits the corresponding event.
|
|
34
|
+
* @param newStatus - The new status to set
|
|
22
35
|
*/
|
|
23
36
|
protected setStatus(newStatus: SerialStatus): void;
|
|
24
37
|
/**
|
|
25
38
|
* Starts the connection process.
|
|
26
39
|
* Handles both direct connection (known path) and scanning (VID/PID).
|
|
40
|
+
* @throws Will emit error events if connection fails
|
|
27
41
|
*/
|
|
28
42
|
connect(): Promise<void>;
|
|
29
43
|
/**
|
|
30
|
-
* Physically opens the port and configures listeners.
|
|
44
|
+
* Physically opens the serial port and configures all necessary listeners.
|
|
45
|
+
* @param path - The port path to open (e.g., '/dev/ttyUSB0' or 'COM3')
|
|
31
46
|
*/
|
|
32
47
|
protected openPort(path: string): void;
|
|
33
48
|
/**
|
|
34
|
-
*
|
|
49
|
+
* Manually disconnects from the serial port.
|
|
50
|
+
* @returns A promise that resolves when the port is fully closed
|
|
35
51
|
*/
|
|
36
52
|
disconnect(): Promise<void>;
|
|
37
53
|
/**
|
|
38
|
-
*
|
|
54
|
+
* Sends data through the serial port using the internal queue.
|
|
55
|
+
* @param data - The data to send (string or Buffer)
|
|
56
|
+
* @param options - Optional send configuration
|
|
57
|
+
* @param options.alias - A command identifier to correlate with responses
|
|
58
|
+
* @param options.timeout - Maximum time to wait for the operation in milliseconds
|
|
59
|
+
* @param options.waitResponse - If true, wait for a response before processing the next item in the queue
|
|
60
|
+
* @returns A promise that resolves when the data has been written to the port
|
|
61
|
+
* @throws If the port is not connected
|
|
39
62
|
*/
|
|
40
63
|
send(data: string | Buffer, options?: {
|
|
41
64
|
alias?: string;
|
|
@@ -43,24 +66,31 @@ export declare class SerialService extends EventEmitter {
|
|
|
43
66
|
waitResponse?: boolean;
|
|
44
67
|
}): Promise<void>;
|
|
45
68
|
/**
|
|
46
|
-
* Low-level write logic
|
|
47
|
-
* Handles backpressure
|
|
69
|
+
* Low-level write logic executed by the QueueManager.
|
|
70
|
+
* Handles backpressure through the 'drain' event.
|
|
71
|
+
* @param data - The data to write to the port
|
|
72
|
+
* @returns A promise that resolves when the data has been fully written
|
|
48
73
|
*/
|
|
49
74
|
protected performWrite(data: string | Buffer): Promise<void>;
|
|
50
75
|
/**
|
|
51
|
-
*
|
|
76
|
+
* Handles connection failures in a centralized manner.
|
|
77
|
+
* Emits error events and schedules reconnection attempts if not intentional.
|
|
78
|
+
* @param reason - The reason for the connection failure
|
|
52
79
|
*/
|
|
53
80
|
protected handleConnectionFailure(reason: string): void;
|
|
54
81
|
/**
|
|
55
|
-
* Schedules next connection attempt with simple backoff.
|
|
82
|
+
* Schedules the next connection attempt with a simple backoff strategy.
|
|
56
83
|
*/
|
|
57
84
|
protected scheduleReconnect(): void;
|
|
58
85
|
/**
|
|
59
|
-
*
|
|
86
|
+
* Cleans up resources and listeners.
|
|
87
|
+
* Unregisters the port and removes all event listeners.
|
|
60
88
|
*/
|
|
61
89
|
protected cleanup(): void;
|
|
62
90
|
/**
|
|
63
|
-
* Executes connection handshake.
|
|
91
|
+
* Executes the connection handshake to verify device responsiveness.
|
|
92
|
+
* Sends a handshake command and waits for a pattern match in the response.
|
|
64
93
|
*/
|
|
65
94
|
protected performHandshake(): void;
|
|
95
|
+
queueIsIdle(): boolean;
|
|
66
96
|
}
|
package/dist/SerialService.d.ts
CHANGED
|
@@ -14,28 +14,51 @@ export declare class SerialService extends EventEmitter {
|
|
|
14
14
|
protected config: SerialConfig;
|
|
15
15
|
protected intentionalDisconnect: boolean;
|
|
16
16
|
protected reconnectTimer: NodeJS.Timeout | null;
|
|
17
|
+
/**
|
|
18
|
+
* Initializes the SerialService with the provided configuration.
|
|
19
|
+
* @param config - Configuration object for serial connection
|
|
20
|
+
*/
|
|
17
21
|
constructor(config: SerialConfig);
|
|
22
|
+
/**
|
|
23
|
+
* Gets the autoConnect configuration value.
|
|
24
|
+
* @returns Whether the service should automatically connect on instantiation
|
|
25
|
+
*/
|
|
18
26
|
get autoConnect(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Gets the current connection status.
|
|
29
|
+
* @returns The current serial connection status
|
|
30
|
+
*/
|
|
19
31
|
get status(): SerialStatus;
|
|
20
32
|
/**
|
|
21
|
-
* Updates status and emits the corresponding event.
|
|
33
|
+
* Updates the service status and emits the corresponding event.
|
|
34
|
+
* @param newStatus - The new status to set
|
|
22
35
|
*/
|
|
23
36
|
protected setStatus(newStatus: SerialStatus): void;
|
|
24
37
|
/**
|
|
25
38
|
* Starts the connection process.
|
|
26
39
|
* Handles both direct connection (known path) and scanning (VID/PID).
|
|
40
|
+
* @throws Will emit error events if connection fails
|
|
27
41
|
*/
|
|
28
42
|
connect(): Promise<void>;
|
|
29
43
|
/**
|
|
30
|
-
* Physically opens the port and configures listeners.
|
|
44
|
+
* Physically opens the serial port and configures all necessary listeners.
|
|
45
|
+
* @param path - The port path to open (e.g., '/dev/ttyUSB0' or 'COM3')
|
|
31
46
|
*/
|
|
32
47
|
protected openPort(path: string): void;
|
|
33
48
|
/**
|
|
34
|
-
*
|
|
49
|
+
* Manually disconnects from the serial port.
|
|
50
|
+
* @returns A promise that resolves when the port is fully closed
|
|
35
51
|
*/
|
|
36
52
|
disconnect(): Promise<void>;
|
|
37
53
|
/**
|
|
38
|
-
*
|
|
54
|
+
* Sends data through the serial port using the internal queue.
|
|
55
|
+
* @param data - The data to send (string or Buffer)
|
|
56
|
+
* @param options - Optional send configuration
|
|
57
|
+
* @param options.alias - A command identifier to correlate with responses
|
|
58
|
+
* @param options.timeout - Maximum time to wait for the operation in milliseconds
|
|
59
|
+
* @param options.waitResponse - If true, wait for a response before processing the next item in the queue
|
|
60
|
+
* @returns A promise that resolves when the data has been written to the port
|
|
61
|
+
* @throws If the port is not connected
|
|
39
62
|
*/
|
|
40
63
|
send(data: string | Buffer, options?: {
|
|
41
64
|
alias?: string;
|
|
@@ -43,24 +66,31 @@ export declare class SerialService extends EventEmitter {
|
|
|
43
66
|
waitResponse?: boolean;
|
|
44
67
|
}): Promise<void>;
|
|
45
68
|
/**
|
|
46
|
-
* Low-level write logic
|
|
47
|
-
* Handles backpressure
|
|
69
|
+
* Low-level write logic executed by the QueueManager.
|
|
70
|
+
* Handles backpressure through the 'drain' event.
|
|
71
|
+
* @param data - The data to write to the port
|
|
72
|
+
* @returns A promise that resolves when the data has been fully written
|
|
48
73
|
*/
|
|
49
74
|
protected performWrite(data: string | Buffer): Promise<void>;
|
|
50
75
|
/**
|
|
51
|
-
*
|
|
76
|
+
* Handles connection failures in a centralized manner.
|
|
77
|
+
* Emits error events and schedules reconnection attempts if not intentional.
|
|
78
|
+
* @param reason - The reason for the connection failure
|
|
52
79
|
*/
|
|
53
80
|
protected handleConnectionFailure(reason: string): void;
|
|
54
81
|
/**
|
|
55
|
-
* Schedules next connection attempt with simple backoff.
|
|
82
|
+
* Schedules the next connection attempt with a simple backoff strategy.
|
|
56
83
|
*/
|
|
57
84
|
protected scheduleReconnect(): void;
|
|
58
85
|
/**
|
|
59
|
-
*
|
|
86
|
+
* Cleans up resources and listeners.
|
|
87
|
+
* Unregisters the port and removes all event listeners.
|
|
60
88
|
*/
|
|
61
89
|
protected cleanup(): void;
|
|
62
90
|
/**
|
|
63
|
-
* Executes connection handshake.
|
|
91
|
+
* Executes the connection handshake to verify device responsiveness.
|
|
92
|
+
* Sends a handshake command and waits for a pattern match in the response.
|
|
64
93
|
*/
|
|
65
94
|
protected performHandshake(): void;
|
|
95
|
+
queueIsIdle(): boolean;
|
|
66
96
|
}
|
|
@@ -7,18 +7,26 @@ export declare class PortRegistry {
|
|
|
7
7
|
private static instance;
|
|
8
8
|
private lockedPorts;
|
|
9
9
|
private constructor();
|
|
10
|
+
/**
|
|
11
|
+
* Gets the singleton instance of the PortRegistry.
|
|
12
|
+
* @returns The PortRegistry singleton instance
|
|
13
|
+
*/
|
|
10
14
|
static getInstance(): PortRegistry;
|
|
11
15
|
/**
|
|
12
16
|
* Attempts to register (lock) exclusive use of a port.
|
|
13
|
-
*
|
|
17
|
+
* @param path - The port path to register
|
|
18
|
+
* @returns True if successfully registered, false if already in use
|
|
14
19
|
*/
|
|
15
20
|
register(path: string): boolean;
|
|
16
21
|
/**
|
|
17
22
|
* Releases the port so it can be used again.
|
|
23
|
+
* @param path - The port path to unregister
|
|
18
24
|
*/
|
|
19
25
|
unregister(path: string): void;
|
|
20
26
|
/**
|
|
21
|
-
* Checks if a port is currently
|
|
27
|
+
* Checks if a port is currently locked by the library.
|
|
28
|
+
* @param path - The port path to check
|
|
29
|
+
* @returns True if the port is in use, false otherwise
|
|
22
30
|
*/
|
|
23
31
|
isLocked(path: string): boolean;
|
|
24
32
|
}
|
|
@@ -7,18 +7,26 @@ export declare class PortRegistry {
|
|
|
7
7
|
private static instance;
|
|
8
8
|
private lockedPorts;
|
|
9
9
|
private constructor();
|
|
10
|
+
/**
|
|
11
|
+
* Gets the singleton instance of the PortRegistry.
|
|
12
|
+
* @returns The PortRegistry singleton instance
|
|
13
|
+
*/
|
|
10
14
|
static getInstance(): PortRegistry;
|
|
11
15
|
/**
|
|
12
16
|
* Attempts to register (lock) exclusive use of a port.
|
|
13
|
-
*
|
|
17
|
+
* @param path - The port path to register
|
|
18
|
+
* @returns True if successfully registered, false if already in use
|
|
14
19
|
*/
|
|
15
20
|
register(path: string): boolean;
|
|
16
21
|
/**
|
|
17
22
|
* Releases the port so it can be used again.
|
|
23
|
+
* @param path - The port path to unregister
|
|
18
24
|
*/
|
|
19
25
|
unregister(path: string): void;
|
|
20
26
|
/**
|
|
21
|
-
* Checks if a port is currently
|
|
27
|
+
* Checks if a port is currently locked by the library.
|
|
28
|
+
* @param path - The port path to check
|
|
29
|
+
* @returns True if the port is in use, false otherwise
|
|
22
30
|
*/
|
|
23
31
|
isLocked(path: string): boolean;
|
|
24
32
|
}
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare class PortScanner {
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* Normalizes strings to
|
|
7
|
+
* Searches for a serial port matching the provided Vendor ID and/or Product ID.
|
|
8
|
+
* Normalizes strings to handle case sensitivity and '0x' prefixes.
|
|
9
|
+
* @param vendorId - The USB vendor ID to search for (optional if productId provided)
|
|
10
|
+
* @param productId - The USB product ID to search for (optional)
|
|
11
|
+
* @returns The port path if found, null otherwise
|
|
12
|
+
* @throws Error if neither vendorId nor productId is provided
|
|
9
13
|
*/
|
|
10
14
|
static findPort(vendorId?: string, productId?: string): Promise<string | null>;
|
|
11
15
|
}
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare class PortScanner {
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* Normalizes strings to
|
|
7
|
+
* Searches for a serial port matching the provided Vendor ID and/or Product ID.
|
|
8
|
+
* Normalizes strings to handle case sensitivity and '0x' prefixes.
|
|
9
|
+
* @param vendorId - The USB vendor ID to search for (optional if productId provided)
|
|
10
|
+
* @param productId - The USB product ID to search for (optional)
|
|
11
|
+
* @returns The port path if found, null otherwise
|
|
12
|
+
* @throws Error if neither vendorId nor productId is provided
|
|
9
13
|
*/
|
|
10
14
|
static findPort(vendorId?: string, productId?: string): Promise<string | null>;
|
|
11
15
|
}
|
|
@@ -11,10 +11,19 @@ export declare class QueueManager {
|
|
|
11
11
|
private currentTimeoutTimer;
|
|
12
12
|
private currentItem;
|
|
13
13
|
private writeHandler;
|
|
14
|
+
/**
|
|
15
|
+
* Initializes the QueueManager with a write handler function.
|
|
16
|
+
* @param writeHandler - Function to execute when writing data to the port
|
|
17
|
+
*/
|
|
14
18
|
constructor(writeHandler: (data: Buffer | string) => Promise<void>);
|
|
15
19
|
/**
|
|
16
|
-
* Adds a command to the queue and returns a promise
|
|
17
|
-
*
|
|
20
|
+
* Adds a command to the queue and returns a promise that resolves when processed.
|
|
21
|
+
* @param data - The data to queue (string or Buffer)
|
|
22
|
+
* @param options - Optional queue item configuration
|
|
23
|
+
* @param options.alias - Command identifier for response correlation
|
|
24
|
+
* @param options.timeout - Maximum time to wait for operation/response in milliseconds
|
|
25
|
+
* @param options.waitResponse - If true, block queue until response is received
|
|
26
|
+
* @returns Promise that resolves when the command has been processed
|
|
18
27
|
*/
|
|
19
28
|
add(data: Buffer | string, options?: {
|
|
20
29
|
alias?: string;
|
|
@@ -22,27 +31,38 @@ export declare class QueueManager {
|
|
|
22
31
|
waitResponse?: boolean;
|
|
23
32
|
}): Promise<void>;
|
|
24
33
|
/**
|
|
25
|
-
*
|
|
26
|
-
* Useful for correlating responses.
|
|
34
|
+
* Gets the alias of the command currently processing or just processed.
|
|
35
|
+
* Useful for correlating received responses with sent commands.
|
|
36
|
+
* @returns The alias of the current command, or undefined if none
|
|
27
37
|
*/
|
|
28
38
|
get currentAlias(): string | undefined;
|
|
29
39
|
/**
|
|
30
|
-
* Clears
|
|
40
|
+
* Clears all pending items from the queue.
|
|
41
|
+
* Useful when handling abrupt disconnections.
|
|
42
|
+
* All pending items will be rejected with a disconnection error.
|
|
31
43
|
*/
|
|
32
44
|
clear(): void;
|
|
33
45
|
/**
|
|
34
|
-
*
|
|
35
|
-
* If
|
|
46
|
+
* Notifies the queue that a response has been received.
|
|
47
|
+
* If waiting for a response, this triggers resolution of the current command
|
|
48
|
+
* and processes the next item in the queue.
|
|
36
49
|
*/
|
|
37
50
|
notifyResponse(): void;
|
|
38
51
|
/**
|
|
39
|
-
*
|
|
52
|
+
* Processes the queue recursively, executing items one by one.
|
|
53
|
+
* Handles timeouts and response waiting when configured.
|
|
54
|
+
* @private
|
|
40
55
|
*/
|
|
41
56
|
private process;
|
|
57
|
+
/**
|
|
58
|
+
* Handles timeout when waiting for a response to a sent command.
|
|
59
|
+
* @private
|
|
60
|
+
* @param item - The queue item that timed out
|
|
61
|
+
*/
|
|
42
62
|
private handleResponseTimeout;
|
|
43
63
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
64
|
+
* Checks if the queue is idle (no pending items, not processing, not waiting for response).
|
|
65
|
+
* @returns True if the queue has no pending operations
|
|
46
66
|
*/
|
|
47
67
|
isIdle(): boolean;
|
|
48
68
|
}
|
|
@@ -11,10 +11,19 @@ export declare class QueueManager {
|
|
|
11
11
|
private currentTimeoutTimer;
|
|
12
12
|
private currentItem;
|
|
13
13
|
private writeHandler;
|
|
14
|
+
/**
|
|
15
|
+
* Initializes the QueueManager with a write handler function.
|
|
16
|
+
* @param writeHandler - Function to execute when writing data to the port
|
|
17
|
+
*/
|
|
14
18
|
constructor(writeHandler: (data: Buffer | string) => Promise<void>);
|
|
15
19
|
/**
|
|
16
|
-
* Adds a command to the queue and returns a promise
|
|
17
|
-
*
|
|
20
|
+
* Adds a command to the queue and returns a promise that resolves when processed.
|
|
21
|
+
* @param data - The data to queue (string or Buffer)
|
|
22
|
+
* @param options - Optional queue item configuration
|
|
23
|
+
* @param options.alias - Command identifier for response correlation
|
|
24
|
+
* @param options.timeout - Maximum time to wait for operation/response in milliseconds
|
|
25
|
+
* @param options.waitResponse - If true, block queue until response is received
|
|
26
|
+
* @returns Promise that resolves when the command has been processed
|
|
18
27
|
*/
|
|
19
28
|
add(data: Buffer | string, options?: {
|
|
20
29
|
alias?: string;
|
|
@@ -22,27 +31,38 @@ export declare class QueueManager {
|
|
|
22
31
|
waitResponse?: boolean;
|
|
23
32
|
}): Promise<void>;
|
|
24
33
|
/**
|
|
25
|
-
*
|
|
26
|
-
* Useful for correlating responses.
|
|
34
|
+
* Gets the alias of the command currently processing or just processed.
|
|
35
|
+
* Useful for correlating received responses with sent commands.
|
|
36
|
+
* @returns The alias of the current command, or undefined if none
|
|
27
37
|
*/
|
|
28
38
|
get currentAlias(): string | undefined;
|
|
29
39
|
/**
|
|
30
|
-
* Clears
|
|
40
|
+
* Clears all pending items from the queue.
|
|
41
|
+
* Useful when handling abrupt disconnections.
|
|
42
|
+
* All pending items will be rejected with a disconnection error.
|
|
31
43
|
*/
|
|
32
44
|
clear(): void;
|
|
33
45
|
/**
|
|
34
|
-
*
|
|
35
|
-
* If
|
|
46
|
+
* Notifies the queue that a response has been received.
|
|
47
|
+
* If waiting for a response, this triggers resolution of the current command
|
|
48
|
+
* and processes the next item in the queue.
|
|
36
49
|
*/
|
|
37
50
|
notifyResponse(): void;
|
|
38
51
|
/**
|
|
39
|
-
*
|
|
52
|
+
* Processes the queue recursively, executing items one by one.
|
|
53
|
+
* Handles timeouts and response waiting when configured.
|
|
54
|
+
* @private
|
|
40
55
|
*/
|
|
41
56
|
private process;
|
|
57
|
+
/**
|
|
58
|
+
* Handles timeout when waiting for a response to a sent command.
|
|
59
|
+
* @private
|
|
60
|
+
* @param item - The queue item that timed out
|
|
61
|
+
*/
|
|
42
62
|
private handleResponseTimeout;
|
|
43
63
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
64
|
+
* Checks if the queue is idle (no pending items, not processing, not waiting for response).
|
|
65
|
+
* @returns True if the queue has no pending operations
|
|
46
66
|
*/
|
|
47
67
|
isIdle(): boolean;
|
|
48
68
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`serialport`)),l=s(require(`events`)),u={DISCONNECTED:`DISCONNECTED`,SCANNING:`SCANNING`,CONNECTING:`CONNECTING`,CONNECTED:`CONNECTED`,RECONNECTING:`RECONNECTING`};var d=class{queue=[];isProcessing=!1;_currentAlias=void 0;waitingForResponse=!1;currentTimeoutTimer=null;currentItem=null;writeHandler;constructor(e){this.writeHandler=e}add(e,t){return new Promise((n,r)=>{this.queue.push({data:e,resolve:n,reject:r,alias:t?.alias,timeout:t?.timeout,waitResponse:t?.waitResponse}),this.process()})}get currentAlias(){return this._currentAlias}clear(){this.queue.forEach(e=>e.reject(Error(`Queue cleared due to disconnection`))),this.queue=[],this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.currentItem.reject(Error(`Queue cleared due to disconnection`))),this.isProcessing=!1,this.waitingForResponse=!1,this.currentItem=null,this.currentTimeoutTimer=null}notifyResponse(){this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}async process(){if(this.isProcessing||this.waitingForResponse||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();if(!e){this.isProcessing=!1;return}this.currentItem=e,this._currentAlias=e.alias;let t=null,n=!1;try{if(!e.waitResponse&&e.timeout&&e.timeout>0&&(t=setTimeout(()=>{n=!0,e.reject(Error(`Write timeout after ${e.timeout}ms`)),this.isProcessing=!1,this.currentItem=null,this.process()},e.timeout)),await this.writeHandler(e.data),n)return;t&&clearTimeout(t),e.waitResponse?(this.waitingForResponse=!0,e.timeout&&e.timeout>0&&(this.currentTimeoutTimer=setTimeout(()=>{this.handleResponseTimeout(e)},e.timeout)),this.isProcessing=!1):(e.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}catch(r){n||(t&&clearTimeout(t),e.reject(r instanceof Error?r:Error(String(r))),this.waitingForResponse=!1,this.currentItem=null,this.isProcessing=!1,this.process())}}handleResponseTimeout(e){this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem=null,e.reject(Error(`Response timeout after ${e.timeout}ms`)),this.process()}isIdle(){return this.queue.length===0&&!this.isProcessing&&!this.waitingForResponse}},f=class{static async findPort(e,t){if(!e&&!t)throw Error(`VendorID or ProductID is required for automatic scanning.`);try{let n=await c.SerialPort.list(),r=n.find(n=>{let r=n.vendorId?.toLowerCase()||``,i=n.productId?.toLowerCase()||``,a=(e||``).toLowerCase().replace(`0x`,``),o=(t||``).toLowerCase().replace(`0x`,``),s=e?r.includes(a):!0,c=t?i.includes(o):!0;return s&&c});return r?r.path:null}catch(e){return console.error(`Error scanning ports:`,e),null}}},p=class e{static instance;lockedPorts=new Set;constructor(){}static getInstance(){return e.instance||=new e,e.instance}register(e){return this.lockedPorts.has(e)?!1:(this.lockedPorts.add(e),!0)}unregister(e){this.lockedPorts.delete(e)}isLocked(e){return this.lockedPorts.has(e)}},m=class extends l.EventEmitter{port=null;queue;_status=u.DISCONNECTED;config;intentionalDisconnect=!1;reconnectTimer=null;constructor(e){super(),this.config=e,this.queue=new d(async e=>this.performWrite(e)),this.config.autoConnect&&this.connect()}get autoConnect(){return this.config.autoConnect}get status(){return this._status}setStatus(e){this._status!==e&&(this._status=e,this.emit(`status`,e))}async connect(){if(this._status===u.CONNECTED||this._status===u.CONNECTING)return;this.intentionalDisconnect=!1,this.setStatus(u.SCANNING);let e=this.config.path;if(!e)try{let t=await f.findPort(this.config.vendorId,this.config.productId);if(t)e=t;else{this.handleConnectionFailure(`Device not found in scan`);return}}catch(e){this.handleConnectionFailure(`Error scanning: ${e}`);return}if(p.getInstance().isLocked(e)){this.handleConnectionFailure(`Port ${e} occupied by another internal instance`);return}if(!p.getInstance().register(e)){this.handleConnectionFailure(`Could not lock port ${e}`);return}this.setStatus(u.CONNECTING),this.openPort(e)}openPort(e){if(this.port=new c.SerialPort({path:e,baudRate:this.config.baudRate,autoOpen:!1}),this.port.open(t=>{if(t){this.handleConnectionFailure(t.message);return}this.config.handshake?this.performHandshake():(this.setStatus(u.CONNECTED),this.emit(`connected`,{path:e,baudRate:this.config.baudRate}))}),this.config.parser){let e=this.port.pipe(this.config.parser);e.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)}),e.on(`error`,e=>this.emit(`error`,e))}else this.port.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)});this.port.on(`error`,e=>{this.emit(`error`,e)}),this.port.on(`close`,()=>{this.cleanup(),this.emit(`disconnected`,this.intentionalDisconnect?`Manual`:`Unexpected`),this.intentionalDisconnect?this.setStatus(u.DISCONNECTED):(this.setStatus(u.RECONNECTING),this.scheduleReconnect())})}async disconnect(){if(this.intentionalDisconnect=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.port&&this.port.isOpen)return new Promise(e=>{this.port?.close(()=>e())})}send(e,t){return this._status===u.CONNECTED?this.queue.add(e,t):Promise.reject(Error(`Port not connected`))}performWrite(e){return new Promise((t,n)=>{if(!this.port||!this.port.isOpen)return n(Error(`Port closed during write`));let r=!this.port.write(e,e=>{if(e)return n(e);r||t()});r&&this.port.once(`drain`,t)})}handleConnectionFailure(e){this.emit(`error`,Error(`Connection failure: ${e}`)),this.cleanup(),this.intentionalDisconnect||(this.setStatus(u.RECONNECTING),this.scheduleReconnect())}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this.connect()},this.config.reconnectInterval)}cleanup(){this.port&&this.port.path&&p.getInstance().unregister(this.port.path),this.port&&(this.port.removeAllListeners(),this.port=null),this.config.parser&&(this.config.parser.removeAllListeners(`data`),this.config.parser.removeAllListeners(`error`)),this.intentionalDisconnect&&this.queue.clear()}performHandshake(){if(!this.port||!this.port.isOpen||!this.config.handshake)return;let{command:e,pattern:t,timeout:n,hexPattern:r}=this.config.handshake,i,a=e=>{let n=``;if(Buffer.isBuffer(e))n=r?e.toString(`hex`):e.toString();else if(typeof e==`string`)n=r?Buffer.from(e).toString(`hex`):e;else{let t=Buffer.from(String(e));n=r?t.toString(`hex`):t.toString()}let i=typeof t==`string`?new RegExp(t):t;i.test(n)&&(o(),this.setStatus(u.CONNECTED),this.emit(`connected`,{path:this.port.path,baudRate:this.config.baudRate}))},o=()=>{clearTimeout(i),this.removeListener(`data`,s)},s=e=>a(e);this.on(`data`,s),i=setTimeout(()=>{o(),this.handleConnectionFailure(`Handshake timeout (pattern: ${t})`)},n),this.performWrite(e).catch(e=>{o(),this.handleConnectionFailure(`Error writing handshake: ${e.message}`)})}};function h(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e.buffer,e.byteOffset,e.byteLength):(Array.isArray(e),Buffer.from(e))}function g(e){return e}function _(e,t=`utf8`){return e.toString(t)}function v(e){return e.toString(`ascii`)}function y(e){return[...e]}exports.PortScanner=f,exports.SerialService=m,exports.SerialStatus=u,exports.toArray=y,exports.toAscii=v,exports.toBuffer=h,exports.toString=_,exports.toUint8Array=g;
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`serialport`)),l=s(require(`events`)),u={DISCONNECTED:`DISCONNECTED`,SCANNING:`SCANNING`,CONNECTING:`CONNECTING`,CONNECTED:`CONNECTED`,RECONNECTING:`RECONNECTING`};var d=class{queue=[];isProcessing=!1;_currentAlias=void 0;waitingForResponse=!1;currentTimeoutTimer=null;currentItem=null;writeHandler;constructor(e){this.writeHandler=e}add(e,t){return new Promise((n,r)=>{this.queue.push({data:e,resolve:n,reject:r,alias:t?.alias,timeout:t?.timeout,waitResponse:t?.waitResponse}),this.process()})}get currentAlias(){return this._currentAlias}clear(){this.queue.forEach(e=>e.reject(Error(`Queue cleared due to disconnection`))),this.queue=[],this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.currentItem.reject(Error(`Queue cleared due to disconnection`))),this.isProcessing=!1,this.waitingForResponse=!1,this.currentItem=null,this.currentTimeoutTimer=null}notifyResponse(){this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}async process(){if(this.isProcessing||this.waitingForResponse||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();if(!e){this.isProcessing=!1;return}this.currentItem=e,this._currentAlias=e.alias;let t=null,n=!1;try{if(!e.waitResponse&&e.timeout&&e.timeout>0&&(t=setTimeout(()=>{n=!0,e.reject(Error(`Write timeout after ${e.timeout}ms`)),this.isProcessing=!1,this.currentItem=null,this.process()},e.timeout)),await this.writeHandler(e.data),n)return;t&&clearTimeout(t),e.waitResponse?(this.waitingForResponse=!0,e.timeout&&e.timeout>0&&(this.currentTimeoutTimer=setTimeout(()=>{this.handleResponseTimeout(e)},e.timeout)),this.isProcessing=!1):(e.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}catch(r){n||(t&&clearTimeout(t),e.reject(r instanceof Error?r:Error(String(r))),this.waitingForResponse=!1,this.currentItem=null,this.isProcessing=!1,this.process())}}handleResponseTimeout(e){this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem=null,e.reject(Error(`Response timeout after ${e.timeout}ms`)),this.process()}isIdle(){return this.queue.length===0&&!this.isProcessing&&!this.waitingForResponse}},f=class{static async findPort(e,t){if(!e&&!t)throw Error(`VendorID or ProductID is required for automatic scanning.`);try{let n=await c.SerialPort.list(),r=n.find(n=>{let r=n.vendorId?.toLowerCase()||``,i=n.productId?.toLowerCase()||``,a=(e||``).toLowerCase().replace(`0x`,``),o=(t||``).toLowerCase().replace(`0x`,``),s=e?r.includes(a):!0,c=t?i.includes(o):!0;return s&&c});return r?r.path:null}catch(e){return console.error(`Error scanning ports:`,e),null}}},p=class e{static instance;lockedPorts=new Set;constructor(){}static getInstance(){return e.instance||=new e,e.instance}register(e){return this.lockedPorts.has(e)?!1:(this.lockedPorts.add(e),!0)}unregister(e){this.lockedPorts.delete(e)}isLocked(e){return this.lockedPorts.has(e)}},m=class extends l.EventEmitter{port=null;queue;_status=u.DISCONNECTED;config;intentionalDisconnect=!1;reconnectTimer=null;constructor(e){super(),this.config=e,this.queue=new d(async e=>this.performWrite(e)),this.config.autoConnect&&this.connect()}get autoConnect(){return this.config.autoConnect}get status(){return this._status}setStatus(e){this._status!==e&&(this._status=e,this.emit(`status`,e))}async connect(){if(this._status===u.CONNECTED||this._status===u.CONNECTING)return;this.intentionalDisconnect=!1,this.setStatus(u.SCANNING);let e=this.config.path;if(!e)try{let t=await f.findPort(this.config.vendorId,this.config.productId);if(t)e=t;else{this.handleConnectionFailure(`Device not found in scan`);return}}catch(e){this.handleConnectionFailure(`Error scanning: ${e}`);return}if(p.getInstance().isLocked(e)){this.handleConnectionFailure(`Port ${e} occupied by another internal instance`);return}if(!p.getInstance().register(e)){this.handleConnectionFailure(`Could not lock port ${e}`);return}this.setStatus(u.CONNECTING),this.openPort(e)}openPort(e){if(this.port=new c.SerialPort({path:e,baudRate:this.config.baudRate,autoOpen:!1}),this.port.open(t=>{if(t){this.handleConnectionFailure(t.message);return}this.config.handshake?this.performHandshake():(this.setStatus(u.CONNECTED),this.emit(`connected`,{path:e,baudRate:this.config.baudRate}))}),this.config.parser){let e=this.port.pipe(this.config.parser);e.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)}),e.on(`error`,e=>this.emit(`error`,e))}else this.port.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)});this.port.on(`error`,e=>{this.emit(`error`,e)}),this.port.on(`close`,()=>{this.cleanup(),this.emit(`disconnected`,this.intentionalDisconnect?`Manual`:`Unexpected`),this.intentionalDisconnect?this.setStatus(u.DISCONNECTED):(this.setStatus(u.RECONNECTING),this.scheduleReconnect())})}async disconnect(){if(this.intentionalDisconnect=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.port&&this.port.isOpen)return new Promise(e=>{this.port?.close(()=>e())})}send(e,t){return this._status===u.CONNECTED?this.queue.add(e,t):Promise.reject(Error(`Port not connected`))}performWrite(e){return new Promise((t,n)=>{if(!this.port||!this.port.isOpen)return n(Error(`Port closed during write`));let r=!this.port.write(e,e=>{if(e)return n(e);r||t()});r&&this.port.once(`drain`,t)})}handleConnectionFailure(e){this.emit(`error`,Error(`Connection failure: ${e}`)),this.cleanup(),this.intentionalDisconnect||(this.setStatus(u.RECONNECTING),this.scheduleReconnect())}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this.connect()},this.config.reconnectInterval)}cleanup(){this.port&&this.port.path&&p.getInstance().unregister(this.port.path),this.port&&(this.port.removeAllListeners(),this.port=null),this.config.parser&&(this.config.parser.removeAllListeners(`data`),this.config.parser.removeAllListeners(`error`)),this.intentionalDisconnect&&this.queue.clear()}performHandshake(){if(!this.port||!this.port.isOpen||!this.config.handshake)return;let{command:e,pattern:t,timeout:n,hexPattern:r}=this.config.handshake,i,a=e=>{let n=``;if(Buffer.isBuffer(e))n=r?e.toString(`hex`):e.toString();else if(typeof e==`string`)n=r?Buffer.from(e).toString(`hex`):e;else{let t=Buffer.from(String(e));n=r?t.toString(`hex`):t.toString()}let i=typeof t==`string`?new RegExp(t):t;i.test(n)&&(o(),this.setStatus(u.CONNECTED),this.emit(`connected`,{path:this.port.path,baudRate:this.config.baudRate}))},o=()=>{clearTimeout(i),this.removeListener(`data`,s)},s=e=>a(e);this.on(`data`,s),i=setTimeout(()=>{o(),this.handleConnectionFailure(`Handshake timeout (pattern: ${t})`)},n),this.performWrite(e).catch(e=>{o(),this.handleConnectionFailure(`Error writing handshake: ${e.message}`)})}queueIsIdle(){return this.queue.isIdle()}};function h(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e.buffer,e.byteOffset,e.byteLength):(Array.isArray(e),Buffer.from(e))}function g(e){return e}function _(e,t=`utf8`){return e.toString(t)}function v(e){return e.toString(`ascii`)}function y(e){return[...e]}exports.PortScanner=f,exports.SerialService=m,exports.SerialStatus=u,exports.toArray=y,exports.toAscii=v,exports.toBuffer=h,exports.toString=_,exports.toUint8Array=g;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SerialPort as e}from"serialport";import{EventEmitter as t}from"events";const n={DISCONNECTED:`DISCONNECTED`,SCANNING:`SCANNING`,CONNECTING:`CONNECTING`,CONNECTED:`CONNECTED`,RECONNECTING:`RECONNECTING`};var r=class{queue=[];isProcessing=!1;_currentAlias=void 0;waitingForResponse=!1;currentTimeoutTimer=null;currentItem=null;writeHandler;constructor(e){this.writeHandler=e}add(e,t){return new Promise((n,r)=>{this.queue.push({data:e,resolve:n,reject:r,alias:t?.alias,timeout:t?.timeout,waitResponse:t?.waitResponse}),this.process()})}get currentAlias(){return this._currentAlias}clear(){this.queue.forEach(e=>e.reject(Error(`Queue cleared due to disconnection`))),this.queue=[],this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.currentItem.reject(Error(`Queue cleared due to disconnection`))),this.isProcessing=!1,this.waitingForResponse=!1,this.currentItem=null,this.currentTimeoutTimer=null}notifyResponse(){this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}async process(){if(this.isProcessing||this.waitingForResponse||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();if(!e){this.isProcessing=!1;return}this.currentItem=e,this._currentAlias=e.alias;let t=null,n=!1;try{if(!e.waitResponse&&e.timeout&&e.timeout>0&&(t=setTimeout(()=>{n=!0,e.reject(Error(`Write timeout after ${e.timeout}ms`)),this.isProcessing=!1,this.currentItem=null,this.process()},e.timeout)),await this.writeHandler(e.data),n)return;t&&clearTimeout(t),e.waitResponse?(this.waitingForResponse=!0,e.timeout&&e.timeout>0&&(this.currentTimeoutTimer=setTimeout(()=>{this.handleResponseTimeout(e)},e.timeout)),this.isProcessing=!1):(e.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}catch(r){n||(t&&clearTimeout(t),e.reject(r instanceof Error?r:Error(String(r))),this.waitingForResponse=!1,this.currentItem=null,this.isProcessing=!1,this.process())}}handleResponseTimeout(e){this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem=null,e.reject(Error(`Response timeout after ${e.timeout}ms`)),this.process()}isIdle(){return this.queue.length===0&&!this.isProcessing&&!this.waitingForResponse}},i=class{static async findPort(t,n){if(!t&&!n)throw Error(`VendorID or ProductID is required for automatic scanning.`);try{let r=await e.list(),i=r.find(e=>{let r=e.vendorId?.toLowerCase()||``,i=e.productId?.toLowerCase()||``,a=(t||``).toLowerCase().replace(`0x`,``),o=(n||``).toLowerCase().replace(`0x`,``),s=t?r.includes(a):!0,c=n?i.includes(o):!0;return s&&c});return i?i.path:null}catch(e){return console.error(`Error scanning ports:`,e),null}}},a=class e{static instance;lockedPorts=new Set;constructor(){}static getInstance(){return e.instance||=new e,e.instance}register(e){return this.lockedPorts.has(e)?!1:(this.lockedPorts.add(e),!0)}unregister(e){this.lockedPorts.delete(e)}isLocked(e){return this.lockedPorts.has(e)}},o=class extends t{port=null;queue;_status=n.DISCONNECTED;config;intentionalDisconnect=!1;reconnectTimer=null;constructor(e){super(),this.config=e,this.queue=new r(async e=>this.performWrite(e)),this.config.autoConnect&&this.connect()}get autoConnect(){return this.config.autoConnect}get status(){return this._status}setStatus(e){this._status!==e&&(this._status=e,this.emit(`status`,e))}async connect(){if(this._status===n.CONNECTED||this._status===n.CONNECTING)return;this.intentionalDisconnect=!1,this.setStatus(n.SCANNING);let e=this.config.path;if(!e)try{let t=await i.findPort(this.config.vendorId,this.config.productId);if(t)e=t;else{this.handleConnectionFailure(`Device not found in scan`);return}}catch(e){this.handleConnectionFailure(`Error scanning: ${e}`);return}if(a.getInstance().isLocked(e)){this.handleConnectionFailure(`Port ${e} occupied by another internal instance`);return}if(!a.getInstance().register(e)){this.handleConnectionFailure(`Could not lock port ${e}`);return}this.setStatus(n.CONNECTING),this.openPort(e)}openPort(t){if(this.port=new e({path:t,baudRate:this.config.baudRate,autoOpen:!1}),this.port.open(e=>{if(e){this.handleConnectionFailure(e.message);return}this.config.handshake?this.performHandshake():(this.setStatus(n.CONNECTED),this.emit(`connected`,{path:t,baudRate:this.config.baudRate}))}),this.config.parser){let e=this.port.pipe(this.config.parser);e.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)}),e.on(`error`,e=>this.emit(`error`,e))}else this.port.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)});this.port.on(`error`,e=>{this.emit(`error`,e)}),this.port.on(`close`,()=>{this.cleanup(),this.emit(`disconnected`,this.intentionalDisconnect?`Manual`:`Unexpected`),this.intentionalDisconnect?this.setStatus(n.DISCONNECTED):(this.setStatus(n.RECONNECTING),this.scheduleReconnect())})}async disconnect(){if(this.intentionalDisconnect=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.port&&this.port.isOpen)return new Promise(e=>{this.port?.close(()=>e())})}send(e,t){return this._status===n.CONNECTED?this.queue.add(e,t):Promise.reject(Error(`Port not connected`))}performWrite(e){return new Promise((t,n)=>{if(!this.port||!this.port.isOpen)return n(Error(`Port closed during write`));let r=!this.port.write(e,e=>{if(e)return n(e);r||t()});r&&this.port.once(`drain`,t)})}handleConnectionFailure(e){this.emit(`error`,Error(`Connection failure: ${e}`)),this.cleanup(),this.intentionalDisconnect||(this.setStatus(n.RECONNECTING),this.scheduleReconnect())}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this.connect()},this.config.reconnectInterval)}cleanup(){this.port&&this.port.path&&a.getInstance().unregister(this.port.path),this.port&&(this.port.removeAllListeners(),this.port=null),this.config.parser&&(this.config.parser.removeAllListeners(`data`),this.config.parser.removeAllListeners(`error`)),this.intentionalDisconnect&&this.queue.clear()}performHandshake(){if(!this.port||!this.port.isOpen||!this.config.handshake)return;let{command:e,pattern:t,timeout:r,hexPattern:i}=this.config.handshake,a,o=e=>{let r=``;if(Buffer.isBuffer(e))r=i?e.toString(`hex`):e.toString();else if(typeof e==`string`)r=i?Buffer.from(e).toString(`hex`):e;else{let t=Buffer.from(String(e));r=i?t.toString(`hex`):t.toString()}let a=typeof t==`string`?new RegExp(t):t;a.test(r)&&(s(),this.setStatus(n.CONNECTED),this.emit(`connected`,{path:this.port.path,baudRate:this.config.baudRate}))},s=()=>{clearTimeout(a),this.removeListener(`data`,c)},c=e=>o(e);this.on(`data`,c),a=setTimeout(()=>{s(),this.handleConnectionFailure(`Handshake timeout (pattern: ${t})`)},r),this.performWrite(e).catch(e=>{s(),this.handleConnectionFailure(`Error writing handshake: ${e.message}`)})}};function s(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e.buffer,e.byteOffset,e.byteLength):(Array.isArray(e),Buffer.from(e))}function c(e){return e}function l(e,t=`utf8`){return e.toString(t)}function u(e){return e.toString(`ascii`)}function d(e){return[...e]}export{i as PortScanner,o as SerialService,n as SerialStatus,d as toArray,u as toAscii,s as toBuffer,l as toString,c as toUint8Array};
|
|
1
|
+
import{SerialPort as e}from"serialport";import{EventEmitter as t}from"events";const n={DISCONNECTED:`DISCONNECTED`,SCANNING:`SCANNING`,CONNECTING:`CONNECTING`,CONNECTED:`CONNECTED`,RECONNECTING:`RECONNECTING`};var r=class{queue=[];isProcessing=!1;_currentAlias=void 0;waitingForResponse=!1;currentTimeoutTimer=null;currentItem=null;writeHandler;constructor(e){this.writeHandler=e}add(e,t){return new Promise((n,r)=>{this.queue.push({data:e,resolve:n,reject:r,alias:t?.alias,timeout:t?.timeout,waitResponse:t?.waitResponse}),this.process()})}get currentAlias(){return this._currentAlias}clear(){this.queue.forEach(e=>e.reject(Error(`Queue cleared due to disconnection`))),this.queue=[],this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.currentItem.reject(Error(`Queue cleared due to disconnection`))),this.isProcessing=!1,this.waitingForResponse=!1,this.currentItem=null,this.currentTimeoutTimer=null}notifyResponse(){this.waitingForResponse&&this.currentItem&&(this.currentTimeoutTimer&&clearTimeout(this.currentTimeoutTimer),this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}async process(){if(this.isProcessing||this.waitingForResponse||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();if(!e){this.isProcessing=!1;return}this.currentItem=e,this._currentAlias=e.alias;let t=null,n=!1;try{if(!e.waitResponse&&e.timeout&&e.timeout>0&&(t=setTimeout(()=>{n=!0,e.reject(Error(`Write timeout after ${e.timeout}ms`)),this.isProcessing=!1,this.currentItem=null,this.process()},e.timeout)),await this.writeHandler(e.data),n)return;t&&clearTimeout(t),e.waitResponse?(this.waitingForResponse=!0,e.timeout&&e.timeout>0&&(this.currentTimeoutTimer=setTimeout(()=>{this.handleResponseTimeout(e)},e.timeout)),this.isProcessing=!1):(e.resolve(),this.currentItem=null,this.isProcessing=!1,this.process())}catch(r){n||(t&&clearTimeout(t),e.reject(r instanceof Error?r:Error(String(r))),this.waitingForResponse=!1,this.currentItem=null,this.isProcessing=!1,this.process())}}handleResponseTimeout(e){this.waitingForResponse=!1,this.currentTimeoutTimer=null,this.currentItem=null,e.reject(Error(`Response timeout after ${e.timeout}ms`)),this.process()}isIdle(){return this.queue.length===0&&!this.isProcessing&&!this.waitingForResponse}},i=class{static async findPort(t,n){if(!t&&!n)throw Error(`VendorID or ProductID is required for automatic scanning.`);try{let r=await e.list(),i=r.find(e=>{let r=e.vendorId?.toLowerCase()||``,i=e.productId?.toLowerCase()||``,a=(t||``).toLowerCase().replace(`0x`,``),o=(n||``).toLowerCase().replace(`0x`,``),s=t?r.includes(a):!0,c=n?i.includes(o):!0;return s&&c});return i?i.path:null}catch(e){return console.error(`Error scanning ports:`,e),null}}},a=class e{static instance;lockedPorts=new Set;constructor(){}static getInstance(){return e.instance||=new e,e.instance}register(e){return this.lockedPorts.has(e)?!1:(this.lockedPorts.add(e),!0)}unregister(e){this.lockedPorts.delete(e)}isLocked(e){return this.lockedPorts.has(e)}},o=class extends t{port=null;queue;_status=n.DISCONNECTED;config;intentionalDisconnect=!1;reconnectTimer=null;constructor(e){super(),this.config=e,this.queue=new r(async e=>this.performWrite(e)),this.config.autoConnect&&this.connect()}get autoConnect(){return this.config.autoConnect}get status(){return this._status}setStatus(e){this._status!==e&&(this._status=e,this.emit(`status`,e))}async connect(){if(this._status===n.CONNECTED||this._status===n.CONNECTING)return;this.intentionalDisconnect=!1,this.setStatus(n.SCANNING);let e=this.config.path;if(!e)try{let t=await i.findPort(this.config.vendorId,this.config.productId);if(t)e=t;else{this.handleConnectionFailure(`Device not found in scan`);return}}catch(e){this.handleConnectionFailure(`Error scanning: ${e}`);return}if(a.getInstance().isLocked(e)){this.handleConnectionFailure(`Port ${e} occupied by another internal instance`);return}if(!a.getInstance().register(e)){this.handleConnectionFailure(`Could not lock port ${e}`);return}this.setStatus(n.CONNECTING),this.openPort(e)}openPort(t){if(this.port=new e({path:t,baudRate:this.config.baudRate,autoOpen:!1}),this.port.open(e=>{if(e){this.handleConnectionFailure(e.message);return}this.config.handshake?this.performHandshake():(this.setStatus(n.CONNECTED),this.emit(`connected`,{path:t,baudRate:this.config.baudRate}))}),this.config.parser){let e=this.port.pipe(this.config.parser);e.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)}),e.on(`error`,e=>this.emit(`error`,e))}else this.port.on(`data`,e=>{this.queue.notifyResponse(),this.emit(`data`,e,this.queue.currentAlias)});this.port.on(`error`,e=>{this.emit(`error`,e)}),this.port.on(`close`,()=>{this.cleanup(),this.emit(`disconnected`,this.intentionalDisconnect?`Manual`:`Unexpected`),this.intentionalDisconnect?this.setStatus(n.DISCONNECTED):(this.setStatus(n.RECONNECTING),this.scheduleReconnect())})}async disconnect(){if(this.intentionalDisconnect=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.port&&this.port.isOpen)return new Promise(e=>{this.port?.close(()=>e())})}send(e,t){return this._status===n.CONNECTED?this.queue.add(e,t):Promise.reject(Error(`Port not connected`))}performWrite(e){return new Promise((t,n)=>{if(!this.port||!this.port.isOpen)return n(Error(`Port closed during write`));let r=!this.port.write(e,e=>{if(e)return n(e);r||t()});r&&this.port.once(`drain`,t)})}handleConnectionFailure(e){this.emit(`error`,Error(`Connection failure: ${e}`)),this.cleanup(),this.intentionalDisconnect||(this.setStatus(n.RECONNECTING),this.scheduleReconnect())}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this.connect()},this.config.reconnectInterval)}cleanup(){this.port&&this.port.path&&a.getInstance().unregister(this.port.path),this.port&&(this.port.removeAllListeners(),this.port=null),this.config.parser&&(this.config.parser.removeAllListeners(`data`),this.config.parser.removeAllListeners(`error`)),this.intentionalDisconnect&&this.queue.clear()}performHandshake(){if(!this.port||!this.port.isOpen||!this.config.handshake)return;let{command:e,pattern:t,timeout:r,hexPattern:i}=this.config.handshake,a,o=e=>{let r=``;if(Buffer.isBuffer(e))r=i?e.toString(`hex`):e.toString();else if(typeof e==`string`)r=i?Buffer.from(e).toString(`hex`):e;else{let t=Buffer.from(String(e));r=i?t.toString(`hex`):t.toString()}let a=typeof t==`string`?new RegExp(t):t;a.test(r)&&(s(),this.setStatus(n.CONNECTED),this.emit(`connected`,{path:this.port.path,baudRate:this.config.baudRate}))},s=()=>{clearTimeout(a),this.removeListener(`data`,c)},c=e=>o(e);this.on(`data`,c),a=setTimeout(()=>{s(),this.handleConnectionFailure(`Handshake timeout (pattern: ${t})`)},r),this.performWrite(e).catch(e=>{s(),this.handleConnectionFailure(`Error writing handshake: ${e.message}`)})}queueIsIdle(){return this.queue.isIdle()}};function s(e){return Buffer.isBuffer(e)?e:e instanceof Uint8Array?Buffer.from(e.buffer,e.byteOffset,e.byteLength):(Array.isArray(e),Buffer.from(e))}function c(e){return e}function l(e,t=`utf8`){return e.toString(t)}function u(e){return e.toString(`ascii`)}function d(e){return[...e]}export{i as PortScanner,o as SerialService,n as SerialStatus,d as toArray,u as toAscii,s as toBuffer,l as toString,c as toUint8Array};
|
package/dist/utils/buffers.d.cts
CHANGED
|
@@ -5,23 +5,35 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Efficiently converts various input types to Buffer.
|
|
7
7
|
* If input is already a Buffer, returns it as is (zero-copy).
|
|
8
|
+
* @param data - The data to convert (string, number array, Uint8Array, or Buffer)
|
|
9
|
+
* @returns A Buffer instance
|
|
8
10
|
*/
|
|
9
11
|
export declare function toBuffer(data: string | number[] | Uint8Array | Buffer): Buffer;
|
|
10
12
|
/**
|
|
11
13
|
* Converts a Buffer to Uint8Array.
|
|
12
14
|
* In Node.js, Buffers ARE Uint8Arrays, so this is ideally zero-copy.
|
|
15
|
+
* @param buf - The buffer to convert
|
|
16
|
+
* @returns The buffer as a Uint8Array
|
|
13
17
|
*/
|
|
14
18
|
export declare function toUint8Array(buf: Buffer): Uint8Array;
|
|
15
19
|
/**
|
|
16
20
|
* Converts a Buffer to string with optional encoding (default utf8).
|
|
17
21
|
* Wrapper alias for toString().
|
|
22
|
+
* @param buf - The buffer to convert
|
|
23
|
+
* @param encoding - The character encoding to use (default: 'utf8')
|
|
24
|
+
* @returns The buffer contents as a string
|
|
18
25
|
*/
|
|
19
26
|
export declare function toString(buf: Buffer, encoding?: BufferEncoding): string;
|
|
20
27
|
/**
|
|
21
|
-
*
|
|
28
|
+
* Converts a Buffer to ASCII string representation.
|
|
29
|
+
* @param buf - The buffer to convert
|
|
30
|
+
* @returns The buffer contents as an ASCII string
|
|
22
31
|
*/
|
|
23
32
|
export declare function toAscii(buf: Buffer): string;
|
|
24
33
|
/**
|
|
25
|
-
* Converts a Buffer to an
|
|
34
|
+
* Converts a Buffer to an array of byte values.
|
|
35
|
+
* Note: Using spread operator may be slow for very large buffers.
|
|
36
|
+
* @param buf - The buffer to convert
|
|
37
|
+
* @returns An array of numbers representing each byte
|
|
26
38
|
*/
|
|
27
39
|
export declare function toArray(buf: Buffer): number[];
|
package/dist/utils/buffers.d.ts
CHANGED
|
@@ -5,23 +5,35 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Efficiently converts various input types to Buffer.
|
|
7
7
|
* If input is already a Buffer, returns it as is (zero-copy).
|
|
8
|
+
* @param data - The data to convert (string, number array, Uint8Array, or Buffer)
|
|
9
|
+
* @returns A Buffer instance
|
|
8
10
|
*/
|
|
9
11
|
export declare function toBuffer(data: string | number[] | Uint8Array | Buffer): Buffer;
|
|
10
12
|
/**
|
|
11
13
|
* Converts a Buffer to Uint8Array.
|
|
12
14
|
* In Node.js, Buffers ARE Uint8Arrays, so this is ideally zero-copy.
|
|
15
|
+
* @param buf - The buffer to convert
|
|
16
|
+
* @returns The buffer as a Uint8Array
|
|
13
17
|
*/
|
|
14
18
|
export declare function toUint8Array(buf: Buffer): Uint8Array;
|
|
15
19
|
/**
|
|
16
20
|
* Converts a Buffer to string with optional encoding (default utf8).
|
|
17
21
|
* Wrapper alias for toString().
|
|
22
|
+
* @param buf - The buffer to convert
|
|
23
|
+
* @param encoding - The character encoding to use (default: 'utf8')
|
|
24
|
+
* @returns The buffer contents as a string
|
|
18
25
|
*/
|
|
19
26
|
export declare function toString(buf: Buffer, encoding?: BufferEncoding): string;
|
|
20
27
|
/**
|
|
21
|
-
*
|
|
28
|
+
* Converts a Buffer to ASCII string representation.
|
|
29
|
+
* @param buf - The buffer to convert
|
|
30
|
+
* @returns The buffer contents as an ASCII string
|
|
22
31
|
*/
|
|
23
32
|
export declare function toAscii(buf: Buffer): string;
|
|
24
33
|
/**
|
|
25
|
-
* Converts a Buffer to an
|
|
34
|
+
* Converts a Buffer to an array of byte values.
|
|
35
|
+
* Note: Using spread operator may be slow for very large buffers.
|
|
36
|
+
* @param buf - The buffer to convert
|
|
37
|
+
* @returns An array of numbers representing each byte
|
|
26
38
|
*/
|
|
27
39
|
export declare function toArray(buf: Buffer): number[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serial-core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": "Resilient serial communication service with automatic reconnection and queues.",
|
|
6
6
|
"author": "Danidoble <danidoble@gmail.com>",
|
|
7
7
|
"license": "GPL-3.0-only",
|