node-web-serial/index.ts

177 lines
3.8 KiB
TypeScript
Raw Normal View History

2022-08-13 10:45:06 +09:00
import { EventEmitter } from "events";
import * as SerialPortStream from "serialport";
// TODO: stream/web 使う https://nodejs.org/en/blog/release/v16.5.0/#experimental-web-streams-api
import { Writable, Readable } from "stream";
type ConnectEventHandler = () => {};
type DisconnectEventHandler = () => {};
interface SerialPortMetadata {
/** Serial Number */
serialNumber?: string;
/** Manufacturer */
manufacturer?: string;
/** Location ID */
locationId?: number | string;
/** Vendor ID */
vendorId?: number | string;
/** Vendor */
vendor?: string;
/** Product ID */
productId?: number | string;
/** Product */
product?: string;
/** PNP ID. Different from Web Serial API specification. */
pnpId?: string;
/** Path. Different from Web Serial API specification. */
path?: string;
}
export interface SerialPortRequestOptions {
filters?: Array<SerialPortMetadata>;
}
export interface SerialPortInfo extends Map<string, undefined | string> {
}
interface Parity {
/** No parity bit is sent for each data word. */
none: never;
/** Data word plus parity bit has even parity. */
even: never;
/** Data word plus parity bit has odd parity. */
odd: never;
/** Parity bit has a mark symbol (logical one). */
mark: never;
/** Parity bit has a space symbol (logical zero). */
space: never;
}
type ParityType = keyof Parity;
export interface SerialOptions {
baudrate?:
| 115200
| 57600
| 38400
| 19200
| 9600
| 4800
| 2400
| 1800
| 1200
| 600
| 300
| 200
| 150
| 134
| 110
| 75
| 50;
databits?: 8 | 7 | 6 | 5;
stopbits?: 1 | 2;
parity?: ParityType;
buffersize?: number;
rtscts?: boolean;
xon?: boolean;
xoff?: boolean;
xany?: boolean;
}
export class SerialPort {
#portInfo: SerialPortStream.PortInfo
#port?: SerialPortStream;
constructor(portInfo: SerialPortStream.PortInfo) {
this.#portInfo = portInfo;
}
getInfo(): SerialPortInfo {
return new Map(Object.entries(this.#portInfo));
}
async open(options: SerialOptions): Promise<void> {
return new Promise((resolve, reject) => {
this.#port = new SerialPortStream(this.#portInfo.path, options, (error) => {
if (error) return reject(error);
return resolve();
})
})
}
get in(): Writable {
}
get out(): Readable {
return this.#port?.readable
}
}
const filterKeys: Array<
& keyof SerialPortMetadata
& keyof SerialPortStream.PortInfo
> = [
"serialNumber",
"manufacturer",
"locationId",
"vendorId",
"productId",
"pnpId",
"path"
];
const matchPort = (filters: SerialPortRequestOptions["filters"]) => (
portInfo: SerialPortStream.PortInfo
) => {
if (filters == null) return true;
return filters.some(filter =>
filterKeys
.flatMap(key => filter[key] == null ? [] : [[filter[key], portInfo[key]]])
.every(([param, info]) => {
if (typeof param === "number") {
return info === param.toString(16);
}
return info === param;
})
);
};
export class Serial extends EventEmitter {
onconnect?: ConnectEventHandler;
ondisconnect?: DisconnectEventHandler;
#ports: Array<SerialPort>;
constructor() {
super();
this.on("connect", () => {
if (this.onconnect != null) this.onconnect();
});
this.on("disconnect", () => {
if (this.ondisconnect != null) this.ondisconnect();
});
this.#ports = []
}
async getPorts(): Promise<Array<SerialPort>> {
return this.#ports;
}
async requestPort({ filters }: SerialPortRequestOptions = {}): Promise<SerialPort> {
const portInfo = await SerialPortStream.list();
const ports = portInfo.filter(matchPort(filters));
if (ports.length === 0) {
throw new Error("Serial port not found.");
}
const port = new SerialPort(ports[0]);
this.#ports = [...this.#ports, port]
return port;
}
}