main.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. import {app, BrowserWindow, ipcMain, dialog} from 'electron';
  2. import {exec, spawn} from 'child_process';
  3. import path from 'path';
  4. import {fileURLToPath} from 'url';
  5. import fs from 'fs';
  6. import axios from 'axios';
  7. import ProcessManager from './processManager.js'
  8. const processManager = new ProcessManager(); // 创建进程管理器实例
  9. const __filename = fileURLToPath(import.meta.url);
  10. const __dirname = path.dirname(__filename);
  11. function createWindow() {
  12. const win = new BrowserWindow({
  13. width: 800,
  14. height: 600,
  15. webPreferences: {
  16. preload: path.join(__dirname, 'preload.js'), // 确保路径正确
  17. contextIsolation: true,
  18. enableRemoteModule: false,
  19. nodeIntegration: false,
  20. }
  21. });
  22. win.webContents.openDevTools(); // 打开开发者工具进行调试
  23. win.loadURL('http://localhost:5173'); // 开发环境
  24. // win.loadURL('http://36.110.106.156:81'); // 生产环境
  25. console.log('Window created and URL loaded');
  26. }
  27. app.whenReady().then(createWindow);
  28. app.on('window-all-closed', () => {
  29. if (process.platform !== 'darwin') {
  30. app.quit();
  31. }
  32. });
  33. app.on('activate', () => {
  34. if (BrowserWindow.getAllWindows().length === 0) {
  35. createWindow();
  36. }
  37. });
  38. // ------------- 进程通信 -------------
  39. ipcMain.handle('dialog:open', async (event, options = {}) => {
  40. const result = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0], options);
  41. return result.canceled ? null : result.filePaths;
  42. });
  43. // 下载文件
  44. ipcMain.handle('download-file', async (event, { url, fileName, savePath, overwriteFlag, dialogFlag, requestType, data }) => {
  45. try {
  46. let filePath
  47. let getPath
  48. // console.log("url", url)
  49. // console.log("response", response)
  50. if (dialogFlag) { // 弹出保存文件对话框
  51. console.log("savePath", savePath)
  52. // 获取保存路径
  53. getPath = await dialog.showSaveDialog({
  54. defaultPath: path.join(app.getPath('downloads'), fileName)
  55. })
  56. filePath = getPath.filePath
  57. if (getPath.canceled || filePath === "") {
  58. return { success: false, message: 'File path does not exist' }
  59. }
  60. } else {
  61. filePath = path.join(savePath, fileName);
  62. }
  63. console.log("filePath", filePath)
  64. // 查找文件是否存在
  65. const fileExists = fs.existsSync(filePath)
  66. if (fileExists && !overwriteFlag) { // 已存在且不覆盖
  67. return {success: true, filePath}
  68. } else { // 不存在
  69. console.log("url", url)
  70. let response
  71. if (requestType === "get") {
  72. console.log("get...")
  73. response = await axios.get(url, {responseType: 'stream'});
  74. } else {
  75. response = await axios.post(url, data, {responseType: 'stream'});
  76. }
  77. console.log("response", response)
  78. // 写入文件
  79. const writer = fs.createWriteStream(filePath);
  80. return new Promise((resolve, reject) => {
  81. response.data.pipe(writer);
  82. let error = null;
  83. writer.on('error', err => {
  84. error = err;
  85. writer.close();
  86. reject(err);
  87. });
  88. writer.on('close', () => {
  89. if (!error) {
  90. resolve({success: true, filePath}); // 返回成功信息
  91. } else {
  92. reject({success: false, error: error.message}); // 返回错误信息
  93. }
  94. });
  95. });
  96. }
  97. } catch (error) {
  98. console.error('下载文件出错:', error);
  99. throw error;
  100. }
  101. });
  102. // 删除文件
  103. ipcMain.on('delete-file', (event, {fileName, savePath}) => {
  104. const filePath = path.join(savePath, fileName);
  105. // 检查文件是否存在
  106. if (fs.existsSync(filePath)) {
  107. // 文件存在,尝试删除
  108. fs.unlink(filePath, (err) => {
  109. if (err) {
  110. event.reply('delete-file-response', { success: false, message: err.message });
  111. } else {
  112. event.reply('delete-file-response', { success: true, message: 'File deleted successfully' });
  113. }
  114. });
  115. } else {
  116. // 文件不存在
  117. event.reply('delete-file-response', { success: false, message: 'File does not exist' });
  118. }
  119. });
  120. // 上传文件
  121. ipcMain.on('upload-file', async (event, {filePath, uploadUrl}) => {
  122. if (!fs.existsSync(filePath)) {
  123. console.log("File does not exist");
  124. event.reply('upload-file-response', {success: false, message: 'File does not exist'});
  125. return;
  126. }
  127. // 读取文件
  128. // fs.readFile(filePath, async (err, data) => {
  129. // if(err) {
  130. // console.log("Error reading file:", err);
  131. // event.reply('upload-file-response', { success: false, message: err.message });
  132. // return;
  133. // }
  134. //
  135. // // 创建formData对象
  136. // const formData = new FormData();
  137. // formData.append('file', new Blob([data]), path.basename(filePath));
  138. //
  139. // try {
  140. // // 使用axios上传文件
  141. // const response = await axios.post(uploadUrl, formData, {
  142. // headers: {
  143. // 'Content-Type': 'multipart/form-data'
  144. // }
  145. // })
  146. // console.log('response', response)
  147. // event.reply('upload-file-response', { success: true, message: 'File uploaded successfully' });
  148. // } catch (err) {
  149. // console.log("Error uploading file:", err);
  150. // event.reply('upload-file-response', { success: false, message: err.message });
  151. // }
  152. // })
  153. try {
  154. const result = await uploadFile(filePath, uploadUrl);
  155. console.log("result", result);
  156. if (result.status) {
  157. event.reply('upload-file-response', { success: true, message: 'File uploaded successfully', details: result.details });
  158. } else {
  159. event.reply('upload-file-response', { success: false, message: 'Error uploading file', details: '' });
  160. }
  161. } catch (err) {
  162. event.reply('upload-file-response', { success: false, message: err.message, details: '' });
  163. }
  164. });
  165. async function uploadFile(filePath, uploadUrl) {
  166. const data = fs.readFileSync(filePath)
  167. // 创建formData对象
  168. const formData = new FormData();
  169. formData.append('file', new Blob([data]), path.basename(filePath));
  170. try {
  171. // 使用axios上传文件
  172. const response = await axios.post(uploadUrl, formData, {
  173. headers: {
  174. 'Content-Type': 'multipart/form-data'
  175. }
  176. })
  177. // console.log('response', response.data)
  178. return response.data
  179. } catch (err) {
  180. console.log("Error uploading file:", err);
  181. return { status: false, code: '', message: '上传件失败', details: '' };
  182. }
  183. }
  184. async function uploadFileWithRetry(filePath, uploadUrl, maxRetries = 3) {
  185. let attempt = 1;
  186. while (attempt <= maxRetries) {
  187. console.log("Uploading file - attempt:", attempt);
  188. try {
  189. const response = await uploadFile(filePath, uploadUrl);
  190. console.log("response", response);
  191. if (response.status) { // 上传成功
  192. return response;
  193. } else { // 上传失败
  194. console.log("Fail to upload file - attempt:", attempt);
  195. attempt++
  196. }
  197. } catch (error) {
  198. console.log("Error uploading file - attempt:", attempt);
  199. console.log("Error:", error);
  200. attempt++
  201. }
  202. }
  203. return { status: false, code: '', message: '上传文件失败', details: '' };
  204. }
  205. // 导入算法镜像
  206. ipcMain.on('docker-import', (event, filePath, tag) => {
  207. const command = 'bash /home/cicv/work/pji_desktop/docker_import/run_docker_import.sh ' + filePath + ' pji_nav ' + tag
  208. console.log('Docker import command:', command);
  209. exec(command, (error, stdout, stderr) => {
  210. if (error) {
  211. console.error(`exec error: ${error}`);
  212. event.reply('docker-import-response', { success: false, message: error.message });
  213. return;
  214. } else {
  215. console.log(`stdout: ${stdout}`);
  216. console.error(`stderr: ${stderr}`);
  217. event.reply('docker-import-response', { success: true, message: 'Docker image imported successfully' });
  218. }
  219. });
  220. });
  221. // 生成world
  222. ipcMain.on('generate-world', (event, {rosbag_path}) => {
  223. const command = 'bash /home/cicv/work/pji_desktop/simulation/generate_world.sh ' + rosbag_path
  224. console.log('World generation command:', command);
  225. exec(command, (error, stdout, stderr) => {
  226. if (error) {
  227. console.error(`exec error: ${error}`);
  228. event.reply('generate-world-response', { success: false, message: error.message });
  229. } else {
  230. console.log(`stdout: ${stdout}`);
  231. console.error(`stderr: ${stderr}`);
  232. event.reply('generate-world-response', { success: true, message: 'World generated successfully' });
  233. }
  234. });
  235. });
  236. // 启动仿真环境
  237. ipcMain.on('start-container', (event, {zip_file_path, image_name, container_name}) => {
  238. // 使用 spawn 启动脚本
  239. const serviceProcess = spawn('bash', ["/home/cicv/work/pji_desktop/simulation/data_preparation.sh", zip_file_path], { detached: true });
  240. // 设置为后台进程
  241. serviceProcess.unref();
  242. // 监听输出
  243. serviceProcess.stdout.on('data', (data) => {
  244. console.log(`第一个脚本的输出: ${data}`);
  245. // 根据第一个脚本的输出判断其是否准备好
  246. if (data.toString().includes('Data preparation done')) {
  247. startSecondScript();
  248. }
  249. });
  250. // 监听错误
  251. serviceProcess.stderr.on('data', (data) => {
  252. console.error(`执行第一个脚本时出错: ${data}`);
  253. });
  254. // 监听关闭
  255. serviceProcess.on('close', (data) => {
  256. console.log(`第一个脚本已关闭: ${data}`);
  257. });
  258. function startSecondScript() {
  259. let command = "bash /home/cicv/work/pji_desktop/simulation/start_container.sh " + image_name + " " + container_name
  260. // 启动第二个脚本
  261. const script2 = exec(command, (error, stdout, stderr) => {
  262. if (error) {
  263. console.error(`执行第二个脚本时出错: ${error}`);
  264. event.sender.send('start-container-response', { success: false, output: error });
  265. return;
  266. }
  267. console.log(`第二个脚本的输出: ${stdout}`);
  268. event.sender.send('start-container-response', { success: true, output: stdout });
  269. });
  270. script2.on('exit', (code) => {
  271. console.log(`第二个脚本已退出,退出码: ${code}`);
  272. });
  273. }
  274. });
  275. // 执行仿真
  276. ipcMain.on('run-simulation', (event, {random_flag, count, obstacle_flag, default_start_flag, default_end_flag, start_point, end_point}) => {
  277. const command = 'bash /home/cicv/work/pji_desktop/simulation/run_simulation.sh ' + random_flag + ' ' + count + ' ' + obstacle_flag + ' ' +
  278. default_start_flag + ' ' + default_end_flag + ' ' + start_point + ' ' + end_point;
  279. console.log('command:', command);
  280. // exec(command, (error, stdout, stderr) => {
  281. // if (error) {
  282. // console.error(`exec error: ${error}`);
  283. // event.reply('run-simulation-response', { success: false, message: error.message });
  284. // } else {
  285. // console.log(`stdout: ${stdout}`);
  286. // console.error(`stderr: ${stderr}`);
  287. // if (stdout.toString().includes('Evaluation finished')) { // 判断结束标志
  288. // event.reply('run-simulation-response', { success: true, message: 'Run simulation successfully' });
  289. // } else {
  290. // event.reply('run-simulation-response', { success: false, message: 'Error running simulation' });
  291. // }
  292. // }
  293. // });
  294. processManager.startProcess('run-simulation', command, (error, stdout, stderr) => {
  295. if (error) {
  296. console.error(`exec error: ${error}`);
  297. event.reply('run-simulation-response', { success: false, message: error.message });
  298. } else {
  299. console.log(`stdout: ${stdout}`);
  300. console.error(`stderr: ${stderr}`);
  301. if (stdout.toString().includes('Evaluation finished')) { // 判断结束标志
  302. event.reply('run-simulation-response', { success: true, message: 'Run simulation successfully' });
  303. } else {
  304. event.reply('run-simulation-response', { success: false, message: 'Error running simulation' });
  305. }
  306. }
  307. });
  308. // 设置超时机制(
  309. setTimeout(() => {
  310. processManager.killProcess('run-simulation');
  311. event.reply('run-simulation-response', { success: false, message: 'Script running timeout' });
  312. }, 3*60*1000); // 3 分钟
  313. });
  314. // 读取并上传算法评价结果
  315. ipcMain.handle('process-evaluation-files', async (event, {N, equipmentNo, sceneNo}) => {
  316. const result = []
  317. const evalDir = "/home/cicv/work/pji_desktop/simulation/data/evaluation";
  318. const bagDir = "/home/cicv/work/pji_desktop/simulation/data/record_bag";
  319. console.log('N', N)
  320. // 记录时间戳
  321. const timeStamp = Date.now()
  322. console.log("timeStamp", timeStamp);
  323. // 遍历评价结果文件夹,读取并上传相关数据
  324. for (let i = 1; i <= N; i++) {
  325. const subDir = path.join(evalDir, `${i}/result`);
  326. console.log("subDir", subDir);
  327. // 文件路径 - report.json
  328. const jsonFilePath = path.join(subDir, "report.json");
  329. console.log("jsonFilePath", jsonFilePath);
  330. // 文件路径 - report.pdf
  331. const pdfFilePath = path.join(subDir, "report.pdf");
  332. console.log("pdfFilePath", pdfFilePath);
  333. // 文件路径 - test-${i}.bag
  334. const bagFilePath = path.join(bagDir, `test-${i}.bag`);
  335. console.log("bagFilePath", bagFilePath);
  336. const uploadPdfUrl = "http://127.0.0.1:8888/simulation/upload/pdf?equipmentNo=" + equipmentNo + "&sceneNo=" + sceneNo +
  337. "&timeStamp=" + timeStamp + "&round=" + i;
  338. const uploadBagUrl = "http://127.0.0.1:8888/simulation/upload/bag?equipmentNo=" + equipmentNo + "&sceneNo=" + sceneNo +
  339. "&timeStamp=" + timeStamp + "&round=" + i;
  340. try {
  341. // 读取json文件
  342. const jsonData = fs.readFileSync(jsonFilePath, "utf-8");
  343. const parsedData = JSON.parse(jsonData);
  344. console.log("parsedData", parsedData);
  345. // 上传pdf文件
  346. // 查找pdf文件是否存在
  347. let pdfFileExists = fs.existsSync(pdfFilePath)
  348. // pdf文件不存在
  349. if (!pdfFileExists) break
  350. // pdf文件存在
  351. const uploadPdfResult = await uploadFileWithRetry(pdfFilePath, uploadPdfUrl);
  352. console.log("uploadPdfResult", uploadPdfResult);
  353. if (!uploadPdfResult.status) {
  354. break
  355. }
  356. // 上传bag文件
  357. // 查找bag文件是否存在
  358. let bagFileExists = fs.existsSync(bagFilePath)
  359. // bag文件不存在
  360. if (!bagFileExists) break
  361. // bag文件存在
  362. const uploadBagResult = await uploadFile(bagFilePath, uploadBagUrl);
  363. console.log("uploadBagResult", uploadBagResult);
  364. if (!uploadBagResult.status) { break}
  365. // 整合结果
  366. result.push({
  367. "round": i,
  368. "jsonData": parsedData,
  369. "uploadPdfKey": uploadPdfResult.details,
  370. "uploadBagKey": uploadBagResult.details,
  371. "timeStamp": timeStamp
  372. })
  373. } catch (error) {
  374. console.log("error", error);
  375. return { success: false, message: "Error uploading evaluation results", data: [] }
  376. }
  377. }
  378. // 判断数据上传是否完整
  379. if (result.length === N) {
  380. return { success: true, message: "Evaluation results uploaded successfully", data: result }
  381. } else {
  382. return { success: false, message: "Error uploading evaluation results", data: [] }
  383. }
  384. })