diff --git a/server/src/Controllers/TutorialController.cs b/server/src/Controllers/TutorialController.cs index f5ffb01..d2c092a 100644 --- a/server/src/Controllers/TutorialController.cs +++ b/server/src/Controllers/TutorialController.cs @@ -14,6 +14,11 @@ public class TutorialController : ControllerBase private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private readonly IWebHostEnvironment _environment; + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] public TutorialController(IWebHostEnvironment environment) { _environment = environment; diff --git a/server/src/Controllers/UDPController.cs b/server/src/Controllers/UDPController.cs index ac090a0..db39d03 100644 --- a/server/src/Controllers/UDPController.cs +++ b/server/src/Controllers/UDPController.cs @@ -109,6 +109,7 @@ public class UDPController : ControllerBase /// 获取指定IP地址接收的数据列表 /// /// IP地址 + /// 任务ID [HttpGet("GetRecvDataArray")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] diff --git a/server/src/Controllers/VideoStreamController.cs b/server/src/Controllers/VideoStreamController.cs index 9a70219..30e1c4c 100644 --- a/server/src/Controllers/VideoStreamController.cs +++ b/server/src/Controllers/VideoStreamController.cs @@ -1,6 +1,6 @@ +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; /// /// 视频流控制器,支持动态配置摄像头连接 @@ -17,10 +17,16 @@ public class VideoStreamController : ControllerBase /// public class CameraConfigRequest { + /// + /// 摄像头地址 + /// [Required] [RegularExpression(@"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", ErrorMessage = "请输入有效的IP地址")] public string Address { get; set; } = ""; + /// + /// 摄像头端口 + /// [Required] [Range(1, 65535, ErrorMessage = "端口必须在1-65535范围内")] public int Port { get; set; } @@ -54,21 +60,11 @@ public class VideoStreamController : ControllerBase var status = _videoStreamService.GetServiceStatus(); // 转换为小写首字母的JSON属性(符合前端惯例) - return TypedResults.Ok(new - { - isRunning = true, // HTTP视频流服务作为后台服务始终运行 - serverPort = _videoStreamService.ServerPort, - streamUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-feed.html", - mjpegUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-stream", - snapshotUrl = $"http://localhost:{_videoStreamService.ServerPort}/snapshot", - connectedClients = _videoStreamService.ConnectedClientsCount, - clientEndpoints = _videoStreamService.GetConnectedClientEndpoints(), - cameraStatus = _videoStreamService.GetCameraStatus() - }); + return TypedResults.Ok(status); } catch (Exception ex) { - logger.Error(ex, "获取 HTTP 视频流服务状态失败"); + logger.Error(ex, "获取 HTTP 视频流服务状态失败"); return TypedResults.InternalServerError(ex.Message); } } @@ -95,13 +91,11 @@ public class VideoStreamController : ControllerBase htmlUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-feed.html", mjpegUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-stream", snapshotUrl = $"http://localhost:{_videoStreamService.ServerPort}/snapshot", - cameraAddress = _videoStreamService.CameraAddress, - cameraPort = _videoStreamService.CameraPort }); } catch (Exception ex) { - logger.Error(ex, "获取 HTTP 视频流信息失败"); + logger.Error(ex, "获取 HTTP 视频流信息失败"); return TypedResults.InternalServerError(ex.Message); } } @@ -123,7 +117,7 @@ public class VideoStreamController : ControllerBase logger.Info("配置摄像头连接: {Address}:{Port}", config.Address, config.Port); var success = await _videoStreamService.ConfigureCameraAsync(config.Address, config.Port); - + if (success) { return TypedResults.Ok(new @@ -166,7 +160,7 @@ public class VideoStreamController : ControllerBase { logger.Info("获取摄像头配置"); var cameraStatus = _videoStreamService.GetCameraStatus(); - + return TypedResults.Ok(new { address = _videoStreamService.CameraAddress, @@ -183,35 +177,23 @@ public class VideoStreamController : ControllerBase } /// - /// 测试摄像头连接 + /// 控制 HTTP 视频流服务开关 /// - /// 连接测试结果 - [HttpPost("TestCameraConnection")] + /// 是否启用服务 + /// 操作结果 + [HttpPost("SetEnabled")] [EnableCors("Users")] [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] - public async Task TestCameraConnection() + public IResult SetEnabled([FromQuery] bool enabled) { - try + logger.Info("设置视频流服务开关: {Enabled}", enabled); + _videoStreamService.Enabled = enabled; + return TypedResults.Ok(new { - logger.Info("测试摄像头连接"); - - var (isSuccess, message) = await _videoStreamService.TestCameraConnectionAsync(); - - return TypedResults.Ok(new - { - success = isSuccess, - message = message, - cameraAddress = _videoStreamService.CameraAddress, - cameraPort = _videoStreamService.CameraPort, - timestamp = DateTime.Now - }); - } - catch (Exception ex) - { - logger.Error(ex, "测试摄像头连接失败"); - return TypedResults.InternalServerError(ex.Message); - } + success = true, + enabled = _videoStreamService.Enabled + }); } /// @@ -229,16 +211,29 @@ public class VideoStreamController : ControllerBase logger.Info("测试 HTTP 视频流连接"); // 尝试通过HTTP请求检查视频流服务是否可访问 + bool isConnected = false; using (var httpClient = new HttpClient()) { httpClient.Timeout = TimeSpan.FromSeconds(2); // 设置较短的超时时间 var response = await httpClient.GetAsync($"http://localhost:{_videoStreamService.ServerPort}/"); // 只要能连接上就认为成功,不管返回状态 - bool isConnected = response.IsSuccessStatusCode; - - return TypedResults.Ok(isConnected); + isConnected = response.IsSuccessStatusCode; } + + logger.Info("测试摄像头连接"); + + var (isSuccess, message) = await _videoStreamService.TestCameraConnectionAsync(); + + return TypedResults.Ok(new + { + isConnected = isConnected, + success = isSuccess, + message = message, + cameraAddress = _videoStreamService.CameraAddress, + cameraPort = _videoStreamService.CameraPort, + timestamp = DateTime.Now + }); } catch (Exception ex) { diff --git a/server/src/Peripherals/CameraClient.cs b/server/src/Peripherals/CameraClient.cs index 89bfc9b..9dffbee 100644 --- a/server/src/Peripherals/CameraClient.cs +++ b/server/src/Peripherals/CameraClient.cs @@ -38,7 +38,7 @@ class Camera public async ValueTask> Init() { var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.timeout); - var ret = await i2c.WriteData(0x78, new byte[] { 0x08, 0x30, 0x02 }, Peripherals.I2cClient.I2cProtocol.I2c); + var ret = await i2c.WriteData(0x78, new byte[] { 0x30, 0x08, 0x02 }, Peripherals.I2cClient.I2cProtocol.I2c); if (!ret.IsSuccessful) { logger.Error($"I2C write failed during camera initialization for {this.address}:{this.port}, error: {ret.Error}"); diff --git a/server/src/Services/HttpVideoStreamService.cs b/server/src/Services/HttpVideoStreamService.cs index 803a8ee..0969e89 100644 --- a/server/src/Services/HttpVideoStreamService.cs +++ b/server/src/Services/HttpVideoStreamService.cs @@ -4,6 +4,73 @@ using Peripherals.CameraClient; // 添加摄像头客户端引用 namespace server.Services; +/// +/// 表示摄像头连接状态信息 +/// +public class CameraStatus +{ + /// + /// 摄像头的IP地址 + /// + public string Address { get; set; } = string.Empty; + + /// + /// 摄像头的端口号 + /// + public int Port { get; set; } + + /// + /// 是否已配置摄像头 + /// + public bool IsConfigured { get; set; } + + /// + /// 摄像头连接字符串(IP:端口) + /// + public string ConnectionString { get; set; } = string.Empty; +} + +/// +/// 表示视频流服务的运行状态 +/// +public class ServiceStatus +{ + /// + /// 服务是否正在运行 + /// + public bool IsRunning { get; set; } + + /// + /// 服务监听的端口号 + /// + public int ServerPort { get; set; } + + /// + /// 视频流的帧率(FPS) + /// + public int FrameRate { get; set; } + + /// + /// 视频分辨率(如 640x480) + /// + public string Resolution { get; set; } = string.Empty; + + /// + /// 当前连接的客户端数量 + /// + public int ConnectedClients { get; set; } + + /// + /// 当前连接的客户端端点列表 + /// + public List ClientEndpoints { get; set; } = new(); + + /// + /// 摄像头连接状态信息 + /// + public CameraStatus CameraStatus { get; set; } = new(); +} + /// /// HTTP 视频流服务,用于从 FPGA 获取图像数据并推送到前端网页 /// 支持动态配置摄像头地址和端口 @@ -28,6 +95,11 @@ public class HttpVideoStreamService : BackgroundService private readonly List _activeClients = new List(); private readonly object _clientsLock = new object(); + /// + /// 获取 / 设置视频流服务是否启用 + /// + public bool Enabled { get; set; } = false; + /// /// 获取当前连接的客户端数量 /// @@ -94,7 +166,7 @@ public class HttpVideoStreamService : BackgroundService try { - await Task.Run(() => + await Task.Run(async () => { lock (_cameraLock) { @@ -122,6 +194,22 @@ public class HttpVideoStreamService : BackgroundService logger.Info("摄像头配置已更新: {Address}:{Port}", _cameraAddress, _cameraPort); } + + // Init Camera + { + var ret = await _camera.Init(); + if (!ret.IsSuccessful) + { + logger.Error(ret.Error); + throw ret.Error; + } + + if (!ret.Value) + { + logger.Error($"Camera Init Failed!"); + throw new Exception($"Camera Init Failed!"); + } + } }); return true; } @@ -176,18 +264,15 @@ public class HttpVideoStreamService : BackgroundService /// 获取摄像头连接状态 /// /// 连接状态信息 - public object GetCameraStatus() + public CameraStatus GetCameraStatus() { - lock (_cameraLock) + return new CameraStatus { - return new - { - Address = _cameraAddress, - Port = _cameraPort, - IsConfigured = _camera != null, - ConnectionString = $"{_cameraAddress}:{_cameraPort}" - }; - } + Address = _cameraAddress, + Port = _cameraPort, + IsConfigured = _camera != null, + ConnectionString = $"{_cameraAddress}:{_cameraPort}" + }; } /// @@ -215,7 +300,17 @@ public class HttpVideoStreamService : BackgroundService _ = Task.Run(() => AcceptClientsAsync(stoppingToken), stoppingToken); // 开始生成视频帧 - await GenerateVideoFrames(stoppingToken); + while (!stoppingToken.IsCancellationRequested) + { + if (Enabled) + { + await GenerateVideoFrames(stoppingToken); + } + else + { + await Task.Delay(500, stoppingToken); + } + } } catch (HttpListenerException ex) { @@ -660,13 +755,13 @@ public class HttpVideoStreamService : BackgroundService /// /// 获取服务状态信息 /// - public object GetServiceStatus() + public ServiceStatus GetServiceStatus() { var cameraStatus = GetCameraStatus(); - return new + return new ServiceStatus { - IsRunning = _httpListener?.IsListening ?? false, + IsRunning = (_httpListener?.IsListening ?? false) && Enabled, ServerPort = _serverPort, FrameRate = _frameRate, Resolution = $"{_frameWidth}x{_frameHeight}", @@ -683,6 +778,8 @@ public class HttpVideoStreamService : BackgroundService { logger.Info("正在停止 HTTP 视频流服务..."); + Enabled = false; + if (_httpListener != null && _httpListener.IsListening) { _httpListener.Stop(); diff --git a/src/APIClient.ts b/src/APIClient.ts index 4d88dbe..115b1eb 100644 --- a/src/APIClient.ts +++ b/src/APIClient.ts @@ -216,11 +216,16 @@ export class VideoStreamClient { } /** - * 测试摄像头连接 - * @return 连接测试结果 + * 控制 HTTP 视频流服务开关 + * @param enabled (optional) 是否启用服务 + * @return 操作结果 */ - testCameraConnection(): Promise { - let url_ = this.baseUrl + "/api/VideoStream/TestCameraConnection"; + setEnabled(enabled: boolean | undefined): Promise { + let url_ = this.baseUrl + "/api/VideoStream/SetEnabled?"; + if (enabled === null) + throw new Error("The parameter 'enabled' cannot be null."); + else if (enabled !== undefined) + url_ += "enabled=" + encodeURIComponent("" + enabled) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { @@ -231,11 +236,11 @@ export class VideoStreamClient { }; return this.http.fetch(url_, options_).then((_response: Response) => { - return this.processTestCameraConnection(_response); + return this.processSetEnabled(_response); }); } - protected processTestCameraConnection(response: Response): Promise { + protected processSetEnabled(response: Response): Promise { const status = response.status; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; if (status === 200) { @@ -2541,4 +2546,4 @@ function throwException(message: string, status: number, response: string, heade throw result; else throw new ApiException(message, status, response, headers, null); -} \ No newline at end of file +} diff --git a/src/views/VideoStreamView.vue b/src/views/VideoStreamView.vue index 34f1198..c7f1192 100644 --- a/src/views/VideoStreamView.vue +++ b/src/views/VideoStreamView.vue @@ -149,69 +149,40 @@ 摄像头配置 -
-
- - +
+
-
- - +
+
+ +
+
@@ -540,8 +511,15 @@