feat: 添加对web compiler的支持

This commit is contained in:
王立帮
2025-05-04 14:40:11 +08:00
parent b1747a3ad5
commit a7bc967378
12 changed files with 709 additions and 319 deletions

View File

@@ -16,7 +16,7 @@ module.exports = {
"no-else-return": 2, // 如果if语句里面有return,后面不能跟else语句
"no-empty": 2, // 块语句中的内容不能为空
"no-var": 0, // 不能使用 var 定义变量
"indent": [2, 4], // 缩进风格
"indent": [2, 4, { SwitchCase: 1 }], // 缩进风格
"strict": 2,
"use-isnan": 2,
"no-redeclare": 2, // 禁止重复声明变量

43
package-lock.json generated
View File

@@ -7,9 +7,10 @@
"": {
"name": "mixly3-server",
"version": "1.0.0",
"license": "ISC",
"license": "MIT",
"dependencies": {
"await-to-js": "^3.0.0",
"better-sse": "^0.14.1",
"commander": "^12.1.0",
"express": "^4.21.1",
"fs-extra": "^11.2.0",
@@ -20,7 +21,7 @@
"mustache": "^4.2.0",
"serialport": "^12.0.0",
"shelljs": "^0.8.5",
"shortid": "^2.2.16",
"shortid": "^2.2.17",
"simple-git": "^3.27.0",
"socket.io": "^4.8.1",
"usb": "^2.14.0"
@@ -1003,6 +1004,15 @@
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/better-sse": {
"version": "0.14.1",
"resolved": "https://registry.npmmirror.com/better-sse/-/better-sse-0.14.1.tgz",
"integrity": "sha512-htQOrymWPKD/LDkRRlgmqS5+xX3p9/BetUQTRshqtwuSk+aQ63RluzHx0CJpY1AYUadCvUex8J+B6XwkGLkZxw==",
"engines": {
"node": ">=20",
"pnpm": ">=9"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -3300,9 +3310,22 @@
}
},
"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=="
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
@@ -4520,12 +4543,12 @@
}
},
"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.",
"version": "2.2.17",
"resolved": "https://registry.npmmirror.com/shortid/-/shortid-2.2.17.tgz",
"integrity": "sha512-GpbM3gLF1UUXZvQw6MCyulHkWbRseNO4cyBEZresZRorwl1+SLu1ZdqgVtuwqz8mB6RpwPkm541mYSqrKyJSaA==",
"license": "MIT",
"dependencies": {
"nanoid": "^2.1.0"
"nanoid": "^3.3.8"
}
},
"node_modules/side-channel": {

View File

@@ -15,9 +15,10 @@
"url": "https://gitee.com/bnu_mixly/mixly3-server.git"
},
"author": "Mixly Team",
"license": "ISC",
"license": "MIT",
"dependencies": {
"await-to-js": "^3.0.0",
"better-sse": "^0.14.1",
"commander": "^12.1.0",
"express": "^4.21.1",
"fs-extra": "^11.2.0",
@@ -28,7 +29,7 @@
"mustache": "^4.2.0",
"serialport": "^12.0.0",
"shelljs": "^0.8.5",
"shortid": "^2.2.16",
"shortid": "^2.2.17",
"simple-git": "^3.27.0",
"socket.io": "^4.8.1",
"usb": "^2.14.0"

200
src/common/boards.js Normal file
View File

@@ -0,0 +1,200 @@
import fs from 'node:fs';
import path from 'node:path';
import MString from './mstring';
import { ARDUINO } from './config';
const Boards = {};
Boards.TYPE = {
AVR: [
"arduino:avr:uno",
"arduino:avr:nano",
"arduino:avr:pro",
"arduino:avr:mega",
"arduino:avr:leonardo"
],
ESP8266: [
"esp8266:esp8266:generic",
"esp8266:esp8266:nodemcu",
"esp8266:esp8266:nodemcuv2",
"esp8266:esp8266:d1"
],
ESP32: [
"esp32:esp32:esp32",
"esp32:esp32:pico32",
"esp32:esp32:node32s",
"esp32:esp32:nodemcu-32s",
"esp32:esp32:m5stack-fire",
"esp32:esp32:mPython",
"esp32:esp32:esp32cam",
"esp32:esp32:bpi-bit"
],
ESP32S2: [
"esp32:esp32:esp32s2"
],
ESP32S3: [
"esp32:esp32:esp32s3"
],
ESP32C3: [
"esp32:esp32:esp32c3"
]
};
Boards.FIRMWARE_PATH = {
AVR: [
{
path: '{buildPath}/testArduino.ino.hex'
}
],
ESP8266: [
{
path: '{buildPath}/testArduino.ino.bin',
offset: '0x0'
}
],
ESP32: [
{
path: '{buildPath}/testArduino.ino.bootloader.bin',
offset: '0x1000'
}, {
path: '{buildPath}/testArduino.ino.partitions.bin',
offset: '0x8000'
}, {
path: '{shellPath}/Arduino15/packages/esp32/hardware/esp32/2.0.15/tools/partitions/boot_app0.bin',
offset: '0xe000'
}, {
path: '{buildPath}/testArduino.ino.bin',
offset: '0x10000'
}
],
ESP32S2: [
{
path: '{buildPath}/testArduino.ino.bootloader.bin',
offset: '0x1000'
}, {
path: '{buildPath}/testArduino.ino.partitions.bin',
offset: '0x8000'
}, {
path: '{shellPath}/Arduino15/packages/esp32/hardware/esp32/2.0.5/tools/partitions/boot_app0.bin',
offset: '0xe000'
}, {
path: '{buildPath}/testArduino.ino.bin',
offset: '0x10000'
}
],
ESP32S3: [
{
path: '{buildPath}/testArduino.ino.bootloader.bin',
offset: '0x1000'
}, {
path: '{buildPath}/testArduino.ino.partitions.bin',
offset: '0x8000'
}, {
path: '{shellPath}/Arduino15/packages/esp32/hardware/esp32/2.0.5/tools/partitions/boot_app0.bin',
offset: '0xe000'
}, {
path: '{buildPath}/testArduino.ino.bin',
offset: '0x10000'
}
],
ESP32C3: [
{
path: '{buildPath}/testArduino.ino.bootloader.bin',
offset: '0x0'
}, {
path: '{buildPath}/testArduino.ino.partitions.bin',
offset: '0x8000'
}, {
path: '{shellPath}/Arduino15/packages/esp32/hardware/esp32/2.0.5/tools/partitions/boot_app0.bin',
offset: '0xe000'
}, {
path: '{buildPath}/testArduino.ino.bin',
offset: '0x10000'
}
]
}
Boards.getBoardType = (board) => {
if (typeof board !== 'string') {
return null;
}
const boardParam = board.split(':');
if (boardParam.length < 3) {
return null;
}
let boardType;
const inBoardType = boardParam[0] + ':' + boardParam[1];
switch (inBoardType) {
case 'arduino:avr':
boardType = 'AVR';
break;
case 'esp8266:esp8266':
boardType = 'ESP8266';
break;
case 'esp32:esp32':
boardType = 'ESP32';
break;
default:
return null;
}
if (boardType !== 'ESP32') {
return boardType;
}
switch (boardParam[2]) {
case 'esp32c3':
boardType = 'ESP32C3';
break;
case 'esp32s2':
boardType = 'ESP32S2';
break;
case 'esp32s3':
boardType = 'ESP32S3';
break;
default:
boardType = 'ESP32';
}
return boardType;
}
Boards.getBoardKey = (board) => {
const idxs = [];
for (let i = 0; i < board.length; i++) {
if (board[i] === ':') idxs.push(i);
if (idxs.length === 3) break;
}
return idxs.length === 3 ? board.slice(0, idxs[2]) : board;
}
Boards.exist = (board) => {
const boardType = Boards.getBoardType(board);
if (!boardType) {
return false;
}
const boardParam = board.split(':');
const inBoardType = boardParam[0] + ':' + boardParam[1];
board = inBoardType + ':' + boardParam[2];
if (Boards.TYPE[boardType] && Boards.TYPE[boardType].includes(board)) {
return true;
}
return false;
}
Boards.getFiles = async (board, buildPath) => {
const type = Boards.getBoardType(board);
const output = [];
const files = Boards.FIRMWARE_PATH[type];
for (let file of files) {
const filePath = MString.tpl(file.path, {
buildPath,
shellPath: ARDUINO.path.folder
});
const data = await fs.promises.readFile(filePath, {
encoding: path.extname(filePath) === '.hex' ? 'utf8' : 'hex'
});
output.push({ ...file, data });
}
return output;
}
export default Boards;

View File

@@ -7,6 +7,5 @@ export const ARDUINO = CONFIG.arduino;
export const MICROPYTHON = CONFIG.micropython;
export const PYTHON = CONFIG.python;
export const CURRENT_PLANTFORM = os.platform();
export const WEB_SOCKT_TEMP_PATH = CONFIG.webSocketTempPath;
export const WEB_COMPILER_TEMP_PATH = CONFIG.webSocketTempPath;
export const TEMP_PATH = CONFIG.tempPath;
export const CLIENT_PATH = CONFIG.clientPath;

View File

@@ -29,7 +29,7 @@ export default class EventsBase {
return this.#events_.reset();
}
disposeEvent() {
dispose() {
this.resetEvent();
this.#events_ = null;
}

View File

@@ -0,0 +1,20 @@
import shortid from 'shortid';
const IdGenerator = {};
IdGenerator.generate = function (input) {
let output = {};
if (input instanceof Array) {
for (let i of input) {
if (typeof i !== 'string') {
continue;
}
output[i] = shortid.generate();
}
return output;
}
return shortid.generate();
}
export default IdGenerator;

View File

@@ -82,7 +82,10 @@ export default class Shell extends EventsBase {
}
async kill() {
if (this.#killed_) {
if (this.#killed_ || !this.#shell_) {
return;
}
if (this.#shell_.exitCode !== null) {
return;
}
this.#shell_.stdin.end();
@@ -92,9 +95,15 @@ export default class Shell extends EventsBase {
} else {
this.#shell_.kill('SIGTERM')
}
this.#shell_ = null;
}
getShell() {
return this.#shell_;
}
dispose() {
this.#shell_ = null;
super.dispose();
}
}

408
src/common/socket.js Normal file
View File

@@ -0,0 +1,408 @@
import { Server } from 'socket.io';
import to from 'await-to-js';
import { usb } from 'usb';
import path from 'node:path';
import fsExtra from 'fs-extra';
import Serial from './serial';
import Debug from './debug';
import Registry from './registry';
import ShellArduino from './shell-arduino';
import ShellMicroPython from './shell-micropython';
import ShellAmpy from './shell-ampy';
import MString from './mstring';
import Boards from './boards';
import { TEMP_PATH, CLIENT_PATH } from './config';
export default class Socket {
#io_ = null;
#namespaceAll_ = null;
#namespaceCompile_ = null;
#serialRegistry_ = new Registry();
#shellMicroPython_ = new Registry();
#shellArduino_ = new Registry();
#shellAmpy_ = new Registry();
constructor(httpsServer, options) {
this.#io_ = new Server(httpsServer, options);
this.#namespaceAll_ = this.#io_.of('/all');
this.#namespaceCompile_ = this.#io_.of('/compile');
this.#namespaceCompile_.on('connection', (socket) => {
this.#shellArduino_.register(socket.id, new ShellArduino());
this.#addEventsListenerForMode1_(socket);
});
this.#namespaceAll_.on('connection', (socket) => {
this.#shellMicroPython_.register(socket.id, new ShellMicroPython());
this.#shellArduino_.register(socket.id, new ShellArduino());
this.#shellAmpy_.register(socket.id, new ShellAmpy());
this.#addEventsListenerForMode2_(socket);
});
usb.on('attach', (device) => {
this.#namespaceAll_.emit('serial.attachEvent', device);
});
usb.on('detach', (device) => {
this.#namespaceAll_.emit('serial.detachEvent', device);
});
}
#addEventsListenerForMode1_(socket) {
socket.on('disconnect', () => {
const folderPath = path.resolve(TEMP_PATH, socket.id);
fsExtra.remove(folderPath).catch(Debug.error);
const shellArduino = this.#shellArduino_.getItem(socket.id);
shellArduino.dispose();
this.#shellArduino_.unregister(socket.id);
});
const shell = this.#shellArduino_.getItem(socket.id);
shell.bind('data', (data) => {
socket.emit('arduino.dataEvent', data);
});
shell.bind('error', (data) => {
socket.emit('arduino.errorEvent', data);
});
shell.bind('close', (code, time) => {
socket.emit('arduino.closeEvent', code, time);
});
socket.on('arduino.compile', async (config, callback) => {
if (!Boards.exist(config.key)) {
callback(['illegal board name', {}]);
return;
}
const shell = this.#shellArduino_.getItem(socket.id);
config.path = config?.path ?? {};
config.path.build = path.resolve(TEMP_PATH, socket.id, 'build');
config.path.code = path.resolve(TEMP_PATH, socket.id, 'testArduino/testArduino.ino');
let [error1,] = await to(fsExtra.ensureDir(config.path.build));
error1 && Debug.error(error1);
let [error2,] = await to(fsExtra.outputFile(config.path.code, config.code));
error2 && Debug.error(error2);
const [error, result] = await to(shell.compile(config));
if (error) {
Debug.error(error);
callback([error, result]);
return;
}
callback([error, result]);
});
socket.on('arduino.upload', async (config, callback) => {
if (!Boards.exist(config.key)) {
callback(['illegal board name', {}]);
return;
}
const shell = this.#shellArduino_.getItem(socket.id);
config.path = config?.path ?? {};
config.path.build = path.resolve(TEMP_PATH, socket.id, 'build');
config.path.code = path.resolve(TEMP_PATH, socket.id, 'testArduino/testArduino.ino');
let [error1,] = await to(fsExtra.ensureDir(config.path.build));
error1 && Debug.error(error1);
let [error2,] = await to(fsExtra.outputFile(config.path.code, config.code));
error2 && Debug.error(error2);
const [error, result] = await to(shell.compile(config));
if (error) {
Debug.error(error);
callback([error, result]);
return;
}
if (result.code === 0) {
const buildPath = config.path.build;
result.files = await Boards.getFiles(config.key, buildPath);
}
callback([error, result]);
});
socket.on('arduino.kill', async (callback) => {
const shell = this.#shellArduino_.getItem(socket.id);
const [error, result] = await to(shell.kill());
error && Debug.error(error);
callback([error, result]);
});
}
#addEventsListenerForMode2_(socket) {
socket.on('disconnect', () => {
for (let key of this.#serialRegistry_.keys()) {
const serial = this.#serialRegistry_.getItem(key);
serial.dispose().catch(Debug.error);
this.#serialRegistry_.unregister(key);
}
const folderPath = path.resolve(TEMP_PATH, socket.id);
fsExtra.remove(folderPath).catch(Debug.error);
const shellMicropython = this.#shellMicroPython_.getItem(socket.id);
shellMicropython.dispose();
this.#shellMicroPython_.unregister(socket.id);
const shellArduino = this.#shellArduino_.getItem(socket.id);
shellArduino.dispose();
this.#shellArduino_.unregister(socket.id);
const shellAmpy = this.#shellAmpy_.getItem(socket.id);
shellAmpy.dispose();
this.#shellAmpy_.unregister(socket.id);
});
this.#addEventsListenerForMode2MicroPython_(socket);
this.#addEventsListenerForMode2Arduino_(socket);
this.#addEventsListenerForMode2Ampy_(socket);
this.#addEventsListenerForMode2Serial_(socket);
}
#addEventsListenerForMode2MicroPython_(socket) {
const shell = this.#shellMicroPython_.getItem(socket.id);
shell.bind('data', (data) => {
socket.emit('arduino.dataEvent', data);
});
shell.bind('error', (data) => {
socket.emit('arduino.errorEvent', data);
});
shell.bind('close', (code, time) => {
socket.emit('arduino.closeEvent', code, time);
});
socket.on('micropython.burn', async (config, callback) => {
const shell = this.#shellMicroPython_.getItem(socket.id);
const [error, result] = await to(shell.burn(config));
error && Debug.error(error);
callback([error, result]);
});
socket.on('micropython.upload', async (config, callback) => {
const shell = this.#shellMicroPython_.getItem(socket.id);
let { filePath = '', libraries = {} } = config;
filePath = MString.tpl(filePath, {
indexPath: path.resolve(CLIENT_PATH, config.boardDirPath)
});
const dirname = path.dirname(filePath);
await to(fsExtra.ensureDir(dirname));
await to(fsExtra.emptyDir(dirname));
await to(fsExtra.outputFile(filePath, config.code));
if (libraries && libraries instanceof Object) {
for (let key in libraries) {
await to(fsExtra.outputFile(path.resolve(dirname, key), libraries[key]));
}
}
const [error, result] = await to(shell.upload(config));
error && Debug.error(error);
callback([error, result]);
});
socket.on('micropython.kill', async (callback) => {
const shell = this.#shellMicroPython_.getItem(socket.id);
const [error, result] = await to(shell.kill());
error && Debug.error(error);
callback([error, result]);
});
}
#addEventsListenerForMode2Arduino_(socket) {
const shell = this.#shellArduino_.getItem(socket.id);
shell.bind('data', (data) => {
socket.emit('arduino.dataEvent', data);
});
shell.bind('error', (data) => {
socket.emit('arduino.errorEvent', data);
});
shell.bind('close', (code, time) => {
socket.emit('arduino.closeEvent', code, time);
});
socket.on('arduino.compile', async (config, callback) => {
console.log(config)
const shell = this.#shellArduino_.getItem(socket.id);
config.path = config?.path ?? {};
config.path.build = path.resolve(TEMP_PATH, socket.id, 'build');
config.path.code = path.resolve(TEMP_PATH, socket.id, 'testArduino/testArduino.ino');
let [error1,] = await to(fsExtra.ensureDir(config.path.build));
error1 && Debug.error(error1);
let [error2,] = await to(fsExtra.outputFile(config.path.code, config.code));
error2 && Debug.error(error2);
const [error, result] = await to(shell.compile(config));
error && Debug.error(error);
callback([error, result]);
});
socket.on('arduino.upload', async (config, callback) => {
config.path = config?.path ?? {};
const shell = this.#shellArduino_.getItem(socket.id);
config.path.build = path.resolve(TEMP_PATH, socket.id, 'build');
config.path.code = path.resolve(TEMP_PATH, socket.id, 'testArduino/testArduino.ino');
let [error1,] = await to(fsExtra.ensureDir(config.path.build));
error1 && Debug.error(error1);
let [error2,] = await to(fsExtra.outputFile(config.path.code, config.code));
error2 && Debug.error(error2);
const [error, result] = await to(shell.upload(config));
error && Debug.error(error);
callback([error, result]);
});
socket.on('arduino.kill', async (callback) => {
const shell = this.#shellArduino_.getItem(socket.id);
const [error, result] = await to(shell.kill());
error && Debug.error(error);
callback([error, result]);
});
}
#addEventsListenerForMode2Ampy_(socket) {
socket.on('ampy.ls', async (port, baud, folderPath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.ls(port, baud, folderPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.get', async (port, baud, filePath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.get(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.mkdir', async (port, baud, folderPath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.mkdir(port, baud, folderPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.mkfile', async (port, baud, filePath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.mkfile(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.isdir', async (port, baud, folderPath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.isdir(port, baud, folderPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.isfile', async (port, baud, filePath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.isfile(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.put', async (port, baud, filePath, data, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const startPath = path.join(TEMP_PATH, socket.id, 'ampy/temp').replaceAll('\\', '/');
const endPath = filePath;
await to(fsExtra.outputFile(startPath, data));
const [error, result] = await to(shell.put(port, baud, startPath, endPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.rm', async (port, baud, filePath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.rm(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.rmdir', async (port, baud, folderPath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.rmdir(port, baud, folderPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.rename', async (port, baud, oldPath, newPath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.rm(port, baud, oldPath, newPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.run', async (port, baud, filePath, callback) => {
const shell = this.#shellAmpy_.getItem(socket.id);
const [error, result] = await to(shell.rm(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
}
#addEventsListenerForMode2Serial_(socket) {
socket.on('serial.getPorts', async (callback) => {
const [error, result] = await to(Serial.getPorts());
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.create', (port) => {
const serial = new Serial(port);
this.#serialRegistry_.register(port, serial);
serial.bind('buffer', (buffer) => {
socket.emit('serial.bufferEvent', port, buffer);
});
serial.bind('string', (str) => {
socket.emit('serial.stringEvent', port, str);
});
serial.bind('error', (error) => {
socket.emit('serial.errorEvent', port, error);
});
serial.bind('open', () => {
socket.emit('serial.openEvent', port);
});
serial.bind('close', (code) => {
socket.emit('serial.closeEvent', port, code);
});
});
socket.on('serial.dispose', async (port, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.dispose());
error && Debug.error(error);
this.#serialRegistry_.unregister(port);
callback([error, result]);
});
socket.on('serial.open', async (port, baud, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.open(baud));
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.close', async (port, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.close());
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.setBaudRate', async (port, baud, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.setBaudRate(baud));
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.send', async (port, data, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.send(data));
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.setDTRAndRTS', async (port, dtr, rts, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.setDTRAndRTS(dtr, rts));
error && Debug.error(error);
callback([error, result]);
});
}
getIO() {
return this.#io_;
}
}

View File

@@ -2,10 +2,10 @@ import express from 'express';
import path from 'node:path';
import * as url from 'node:url';
import { readFileSync } from 'node:fs';
import { createServer } from 'https';
import Socket from './web-socket/socket';
import { createServer } from 'node:https';
import Socket from './common/socket';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const __dirname = path.dirname(url.fileURLToPath(new URL(import.meta.url)));
const app = express();
const httpsServer = createServer({
@@ -13,8 +13,9 @@ const httpsServer = createServer({
cert: readFileSync(path.resolve(__dirname, '../certs/server.crt'))
}, app);
new Socket(httpsServer, {
const socket = new Socket(httpsServer, {
path: '/mixly-socket/',
maxHttpBufferSize: 1e8,
cors: {
origin: '*',
methods: ['GET', 'POST'],
@@ -23,4 +24,21 @@ new Socket(httpsServer, {
}
});
const io = socket.getIO();
function close() {
io.close(() => {
console.log('Socket服务已关闭');
process.exit(0);
});
}
process.on('SIGINT', () => {
close();
});
process.on('SIGTERM', () => {
close();
});
httpsServer.listen(4000);

View File

@@ -2,27 +2,26 @@
"debug": true,
"arduino": {
"path": {
"folder": "D:/gitee/mixly/arduino-cli",
"cli": "D:/gitee/mixly/arduino-cli/arduino-cli.exe",
"folder": "D:/gitee/arduino-cli-win32/arduino-cli",
"cli": "D:/gitee/arduino-cli-win32/arduino-cli/arduino-cli.exe",
"libraries": [
"D:/gitee/mixly/arduino-cli/libraries"
"D:/gitee/arduino-cli-win32/arduino-cli/libraries"
],
"cache": "D:/gitee/mixly/arduino-cli/cache",
"config": "D:/gitee/mixly/arduino-cli/arduino-cli.json"
"cache": "D:/gitee/arduino-cli-win32/arduino-cli/cache",
"config": "D:/gitee/arduino-cli-win32/arduino-cli/arduino-cli.json"
}
},
"micropython": {
"path": {
"ampy": "D:/gitee/mixly3-resource/mixly3.0-win32-x64/resources/app/src/tools/python/ampy/cli.py",
"esptool": "D:/gitee/mixly3-resource/mixly3.0-win32-x64/resources/app/src/tools/python/esptool/__init__.py"
"ampy": "D:/gitee/mixly3.0-win32-x64/resources/app/src/tools/python/ampy_main.py",
"esptool": "D:/gitee/mixly3.0-win32-x64/resources/app/src/tools/python/esptool_main.py"
}
},
"python": {
"path": {
"cli": "D:/gitee/mixly3-resource/mixly3.0-win32-x64/mixpyBuild/win_python3/python3.exe"
"cli": "D:/gitee/mixly3.0-win32-x64/mixpyBuild/win_python3/python3.exe"
}
},
"webSocketTempPath": "D:/gitee/mixly3-resource/mixly3-server/temp/web-socket",
"webCompilerTempPath": "D:/gitee/mixly3-resource/mixly3-server/temp/web-compiler",
"clientPath": "D:/gitee/mixly3-resource/mixly3.0-win32-x64/resources/app/src"
"tempPath": "D:/gitee/mixly3-server/temp",
"clientPath": "D:/gitee/mixly3.0-win32-x64/resources/app/src"
}

View File

@@ -1,287 +0,0 @@
import { Server } from 'socket.io';
import to from 'await-to-js';
import { usb } from 'usb';
import path from 'node:path';
import fsExtra from 'fs-extra';
import Serial from '../common/serial';
import Debug from '../common/debug';
import Registry from '../common/registry';
import ShellArduino from '../common/shell-arduino';
import ShellMicroPython from '../common/shell-micropython';
import ShellAmpy from '../common/shell-ampy';
import MString from '../common/mstring';
import { WEB_SOCKT_TEMP_PATH, CLIENT_PATH } from '../common/config';
export default class Socket {
#io_ = null;
#serialRegistry_ = new Registry();
#shellMicroPython_ = new ShellMicroPython();
#shellArduino_ = new ShellArduino();
#shellAmpy_ = new ShellAmpy();
constructor(httpsServer, options) {
this.#io_ = new Server(httpsServer, options);
this.#io_.on('connection', (socket) => {
this.#addEventsListener_(socket);
});
usb.on('attach', (device) => {
this.#io_.emit('serial.attachEvent', device);
});
usb.on('detach', (device) => {
this.#io_.emit('serial.detachEvent', device);
});
}
#addEventsListener_(socket) {
socket.on('disconnect', () => {
for (let key of this.#serialRegistry_.keys()) {
const serial = this.#serialRegistry_.getItem(key);
serial.dispose().catch(Debug.error);
this.#serialRegistry_.unregister(key);
}
});
this.#addEventsListenerForMicroPython_(socket);
this.#addEventsListenerForArduino_(socket);
this.#addEventsListenerForAmpy_(socket);
this.#addEventsListenerForSerial_(socket);
}
#addEventsListenerForMicroPython_(socket) {
this.#shellMicroPython_.bind('data', (data) => {
socket.emit('arduino.dataEvent', data);
});
this.#shellMicroPython_.bind('error', (data) => {
socket.emit('arduino.errorEvent', data);
});
this.#shellMicroPython_.bind('close', (code, time) => {
socket.emit('arduino.closeEvent', code, time);
});
socket.on('micropython.burn', async (config, callback) => {
const [error, result] = await to(this.#shellMicroPython_.burn(config));
error && Debug.error(error);
callback([error, result]);
});
socket.on('micropython.upload', async (config, callback) => {
let { filePath = '', libraries = {} } = config;
filePath = MString.tpl(filePath, {
indexPath: path.resolve(CLIENT_PATH, config.boardDirPath)
});
const dirname = path.dirname(filePath);
await to(fsExtra.ensureDir(dirname));
await to(fsExtra.emptyDir(dirname));
await to(fsExtra.outputFile(filePath, config.code));
if (libraries && libraries instanceof Object) {
for (let key in libraries) {
await to(fsExtra.outputFile(path.resolve(dirname, key), libraries[key]));
}
}
const [error, result] = await to(this.#shellMicroPython_.upload(config));
error && Debug.error(error);
callback([error, result]);
});
socket.on('micropython.kill', async (callback) => {
const [error, result] = await to(this.#shellMicroPython_.kill());
error && Debug.error(error);
callback([error, result]);
});
}
#addEventsListenerForArduino_(socket) {
this.#shellArduino_.bind('data', (data) => {
socket.emit('arduino.dataEvent', data);
});
this.#shellArduino_.bind('error', (data) => {
socket.emit('arduino.errorEvent', data);
});
this.#shellArduino_.bind('close', (code, time) => {
socket.emit('arduino.closeEvent', code, time);
});
socket.on('arduino.compile', async (config, callback) => {
config.path = config?.path ?? {};
config.path.build = path.resolve(WEB_SOCKT_TEMP_PATH, 'build');
config.path.code = path.resolve(WEB_SOCKT_TEMP_PATH, 'testArduino/testArduino.ino');
let [error1,] = await to(fsExtra.ensureDir(config.path.build));
error1 && Debug.error(error1);
let [error2,] = await to(fsExtra.outputFile(config.path.code, config.code));
error2 && Debug.error(error2);
const [error, result] = await to(this.#shellArduino_.compile(config));
error && Debug.error(error);
callback([error, result]);
});
socket.on('arduino.upload', async (config, callback) => {
config.path = config?.path ?? {};
config.path.build = path.resolve(WEB_SOCKT_TEMP_PATH, 'build');
config.path.code = path.resolve(WEB_SOCKT_TEMP_PATH, 'testArduino/testArduino.ino');
let [error1,] = await to(fsExtra.ensureDir(config.path.build));
error1 && Debug.error(error1);
let [error2,] = await to(fsExtra.outputFile(config.path.code, config.code));
error2 && Debug.error(error2);
const [error, result] = await to(this.#shellArduino_.upload(config));
error && Debug.error(error);
callback([error, result]);
});
socket.on('arduino.kill', async (callback) => {
const [error, result] = await to(this.#shellArduino_.kill());
error && Debug.error(error);
callback([error, result]);
});
}
#addEventsListenerForAmpy_(socket) {
socket.on('ampy.ls', async (port, baud, folderPath, callback) => {
const [error, result] = await to(this.#shellAmpy_.ls(port, baud, folderPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.get', async (port, baud, filePath, callback) => {
const [error, result] = await to(this.#shellAmpy_.get(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.mkdir', async (port, baud, folderPath, callback) => {
const [error, result] = await to(this.#shellAmpy_.mkdir(port, baud, folderPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.mkfile', async (port, baud, filePath, callback) => {
const [error, result] = await to(this.#shellAmpy_.mkfile(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.isdir', async (port, baud, folderPath, callback) => {
const [error, result] = await to(this.#shellAmpy_.isdir(port, baud, folderPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.isfile', async (port, baud, filePath, callback) => {
const [error, result] = await to(this.#shellAmpy_.isfile(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.put', async (port, baud, filePath, data, callback) => {
const startPath = path.join(WEB_SOCKT_TEMP_PATH, 'ampy/temp').replaceAll('\\', '/');
const endPath = filePath;
await to(fsExtra.outputFile(startPath, data));
const [error, result] = await to(this.#shellAmpy_.put(port, baud, startPath, endPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.rm', async (port, baud, filePath, callback) => {
const [error, result] = await to(this.#shellAmpy_.rm(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.rmdir', async (port, baud, folderPath, callback) => {
const [error, result] = await to(this.#shellAmpy_.rmdir(port, baud, folderPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.rename', async (port, baud, oldPath, newPath, callback) => {
const [error, result] = await to(this.#shellAmpy_.rm(port, baud, oldPath, newPath));
error && Debug.error(error);
callback([error, result]);
});
socket.on('ampy.run', async (port, baud, filePath, callback) => {
const [error, result] = await to(this.#shellAmpy_.rm(port, baud, filePath));
error && Debug.error(error);
callback([error, result]);
});
}
#addEventsListenerForSerial_(socket) {
socket.on('serial.getPorts', async (callback) => {
const [error, result] = await to(Serial.getPorts());
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.create', (port) => {
const serial = new Serial(port);
this.#serialRegistry_.register(port, serial);
serial.bind('buffer', (buffer) => {
socket.emit('serial.bufferEvent', port, buffer);
});
serial.bind('string', (str) => {
socket.emit('serial.stringEvent', port, str);
});
serial.bind('error', (error) => {
socket.emit('serial.errorEvent', port, error);
});
serial.bind('open', () => {
socket.emit('serial.openEvent', port);
});
serial.bind('close', (code) => {
socket.emit('serial.closeEvent', port, code);
});
});
socket.on('serial.dispose', async (port, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.dispose());
error && Debug.error(error);
this.#serialRegistry_.unregister(port);
callback([error, result]);
});
socket.on('serial.open', async (port, baud, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.open(baud));
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.close', async (port, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.close());
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.setBaudRate', async (port, baud, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.setBaudRate(baud));
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.send', async (port, data, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.send(data));
error && Debug.error(error);
callback([error, result]);
});
socket.on('serial.setDTRAndRTS', async (port, dtr, rts, callback) => {
const serial = this.#serialRegistry_.getItem(port);
const [error, result] = await to(serial.setDTRAndRTS(dtr, rts));
error && Debug.error(error);
callback([error, result]);
});
}
getIO() {
return this.#io_;
}
}