feat: sync mixly root files and common folder

This commit is contained in:
yczpf2019
2026-01-24 16:12:04 +08:00
parent 93e17c00ae
commit c8c5fcf726
2920 changed files with 186461 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
goog.loadJs('web', () => {
goog.require('path');
goog.require('Mixly.Env');
goog.require('Mixly.FS');
goog.require('Mixly.Debug');
goog.require('Mixly.MJson');
goog.require('Mixly.WebSocket.Ampy');
goog.provide('Mixly.WebSocket.AmpyFS');
const {
Env,
FS,
Debug,
MJson,
WebSocket
} = Mixly;
const { Ampy } = WebSocket;
class AmpyFS extends FS {
#ampy_ = null;
#port_ = '';
#baud_ = 115200;
#decoder_ = new TextDecoder('utf8');
constructor() {
super();
this.#ampy_ = new Ampy();
}
async rename(oldPath, newPath) {
let stdout = '', error = null;
try {
stdout = await this.#ampy_.rename(this.#port_, this.#baud_, oldPath, newPath);
} catch (e) {
error = e;
Debug.error(error);
}
return [error, stdout];
}
async createFile(filePath) {
let stdout = '', error = null;
try {
stdout = await this.#ampy_.mkfile(this.#port_, this.#baud_, filePath);
} catch (e) {
error = e;
Debug.error(error);
}
return [error, stdout];
}
async readFile(filePath) {
let stdout = '', error = null;
try {
stdout = await this.#ampy_.get(this.#port_, this.#baud_, filePath);
stdout = this.#decoder_.decode(this.#ampy_.unhexlify(stdout));
} catch (e) {
error = e;
Debug.error(error);
}
return [error, stdout];
}
async writeFile(filePath, data) {
let stdout = '', error = null;
try {
stdout = await this.#ampy_.put(this.#port_, this.#baud_, filePath, data);
} catch (e) {
error = e;
Debug.error(error);
}
return [error, stdout];
}
async isFile(filePath) {
/*const [error, stdout] = await this.readDirectory(filePath);
if (error) {
return true;
}
return false;*/
let error = null;
if (path.extname(filePath)) {
return [error, true];
} else {
return [error, false];
}
}
async renameFile(oldFilePath, newFilePath) {
return this.rename(oldFilePath, newFilePath);
}
// async moveFile(oldFilePath, newFilePath) {}
// async copyFile(oldFilePath, newFilePath) {}
async deleteFile(filePath) {
let stdout = '', error = null;
try {
stdout = await this.#ampy_.rm(this.#port_, this.#baud_, filePath);
} catch (e) {
error = e;
Debug.error(error);
}
return [error, stdout];
}
async createDirectory(folderPath) {
let stdout = '', error = null;
try {
stdout = await this.#ampy_.mkdir(this.#port_, this.#baud_, folderPath);
} catch (e) {
error = e;
Debug.error(error);
}
return [error, stdout];
}
async readDirectory(folderPath) {
let stdout = [], error = null;
try {
const output = await this.#ampy_.ls(this.#port_, this.#baud_, folderPath);
const dirs = Array.from(new Set(output.split('\r\n')));
for (let i in dirs) {
if (!dirs[i]) {
continue;
}
stdout.push(MJson.parse(dirs[i].replaceAll('\'', '"')));
}
} catch (e) {
error = e;
Debug.error(error);
}
return [error, stdout];
}
async isDirectory(folderPath) {
/*const [error, stdout] = await this.readDirectory(folderPath);
if (error) {
return false;
}
return true;*/
let error = null;
if (path.extname(folderPath)) {
return [error, false];
} else {
return [error, true];
}
}
async isDirectoryEmpty(folderPath) {
/*const [error, stdout] = await this.readDirectory(folderPath);
let isEmpty = false;
if (error || !stdout.length) {
isEmpty = true;
}*/
return [null, false];
}
async renameDirectory(oldFolderPath, newFolderPath) {
return this.rename(oldFolderPath, newFolderPath);
}
// async moveDirectory(oldFolderPath, newFolderPath) {}
// async copyDirectory(oldFolderPath, newFolderPath) {}
async deleteDirectory(folderPath) {
let stdout = '', error = null;
try {
stdout = await this.#ampy_.rmdir(this.#port_, this.#baud_, folderPath);
} catch (e) {
error = e;
Debug.error(error);
}
return [error, stdout];
}
setPortName(port) {
this.#port_ = port;
}
getPortName() {
return this.#port_;
}
setBaudRate(baud) {
this.#baud_ = baud;
}
getBaudRate() {
return this.#baud_;
}
}
WebSocket.AmpyFS = AmpyFS;
});

View File

@@ -0,0 +1,108 @@
goog.loadJs('web', () => {
goog.require('path');
goog.require('Mustache');
goog.require('Mixly.Ampy');
goog.require('Mixly.Env');
goog.require('Mixly.Serial');
goog.require('Mixly.WebSocket');
goog.provide('Mixly.WebSocket.Ampy');
const {
Ampy,
Env,
Serial,
WebSocket
} = Mixly;
class AmpyExt extends Ampy {
static {
this.mixlySocket = null;
this.socket = null;
this.getSocket = function () {
return this.socket;
}
this.getMixlySocket = function () {
return this.mixlySocket;
}
this.init = function (mixlySocket) {
this.mixlySocket = mixlySocket;
this.socket = mixlySocket.getSocket();
}
}
constructor() {
super();
}
async ls(port, baud, folderPath) {
return this.exec('ampy.ls', port, baud, folderPath);
}
async get(port, baud, filePath) {
return this.exec('ampy.get', port, baud, filePath);
}
async mkdir(port, baud, folderPath) {
return this.exec('ampy.mkdir', port, baud, folderPath);
}
async mkfile(port, baud, filePath) {
return this.exec('ampy.mkfile', port, baud, filePath);
}
async isdir(port, baud, folderPath) {
return this.exec('ampy.isdir', port, baud, folderPath);
}
async isfile(port, baud, filePath) {
return this.exec('ampy.isfile', port, baud, filePath);
}
async put(port, baud, filePath, data) {
return this.exec('ampy.put', port, baud, filePath, data);
}
async rm(port, baud, filePath) {
return this.exec('ampy.rm', port, baud, filePath);
}
async rmdir(port, baud, folderPath) {
return this.exec('ampy.rmdir', port, baud, folderPath);
}
async rename(port, baud, oldPath, newPath) {
return this.exec('ampy.rename', port, baud, oldPath, newPath);
}
async run(port, baud, filePath) {
return this.exec('ampy.run', port, baud, filePath);
}
async exec(eventType, port, ...args) {
const portsName = Serial.getCurrentPortsName();
if (!portsName.includes(port)) {
throw new Error('无可用串口');
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
if (statusBarSerial) {
await statusBarSerial.close();
}
const mixlySocket = AmpyExt.getMixlySocket();
const output = await mixlySocket.emitAsync(eventType, port, ...args);
if (output[0]) {
throw new Error(output[0]);
}
return output[1];
}
}
WebSocket.Ampy = AmpyExt;
});

View File

@@ -0,0 +1,275 @@
goog.loadJs('web', () => {
goog.require('layui');
goog.require('dayjs.duration');
goog.require('Mixly.Boards');
goog.require('Mixly.Debug');
goog.require('Mixly.LayerExt');
goog.require('Mixly.Msg');
goog.require('Mixly.Workspace');
goog.require('Mixly.LayerProgress');
goog.require('Mixly.WebSocket.Serial');
goog.provide('Mixly.WebSocket.ArduShell');
const {
Boards,
Debug,
LayerExt,
Msg,
Workspace,
LayerProgress,
WebSocket
} = Mixly;
const { Serial } = WebSocket;
const { layer } = layui;
class WebSocketArduShell {
static {
this.mixlySocket = null;
this.socket = null;
this.shell = null;
this.getSocket = function () {
return this.socket;
}
this.getMixlySocket = function () {
return this.mixlySocket;
}
this.init = function (mixlySocket) {
this.mixlySocket = mixlySocket;
this.socket = mixlySocket.getSocket();
this.shell = new WebSocketArduShell();
const socket = this.socket;
socket.on('arduino.dataEvent', (data) => {
if (data.length > 1000) {
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data);
});
socket.on('arduino.errorEvent', (data) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
try {
data = unescape(data.replace(/(_E[0-9A-F]{1}_[0-9A-F]{2}_[0-9A-F]{2})+/gm, '%$1'));
data = unescape(data.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1'));
} catch (error) {
Debug.error(error);
}
statusBarTerminal.addValue(data);
});
}
this.initCompile = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 });
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
mainStatusBarTabs.show();
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode();
statusBarTerminal.setValue(`${Msg.Lang['shell.compiling']}...\n`);
this.shell.compile(code)
.then((info) => {
this.endCallback(info.code, info.time);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.compileFailed']}==\n`);
});
}
this.initUpload = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 });
return;
}
const port = Serial.getSelectedPortName();
if (!port) {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
mainStatusBarTabs.show();
statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`);
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode();
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve();
closePromise
.then(() => {
return this.shell.upload(port, code)
})
.then((info) => {
this.endCallback(info.code, info.time);
if (info.code || !Serial.portIsLegal(port)) {
return;
}
mainStatusBarTabs.add('serial', port);
mainStatusBarTabs.changeTo(port);
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
statusBarSerial.open()
.then(() => {
const baudRates = code.match(/(?<=Serial.begin[\s]*\([\s]*)[0-9]*(?=[\s]*\))/g);
if (!baudRates?.length) {
return statusBarSerial.setBaudRate(9600);
} else {
return statusBarSerial.setBaudRate(baudRates[0] - 0);
}
})
.catch(Debug.error);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadFailed']}==\n`);
});
}
this.endCallback = function (code, time) {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
let message = '';
if (code) {
message = (this.shell.isCompiling() ? Msg.Lang['shell.compileFailed'] : Msg.Lang['shell.uploadFailed']);
statusBarTerminal.addValue(`\n==${message}==\n`);
} else {
message = (this.shell.isCompiling() ? Msg.Lang['shell.compileSucc'] : Msg.Lang['shell.uploadSucc']);
statusBarTerminal.addValue(`\n==${message}(${Msg.Lang['shell.timeCost']} ${
dayjs.duration(time).format('HH:mm:ss.SSS')
})==\n`);
}
layer.msg(message, { time: 1000 });
}
}
#running_ = false;
#upload_ = false;
#killing_ = false;
#layer_ = null;
constructor() {
this.#layer_ = new LayerProgress({
width: 200,
cancelValue: Msg.Lang['nav.btn.stop'],
skin: 'layui-anim layui-anim-scale',
cancel: () => {
if (this.#killing_) {
return false;
}
this.#layer_.title(`${Msg.Lang['shell.aborting']}...`);
this.#killing_ = true;
this.kill().catch(Debug.error);
return false;
},
cancelDisplay: false
});
}
async compile(code) {
return new Promise(async (resolve, reject) => {
this.#running_ = true;
this.#upload_ = false;
this.#killing_ = false;
this.showProgress();
const key = Boards.getSelectedBoardCommandParam();
const config = { key, code };
const mixlySocket = WebSocketArduShell.getMixlySocket();
mixlySocket.emit('arduino.compile', config, (response) => {
this.hideProgress();
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
async upload(port, code) {
return new Promise(async (resolve, reject) => {
this.#running_ = true;
this.#upload_ = true;
this.#killing_ = false;
this.showProgress();
const key = Boards.getSelectedBoardCommandParam();
const config = { key, code, port };
const mixlySocket = WebSocketArduShell.getMixlySocket();
mixlySocket.emit('arduino.upload', config, (response) => {
this.hideProgress();
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
async kill() {
return new Promise(async (resolve, reject) => {
const mixlySocket = WebSocketArduShell.getMixlySocket();
mixlySocket.emit('arduino.kill', (response) => {
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
showProgress() {
const message = this.isCompiling() ? Msg.Lang['shell.compiling'] : Msg.Lang['shell.uploading'];
this.#layer_.title(`${message}...`);
this.#layer_.show();
}
hideProgress() {
this.#layer_.hide();
}
isUploading() {
return this.#running_ && this.#upload_;
}
isCompiling() {
return this.#running_ && !this.#upload_;
}
}
WebSocket.ArduShell = WebSocketArduShell;
});

View File

@@ -0,0 +1,350 @@
goog.loadJs('web', () => {
goog.require('path');
goog.require('layui');
goog.require('dayjs.duration');
goog.require('Mixly.Debug');
goog.require('Mixly.LayerExt');
goog.require('Mixly.Msg');
goog.require('Mixly.Env');
goog.require('Mixly.Config');
goog.require('Mixly.Workspace');
goog.require('Mixly.MString');
goog.require('Mixly.LayerProgress');
goog.require('Mixly.WebSocket.Serial');
goog.provide('Mixly.WebSocket.BU');
const {
Debug,
LayerExt,
Config,
Msg,
Env,
Workspace,
MString,
LayerProgress,
WebSocket
} = Mixly;
const { SELECTED_BOARD } = Config;
const { Serial } = WebSocket;
const { layer } = layui;
class WebSocketBU {
static {
this.mixlySocket = null;
this.socket = null;
this.shell = null;
this.getSocket = function () {
return this.socket;
}
this.getMixlySocket = function () {
return this.mixlySocket;
}
this.init = function (mixlySocket) {
this.mixlySocket = mixlySocket;
this.socket = mixlySocket.getSocket();
this.shell = new WebSocketBU();
const socket = this.socket;
socket.on('micropython.dataEvent', (data) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data);
});
socket.on('micropython.errorEvent', (data) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data);
});
}
this.initBurn = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 });
return;
}
const port = Serial.getSelectedPortName();
if (!port) {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
mainStatusBarTabs.show();
statusBarTerminal.setValue(`${Msg.Lang['shell.burning']}...\n`);
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve();
closePromise
.then(() => {
return this.shell.burn(port);
})
.then((info) => {
this.endCallback(info.code, info.time);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.burnFailed']}==\n`);
});
}
this.initUpload = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 });
return;
}
const port = Serial.getSelectedPortName();
if (!port) {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
mainStatusBarTabs.show();
statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`);
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode();
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve();
closePromise
.then(() => {
return this.shell.upload(port, code)
})
.then((info) => {
this.endCallback(info.code, info.time);
if (info.code || !Serial.portIsLegal(port)) {
return;
}
mainStatusBarTabs.add('serial', port);
mainStatusBarTabs.changeTo(port);
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
statusBarSerial.open()
.then(() => {
return statusBarSerial.setBaudRate(115200);
})
.catch(Debug.error);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadFailed']}==\n`);
});
}
this.endCallback = function (code, time) {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
let message = '';
if (code) {
message = (this.shell.isBurning() ? Msg.Lang['shell.burnFailed'] : Msg.Lang['shell.uploadFailed']);
statusBarTerminal.addValue(`\n==${message}==\n`);
} else {
message = (this.shell.isBurning() ? Msg.Lang['shell.burnSucc'] : Msg.Lang['shell.uploadSucc']);
statusBarTerminal.addValue(`\n==${message}(${Msg.Lang['shell.timeCost']} ${
dayjs.duration(time).format('HH:mm:ss.SSS')
})==\n`);
}
layer.msg(message, { time: 1000 });
}
}
#running_ = false;
#upload_ = false;
#killing_ = false;
#layer_ = null;
constructor() {
this.#layer_ = new LayerProgress({
width: 200,
cancelValue: Msg.Lang['nav.btn.stop'],
skin: 'layui-anim layui-anim-scale',
cancel: () => {
if (this.#killing_) {
return false;
}
this.#layer_.title(`${Msg.Lang['shell.aborting']}...`);
this.#killing_ = true;
this.kill().catch(Debug.error);
return false;
},
cancelDisplay: false
});
}
async burn(port) {
return new Promise(async (resolve, reject) => {
this.#running_ = true;
this.#upload_ = false;
this.#killing_ = false;
this.showProgress();
const config = {
boardDirPath: `.${Env.boardDirPath}`,
port,
command: SELECTED_BOARD.burn.command
};
const mixlySocket = WebSocketBU.getMixlySocket();
mixlySocket.emit('micropython.burn', config, (response) => {
this.hideProgress();
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
async upload(port, code) {
return new Promise(async (resolve, reject) => {
this.#running_ = true;
this.#upload_ = true;
this.#killing_ = false;
this.showProgress();
const importsMap = this.getImportModules(code);
let libraries = {};
for (let key in importsMap) {
const filename = importsMap[key]['__name__'];
const data = goog.readFileSync(importsMap[key]['__path__']);
libraries[filename] = data;
}
const config = {
boardDirPath: `.${Env.boardDirPath}`,
command: SELECTED_BOARD.upload.command,
filePath: SELECTED_BOARD.upload.filePath,
port, code, libraries
};
const mixlySocket = WebSocketBU.getMixlySocket();
mixlySocket.emit('micropython.upload', config, (response) => {
this.hideProgress();
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
getImportModulesName(code) {
// 正则表达式: 匹配 import 或 from 导入语句
const importRegex = /(?:import\s+([a-zA-Z0-9_]+)|from\s+([a-zA-Z0-9_]+)\s+import)/g;
let imports = [];
let match;
while ((match = importRegex.exec(code)) !== null) {
if (match[1]) {
imports.push(match[1]); // 'import module'
}
if (match[2]) {
imports.push(match[2]); // 'from module import ...'
}
}
return imports;
}
getImportModules(code) {
let importsMap = {};
const libPath = SELECTED_BOARD.upload.libPath;
for (let i = libPath.length - 1; i >= 0; i--) {
const dirname = MString.tpl(libPath[i], { indexPath: Env.boardDirPath });
const map = goog.readJsonSync(path.join(dirname, 'map.json'));
if (!(map && map instanceof Object)) {
continue;
}
for (let key in map) {
importsMap[key] = structuredClone(map[key]);
importsMap[key]['__path__'] = path.join(dirname, map[key]['__name__']);
}
}
let usedMap = {};
let currentImports = this.getImportModulesName(code);
while (currentImports.length) {
let temp = [];
for (let moduleName of currentImports) {
let moduleInfo = importsMap[moduleName];
if (!moduleInfo) {
continue;
}
usedMap[moduleName] = moduleInfo;
const moduleImports = moduleInfo['__require__'];
if (!moduleImports) {
continue;
}
for (let name of moduleImports) {
if (usedMap[name] || !importsMap[name] || temp.includes(name)) {
continue;
}
temp.push(name);
}
}
currentImports = temp;
}
return usedMap;
}
async kill() {
return new Promise(async (resolve, reject) => {
const mixlySocket = WebSocketBU.getMixlySocket();
mixlySocket.emit('micropython.kill', (response) => {
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
showProgress() {
const message = this.isBurning() ? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading'];
this.#layer_.title(`${message}...`);
this.#layer_.show();
}
hideProgress() {
this.#layer_.hide();
}
isUploading() {
return this.#running_ && this.#upload_;
}
isBurning() {
return this.#running_ && !this.#upload_;
}
}
WebSocket.BU = WebSocketBU;
});

View File

@@ -0,0 +1,11 @@
goog.loadJs('web', () => {
goog.require('Mixly.Web.File');
goog.provide('Mixly.WebSocket.File');
const { Web, WebSocket } = Mixly;
WebSocket.File = Web.File;
});

View File

@@ -0,0 +1,101 @@
goog.loadJs('web', () => {
goog.require('Mixly.Debug');
goog.require('Mixly.Config');
goog.require('Mixly.StatusBarsManager');
goog.require('Mixly.Socket');
goog.require('Mixly.WebSocket.Serial');
goog.require('Mixly.WebSocket.ArduShell');
goog.require('Mixly.WebSocket.BU');
goog.require('Mixly.WebSocket.Ampy');
goog.provide('Mixly.WebSocket.Loader');
const {
Debug,
Config,
StatusBarsManager,
Socket,
WebSocket
} = Mixly;
const {
Loader,
Serial,
ArduShell,
BU,
Ampy
} = WebSocket;
const { SOFTWARE } = Config;
Loader.init = function () {
let url = '';
if (SOFTWARE.webSocket?.url) {
const info = new window.URL(SOFTWARE.webSocket.url);
if (window.location.protocol === 'http:') {
info.protocol = 'ws:';
}
if (info.hostname === 'default') {
info.hostname = window.location.hostname;
}
url = info.origin;
} else {
url = `wss://${window.location.host}`;
}
const mixlySocket = new Socket(`${url}/all`, {
path: '/mixly-socket/',
reconnection: true,
reconnectionDelayMax: 10000,
transports: ['websocket'],
protocols: ['my-protocol-v1']
});
const socket = mixlySocket.getSocket();
socket.on('connect', () => {
Serial.getPorts()
.then((ports) => {
let portsName = [];
for (let port of ports) {
portsName.push(port.name);
}
const { mainStatusBarTabs } = Mixly;
let keys = mainStatusBarTabs.keys();
const statusBarType = StatusBarsManager.typesRegistry.getItem('serial');
for (let key of keys) {
const statusBar = mainStatusBarTabs.getStatusBarById(key);
if (!(statusBar instanceof statusBarType)) {
continue;
}
const portName = statusBar.getPortName();
if (!portsName.includes(portName)) {
continue;
}
socket.emit('serial.create', portName);
}
Serial.renderSelectBox(ports);
})
.catch(Debug.error);
});
socket.on('disconnect', () => {
const { mainStatusBarTabs } = Mixly;
let keys = mainStatusBarTabs.keys();
const statusBarType = StatusBarsManager.typesRegistry.getItem('serial');
for (let key of keys) {
const statusBar = mainStatusBarTabs.getStatusBarById(key);
if (statusBar instanceof statusBarType) {
statusBar.close().catch(Debug.error);
}
}
Serial.refreshPorts();
});
Serial.init(mixlySocket);
ArduShell.init(mixlySocket);
BU.init(mixlySocket);
Ampy.init(mixlySocket);
}
});

View File

@@ -0,0 +1,360 @@
goog.loadJs('web', () => {
goog.require('Mixly.Serial');
goog.require('Mixly.Env');
goog.require('Mixly.Msg');
goog.require('Mixly.Debug');
goog.require('Mixly.Registry');
goog.require('Mixly.WebSocket');
goog.provide('Mixly.WebSocket.Serial');
const {
Serial,
Env,
Msg,
Debug,
Registry,
WebSocket
} = Mixly;
class WebSocketSerial extends Serial {
static {
this.eventRegistry = new Registry();
this.mixlySocket = null;
this.socket = null;
this.getConfig = function () {
return Serial.getConfig();
}
this.getSelectedPortName = function () {
return Serial.getSelectedPortName();
}
this.getCurrentPortsName = function () {
return Serial.getCurrentPortsName();
}
this.renderSelectBox = function (ports) {
return Serial.renderSelectBox(ports);
}
this.getPorts = async function () {
return new Promise((resolve, reject) => {
if (this.socket.connected) {
this.socket.emit('serial.getPorts', (response) => {
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
} else {
resolve([]);
}
});
}
this.refreshPorts = function () {
this.getPorts()
.then((ports) => {
Serial.renderSelectBox(ports);
})
.catch(Debug.error);
}
this.init = function (mixlySocket) {
this.mixlySocket = mixlySocket;
this.socket = mixlySocket.getSocket();
const socket = this.socket;
socket.on('serial.attachEvent', () => {
this.refreshPorts();
});
socket.on('serial.detachEvent', () => {
this.refreshPorts();
});
socket.on('serial.bufferEvent', (port, buffer) => {
const eventName = `${port}-buffer`;
if (!this.eventRegistry.hasKey(eventName)) {
return;
}
const event = this.eventRegistry.getItem(eventName);
event(buffer);
});
socket.on('serial.stringEvent', (port, str) => {
const eventName = `${port}-string`;
if (!this.eventRegistry.hasKey(eventName)) {
return;
}
const event = this.eventRegistry.getItem(eventName);
event(str);
});
socket.on('serial.errorEvent', (port, error) => {
const eventName = `${port}-error`;
if (!this.eventRegistry.hasKey(eventName)) {
return;
}
const event = this.eventRegistry.getItem(eventName);
event(error);
});
socket.on('serial.openEvent', (port) => {
const eventName = `${port}-open`;
if (!this.eventRegistry.hasKey(eventName)) {
return;
}
const event = this.eventRegistry.getItem(eventName);
event();
});
socket.on('serial.closeEvent', (port, code) => {
const eventName = `${port}-close`;
if (!this.eventRegistry.hasKey(eventName)) {
return;
}
const event = this.eventRegistry.getItem(eventName);
event(code);
});
}
this.getSocket = function () {
return this.socket;
}
this.getMixlySocket = function () {
return this.mixlySocket;
}
this.getEventRegistry = function () {
return this.eventRegistry;
}
}
constructor(port) {
super(port);
this.#addEventsListener_();
const socket = WebSocketSerial.getSocket();
if (socket.connected) {
socket.emit('serial.create', port);
}
}
#addEventsListener_() {
const port = this.getPortName();
const eventRegistry = WebSocketSerial.getEventRegistry();
eventRegistry.register(`${port}-buffer`, (buffer) => {
this.onBuffer(buffer);
});
eventRegistry.register(`${port}-string`, (str) => {
this.onString(str);
});
eventRegistry.register(`${port}-error`, (error) => {
this.onError(error);
this.onClose(1);
});
eventRegistry.register(`${port}-open`, () => {
this.onOpen();
});
eventRegistry.register(`${port}-close`, (code) => {
this.onClose(code);
});
}
async open(baud) {
return new Promise((resolve, reject) => {
const portsName = Serial.getCurrentPortsName();
const currentPort = this.getPortName();
if (!portsName.includes(currentPort)) {
reject('无可用串口');
return;
}
if (this.isOpened()) {
resolve();
return;
}
baud = baud ?? this.getBaudRate();
const mixlySocket = WebSocketSerial.getMixlySocket();
mixlySocket.emit('serial.open', currentPort, baud, (response) => {
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
this.onError(error);
reject(error);
} else {
super.open(baud);
this.setBaudRate(baud);
resolve(result);
}
});
});
}
async close() {
return new Promise((resolve, reject) => {
if (!this.isOpened()) {
resolve();
return;
}
super.close();
const mixlySocket = WebSocketSerial.getMixlySocket();
mixlySocket.emit('serial.close', this.getPortName(), (response) => {
if (response.error) {
this.onClose(1);
resolve(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
async setBaudRate(baud) {
return new Promise((resolve, reject) => {
if (!this.isOpened()
|| this.getRawBaudRate() === baud
|| !this.baudRateIsLegal(baud)) {
resolve();
return;
}
const mixlySocket = WebSocketSerial.getMixlySocket();
mixlySocket.emit('serial.setBaudRate', this.getPortName(), baud, (response) => {
if (response.error) {
reject(response.error);
return;
}
const [error,] = response;
if (error) {
reject(error);
} else {
super.setBaudRate(baud);
this.setDTRAndRTS(this.getDTR(), this.getRTS()).finally(resolve);
}
});
});
}
async send(data) {
return new Promise((resolve, reject) => {
if (!this.isOpened()) {
resolve();
return;
}
const mixlySocket = WebSocketSerial.getMixlySocket();
mixlySocket.emit('serial.send', this.getPortName(), data, (response) => {
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
async sendString(str) {
return this.send(str);
}
async sendBuffer(buffer) {
return this.send(buffer);
}
async setDTRAndRTS(dtr, rts) {
return new Promise((resolve, reject) => {
if (!this.isOpened()) {
resolve();
return;
}
const mixlySocket = WebSocketSerial.getMixlySocket();
mixlySocket.emit('serial.setDTRAndRTS', this.getPortName(), dtr, rts, (response) => {
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
super.setDTRAndRTS(dtr, rts);
resolve(result);
}
});
});
}
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);
}
}
async #awaitDispose_() {
return new Promise((resolve, reject) => {
const mixlySocket = WebSocketSerial.getMixlySocket();
mixlySocket.emit('serial.dispose', this.getPortName(), (response) => {
if (response.error) {
resolve();
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
async dispose() {
const port = this.getPortName();
const eventRegistry = WebSocketSerial.getEventRegistry();
eventRegistry.unregister(`${port}-buffer`);
eventRegistry.unregister(`${port}-string`);
eventRegistry.unregister(`${port}-error`);
eventRegistry.unregister(`${port}-open`);
eventRegistry.unregister(`${port}-close`);
await super.dispose();
await this.#awaitDispose_();
}
}
WebSocket.Serial = WebSocketSerial;
});

View File

@@ -0,0 +1,6 @@
goog.loadJs('web', () => {
goog.require('Mixly');
goog.provide('Mixly.WebSocket');
});