-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathserialEngine.js
More file actions
157 lines (144 loc) · 6.14 KB
/
Copy pathserialEngine.js
File metadata and controls
157 lines (144 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* ═══════════════════════════════════════════════════════════════
* serialEngine.js — Web Serial API 通信适配器
*
* 职责:封装 Web Serial API 的串口连接、数据读取与断开逻辑,
* 通过回调向 app.js 上报数据和连接状态变化。
*
* 代码结构:
* 1. 构造函数(端口、读取器、回调、断开事件监听)
* 2. 回调注册(onData、onStatusChange)
* 3. 连接管理(connect、disconnect、forceDisconnect)
* 4. 数据传输(send、readLoop)
* ═══════════════════════════════════════════════════════════════ */
class SerialEngine {
/** 初始化端口/读取器状态、回调指针,注册浏览器串口断开事件 */
constructor() {
this.port = null;
this.reader = null;
this.keepReading = false;
this.readTask = null;
this._closing = null;
this._connected = false;
this.onDataCallback = null;
this.onConnectStatusChange = null;
this._onSerialDisconnect = (event) => {
if (!this.port) return;
if (this.port && event && event.port && event.port !== this.port) return;
void this._handlePortDisconnect();
};
if (typeof navigator !== 'undefined' && navigator.serial && navigator.serial.addEventListener) {
navigator.serial.addEventListener('disconnect', this._onSerialDisconnect);
}
}
/* ── 回调注册 ── */
/** 注册数据到达回调 callback(Uint8Array) */
onData(callback) { this.onDataCallback = callback; }
/** 注册连接状态变化回调 callback(connected: boolean, error?: Error) */
onStatusChange(callback) { this.onConnectStatusChange = callback; }
/* ── 连接管理 ── */
/** 请求用户选择串口并打开,成功后启动 readLoop 持续读取 */
async connect(config) {
if (!('serial' in navigator)) {
throw new Error('当前浏览器不支持 Web Serial API');
}
try {
const port = await navigator.serial.requestPort();
await port.open({
baudRate: config.baudRate || 115200,
dataBits: config.dataBits || 8,
stopBits: config.stopBits || 1,
parity: config.parity || 'none'
});
this.port = port;
this.keepReading = true;
this.readTask = this.readLoop();
this._connected = true;
if (this.onConnectStatusChange) this.onConnectStatusChange(true);
return true;
} catch (error) {
this.port = null;
throw error;
}
}
/** 安全断开串口:取消读取 → 释放读取器 → 关闭端口 → 通知状态 */
async disconnect() {
if (this._closing) return this._closing;
this._closing = (async () => {
this.keepReading = false;
try { if (this.reader) await this.reader.cancel(); } catch (_) {}
try { if (this.readTask) await this.readTask; } catch (_) {}
try { if (this.port) await this.port.close(); } catch (_) {}
this.port = null;
this.readTask = null;
if (this._connected) {
this._connected = false;
if (this.onConnectStatusChange) this.onConnectStatusChange(false);
}
this._closing = null;
})();
return this._closing;
}
/** 强制断开(接口与 NetEngine 保持一致,内部委托给 disconnect) */
async forceDisconnect() {
return this.disconnect();
}
/** 浏览器串口断开事件处理(用户拔出设备时触发) */
async _handlePortDisconnect() {
await this.disconnect();
}
/* ── 数据传输 ── */
/** 向串口写入 Uint8Array 数据 */
async send(data) {
if (!this.port || !this.port.writable) throw new Error('串口未连接,无法发送');
const writer = this.port.writable.getWriter();
try {
await writer.write(data instanceof Uint8Array ? data : new Uint8Array(data));
} finally {
writer.releaseLock();
}
}
/** 持续读取串口数据流,通过 onDataCallback 上报,直到 keepReading 为 false */
async readLoop() {
let failed = false;
let failure = null;
while (this.port && this.port.readable && this.keepReading) {
let reader;
try { reader = this.port.readable.getReader(); }
catch (error) { failure = error; failed = true; this.keepReading = false; break; }
this.reader = reader;
try {
while (this.keepReading) {
const { value, done } = await reader.read();
if (done) {
if (this.keepReading) {
failure = new Error('串口读取已结束');
failed = true;
this.keepReading = false;
}
break;
}
if (value && this.onDataCallback) this.onDataCallback(value);
}
} catch (error) {
if (this.keepReading) {
failure = error;
failed = true;
this.keepReading = false;
}
} finally {
reader.releaseLock();
if (this.reader === reader) this.reader = null;
}
}
if (failed) {
try { if (this.port) await this.port.close(); } catch (_) {}
this.port = null;
if (this._connected) {
this._connected = false;
if (this.onConnectStatusChange) this.onConnectStatusChange(false, failure);
}
}
}
}
globalThis.SerialPlotter ??= {};
globalThis.SerialPlotter.SerialEngine = SerialEngine;