NetMQ 协议,支持摄像头增、删、改

This commit is contained in:
2026-01-12 18:27:58 +08:00
parent 031d4f3416
commit 3f8e42e560
20 changed files with 604 additions and 332 deletions

View File

@@ -21,6 +21,29 @@ namespace SHH.CameraDashboard
#endregion
#region OnExit
/// <summary>
/// 退出时执行
/// </summary>
/// <param name="e"></param>
protected override void OnExit(ExitEventArgs e)
{
// 1. 显式停止通讯总线
CommandBusClient.Instance.Stop();
// 2. 如果你有其他的单例服务需要清理(比如视频解码库),也放在这里
base.OnExit(e);
// 3. 终极保底:如果程序在清理逻辑执行后 3 秒还没消失,强制杀掉进程
// 防止某些第三方 DLL如海康 SDK的线程卡死
Task.Delay(3000).ContinueWith(_ =>
{
System.Diagnostics.Process.GetCurrentProcess().Kill();
});
}
#endregion
protected override async void OnStartup(StartupEventArgs e)
{
@@ -99,142 +122,70 @@ namespace SHH.CameraDashboard
{
Console.WriteLine($"[自动化] 新服务上线: {client.InstanceId}");
Task.Run(async () =>
{
await Task.Delay(500);
//Task.Run(async () =>
//{
// await Task.Delay(500);
// 1. 构建业务配置对象
var cameraConfig = new CameraConfigDto
{
Id = 17798,
Name = "206摄像头",
Location = "404办公室",
IpAddress = "172.16.41.88",
Username = "admin",
Password = "abcd1234",
Port = 8000,
ChannelIndex = 1,
StreamType = 0,
Brand = DeviceBrand.HikVision.GetHashCode(), // 对应 DeviceBrand 枚举
RenderHandle = 0, // 初始化为0
MainboardIp = "", // 留空
MainboardPort = 0,
RtspPath = ""
};
// // 1. 构建业务配置对象
// var cameraConfig = new CameraConfigDto
// {
// Id = 17798,
// Name = "206摄像头",
// Location = "404办公室",
// IpAddress = "172.16.41.88",
// Username = "admin",
// Password = "abcd1234",
// Port = 8000,
// ChannelIndex = 1,
// StreamType = 0,
// Brand = DeviceBrand.HikVision.GetHashCode(), // 对应 DeviceBrand 枚举
// RenderHandle = 0, // 初始化为0
// MainboardIp = "", // 留空
// MainboardPort = 0,
// RtspPath = ""
// };
// ★ 新增:一并带上订阅要求 ★
cameraConfig.AutoSubscriptions = new List<CameraConfigSubscribeDto>
{
// 第一条:显示帧,要求 8 帧
new CameraConfigSubscribeDto {
AppId = "UI_Display",
Type = 0,
TargetFps = 8,
Memo = "显示帧"
},
// 第二条:分析帧,要求 1 帧
new CameraConfigSubscribeDto {
AppId = "AI_Analysis",
Type = 0,
Memo = "分析帧",
TargetFps = 1
}
};
// // ★ 新增:一并带上订阅要求 ★
// cameraConfig.AutoSubscriptions = new List<CameraConfigSubscribeDto>
// {
// // 第一条:显示帧,要求 8 帧
// new CameraConfigSubscribeDto {
// AppId = "UI_Display",
// Type = 0,
// TargetFps = 8,
// Memo = "显示帧"
// },
// // 第二条:分析帧,要求 1 帧
// new CameraConfigSubscribeDto {
// AppId = "AI_Analysis",
// Type = 0,
// Memo = "分析帧",
// TargetFps = 1
// }
// };
// 2. 构造指令包
var command = new CommandPayload
{
Protocol = ProtocolHeaders.Command,
CmdCode = ProtocolHeaders.SyncCamera,
TargetId = client.InstanceId,
RequestId = Guid.NewGuid().ToString("N"),
// // 2. 构造指令包
// var command = new CommandPayload
// {
// Protocol = ProtocolHeaders.Command,
// CmdCode = ProtocolHeaders.SyncCamera,
// TargetId = client.InstanceId,
// RequestId = Guid.NewGuid().ToString("N"),
// ★ 修正 1: 使用 JsonParams 属性名,并将对象序列化为 JSON 字符串 ★
// 因为你的 DTO 定义 JsonParams 是 string 类型
JsonParams = JsonHelper.Serialize(cameraConfig),
// // ★ 修正 1: 使用 JsonParams 属性名,并将对象序列化为 JSON 字符串 ★
// // 因为你的 DTO 定义 JsonParams 是 string 类型
// JsonParams = JsonHelper.Serialize(cameraConfig),
// ★ 修正 2: Timestamp 直接赋值 DateTime 对象 ★
// 因为你的 DTO 定义 Timestamp 是 DateTime 类型
Timestamp = DateTime.Now,
// // ★ 修正 2: Timestamp 直接赋值 DateTime 对象 ★
// // 因为你的 DTO 定义 Timestamp 是 DateTime 类型
// Timestamp = DateTime.Now,
RequireAck = true
};
// RequireAck = true
// };
// 3. 发送
await CommandBusClient.Instance.SendInternalAsync(client.InstanceId, command);
});
}
/// <summary>
/// 在程序启动时订阅事件
/// </summary>
/// <param name="obj"></param>
private void SetupAutomaticConfiguration(ConnectedClient obj)
{
// 监听注册事件:每当有 Service (CommandClientWorker) 连上来注册成功
CommandServer.Instance.OnClientRegistered += (client) =>
{
Console.WriteLine($"[自动化] 检测到新服务上线: {client.ServiceId} ({client.Ip})");
// 放到线程池去执行,避免阻塞 UI 或网络接收线程
Task.Run(async () =>
{
// 1. 稍微延时一点点 (500ms),给 Service 一点喘息时间准备接收指令
await Task.Delay(500);
// 2. 构造您指定的“206摄像头”配置
var cameraConfig = new CameraConfigDto
{
Id = 17798,
Name = "206摄像头",
Location = "404办公室",
IpAddress = "172.16.41.88",
Username = "admin",
Password = "abcd1234",
Port = 8000,
ChannelIndex = 1,
StreamType = 0,
Brand = DeviceBrand.HikVision.GetHashCode(), // 对应 DeviceBrand 枚举
RenderHandle = 0, // 初始化为0
MainboardIp = "", // 留空
MainboardPort = 0,
RtspPath = ""
};
// ★ 新增:一并带上订阅要求 ★
cameraConfig.AutoSubscriptions = new List<CameraConfigSubscribeDto>
{
// 第一条:显示帧,要求 8 帧
new CameraConfigSubscribeDto {
AppId = "UI_Display",
Type = 0,
TargetFps = 8,
Memo = "显示帧"
},
// 第二条:分析帧,要求 1 帧
new CameraConfigSubscribeDto {
AppId = "AI_Analysis",
Type = 0,
Memo = "分析帧",
TargetFps = 1
}
};
// 3. 封装协议包
var commandPacket = new
{
Action = "SyncCamera", // 告诉 Service 执行什么动作
Payload = cameraConfig, // 数据载荷
Time = DateTime.Now
};
// 4. 定向发送
// client.ServiceId 就是那个 "CameraApp_01"
CommandServer.Instance.SendCommand(client.ServiceId, commandPacket);
Console.WriteLine($"[自动化] 已向 {client.ServiceId} 下发配置: 206摄像头");
});
};
// // 3. 发送
// await CommandBusClient.Instance.SendInternalAsync(client.InstanceId, command);
//});
}
/// <summary>

View File

@@ -147,10 +147,37 @@ namespace SHH.CameraDashboard
public void Stop()
{
_isRunning = false;
_poller?.Stop();
_poller?.Dispose();
_routerSocket?.Dispose();
// 增加锁,防止重复释放
lock (_disposeLock)
{
if (!_isRunning) return;
_isRunning = false;
Console.WriteLine("[Bus] 正在释放 NetMQ 资源...");
// 1. 停止 Poller
if (_poller != null)
{
_poller.Stop();
_poller.Dispose();
_poller = null;
}
// 2. 释放 Socket
if (_routerSocket != null)
{
_routerSocket.Close();
_routerSocket.Dispose();
_routerSocket = null;
}
// 3. ★★★ 解决残留的关键:强制清理静态环境 ★★★
// 参数为 true 会等待后台 I/O 线程完成(可能卡住),
// 参数为 false 则强制放弃未完成的 I/O 直接关闭。
NetMQConfig.Cleanup(false);
Console.WriteLine("[Bus] NetMQ 资源已安全释放。");
}
}
public void Dispose() => Stop();