import {app, BrowserWindow, ipcMain, dialog} from 'electron'; import {exec, spawn} from 'child_process'; import path from 'path'; import {fileURLToPath} from 'url'; import fs from 'fs'; import axios from 'axios'; import ProcessManager from './processManager.js' const processManager = new ProcessManager(); // 创建进程管理器实例 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PJI_SCRIPT_PATH_PREFIX = "/home/cicv/work/pji_desktop/" // console.log("PJI_SCRIPT_PATH_PREFIX", PJI_SCRIPT_PATH_PREFIX) function createWindow() { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), // 确保路径正确 contextIsolation: true, enableRemoteModule: false, nodeIntegration: false, } }); win.webContents.openDevTools(); // 打开开发者工具进行调试 win.loadURL('http://localhost:5173'); // 开发环境 // win.loadURL('http://36.110.106.156:81'); // 生产环境 console.log('Window created and URL loaded'); } app.whenReady().then(createWindow); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); // // 禁用GPU加速 // app.disableHardwareAcceleration() // ------------- 进程通信 ------------- ipcMain.handle('dialog:open', async (event, options = {}) => { const result = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0], options); return result.canceled ? null : result.filePaths; }); // 下载文件 ipcMain.handle('download-file', async (event, { url, fileName, savePath, overwriteFlag, dialogFlag, requestType, data }) => { try { let filePath let getPath // console.log("url", url) // console.log("response", response) if (dialogFlag) { // 弹出保存文件对话框 console.log("savePath", savePath) // 获取保存路径 getPath = await dialog.showSaveDialog({ defaultPath: path.join(app.getPath('downloads'), fileName) }) filePath = getPath.filePath if (getPath.canceled || filePath === "") { return { success: false, message: 'File path does not exist' } } } else { filePath = path.join(savePath, fileName); } console.log("filePath", filePath) // 查找文件是否存在 const fileExists = fs.existsSync(filePath) if (fileExists && !overwriteFlag) { // 已存在且不覆盖 return {success: true, filePath} } else { // 不存在 console.log("url", url) let response if (requestType === "get") { console.log("get...") response = await axios.get(url, {responseType: 'stream'}); } else { response = await axios.post(url, data, {responseType: 'stream'}); } console.log("response", response) // 写入文件 const writer = fs.createWriteStream(filePath); return new Promise((resolve, reject) => { response.data.pipe(writer); let error = null; writer.on('error', err => { error = err; writer.close(); reject(err); }); writer.on('close', () => { if (!error) { resolve({success: true, filePath}); // 返回成功信息 } else { reject({success: false, error: error.message}); // 返回错误信息 } }); }); } } catch (error) { console.error('下载文件出错:', error); throw error; } }); // 删除文件 ipcMain.on('delete-file', (event, {fileName, savePath}) => { const filePath = path.join(savePath, fileName); // 检查文件是否存在 if (fs.existsSync(filePath)) { // 文件存在,尝试删除 fs.unlink(filePath, (err) => { if (err) { event.reply('delete-file-response', { success: false, message: err.message }); } else { event.reply('delete-file-response', { success: true, message: 'File deleted successfully' }); } }); } else { // 文件不存在 event.reply('delete-file-response', { success: false, message: 'File does not exist' }); } }); // 上传文件 ipcMain.on('upload-file', async (event, {filePath, uploadUrl}) => { if (!fs.existsSync(filePath)) { console.log("File does not exist"); event.reply('upload-file-response', {success: false, message: 'File does not exist'}); return; } try { const result = await uploadFile(filePath, uploadUrl); console.log("result", result); if (result.status) { event.reply('upload-file-response', { success: true, message: 'File uploaded successfully', details: result.details }); } else { event.reply('upload-file-response', { success: false, message: 'Error uploading file', details: '' }); } } catch (err) { event.reply('upload-file-response', { success: false, message: err.message, details: '' }); } }); async function uploadFile(filePath, uploadUrl) { const data = fs.readFileSync(filePath) // 创建formData对象 const formData = new FormData(); formData.append('file', new Blob([data]), path.basename(filePath)); try { // 使用axios上传文件 const response = await axios.post(uploadUrl, formData, { headers: { 'Content-Type': 'multipart/form-data' } }) // console.log('response', response.data) return response.data } catch (err) { console.log("Error uploading file:", err); return { status: false, code: '', message: '上传件失败', details: '' }; } } async function uploadFileWithRetry(filePath, uploadUrl, maxRetries = 3) { let attempt = 1; while (attempt <= maxRetries) { console.log("Uploading file - attempt:", attempt); try { const response = await uploadFile(filePath, uploadUrl); console.log("response", response); if (response.status) { // 上传成功 return response; } else { // 上传失败 console.log("Fail to upload file - attempt:", attempt); attempt++ } } catch (error) { console.log("Error uploading file - attempt:", attempt); console.log("Error:", error); attempt++ } } return { status: false, code: '', message: '上传文件失败', details: '' }; } // 导入算法镜像 ipcMain.on('docker-import', (event, filePath, tag) => { const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + 'docker_import/run_docker_import.sh ' + filePath + ' pji_nav ' + tag console.log('Docker import command:', command); exec(command, (error, stdout, stderr) => { if (error) { console.error(`exec error: ${error}`); event.reply('docker-import-response', { success: false, message: error.message }); } else { console.log(`stdout: ${stdout}`); console.error(`stderr: ${stderr}`); event.reply('docker-import-response', { success: true, message: 'Docker image imported successfully' }); } }); }); // 更新地图 ipcMain.on('update-map', (event, {container_name, file_path}) => { const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + '/map_update/run_map_update.sh ' + container_name + ' ' + file_path; console.log('command:', command); processManager.startProcess('update-map', command, (error, stdout, stderr) => { if (error) { console.error(`exec error: ${error}`); // event.reply('update-map-response', { success: false, message: error.message }); } else { console.log(`stdout: ${stdout}`); console.error(`stderr: ${stderr}`); if (stdout.toString().includes('Result JSON file created')) { // 判断结束标志 event.reply('update-map-response', { success: true, message: 'Update map successfully' }); } else { event.reply('update-map-response', { success: false, message: 'Error updating map' }); } } }); }); // 生成world ipcMain.on('generate-world', (event, {rosbag_path}) => { const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + 'simulation/generate_world.sh ' + rosbag_path console.log('World generation command:', command); exec(command, (error, stdout, stderr) => { if (error) { console.error(`exec error: ${error}`); event.reply('generate-world-response', { success: false, message: error.message }); } else { console.log(`stdout: ${stdout}`); console.error(`stderr: ${stderr}`); event.reply('generate-world-response', { success: true, message: 'World generated successfully' }); } }); }); // 启动仿真环境 ipcMain.on('start-container', (event, {zip_file_path, image_name, container_name}) => { // 使用 spawn 启动脚本 const serviceProcess = spawn('bash', [PJI_SCRIPT_PATH_PREFIX + "simulation/data_preparation.sh", zip_file_path], { detached: true }); // 设置为后台进程 serviceProcess.unref(); // 监听输出 serviceProcess.stdout.on('data', (data) => { console.log(`第一个脚本的输出: ${data}`); // 根据第一个脚本的输出判断其是否准备好 if (data.toString().includes('Data preparation done')) { startSecondScript(); } }); // 监听错误 serviceProcess.stderr.on('data', (data) => { console.error(`执行第一个脚本时出错: ${data}`); }); // 监听关闭 serviceProcess.on('close', (data) => { console.log(`第一个脚本已关闭: ${data}`); }); function startSecondScript() { let command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + "/simulation/start_container.sh " + image_name + " " + container_name // 启动第二个脚本 const script2 = exec(command, (error, stdout, stderr) => { if (error) { console.error(`执行第二个脚本时出错: ${error}`); event.sender.send('start-container-response', { success: false, output: error }); return; } console.log(`第二个脚本的输出: ${stdout}`); event.sender.send('start-container-response', { success: true, output: stdout }); }); script2.on('exit', (code) => { console.log(`第二个脚本已退出,退出码: ${code}`); }); } }); // 执行仿真 ipcMain.on('run-simulation', (event, {random_flag, count, obstacle_flag, default_start_flag, default_end_flag, start_point, end_point}) => { const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + 'simulation/run_simulation.sh ' + random_flag + ' ' + count + ' ' + obstacle_flag + ' ' + default_start_flag + ' ' + default_end_flag + ' ' + start_point + ' ' + end_point; console.log('command:', command); processManager.startProcess('run-simulation', command, (error, stdout, stderr) => { if (error) { console.error(`exec error: ${error}`); // event.reply('run-simulation-response', { success: false, message: error.message }); } else { console.log(`stdout: ${stdout}`); console.error(`stderr: ${stderr}`); if (stdout.toString().includes('Evaluation finished')) { // 判断结束标志 event.reply('run-simulation-response', { success: true, message: 'Run simulation successfully' }); } else { event.reply('run-simulation-response', { success: false, message: 'Error running simulation' }); } } }); // 设置超时机制( const timeoutId = setTimeout(() => { if (!event.sender.isDestroyed()) { // 如果渲染进程仍然存在 processManager.killProcess('run-simulation'); event.reply('run-simulation-response', { success: false, message: 'Script running timeout' }); } clearTimeout(timeoutId) }, 10*60*1000); // 10 分钟 }); // 读取并上传算法评价结果 ipcMain.handle('process-evaluation-files', async (event, {N, equipmentNo, sceneNo}) => { const result = [] const evalDir = PJI_SCRIPT_PATH_PREFIX + "simulation/data/evaluation"; const bagDir = PJI_SCRIPT_PATH_PREFIX + "simulation/data/record_bag"; console.log('N', N) // 记录时间戳 const timeStamp = Date.now() console.log("timeStamp", timeStamp); // 遍历评价结果文件夹,读取并上传相关数据 for (let i = 1; i <= N; i++) { const subDir = path.join(evalDir, `${i}/result`); console.log("subDir", subDir); // 文件路径 - report.json const jsonFilePath = path.join(subDir, "report.json"); console.log("jsonFilePath", jsonFilePath); // 文件路径 - report.pdf const pdfFilePath = path.join(subDir, "report.pdf"); console.log("pdfFilePath", pdfFilePath); // 文件路径 - test-${i}.bag const bagFilePath = path.join(bagDir, `test-${i}.bag`); console.log("bagFilePath", bagFilePath); const uploadPdfUrl = "http://127.0.0.1:8888/simulation/upload/pdf?equipmentNo=" + equipmentNo + "&sceneNo=" + sceneNo + "&timeStamp=" + timeStamp + "&round=" + i; const uploadBagUrl = "http://127.0.0.1:8888/simulation/upload/bag?equipmentNo=" + equipmentNo + "&sceneNo=" + sceneNo + "&timeStamp=" + timeStamp + "&round=" + i; try { // 读取json文件 const jsonData = fs.readFileSync(jsonFilePath, "utf-8"); const parsedData = JSON.parse(jsonData); console.log("parsedData", parsedData); // 上传pdf文件 // 查找pdf文件是否存在 let pdfFileExists = fs.existsSync(pdfFilePath) // pdf文件不存在 if (!pdfFileExists) break // pdf文件存在 const uploadPdfResult = await uploadFileWithRetry(pdfFilePath, uploadPdfUrl); console.log("uploadPdfResult", uploadPdfResult); if (!uploadPdfResult.status) { break } // 上传bag文件 // 查找bag文件是否存在 let bagFileExists = fs.existsSync(bagFilePath) // bag文件不存在 if (!bagFileExists) break // bag文件存在 const uploadBagResult = await uploadFile(bagFilePath, uploadBagUrl); console.log("uploadBagResult", uploadBagResult); if (!uploadBagResult.status) { break} // 整合结果 result.push({ "round": i, "jsonData": parsedData, "uploadPdfKey": uploadPdfResult.details, "uploadBagKey": uploadBagResult.details, "timeStamp": timeStamp }) } catch (error) { console.log("error", error); return { success: false, message: "Error uploading evaluation results", data: [] } } } // 判断数据上传是否完整 if (result.length === N) { return { success: true, message: "Evaluation results uploaded successfully", data: result } } else { return { success: false, message: "Error uploading evaluation results", data: [] } } }) async function uploadMapResult(filePath, uploadUrl) { // 查找文件是否存在 let fileExists = fs.existsSync(filePath) let uploadResult = await uploadFileWithRetry(filePath, uploadUrl); if (!fileExists) return { success: false, message: "Error uploading map results", data: [] } // console.log("uploadResult", uploadResult); if (!uploadResult.status) { return { success: false, message: "Error uploading map results", data: [] } } return { success: true, message: "", data: uploadResult } } // 读取并上传地图更新结果 ipcMain.handle('process-map-update-files', async (event, {equipmentNo, timeStamp}) => { let result = {} equipmentNo = "pji-test" timeStamp = Date.now() // 记录时间戳 console.log("timeStamp", timeStamp); const dataDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data"; const mapBufDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data/bag_folder/mapBuf"; const resultDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data/bag_folder/result"; const updateMapDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data/bag_folder/update_map"; // 读取result*.json // 文件路径 - result*.json const jsonPrefix = "result"; let files = fs.readdirSync(resultDir); let matchedFile = files.find(file => file.startsWith(jsonPrefix)) let jsonFilePath = "" if (matchedFile) { // 如果找到匹配的文件 jsonFilePath = path.join(resultDir, matchedFile); } console.log("jsonFilePath", jsonFilePath); // 文件路径 - 更新前pgm const prePgmPath = path.join(mapBufDir, "map.pgm"); console.log("prePgmPath", prePgmPath); // 文件路径 - 更新后pgm const pgmSuffix = ".pgm"; files = fs.readdirSync(updateMapDir); matchedFile = files.find(file => file.endsWith(pgmSuffix)) let updatePgmPath = "" if (matchedFile) { // 如果找到匹配的文件 updatePgmPath = path.join(updateMapDir, matchedFile); } console.log("updatePgmPath", updatePgmPath); // 文件路径 - 更新前png const prePngPath = path.join(mapBufDir, "map_pre.png"); console.log("prePngPath", prePngPath); // 文件路径 - 更新后png const updatePngPath = path.join(mapBufDir, "map_update.png"); console.log("updatePngPath", updatePngPath); // 文件路径 - 更新地图压缩包 const updateMapPath = path.join(dataDir, "update.zip"); console.log("updateMapPath", updateMapPath); try { // 读取json文件 const jsonData = fs.readFileSync(jsonFilePath, "utf-8"); const parsedData = JSON.parse(jsonData); console.log("parsedData", parsedData); const mapId = parsedData["origin_pgm_id"]; // 上传 更新前pgm let uploadUrl = "http://127.0.0.1:8888/map/upload/map?equipmentNo=" + equipmentNo + "&mapId=" + mapId + "&timeStamp=" + timeStamp ; // 上传文件 // 上传 更新前pgm let res = await uploadMapResult(prePgmPath, uploadUrl); if (!res.success) { return { success: false, message: "Error uploading map results", data: [] } } let prePgmUrl = res.data.details console.log("prePgmUrl", prePgmUrl); // 上传 更新前png res = await uploadMapResult(prePngPath, uploadUrl); if (!res.success) { return { success: false, message: "Error uploading map results", data: [] } } let prePngUrl = res.data.details console.log("prePngUrl", prePngUrl); // 上传 更新后pgm res = await uploadMapResult(updatePgmPath, uploadUrl); if (!res.success) { return { success: false, message: "Error uploading map results", data: [] } } let updatePgmUrl = res.data.details console.log("updatePgmUrl", updatePgmUrl); // 上传 更新后png res = await uploadMapResult(updatePngPath, uploadUrl); if (!res.success) { return { success: false, message: "Error uploading map results", data: [] } } let updatePngUrl = res.data.details console.log("updatePngUrl", updatePngUrl); // 上传 更新地图压缩包 res = await uploadMapResult(updateMapPath, uploadUrl); if (!res.success) { return { success: false, message: "Error uploading map results", data: [] } } let updateMapUrl = res.data.details console.log("updateMapUrl", updateMapUrl); result = { "jsonData": parsedData, "prePgmUrl": prePgmUrl, "prePngUrl": prePngUrl, "updatePgmUrl": updatePgmUrl, "updatePngUrl": updatePngUrl, "updateMapUrl": updateMapUrl, } return { success: true, message: "Evaluation results uploaded successfully", data: result } } catch (error) { console.log("error", error); } })