烧录软件(单个烧录)

qubiaobiao 2023-10-31 17:24:10 +08:00
commit 7c30acc526
30 changed files with 5256 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.vscode
.idea

26
README.md Normal file
View File

@ -0,0 +1,26 @@
# Vite Electron Typescript Template
`vite 2` `vue 3` `electron 12`
## How to use
clone the repo via git and install dependencies:
```shell
git clone --depth 1 --single-branch https://github.com/hocili/vite-electron-typescript-template.git your-project-name
cd your-project-name
yarn
```
## Starting Development
Start the app in the `dev` environment:
```shell
yarn dev
```
```shell
yarn electron:dev
```
## Packaging for Production
To package apps for the local platform:
```shell
yarn electron:build
```

View File

@ -0,0 +1,57 @@
const fs = require('fs')
const archiver = require('archiver')
const { Platform } = require("electron-builder")
const { name } = require('./package.json')
exports.default = function (buildResult) {
// you can return additional files to publish
// return ["/path/to/additional/result/file"]
const zipFilePath = buildResult.outDir + '/' + name + '.zip'
const sourceDirPath = buildResult.outDir + '/' + Platform.current().buildConfigurationKey + '-unpacked/'
// create a file to stream archive data to.
const output = fs.createWriteStream(zipFilePath)
const archive = archiver('zip', {
zlib: { level: 9 } // Sets the compression level.
})
// listen for all archive data to be written
// 'close' event is fired only when a file descriptor is involved
output.on('close', function() {
console.log(archive.pointer() + ' total bytes');
console.log('archiver has been finalized and the output file descriptor has closed.');
});
// This event is fired when the data source is drained no matter what was the data source.
// It is not part of this library but rather from the NodeJS Stream API.
// @see: https://nodejs.org/api/stream.html#stream_event_end
output.on('end', function() {
console.log('Data has been drained');
});
// good practice to catch warnings (ie stat failures and other non-blocking errors)
archive.on('warning', function(err) {
if (err.code === 'ENOENT') {
// log warning
} else {
// throw error
throw err;
}
});
// good practice to catch this error explicitly
archive.on('error', function(err) {
throw err;
});
// pipe archive data to the file
archive.pipe(output);
// append files from a sub-directory, putting its contents at the root of archive
archive.directory(sourceDirPath, false);
// finalize the archive (ie we are done appending files but streams have to finish yet)
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
archive.finalize();
}

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>esp32程序烧录</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/render/main.ts"></script>
</body>
</html>

4
index.js Normal file
View File

@ -0,0 +1,4 @@
require('ts-node').register({
project:'./tsconfig.electron.json'
});
require('./src/main/app.ts')

210
npminstall-debug.log Normal file
View File

@ -0,0 +1,210 @@
{
root: 'D:\\VS Code\\hwasmart-yanfaleixiangmu-esp32-program-burner-',
registry: 'https://registry.npmmirror.com',
pkgs: [
{
name: 'archiver',
version: '5.3.1',
type: 'version',
alias: undefined,
arg: [Result]
},
{
name: 'dev',
version: 'latest',
type: 'tag',
alias: undefined,
arg: [Result]
}
],
production: false,
cacheStrict: false,
cacheDir: 'C:\\Users\\OEM\\.npminstall_tarball',
env: {
npm_config_registry: 'https://registry.npmmirror.com',
npm_config_argv: '{"remain":[],"cooked":["--fix-bug-versions","--china","--userconfig=C:\\\\Users\\\\OEM\\\\.cnpmrc","--disturl=https://cdn.npmmirror.com/binaries/node","--registry=https://registry.npmmirror.com","archiver@5.3.1","dev"],"original":["--fix-bug-versions","--china","--userconfig=C:\\\\Users\\\\OEM\\\\.cnpmrc","--disturl=https://cdn.npmmirror.com/binaries/node","--registry=https://registry.npmmirror.com","archiver@5.3.1","dev"]}',
npm_config_user_agent: 'npminstall/7.9.0 npm/? node/v16.18.1 win32 x64',
npm_config_cache: 'C:\\Users\\OEM\\.npminstall_tarball',
NODE: 'C:\\Program Files\\nodejs\\node.exe',
npm_node_execpath: 'C:\\Program Files\\nodejs\\node.exe',
npm_execpath: 'C:\\Users\\OEM\\AppData\\Roaming\\npm\\node_modules\\cnpm\\node_modules\\npminstall\\bin\\install.js',
npm_config_userconfig: 'C:\\Users\\OEM\\.cnpmrc',
npm_config_disturl: 'https://cdn.npmmirror.com/binaries/node',
npm_config_r: 'https://registry.npmmirror.com',
COREPACK_NPM_REGISTRY: 'https://registry.npmmirror.com',
NODEJS_ORG_MIRROR: 'https://cdn.npmmirror.com/binaries/node',
NVM_NODEJS_ORG_MIRROR: 'https://cdn.npmmirror.com/binaries/node',
PHANTOMJS_CDNURL: 'https://cdn.npmmirror.com/binaries/phantomjs',
CHROMEDRIVER_CDNURL: 'https://cdn.npmmirror.com/binaries/chromedriver',
OPERADRIVER_CDNURL: 'https://cdn.npmmirror.com/binaries/operadriver',
CYPRESS_DOWNLOAD_PATH_TEMPLATE: 'https://cdn.npmmirror.com/binaries/cypress/${version}/${platform}-${arch}/cypress.zip',
ELECTRON_MIRROR: 'https://cdn.npmmirror.com/binaries/electron/',
ELECTRON_BUILDER_BINARIES_MIRROR: 'https://cdn.npmmirror.com/binaries/electron-builder-binaries/',
SASS_BINARY_SITE: 'https://cdn.npmmirror.com/binaries/node-sass',
SWC_BINARY_SITE: 'https://cdn.npmmirror.com/binaries/node-swc',
NWJS_URLBASE: 'https://cdn.npmmirror.com/binaries/nwjs/v',
PUPPETEER_DOWNLOAD_HOST: 'https://cdn.npmmirror.com/binaries/chrome-for-testing',
PUPPETEER_DOWNLOAD_BASE_URL: 'https://cdn.npmmirror.com/binaries/chrome-for-testing',
PLAYWRIGHT_DOWNLOAD_HOST: 'https://cdn.npmmirror.com/binaries/playwright',
SENTRYCLI_CDNURL: 'https://cdn.npmmirror.com/binaries/sentry-cli',
SAUCECTL_INSTALL_BINARY_MIRROR: 'https://cdn.npmmirror.com/binaries/saucectl',
RE2_DOWNLOAD_MIRROR: 'https://cdn.npmmirror.com/binaries/node-re2',
RE2_DOWNLOAD_SKIP_PATH: 'true',
PRISMA_ENGINES_MIRROR: 'https://cdn.npmmirror.com/binaries/prisma',
npm_config_better_sqlite3_binary_host: 'https://cdn.npmmirror.com/binaries/better-sqlite3',
npm_config_keytar_binary_host: 'https://cdn.npmmirror.com/binaries/keytar',
npm_config_sharp_binary_host: 'https://cdn.npmmirror.com/binaries/sharp',
npm_config_sharp_libvips_binary_host: 'https://cdn.npmmirror.com/binaries/sharp-libvips',
npm_config_robotjs_binary_host: 'https://cdn.npmmirror.com/binaries/robotjs',
npm_rootpath: 'D:\\VS Code\\hwasmart-yanfaleixiangmu-esp32-program-burner-',
INIT_CWD: 'D:\\VS Code\\hwasmart-yanfaleixiangmu-esp32-program-burner-'
},
binaryMirrors: {
ENVS: {
COREPACK_NPM_REGISTRY: 'https://registry.npmmirror.com',
NODEJS_ORG_MIRROR: 'https://cdn.npmmirror.com/binaries/node',
NVM_NODEJS_ORG_MIRROR: 'https://cdn.npmmirror.com/binaries/node',
PHANTOMJS_CDNURL: 'https://cdn.npmmirror.com/binaries/phantomjs',
CHROMEDRIVER_CDNURL: 'https://cdn.npmmirror.com/binaries/chromedriver',
OPERADRIVER_CDNURL: 'https://cdn.npmmirror.com/binaries/operadriver',
CYPRESS_DOWNLOAD_PATH_TEMPLATE: 'https://cdn.npmmirror.com/binaries/cypress/${version}/${platform}-${arch}/cypress.zip',
ELECTRON_MIRROR: 'https://cdn.npmmirror.com/binaries/electron/',
ELECTRON_BUILDER_BINARIES_MIRROR: 'https://cdn.npmmirror.com/binaries/electron-builder-binaries/',
SASS_BINARY_SITE: 'https://cdn.npmmirror.com/binaries/node-sass',
SWC_BINARY_SITE: 'https://cdn.npmmirror.com/binaries/node-swc',
NWJS_URLBASE: 'https://cdn.npmmirror.com/binaries/nwjs/v',
PUPPETEER_DOWNLOAD_HOST: 'https://cdn.npmmirror.com/binaries/chrome-for-testing',
PUPPETEER_DOWNLOAD_BASE_URL: 'https://cdn.npmmirror.com/binaries/chrome-for-testing',
PLAYWRIGHT_DOWNLOAD_HOST: 'https://cdn.npmmirror.com/binaries/playwright',
SENTRYCLI_CDNURL: 'https://cdn.npmmirror.com/binaries/sentry-cli',
SAUCECTL_INSTALL_BINARY_MIRROR: 'https://cdn.npmmirror.com/binaries/saucectl',
RE2_DOWNLOAD_MIRROR: 'https://cdn.npmmirror.com/binaries/node-re2',
RE2_DOWNLOAD_SKIP_PATH: 'true',
PRISMA_ENGINES_MIRROR: 'https://cdn.npmmirror.com/binaries/prisma',
npm_config_better_sqlite3_binary_host: 'https://cdn.npmmirror.com/binaries/better-sqlite3',
npm_config_keytar_binary_host: 'https://cdn.npmmirror.com/binaries/keytar',
npm_config_sharp_binary_host: 'https://cdn.npmmirror.com/binaries/sharp',
npm_config_sharp_libvips_binary_host: 'https://cdn.npmmirror.com/binaries/sharp-libvips',
npm_config_robotjs_binary_host: 'https://cdn.npmmirror.com/binaries/robotjs'
},
'@ali/s2': { host: 'https://cdn.npmmirror.com/binaries/looksgood-s2' },
sharp: { replaceHostFiles: [Array], replaceHostMap: [Object] },
'@tensorflow/tfjs-node': {
replaceHostFiles: [Array],
replaceHostRegExpMap: [Object],
replaceHostMap: [Object]
},
cypress: {
host: 'https://cdn.npmmirror.com/binaries/cypress',
newPlatforms: [Object]
},
'utf-8-validate': {
host: 'https://cdn.npmmirror.com/binaries/utf-8-validate/v{version}'
},
xprofiler: {
remote_path: './xprofiler/v{version}/',
host: 'https://cdn.npmmirror.com/binaries'
},
leveldown: { host: 'https://cdn.npmmirror.com/binaries/leveldown/v{version}' },
couchbase: { host: 'https://cdn.npmmirror.com/binaries/couchbase/v{version}' },
gl: { host: 'https://cdn.npmmirror.com/binaries/gl/v{version}' },
sqlite3: {
host: 'https://cdn.npmmirror.com/binaries/sqlite3',
remote_path: 'v{version}'
},
'@journeyapps/sqlcipher': { host: 'https://cdn.npmmirror.com/binaries' },
grpc: {
host: 'https://cdn.npmmirror.com/binaries',
remote_path: '{name}/v{version}'
},
'grpc-tools': { host: 'https://cdn.npmmirror.com/binaries' },
wrtc: {
host: 'https://cdn.npmmirror.com/binaries',
remote_path: '{name}/v{version}'
},
fsevents: { host: 'https://cdn.npmmirror.com/binaries/fsevents' },
nodejieba: { host: 'https://cdn.npmmirror.com/binaries/nodejieba' },
canvas: { host: 'https://cdn.npmmirror.com/binaries/canvas' },
'skia-canvas': { host: 'https://cdn.npmmirror.com/binaries/skia-canvas' },
'flow-bin': {
replaceHost: 'https://github.com/facebook/flow/releases/download/v',
host: 'https://cdn.npmmirror.com/binaries/flow/v'
},
'jpegtran-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/jpegtran-bin'
},
'cwebp-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/cwebp-bin'
},
'zopflipng-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/zopflipng-bin'
},
'optipng-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/optipng-bin'
},
mozjpeg: {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/mozjpeg-bin'
},
gifsicle: {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/gifsicle-bin'
},
'pngquant-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/pngquant-bin',
replaceHostMap: [Object]
},
'pngcrush-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/pngcrush-bin'
},
'jpeg-recompress-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/jpeg-recompress-bin'
},
'advpng-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/advpng-bin'
},
'pngout-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/pngout-bin'
},
'jpegoptim-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/jpegoptim-bin'
},
argon2: { host: 'https://cdn.npmmirror.com/binaries/argon2' },
'ali-zeromq': { host: 'https://cdn.npmmirror.com/binaries/ali-zeromq' },
'ali-usb_ctl': { host: 'https://cdn.npmmirror.com/binaries/ali-usb_ctl' },
'gdal-async': { host: 'https://cdn.npmmirror.com/binaries/node-gdal-async' },
'libpg-query': { host: 'https://cdn.npmmirror.com/binaries' }
},
forbiddenLicenses: null,
flatten: false,
proxy: undefined,
prune: false,
disableFallbackStore: false,
workspacesMap: Map(0) {},
enableWorkspace: false,
workspaceRoot: 'D:\\VS Code\\hwasmart-yanfaleixiangmu-esp32-program-burner-',
isWorkspaceRoot: true,
isWorkspacePackage: false,
offline: false,
strictSSL: true,
ignoreScripts: false,
foregroundScripts: false,
ignoreOptionalDependencies: false,
detail: false,
forceLinkLatest: false,
trace: false,
engineStrict: false,
registryOnly: false,
client: false,
autoFixVersion: [Function: autoFixVersion]
}

54
package.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "esp32-program-batch-burner",
"version": "3.3.3",
"description": "esp32程序批量烧录",
"main": "dist/main/app.js",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"electron:dev": "cross-env NODE_ENV=development electron index.js",
"electron:copy-esp32-dir": "fse copy ./public/esp32/ ./dist/release/win-unpacked/esp32/",
"electron:build": "rimraf dist && vite build && tsc -p tsconfig.electron.json && electron-builder --dir && yarn electron:copy-esp32-dir"
},
"dependencies": {
"archiver": "5.3.1",
"serialport": "9.2.4",
"vue": "^3.2.26"
},
"devDependencies": {
"@atao60/fse-cli": "^0.1.7",
"@vitejs/plugin-vue": "^1.1.5",
"@vue/compiler-sfc": "^3.2.26",
"archiver": "^5.3.1",
"autoprefixer": "^10.4.2",
"cross-env": "^7.0.3",
"electron": "13.6.6",
"electron-builder": "^22.10.5",
"electron-rebuild": "2.3.5",
"postcss": "^8.4.5",
"rimraf": "^3.0.2",
"tailwindcss": "^3.0.13",
"ts-node": "^9.1.1",
"typescript": "^4.2.3",
"vite": "^2.0.5"
},
"build": {
"productName": "esp32程序批量烧录",
"appId": "your.id",
"mac": {
"category": "your.app.category.type"
},
"files": [
"dist/main/**/*",
"dist/render/**/*"
],
"win": {
"icon": "./public/favicon.ico"
},
"directories": {
"output": "dist/release"
},
"afterAllArtifactBuild": "afterAllArtifactBuildHook.js"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
public/esp32/boot_app0.bin Normal file

Binary file not shown.

BIN
public/esp32/bootloader.bin Normal file

Binary file not shown.

BIN
public/esp32/esptool.exe Normal file

Binary file not shown.

BIN
public/esp32/partitions.bin Normal file

Binary file not shown.

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

58
src/main/app.ts Normal file
View File

@ -0,0 +1,58 @@
import { app, Menu, BrowserWindow } from 'electron'
import { join } from "path"
import './fileManager'
import './burnManager'
import serialPortManager from './serialPortManager'
Menu.setApplicationMenu(null)
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
minWidth: 560,
minHeight: 400,
title:'esp32程序烧录',
icon: join(__dirname, '../../public/favicon.ico'),
webPreferences: {
nodeIntegration: true,
preload: join(__dirname, 'preload.js')
}
})
if (process.env.NODE_ENV === 'development') {
win.loadURL('http://localhost:3000/')
win.webContents.openDevTools()
} else {
win.loadFile('dist/render/index.html')
}
win.webContents.on('did-finish-load', () => {
serialPortManager.sendPortList(win.webContents)
});
win.hookWindowMessage(537, (wParam, lParam) => {
// WM_DEVICECHANGE = 537; 通知应用程序对设备或计算机的硬件配置的更改
// console.log('WM_DEVICECHANGE');
if ((wParam[0] == 0 && wParam[1] == 0x80) || (wParam[0] == 0x04 && wParam[1] == 0x80)) {
// DBT_DEVICEREMOVECOMPLETE 0x8004 已删除设备或介质
// DBT_DEVICEARRIVAL 0x8000 已插入设备或介质,现已推出
// console.log(wParam)
serialPortManager.sendPortList(win.webContents)
}
});
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})

85
src/main/burnManager.js Normal file
View File

@ -0,0 +1,85 @@
import { ipcMain } from 'electron'
import { execFile } from 'child_process'
const ESP32_RELATIVE_PATH = process.env.NODE_ENV === 'development' ? './public/esp32/' : './esp32/'
ipcMain.on('BURN', (event, {selectedPort, selectedFilePath, errsum, sucsum}) => {
// event.reply('CLEAN_MSG')
execFile(ESP32_RELATIVE_PATH + 'esptool.exe',
['--chip', 'esp32', '--port', selectedPort, '--baud', '921600', '--before', 'default_reset', '--after', 'hard_reset', 'write_flash', '-z', '--flash_mode', 'dio', '--flash_freq', '80m', '--flash_size', 'detect',
'0xe000', ESP32_RELATIVE_PATH + 'boot_app0.bin',
'0x1000', ESP32_RELATIVE_PATH + 'bootloader.bin',
'0x10000', selectedFilePath,
'0x8000', ESP32_RELATIVE_PATH + 'partitions.bin'], (error, stdout, stderr) => {
if (error) {
console.log('-----------------------------')
console.error(error);
console.log('-----------------------------')
let errmsg
let errget = /A fatal error occurred/
let porterr = /Failed to open port/
let pererr = /Permission denied/
let wrong = /SerialException: Cannot configure port, something went wrong. Original message: OSError/
if(errget.test(stdout)){
console.log('错误')
let timeout = /Timed out waiting for packet header/
let nodata = /No serial data received/
let noise = /Possible serial noise or corruption/
let flash = /Failed to write compressed data to flash/
let md5err = /MD5 of file does not match data in flash/
let chiperr = /This chip is ESP32-S2 not ESP32. Wrong --chip argument/
if(timeout.test(stdout)){
console.log('esp32模组未正常进入下载模式或串口被占用')
errmsg = '请检查串口调试助手工具查看是否有进入下载模式的打印或关闭串口后重新烧录'
}else if(nodata.test(stdout)){
console.log('TX、RX引脚未连接或usb转串口工具硬件存在问题')
errmsg = '请检查串口引脚连接是否正常或更换usb转串口工具后重新烧录'
}else if(noise.test(stdout)){
console.log('usb电缆错误或开发板spi flash 引脚短路或电压不稳定')
errmsg = '请尝试更换usb电缆、更换模组、更换开发板或更换稳压电源后重新烧录'
}else if(flash.test(stdout)){
console.log('flah参数问题')
errmsg = '请确保电源稳定、以及串口连接无异常,重试下载烧录程序后重新烧录'
}else if(md5err.test(stdout)){
console.log('flash损坏或引脚焊接问题')
errmsg = 'MD5文件与flash中的数据不匹配请重试烧录'
}else if(chiperr.test(stdout)){
console.log('串口连接的设备不是烧录设备')
errmsg = '请确认串口连接的是烧录设备后重新烧录'
}
}else if(porterr.test(stdout)){
console.log('没有串口权限')
errmsg = '请提升用户权限后重新烧录'
}else if(pererr.test(stdout)){
console.log('串口被占用')
errmsg = '请检查串口调试助手工具关闭串口后重新烧录'
}else if(wrong.test(error)){
console.log('无法配置串口')
errmsg = '请确保烧录设备已正确连接到计算机并检查串口配置后重新烧录'
}else{
console.log('未知错误')
errmsg = '未知错误,请重新烧录'
}
// event.reply('ALERT_MSG', errmsg)
event.reply('ERR_SUM', errsum+1)
// event.reply('APPEND_MSG', error.stack)
console.log('-----------------------------')
console.log(stdout);
console.log('-----------------------------')
// event.reply('APPEND_MSG', stdout)
event.reply('APPEND_MSG', selectedPort + '烧录失败' + '\n' + error.stack + stdout)
event.reply('FAILED_RESULT', selectedPort)
event.reply('BURN_END')
}else{
console.log('-----------------------------')
console.log(stdout);
console.log('-----------------------------')
// event.reply('ALERT_MSG', '烧录成功!')
event.reply('SUC_SUM', sucsum+1)
// event.reply('APPEND_MSG', stdout)
event.reply('APPEND_MSG', selectedPort + '烧录成功')
event.reply('SUC_RESULT', selectedPort)
event.reply('BURN_END')
}
});
})

6
src/main/data.js Normal file
View File

@ -0,0 +1,6 @@
export const filters = [
{ usbProductId: 8963 , usbVendorId: 1659 }, // 核心处理模块micro-usb蓝色无线通信模块
{ usbProductId: 29986, usbVendorId: 6790 }, // 核心处理模块(type-c),红色无线通信模块
{ usbProductId: 29987, usbVendorId: 6790 }, // 核心处理模块(type-c)
]

14
src/main/fileManager.js Normal file
View File

@ -0,0 +1,14 @@
import { dialog, ipcMain } from 'electron'
ipcMain.on('SELECT_FILE', (event) => {
const filePaths = dialog.showOpenDialogSync({
filters: [{name: 'esp32 bin file', extensions: ['bin']}],
properties: ['openFile']
})
if (filePaths === undefined) {
return
}
event.reply('SELECT_FILE', filePaths[0])
})

20
src/main/preload.js Normal file
View File

@ -0,0 +1,20 @@
const {ipcRenderer, contextBridge} = require('electron')
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, data) => {
// whitelist channels
let validChannels = ['toMain']
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
ipcRenderer.send(channel, data)
},
receive: (channel, func) => {
let validChannels = ['fromMain']
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
})

View File

@ -0,0 +1,25 @@
import SerialPort from 'serialport'
import { filters } from './data'
const serialPortManager = {
sendPortList: async (sender) => {
if (!sender) return
const ports = await serialPortManager.list_ports()
let targetArr = []
ports.forEach(element => {
filters.forEach(item => {
//十六进制转十进制
if(parseInt(element.vendorId,16)==item.usbVendorId&&parseInt(element.productId,16)==item.usbProductId){
targetArr.push(element)
}
})
})
sender.send('LIST_PORTS', targetArr)
},
list_ports: async () => {
const ports = await SerialPort.list()
return ports
}
}
export default serialPortManager

12
src/render/App.vue Normal file
View File

@ -0,0 +1,12 @@
<template>
<div class="container mx-auto">
<ProgramBurnForm />
</div>
</template>
<script lang="ts" setup>
import { provide } from 'vue'
import ProgramBurnForm from './components/ProgramBurnForm.vue'
provide('IPCRENDERER_API_SUPPORTED', 'ipcRenderer' in window)
</script>

BIN
src/render/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,444 @@
<template>
<h1 class="text-center text-3xl title">ESP32程序批量烧录</h1>
<div class="home">
<div class="operatebox">
<div class="flexbox">
<div class="p-4 nop">
<div class="textport">待烧录串口</div>
<textarea disabled class="commsg resize-none border-2" :value="ports">
</textarea>
</div>
<div class="p-4 nop">
<div class="textport"> </div>
<button
class="seleanni px-4 border-2 rounded text-white"
:class="loading ? 'bg-gray-500' : 'bg-blue-500'"
:disabled="loading"
@click="selectFile"
>
   
</button>
</div>
<div class="p-4 truncate nop">
<input
disabled
type="text"
class="filebox border-2"
:value="selectedFilePath"
/>
</div>
</div>
<div class="col-span-2 nopd">
<button
class="anni px-4 border-2 rounded text-white"
:class="loading ? 'bg-gray-500' : 'bg-blue-500'"
:disabled="loading"
@click="burn_start"
>
<svg
v-if="loadings"
class="svgbox animate-spin h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<div class="fontset" v-else>   </div>
</button>
</div>
<div class="tips">
<h6 class="tipstitle">   </h6>
<textarea disabled class="tipsmsg resize-none border-2">
1. 待烧录或烧录失败的串口会自动显示
2. 点击按钮选择需要烧录的程序
3. 点击烧录按钮等待烧录完成
**右侧为烧录输出**
#若烧录失败可以通过烧录输出判断错误原因#
</textarea
>
</div>
</div>
<div class="msgbox">
<textarea
ref="container"
disabled
class="textbox resize-none border-2 w-full"
:value="msg"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, inject, watch, onMounted } from "vue";
function alert(data) {
loading.value = true;
var a = document.createElement("div"),
p = document.createElement("p"),
btn = document.createElement("div"),
textNode = document.createTextNode(data ? data : ""),
btnText = document.createTextNode("确定");
//
css(a, {
"padding-top": "60px",
position: "fixed",
left: "0",
right: "0",
top: "40%",
width: "300px",
height: "180px",
margin: "0 auto",
"background-color": "#fff",
border: "2px solid #ccc",
"font-size": "100%",
"text-align": "center",
"z-index": "3",
});
css(btn, {
margin: "10%",
"margin-bottom": "5%",
width: "80%",
position: "absolute",
bottom: "0",
background: "#2222ff",
opacity: "0.8",
"border-radius": "24px",
color: "#fff",
});
//
p.appendChild(textNode);
btn.appendChild(btnText);
a.appendChild(p);
a.appendChild(btn);
//
document.getElementsByTagName("body")[0].appendChild(a);
//
btn.onclick = function () {
a.parentNode?.removeChild(a);
loading.value = false;
};
}
function css(targetObj, cssObj) {
var str = targetObj.getAttribute("style") ? targetObj.getAttribute("style") : "";
for (var i in cssObj) {
str += i + ":" + cssObj[i] + ";";
}
targetObj.style.cssText = str;
}
window.alert = alert;
const IPCRENDERER_API_SUPPORTED = inject("IPCRENDERER_API_SUPPORTED", false);
const loading = ref(false);
const loadings = ref(false);
const msg = ref("烧录结果输出:\n");
const container = ref(null);
const selectedPort = ref("");
const ports = ref([]);
const errsum = ref(0);
const sucsum = ref(0);
const succom = ref([]);
const failedcom = ref([]);
const comlength = ref(0);
const setPorts = (value: Object[]) => {
let compath = [];
value.forEach((element) => {
compath.push(element.path);
});
if (loading.value == false) {
ports.value = compath;
}
comlength.value = ports.value.length;
};
const selectedFilePath = ref("");
const setSelectedFilePath = (value: string) => (selectedFilePath.value = value);
const selectFile = () => {
if (!IPCRENDERER_API_SUPPORTED) {
alert("The ipcRenderer API is not supported.");
return;
}
ipcRenderer.send("SELECT_FILE");
};
const alert_msg = (value: string) => {
if (!value) {
return;
}
alert(value);
};
const errlog = (value: number) => {
if (!value) {
return;
}
errsum.value = value;
};
const suclog = (value: number) => {
if (!value) {
return;
}
sucsum.value = value;
};
const append_msg = (value: string) => {
if (!value) {
return;
}
msg.value += value + "\n";
};
const suc_result = (value: Array<string>) => {
if (!value) {
return;
}
succom.value.push(value);
};
const failed_result = (value: Array<string>) => {
if (!value) {
return;
}
failedcom.value.push(value);
};
const scroll = () => {
setTimeout(() => (container.value.scrollTop = container.value.scrollHeight), 5);
};
const burn = () => {
selectedPort.value = ports.value.shift();
const param = {
selectedPort: selectedPort.value,
selectedFilePath: selectedFilePath.value,
errsum: errsum.value,
sucsum: sucsum.value,
};
ipcRenderer.send("BURN", param);
loading.value = true;
loadings.value = true;
};
const burn_start = () => {
if (ports.value.length == 0) {
alert("未发现串口");
return;
}
if (!selectedFilePath.value) {
alert("请选择程序文件");
return;
}
succom.value = [];
failedcom.value = [];
errsum.value = 0;
sucsum.value = 0;
msg.value = "烧录结果输出:\n";
burn();
};
watch(msg, () => {
scroll();
});
onMounted(() => {
if (!IPCRENDERER_API_SUPPORTED) {
alert("The ipcRenderer API is not supported.");
return;
}
ipcRenderer.receive("SELECT_FILE", setSelectedFilePath);
ipcRenderer.receive("LIST_PORTS", setPorts);
ipcRenderer.receive("BURN_END", () => {
if (ports.value.length == 0) {
loadings.value = false;
loading.value = false;
if (errsum.value == 0) {
alert("串口已全部烧录成功!");
return;
} else if (sucsum.value == 0) {
alert("串口已全部烧录失败!");
ports.value = failedcom.value;
return;
} else {
alert(`烧录结束,成功:${sucsum.value}个,失败:${errsum.value}`);
ports.value = failedcom.value;
return;
}
}
burn();
});
ipcRenderer.receive("ALERT_MSG", alert_msg);
ipcRenderer.receive("ERR_SUM", errlog);
ipcRenderer.receive("SUC_SUM", suclog);
ipcRenderer.receive("APPEND_MSG", append_msg);
ipcRenderer.receive("FAILED_RESULT", failed_result);
ipcRenderer.receive("SUC_RESULT", suc_result);
// ipcRenderer.receive('CLEAN_MSG' , () => msg.value = '')
});
</script>
<style scoped>
.title {
min-width: 220px;
height: 14vh;
line-height: 14vh;
}
.textport {
width: 45%;
font-size: clamp(0.8rem, 0.5rem + 1vw, 2rem);
display: flex;
align-items: center;
}
.anni {
width: 40%;
min-width: 60px;
height: 80%;
margin-top: 1%;
border-radius: 48px;
position: relative;
z-index: 2;
}
.fontset {
margin: auto;
width: 100%;
text-align: center;
display: flex;
font-size: clamp(0.8rem, 0.5rem + 1vw, 2rem);
justify-content: space-around;
align-items: center;
}
.seleanni {
width: 45%;
border-radius: 24px;
height: 100%;
font-size: clamp(0.8rem, 0.5rem + 1vw, 2rem);
}
.selebox {
width: 45%;
border-radius: 24px;
height: 100%;
box-shadow: inset 4px 4px 4px rgba(0, 0, 0, 0.3),
inset -4px -4px 4px rgba(255, 255, 255, 0.7);
}
.filebox {
min-width: 90px;
width: 90%;
background-color: #fff;
height: 100%;
border-radius: 24px;
white-space: nowrap;
overflow: hidden;
box-shadow: inset 4px 4px 4px rgba(0, 0, 0, 0.3),
inset -4px -4px 4px rgba(255, 255, 255, 0.7);
}
.flexbox {
margin: 0;
width: 100%;
height: 40%;
background-color: #eeffff;
display: flex;
justify-content: center;
align-content: space-evenly;
flex-wrap: wrap;
overflow: hidden;
}
.svgbox {
margin: auto;
}
.nop {
padding: 0;
padding-left: 10%;
flex: 1;
min-width: 100%;
max-width: 200%;
height: 16%;
min-height: 28px;
overflow: hidden;
display: flex;
justify-content: start;
}
.nopd {
padding: 0;
flex: 1;
display: flex;
width: 100%;
height: 10%;
justify-content: center;
align-content: center;
}
.msgbox {
padding: 0;
width: 48%;
height: 100%;
overflow: hidden;
}
.textbox {
height: 100%;
background-color: #ffe;
}
.home {
height: 80vh;
display: flex;
justify-content: center;
margin: auto;
}
.operatebox {
margin: 0;
width: 48%;
margin-right: 1%;
height: 100%;
background-color: #ccffff;
}
.tips {
width: 100%;
height: 50%;
background-color: #99ffff;
padding-left: 9%;
}
.tipstitle {
width: 90%;
min-width: 120px;
min-height: 10%;
text-align: center;
padding-top: 1.5%;
font-size: clamp(0.6rem, 0.5rem + 1vw, 1.2rem);
}
.tipsmsg {
width: 90%;
height: 80%;
background-color: #efe;
}
.commsg {
width: 45%;
height: 100%;
background-color: #efe;
}
</style>

3
src/render/index.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

5
src/render/main.ts Normal file
View File

@ -0,0 +1,5 @@
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
createApp(App).mount('#app')

5
src/render/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

10
tailwind.config.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

16
tsconfig.electron.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "esnext",
"module": "CommonJS",
"moduleResolution": "node",
"strict": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"allowJs": true,
"outDir": "dist/main"
},
"include": [
"src/main/app.ts",
"src/main/preload.js",
]
}

16
tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["vite/client"]
},
"include": ["src/render/**/*.ts", "src/render/**/*.d.ts", "src/render/**/*.tsx", "src/render/**/*.vue"],
"exclude": ["src/app.ts"]
}

23
vite.config.ts Normal file
View File

@ -0,0 +1,23 @@
import { join } from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const outDir = join(__dirname, 'dist/render')
const renderDir = join(__dirname, 'src/render')
const publicDir = join(__dirname, 'public')
// https://vitejs.dev/config/
export default defineConfig({
publicDir,
base: './',
plugins: [vue()],
build: {
outDir,
emptyOutDir: true,
},
resolve: {
alias: {
'@': renderDir,
}
},
})

4136
yarn.lock Normal file

File diff suppressed because it is too large Load Diff