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; } export interface SerialPortInfo extends Map { } 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 { 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; 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> { return this.#ports; } async requestPort({ filters }: SerialPortRequestOptions = {}): Promise { 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; } }