Update: 更新socket工作模式
This commit is contained in:
30
package-lock.json
generated
30
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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
1
src/common/config.js
Normal file
@@ -0,0 +1 @@
|
||||
export const DEBUG = false;
|
||||
18
src/common/debug.js
Normal file
18
src/common/debug.js
Normal 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
36
src/common/events-base.js
Normal 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
86
src/common/events.js
Normal 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
60
src/common/registry.js
Normal 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
171
src/common/serial.js
Normal 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
23
src/web-socket/socket.js
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user