Files
mixly3-server/mixly/common/modules/mixly-modules/web/serialport.js
2026-01-24 16:12:04 +08:00

279 lines
7.5 KiB
JavaScript

goog.loadJs('web', () => {
goog.require('Mixly.Serial');
goog.require('Mixly.Debug');
goog.require('Mixly.Config');
goog.require('Mixly.Web');
goog.provide('Mixly.Web.SerialPort');
const {
Serial,
Debug,
Config,
Web
} = Mixly;
const { SELECTED_BOARD } = Config;
class WebSerialPort extends Serial {
static {
this.type = 'serialport';
this.getConfig = function () {
return Serial.getConfig();
}
this.getSelectedPortName = function () {
return Serial.getSelectedPortName();
}
this.getCurrentPortsName = function () {
return Serial.getCurrentPortsName();
}
this.refreshPorts = function () {
Serial.refreshPorts();;
}
this.requestPort = async function () {
let options = SELECTED_BOARD?.web?.devices?.serial;
if (!options || typeof(options) !== 'object') {
options = {
filters: []
};
}
const serialport = await navigator.serial.requestPort(options);
this.addPort(serialport);
this.refreshPorts();
}
this.getPort = function (name) {
return Serial.nameToPortRegistry.getItem(name);
}
this.addPort = function (serialport) {
if (this.portToNameRegistry.hasKey(serialport)) {
return;
}
let name = '';
for (let i = 1; i <= 20; i++) {
name = `serial${i}`;
if (Serial.nameToPortRegistry.hasKey(name)) {
continue;
}
break;
}
Serial.portToNameRegistry.register(serialport, name);
Serial.nameToPortRegistry.register(name, serialport);
}
this.removePort = function (serialport) {
if (!Serial.portToNameRegistry.hasKey(serialport)) {
return;
}
const name = Serial.portToNameRegistry.getItem(serialport);
if (!name) {
return;
}
Serial.portToNameRegistry.unregister(serialport);
Serial.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();
}
}
#serialport_ = null;
#keepReading_ = null;
#reader_ = null;
#writer_ = null;
#stringTemp_ = '';
constructor(port) {
super(port);
this.#serialport_ = WebSerialPort.getPort(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 !== undefined && 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) {
const portsName = Serial.getCurrentPortsName();
const currentPortName = this.getPortName();
if (!portsName.includes(currentPortName)) {
throw Error('no device available');
return;
}
if (this.isOpened()) {
return;
}
baud = baud ?? this.getBaudRate();
this.#serialport_ = WebSerialPort.getPort(currentPortName);
await this.#serialport_.open({ baudRate: baud });
super.open(baud);
super.setBaudRate(baud);
this.#keepReading_ = true;
this.onOpen();
this.#addEventsListener_();
}
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;
}
super.close();
if (this.#serialport_.readable?.locked) {
this.#keepReading_ = false;
await this.#reader_?.cancel();
}
await this.#waitForUnlock_(400);
this.#reader_ = undefined;
await this.#serialport_.close();
this.#stringTemp_ = '';
this.onClose(1);
}
async setBaudRate(baud) {
if (!this.isOpened()
|| this.getRawBaudRate() === baud
|| !this.baudRateIsLegal(baud)) {
return;
}
await this.close();
await this.open(baud);
}
async sendString(str) {
const buffer = this.encode(str);
return this.sendBuffer(buffer);
}
async sendBuffer(buffer) {
const { writable } = this.#serialport_;
const writer = writable.getWriter();
if (buffer.constructor.name !== 'Uint8Array') {
buffer = new Uint8Array(buffer);
}
try {
await writer.write(buffer);
writer.releaseLock();
} catch (error) {
writer.releaseLock();
throw Error(error);
}
}
async setDTRAndRTS(dtr, rts) {
if (!this.isOpened()) {
return;
}
await this.#serialport_.setSignals({
dataTerminalReady: dtr,
requestToSend: rts
});
super.setDTRAndRTS(dtr, rts);
}
async setDTR(dtr) {
if (!this.isOpened()) {
return;
}
await this.#serialport_.setSignals({
dataTerminalReady: dtr
});
super.setDTR(dtr);
}
async setRTS(rts) {
if (!this.isOpened()) {
return;
}
await this.#serialport_.setSignals({
requestToSend: rts
});
super.setRTS(rts);
}
getVID() {
const info = this.#serialport_.getInfo();
return info.usbVendorId;
}
getPID() {
const info = this.#serialport_.getInfo();
return info.usbProductId;
}
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;
}
}
}
}
Web.SerialPort = WebSerialPort;
});