main.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  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. const PJI_SCRIPT_PATH_PREFIX = "/mnt/pji_desktop/scripts/"
  12. // console.log("PJI_SCRIPT_PATH_PREFIX", PJI_SCRIPT_PATH_PREFIX)
  13. function createWindow() {
  14. const win = new BrowserWindow({
  15. width: 800,
  16. height: 600,
  17. webPreferences: {
  18. preload: path.join(__dirname, 'preload.js'), // 确保路径正确
  19. contextIsolation: true,
  20. enableRemoteModule: false,
  21. nodeIntegration: false,
  22. }
  23. });
  24. win.webContents.openDevTools(); // 打开开发者工具进行调试
  25. win.loadURL('http://localhost:5173'); // 开发环境
  26. // win.loadURL('http://36.110.106.156:81'); // 生产环境
  27. console.log('Window created and URL loaded');
  28. }
  29. app.whenReady().then(createWindow);
  30. app.on('window-all-closed', () => {
  31. if (process.platform !== 'darwin') {
  32. app.quit();
  33. }
  34. });
  35. app.on('activate', () => {
  36. if (BrowserWindow.getAllWindows().length === 0) {
  37. createWindow();
  38. }
  39. });
  40. // // 禁用GPU加速
  41. // app.disableHardwareAcceleration()
  42. // ------------- 进程通信 -------------
  43. ipcMain.handle('dialog:open', async (event, options = {}) => {
  44. const result = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0], options);
  45. return result.canceled ? null : result.filePaths;
  46. });
  47. // 下载文件
  48. ipcMain.handle('download-file', async (event, { url, fileName, savePath, overwriteFlag, dialogFlag, requestType, data }) => {
  49. try {
  50. let filePath
  51. let getPath
  52. // console.log("url", url)
  53. // console.log("response", response)
  54. if (dialogFlag) { // 弹出保存文件对话框
  55. console.log("savePath", savePath)
  56. // 获取保存路径
  57. getPath = await dialog.showSaveDialog({
  58. defaultPath: path.join(app.getPath('downloads'), fileName)
  59. })
  60. filePath = getPath.filePath
  61. if (getPath.canceled || filePath === "") {
  62. return { success: false, message: 'File path does not exist' }
  63. }
  64. } else {
  65. filePath = path.join(savePath, fileName);
  66. }
  67. console.log("filePath", filePath)
  68. // 查找文件是否存在
  69. const fileExists = fs.existsSync(filePath)
  70. if (fileExists && !overwriteFlag) { // 已存在且不覆盖
  71. return {success: true, filePath}
  72. } else { // 不存在
  73. console.log("url", url)
  74. let response
  75. if (requestType === "get") {
  76. console.log("get...")
  77. response = await axios.get(url, {responseType: 'stream'});
  78. } else {
  79. response = await axios.post(url, data, {responseType: 'stream'});
  80. }
  81. console.log("response", response)
  82. // 写入文件
  83. const writer = fs.createWriteStream(filePath);
  84. return new Promise((resolve, reject) => {
  85. response.data.pipe(writer);
  86. let error = null;
  87. writer.on('error', err => {
  88. error = err;
  89. writer.close();
  90. reject(err);
  91. });
  92. writer.on('close', () => {
  93. if (!error) {
  94. resolve({success: true, filePath}); // 返回成功信息
  95. } else {
  96. reject({success: false, error: error.message}); // 返回错误信息
  97. }
  98. });
  99. });
  100. }
  101. } catch (error) {
  102. console.error('下载文件出错:', error);
  103. throw error;
  104. }
  105. });
  106. // 删除文件
  107. ipcMain.on('delete-file', (event, {fileName, savePath}) => {
  108. const filePath = path.join(savePath, fileName);
  109. // 检查文件是否存在
  110. if (fs.existsSync(filePath)) {
  111. // 文件存在,尝试删除
  112. fs.unlink(filePath, (err) => {
  113. if (err) {
  114. event.reply('delete-file-response', { success: false, message: err.message });
  115. } else {
  116. event.reply('delete-file-response', { success: true, message: 'File deleted successfully' });
  117. }
  118. });
  119. } else {
  120. // 文件不存在
  121. event.reply('delete-file-response', { success: false, message: 'File does not exist' });
  122. }
  123. });
  124. // 上传文件
  125. ipcMain.on('upload-file', async (event, {filePath, uploadUrl}) => {
  126. if (!fs.existsSync(filePath)) {
  127. console.log("File does not exist");
  128. event.reply('upload-file-response', {success: false, message: 'File does not exist'});
  129. return;
  130. }
  131. try {
  132. const result = await uploadFile(filePath, uploadUrl);
  133. console.log("result", result);
  134. if (result.status) {
  135. event.reply('upload-file-response', { success: true, message: 'File uploaded successfully', details: result.details });
  136. } else {
  137. event.reply('upload-file-response', { success: false, message: 'Error uploading file', details: '' });
  138. }
  139. } catch (err) {
  140. event.reply('upload-file-response', { success: false, message: err.message, details: '' });
  141. }
  142. });
  143. async function uploadFile(filePath, uploadUrl) {
  144. const data = fs.readFileSync(filePath)
  145. // 创建formData对象
  146. const formData = new FormData();
  147. formData.append('file', new Blob([data]), path.basename(filePath));
  148. try {
  149. // 使用axios上传文件
  150. const response = await axios.post(uploadUrl, formData, {
  151. headers: {
  152. 'Content-Type': 'multipart/form-data'
  153. }
  154. })
  155. // console.log('response', response.data)
  156. return response.data
  157. } catch (err) {
  158. console.log("Error uploading file:", err);
  159. return { status: false, code: '', message: '上传件失败', details: '' };
  160. }
  161. }
  162. async function uploadFileWithRetry(filePath, uploadUrl, maxRetries = 3) {
  163. let attempt = 1;
  164. while (attempt <= maxRetries) {
  165. console.log("Uploading file - attempt:", attempt);
  166. try {
  167. const response = await uploadFile(filePath, uploadUrl);
  168. console.log("response", response);
  169. if (response.status) { // 上传成功
  170. return response;
  171. } else { // 上传失败
  172. console.log("Fail to upload file - attempt:", attempt);
  173. attempt++
  174. }
  175. } catch (error) {
  176. console.log("Error uploading file - attempt:", attempt);
  177. console.log("Error:", error);
  178. attempt++
  179. }
  180. }
  181. return { status: false, code: '', message: '上传文件失败', details: '' };
  182. }
  183. // 导入算法镜像
  184. ipcMain.on('docker-import', (event, filePath, tag) => {
  185. const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + 'docker_import/run_docker_import.sh ' + filePath + ' pji_nav ' + tag
  186. console.log('Docker import command:', command);
  187. exec(command, (error, stdout, stderr) => {
  188. if (error) {
  189. console.error(`exec error: ${error}`);
  190. event.reply('docker-import-response', { success: false, message: error.message });
  191. } else {
  192. console.log(`stdout: ${stdout}`);
  193. console.error(`stderr: ${stderr}`);
  194. event.reply('docker-import-response', { success: true, message: 'Docker image imported successfully' });
  195. }
  196. });
  197. });
  198. // 更新地图
  199. ipcMain.on('update-map', (event, {container_name, file_path}) => {
  200. const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + '/map_update/run_map_update.sh ' + container_name + ' ' + file_path;
  201. console.log('command:', command);
  202. processManager.startProcess('update-map', command, (error, stdout, stderr) => {
  203. if (error) {
  204. console.error(`exec error: ${error}`);
  205. // event.reply('update-map-response', { success: false, message: error.message });
  206. } else {
  207. console.log(`stdout: ${stdout}`);
  208. console.error(`stderr: ${stderr}`);
  209. if (stdout.toString().includes('Result JSON file created')) { // 判断结束标志
  210. event.reply('update-map-response', { success: true, message: 'Update map successfully' });
  211. } else {
  212. event.reply('update-map-response', { success: false, message: 'Error updating map' });
  213. }
  214. }
  215. });
  216. });
  217. // 生成world
  218. ipcMain.on('generate-world', (event, {rosbag_path}) => {
  219. const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + 'simulation/generate_world.sh ' + rosbag_path
  220. console.log('World generation command:', command);
  221. exec(command, (error, stdout, stderr) => {
  222. if (error) {
  223. console.error(`exec error: ${error}`);
  224. event.reply('generate-world-response', { success: false, message: error.message });
  225. } else {
  226. console.log(`stdout: ${stdout}`);
  227. console.error(`stderr: ${stderr}`);
  228. event.reply('generate-world-response', { success: true, message: 'World generated successfully' });
  229. }
  230. });
  231. });
  232. // 启动仿真环境
  233. ipcMain.on('start-container', (event, {zip_file_path, image_name, container_name}) => {
  234. // 使用 spawn 启动脚本
  235. const serviceProcess = spawn('bash', [PJI_SCRIPT_PATH_PREFIX + "simulation/data_preparation.sh", zip_file_path], { detached: true });
  236. // 设置为后台进程
  237. serviceProcess.unref();
  238. // 监听输出
  239. serviceProcess.stdout.on('data', (data) => {
  240. console.log(`第一个脚本的输出: ${data}`);
  241. // 根据第一个脚本的输出判断其是否准备好
  242. if (data.toString().includes('Data preparation done')) {
  243. startSecondScript();
  244. }
  245. });
  246. // 监听错误
  247. serviceProcess.stderr.on('data', (data) => {
  248. console.error(`执行第一个脚本时出错: ${data}`);
  249. });
  250. // 监听关闭
  251. serviceProcess.on('close', (data) => {
  252. console.log(`第一个脚本已关闭: ${data}`);
  253. });
  254. function startSecondScript() {
  255. let command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + "/simulation/start_container.sh " + image_name + " " + container_name
  256. // 启动第二个脚本
  257. const script2 = exec(command, (error, stdout, stderr) => {
  258. if (error) {
  259. console.error(`执行第二个脚本时出错: ${error}`);
  260. event.sender.send('start-container-response', { success: false, output: error });
  261. return;
  262. }
  263. console.log(`第二个脚本的输出: ${stdout}`);
  264. event.sender.send('start-container-response', { success: true, output: stdout });
  265. });
  266. script2.on('exit', (code) => {
  267. console.log(`第二个脚本已退出,退出码: ${code}`);
  268. });
  269. }
  270. });
  271. // 执行仿真
  272. ipcMain.on('run-simulation', (event, {random_flag, count, obstacle_flag, default_start_flag, default_end_flag, start_point, end_point}) => {
  273. const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + 'simulation/run_simulation.sh ' + random_flag + ' ' + count + ' ' + obstacle_flag + ' ' +
  274. default_start_flag + ' ' + default_end_flag + ' ' + start_point + ' ' + end_point;
  275. console.log('command:', command);
  276. processManager.startProcess('run-simulation', command, (error, stdout, stderr) => {
  277. if (error) {
  278. console.error(`exec error: ${error}`);
  279. // event.reply('run-simulation-response', { success: false, message: error.message });
  280. } else {
  281. console.log(`stdout: ${stdout}`);
  282. console.error(`stderr: ${stderr}`);
  283. if (stdout.toString().includes('Evaluation finished')) { // 判断结束标志
  284. event.reply('run-simulation-response', { success: true, message: 'Run simulation successfully' });
  285. } else {
  286. event.reply('run-simulation-response', { success: false, message: 'Error running simulation' });
  287. }
  288. }
  289. });
  290. // 设置超时机制(
  291. const timeoutId = setTimeout(() => {
  292. if (!event.sender.isDestroyed()) {
  293. // 如果渲染进程仍然存在
  294. processManager.killProcess('run-simulation');
  295. event.reply('run-simulation-response', { success: false, message: 'Script running timeout' });
  296. }
  297. clearTimeout(timeoutId)
  298. }, 10*60*1000); // 10 分钟
  299. });
  300. // 读取并上传算法评价结果
  301. ipcMain.handle('process-evaluation-files', async (event, {N, equipmentNo, sceneNo}) => {
  302. const result = []
  303. const evalDir = PJI_SCRIPT_PATH_PREFIX + "simulation/data/evaluation";
  304. const bagDir = PJI_SCRIPT_PATH_PREFIX + "simulation/data/record_bag";
  305. console.log('N', N)
  306. // 记录时间戳
  307. const timeStamp = Date.now()
  308. console.log("timeStamp", timeStamp);
  309. // 遍历评价结果文件夹,读取并上传相关数据
  310. for (let i = 1; i <= N; i++) {
  311. const subDir = path.join(evalDir, `${i}/result`);
  312. console.log("subDir", subDir);
  313. // 文件路径 - report.json
  314. const jsonFilePath = path.join(subDir, "report.json");
  315. console.log("jsonFilePath", jsonFilePath);
  316. // 文件路径 - report.pdf
  317. const pdfFilePath = path.join(subDir, "report.pdf");
  318. console.log("pdfFilePath", pdfFilePath);
  319. // 文件路径 - test-${i}.bag
  320. const bagFilePath = path.join(bagDir, `test-${i}.bag`);
  321. console.log("bagFilePath", bagFilePath);
  322. const uploadPdfUrl = "http://127.0.0.1:8888/simulation/upload/pdf?equipmentNo=" + equipmentNo + "&sceneNo=" + sceneNo +
  323. "&timeStamp=" + timeStamp + "&round=" + i;
  324. const uploadBagUrl = "http://127.0.0.1:8888/simulation/upload/bag?equipmentNo=" + equipmentNo + "&sceneNo=" + sceneNo +
  325. "&timeStamp=" + timeStamp + "&round=" + i;
  326. try {
  327. // 读取json文件
  328. const jsonData = fs.readFileSync(jsonFilePath, "utf-8");
  329. const parsedData = JSON.parse(jsonData);
  330. console.log("parsedData", parsedData);
  331. // 上传pdf文件
  332. // 查找pdf文件是否存在
  333. let pdfFileExists = fs.existsSync(pdfFilePath)
  334. // pdf文件不存在
  335. if (!pdfFileExists) break
  336. // pdf文件存在
  337. const uploadPdfResult = await uploadFileWithRetry(pdfFilePath, uploadPdfUrl);
  338. console.log("uploadPdfResult", uploadPdfResult);
  339. if (!uploadPdfResult.status) {
  340. break
  341. }
  342. // 上传bag文件
  343. // 查找bag文件是否存在
  344. let bagFileExists = fs.existsSync(bagFilePath)
  345. // bag文件不存在
  346. if (!bagFileExists) break
  347. // bag文件存在
  348. const uploadBagResult = await uploadFile(bagFilePath, uploadBagUrl);
  349. console.log("uploadBagResult", uploadBagResult);
  350. if (!uploadBagResult.status) { break}
  351. // 整合结果
  352. result.push({
  353. "round": i,
  354. "jsonData": parsedData,
  355. "uploadPdfKey": uploadPdfResult.details,
  356. "uploadBagKey": uploadBagResult.details,
  357. "timeStamp": timeStamp
  358. })
  359. } catch (error) {
  360. console.log("error", error);
  361. return { success: false, message: "Error uploading evaluation results", data: [] }
  362. }
  363. }
  364. // 判断数据上传是否完整
  365. if (result.length === N) {
  366. return { success: true, message: "Evaluation results uploaded successfully", data: result }
  367. } else {
  368. return { success: false, message: "Error uploading evaluation results", data: [] }
  369. }
  370. })
  371. async function uploadMapResult(filePath, uploadUrl) {
  372. // 查找文件是否存在
  373. let fileExists = fs.existsSync(filePath)
  374. let uploadResult = await uploadFileWithRetry(filePath, uploadUrl);
  375. if (!fileExists) return { success: false, message: "Error uploading map results", data: [] }
  376. // console.log("uploadResult", uploadResult);
  377. if (!uploadResult.status) {
  378. return { success: false, message: "Error uploading map results", data: [] }
  379. }
  380. return { success: true, message: "", data: uploadResult }
  381. }
  382. // 读取并上传地图更新结果
  383. ipcMain.handle('process-map-update-files', async (event, {equipmentNo, timeStamp}) => {
  384. let result = {}
  385. equipmentNo = "pji-test"
  386. timeStamp = Date.now()
  387. // 记录时间戳
  388. console.log("timeStamp", timeStamp);
  389. const dataDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data";
  390. const mapBufDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data/bag_folder/mapBuf";
  391. const resultDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data/bag_folder/result";
  392. const updateMapDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data/bag_folder/update_map";
  393. // 读取result*.json
  394. // 文件路径 - result*.json
  395. const jsonPrefix = "result";
  396. let files = fs.readdirSync(resultDir);
  397. let matchedFile = files.find(file => file.startsWith(jsonPrefix))
  398. let jsonFilePath = ""
  399. if (matchedFile) {
  400. // 如果找到匹配的文件
  401. jsonFilePath = path.join(resultDir, matchedFile);
  402. }
  403. console.log("jsonFilePath", jsonFilePath);
  404. // 文件路径 - 更新前pgm
  405. const prePgmPath = path.join(mapBufDir, "map.pgm");
  406. console.log("prePgmPath", prePgmPath);
  407. // 文件路径 - 更新后pgm
  408. const pgmSuffix = ".pgm";
  409. files = fs.readdirSync(updateMapDir);
  410. matchedFile = files.find(file => file.endsWith(pgmSuffix))
  411. let updatePgmPath = ""
  412. if (matchedFile) {
  413. // 如果找到匹配的文件
  414. updatePgmPath = path.join(updateMapDir, matchedFile);
  415. }
  416. console.log("updatePgmPath", updatePgmPath);
  417. // 文件路径 - 更新前png
  418. const prePngPath = path.join(mapBufDir, "map_pre.png");
  419. console.log("prePngPath", prePngPath);
  420. // 文件路径 - 更新后png
  421. const updatePngPath = path.join(mapBufDir, "map_update.png");
  422. console.log("updatePngPath", updatePngPath);
  423. // 文件路径 - 更新地图压缩包
  424. const updateMapPath = path.join(dataDir, "update.zip");
  425. console.log("updateMapPath", updateMapPath);
  426. try {
  427. // 读取json文件
  428. const jsonData = fs.readFileSync(jsonFilePath, "utf-8");
  429. const parsedData = JSON.parse(jsonData);
  430. console.log("parsedData", parsedData);
  431. const mapId = parsedData["origin_pgm_id"];
  432. // 上传 更新前pgm
  433. let uploadUrl = "http://127.0.0.1:8888/map/upload/map?equipmentNo=" + equipmentNo + "&mapId=" + mapId +
  434. "&timeStamp=" + timeStamp ;
  435. // 上传文件
  436. // 上传 更新前pgm
  437. let res = await uploadMapResult(prePgmPath, uploadUrl);
  438. if (!res.success) {
  439. return { success: false, message: "Error uploading map results", data: [] }
  440. }
  441. let prePgmUrl = res.data.details
  442. console.log("prePgmUrl", prePgmUrl);
  443. // 上传 更新前png
  444. res = await uploadMapResult(prePngPath, uploadUrl);
  445. if (!res.success) {
  446. return { success: false, message: "Error uploading map results", data: [] }
  447. }
  448. let prePngUrl = res.data.details
  449. console.log("prePngUrl", prePngUrl);
  450. // 上传 更新后pgm
  451. res = await uploadMapResult(updatePgmPath, uploadUrl);
  452. if (!res.success) {
  453. return { success: false, message: "Error uploading map results", data: [] }
  454. }
  455. let updatePgmUrl = res.data.details
  456. console.log("updatePgmUrl", updatePgmUrl);
  457. // 上传 更新后png
  458. res = await uploadMapResult(updatePngPath, uploadUrl);
  459. if (!res.success) {
  460. return { success: false, message: "Error uploading map results", data: [] }
  461. }
  462. let updatePngUrl = res.data.details
  463. console.log("updatePngUrl", updatePngUrl);
  464. // 上传 更新地图压缩包
  465. res = await uploadMapResult(updateMapPath, uploadUrl);
  466. if (!res.success) {
  467. return { success: false, message: "Error uploading map results", data: [] }
  468. }
  469. let updateMapUrl = res.data.details
  470. console.log("updateMapUrl", updateMapUrl);
  471. result = {
  472. "jsonData": parsedData,
  473. "prePgmUrl": prePgmUrl,
  474. "prePngUrl": prePngUrl,
  475. "updatePgmUrl": updatePgmUrl,
  476. "updatePngUrl": updatePngUrl,
  477. "updateMapUrl": updateMapUrl,
  478. }
  479. return { success: true, message: "Evaluation results uploaded successfully", data: result }
  480. } catch (error) {
  481. console.log("error", error);
  482. }
  483. })