Update: 更新socket工作模式

This commit is contained in:
王立帮
2024-11-29 21:21:58 +08:00
parent 546912edd7
commit d8ceafadbf
9 changed files with 428 additions and 0 deletions

30
package-lock.json generated
View File

@@ -9,12 +9,15 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"await-to-js": "^3.0.0",
"commander": "^12.1.0",
"express": "^4.21.1",
"fs-extra": "^11.2.0",
"lodash": "^4.17.21",
"mitt": "^3.0.1",
"serialport": "^12.0.0",
"shelljs": "^0.8.5",
"shortid": "^2.2.16",
"simple-git": "^3.27.0",
"socket.io": "^4.8.1",
"usb": "^2.14.0"
@@ -270,6 +273,14 @@
"resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/await-to-js": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/await-to-js/-/await-to-js-3.0.0.tgz",
"integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -917,11 +928,21 @@
"node": "*"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nanoid": {
"version": "2.1.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-2.1.11.tgz",
"integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz",
@@ -1235,6 +1256,15 @@
"node": ">=4"
}
},
"node_modules/shortid": {
"version": "2.2.16",
"resolved": "https://registry.npmmirror.com/shortid/-/shortid-2.2.16.tgz",
"integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"dependencies": {
"nanoid": "^2.1.0"
}
},
"node_modules/side-channel": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz",

View File

@@ -13,12 +13,15 @@
"author": "Mixly Team",
"license": "ISC",
"dependencies": {
"await-to-js": "^3.0.0",
"commander": "^12.1.0",
"express": "^4.21.1",
"fs-extra": "^11.2.0",
"lodash": "^4.17.21",
"mitt": "^3.0.1",
"serialport": "^12.0.0",
"shelljs": "^0.8.5",
"shortid": "^2.2.16",
"simple-git": "^3.27.0",
"socket.io": "^4.8.1",
"usb": "^2.14.0"

1
src/common/config.js Normal file
View File

@@ -0,0 +1 @@
export const DEBUG = false;

18
src/common/debug.js Normal file
View File

@@ -0,0 +1,18 @@
import { DEBUG } from './config';
const Debug = {};
for (let key in console) {
if (typeof console[key] !== 'function') {
continue;
}
Debug[key] = (...args) => {
if (DEBUG) {
console[key](...args);
} else {
console.log(`[${key.toUpperCase()}]`, ...args);
}
}
}
export default Debug;

36
src/common/events-base.js Normal file
View File

@@ -0,0 +1,36 @@
import Events from './events';
export default class EventsBase {
#events_ = new Events();
constructor() {}
bind(type, func) {
return this.#events_.bind(type, func);
}
unbind(id) {
this.#events_.unbind(id);
}
addEventsType(eventsType) {
this.#events_.addType(eventsType);
}
runEvent(eventsType, ...args) {
return this.#events_.run(eventsType, ...args);
}
offEvent(eventsType) {
this.#events_.off(eventsType);
}
resetEvent() {
this.#events_.reset();
}
disposeEvent() {
this.resetEvent();
this.#events_ = null;
}
}

86
src/common/events.js Normal file
View File

@@ -0,0 +1,86 @@
// import mitt from 'mitt';
import _ from 'lodash';
import shortid from 'shortid';
import Debug from './debug';
import Registry from './registry';
export default class Events {
#eventsType_ = [];
#events_ = new Registry();
constructor(eventsType = []) {
this.#eventsType_ = eventsType;
}
addType(eventsType) {
this.#eventsType_ = _.uniq(_.concat([this.#eventsType_, eventsType]));
}
exist(type) {
if (!this.#eventsType_.includes(type)) {
Debug.warn(`${type} event does not exist under the class`);
return false;
}
return true;
}
bind(type, func) {
if (!this.exist(type)) {
return this;
}
const id = shortid.generate();
let typeEvent = this.#events_.getItem(type);
if (!typeEvent) {
typeEvent = new Registry();
this.#events_.register(type, typeEvent);
}
typeEvent.register(id, func);
return id;
}
unbind(id) {
for (let [_, value] of this.#events_.getAllItems()) {
let typeEvent = value;
if (!typeEvent.getItem(id)) {
continue;
}
typeEvent.unregister(id);
}
return this;
}
off(type) {
if (this.#events_.getItem(type)) {
this.#events_.unregister(type);
}
return this;
}
run(type, ...args) {
let outputs = [];
if (!this.exist(type)) {
return outputs;
}
const eventsFunc = this.#events_.getItem(type);
if (!eventsFunc) {
return outputs;
}
for (let [_, func] of eventsFunc.getAllItems()) {
outputs.push(func(...args));
}
return outputs;
}
reset() {
this.#events_.reset();
}
length(type) {
const typeEvent = this.#events_.getItem(type);
if (typeEvent) {
return typeEvent.length();
}
return 0;
}
}

60
src/common/registry.js Normal file
View File

@@ -0,0 +1,60 @@
export default class Registry {
#registry_ = new Map();
constructor() {
this.reset();
}
reset() {
this.#registry_.clear();
}
validate(keys) {
if (!(keys instanceof Array)) {
keys = [keys];
}
return keys;
}
register(keys, value) {
keys = this.validate(keys);
for (let key of keys) {
if (this.#registry_.has(key)) {
Debug.warn(`${key}已存在,不可重复注册`);
continue;
}
this.#registry_.set(key, value);
}
}
unregister(keys) {
keys = this.validate(keys);
for (let key of keys) {
if (!this.#registry_.has(key)) {
Debug.warn(`${key}不存在,无需取消注册`);
continue;
}
this.#registry_.delete(key);
}
}
length() {
return this.#registry_.size;
}
hasKey(key) {
return this.#registry_.has(key);
}
keys() {
return [...this.#registry_.keys()];
}
getItem(key) {
return this.#registry_.get(key) ?? null;
}
getAllItems() {
return this.#registry_;
}
}

171
src/common/serial.js Normal file
View File

@@ -0,0 +1,171 @@
import os from 'node:os';
import { ChildProcess } from 'node:child_process';
import {
SerialPort,
ReadlineParser,
ByteLengthParser
} from 'serialport';
import EventsBase from './events-base';
export default class Serial extends EventsBase {
static {
this.portsName = [];
this.getCurrentPortsName = function () {
return this.portsName;
}
this.getPorts = async function () {
return new Promise((resolve, reject) => {
if (os.platform() === 'linux') {
ChildProcess.exec('ls /dev/ttyACM* /dev/tty*USB*', (_, stdout, stderr) => {
let portsName = MArray.unique(stdout.split('\n'));
let newPorts = [];
for (let i = 0; i < portsName.length; i++) {
if (!portsName[i]) {
continue;
}
newPorts.push({
vendorId: 'None',
productId: 'None',
name: portsName[i]
});
}
resolve(newPorts);
});
} else {
SerialPort.list().then(ports => {
let newPorts = [];
for (let i = 0; i < ports.length; i++) {
let port = ports[i];
newPorts.push({
vendorId: port.vendorId,
productId: port.productId,
name: port.path
});
}
resolve(newPorts);
}).catch(reject);
}
});
}
}
#serialport_ = null;
#parserBytes_ = null;
#parserLine_ = null;
#port_ = null;
constructor(port) {
this.#port_ = port;
this.addEventsType(['buffer', 'String', 'error', 'open', 'close']);
}
#addEventsListener_() {
this.#parserBytes_.on('data', (buffer) => {
this.runEvent('buffer', buffer);
});
this.#parserLine_.on('data', (str) => {
this.runEvent('String', str);
});
this.#serialport_.on('error', (error) => {
this.runEvent('error', error);
});
this.#serialport_.on('open', () => {
this.runEvent('open');
});
this.#serialport_.on('close', () => {
this.runEvent('close');
});
}
getPortName() {
return this.#port_;
}
async open(baud) {
return new Promise((resolve, reject) => {
this.#serialport_ = new SerialPort({
path: this.getPortName(),
baudRate: baud, // 波特率
dataBits: 8, // 数据位
parity: 'none', // 奇偶校验
stopBits: 1, // 停止位
flowControl: false,
autoOpen: false // 不自动打开
}, false);
this.#parserBytes_ = this.#serialport_.pipe(new ByteLengthParser({ length: 1 }));
this.#parserLine_ = this.#serialport_.pipe(new ReadlineParser());
this.#serialport_.open((error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
this.#addEventsListener_();
});
}
async close() {
return new Promise((resolve, reject) => {
this.#serialport_.close((error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
async setBaudRate(baud) {
return new Promise((resolve, reject) => {
this.#serialport_.update({ baudRate: baud }, (error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
async send(data) {
return new Promise((resolve, reject) => {
this.#serialport_.write(data, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
async setDTRAndRTS(dtr, rts) {
return new Promise((resolve, reject) => {
this.#serialport_.set({ dtr, rts }, (error) => {
if (error) {
reject(error);
} else {
super.setDTRAndRTS(dtr, rts);
resolve();
}
});
});
}
dispose() {
this.disposeEvent();
this.#serialport_ = null;
this.#parserBytes_ = null;
this.#parserLine_ = null;
this.#port_ = null;
}
}

23
src/web-socket/socket.js Normal file
View File

@@ -0,0 +1,23 @@
import { Server } from 'socket.io';
import to from 'await-to-js';
import Serial from '../common/serial';
import Debug from '../common/debug';
export default class Socket {
#io_ = null;
constructor(httpsServer, options) {
this.#io_ = new Server(httpsServer, options);
this.#io_.on('connection', (socket) => {
this.#addEventsListener_(socket);
});
}
#addEventsListener_(socket) {
socket.on('serial/get-ports', async () => {
const [error, result] = await to(Serial.getPorts());
error && Debug.error(error);
return result;
});
}
}