Files
Ayay/SHH.CameraService/GrpcImpls/Handlers/DeviceConfigHandler.cs

240 lines
9.6 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 Ayay.SerilogLogs;
using Newtonsoft.Json.Linq;
using Serilog;
using SHH.CameraSdk;
using SHH.Contracts;
namespace SHH.CameraService;
/// <summary>
/// 同步设备配置处理器
/// </summary>
public class DeviceConfigHandler : ICommandHandler
{
private ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
private readonly CameraManager _cameraManager;
/// <summary>
/// 命令名称
/// </summary>
public string ActionName => ProtocolCodes.Sync_Camera;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="cameraManager"></param>
public DeviceConfigHandler(CameraManager cameraManager)
{
_cameraManager = cameraManager;
}
/// <summary>
/// 执行处理
/// </summary>
/// <param name="payload"></param>
/// <returns></returns>
public async Task ExecuteAsync(JToken payload)
{
// 1. 反序列化配置 DTO
var dto = payload.ToObject<CameraConfigDto>();
if (dto == null) return;
// 2. 尝试获取现有设备
var device = _cameraManager.GetDevice(dto.Id);
string op = device != null ? "更新" : "新增";
string changeSummary = string.Empty;
if (device != null)
{
var old = device.Config;
var sb = new System.Text.StringBuilder();
// 1. 物理参数审计 (冷更新判定)
if (dto.IpAddress != old.IpAddress) sb.Append($"IP:{old.IpAddress}->{dto.IpAddress}; ");
if (dto.Port != old.Port) sb.Append($"Port:{old.Port}->{dto.Port}; ");
if (dto.Username != old.Username) sb.Append($"User:{old.Username}->{dto.Username}; ");
if (dto.Password != old.Password) sb.Append("密码:[已变更]; ");
if (dto.ChannelIndex != old.ChannelIndex) sb.Append($"通道:{old.ChannelIndex}->{dto.ChannelIndex}; ");
// 2. 运行意图审计 (播放/停止)
// Modified: 明确呈现播放状态的切换
if (dto.ImmediateExecution != device.IsRunning)
sb.Append($"运行状态:{(device.IsRunning ? "" : "")}->{(dto.ImmediateExecution ? "" : "")}; ");
// 3. 图像参数审计
if (dto.StreamType != old.StreamType) sb.Append($"码流:{old.StreamType}->{dto.StreamType}; ");
if (dto.UseGrayscale) sb.Append("灰度模式:开启; ");
// 4. 订阅策略深度审计 (使用新增的强类型方法)
// Optimized: 通过 AppId 匹配,找出 FPS 变动
if (dto.AutoSubscriptions != null)
{
var currentReqs = device.Controller?.GetRequirements();
if (currentReqs != null)
{
foreach (var newSub in dto.AutoSubscriptions)
{
var matched = currentReqs.FirstOrDefault(x => x.AppId == newSub.AppId);
if (matched != null)
{
if (matched.TargetFps != newSub.TargetFps || (int)matched.Type != newSub.Type)
{
sb.Append($"[订阅变动:{newSub.AppId}] FPS:{matched.TargetFps}->{newSub.TargetFps}; ");
}
}
else
{
sb.Append($"[新增订阅:{newSub.AppId}] FPS:{newSub.TargetFps}; ");
}
}
}
}
changeSummary = sb.Length > 0 ? $" | 变更明目: {sb.ToString().TrimEnd(' ', ';')}" : " | 配置一致";
}
_sysLog.Information($"[Sync] 即将{op}设备配置, 新配置 => ID:{dto.Id} Name:{dto.Name} IP:{dto.IpAddress} Port:{dto.Port} Brand:{(DeviceBrand)dto.Brand} Rtsp:{dto.RtspPath}");
_sysLog.Debug($"[Sync] 即将{op}设备配置, 新配置 => ID:{dto.Id} Name:{dto.Name} IP:{dto.IpAddress} 详情:" + "{@dto}", dto, dto.AutoSubscriptions);
if (!string.IsNullOrEmpty(changeSummary))
_sysLog.Warning($"[Sync] 即将{op}设备配置, ID:{dto.Id} 变更项 => {changeSummary}");
if (device != null)
{
// =========================================================
// 场景 A: 设备已存在 -> 执行智能更新 (Smart Update)
// =========================================================
// 将全量配置映射为部分更新 DTO
var updateDto = new DeviceUpdateDto
{
// --- 冷更新参数 (变更会触发重启) ---
IpAddress = dto.IpAddress,
Port = dto.Port,
Username = dto.Username,
Password = dto.Password,
ChannelIndex = dto.ChannelIndex,
Brand = dto.Brand,
RtspPath = dto.RtspPath,
RenderHandle = dto.RenderHandle, // long 类型直接赋值
// --- 热更新参数 (变更立即生效) ---
Name = dto.Name,
Location = dto.Location,
StreamType = dto.StreamType,
MainboardIp = dto.MainboardIp,
MainboardPort = dto.MainboardPort,
// --- 图像处理参数 (热更新) ---
AllowCompress = dto.AllowCompress,
AllowExpand = dto.AllowExpand,
TargetResolution = dto.TargetResolution,
EnhanceImage = dto.EnhanceImage,
UseGrayscale = dto.UseGrayscale
};
// 调用 Manager 的核心更新逻辑 (它会自动判断是 Stop->Start 还是直接应用)
await _cameraManager.UpdateDeviceConfigAsync(dto.Id, updateDto);
}
else
{
// =========================================================
// 场景 B: 设备不存在 -> 执行新增 (Add New)
// =========================================================
// 构造全新的设备配置
var newConfig = new VideoSourceConfig
{
Id = dto.Id,
Name = dto.Name,
Brand = (DeviceBrand)dto.Brand, // int -> Enum 强转
IpAddress = dto.IpAddress,
Port = dto.Port,
Username = dto.Username,
Password = dto.Password,
ChannelIndex = dto.ChannelIndex,
StreamType = dto.StreamType,
RtspPath = dto.RtspPath,
MainboardIp = dto.MainboardIp,
MainboardPort = dto.MainboardPort,
RenderHandle = (IntPtr)dto.RenderHandle, // long -> IntPtr 转换
ConnectionTimeoutMs = 5000 // 默认超时
};
// 添加到管理器池
_cameraManager.AddDevice(newConfig);
// 重新获取引用以进行后续操作
device = _cameraManager.GetDevice(dto.Id);
}
// ★★★ 核心修复:统一处理“运行意图” ★★★
if (device != null)
{
// 将 DTO 的立即执行标志直接同步给设备的运行意图
device.IsRunning = dto.ImmediateExecution;
if (dto.ImmediateExecution)
{
// 情况 1: 收到“启动”指令
if (!device.IsActived) // 只有没在线时才点火
{
// 必须在线再执行
if (device.IsPhysicalOnline)
{
_sysLog.Warning($"[Sync] 设备立即启动 => ID:{dto.Id} Name:{dto.Name} IP:{dto.IpAddress} Port:{dto.Port} Brand:{(DeviceBrand)dto.Brand} Rtsp:{dto.RtspPath}");
_ = device.StartAsync();
}
}
}
else
{
// 情况 2: 收到“停止”指令 (即 ImmediateExecution = false)
if (device.IsActived) // 只有在线时才熄火
{
_sysLog.Warning($"[Sync] 设备立即停止 {dto.Id}");
_ = device.StopAsync();
}
}
}
// =========================================================
// 3. 处理自动订阅策略 (Auto Subscriptions)
// =========================================================
// 无论新增还是更新,都确保订阅策略是最新的
if (device != null && dto.AutoSubscriptions != null)
{
var controller = device.Controller;
if (controller != null)
{
foreach (var sub in dto.AutoSubscriptions)
{
// 如果没有 AppId生成一个临时的通常 Dashboard 会下发固定的 AppId
string appId = string.IsNullOrWhiteSpace(sub.AppId)
? $"AUTO_{Guid.NewGuid().ToString("N")[..8]}"
: sub.AppId;
// 构造流控需求
var req = new FrameRequirement
{
AppId = appId,
TargetFps = sub.TargetFps,
Type = (SubscriptionType)sub.Type, // int -> Enum
Memo = sub.Memo ?? "Sync Auto",
// 自动订阅通常不包含具体的 Handle 或 SavePath除非协议里带了
// 如果需要支持网络转发,这里可以扩展映射 sub.TargetIp 等
Handle = "",
SavePath = ""
};
// 注册到帧控制器
controller.Register(req);
}
}
}
}
}