Initial commit
This commit is contained in:
135
services/serialService.ts
Normal file
135
services/serialService.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { SerialPort, NavigatorSerial } from '../types';
|
||||
|
||||
export class SerialManager {
|
||||
port: SerialPort | null = null;
|
||||
private reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
||||
private writer: WritableStreamDefaultWriter<Uint8Array> | null = null;
|
||||
private isReading = false;
|
||||
|
||||
// Callback for receiving data
|
||||
private onDataCallback: ((data: Uint8Array) => void) | null = null;
|
||||
// Optional bridge target to automatically forward read data to
|
||||
private bridgeTarget: SerialManager | null = null;
|
||||
|
||||
constructor(public id: string) {}
|
||||
|
||||
async connect(baudRate: number): Promise<void> {
|
||||
const nav = navigator as unknown as { serial: NavigatorSerial };
|
||||
if (!nav.serial) {
|
||||
console.error("Web Serial API is not supported in this environment.");
|
||||
throw new Error("Web Serial API not supported in this browser.");
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[${this.id}] Requesting port...`);
|
||||
// Explicitly passing filters: [] allows the user to see all available ports
|
||||
this.port = await nav.serial.requestPort({ filters: [] });
|
||||
console.log(`[${this.id}] Port selected. Opening with baudRate ${baudRate}...`);
|
||||
await this.port.open({ baudRate });
|
||||
console.log(`[${this.id}] Port opened successfully.`);
|
||||
} catch (err) {
|
||||
console.error(`[${this.id}] Failed to connect:`, err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
this.isReading = false;
|
||||
|
||||
if (this.reader) {
|
||||
try {
|
||||
await this.reader.cancel();
|
||||
// The loop will catch the cancel and release the lock
|
||||
} catch (e) {
|
||||
console.warn("Error cancelling reader", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.writer) {
|
||||
try {
|
||||
this.writer.releaseLock();
|
||||
} catch (e) {
|
||||
console.warn("Error releasing writer lock", e);
|
||||
}
|
||||
this.writer = null;
|
||||
}
|
||||
|
||||
if (this.port) {
|
||||
// Wait a tick for lock release
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await this.port?.close();
|
||||
console.log(`[${this.id}] Port closed.`);
|
||||
} catch(e) { console.error("Error closing port", e); }
|
||||
this.port = null;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
setBridgeTarget(target: SerialManager | null) {
|
||||
this.bridgeTarget = target;
|
||||
}
|
||||
|
||||
startReading(onData: (data: Uint8Array) => void) {
|
||||
if (!this.port || !this.port.readable) return;
|
||||
|
||||
this.onDataCallback = onData;
|
||||
this.isReading = true;
|
||||
this.readLoop();
|
||||
}
|
||||
|
||||
private async readLoop() {
|
||||
while (this.port && this.port.readable && this.isReading) {
|
||||
try {
|
||||
this.reader = this.port.readable.getReader();
|
||||
while (true) {
|
||||
const { value, done } = await this.reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
if (value) {
|
||||
// 1. Notify UI
|
||||
if (this.onDataCallback) this.onDataCallback(value);
|
||||
|
||||
// 2. Bypass/Bridge: If a target is set, write data immediately to it
|
||||
if (this.bridgeTarget) {
|
||||
this.bridgeTarget.send(value).catch(err => console.error("Bridge write error:", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Read error on ${this.id}:`, error);
|
||||
break;
|
||||
} finally {
|
||||
if (this.reader) {
|
||||
this.reader.releaseLock();
|
||||
this.reader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async send(data: Uint8Array): Promise<void> {
|
||||
if (!this.port || !this.port.writable) {
|
||||
console.warn(`[${this.id}] Cannot send: Port not writable or disconnected.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.writer) {
|
||||
this.writer = this.port.writable.getWriter();
|
||||
}
|
||||
|
||||
try {
|
||||
await this.writer.write(data);
|
||||
} catch (e) {
|
||||
console.error("Write error:", e);
|
||||
// If writer is dead, release and retry next time
|
||||
this.writer.releaseLock();
|
||||
this.writer = null;
|
||||
throw e;
|
||||
}
|
||||
// We intentionally keep the writer locked for performance in high-throughput
|
||||
// scenarios, but for a general app, you might releaseLock here if you expect
|
||||
// other things to grab it. For this app, SerialManager owns the writer.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user