Update: 在线版增加对HID的支持
This commit is contained in:
@@ -1,285 +1,275 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('Mixly.MString');
|
||||
goog.require('Mixly.Serial');
|
||||
goog.require('Mixly.Registry');
|
||||
goog.require('Mixly.Web');
|
||||
goog.provide('Mixly.Web.SerialPort');
|
||||
|
||||
const { MString, Web } = Mixly;
|
||||
const {
|
||||
Serial,
|
||||
Registry,
|
||||
Web
|
||||
} = Mixly;
|
||||
|
||||
const { SerialPort } = Web;
|
||||
|
||||
SerialPort.output = [];
|
||||
SerialPort.inputBuffer = [];
|
||||
SerialPort.outputBuffer = [];
|
||||
SerialPort.refreshInputBuffer = false;
|
||||
SerialPort.refreshOutputBuffer = true;
|
||||
SerialPort.obj = null;
|
||||
SerialPort.onDataLine = null;
|
||||
SerialPort.keepReading = false;
|
||||
class WebSerialPort extends Serial {
|
||||
static {
|
||||
this.portToNameRegistry = new Registry();
|
||||
this.nameToPortRegistry = new Registry();
|
||||
|
||||
SerialPort.encoder = new TextEncoder('utf8');
|
||||
SerialPort.decoder = new TextDecoder('utf8');
|
||||
SerialPort.dtr = false;
|
||||
SerialPort.rts = false;
|
||||
SerialPort.name = 'serialport';
|
||||
|
||||
SerialPort.connect = (baud = 115200, onDataLine = (message) => {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (SerialPort.isConnected()) {
|
||||
resolve();
|
||||
return;
|
||||
this.getConfig = function () {
|
||||
return Serial.getConfig();
|
||||
}
|
||||
navigator.serial.requestPort()
|
||||
.then((device) => {
|
||||
SerialPort.obj = device;
|
||||
return device.open({ baudRate: baud });
|
||||
})
|
||||
.then(() => {
|
||||
SerialPort.keepReading = true;
|
||||
SerialPort.onDataLine = onDataLine;
|
||||
SerialPort.addReadEvent(onDataLine);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
SerialPort.obj = null;
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SerialPort.close = async () => {
|
||||
if (SerialPort.isConnected()) {
|
||||
SerialPort.keepReading = false;
|
||||
if (!SerialPort.isConnected()) {
|
||||
return;
|
||||
this.getSelectedPortName = function () {
|
||||
return Serial.getSelectedPortName();
|
||||
}
|
||||
const serialObj = SerialPort.obj;
|
||||
if (serialObj.readable && serialObj.readable.locked) {
|
||||
try {
|
||||
await SerialPort.reader.cancel();
|
||||
SerialPort.reader.releaseLock();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
this.getCurrentPortsName = function () {
|
||||
return Serial.getCurrentPortsName();
|
||||
}
|
||||
|
||||
this.refreshPorts = function () {
|
||||
let portsName = [];
|
||||
for (let name of this.nameToPortRegistry.keys()) {
|
||||
portsName.push({ name });
|
||||
}
|
||||
Serial.renderSelectBox(portsName);
|
||||
}
|
||||
if (serialObj.writable && serialObj.writable.locked) {
|
||||
try {
|
||||
SerialPort.writer.releaseLock();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
this.requestPort = async function () {
|
||||
const serialport = await navigator.serial.requestPort();
|
||||
this.addPort(serialport);
|
||||
this.refreshPorts();
|
||||
}
|
||||
|
||||
this.getPort = function (name) {
|
||||
return this.nameToPortRegistry.getItem(name);
|
||||
}
|
||||
|
||||
this.addPort = function (serialport) {
|
||||
if (this.portToNameRegistry.hasKey(serialport)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
await serialObj.close();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
SerialPort.obj = null;
|
||||
}
|
||||
}
|
||||
|
||||
SerialPort.isConnected = () => {
|
||||
return SerialPort.obj ? true : false;
|
||||
}
|
||||
|
||||
SerialPort.readLine = () => {
|
||||
var text = "", ch = '';
|
||||
var endWithLF = false;
|
||||
let i = 0;
|
||||
do {
|
||||
ch = SerialPort.readChar();
|
||||
if (ch.length) {
|
||||
if (ch === '\n') {
|
||||
endWithLF = true;
|
||||
} else {
|
||||
text += ch;
|
||||
}
|
||||
}
|
||||
} while (ch.length && !endWithLF)
|
||||
return { text: text, endWithLF: endWithLF };
|
||||
}
|
||||
|
||||
SerialPort.readChar = () => {
|
||||
var readBuf = [];
|
||||
var buffLength = 0;
|
||||
var text = "";
|
||||
const len = SerialPort.outputBuffer.length;
|
||||
/* UTF-8编码方式
|
||||
* ------------------------------------------------------------
|
||||
* |1字节 0xxxxxxx |
|
||||
* |2字节 110xxxxx 10xxxxxx |
|
||||
* |3字节 1110xxxx 10xxxxxx 10xxxxxx |
|
||||
* |4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
|
||||
* |5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
|
||||
* |6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx|
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
for (var i = 0; i < len; i++) {
|
||||
const data = SerialPort.outputBuffer.shift();
|
||||
if ((data & 0x80) == 0x00) {
|
||||
text = String.fromCharCode(data);
|
||||
break;
|
||||
} else if ((data & 0xc0) == 0x80) {
|
||||
readBuf.push(data);
|
||||
if (readBuf.length >= buffLength) {
|
||||
text = SerialPort.decoder.decode(new Uint8Array(readBuf));
|
||||
let name = '';
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
name = `serial${i}`;
|
||||
if (this.nameToPortRegistry.hasKey(name)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let dataNum = data & 0xe0;
|
||||
switch (dataNum) {
|
||||
case 0xfc:
|
||||
buffLength = 6;
|
||||
break;
|
||||
case 0xf8:
|
||||
buffLength = 5;
|
||||
break;
|
||||
case 0xf0:
|
||||
buffLength = 4;
|
||||
break;
|
||||
case 0xe0:
|
||||
buffLength = 3;
|
||||
break;
|
||||
case 0xc0:
|
||||
default:
|
||||
buffLength = 2;
|
||||
this.portToNameRegistry.register(serialport, name);
|
||||
this.nameToPortRegistry.register(name, serialport);
|
||||
}
|
||||
|
||||
this.removePort = function (serialport) {
|
||||
if (!this.portToNameRegistry.hasKey(serialport)) {
|
||||
return;
|
||||
}
|
||||
readBuf.push(data);
|
||||
const name = this.portToNameRegistry.getItem(serialport);
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
this.portToNameRegistry.unregister(serialport);
|
||||
this.nameToPortRegistry.unregister(name);
|
||||
}
|
||||
|
||||
this.addEventsListener = function () {
|
||||
navigator?.serial?.addEventListener('connect', (event) => {
|
||||
this.addPort(event.target);
|
||||
this.refreshPorts();
|
||||
});
|
||||
|
||||
navigator?.serial?.addEventListener('disconnect', (event) => {
|
||||
this.removePort(event.target);
|
||||
this.refreshPorts();
|
||||
});
|
||||
}
|
||||
|
||||
this.init = function () {
|
||||
navigator?.serial?.getPorts().then((serialports) => {
|
||||
for (let serialport of serialports) {
|
||||
this.addPort(serialport);
|
||||
}
|
||||
});
|
||||
this.addEventsListener();
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
SerialPort.startReadLine = (onDataLine = (message) => {}) => {
|
||||
SerialPort.readLineTimer = window.setTimeout(() => {
|
||||
if (!SerialPort.keepReading) {
|
||||
window.clearTimeout(SerialPort.readLineTimer);
|
||||
#serialport_ = null;
|
||||
#keepReading_ = null;
|
||||
#reader_ = null;
|
||||
#writer_ = null;
|
||||
#stringTemp_ = '';
|
||||
constructor(port) {
|
||||
super(port);
|
||||
}
|
||||
|
||||
#addEventsListener_() {
|
||||
this.#addReadEventListener_();
|
||||
}
|
||||
|
||||
async #addReadEventListener_() {
|
||||
const { readable } = this.#serialport_;
|
||||
while (readable && this.#keepReading_) {
|
||||
this.#reader_ = readable.getReader();
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await this.#reader_.read();
|
||||
value && this.onBuffer(value);
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.#keepReading_ = false;
|
||||
Debug.error(error);
|
||||
} finally {
|
||||
this.#reader_ && this.#reader_.releaseLock();
|
||||
await this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async open(baud) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const portsName = Serial.getCurrentPortsName();
|
||||
const currentPortName = this.getPortName();
|
||||
if (!portsName.includes(currentPortName)) {
|
||||
reject('无可用串口');
|
||||
return;
|
||||
}
|
||||
if (this.isOpened()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
baud = baud ?? this.getBaudRate();
|
||||
this.#serialport_ = WebSerialPort.getPort(currentPortName);
|
||||
this.#serialport_.open({ baudRate: baud })
|
||||
.then(() => {
|
||||
super.open(baud);
|
||||
super.setBaudRate(baud);
|
||||
this.#keepReading_ = true;
|
||||
this.onOpen();
|
||||
this.#addEventsListener_();
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
async #waitForUnlock_(timeout) {
|
||||
while (
|
||||
(this.#serialport_.readable && this.#serialport_.readable.locked) ||
|
||||
(this.#serialport_.writable && this.#serialport_.writable.locked)
|
||||
) {
|
||||
await this.sleep(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (!this.isOpened()) {
|
||||
return;
|
||||
}
|
||||
let endWithLF = false;
|
||||
do {
|
||||
const readObj = SerialPort.readLine();
|
||||
endWithLF = readObj.endWithLF;
|
||||
const { text } = readObj;
|
||||
SerialPort.output.push((SerialPort.output.length? SerialPort.output.pop() : '') + text);
|
||||
if (endWithLF) {
|
||||
const len = SerialPort.output.length;
|
||||
SerialPort.output[len - 1] = MString.decode(SerialPort.output[len - 1]);
|
||||
if (len) {
|
||||
onDataLine(SerialPort.output[len - 1]);
|
||||
}
|
||||
SerialPort.output.push('');
|
||||
}
|
||||
} while (endWithLF);
|
||||
while (SerialPort.output.length > 500) {
|
||||
SerialPort.output.shift();
|
||||
super.close();
|
||||
if (this.#serialport_.readable?.locked) {
|
||||
this.#keepReading_ = false;
|
||||
await this.#reader_?.cancel();
|
||||
}
|
||||
if (SerialPort.keepReading) {
|
||||
SerialPort.startReadLine(onDataLine);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
await this.#waitForUnlock_(400);
|
||||
this.#reader_ = undefined;
|
||||
await this.#serialport_.close();
|
||||
this.#stringTemp_ = '';
|
||||
this.onClose(1);
|
||||
}
|
||||
|
||||
SerialPort.addReadEvent = async (onDataLine = (message) => {}) => {
|
||||
SerialPort.output = [];
|
||||
SerialPort.inputBuffer = [];
|
||||
SerialPort.outputBuffer = [];
|
||||
SerialPort.refreshInputBuffer = false;
|
||||
SerialPort.refreshOutputBuffer = true;
|
||||
SerialPort.startReadLine(onDataLine);
|
||||
while (SerialPort.obj.readable && SerialPort.keepReading) {
|
||||
SerialPort.reader = SerialPort.obj.readable.getReader();
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await SerialPort.reader.read();
|
||||
if (SerialPort.refreshOutputBuffer && value) {
|
||||
SerialPort.outputBuffer = [ ...SerialPort.outputBuffer, ...value ];
|
||||
}
|
||||
if (SerialPort.refreshInputBuffer && value) {
|
||||
SerialPort.inputBuffer = [ ...SerialPort.inputBuffer, ...value ];
|
||||
}
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
async setBaudRate(baud) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isOpened()
|
||||
|| this.getBaudRate() === baud
|
||||
|| !this.baudRateIsLegal(baud)) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.close()
|
||||
.then(() => this.open(baud))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
async sendString(str) {
|
||||
const buffer = this.encode(str);
|
||||
return this.sendBuffer(buffer);
|
||||
}
|
||||
|
||||
async sendBuffer(buffer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { writable } = this.#serialport_;
|
||||
const writer = writable.getWriter();
|
||||
if (!(buffer instanceof Uint8Array)) {
|
||||
buffer = new Uint8Array(buffer);
|
||||
}
|
||||
writer.write(buffer)
|
||||
.then(() => {
|
||||
writer.releaseLock();
|
||||
resolve();
|
||||
})
|
||||
.catch(() => {
|
||||
writer.releaseLock();
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async setDTRAndRTS(dtr, rts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isOpened()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.#serialport_.setSignals({
|
||||
dataTerminalReady: dtr,
|
||||
requestToSend: rts
|
||||
})
|
||||
.then(() => {
|
||||
super.setDTRAndRTS(dtr, rts);
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
async setDTR(dtr) {
|
||||
return this.setDTRAndRTS(dtr, this.getRTS());
|
||||
}
|
||||
|
||||
async setRTS(rts) {
|
||||
return this.setDTRAndRTS(this.getDTR(), rts);
|
||||
}
|
||||
|
||||
onBuffer(buffer) {
|
||||
super.onBuffer(buffer);
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
super.onByte(buffer[i]);
|
||||
}
|
||||
const string = this.decodeBuffer(buffer);
|
||||
if (!string) {
|
||||
return;
|
||||
}
|
||||
for (let char of string) {
|
||||
super.onChar(char);
|
||||
if (['\r', '\n'].includes(char)) {
|
||||
super.onString(this.#stringTemp_);
|
||||
this.#stringTemp_ = '';
|
||||
} else {
|
||||
this.#stringTemp_ += char;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
SerialPort.reader.releaseLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SerialPort.AddOnConnectEvent = (onConnect) => {
|
||||
navigator.serial.addEventListener('connect', (event) => {
|
||||
onConnect();
|
||||
});
|
||||
}
|
||||
|
||||
SerialPort.AddOnDisconnectEvent = (onDisconnect) => {
|
||||
navigator.serial.addEventListener('disconnect', (event) => {
|
||||
SerialPort.obj && SerialPort.close();
|
||||
onDisconnect();
|
||||
});
|
||||
}
|
||||
|
||||
SerialPort.writeString = async (str) => {
|
||||
const buffer = SerialPort.encoder.encode(str);
|
||||
await SerialPort.writeByteArr(buffer);
|
||||
}
|
||||
|
||||
SerialPort.writeByteArr = async (buffer) => {
|
||||
const writer = SerialPort.obj.writable.getWriter();
|
||||
await writer.write(new Int8Array(buffer).buffer);
|
||||
writer.releaseLock();
|
||||
await SerialPort.sleep(200);
|
||||
}
|
||||
|
||||
SerialPort.writeCtrlA = async () => {
|
||||
await SerialPort.writeByteArr([1, 13, 10]);
|
||||
}
|
||||
|
||||
SerialPort.writeCtrlB = async () => {
|
||||
await SerialPort.writeByteArr([2, 13, 10]);
|
||||
}
|
||||
|
||||
SerialPort.writeCtrlC = async () => {
|
||||
await SerialPort.writeByteArr([3, 13, 10]);
|
||||
}
|
||||
|
||||
SerialPort.writeCtrlD = async () => {
|
||||
await SerialPort.writeByteArr([3, 4]);
|
||||
}
|
||||
|
||||
SerialPort.setBaudRate = async (baud) => {
|
||||
SerialPort.keepReading = false;
|
||||
const serialObj = SerialPort.obj;
|
||||
await SerialPort.close();
|
||||
await serialObj.open({ baudRate: baud - 0 });
|
||||
SerialPort.obj = serialObj;
|
||||
SerialPort.keepReading = true;
|
||||
SerialPort.setSignals(SerialPort.dtr, SerialPort.rts);
|
||||
SerialPort.addReadEvent(SerialPort.onDataLine);
|
||||
}
|
||||
|
||||
SerialPort.setDTR = async (value) => {
|
||||
SerialPort.dtr = value;
|
||||
await SerialPort.obj.setSignals({ dataTerminalReady: value });
|
||||
}
|
||||
|
||||
SerialPort.setRTS = async (value) => {
|
||||
SerialPort.rts = value;
|
||||
await SerialPort.obj.setSignals({ requestToSend: value });
|
||||
}
|
||||
|
||||
SerialPort.setSignals = async (dtr, rts) => {
|
||||
SerialPort.dtr = dtr;
|
||||
SerialPort.rts = rts;
|
||||
await SerialPort.obj.setSignals({ dataTerminalReady: dtr, requestToSend: rts });
|
||||
}
|
||||
|
||||
SerialPort.sleep = (ms) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
Web.SerialPort = WebSerialPort;
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user