Jelajahi Sumber

feat(camera): 新增 Smart Shooter 6 相机控制支持

- 重构控制器配置为结构化的 CONTROLLERS 对象,统一管理三种控制器
- 新增 Smart Shooter 6 的 exe 路径、检测及启动逻辑
- 添加 detectController 函数,按目录中实际存在的 exe 自动识别类型
- 添加 normalizeControlType 兼容历史配置中 SmartShooter 仍为默认值的场景
- 新增 migrateLegacySmartShooter 迁移逻辑,将旧出厂默认值自动切换为 6
- 默认配置从 Smart Shooter 5 更新为 Smart Shooter 6
- 前端预览路径判断增加 SmartShooter6 类型支持
- 更新错误提示信息,明确提示支持的软件版本
panqiuyao 15 jam lalu
induk
melakukan
8b1869204a

+ 5 - 2
electron/controller/camera.js

@@ -125,8 +125,11 @@ class CameraController extends Controller {
 
 
     } catch (error) {
     } catch (error) {
 
 
-      let msg = '请安装SmartShooter5软件'
-      if(readConfigFile().controlType === 'digiCamControl') msg = '请安装digiCamControl软件,并打开digiCamControl软件的web服务,端口为5513'
+      const controlType = readConfigFile().controlType
+      let msg = '请安装 Smart Shooter 5 或 Smart Shooter 6'
+      if(controlType === 'SmartShooter') msg = '请安装 Smart Shooter 5'
+      if(controlType === 'SmartShooter6') msg = '请安装 Smart Shooter 6'
+      if(controlType === 'digiCamControl') msg = '请安装digiCamControl软件,并打开digiCamControl软件的web服务,端口为5513'
       return {
       return {
         status:-1,
         status:-1,
         msg,
         msg,

+ 63 - 20
electron/utils/camera.js

@@ -18,25 +18,63 @@ let cameraProcess = null;        // 子进程引用
 let isProcessRunning = false;    // 进程运行状态
 let isProcessRunning = false;    // 进程运行状态
 const processEmitter = new EventEmitter(); // 进程事件发射器
 const processEmitter = new EventEmitter(); // 进程事件发射器
 
 
-const exe = {
-  "digiCamControl":"CameraControl.exe",
-  "SmartShooter":"SmartShooter5.exe",
+// Smart Shooter 5 / 6 与 digiCamControl 并存。
+// 5 和 6 共用同一套 External API(54543 事件 / 54544 请求),不能同时开,按 controlType 只启动其中一个。
+const CONTROLLERS = {
+  digiCamControl: {
+    exe: "CameraControl.exe",
+    pathKey: "digiCamControlPath",
+    label: "digiCamControl",
+  },
+  SmartShooter: {
+    exe: "SmartShooter5.exe",
+    pathKey: "SmartShooterPath",
+    label: "Smart Shooter 5",
+  },
+  SmartShooter6: {
+    exe: "SmartShooter6.exe",
+    pathKey: "SmartShooter6Path",
+    label: "Smart Shooter 6",
+  },
 }
 }
 
 
+// 历史配置 controlType 仍是 SmartShooter,保持原行为
+function normalizeControlType(type) {
+  return CONTROLLERS[type] ? type : "SmartShooter"
+}
 
 
-function getExePath () {
-  let exePath =  ""
-  if(readConfigFile().controlType === 'digiCamControl'){
-    exePath =  path.join( readConfigFile().controlPath || readConfigFile().digiCamControlPath, exe["digiCamControl"]);
-  }else if(readConfigFile().controlType === 'SmartShooter'){
-    exePath =  path.join( readConfigFile().controlPath || readConfigFile().SmartShooterPath,  exe["SmartShooter"]);
+function isSmartShooterType(type) {
+  return type === "SmartShooter" || type === "SmartShooter6"
+}
+
+function getController() {
+  const config = readConfigFile() || {}
+  const type = normalizeControlType(config.controlType)
+  const spec = CONTROLLERS[type]
+  return {
+    type,
+    ...spec,
+    dir: config.controlPath || config[spec.pathKey] || "",
   }
   }
+}
+
+function getExePath () {
+  const controller = getController()
+  const exePath = controller.dir ? path.join(controller.dir, controller.exe) : ""
 
 
   console.log('ex============ePath');
   console.log('ex============ePath');
   console.log(exePath);
   console.log(exePath);
   return exePath
   return exePath
 }
 }
 
 
+// 用户手动选安装目录时,按目录里实际存在的 exe 识别,5 和 6 互不覆盖
+function detectController(dir) {
+  if (fs.existsSync(path.join(dir, CONTROLLERS.SmartShooter6.exe))) return "SmartShooter6"
+  if (fs.existsSync(path.join(dir, CONTROLLERS.SmartShooter.exe))) return "SmartShooter"
+  if (fs.existsSync(path.join(dir, CONTROLLERS.digiCamControl.exe))) return "digiCamControl"
+  return ""
+}
+
 
 
 
 
 
 
@@ -61,27 +99,29 @@ async function checkCameraControlCmdExists() {
       if (!canceled && filePaths.length > 0) {
       if (!canceled && filePaths.length > 0) {
         const selectedPath = filePaths[0];
         const selectedPath = filePaths[0];
 
 
-        // Check if SmartShooter5.exe exists in the selected directory
-        const hasExe = path.join(selectedPath, exe["SmartShooter"]);
-
-        if (fs.existsSync(hasExe)) {
-          writeConfigFile("controlType","SmartShooter")
-        }else{
-          writeConfigFile("controlType","digiCamControl")
+        const detected = detectController(selectedPath);
+        if (!detected) {
+          console.error('所选目录不是已知的相机控制软件');
+          return {
+            status:-1,
+            msg:"所选目录中未找到 SmartShooter6.exe、SmartShooter5.exe 或 CameraControl.exe",
+          }
         }
         }
-        writeConfigFile("controlPath",selectedPath)
+        writeConfigFile("controlType", detected)
+        writeConfigFile("controlPath", selectedPath)
+        writeConfigFile(CONTROLLERS[detected].pathKey, selectedPath)
       } else {
       } else {
         console.error('用户未选择文件夹');
         console.error('用户未选择文件夹');
         return {
         return {
           status:-1,
           status:-1,
-          msg:"无法找到 CameraControlCmd.exe 或者  SmartShooter5.exe",
+          msg:"无法找到 SmartShooter6.exe、SmartShooter5.exe 或 CameraControl.exe",
         }
         }
       }
       }
     }
     }
     const res = await openCameraControlCmd();
     const res = await openCameraControlCmd();
     
     
-    // 如果是SmartShooter,启动后最小化窗口
-    if(readConfigFile().controlType === 'SmartShooter'){
+    // Smart Shooter 5 / 6 启动后最小化窗口;digiCamControl 走自己的最小化命令
+    if(isSmartShooterType(getController().type)){
       // 等待软件启动完成
       // 等待软件启动完成
       setTimeout(async () => {
       setTimeout(async () => {
         try {
         try {
@@ -167,6 +207,9 @@ module.exports = {
   checkCameraControlCmdExists,
   checkCameraControlCmdExists,
   closeCameraControlTips,
   closeCameraControlTips,
   minimizeSmartShooter,
   minimizeSmartShooter,
+  CONTROLLERS,
+  isSmartShooterType,
+  getController,
   // 进程状态管理
   // 进程状态管理
   isProcessRunning: () => isProcessRunning,
   isProcessRunning: () => isProcessRunning,
   onProcessClosed: (callback) => processEmitter.on('camera-process-closed', callback),
   onProcessClosed: (callback) => processEmitter.on('camera-process-closed', callback),

+ 3 - 2
electron/utils/config.default.json

@@ -1,9 +1,10 @@
 {
 {
   "debug":false,
   "debug":false,
-  "controlType": "SmartShooter",
-  "controlPath": "C:\\Program Files\\Smart Shooter 5",
+  "controlType": "SmartShooter6",
+  "controlPath": "C:\\Program Files\\Smart Shooter 6",
   "digiCamControlPath":"C:\\Program Files (x86)\\digiCamControl",
   "digiCamControlPath":"C:\\Program Files (x86)\\digiCamControl",
   "SmartShooterPath":"C:\\Program Files\\Smart Shooter 5",
   "SmartShooterPath":"C:\\Program Files\\Smart Shooter 5",
+  "SmartShooter6Path":"C:\\Program Files\\Smart Shooter 6",
   "pyapp": "127.0.0.1",
   "pyapp": "127.0.0.1",
   "remoteUrl": false,
   "remoteUrl": false,
   "PORT": "3001",
   "PORT": "3001",

+ 28 - 4
electron/utils/config.js

@@ -7,6 +7,25 @@ const defaultConfig = require('./config.default');
 
 
 const configPath = path.join(app.getPath("userData"),'config.default.json');
 const configPath = path.join(app.getPath("userData"),'config.default.json');
 
 
+// Smart Shooter 5 的历史默认安装目录。用户目录里的旧配置会盖掉代码默认值,
+// 只有仍停在这套出厂值时才自动切到 6;手动选过别的目录则保持不动。
+const LEGACY_SMARTSHOOTER5 = {
+  controlType: "SmartShooter",
+  controlPath: "C:\\Program Files\\Smart Shooter 5",
+}
+
+function migrateLegacySmartShooter(config) {
+  if (!config || config.smartShooter6Migrated) return { config, changed: false }
+  const stillFactoryDefault = config.controlType === LEGACY_SMARTSHOOTER5.controlType
+    && config.controlPath === LEGACY_SMARTSHOOTER5.controlPath
+  config.smartShooter6Migrated = true
+  if (!stillFactoryDefault) return { config, changed: true }
+  config.controlType = defaultConfig.controlType
+  config.controlPath = defaultConfig.controlPath
+  config.SmartShooterPath = config.SmartShooterPath || LEGACY_SMARTSHOOTER5.controlPath
+  config.SmartShooter6Path = defaultConfig.SmartShooter6Path
+  return { config, changed: true }
+}
 
 
 /**
 /**
  * 读取配置文件
  * 读取配置文件
@@ -16,11 +35,16 @@ function readConfigFile() {
   try {
   try {
     if (fs.existsSync(configPath)) {
     if (fs.existsSync(configPath)) {
       const data = fs.readFileSync(configPath, 'utf8');
       const data = fs.readFileSync(configPath, 'utf8');
-
-      return {
+      const parsed = JSON.parse(data);
+      const { config: merged, changed } = migrateLegacySmartShooter({
         ...defaultConfig,
         ...defaultConfig,
-        ...JSON.parse(data),
-      };
+        ...parsed,
+      });
+      if (changed) {
+        fs.writeFileSync(configPath, JSON.stringify(merged, null, 2), 'utf8');
+      }
+
+      return merged;
     } else {
     } else {
       console.log('配置文件不存在');
       console.log('配置文件不存在');
       // 创建空白JSON文件
       // 创建空白JSON文件

+ 1 - 1
frontend/src/views/Photography/check.vue

@@ -513,7 +513,7 @@ clientStore.ipc.on(icpList.socket.message+'_run_mcu_single', async (event, resul
 const exampleImage = ref('https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400')
 const exampleImage = ref('https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400')
 onMounted(async ()=>{
 onMounted(async ()=>{
   await  configInfoStore.getAppConfig()
   await  configInfoStore.getAppConfig()
-  if(configInfoStore.appConfig.controlType === "SmartShooter"){
+  if(configInfoStore.appConfig.controlType === "SmartShooter" || configInfoStore.appConfig.controlType === "SmartShooter6"){
     preview.value = configInfoStore.appConfig.userDataPath  + "\\preview\\liveview.png"
     preview.value = configInfoStore.appConfig.userDataPath  + "\\preview\\liveview.png"
   }
   }
   exampleImage.value =  configInfoStore.appConfig.exampleImage || 'https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400'
   exampleImage.value =  configInfoStore.appConfig.exampleImage || 'https://huilimaimg.cnhqt.com/frontend/zhihuiyin/demo.jpg?x-oss-process=image/resize,w_400'