فهرست منبع

完成webui的相机操作页面全部功能开发

niujiuru 2 هفته پیش
والد
کامیت
adeaa4bb11

+ 42 - 0
web-ui.service/page5_handler.go

@@ -212,11 +212,53 @@ func cameraGetImageHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	if file != "/tmp/camera_preview_0.jpg" && file != "/tmp/camera_preview_1.jpg" {
+		http.Error(w, "invalid file", http.StatusBadRequest)
+		return
+	}
+
 	if _, err := os.Stat(file); err != nil {
 		http.Error(w, err.Error(), http.StatusNotFound)
 		return
 	}
 
 	w.Header().Set("Content-Type", "image/jpeg")
+
+	if r.URL.Query().Get("download") == "1" {
+		w.Header().Set(
+			"Content-Disposition",
+			`attachment; filename="camera.jpg"`,
+		)
+	}
+
 	http.ServeFile(w, r, file)
 }
+
+func setWaitAEDoneForTakePhoto(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodPost {
+		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	var req struct {
+		WaitAE string `json:"waitAE"`
+	}
+
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	var result any
+
+	if err := callRPCResult(r.Context(), 7003, "core.setWaitAEDoneForTakePhoto",
+		map[string]string{"waitAE": req.WaitAE}, &result); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(map[string]bool{
+		"ok": true,
+	})
+}

+ 2 - 2
web-ui.service/web/pages/page2.html

@@ -80,9 +80,9 @@
               <span style="margin:0 1px;"></span>
               <button class="btn" title="清除当前屏显日志" onclick="clearLog('text4')">清除</button>
               <span style="margin:0 1px;"></span>
-              <button class="btn" title="下载服务日志文件" onclick="downloadNetLogFile()">下载</button>
+              <button class="btn" title="下载服务日志文件" onclick="downloadNetLogFile(event)">下载</button>
               <span style="margin:0 1px;"></span>
-              <button class="btn" title="点击重启联网服务" onclick="restartNetService()">重启</button>
+              <button class="btn" title="点击重启联网服务" onclick="restartNetService(event)">重启</button>
             </div>
           </div>
           <div class="output" id="text4">自动实时获取数据...</div>

+ 2 - 2
web-ui.service/web/pages/page3.html

@@ -58,9 +58,9 @@
             <span style="margin:0 1px;"></span>
             <button class="btn" title="清除当前屏显日志" onclick="clearLog('text1')">清除</button>
             <span style="margin:0 1px;"></span>
-            <button class="btn" title="下载服务日志文件" onclick="downloadTimeLogFile()">下载</button>
+            <button class="btn" title="下载服务日志文件" onclick="downloadTimeLogFile(event)">下载</button>
             <span style="margin:0 1px;"></span>
-            <button class="btn" title="点击重启时间服务" onclick="restartTimeService()">重启</button>
+            <button class="btn" title="点击重启时间服务" onclick="restartTimeService(event)">重启</button>
           </div>
         </div>
         <div class="output" id="text1">自动实时获取数据...</div>

+ 2 - 2
web-ui.service/web/pages/page4.html

@@ -50,9 +50,9 @@
             <span style="margin:0 1px;"></span>
             <button class="btn" title="清除当前屏显日志" onclick="clearLog('text1')">清除</button>
             <span style="margin:0 1px;"></span>
-            <button class="btn" title="下载服务日志文件" onclick="downloadGnssLogFile()">下载</button>
+            <button class="btn" title="下载服务日志文件" onclick="downloadGnssLogFile(event)">下载</button>
             <span style="margin:0 1px;"></span>
-            <button class="btn" title="点击重启时间服务" onclick="restartGnssService()">重启</button>
+            <button class="btn" title="点击重启时间服务" onclick="restartGnssService(event)">重启</button>
           </div>
         </div>
         <div class="output" id="text1">自动实时获取数据...</div>

+ 5 - 5
web-ui.service/web/pages/page5.html

@@ -30,7 +30,7 @@
           <span class="label">大华数量</span>
           <span class="value" id="avar4">--</span>
         </div>
-        <div class="status-item button-item"">
+        <div class="status-item button-item">
           <span style="margin:0 1px;"></span>
           <button class="btn" title="点击刷新最新数据" id="btn1" onclick="getCameraStatus()">数据刷新</button>
         </div>
@@ -42,14 +42,14 @@
           <span class="value">192.168.100.100/24</span>
         </div>
         <div class="status-item">
-          <span class="label">供电开关</span>
+          <span class="label">GPIO22供电开关</span>
           <span class="value" id="avar5">--</span>
         </div>
         <div class="status-item">
           <span class="label">连接状态</span>
           <span class="value" id="avar6">--</span>
         </div>
-        <div class="status-item button-item"">
+        <div class="status-item button-item">
           <span style="margin:0 1px;"></span>
           <button class="btn" title="开启GPIO22供电控制" onclick="cameraPower('true')">上电</button>
           <span style="margin:0 1px;"></span>
@@ -119,9 +119,9 @@
               <span style="margin:0 1px;"></span>
               <button class="btn" title="清除当前屏显日志" onclick="clearLog('text1')">清除</button>
               <span style="margin:0 1px;"></span>
-              <button class="btn" title="下载服务日志文件" onclick="downloadCameraLogFile()">下载</button>
+              <button class="btn" title="下载服务日志文件" onclick="downloadCameraLogFile(event)">下载</button>
               <span style="margin:0 1px;"></span>
-              <button class="btn" title="点击重启相机服务" onclick="restartCameraService()">重启</button>
+              <button class="btn" title="点击重启相机服务" onclick="restartCameraService(event)">重启</button>
             </div>
           </div>
           <div class="output" id="text1">自动实时获取数据...</div>

+ 2 - 2
web-ui.service/web/static/js/page2.js

@@ -92,7 +92,7 @@ function getNetLogLevel() {
   });
 }
 
-function downloadNetLogFile() {
+function downloadNetLogFile(event) {
   const btn = event.currentTarget;
   btn.disabled = true;
 
@@ -143,7 +143,7 @@ function downloadNetLogFile() {
   });
 }
 
-function restartNetService() {
+function restartNetService(event) {
   if (!confirm("确定重启联网服务吗?")) {
     return;
   }

+ 2 - 2
web-ui.service/web/static/js/page3.js

@@ -32,7 +32,7 @@ function getTimeLogLevel() {
   });
 }
 
-function downloadTimeLogFile() {
+function downloadTimeLogFile(event) {
   const btn = event.currentTarget;
   btn.disabled = true;
 
@@ -83,7 +83,7 @@ function downloadTimeLogFile() {
   });
 }
 
-function restartTimeService() {
+function restartTimeService(event) {
   if (!confirm("确定重启时间服务吗?")) {
     return;
   }

+ 2 - 2
web-ui.service/web/static/js/page4.js

@@ -32,7 +32,7 @@ function getGnssLogLevel() {
   });
 }
 
-function downloadGnssLogFile() {
+function downloadGnssLogFile(event) {
   const btn = event.currentTarget;
   btn.disabled = true;
 
@@ -83,7 +83,7 @@ function downloadGnssLogFile() {
   });
 }
 
-function restartGnssService() {
+function restartGnssService(event) {
   if (!confirm("确定重启时间服务吗?")) {
     return;
   }

+ 137 - 13
web-ui.service/web/static/js/page5.js

@@ -32,7 +32,7 @@ function getCameraLogLevel() {
   });
 }
 
-function downloadCameraLogFile() {
+function downloadCameraLogFile(event) {
   const btn = event.currentTarget;
   btn.disabled = true;
 
@@ -83,12 +83,12 @@ function downloadCameraLogFile() {
   });
 }
 
-function restartCameraService() {
+function restartCameraService(event) {
   if (!confirm("确定重启相机服务吗?")) {
     return;
   }
 
-  if (cameraTakingPhoto) {
+  if (cameraTakingPhoto || cameraPreviewRunning) {
     alert("相机使用中,请稍后再重试!");
     return;
   }
@@ -141,8 +141,7 @@ function getCameraStatus() {
         .forEach(id => setText(id, null, "page5"));
     })
     .finally(() => {
-      ["btn1", "btn2", "btn3", "btn4"]
-      .forEach(id => setBtnStatus(id, true, "page5"));
+      updateCameraButtons();
     });
 
   getCameraLogLevel();
@@ -194,15 +193,24 @@ function cameraPing() {
 
 let cameraPreviewTimer = null;
 let cameraPreviewRunning = false;
+let cameraPreviewFrames = 0;
 let cameraPhotoIndex = 0;
 let cameraCurrentFile = "";
-let cameraTakingPhoto = false; 
+let cameraTakingPhoto = false;
 
 const cameraPhotoFiles = [
   "/tmp/camera_preview_0.jpg",
   "/tmp/camera_preview_1.jpg"
 ];
 
+function updateCameraButtons() {
+  const normalEnable = !cameraPreviewRunning && !cameraTakingPhoto;
+  ["btn1", "btn2", "btn3"]
+  .forEach(id => setBtnStatus(id, normalEnable, "page5"));
+
+  setBtnStatus("btn4", cameraPreviewRunning, "page5");
+}
+
 async function cameraTakePhoto(filename) {
   const r = await fetch("/api/camera/takePhoto", {
     method: "POST",
@@ -226,13 +234,18 @@ function setImageSrc(id, url)
 }
 
 async function takeOnePhoto() {
-  if (cameraTakingPhoto) {
+  if (cameraPreviewRunning || cameraTakingPhoto) {
     return;
   }
-  cameraTakingPhoto = true;
 
-  ["btn1", "btn2", "btn3", "btn4"]
-  .forEach(id => setBtnStatus(id, false, "page5"));
+  try {
+    await setCameraWaitAEDone(true);
+  } catch(e) {
+    console.error("open wait AE failed:", e);
+  }
+
+  cameraTakingPhoto = true;
+  updateCameraButtons();
 
   ["cvar1", "cvar2", "cvar3", "cvar4", "cvar5", "cvar6"]
   .forEach(id => setText(id, null, "page5"));
@@ -259,19 +272,130 @@ async function takeOnePhoto() {
     setText("cvar1", "拍照失败", "page5");
   } finally {
     cameraTakingPhoto = false;
-    ["btn1", "btn2", "btn3", "btn4"]
-    .forEach(id => setBtnStatus(id, true, "page5"));
+    updateCameraButtons();
+  }
+}
+
+async function setCameraWaitAEDone(waitAE) {
+  const r = await fetch("/api/camera/setWaitAEDoneForTakePhoto", {
+    method:"POST",
+    headers:{
+      "Content-Type":"application/json"
+    },
+    body:JSON.stringify({
+      waitAE: waitAE ? "1" : "0"
+    })
+  });
+  if (!r.ok) {
+    throw new Error(await r.text());
+  }
+}
+
+async function startCameraPreview() {
+  if (cameraPreviewRunning || cameraTakingPhoto) {
+    return;
+  }
+
+  try {
+    await setCameraWaitAEDone(false);
+  } catch(e) {
+    console.error("close wait AE failed:", e);
+  }
+
+  cameraPreviewRunning = true;
+  updateCameraButtons();
+  cameraPreviewFrames  = 0;
+
+  ["cvar1", "cvar2", "cvar3", "cvar4", "cvar5", "cvar6"]
+  .forEach(id => setText(id, null, "page5"));
+
+  cameraPreviewLoop();
+}
+
+async function cameraPreviewLoop() {
+  if (!cameraPreviewRunning) {
+    return;
+  }
+
+  cameraTakingPhoto = true;
+  updateCameraButtons();
+  setText("cvar1", "拍照中, 请稍后...", "page5");
+  const filename = cameraPhotoFiles[cameraPhotoIndex];
+  cameraPhotoIndex = cameraPhotoIndex ? 0 : 1;
+
+  try {
+    const data = await cameraTakePhoto(filename);
+    if (!data.ok) throw new Error("take photo failed");
+
+    cameraCurrentFile = filename;
+    cameraPreviewFrames++;
+    setImageSrc("camera-image", "/api/camera/getImage?file=" + encodeURIComponent(filename) + "&t=" + Date.now());
+    setText("cvar1", "拍照完成", "page5");
+    setText("cvar2", String(cameraPreviewFrames), "page5");
+    setText("cvar3", data.var1, "page5");
+    setText("cvar4", data.var2, "page5");
+    setText("cvar5", data.var3, "page5");
+    setText("cvar6", data.var4 + "x" + data.var5, "page5");
+  } catch(e) {
+    console.error("cameraPreviewLoop failed:", e);
+    stopCameraPreview();
+    setText("cvar1", "拍照失败", "page5");
+  } finally {
+    cameraTakingPhoto = false;
+    updateCameraButtons();
+    if (cameraPreviewRunning) {
+      cameraPreviewTimer = setTimeout(cameraPreviewLoop, 20);
+    } else {
+      setCameraWaitAEDone(true)
+      .catch(e => {
+        console.error("restore wait AE failed:", e);
+      });
+    }
+  }
+}
+
+function stopCameraPreview() {
+  if(!cameraPreviewRunning) {
+    return;
   }
+  cameraPreviewRunning = false;
+  updateCameraButtons();
+  if (cameraPreviewTimer) {
+    clearTimeout(cameraPreviewTimer);
+    cameraPreviewTimer = null;
+  }
+  setText("cvar1", "停止中, 请稍后...", "page5");
+}
+
+function downloadCameraImage() {
+  if (!cameraCurrentFile) {
+    alert("暂无图片");
+    return;
+  }
+
+  const a = document.createElement("a");
+  a.href  = "/api/camera/getImage?file=" + encodeURIComponent(cameraCurrentFile) + "&download=1";
+  a.download = "camera.jpg";
+
+  document.body.appendChild(a);
+  a.click();
+  a.remove();
 }
 
 function initPage5() {
   getCameraStatus();
-  if (cameraTakingPhoto) {
+  if (cameraCurrentFile) {
+    setImageSrc("camera-image", "/api/camera/getImage?file=" + encodeURIComponent(cameraCurrentFile) + "&t=" + Date.now());
+  }
+  if (cameraTakingPhoto || cameraPreviewRunning) {
     setText("cvar1", "拍照中, 请稍后...", "page5");
   }
   connectLog("yfkj-camera-capture.service", "text1");
 }
 
 function exitPage5() {
+  if (cameraPreviewRunning) {
+    stopCameraPreview();
+  }
   disconnectLog();
 }

+ 5 - 0
web-ui.service/web_route.go

@@ -169,6 +169,11 @@ var routes = []Route{
 	},
 
 	{
+		"/api/camera/setWaitAEDoneForTakePhoto",
+		setWaitAEDoneForTakePhoto,
+	},
+
+	{
 		"/api/ssh/ws",
 		sshWSHandler,
 	},