webserial-core 1.2.0 → 2.0.0-dev.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/{LICENSE → LICENSE.md} +1 -1
- package/README.md +142 -287
- package/dist/adapters/web-bluetooth/WebBluetoothProvider.d.ts +19 -0
- package/dist/adapters/web-bluetooth/index.d.ts +6 -0
- package/dist/adapters/web-usb/WebUsbProvider.d.ts +127 -0
- package/dist/adapters/web-usb/index.d.ts +6 -0
- package/dist/adapters/websocket/WebSocketProvider.d.ts +20 -0
- package/dist/adapters/websocket/index.d.ts +6 -0
- package/dist/core/AbstractSerialDevice.d.ts +108 -0
- package/dist/core/SerialEventEmitter.d.ts +37 -0
- package/dist/core/SerialRegistry.d.ts +53 -0
- package/dist/errors/index.d.ts +40 -0
- package/dist/index.d.ts +10 -0
- package/dist/parsers/DelimiterParser.d.ts +22 -0
- package/dist/parsers/FixedLengthParser.d.ts +23 -0
- package/dist/parsers/RawParser.d.ts +22 -0
- package/dist/parsers/index.d.ts +7 -0
- package/dist/queue/CommandQueue.d.ts +98 -0
- package/dist/types/index.d.ts +124 -0
- package/dist/webserial-core.cjs +1 -0
- package/dist/webserial-core.mjs +853 -0
- package/dist/webserial-core.umd.js +1 -0
- package/package.json +62 -68
- package/dist/types/Core.d.ts +0 -268
- package/dist/types/Core.d.ts.map +0 -1
- package/dist/types/Devices.d.ts +0 -62
- package/dist/types/Devices.d.ts.map +0 -1
- package/dist/types/Dispatcher.d.ts +0 -98
- package/dist/types/Dispatcher.d.ts.map +0 -1
- package/dist/types/SerialError.d.ts +0 -61
- package/dist/types/SerialError.d.ts.map +0 -1
- package/dist/types/SerialEvent.d.ts +0 -4
- package/dist/types/SerialEvent.d.ts.map +0 -1
- package/dist/types/Socket.d.ts +0 -29
- package/dist/types/Socket.d.ts.map +0 -1
- package/dist/types/main.d.ts +0 -15
- package/dist/types/main.d.ts.map +0 -1
- package/dist/types/utils.d.ts +0 -3
- package/dist/types/utils.d.ts.map +0 -1
- package/dist/webserial-core.js +0 -1236
- package/dist/webserial-core.js.map +0 -1
- package/dist/webserial-core.umd.cjs +0 -5
- package/dist/webserial-core.umd.cjs.map +0 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { SerialEventEmitter } from './SerialEventEmitter.js';
|
|
2
|
+
import { SerialDeviceOptions, SerialPolyfillOptions, SerialProvider } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for all serial devices.
|
|
5
|
+
*
|
|
6
|
+
* @typeParam T - Type of parsed data emitted by `"serial:data"` events.
|
|
7
|
+
* Use `string` with a delimiter parser, `Uint8Array` for raw/fixed-length,
|
|
8
|
+
* or any custom type with a custom parser.
|
|
9
|
+
*/
|
|
10
|
+
export declare abstract class AbstractSerialDevice<T> extends SerialEventEmitter<T> {
|
|
11
|
+
/** The currently open serial port, or `null` when disconnected. */
|
|
12
|
+
protected port: SerialPort | null;
|
|
13
|
+
private reader;
|
|
14
|
+
private writer;
|
|
15
|
+
private queue;
|
|
16
|
+
private options;
|
|
17
|
+
private isConnecting;
|
|
18
|
+
private abortController;
|
|
19
|
+
private userInitiatedDisconnect;
|
|
20
|
+
private reconnectTimerId;
|
|
21
|
+
private isHandshaking;
|
|
22
|
+
/**
|
|
23
|
+
* Custom serial provider (e.g. a WebUSB polyfill).
|
|
24
|
+
* Falls back to `navigator.serial` when not set.
|
|
25
|
+
*/
|
|
26
|
+
private static customProvider;
|
|
27
|
+
/**
|
|
28
|
+
* Options forwarded to the polyfill provider on every
|
|
29
|
+
* `requestPort()` / `getPorts()` call.
|
|
30
|
+
*/
|
|
31
|
+
private static polyfillOptions;
|
|
32
|
+
constructor(options: SerialDeviceOptions<T>);
|
|
33
|
+
/**
|
|
34
|
+
* Override this method in your subclass to perform a handshake
|
|
35
|
+
* after the port is opened. Return `true` if the handshake
|
|
36
|
+
* succeeds (correct device), or `false` to reject the port.
|
|
37
|
+
*
|
|
38
|
+
* The method receives the opened port and should write/read
|
|
39
|
+
* directly to validate the device identity.
|
|
40
|
+
*
|
|
41
|
+
* If not overridden, all ports are accepted (no handshake).
|
|
42
|
+
*/
|
|
43
|
+
protected handshake(): Promise<boolean>;
|
|
44
|
+
connect(): Promise<void>;
|
|
45
|
+
disconnect(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Internal cleanup: tears down the port, reader, writer without
|
|
48
|
+
* marking it as user-initiated. This allows auto-reconnect to trigger.
|
|
49
|
+
*/
|
|
50
|
+
private cleanupPort;
|
|
51
|
+
forget(): Promise<void>;
|
|
52
|
+
send(data: string | Uint8Array): Promise<void>;
|
|
53
|
+
clearQueue(): void;
|
|
54
|
+
private writeToPort;
|
|
55
|
+
private readLoop;
|
|
56
|
+
/**
|
|
57
|
+
* Opens a port, locks it in the registry, starts reading, and runs the handshake.
|
|
58
|
+
* If handshake fails, tears down reader/queue, closes and unlocks the port.
|
|
59
|
+
*/
|
|
60
|
+
private openAndHandshake;
|
|
61
|
+
/**
|
|
62
|
+
* Cleans up after a failed handshake attempt and restores the queue.
|
|
63
|
+
*/
|
|
64
|
+
private teardownHandshake;
|
|
65
|
+
/**
|
|
66
|
+
* Cancels and releases the current reader.
|
|
67
|
+
*/
|
|
68
|
+
private stopReader;
|
|
69
|
+
/**
|
|
70
|
+
* Wraps the handshake() in a timeout race.
|
|
71
|
+
*/
|
|
72
|
+
private runHandshakeWithTimeout;
|
|
73
|
+
/**
|
|
74
|
+
* Iterates ALL previously-authorized ports matching filters.
|
|
75
|
+
* For each match, opens + handshake. Returns the first port that passes.
|
|
76
|
+
* If a port fails handshake, it is closed and the next one is tried.
|
|
77
|
+
*/
|
|
78
|
+
private findAndValidatePort;
|
|
79
|
+
private startReconnecting;
|
|
80
|
+
stopReconnecting(): void;
|
|
81
|
+
private reconnect;
|
|
82
|
+
static getInstances(): AbstractSerialDevice<unknown>[];
|
|
83
|
+
static connectAll(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Sets a custom serial provider (e.g. a WebUSB polyfill for Android).
|
|
86
|
+
* Call this once before any `connect()` if the native Web Serial API
|
|
87
|
+
* is not available.
|
|
88
|
+
*
|
|
89
|
+
* @param provider The provider object (`{ requestPort, getPorts }`).
|
|
90
|
+
* @param options Polyfill options forwarded on every `requestPort` /
|
|
91
|
+
* `getPorts` call. Use `usbControlInterfaceClass` to
|
|
92
|
+
* support devices that don't use the standard CDC class
|
|
93
|
+
* code (2). E.g. `{ usbControlInterfaceClass: 255 }`.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* import { serial as polyfill } from 'web-serial-polyfill';
|
|
98
|
+
* AbstractSerialDevice.setProvider(polyfill, {
|
|
99
|
+
* usbControlInterfaceClass: 255,
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
static setProvider(provider: SerialProvider, options?: SerialPolyfillOptions): void;
|
|
104
|
+
/**
|
|
105
|
+
* Returns the serial provider: instance provider > custom provider > navigator.serial > null.
|
|
106
|
+
*/
|
|
107
|
+
private getSerial;
|
|
108
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SerialEventMap } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Type-safe event emitter base class for serial device events.
|
|
4
|
+
*
|
|
5
|
+
* All event names and listener signatures are derived from the
|
|
6
|
+
* {@link SerialEventMap} generic type, providing full IDE autocomplete
|
|
7
|
+
* and compile-time safety.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam T - The parsed data type, forwarded to `SerialEventMap<T>`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SerialEventEmitter<T> {
|
|
12
|
+
private listeners;
|
|
13
|
+
/**
|
|
14
|
+
* Registers a listener for the given event.
|
|
15
|
+
*
|
|
16
|
+
* @param event - The event name (constrained to `SerialEventMap` keys).
|
|
17
|
+
* @param listener - The callback to invoke when the event fires.
|
|
18
|
+
* @returns `this` for method chaining.
|
|
19
|
+
*/
|
|
20
|
+
on<K extends keyof SerialEventMap<T>>(event: K, listener: SerialEventMap<T>[K]): this;
|
|
21
|
+
/**
|
|
22
|
+
* Removes a previously registered listener.
|
|
23
|
+
*
|
|
24
|
+
* @param event - The event name.
|
|
25
|
+
* @param listener - The exact listener reference to remove.
|
|
26
|
+
* @returns `this` for method chaining.
|
|
27
|
+
*/
|
|
28
|
+
off<K extends keyof SerialEventMap<T>>(event: K, listener: SerialEventMap<T>[K]): this;
|
|
29
|
+
/**
|
|
30
|
+
* Emits an event, invoking all registered listeners synchronously.
|
|
31
|
+
*
|
|
32
|
+
* @param event - The event name.
|
|
33
|
+
* @param args - Arguments passed to each listener (type-inferred from `SerialEventMap`).
|
|
34
|
+
* @returns `true` if any listeners were invoked, `false` otherwise.
|
|
35
|
+
*/
|
|
36
|
+
emit<K extends keyof SerialEventMap<T>>(event: K, ...args: Parameters<SerialEventMap<T>[K]>): boolean;
|
|
37
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { AbstractSerialDevice } from './AbstractSerialDevice.js';
|
|
2
|
+
/**
|
|
3
|
+
* Global registry for serial device instances and port locks.
|
|
4
|
+
*
|
|
5
|
+
* Maintains:
|
|
6
|
+
* - A `Set` of all registered {@link AbstractSerialDevice} instances.
|
|
7
|
+
* - A `WeakMap` from `SerialPort` to the device instance that currently owns it.
|
|
8
|
+
*
|
|
9
|
+
* All methods are static; this class acts as a module-level singleton.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SerialRegistry {
|
|
12
|
+
private static instances;
|
|
13
|
+
private static portInstanceMap;
|
|
14
|
+
/**
|
|
15
|
+
* Returns all currently registered device instances.
|
|
16
|
+
*
|
|
17
|
+
* @returns An array snapshot of all registered devices.
|
|
18
|
+
*/
|
|
19
|
+
static getInstances(): AbstractSerialDevice<unknown>[];
|
|
20
|
+
/**
|
|
21
|
+
* Registers a device instance so it appears in `getInstances()`.
|
|
22
|
+
*
|
|
23
|
+
* @param instance - The device to register.
|
|
24
|
+
*/
|
|
25
|
+
static register(instance: AbstractSerialDevice<unknown>): void;
|
|
26
|
+
/**
|
|
27
|
+
* Removes a device instance from the registry.
|
|
28
|
+
*
|
|
29
|
+
* @param instance - The device to unregister.
|
|
30
|
+
*/
|
|
31
|
+
static unregister(instance: AbstractSerialDevice<unknown>): void;
|
|
32
|
+
/**
|
|
33
|
+
* Returns `true` if the port is held by a **different** device instance.
|
|
34
|
+
*
|
|
35
|
+
* @param port - The serial port to check.
|
|
36
|
+
* @param instance - The device requesting access.
|
|
37
|
+
* @returns `true` if the port is locked by another device.
|
|
38
|
+
*/
|
|
39
|
+
static isPortInUse(port: SerialPort, instance: AbstractSerialDevice<unknown>): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Assigns exclusive ownership of a port to an instance.
|
|
42
|
+
*
|
|
43
|
+
* @param port - The port to lock.
|
|
44
|
+
* @param instance - The device claiming the port.
|
|
45
|
+
*/
|
|
46
|
+
static lockPort(port: SerialPort, instance: AbstractSerialDevice<unknown>): void;
|
|
47
|
+
/**
|
|
48
|
+
* Releases the exclusive lock on a port.
|
|
49
|
+
*
|
|
50
|
+
* @param port - The port to unlock.
|
|
51
|
+
*/
|
|
52
|
+
static unlockPort(port: SerialPort): void;
|
|
53
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file errors/index.ts
|
|
3
|
+
*
|
|
4
|
+
* Custom error classes for `webserial-core`. All errors extend the native
|
|
5
|
+
* `Error` class and set a stable `name` property for `instanceof` checks
|
|
6
|
+
* across environments that may not preserve class names.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Thrown when a port is requested while another device instance already
|
|
10
|
+
* holds an exclusive lock on it.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SerialPortConflictError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Thrown when the user cancels the port-picker dialog or when the browser
|
|
17
|
+
* denies permission to access a serial port.
|
|
18
|
+
*/
|
|
19
|
+
export declare class SerialPermissionError extends Error {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Thrown when a command in the queue does not receive a response before the
|
|
24
|
+
* configured `commandTimeout` elapses.
|
|
25
|
+
*/
|
|
26
|
+
export declare class SerialTimeoutError extends Error {
|
|
27
|
+
constructor(message: string);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Thrown when reading from an open serial port fails.
|
|
31
|
+
*/
|
|
32
|
+
export declare class SerialReadError extends Error {
|
|
33
|
+
constructor(message: string);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Thrown when writing to an open serial port fails.
|
|
37
|
+
*/
|
|
38
|
+
export declare class SerialWriteError extends Error {
|
|
39
|
+
constructor(message: string);
|
|
40
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './core/AbstractSerialDevice.js';
|
|
2
|
+
export * from './core/SerialEventEmitter.js';
|
|
3
|
+
export * from './core/SerialRegistry.js';
|
|
4
|
+
export * from './errors/index.js';
|
|
5
|
+
export * from './parsers/index.js';
|
|
6
|
+
export * from './queue/CommandQueue.js';
|
|
7
|
+
export * from './types/index.js';
|
|
8
|
+
export * from './adapters/web-usb/index.js';
|
|
9
|
+
export * from './adapters/web-bluetooth/index.js';
|
|
10
|
+
export * from './adapters/websocket/index.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SerialParser } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a delimiter-based parser that splits the byte stream into
|
|
4
|
+
* string messages separated by the given delimiter string.
|
|
5
|
+
*
|
|
6
|
+
* Commonly used with `'\n'` for Arduino `Serial.println()` output.
|
|
7
|
+
*
|
|
8
|
+
* @param char - The delimiter string (e.g. `'\n'`, `'\r\n'`, `';'`).
|
|
9
|
+
* @returns A {@link SerialParser} that emits `string` values.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { AbstractSerialDevice, delimiter } from 'webserial-core';
|
|
14
|
+
*
|
|
15
|
+
* class MyDevice extends AbstractSerialDevice<string> {
|
|
16
|
+
* constructor() {
|
|
17
|
+
* super({ baudRate: 9600, parser: delimiter('\n') });
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function delimiter(char: string): SerialParser<string>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SerialParser } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a fixed-length parser that buffers bytes and emits
|
|
4
|
+
* `Uint8Array` chunks of exactly `length` bytes.
|
|
5
|
+
*
|
|
6
|
+
* Excess bytes are retained in the internal buffer for the next emission.
|
|
7
|
+
*
|
|
8
|
+
* @param length - The exact number of bytes per emitted chunk. Must be > 0.
|
|
9
|
+
* @returns A {@link SerialParser} that emits `Uint8Array` values.
|
|
10
|
+
* @throws {Error} If `length` is not a positive integer.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { AbstractSerialDevice, fixedLength } from 'webserial-core';
|
|
15
|
+
*
|
|
16
|
+
* class SensorDevice extends AbstractSerialDevice<Uint8Array> {
|
|
17
|
+
* constructor() {
|
|
18
|
+
* super({ baudRate: 115200, parser: fixedLength(8) });
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function fixedLength(length: number): SerialParser<Uint8Array>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SerialParser } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a raw pass-through parser that emits each incoming byte chunk
|
|
4
|
+
* directly without any buffering or splitting.
|
|
5
|
+
*
|
|
6
|
+
* Use this when you want to handle framing yourself, or when the device
|
|
7
|
+
* sends infrequent, self-contained binary packets.
|
|
8
|
+
*
|
|
9
|
+
* @returns A {@link SerialParser} that emits raw `Uint8Array` chunks.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { AbstractSerialDevice, raw } from 'webserial-core';
|
|
14
|
+
*
|
|
15
|
+
* class RawDevice extends AbstractSerialDevice<Uint8Array> {
|
|
16
|
+
* constructor() {
|
|
17
|
+
* super({ baudRate: 115200, parser: raw() });
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function raw(): SerialParser<Uint8Array>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file CommandQueue.ts
|
|
3
|
+
*
|
|
4
|
+
* FIFO command queue for serial communication. Commands are sent one at a
|
|
5
|
+
* time; the next command is held until the caller invokes `advance()` (e.g.
|
|
6
|
+
* on receiving a response) or the per-command timeout elapses.
|
|
7
|
+
*
|
|
8
|
+
* Setting `commandTimeout` to `0` disables the timeout and sends commands
|
|
9
|
+
* back-to-back as fast as the port allows.
|
|
10
|
+
*/
|
|
11
|
+
/** Options for constructing a {@link CommandQueue}. */
|
|
12
|
+
export interface CommandQueueOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Maximum time in milliseconds to wait for a response before advancing.
|
|
15
|
+
* Set to `0` to disable per-command timeouts.
|
|
16
|
+
*/
|
|
17
|
+
commandTimeout: number;
|
|
18
|
+
/**
|
|
19
|
+
* Called when the queue is ready to transmit the next command.
|
|
20
|
+
* Must write the bytes to the physical port.
|
|
21
|
+
*
|
|
22
|
+
* @param command - The raw bytes to send.
|
|
23
|
+
*/
|
|
24
|
+
onSend: (command: Uint8Array) => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Called when a command's timeout elapses before `advance()` is invoked.
|
|
27
|
+
*
|
|
28
|
+
* @param command - The command that timed out.
|
|
29
|
+
*/
|
|
30
|
+
onTimeout: (command: Uint8Array) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* FIFO queue that serializes serial commands, optionally waiting for a
|
|
34
|
+
* response (or timeout) between each transmission.
|
|
35
|
+
*/
|
|
36
|
+
export declare class CommandQueue {
|
|
37
|
+
private queue;
|
|
38
|
+
/** `true` while the queue is waiting for a response to the current command. */
|
|
39
|
+
isProcessing: boolean;
|
|
40
|
+
private isPaused;
|
|
41
|
+
private timeoutId;
|
|
42
|
+
private readonly commandTimeout;
|
|
43
|
+
private readonly onSend;
|
|
44
|
+
private readonly onTimeout;
|
|
45
|
+
/**
|
|
46
|
+
* @param options - Queue configuration. See {@link CommandQueueOptions}.
|
|
47
|
+
*/
|
|
48
|
+
constructor(options: CommandQueueOptions);
|
|
49
|
+
/**
|
|
50
|
+
* The number of commands currently waiting in the queue.
|
|
51
|
+
*/
|
|
52
|
+
get queueSize(): number;
|
|
53
|
+
/**
|
|
54
|
+
* Adds a command to the end of the queue and triggers processing
|
|
55
|
+
* if the queue is running and idle.
|
|
56
|
+
*
|
|
57
|
+
* @param command - Raw bytes to enqueue.
|
|
58
|
+
*/
|
|
59
|
+
enqueue(command: Uint8Array): void;
|
|
60
|
+
/**
|
|
61
|
+
* Signals that a response has been received (or processing is otherwise
|
|
62
|
+
* complete) and advances to the next command.
|
|
63
|
+
*
|
|
64
|
+
* Call this after each expected response to keep the queue moving.
|
|
65
|
+
*/
|
|
66
|
+
advance(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Pauses the queue. In-flight commands are not cancelled, but no new
|
|
69
|
+
* command will be dequeued until `resume()` is called.
|
|
70
|
+
*/
|
|
71
|
+
pause(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Resumes a paused queue and immediately processes the next command
|
|
74
|
+
* if one is available.
|
|
75
|
+
*/
|
|
76
|
+
resume(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Discards all pending commands and cancels any active timeout.
|
|
79
|
+
* Does not affect the paused/running state.
|
|
80
|
+
*/
|
|
81
|
+
clear(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Returns a shallow copy of the current queue contents, leaving the
|
|
84
|
+
* queue unchanged.
|
|
85
|
+
*
|
|
86
|
+
* @returns A snapshot array of pending commands.
|
|
87
|
+
*/
|
|
88
|
+
snapshot(): Uint8Array[];
|
|
89
|
+
/**
|
|
90
|
+
* Prepends a previously saved snapshot to the current queue.
|
|
91
|
+
* Useful for restoring commands after a failed handshake.
|
|
92
|
+
*
|
|
93
|
+
* @param saved - Commands to prepend.
|
|
94
|
+
*/
|
|
95
|
+
restore(saved: Uint8Array[]): void;
|
|
96
|
+
private tryProcessNext;
|
|
97
|
+
private clearCommandTimeout;
|
|
98
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { AbstractSerialDevice } from '../core/AbstractSerialDevice.js';
|
|
2
|
+
declare global {
|
|
3
|
+
interface SerialPortInfo {
|
|
4
|
+
usbVendorId?: number;
|
|
5
|
+
usbProductId?: number;
|
|
6
|
+
}
|
|
7
|
+
interface SerialPort {
|
|
8
|
+
readable: ReadableStream<Uint8Array> | null;
|
|
9
|
+
writable: WritableStream<Uint8Array> | null;
|
|
10
|
+
getInfo(): SerialPortInfo;
|
|
11
|
+
open(options: SerialOptions): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
forget?(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
interface SerialOptions {
|
|
16
|
+
baudRate: number;
|
|
17
|
+
dataBits?: number;
|
|
18
|
+
stopBits?: number;
|
|
19
|
+
parity?: "none" | "even" | "odd";
|
|
20
|
+
bufferSize?: number;
|
|
21
|
+
flowControl?: "none" | "hardware";
|
|
22
|
+
}
|
|
23
|
+
interface Navigator {
|
|
24
|
+
serial?: {
|
|
25
|
+
requestPort(options?: {
|
|
26
|
+
filters?: SerialPortFilter[];
|
|
27
|
+
}): Promise<SerialPort>;
|
|
28
|
+
getPorts(): Promise<SerialPort[]>;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export interface SerialPortFilter {
|
|
33
|
+
usbVendorId?: number;
|
|
34
|
+
usbProductId?: number;
|
|
35
|
+
bluetoothServiceClassId?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Options forwarded to the WebUSB polyfill when using a custom serial
|
|
39
|
+
* provider. These map directly to `SerialPolyfillOptions` in
|
|
40
|
+
* `web-serial-polyfill`.
|
|
41
|
+
*/
|
|
42
|
+
export interface SerialPolyfillOptions {
|
|
43
|
+
/** USB interface class for the CDC control interface. Default: `2`.
|
|
44
|
+
* Set to `255` (vendor-specific) for non-standard devices. */
|
|
45
|
+
usbControlInterfaceClass?: number;
|
|
46
|
+
/** USB interface class for the CDC data/transfer interface. Default: `10`. */
|
|
47
|
+
usbTransferInterfaceClass?: number;
|
|
48
|
+
/**
|
|
49
|
+
* USB-to-serial protocol used for device initialization.
|
|
50
|
+
*
|
|
51
|
+
* - `'cdc_acm'` — Standard CDC ACM (class 2). Sends `SetLineCoding` +
|
|
52
|
+
* `SetControlLineState`.
|
|
53
|
+
* - `'cp210x'` — Silicon Labs CP2102 / CP2104 vendor protocol. Sends
|
|
54
|
+
* vendor-specific commands to enable UART, set baud rate, set line
|
|
55
|
+
* control, and activate DTR/RTS.
|
|
56
|
+
* - `'none'` — Skip all initialization. The device is opened and interfaces
|
|
57
|
+
* claimed, but no control transfers are sent.
|
|
58
|
+
*
|
|
59
|
+
* When omitted the provider auto-detects:
|
|
60
|
+
* interface class 2 → `cdc_acm`,
|
|
61
|
+
* vendorId `0x10c4` → `cp210x`,
|
|
62
|
+
* otherwise → `none`.
|
|
63
|
+
*/
|
|
64
|
+
protocol?: "cdc_acm" | "cp210x" | "none";
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A serial provider that abstracts the browser's `navigator.serial` API.
|
|
68
|
+
* Pass a custom implementation (e.g. a WebUSB polyfill) via
|
|
69
|
+
* `AbstractSerialDevice.setProvider()` for platforms that don't
|
|
70
|
+
* support the native Web Serial API.
|
|
71
|
+
*
|
|
72
|
+
* The second argument on each method is **optional** and only used by
|
|
73
|
+
* polyfill providers — the native `navigator.serial` ignores it.
|
|
74
|
+
*/
|
|
75
|
+
export interface SerialProvider {
|
|
76
|
+
requestPort(options?: {
|
|
77
|
+
filters?: SerialPortFilter[];
|
|
78
|
+
}, polyfillOptions?: SerialPolyfillOptions): Promise<SerialPort>;
|
|
79
|
+
getPorts(polyfillOptions?: SerialPolyfillOptions): Promise<SerialPort[]>;
|
|
80
|
+
}
|
|
81
|
+
export interface SerialParser<T> {
|
|
82
|
+
/**
|
|
83
|
+
* Defines how to parse raw byte chunks into the target type T.
|
|
84
|
+
* Emit can be called multiple times if the chunk contains multiple complete frames.
|
|
85
|
+
*/
|
|
86
|
+
parse(chunk: Uint8Array, emit: (parsed: T) => void): void;
|
|
87
|
+
/**
|
|
88
|
+
* Resets the internal state of the parser (e.g. discards partial buffers).
|
|
89
|
+
*/
|
|
90
|
+
reset?(): void;
|
|
91
|
+
}
|
|
92
|
+
export interface SerialDeviceOptions<T> {
|
|
93
|
+
filters?: SerialPortFilter[];
|
|
94
|
+
baudRate: number;
|
|
95
|
+
dataBits?: 7 | 8;
|
|
96
|
+
stopBits?: 1 | 2;
|
|
97
|
+
parity?: "none" | "even" | "odd";
|
|
98
|
+
bufferSize?: number;
|
|
99
|
+
flowControl?: "none" | "hardware";
|
|
100
|
+
commandTimeout?: number;
|
|
101
|
+
parser?: SerialParser<T>;
|
|
102
|
+
/** Enable automatic reconnection when the device disconnects unexpectedly. */
|
|
103
|
+
autoReconnect?: boolean;
|
|
104
|
+
/** Polling interval in ms for scanning authorized ports during reconnection. Default: 1500 */
|
|
105
|
+
autoReconnectInterval?: number;
|
|
106
|
+
/** Timeout in ms for the handshake to complete. Default: 2000 */
|
|
107
|
+
handshakeTimeout?: number;
|
|
108
|
+
/** Optional custom serial provider (e.g. WebUsbProvider) for THIS specific device instance. Overrides the global static Provider. */
|
|
109
|
+
provider?: SerialProvider;
|
|
110
|
+
/** Options forwarded to the custom provider when requesting or getting ports for THIS instance. */
|
|
111
|
+
polyfillOptions?: SerialPolyfillOptions;
|
|
112
|
+
}
|
|
113
|
+
export interface SerialEventMap<T> {
|
|
114
|
+
"serial:connecting": (instance: AbstractSerialDevice<T>) => void;
|
|
115
|
+
"serial:connected": (instance: AbstractSerialDevice<T>) => void;
|
|
116
|
+
"serial:disconnected": (instance: AbstractSerialDevice<T>) => void;
|
|
117
|
+
"serial:reconnecting": (instance: AbstractSerialDevice<T>) => void;
|
|
118
|
+
"serial:data": (data: T, instance: AbstractSerialDevice<T>) => void;
|
|
119
|
+
"serial:sent": (data: Uint8Array, instance: AbstractSerialDevice<T>) => void;
|
|
120
|
+
"serial:error": (error: Error, instance: AbstractSerialDevice<T>) => void;
|
|
121
|
+
"serial:need-permission": (instance: AbstractSerialDevice<T>) => void;
|
|
122
|
+
"serial:queue-empty": (instance: AbstractSerialDevice<T>) => void;
|
|
123
|
+
"serial:timeout": (command: Uint8Array, instance: AbstractSerialDevice<T>) => void;
|
|
124
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class{listeners={};on(e,t){return this.listeners[e]||(this.listeners[e]=new Set),this.listeners[e].add(t),this}off(e,t){return this.listeners[e]&&this.listeners[e].delete(t),this}emit(e,...t){let n=this.listeners[e];if(!n||n.size===0)return!1;for(let e of n)e(...t);return!0}},t=class{static instances=new Set;static portInstanceMap=new WeakMap;static getInstances(){return Array.from(this.instances)}static register(e){this.instances.add(e)}static unregister(e){this.instances.delete(e)}static isPortInUse(e,t){let n=this.portInstanceMap.get(e);return n!==void 0&&n!==t}static lockPort(e,t){this.portInstanceMap.set(e,t)}static unlockPort(e){this.portInstanceMap.delete(e)}},n=class{queue=[];isProcessing=!1;isPaused=!0;timeoutId=null;commandTimeout;onSend;onTimeout;constructor(e){this.commandTimeout=e.commandTimeout,this.onSend=e.onSend,this.onTimeout=e.onTimeout}get queueSize(){return this.queue.length}enqueue(e){this.queue.push(e),this.tryProcessNext()}advance(){this.clearCommandTimeout(),this.isProcessing=!1,this.tryProcessNext()}pause(){this.isPaused=!0,this.clearCommandTimeout(),this.isProcessing=!1}resume(){this.isPaused=!1,this.tryProcessNext()}clear(){this.queue=[],this.clearCommandTimeout(),this.isProcessing=!1}snapshot(){return[...this.queue]}restore(e){this.queue=[...e,...this.queue]}tryProcessNext(){if(this.isPaused||this.isProcessing||this.queue.length===0)return;this.isProcessing=!0;let e=this.queue.shift();this.commandTimeout>0&&(this.timeoutId=setTimeout(()=>{this.timeoutId=null,this.onTimeout(e),this.advance()},this.commandTimeout)),this.onSend(e).catch(()=>{this.advance()})}clearCommandTimeout(){this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}},r=class e extends Error{constructor(t){super(t),this.name=`SerialPortConflictError`,Object.setPrototypeOf(this,e.prototype)}},i=class e extends Error{constructor(t){super(t),this.name=`SerialPermissionError`,Object.setPrototypeOf(this,e.prototype)}},a=class e extends Error{constructor(t){super(t),this.name=`SerialTimeoutError`,Object.setPrototypeOf(this,e.prototype)}},o=class e extends Error{constructor(t){super(t),this.name=`SerialReadError`,Object.setPrototypeOf(this,e.prototype)}},s=class e extends Error{constructor(t){super(t),this.name=`SerialWriteError`,Object.setPrototypeOf(this,e.prototype)}},c=class r extends e{port=null;reader=null;writer=null;queue;options;isConnecting=!1;abortController=null;userInitiatedDisconnect=!1;reconnectTimerId=null;isHandshaking=!1;static customProvider=null;static polyfillOptions;constructor(e){super(),this.options={baudRate:e.baudRate,dataBits:e.dataBits??8,stopBits:e.stopBits??1,parity:e.parity??`none`,bufferSize:e.bufferSize??255,flowControl:e.flowControl??`none`,filters:e.filters??[],commandTimeout:e.commandTimeout??0,parser:e.parser,autoReconnect:e.autoReconnect??!1,autoReconnectInterval:e.autoReconnectInterval??1500,handshakeTimeout:e.handshakeTimeout??2e3,provider:e.provider,polyfillOptions:e.polyfillOptions},this.queue=new n({commandTimeout:this.options.commandTimeout,onSend:async e=>{await this.writeToPort(e),this.emit(`serial:sent`,e,this)},onTimeout:e=>{this.emit(`serial:timeout`,e,this)}}),this.on(`serial:data`,()=>{this.queue.advance()}),t.register(this)}async handshake(){return!0}async connect(){if(!this.isConnecting&&!this.port){this.isConnecting=!0,this.emit(`serial:connecting`,this);try{let e=this.getSerial();if(!e)throw Error(`Web Serial API is not supported in this browser. Use AbstractSerialDevice.setProvider() to set a WebUSB polyfill.`);if(this.port=await this.findAndValidatePort(),!this.port){let t;try{t=await e.requestPort({filters:this.options.filters},this.options.polyfillOptions??r.polyfillOptions)}catch(e){throw e instanceof DOMException&&(e.name===`NotFoundError`||e.name===`SecurityError`||e.name===`AbortError`)?new i(e instanceof Error?e.message:String(e)):e instanceof Error?e:Error(String(e))}if(!await this.openAndHandshake(t))throw Error(`Handshake failed: the selected device did not respond correctly.`);this.port=t}this.abortController=new AbortController,this.queue.resume(),this.emit(`serial:connected`,this)}catch(e){if(e instanceof i?this.emit(`serial:need-permission`,this):this.emit(`serial:error`,e instanceof Error?e:Error(String(e)),this),this.port){t.unlockPort(this.port);try{await this.port.close()}catch{}this.port=null}throw e}finally{this.isConnecting=!1}}}async disconnect(){this.port&&(this.userInitiatedDisconnect=!0,this.stopReconnecting(),await this.cleanupPort())}async cleanupPort(){if(this.port){this.queue.pause(),this.abortController?.abort(),this.abortController=null;try{let e=this.reader,t=this.writer;if(this.reader=null,this.writer=null,e){try{await e.cancel()}catch{}try{e.releaseLock()}catch{}}if(t){try{await t.close()}catch{}try{t.releaseLock()}catch{}}try{await this.port.close()}catch{}}catch(e){this.emit(`serial:error`,e instanceof Error?e:Error(String(e)),this)}finally{this.port&&t.unlockPort(this.port),this.port=null,this.options.parser?.reset?.(),this.emit(`serial:disconnected`,this),!this.userInitiatedDisconnect&&this.options.autoReconnect&&this.startReconnecting(),this.userInitiatedDisconnect=!1}}}async forget(){await this.disconnect(),this.port&&typeof this.port.forget==`function`&&await this.port.forget(),t.unregister(this)}async send(e){let t;t=typeof e==`string`?new TextEncoder().encode(e):e,t.length>0&&this.queue.enqueue(t)}clearQueue(){this.queue.clear(),this.emit(`serial:queue-empty`,this)}async writeToPort(e){if(!this.port||!this.port.writable)throw new s(`Port not writable.`);this.writer=this.port.writable.getWriter();try{await this.writer.write(e)}catch(e){throw new s(e instanceof Error?e.message:String(e))}finally{this.writer.releaseLock(),this.writer=null}}async readLoop(){if(!(!this.port||!this.port.readable)&&!this.reader){this.reader=this.port.readable.getReader();try{for(;;){let{value:e,done:t}=await this.reader.read();if(t)break;e&&(this.options.parser?this.options.parser.parse(e,e=>{this.emit(`serial:data`,e,this)}):this.emit(`serial:data`,e,this))}}catch(e){if(this.port)throw new o(e instanceof Error?e.message:String(e))}finally{if(this.reader){try{this.reader.releaseLock()}catch{}this.reader=null}}}}async openAndHandshake(e){let n=this;if(t.isPortInUse(e,n))return!1;t.lockPort(e,n);try{await e.open({baudRate:this.options.baudRate,dataBits:this.options.dataBits,stopBits:this.options.stopBits,parity:this.options.parity,bufferSize:this.options.bufferSize,flowControl:this.options.flowControl})}catch(n){throw t.unlockPort(e),n instanceof Error?n:Error(String(n))}this.port=e,this.abortController=new AbortController;let r=this.queue.snapshot();this.isHandshaking=!0,this.readLoop().catch(e=>{!this.isHandshaking&&this.port&&(this.emit(`serial:error`,e,this),this.cleanupPort())}),this.queue.resume();try{let t=await this.runHandshakeWithTimeout();return this.isHandshaking=!1,t?(this.queue.pause(),this.queue.clear(),this.queue.restore(r),this.options.parser?.reset?.(),!0):(await this.teardownHandshake(e,r),!1)}catch{return this.isHandshaking=!1,await this.teardownHandshake(e,r),!1}}async teardownHandshake(e,n){this.queue.pause(),this.queue.clear(),this.queue.restore(n),await this.stopReader(),this.port=null,this.abortController=null,this.options.parser?.reset?.();try{await e.close()}catch{}t.unlockPort(e)}async stopReader(){let e=this.reader;if(this.reader=null,e){try{await e.cancel()}catch{}try{e.releaseLock()}catch{}}}async runHandshakeWithTimeout(){let e=this.options.handshakeTimeout??2e3;return Promise.race([this.handshake(),new Promise(t=>setTimeout(()=>t(!1),e))])}async findAndValidatePort(){let e=this.getSerial();if(!e)return null;let n=await e.getPorts(this.options.polyfillOptions??r.polyfillOptions);if(n.length===0)return null;let i=this.options.filters??[],a=this;for(let e of n)if(!t.isPortInUse(e,a)){if(i.length>0){let t=e.getInfo();if(!i.some(e=>{let n=e.usbVendorId===void 0||e.usbVendorId===t.usbVendorId,r=e.usbProductId===void 0||e.usbProductId===t.usbProductId;return n&&r}))continue}try{if(await this.openAndHandshake(e))return e}catch{}}return null}startReconnecting(){this.reconnectTimerId||=(this.emit(`serial:reconnecting`,this),setInterval(async()=>{if(this.port||this.isConnecting){this.stopReconnecting();return}try{let e=await this.findAndValidatePort();e&&(this.stopReconnecting(),await this.reconnect(e))}catch{}},this.options.autoReconnectInterval))}stopReconnecting(){this.reconnectTimerId&&=(clearInterval(this.reconnectTimerId),null)}async reconnect(e){if(!(this.isConnecting||this.port)){this.isConnecting=!0,this.emit(`serial:connecting`,this);try{this.port=e,this.abortController=new AbortController,this.queue.resume(),this.emit(`serial:connected`,this)}catch(e){this.emit(`serial:error`,e instanceof Error?e:Error(String(e)),this),this.port&&=(t.unlockPort(this.port),null),this.options.autoReconnect&&this.startReconnecting()}finally{this.isConnecting=!1}}}static getInstances(){return t.getInstances()}static async connectAll(){let e=t.getInstances();for(let t of e)try{await t.connect()}catch{}}static setProvider(e,t){r.customProvider=e,r.polyfillOptions=t}getSerial(){return this.options.provider?this.options.provider:r.customProvider?r.customProvider:typeof navigator<`u`&&navigator.serial?navigator.serial:null}};function l(e){if(e<=0)throw Error(`FixedLengthParser: length must be greater than 0`);let t=new Uint8Array;return{parse(n,r){let i=new Uint8Array(t.length+n.length);for(i.set(t),i.set(n,t.length),t=i;t.length>=e;)r(t.slice(0,e)),t=t.slice(e)},reset(){t=new Uint8Array}}}function u(e){let t=``,n=new TextDecoder;return{parse(r,i){t+=n.decode(r,{stream:!0});let a;for(;(a=t.indexOf(e))!==-1;)i(t.slice(0,a)),t=t.slice(a+e.length)},reset(){t=``,n=new TextDecoder}}}function d(){return{parse(e,t){t(e)},reset(){}}}var f=32,p=34,m=0,h=30,g=3,_=7,v=1,y=0,b=771,x=768,S=255,C=8,w=`none`,T=1,E=[16,8,7,6,5],D=[1,2],O=[`none`,`even`,`odd`],k=[`none`,`odd`,`even`],A=[1,1.5,2],j={usbControlInterfaceClass:2,usbTransferInterfaceClass:10,protocol:void 0};function M(e,t){let n=e.configurations[0];if(!n)return null;for(let e of n.interfaces)if(e.alternates[0]?.interfaceClass===t)return e;return null}function N(e,t){let n=e.configurations[0];if(!n)return null;for(let e of n.interfaces){let n=e.alternates[0];if(!n||n.interfaceClass!==t)continue;let r=n.endpoints.some(e=>e.direction===`in`),i=n.endpoints.some(e=>e.direction===`out`);if(r&&i)return e}return null}function P(e,t){let n=e.alternates[0];if(n){for(let e of n.endpoints)if(e.direction===t)return e}throw TypeError(`Interface ${e.interfaceNumber} does not have an ${t} endpoint.`)}function F(e,t){return t===2?`cdc_acm`:e.vendorId===4292?`cp210x`:`none`}var I=class{device_;endpoint_;onError_;constructor(e,t,n){this.device_=e,this.endpoint_=t,this.onError_=n}pull(e){(async()=>{let t=this.endpoint_.packetSize;try{let n=await this.device_.transferIn(this.endpoint_.endpointNumber,t);if(n.status!==`ok`){e.error(`USB error: ${n.status}`),this.onError_();return}if(n.data?.buffer&&n.data.byteLength>0){let t=new Uint8Array(n.data.buffer,n.data.byteOffset,n.data.byteLength);t.length>0&&e.enqueue(t)}}catch(t){e.error(String(t)),this.onError_()}})()}},L=class{device_;endpoint_;onError_;constructor(e,t,n){this.device_=e,this.endpoint_=t,this.onError_=n}async write(e,t){try{let n=await this.device_.transferOut(this.endpoint_.endpointNumber,e.buffer);n.status!==`ok`&&(t.error(n.status),this.onError_())}catch(e){t.error(String(e)),this.onError_()}}},R=class{device_;protocol_;controlInterface_;transferInterface_;inEndpoint_;outEndpoint_;serialOptions_;readable_=null;writable_=null;cdcOutputSignals_={dataTerminalReady:!1,requestToSend:!1,break:!1};constructor(e,t){this.device_=e;let n={...j,...t};this.protocol_=n.protocol??F(e,n.usbControlInterfaceClass);let r=n.usbControlInterfaceClass,i=n.usbTransferInterfaceClass;if(r===i){let t=N(e,i);if(!t)throw TypeError(`Unable to find interface with class ${i} that has both IN and OUT endpoints.`);this.controlInterface_=t,this.transferInterface_=t}else{let t=M(e,r);if(!t)throw TypeError(`Unable to find control interface with class ${r}.`);let n=N(e,i)??M(e,i);if(!n)throw TypeError(`Unable to find transfer interface with class ${i}.`);this.controlInterface_=t,this.transferInterface_=n}this.inEndpoint_=P(this.transferInterface_,`in`),this.outEndpoint_=P(this.transferInterface_,`out`)}get readable(){return!this.readable_&&this.device_.opened&&(this.readable_=new ReadableStream(new I(this.device_,this.inEndpoint_,()=>{this.readable_=null}),{highWaterMark:this.serialOptions_?.bufferSize??S})),this.readable_}get writable(){return!this.writable_&&this.device_.opened&&(this.writable_=new WritableStream(new L(this.device_,this.outEndpoint_,()=>{this.writable_=null}),new ByteLengthQueuingStrategy({highWaterMark:this.serialOptions_?.bufferSize??S}))),this.writable_}async open(e){this.serialOptions_=e,this.validateOptions();try{switch(await this.device_.open(),this.device_.configuration===null&&await this.device_.selectConfiguration(1),await this.device_.claimInterface(this.controlInterface_.interfaceNumber),this.controlInterface_!==this.transferInterface_&&await this.device_.claimInterface(this.transferInterface_.interfaceNumber),this.protocol_){case`cdc_acm`:await this.cdcInit();break;case`cp210x`:await this.cp210xInit();break;case`none`:break}}catch(e){throw this.device_.opened&&await this.device_.close(),Error(`Error setting up device: `+(e instanceof Error?e.message:String(e)),{cause:e})}}async close(){let e=[];if(this.readable_&&e.push(this.readable_.cancel()),this.writable_&&e.push(this.writable_.abort()),await Promise.all(e),this.readable_=null,this.writable_=null,this.device_.opened){switch(this.protocol_){case`cdc_acm`:await this.cdcSetSignals({dataTerminalReady:!1,requestToSend:!1});break;case`cp210x`:await this.cp210xDeinit();break}await this.device_.close()}}async forget(){return this.device_.forget()}getInfo(){return{usbVendorId:this.device_.vendorId,usbProductId:this.device_.productId}}async cdcInit(){await this.cdcSetLineCoding(),await this.cdcSetSignals({dataTerminalReady:!0})}async cdcSetSignals(e){if(this.cdcOutputSignals_={...this.cdcOutputSignals_,...e},e.dataTerminalReady!==void 0||e.requestToSend!==void 0){let e=(this.cdcOutputSignals_.dataTerminalReady?1:0)|(this.cdcOutputSignals_.requestToSend?2:0);await this.device_.controlTransferOut({requestType:`class`,recipient:`interface`,request:p,value:e,index:this.controlInterface_.interfaceNumber})}}async cdcSetLineCoding(){let e=new ArrayBuffer(7),t=new DataView(e);if(t.setUint32(0,this.serialOptions_.baudRate,!0),t.setUint8(4,A.indexOf(this.serialOptions_.stopBits??T)),t.setUint8(5,k.indexOf(this.serialOptions_.parity??w)),t.setUint8(6,this.serialOptions_.dataBits??C),(await this.device_.controlTransferOut({requestType:`class`,recipient:`interface`,request:f,value:0,index:this.controlInterface_.interfaceNumber},e)).status!==`ok`)throw new DOMException(`Failed to set line coding.`,`NetworkError`)}async cp210xInit(){let e=this.controlInterface_.interfaceNumber;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:m,value:v,index:e});let t=new ArrayBuffer(4);new DataView(t).setUint32(0,this.serialOptions_.baudRate,!0),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:h,value:0,index:e},t);let n=this.serialOptions_.dataBits??C,r={none:0,odd:16,even:32}[this.serialOptions_.parity??w]??0,i=({1:0,2:2}[this.serialOptions_.stopBits??T]??0)<<8|r|n;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:g,value:i,index:e}),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:_,value:b,index:e})}async cp210xDeinit(){let e=this.controlInterface_.interfaceNumber;await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:_,value:x,index:e}),await this.device_.controlTransferOut({requestType:`vendor`,recipient:`interface`,request:m,value:y,index:e})}validateOptions(){if(this.serialOptions_.baudRate%1!=0)throw RangeError(`Invalid baud rate: ${this.serialOptions_.baudRate}`);if(this.serialOptions_.dataBits!==void 0&&!E.includes(this.serialOptions_.dataBits))throw RangeError(`Invalid dataBits: ${this.serialOptions_.dataBits}`);if(this.serialOptions_.stopBits!==void 0&&!D.includes(this.serialOptions_.stopBits))throw RangeError(`Invalid stopBits: ${this.serialOptions_.stopBits}`);if(this.serialOptions_.parity!==void 0&&!O.includes(this.serialOptions_.parity))throw RangeError(`Invalid parity: ${this.serialOptions_.parity}`)}},z=class{options_;constructor(e){this.options_={...j,...e}}async requestPort(e,t){let n={...this.options_,...t},r=[];if(e?.filters&&e.filters.length>0)for(let t of e.filters){let e={};t.usbVendorId!==void 0&&(e.vendorId=t.usbVendorId),t.usbProductId!==void 0&&(e.productId=t.usbProductId),n.usbControlInterfaceClass!==void 0&&n.usbControlInterfaceClass!==255?e.classCode=n.usbControlInterfaceClass:e.vendorId===void 0&&e.productId===void 0&&(e.classCode=n.usbControlInterfaceClass??2),r.push(e)}else r.push({classCode:n.usbControlInterfaceClass??2});return new R(await navigator.usb.requestDevice({filters:r}),n)}async getPorts(e){let t={...this.options_,...e},n=await navigator.usb.getDevices(),r=[];for(let e of n)try{let n=new R(e,t);r.push(n)}catch{}return r}},B=`6e400001-b5a3-f393-e0a9-e50e24dcca9e`,V=`6e400003-b5a3-f393-e0a9-e50e24dcca9e`,H=`6e400002-b5a3-f393-e0a9-e50e24dcca9e`,U=20,W=10;function G(e){let t=null,n=null,r=null;return{get readable(){return t},get writable(){return n},getInfo(){return{}},async open(){if(!e.gatt)throw Error(`GATT not available on this Bluetooth device.`);r=await e.gatt.connect();let i=await r.getPrimaryService(B),a=await i.getCharacteristic(V),o=await i.getCharacteristic(H);await a.startNotifications(),t=new ReadableStream({start(e){a.addEventListener(`characteristicvaluechanged`,t=>{let n=t.target.value.buffer;e.enqueue(new Uint8Array(n))})}}),n=new WritableStream({async write(e){for(let t=0;t<e.length;t+=U){let n=e.slice(t,t+U);await o.writeValueWithoutResponse(n),t+U<e.length&&await new Promise(e=>setTimeout(e,W))}}})},async close(){r?.connected&&r.disconnect(),t=null,n=null}}}function K(){return{async requestPort(){if(!navigator.bluetooth)throw Error(`Web Bluetooth API is not supported in this browser. Use Chrome on Android, macOS, or ChromeOS.`);return G(await navigator.bluetooth.requestDevice({filters:[{services:[B]}]}))},async getPorts(){return[]}}}function q(e){return new Promise((t,n)=>{e.addEventListener(`open`,()=>t(),{once:!0}),e.addEventListener(`error`,e=>n(e),{once:!0})})}function J(e,t){return new Promise(n=>{let r=i=>{let a=JSON.parse(i.data);a.type===t&&(e.removeEventListener(`message`,r),n(a.payload))};e.addEventListener(`message`,r)})}function Y(e,t){let n=null,r=null;return{get readable(){return n},get writable(){return r},getInfo(){return{usbVendorId:t.vendorId,usbProductId:t.productId}},async open(i){e.send(JSON.stringify({type:`open`,path:t.path,baudRate:i.baudRate,dataBits:i.dataBits,stopBits:i.stopBits,parity:i.parity,parser:{type:`delimiter`,value:`\\n`}})),await J(e,`opened`);let a=[],o=null,s=!1;function c(e){let t=JSON.parse(e.data);if(t.type===`data`&&t.bytes){let e=new Uint8Array(t.bytes);o?o.enqueue(e):a.push(e)}t.type===`closed`&&(s=!0,o&&o.close())}e.addEventListener(`message`,c),n=new ReadableStream({start(e){o=e;for(let t of a)e.enqueue(t);a.length=0,s&&e.close()},cancel(){e.removeEventListener(`message`,c),o=null}}),r=new WritableStream({write(t){e.send(JSON.stringify({type:`write`,bytes:Array.from(t)}))}})},async close(){e.send(JSON.stringify({type:`close`})),n=null,r=null,e.close()}}}function X(e){return{async requestPort(t){let n=new WebSocket(e);await q(n),n.send(JSON.stringify({type:`list-ports`,filters:t?.filters??[]}));let r=(await J(n,`port-list`))[0];if(!r)throw Error(`No ports available on the bridge server. Make sure the Node.js server is running and a device is connected.`);return Y(n,r)},async getPorts(){let t=new WebSocket(e);return await q(t),t.send(JSON.stringify({type:`list-ports`,filters:[]})),(await J(t,`port-list`)).map(e=>Y(t,e))}}}exports.AbstractSerialDevice=c,exports.CommandQueue=n,exports.SerialEventEmitter=e,exports.SerialPermissionError=i,exports.SerialPortConflictError=r,exports.SerialReadError=o,exports.SerialRegistry=t,exports.SerialTimeoutError=a,exports.SerialWriteError=s,exports.WebUsbProvider=z,exports.createBluetoothProvider=K,exports.createWebSocketProvider=X,exports.delimiter=u,exports.fixedLength=l,exports.raw=d;
|