LeRobot.js / packages /node /src /utils /serial-port-wrapper.ts
NERDDISCO
feat: added node support (#8)
bdc1ac8 unverified
/**
* Node.js Serial Port Wrapper
* Standardized serialport API interface with write/read operations
*/
import { SerialPort } from "serialport";
/**
* Node.js Serial Port wrapper - provides write/read interface similar to web version
*/
export class NodeSerialPortWrapper {
private port: SerialPort;
private isConnected: boolean = false;
public readonly path: string; // Expose path for releaseMotors compatibility
// Expose underlying port for OLD WORKING approach compatibility
public get underlyingPort(): SerialPort {
return this.port;
}
constructor(path: string, options: any = {}) {
this.path = path;
this.port = new SerialPort({
path,
baudRate: options.baudRate || 1000000,
dataBits: options.dataBits || 8,
parity: options.parity || "none",
stopBits: options.stopBits || 1,
autoOpen: false,
});
}
get isOpen(): boolean {
return this.isConnected && this.port.isOpen;
}
async initialize(): Promise<void> {
return new Promise((resolve, reject) => {
this.port.open((err) => {
if (err) {
reject(new Error(`Failed to open port: ${err.message}`));
} else {
this.isConnected = true;
resolve();
}
});
});
}
// Add open method to match SerialPort interface
async open(): Promise<void> {
return this.initialize();
}
async write(data: Uint8Array): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.isConnected) {
reject(new Error("Port not open for writing"));
return;
}
this.port.write(Buffer.from(data), (err) => {
if (err) {
reject(new Error(`Write failed: ${err.message}`));
} else {
resolve();
}
});
});
}
async read(timeout: number = 1000): Promise<Buffer | null> {
return new Promise((resolve, reject) => {
if (!this.isConnected) {
reject(new Error("Port not open for reading"));
return;
}
const timeoutId = setTimeout(() => {
reject(new Error("Read timeout"));
}, timeout);
const onData = (data: Buffer) => {
clearTimeout(timeoutId);
this.port.removeListener("data", onData);
resolve(data); // Return Buffer directly to match interface
};
this.port.once("data", onData);
});
}
async writeAndRead(
data: Uint8Array,
timeout: number = 1000
): Promise<Uint8Array> {
await this.write(data);
// Wait for motor response (motors need time to process command)
await new Promise((resolve) => setTimeout(resolve, 10));
const response = await this.read(timeout);
return response ? new Uint8Array(response) : new Uint8Array();
}
async close(): Promise<void> {
return new Promise((resolve) => {
if (this.isConnected && this.port.isOpen) {
this.port.close(() => {
this.isConnected = false;
resolve();
});
} else {
this.isConnected = false;
resolve();
}
});
}
}