diff --git a/.gitignore b/.gitignore
index 50c73fa..1d74a0e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,4 @@ DebuggerCmd.md
# Generated Files
*.sqlite
+components.d.ts
diff --git a/components.d.ts b/components.d.ts
deleted file mode 100644
index 6ba3abd..0000000
--- a/components.d.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/* eslint-disable */
-// @ts-nocheck
-// Generated by unplugin-vue-components
-// Read more: https://github.com/vuejs/core/pull/3399
-// biome-ignore lint: disable
-export {}
-
-/* prettier-ignore */
-declare module 'vue' {
- export interface GlobalComponents {
- Alert: typeof import('./src/components/Alert/Alert.vue')['default']
- BaseBoard: typeof import('./src/components/equipments/BaseBoard.vue')['default']
- BaseInputField: typeof import('./src/components/InputField/BaseInputField.vue')['default']
- CollapsibleSection: typeof import('./src/components/CollapsibleSection.vue')['default']
- ComponentSelector: typeof import('./src/components/LabCanvas/ComponentSelector.vue')['default']
- DDR: typeof import('./src/components/equipments/DDR.vue')['default']
- DDS: typeof import('./src/components/equipments/DDS.vue')['default']
- DDSPropertyEditor: typeof import('./src/components/equipments/DDSPropertyEditor.vue')['default']
- DiagramCanvas: typeof import('./src/components/LabCanvas/DiagramCanvas.vue')['default']
- Dialog: typeof import('./src/components/Dialog.vue')['default']
- ETH: typeof import('./src/components/equipments/ETH.vue')['default']
- HDMI: typeof import('./src/components/equipments/HDMI.vue')['default']
- IpInputField: typeof import('./src/components/InputField/IpInputField.vue')['default']
- ItemList: typeof import('./src/components/LabCanvas/ItemList.vue')['default']
- LogicalWaveFormDisplay: typeof import('./src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue')['default']
- MarkdownRenderer: typeof import('./src/components/MarkdownRenderer.vue')['default']
- MechanicalButton: typeof import('./src/components/equipments/MechanicalButton.vue')['default']
- MotherBoard: typeof import('./src/components/equipments/MotherBoard.vue')['default']
- MotherBoardCaps: typeof import('./src/components/equipments/MotherBoardCaps.vue')['default']
- Navbar: typeof import('./src/components/Navbar.vue')['default']
- OscilloscopeWaveformDisplay: typeof import('./src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue')['default']
- PG2L100H_FBG676: typeof import('./src/components/equipments/PG2L100H_FBG676.vue')['default']
- Pin: typeof import('./src/components/equipments/Pin.vue')['default']
- PopButton: typeof import('./src/components/PopButton.vue')['default']
- PortInputField: typeof import('./src/components/InputField/PortInputField.vue')['default']
- PropertyEditor: typeof import('./src/components/PropertyEditor.vue')['default']
- PropertyPanel: typeof import('./src/components/PropertyPanel.vue')['default']
- RouterLink: typeof import('vue-router')['RouterLink']
- RouterView: typeof import('vue-router')['RouterView']
- SD: typeof import('./src/components/equipments/SD.vue')['default']
- SevenSegmentDisplay: typeof import('./src/components/equipments/SevenSegmentDisplay.vue')['default']
- SFP: typeof import('./src/components/equipments/SFP.vue')['default']
- Sidebar: typeof import('./src/components/Sidebar.vue')['default']
- SMA: typeof import('./src/components/equipments/SMA.vue')['default']
- SMT_LED: typeof import('./src/components/equipments/SMT_LED.vue')['default']
- Switch: typeof import('./src/components/equipments/Switch.vue')['default']
- ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default']
- ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default']
- TriggerSettings: typeof import('./src/components/LogicAnalyzer/TriggerSettings.vue')['default']
- TutorialCarousel: typeof import('./src/components/TutorialCarousel.vue')['default']
- UploadCard: typeof import('./src/components/UploadCard.vue')['default']
- WaveformDisplay: typeof import('./src/components/WaveformDisplay/WaveformDisplay.vue')['default']
- Wire: typeof import('./src/components/equipments/Wire.vue')['default']
- }
-}
diff --git a/server/server.csproj b/server/server.csproj
index 59e8975..f138d5c 100644
--- a/server/server.csproj
+++ b/server/server.csproj
@@ -26,6 +26,8 @@
+
+
diff --git a/server/src/Services/HttpVideoStreamService.cs b/server/src/Services/HttpVideoStreamService.cs
index 3fec4ad..88b1fe2 100644
--- a/server/src/Services/HttpVideoStreamService.cs
+++ b/server/src/Services/HttpVideoStreamService.cs
@@ -1,8 +1,14 @@
+#define USB_CAMERA
+
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Peripherals.CameraClient; // 添加摄像头客户端引用
+#if USB_CAMERA
+using OpenCvSharp;
+#endif
+
namespace server.Services;
///
@@ -82,7 +88,7 @@ public class HttpVideoStreamService : BackgroundService
private HttpListener? _httpListener;
private readonly int _serverPort = 8080;
private readonly int _frameRate = 30; // 30 FPS
-
+
// 动态分辨率配置
private int _frameWidth = 640; // 默认640x480
private int _frameHeight = 480;
@@ -95,6 +101,13 @@ public class HttpVideoStreamService : BackgroundService
private int _cameraPort = 8888; // 默认端口
private readonly object _cameraLock = new object();
+ // USB Camera 相关
+#if USB_CAMERA
+ private VideoCapture? _usbCamera;
+ private bool _usbCameraEnable = false;
+ private readonly object _usbCameraLock = new object();
+#endif
+
// 模拟 FPGA 图像数据
private int _frameCounter = 0;
private readonly List _activeClients = new List();
@@ -346,9 +359,16 @@ public class HttpVideoStreamService : BackgroundService
if (requestPath == "/video-stream")
{
- // MJPEG 流请求
+ // MJPEG 流请求(FPGA)
_ = Task.Run(() => HandleMjpegStreamAsync(response, cancellationToken), cancellationToken);
}
+#if USB_CAMERA
+ else if (requestPath == "/usb-camera")
+ {
+ // USB Camera MJPEG流请求
+ _ = Task.Run(() => HandleUsbCameraStreamAsync(response, cancellationToken), cancellationToken);
+ }
+#endif
else if (requestPath == "/snapshot")
{
// 单帧图像请求
@@ -382,6 +402,87 @@ public class HttpVideoStreamService : BackgroundService
}
}
+ // USB Camera MJPEG流处理
+#if USB_CAMERA
+ private async Task HandleUsbCameraStreamAsync(HttpListenerResponse response, CancellationToken cancellationToken)
+ {
+ try
+ {
+ lock (_usbCameraLock)
+ {
+ if (_usbCamera == null)
+ {
+ _usbCamera = new VideoCapture(0);
+ _usbCamera.Fps = _frameRate;
+ _usbCamera.FrameWidth = _frameWidth;
+ _usbCamera.FrameHeight = _frameHeight;
+ _usbCameraEnable = _usbCamera.IsOpened();
+ }
+ }
+ if (!_usbCameraEnable || _usbCamera == null || !_usbCamera.IsOpened())
+ {
+ response.StatusCode = 500;
+ await response.OutputStream.FlushAsync(cancellationToken);
+ response.Close();
+ return;
+ }
+
+ response.ContentType = "multipart/x-mixed-replace; boundary=--boundary";
+ response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.Headers.Add("Pragma", "no-cache");
+ response.Headers.Add("Expires", "0");
+
+ using (var mat = new Mat())
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ bool grabbed;
+ lock (_usbCameraLock)
+ {
+ grabbed = _usbCamera.Read(mat);
+ }
+ if (!grabbed || mat.Empty())
+ {
+ await Task.Delay(50, cancellationToken);
+ continue;
+ }
+
+ // 编码为JPEG
+ byte[]? jpegData = null;
+ try
+ {
+ jpegData = mat.ToBytes(".jpg", new int[] { (int)ImwriteFlags.JpegQuality, 80 });
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, "USB Camera帧编码JPEG失败");
+ continue;
+ }
+ if (jpegData == null)
+ continue;
+
+ // MJPEG帧头
+ var header = Encoding.ASCII.GetBytes("--boundary\r\nContent-Type: image/jpeg\r\nContent-Length: " + jpegData.Length + "\r\n\r\n");
+ await response.OutputStream.WriteAsync(header, 0, header.Length, cancellationToken);
+ await response.OutputStream.WriteAsync(jpegData, 0, jpegData.Length, cancellationToken);
+ await response.OutputStream.WriteAsync(new byte[] { 0x0D, 0x0A }, 0, 2, cancellationToken); // \r\n
+ await response.OutputStream.FlushAsync(cancellationToken);
+
+ await Task.Delay(1000 / _frameRate, cancellationToken);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, "USB Camera MJPEG流处理异常");
+ }
+ finally
+ {
+ try { response.Close(); } catch { }
+ }
+ }
+#endif
+
private async Task HandleMjpegStreamAsync(HttpListenerResponse response, CancellationToken cancellationToken)
{
try
@@ -1046,9 +1147,9 @@ public class HttpVideoStreamService : BackgroundService
}
logger.Info("开始初始化摄像头自动对焦功能");
-
+
var result = await _camera!.InitAutoFocus();
-
+
if (result.IsSuccessful && result.Value)
{
logger.Info("摄像头自动对焦功能初始化成功");
@@ -1085,9 +1186,9 @@ public class HttpVideoStreamService : BackgroundService
}
logger.Info("开始执行摄像头自动对焦");
-
+
var result = await _camera!.PerformAutoFocus();
-
+
if (result.IsSuccessful && result.Value)
{
logger.Info("摄像头自动对焦执行成功");