Files
Ayay/SHH.CameraService/Core/NetSenders/DeviceStateMonitorWorker.cs
wilson 3351ae739e 在 AiVideo 中能看到图像
增加了在线状态同步逻辑
2026-01-09 12:30:36 +08:00

151 lines
5.3 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 System.Collections.Concurrent;
using Microsoft.Extensions.Hosting;
using NetMQ;
using NetMQ.Sockets;
using MessagePack;
using SHH.CameraSdk;
using SHH.Contracts;
namespace SHH.CameraService
{
/// <summary>
/// [二合一] 设备状态聚合与上报服务
/// </summary>
public class DeviceStateMonitorWorker : BackgroundService
{
private readonly CameraManager _manager;
private readonly ServiceConfig _config;
// ★ 2. 注入拦截器管道
private readonly InterceptorPipeline _pipeline;
// 本地状态全集缓存
private readonly ConcurrentDictionary<string, StatusEventPayload> _stateStore = new();
// 标记是否有新变更
private volatile bool _isDirty = false;
private long _lastSendTick = 0;
// ★ 3. 构造函数增加 InterceptorPipeline 参数
public DeviceStateMonitorWorker(
CameraManager manager,
ServiceConfig config,
InterceptorPipeline pipeline) // <--- 注入点
{
_manager = manager;
_config = config;
_pipeline = pipeline;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// 1. 初始化缓存 (默认离线)
foreach (var dev in _manager.GetAllDevices())
{
UpdateLocalState(dev.Id, false, "Init");
}
// 2. 挂载 SDK 事件
_manager.OnDeviceStatusChanged += OnSdkStatusChanged;
// 3. 建立连接
var cmdEndpoint = _config.CommandEndpoints.FirstOrDefault()?.Uri;
if (string.IsNullOrEmpty(cmdEndpoint))
{
Console.WriteLine("[StatusWorker] 警告: 未配置 Command 端点,状态上报无法启动。");
return;
}
Console.WriteLine($"[StatusWorker] 启动状态上报,直连服务端: {cmdEndpoint}");
using var socket = new DealerSocket();
socket.Options.SendHighWatermark = 1000;
// 设置 Identity 是个好习惯,虽然这里只发不收
// socket.Options.Identity = ...
socket.Connect(cmdEndpoint);
// 4. 定时循环 (1秒1次)
var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// ★ 4. 关键修正:必须使用 await 调用新的异步方法
await CheckAndDirectSendAsync(socket);
}
}
finally
{
_manager.OnDeviceStatusChanged -= OnSdkStatusChanged;
socket.Dispose();
}
}
private void OnSdkStatusChanged(long deviceId, bool isOnline, string reason)
{
UpdateLocalState(deviceId, isOnline, reason);
_isDirty = true;
}
private void UpdateLocalState(long deviceId, bool isOnline, string reason)
{
var evt = new StatusEventPayload
{
CameraId = deviceId.ToString(),
IsOnline = isOnline,
Reason = reason,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
};
_stateStore[deviceId.ToString()] = evt;
}
/// <summary>
/// 检查并在当前线程直接发送 (已改为异步 Task)
/// </summary>
// ★ 5. 关键修正void -> async Task
private async Task CheckAndDirectSendAsync(NetMQSocket socket)
{
long now = Environment.TickCount64;
// 策略: 有变更 或 超过5秒(心跳)
bool shouldSend = _isDirty || (now - _lastSendTick > 5000);
if (shouldSend)
{
try
{
// A. 组包 (全量)
var snapshot = _stateStore.Values.ToList();
var batch = new StatusBatchPayload
{
Items = snapshot,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
};
// B. 序列化
byte[] data = MessagePackSerializer.Serialize(batch);
// =========================================================
// ★ 6. 拦截器调用
// =========================================================
// 这里的 "STATUS_BATCH" 是协议头,你可以替换为 ProtocolHeaders.StatusBatch (如果定义了的话)
var ctx = await _pipeline.ExecuteSendAsync("STATUS_BATCH", data);
if (ctx != null) // 如果没被拦截
{
// C. 直接发送
socket.SendMoreFrame(ctx.Protocol)
.SendFrame(ctx.Data);
// D. 重置标记
_isDirty = false;
_lastSendTick = now;
}
}
catch (Exception ex)
{
Console.WriteLine($"[StatusWorker] 发送失败: {ex.Message}");
}
}
}
}
}