Files
Ayay/SHH.CameraSdk/Controllers/CamerasController.cs
2025-12-27 14:16:50 +08:00

336 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.AspNetCore.Mvc;
namespace SHH.CameraSdk;
[ApiController]
[Route("api/[controller]")]
public class CamerasController : ControllerBase
{
private readonly CameraManager _manager;
// 构造函数注入管理器
public CamerasController(CameraManager manager, DisplayWindowManager displayManager)
{
_manager = manager;
_displayManager = displayManager;
}
// ==========================================================================
// 区域 A: 设备全生命周期管理 (CRUD)
// ==========================================================================
/// <summary>
/// 1. 获取所有设备清单
/// </summary>
[HttpGet]
public IActionResult GetAll()
{
var devices = _manager.GetAllDevices().Select(d => new
{
d.Id,
d.Config.IpAddress,
d.Config.Name,
Status = d.Status.ToString(),
d.RealFps,
d.TotalFrames
});
return Ok(devices);
}
/// <summary>
/// 2. 新增设备 (写入配置并初始化)
/// </summary>
[HttpPost]
public IActionResult Add([FromBody] CameraConfigDto dto)
{
if (_manager.GetDevice(dto.Id) != null)
return Conflict($"设备ID {dto.Id} 已存在");
// DTO 转 Config (实际项目中建议用 AutoMapper)
var config = MapToConfig(dto);
_manager.AddDevice(config); // 添加到内存池
// 注意:此时 IsRunning 默认为 false等待手动 Start 或 API 控制
return CreatedAtAction(nameof(GetAll), new { id = dto.Id }, dto);
}
/// <summary>
/// 3. 编辑设备 (自动识别冷热更新)
/// </summary>
[HttpPut("{id}")]
public async Task<IActionResult> Update(long id, [FromBody] CameraConfigDto dto)
{
try
{
if (id != dto.Id) return BadRequest("ID 不匹配");
// 调用 Manager 的智能更新逻辑 (之前实现的 UpdateDeviceConfigAsync)
await _manager.UpdateDeviceConfigAsync(id, MapToUpdateDto(dto));
return Ok(new { Success = true, Message = "配置已更新" });
}
catch (KeyNotFoundException) { return NotFound(); }
catch (System.Exception ex) { return StatusCode(500, ex.Message); }
}
/// <summary>
/// 4. 删除设备 (销毁连接)
/// </summary>
[HttpDelete("{id}")]
public async Task<IActionResult> Remove(long id)
{
var device = _manager.GetDevice(id);
if (device == null) return NotFound();
await device.StopAsync(); // 停流
_manager.RemoveDevice(id); // 从池中移除
return Ok($"设备 {id} 已移除");
}
// ==========================================================================
// 区域 B: 多进程流控订阅 (Subscription Strategy)
// ==========================================================================
// ==========================================================================
// 区域 C: 句柄动态绑定 (Handle Binding)
// ==========================================================================
/// <summary>
/// 6. 绑定显示窗口 (对应 A进程-句柄场景)
/// </summary>
[HttpPost("{id}/bind-handle")]
public IActionResult BindHandle(long id, [FromBody] BindHandleDto dto)
{
var device = _manager.GetDevice(id);
if (device == null) return NotFound();
// 构造动态选项,应用句柄
var options = new DynamicStreamOptions
{
RenderHandle = (System.IntPtr)dto.Handle
};
device.ApplyOptions(options); // 触发驱动层的 OnApplyOptions
device.AddAuditLog($"绑定新句柄: {dto.Handle} ({dto.Purpose})");
return Ok(new { Success = true });
}
// ==========================================================================
// 区域 D: 设备运行控制
// ==========================================================================
/// <summary>
/// 手动控制设备运行状态 (开关机)
/// </summary>
[HttpPost("{id}/power")]
public async Task<IActionResult> TogglePower(long id, [FromQuery] bool enabled)
{
var device = _manager.GetDevice(id);
if (device == null) return NotFound();
// 1. 更新运行意图
device.IsRunning = enabled;
// 2. 审计与执行
if (enabled)
{
device.AddAuditLog("用户指令:手动开启设备");
// 异步启动Coordinator 也会在下个周期辅助检查
_ = device.StartAsync();
}
else
{
device.AddAuditLog("用户指令:手动关闭设备");
await device.StopAsync();
}
return Ok(new { DeviceId = id, IsRunning = enabled });
}
/// <summary>
/// 热应用动态参数 (如切换码流)
/// </summary>
[HttpPost("{id}/options")]
public IActionResult ApplyOptions(long id, [FromBody] DynamicStreamOptions options)
{
var device = _manager.GetDevice(id);
if (device == null) return NotFound();
// 1. 如果涉及码流切换,先同步更新配置对象
if (options.StreamType.HasValue)
{
var newConfig = device.Config.DeepCopy();
newConfig.StreamType = options.StreamType.Value;
device.UpdateConfig(newConfig);
}
// 2. 应用到驱动层(触发热切换逻辑)
device.ApplyOptions(options);
return Ok(new { Message = "动态参数已发送", DeviceId = id });
}
// ==========================================================================
// 辅助方法 (Mapping)
// ==========================================================================
private VideoSourceConfig MapToConfig(CameraConfigDto dto)
{
return new VideoSourceConfig
{
Id = dto.Id,
Name = dto.Name,
Brand = (DeviceBrand)dto.Brand,
IpAddress = dto.IpAddress,
Port = dto.Port,
Username = dto.Username,
Password = dto.Password,
ChannelIndex = dto.ChannelIndex,
StreamType = dto.StreamType,
RtspPath = dto.RtspPath
};
}
/// <summary>
/// [辅助方法] 将全量配置 DTO 转换为更新 DTO
/// </summary>
private DeviceUpdateDto MapToUpdateDto(CameraConfigDto dto)
{
return new DeviceUpdateDto
{
// ==========================================
// 1. 冷更新参数 (基础连接信息)
// ==========================================
IpAddress = dto.IpAddress,
Port = dto.Port,
Username = dto.Username,
Password = dto.Password,
ChannelIndex = dto.ChannelIndex,
Brand = dto.Brand,
RtspPath = dto.RtspPath,
MainboardIp = dto.MainboardIp,
MainboardPort = dto.MainboardPort,
// ==========================================
// 2. 热更新参数 (运行时属性)
// ==========================================
Name = dto.Name,
Location = dto.Location,
StreamType = dto.StreamType,
// 注意:通常句柄是通过 bind-handle 接口单独绑定的,
// 但如果 ConfigDto 里包含了上次保存的句柄,也可以映射
// RenderHandle = dto.RenderHandle,
// ==========================================
// 3. 图像处理参数
// ==========================================
AllowCompress = dto.AllowCompress,
AllowExpand = dto.AllowExpand,
TargetResolution = dto.TargetResolution,
EnhanceImage = dto.EnhanceImage,
UseGrayscale = dto.UseGrayscale
};
}
private readonly DisplayWindowManager _displayManager; // [新增]
/// <summary>
/// 综合订阅策略更新接口
/// 支持:本地 OpenCV 窗口、海康句柄穿透、本地录像、网络传输
/// </summary>
[HttpPost("{id}/subscriptions")]
public IActionResult UpdateSubscription(int id, [FromBody] SubscriptionDto dto)
{
var device = _manager.GetDevice(id);
if (device == null) return NotFound("设备不存在");
// 1. 自动生成 ID 逻辑
if (string.IsNullOrWhiteSpace(dto.AppId))
{
dto.AppId = $"SUB_{Guid.NewGuid().ToString("N").Substring(0, 8).ToUpper()}";
}
// 2. 获取流控控制器
var controller = device.Controller;
if (controller == null) return BadRequest("该设备类型不支持流控调度");
// 3. 处理注销逻辑 (FPS 为 0 代表停止订阅)
if (dto.DisplayFps <= 0)
{
controller.Unregister(dto.AppId);
// 停止显示管理器中所有相关的显示任务 (无论是本地窗口还是句柄绑定)
_displayManager.StopDisplay(dto.AppId);
device.AddAuditLog($"注销订阅: {dto.AppId}");
return Ok(new { Message = "Subscription removed", AppId = dto.AppId });
}
// 4. 业务参数合法性校验
switch (dto.Type)
{
case SubscriptionType.LocalRecord when string.IsNullOrEmpty(dto.SavePath):
return BadRequest("录像模式必须指定存放路径");
case SubscriptionType.HandleDisplay when string.IsNullOrEmpty(dto.Handle):
return BadRequest("句柄显示模式必须提供窗口句柄");
}
// 5. 将需求注册到流控控制器
controller.Register(dto.AppId, dto.DisplayFps);
// 6. 路由显示逻辑 (核心整合点)
if (dto.Type == SubscriptionType.LocalWindow)
{
// --- 保留旧版功能:启动本地 OpenCV 渲染窗口 ---
_displayManager.StartDisplay(dto.AppId, id);
}
else if (dto.Type == SubscriptionType.HandleDisplay && !string.IsNullOrEmpty(dto.Handle))
{
}
device.AddAuditLog($"更新订阅: {dto.AppId} ({dto.Type}), 目标 {dto.DisplayFps} FPS");
return Ok(new
{
Success = true,
AppId = dto.AppId,
Message = "订阅策略已应用",
CurrentConfig = controller.GetCurrentRequirements() // 返回当前所有订阅状态供前端同步
});
}
// 1. 获取单个设备详情(用于编辑回填)
[HttpGet("{id}")]
public IActionResult GetDevice(int id)
{
var cam = _manager.GetDevice(id);
if (cam == null) return NotFound();
return Ok(cam.Config); // 返回原始配置对象
}
// 3. 清除特定设备的日志
[HttpDelete("{id}/logs")]
public IActionResult ClearLogs(int id)
{
var cam = _manager.GetDevice(id);
cam?.ClearAuditLogs();
return Ok();
}
//// 4. 抓图诊断
//[HttpGet("{id}/capture")]
//public async Task<IActionResult> Capture(int id)
//{
// var cam = _manager.GetDevice(id);
// if (cam == null) return NotFound();
// var bytes = await cam.CaptureCurrentFrameAsync();
// return File(bytes, "image/jpeg");
//}
}