Files
Ayay/SHH.CameraSdk/Controllers/CamerasController.cs

332 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
};
}
/// <summary>
/// [新增] 查询某台设备的当前流控策略
/// </summary>
[HttpGet("{id}/subscriptions")]
public IActionResult GetSubscriptions(long id)
{
var device = _manager.GetDevice(id);
if (device == null) return NotFound();
// 调用刚才在 FrameController 写的方法
var subs = device.Controller.GetCurrentRequirements();
return Ok(subs);
}
private readonly DisplayWindowManager _displayManager; // [新增]
[HttpPost("{id}/subscriptions")]
public IActionResult UpdateSubscription(int id, [FromBody] SubscriptionDto dto)
{
var device = _manager.GetDevice(id);
if (device == null) return NotFound("设备不存在");
if (device is HikVideoSource hikCam)
{
// 1. 更新流控策略 (FrameController)
// 告诉底层:这个 AppId 需要多少帧
int totalFps = dto.DisplayFps + dto.AnalysisFps;
if (totalFps > 0)
{
// 情况 A: 这是一个新增或更新订阅
hikCam.Controller.Register(dto.AppId, totalFps);
// 如果是预览模式,启动窗口
if (dto.DisplayFps > 0)
{
_displayManager.StartDisplay(dto.AppId, id);
}
}
else
{
// 情况 B: 这是一个停止订阅请求 (FPS 为 0)
// 1. 【核心修复】从调度中心物理删除,不再出现在列表中
hikCam.Controller.Unregister(dto.AppId);
// 2. 关闭可能存在的本地窗口
_displayManager.StopDisplay(dto.AppId);
}
return Ok(new { message = "Policy updated", currentConfig = hikCam.Controller.GetCurrentRequirements() });
}
return BadRequest("Device implies no controller");
}
// 1. 获取单个设备详情(用于编辑回填)
[HttpGet("{id}")]
public IActionResult GetDevice(int id)
{
var cam = _manager.GetDevice(id);
if (cam == null) return NotFound();
return Ok(cam.Config); // 返回原始配置对象
}
// 2. 更新设备(保存功能)
[HttpPut("{id}")]
public async Task<IActionResult> UpdateDevice(int id, [FromBody] VideoSourceConfig config)
{
// 核心逻辑:先停止旧设备 -> 更新配置 -> 重新添加到容器 -> 如果之前在运行则重新启动
await _manager.UpdateDeviceAsync(id, config);
return Ok();
}
// 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");
//}
}