新通讯图像协议对接成功
This commit is contained in:
@@ -1,194 +0,0 @@
|
|||||||
//using System.Text;
|
|
||||||
//using MessagePack;
|
|
||||||
//using Microsoft.Extensions.Hosting;
|
|
||||||
//using NetMQ;
|
|
||||||
//using NetMQ.Monitoring; // ★ 1. 必须引用 Monitoring 命名空间
|
|
||||||
//using NetMQ.Sockets;
|
|
||||||
//using SHH.CameraSdk;
|
|
||||||
//using SHH.Contracts;
|
|
||||||
|
|
||||||
//namespace SHH.CameraService;
|
|
||||||
|
|
||||||
//public class CommandClientWorker : BackgroundService
|
|
||||||
//{
|
|
||||||
// private readonly ServiceConfig _config;
|
|
||||||
// private readonly CommandDispatcher _dispatcher;
|
|
||||||
// private readonly InterceptorPipeline _pipeline;
|
|
||||||
|
|
||||||
// // 管理多个 Socket
|
|
||||||
// private readonly List<DealerSocket> _sockets = new();
|
|
||||||
|
|
||||||
// // ★ 2. 新增:保存 Monitor 列表,防止被 GC 回收
|
|
||||||
// private readonly List<NetMQMonitor> _monitors = new();
|
|
||||||
|
|
||||||
// private NetMQPoller? _poller;
|
|
||||||
|
|
||||||
// public CommandClientWorker(
|
|
||||||
// ServiceConfig config,
|
|
||||||
// CommandDispatcher dispatcher,
|
|
||||||
// InterceptorPipeline pipeline)
|
|
||||||
// {
|
|
||||||
// _config = config;
|
|
||||||
// _dispatcher = dispatcher;
|
|
||||||
// _pipeline = pipeline;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
||||||
// {
|
|
||||||
// await Task.Yield();
|
|
||||||
|
|
||||||
// if (!_config.ShouldConnect || _config.CommandEndpoints.Count == 0) return;
|
|
||||||
|
|
||||||
// _poller = new NetMQPoller();
|
|
||||||
|
|
||||||
// // -------------------------------------------------------------
|
|
||||||
// // 核心修改区:建立连接并挂载监控器
|
|
||||||
// // -------------------------------------------------------------
|
|
||||||
// foreach (var ep in _config.CommandEndpoints)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var socket = new DealerSocket();
|
|
||||||
// socket.Options.Identity = Encoding.UTF8.GetBytes(_config.AppId);
|
|
||||||
|
|
||||||
// var monitorUrl = $"inproc://monitor_{Guid.NewGuid():N}";
|
|
||||||
// var monitor = new NetMQMonitor(socket, monitorUrl, SocketEvents.Connected);
|
|
||||||
|
|
||||||
// monitor.Connected += async (s, args) =>
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"[指令] 网络连接建立: {ep.Uri} -> 正在补发注册包...");
|
|
||||||
// await SendRegisterAsync(socket);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // ★★★ 修正点:使用 AttachToPoller 代替 Add ★★★
|
|
||||||
// // 错误写法: _poller.Add(monitor);
|
|
||||||
// monitor.AttachToPoller(_poller);
|
|
||||||
|
|
||||||
// // 依然需要保存引用,防止被 GC 回收
|
|
||||||
// _monitors.Add(monitor);
|
|
||||||
|
|
||||||
// socket.Connect(ep.Uri);
|
|
||||||
// socket.ReceiveReady += OnSocketReceiveReady;
|
|
||||||
|
|
||||||
// _sockets.Add(socket);
|
|
||||||
// _poller.Add(socket);
|
|
||||||
|
|
||||||
// Console.WriteLine($"[指令] 通道初始化完成: {ep.Uri} (带自动重连监控)");
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"[指令] 连接初始化异常: {ex.Message}");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (_sockets.Count == 0) return;
|
|
||||||
|
|
||||||
// // =================================================================
|
|
||||||
// // 6. 绑定 ACK 逻辑 (保持不变)
|
|
||||||
// // =================================================================
|
|
||||||
// _dispatcher.OnResponseReady += async (result) =>
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// byte[] resultBytes = MessagePackSerializer.Serialize(result);
|
|
||||||
// var ctx = await _pipeline.ExecuteSendAsync(ProtocolHeaders.CommandResult, resultBytes);
|
|
||||||
|
|
||||||
// if (ctx != null)
|
|
||||||
// {
|
|
||||||
// foreach (var socket in _sockets)
|
|
||||||
// {
|
|
||||||
// socket.SendMoreFrame(ctx.Protocol).SendFrame(ctx.Data);
|
|
||||||
// }
|
|
||||||
// Console.WriteLine($"[指令] ACK 已广播 (ID: {result.RequestId})");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"[ACK] 发送失败: {ex.Message}");
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // =================================================================
|
|
||||||
// // 7. 启动 Poller
|
|
||||||
// // =================================================================
|
|
||||||
// // 注意:我们不需要手动发第一次注册包了,
|
|
||||||
// // 因为 Poller 启动后,底层 TCP 会建立连接,从而触发 monitor.Connected 事件,
|
|
||||||
// // 事件里会自动发送注册包。这就是“自动档”的好处。
|
|
||||||
// _poller.RunAsync();
|
|
||||||
|
|
||||||
// // 阻塞直到取消
|
|
||||||
// while (!stoppingToken.IsCancellationRequested)
|
|
||||||
// {
|
|
||||||
// await Task.Delay(1000, stoppingToken);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 清理
|
|
||||||
// _poller.Stop();
|
|
||||||
// _poller.Dispose();
|
|
||||||
// foreach (var m in _monitors) m.Dispose(); // 释放监控器
|
|
||||||
// foreach (var s in _sockets) s.Dispose();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // =================================================================
|
|
||||||
// // ★ 8. 抽离出的注册包发送逻辑 (供 Monitor 调用)
|
|
||||||
// // =================================================================
|
|
||||||
// private async Task SendRegisterAsync(DealerSocket targetSocket)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var registerPayload = new RegisterPayload
|
|
||||||
// {
|
|
||||||
// Protocol = ProtocolHeaders.ServerRegister,
|
|
||||||
// InstanceId = _config.AppId,
|
|
||||||
// ProcessId = Environment.ProcessId,
|
|
||||||
// Version = "1.0.0",
|
|
||||||
// ServerIp = "127.0.0.1", // 建议优化:获取本机真实IP
|
|
||||||
// WebApiPort = _config.BasePort,
|
|
||||||
// StartTime = DateTime.Now
|
|
||||||
// };
|
|
||||||
|
|
||||||
// byte[] regData = MessagePackSerializer.Serialize(registerPayload);
|
|
||||||
|
|
||||||
// // 执行拦截器
|
|
||||||
// var ctx = await _pipeline.ExecuteSendAsync(ProtocolHeaders.ServerRegister, regData);
|
|
||||||
|
|
||||||
// if (ctx != null)
|
|
||||||
// {
|
|
||||||
// // 直接向触发事件的那个 Socket 发送
|
|
||||||
// // DealerSocket 允许在连接未完全就绪时 Send,它会缓存直到网络通畅
|
|
||||||
// targetSocket.SendMoreFrame(ctx.Protocol).SendFrame(ctx.Data);
|
|
||||||
// // Console.WriteLine($"[指令] 身份注册包已推入队列: {targetSocket.Options.Identity}");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"[指令] 注册包发送失败: {ex.Message}");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private async void OnSocketReceiveReady(object? sender, NetMQSocketEventArgs e)
|
|
||||||
// {
|
|
||||||
// NetMQMessage incomingMsg = new NetMQMessage();
|
|
||||||
// if (e.Socket.TryReceiveMultipartMessage(ref incomingMsg))
|
|
||||||
// {
|
|
||||||
// if (incomingMsg.FrameCount >= 2)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// string rawProtocol = incomingMsg[0].ConvertToString();
|
|
||||||
// byte[] rawData = incomingMsg[1].ToByteArray();
|
|
||||||
|
|
||||||
// var ctx = await _pipeline.ExecuteReceiveAsync(rawProtocol, rawData);
|
|
||||||
// if (ctx != null)
|
|
||||||
// {
|
|
||||||
// await _dispatcher.DispatchAsync(ctx.Protocol, ctx.Data);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"[指令] 处理异常: {ex.Message}");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
// 文件: Core\CmdClients\CommandDispatcher.cs
|
|
||||||
|
|
||||||
using MessagePack;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using SHH.Contracts;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace SHH.CameraService;
|
|
||||||
|
|
||||||
public class CommandDispatcher
|
|
||||||
{
|
|
||||||
// 1. 注入路由表
|
|
||||||
private readonly Dictionary<string, ICommandHandler> _handlers;
|
|
||||||
|
|
||||||
// 2. 定义回执事件 (ACK闭环的核心)
|
|
||||||
public event Action<CommandResult>? OnResponseReady;
|
|
||||||
|
|
||||||
// 3. 构造函数:注入所有 Handler
|
|
||||||
public CommandDispatcher(IEnumerable<ICommandHandler> handlers)
|
|
||||||
{
|
|
||||||
// 将注入的 Handler 转换为字典,Key = ActionName (e.g. "SyncCamera")
|
|
||||||
_handlers = handlers.ToDictionary(h => h.ActionName, h => h, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DispatchAsync(string protocol, byte[] data)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 只处理 COMMAND 协议
|
|
||||||
if (protocol != ProtocolHeaders.Command) return;
|
|
||||||
|
|
||||||
// 反序列化信封
|
|
||||||
var envelope = MessagePackSerializer.Deserialize<CommandPayload>(data);
|
|
||||||
if (envelope == null) return;
|
|
||||||
|
|
||||||
string cmdCode = envelope.CmdCode; // e.g. "SyncCamera"
|
|
||||||
Console.WriteLine($"[分发] 收到指令: {cmdCode} (ID: {envelope.RequestId})");
|
|
||||||
|
|
||||||
bool isSuccess = true;
|
|
||||||
string message = "OK";
|
|
||||||
|
|
||||||
// --- 路由匹配逻辑 ---
|
|
||||||
if (_handlers.TryGetValue(cmdCode, out var handler))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 数据适配:你的 Handler 需要 JToken
|
|
||||||
// 如果 envelope.JsonParams 是空的,传个空对象防止报错
|
|
||||||
var jsonStr = string.IsNullOrEmpty(envelope.JsonParams) ? "{}" : envelope.JsonParams;
|
|
||||||
var token = JToken.Parse(jsonStr);
|
|
||||||
|
|
||||||
// ★★★ 核心:调用 SyncCameraHandler.ExecuteAsync ★★★
|
|
||||||
await handler.ExecuteAsync(token);
|
|
||||||
|
|
||||||
message = $"Executed {cmdCode}";
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
isSuccess = false;
|
|
||||||
message = $"Handler Error: {ex.Message}";
|
|
||||||
Console.WriteLine($"[业务异常] {message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
isSuccess = false;
|
|
||||||
message = $"No handler found for {cmdCode}";
|
|
||||||
Console.WriteLine($"[警告] {message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- ACK 闭环逻辑 ---
|
|
||||||
if (envelope.RequireAck)
|
|
||||||
{
|
|
||||||
var result = new CommandResult
|
|
||||||
{
|
|
||||||
Protocol = ProtocolHeaders.CommandResult,
|
|
||||||
RequestId = envelope.RequestId, // 必须带回 ID
|
|
||||||
Success = isSuccess,
|
|
||||||
Message = message,
|
|
||||||
Timestamp = DateTime.Now.Ticks
|
|
||||||
};
|
|
||||||
// 触发事件
|
|
||||||
OnResponseReady?.Invoke(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"[Dispatcher] 致命错误: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
namespace SHH.CameraService;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 在线客户端信息模型 (已更新)
|
|
||||||
/// </summary>
|
|
||||||
public class ConnectedClient
|
|
||||||
{
|
|
||||||
/// <summary> 唯一标识 (AppId) </summary>
|
|
||||||
public string ServiceId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary> 版本号 </summary>
|
|
||||||
public string Version { get; set; } = "1.0.0";
|
|
||||||
|
|
||||||
/// <summary> 远程进程 ID </summary>
|
|
||||||
public int Pid { get; set; }
|
|
||||||
|
|
||||||
/// <summary> 客户端 IP </summary>
|
|
||||||
public string Ip { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary> WebAPI 端口 (Dashboard 调用 REST 接口用) </summary>
|
|
||||||
public int WebPort { get; set; }
|
|
||||||
|
|
||||||
/// <summary> 该客户端正在推流的目标地址 </summary>
|
|
||||||
public List<string> TargetVideoNodes { get; set; } = new List<string>();
|
|
||||||
|
|
||||||
public DateTime LastHeartbeat { get; set; }
|
|
||||||
|
|
||||||
// 辅助属性:拼接出完整的 API BaseUrl
|
|
||||||
public string WebApiUrl => $"http://{Ip}:{WebPort}";
|
|
||||||
}
|
|
||||||
66
SHH.CameraService/Core/CommandDispatcher.cs
Normal file
66
SHH.CameraService/Core/CommandDispatcher.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using SHH.Contracts.Grpc;
|
||||||
|
|
||||||
|
namespace SHH.CameraService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// gRPC 指令分发器
|
||||||
|
/// 职责:接收从 GrpcCommandReceiverWorker 传入的 Proto 消息,解析参数并路由至具体的 Handler。
|
||||||
|
/// </summary>
|
||||||
|
public class CommandDispatcher
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, ICommandHandler> _handlers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数:通过 DI 注入所有已注册的处理器 (SyncCameraHandler, RemoveCameraHandler 等)
|
||||||
|
/// </summary>
|
||||||
|
public CommandDispatcher(IEnumerable<ICommandHandler> handlers)
|
||||||
|
{
|
||||||
|
// 将处理器列表转换为字典,方便 O(1) 查询
|
||||||
|
_handlers = handlers.ToDictionary(
|
||||||
|
h => h.ActionName,
|
||||||
|
h => h,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行指令分发
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="protoMsg">从 gRPC Server Streaming 接收到的原始 Proto 指令对象</param>
|
||||||
|
public async Task DispatchAsync(CommandPayloadProto protoMsg)
|
||||||
|
{
|
||||||
|
if (protoMsg == null) return;
|
||||||
|
|
||||||
|
string cmdCode = protoMsg.CmdCode; // 例如 "Sync_Camera"
|
||||||
|
Console.WriteLine($"[Dispatcher] 收到远程指令: {cmdCode}, 请求ID: {protoMsg.RequestId}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. 查找对应的处理器
|
||||||
|
if (_handlers.TryGetValue(cmdCode, out var handler))
|
||||||
|
{
|
||||||
|
// 2. 参数转换:将 Proto 里的 JSON 字符串转换为原有 Handler 需要的 JToken
|
||||||
|
// 这样你之前的 SyncCameraHandler 代码不需要做任何逻辑改动即可直接复用
|
||||||
|
var jsonStr = string.IsNullOrWhiteSpace(protoMsg.JsonParams) ? "{}" : protoMsg.JsonParams;
|
||||||
|
var token = JToken.Parse(jsonStr);
|
||||||
|
|
||||||
|
// 3. 调用具体业务执行
|
||||||
|
await handler.ExecuteAsync(token);
|
||||||
|
|
||||||
|
Console.WriteLine($"[Dispatcher] 指令 {cmdCode} 执行成功。");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Dispatcher Warning] 未找到指令处理器: {cmdCode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Dispatcher Error] 执行指令 {cmdCode} 异常: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:关于 ACK (require_ack)
|
||||||
|
// 在 NetMQ 时代需要手动回发结果,在 gRPC Server Streaming 模式下,
|
||||||
|
// 建议通过 Unary RPC (例如另设一个 ReportCommandResult 方法) 异步上报执行结果。
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
//using MessagePack;
|
|
||||||
//using NetMQ;
|
|
||||||
//using SHH.Contracts;
|
|
||||||
|
|
||||||
//namespace SHH.CameraService
|
|
||||||
//{
|
|
||||||
// /// <summary>
|
|
||||||
// /// 负责将业务契约转换为 ZeroMQ 传输协议
|
|
||||||
// /// </summary>
|
|
||||||
// public static class NetMQProtocolExtensions
|
|
||||||
// {
|
|
||||||
// private const string PROTOCOL_HEADER = "SHH_V1";
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// 扩展方法:将 Payload 转为 NetMQMessage
|
|
||||||
// /// 使用方法:var msg = payload.ToNetMqMessage();
|
|
||||||
// /// </summary>
|
|
||||||
// public static NetMQMessage ToNetMqMessage(this VideoPayload payload)
|
|
||||||
// {
|
|
||||||
// var msg = new NetMQMessage();
|
|
||||||
|
|
||||||
// // Frame 0: 协议魔数
|
|
||||||
// msg.Append(PROTOCOL_HEADER);
|
|
||||||
|
|
||||||
// ////// Frame 1: 元数据 JSON
|
|
||||||
// ////msg.Append(payload.GetMetadataJson());
|
|
||||||
|
|
||||||
// // ★★★ 修复点:在序列化之前,手动更新 Payload 的标志位 ★★★
|
|
||||||
// payload.HasOriginalImage = (payload.OriginalImageBytes != null && payload.OriginalImageBytes.Length > 0);
|
|
||||||
// payload.HasTargetImage = (payload.TargetImageBytes != null && payload.TargetImageBytes.Length > 0);
|
|
||||||
|
|
||||||
// // Frame 1: Metadata (MessagePack)
|
|
||||||
// byte[] metaBytes = MessagePackSerializer.Serialize(payload);
|
|
||||||
// msg.Append(metaBytes);
|
|
||||||
|
|
||||||
// // Frame 2: 原始图 (保持帧位对齐,无数据则发空帧)
|
|
||||||
// if (payload.HasOriginalImage && payload.OriginalImageBytes != null)
|
|
||||||
// msg.Append(payload.OriginalImageBytes);
|
|
||||||
// else
|
|
||||||
// msg.Append(Array.Empty<byte>());
|
|
||||||
|
|
||||||
// // Frame 3: 处理图
|
|
||||||
// if (payload.HasTargetImage && payload.TargetImageBytes != null)
|
|
||||||
// msg.Append(payload.TargetImageBytes);
|
|
||||||
// else
|
|
||||||
// msg.Append(Array.Empty<byte>());
|
|
||||||
|
|
||||||
// return msg;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// 扩展方法:从 NetMQMessage 还原 Payload
|
|
||||||
// /// </summary>
|
|
||||||
// public static VideoPayload ToVideoPayload(this NetMQMessage msg)
|
|
||||||
// {
|
|
||||||
// if (msg == null || msg.FrameCount < 2) return null;
|
|
||||||
|
|
||||||
// // Frame 0 Check
|
|
||||||
// if (msg[0].ConvertToString() != PROTOCOL_HEADER) return null;
|
|
||||||
|
|
||||||
// //// Frame 1: Metadata
|
|
||||||
// //string json = msg[1].ConvertToString();
|
|
||||||
// //var payload = VideoPayload.FromMetadataJson(json);
|
|
||||||
|
|
||||||
// // [新代码] 直接从二进制还原
|
|
||||||
// // ToByteArray() 虽然会产生一次拷贝,但对于 Metadata 这种小数据影响微乎其微
|
|
||||||
// // 相比 JSON 解析 String 的开销,这已经非常快了
|
|
||||||
// var payload = MessagePackSerializer.Deserialize<VideoPayload>(msg[1].ToByteArray());
|
|
||||||
// if (payload == null) return null;
|
|
||||||
|
|
||||||
// // Frame 2: Raw Image
|
|
||||||
// // 利用 BufferSize 避免不必要的内存拷贝,如果长度为0则跳过
|
|
||||||
// if (payload.HasOriginalImage && msg[2].BufferSize > 0)
|
|
||||||
// {
|
|
||||||
// payload.OriginalImageBytes = msg[2].ToByteArray();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Frame 3: Processed Image
|
|
||||||
// if (payload.HasTargetImage && msg[3].BufferSize > 0)
|
|
||||||
// {
|
|
||||||
// payload.TargetImageBytes = msg[3].ToByteArray();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return payload;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
using Grpc.Core;
|
|
||||||
using Grpc.Net.Client;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using SHH.CameraSdk;
|
|
||||||
using SHH.Contracts;
|
|
||||||
using SHH.Contracts.Grpc; // 引用 Proto 生成的命名空间
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SHH.CameraService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// gRPC 指令接收后台服务
|
|
||||||
/// 负责:1. 逻辑注册 2. 维持指令长连接 3. 指令分发
|
|
||||||
/// </summary>
|
|
||||||
public class GrpcCommandReceiverWorker : BackgroundService
|
|
||||||
{
|
|
||||||
private readonly ILogger<GrpcCommandReceiverWorker> _logger;
|
|
||||||
private readonly ServiceConfig _config;
|
|
||||||
private readonly IEnumerable<ICommandHandler> _handlers; // 自动注入所有指令处理器
|
|
||||||
|
|
||||||
public GrpcCommandReceiverWorker(
|
|
||||||
ILogger<GrpcCommandReceiverWorker> logger,
|
|
||||||
ServiceConfig config,
|
|
||||||
IEnumerable<ICommandHandler> handlers)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_config = config;
|
|
||||||
_handlers = handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
// 给 SDK 和数据库留出几秒钟的加载时间
|
|
||||||
_logger.LogInformation("[gRPC Bus] 后台 Worker 准备就绪,3秒后发起连接...");
|
|
||||||
await Task.Delay(3000, stoppingToken);
|
|
||||||
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 1. 地址预处理 (将 127.0.0.1 强制转换为 localhost 解决 Unimplemented 问题)
|
|
||||||
var ep = _config.CommandEndpoints.First();
|
|
||||||
string targetUrl = ep.Uri.Replace("tcp://", "http://").Replace("127.0.0.1", "localhost");
|
|
||||||
|
|
||||||
using var channel = GrpcChannel.ForAddress(targetUrl);
|
|
||||||
var client = new GatewayProvider.GatewayProviderClient(channel);
|
|
||||||
|
|
||||||
// --- 第一步:发起逻辑注册 (Unary) ---
|
|
||||||
_logger.LogInformation("[gRPC Bus] 正在发起逻辑注册: {Url}", targetUrl);
|
|
||||||
var regResp = await client.RegisterInstanceAsync(new RegisterRequest
|
|
||||||
{
|
|
||||||
InstanceId = _config.AppId,
|
|
||||||
Version = "2.0.0-grpc",
|
|
||||||
ServerIp = "127.0.0.1",
|
|
||||||
StartTimeTicks = DateTime.Now.Ticks
|
|
||||||
}, cancellationToken: stoppingToken);
|
|
||||||
|
|
||||||
if (regResp.Success)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("[gRPC Bus] 逻辑注册成功。正在开启长连接指令通道...");
|
|
||||||
|
|
||||||
// --- 第二步:开启物理指令流 (Server Streaming) ---
|
|
||||||
using var call = client.OpenCommandChannel(new CommandStreamRequest
|
|
||||||
{
|
|
||||||
InstanceId = _config.AppId
|
|
||||||
}, cancellationToken: stoppingToken);
|
|
||||||
|
|
||||||
// --- 第三步:阻塞式监听服务端推送 ---
|
|
||||||
// 只要服务端通过 responseStream.WriteAsync 发消息,这里就会命中
|
|
||||||
while (await call.ResponseStream.MoveNext(stoppingToken))
|
|
||||||
{
|
|
||||||
var protoMsg = call.ResponseStream.Current;
|
|
||||||
_logger.LogInformation("[gRPC Bus] 收到远程指令: {CmdCode}", protoMsg.CmdCode);
|
|
||||||
|
|
||||||
// 异步分发,不阻塞接收循环
|
|
||||||
_ = DispatchCommandAsync(protoMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) { break; }
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError("[gRPC Bus] 链路异常,5秒后重试: {Msg}", ex.Message);
|
|
||||||
await Task.Delay(5000, stoppingToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 指令分发逻辑
|
|
||||||
/// </summary>
|
|
||||||
private async Task DispatchCommandAsync(CommandPayloadProto msg)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 1. 寻找匹配的处理器 (SyncCameraHandler / RemoveCameraHandler)
|
|
||||||
var handler = _handlers.FirstOrDefault(h => h.ActionName == msg.CmdCode);
|
|
||||||
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
// 2. 将 Proto 的参数转为 JToken,保持与原有处理器兼容
|
|
||||||
var jsonParams = JToken.Parse(msg.JsonParams);
|
|
||||||
await handler.ExecuteAsync(jsonParams);
|
|
||||||
_logger.LogInformation("[gRPC Bus] 指令 {CmdCode} 执行完成", msg.CmdCode);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning("[gRPC Bus] 未找到处理 {CmdCode} 的处理器", msg.CmdCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "[gRPC Bus] 指令执行失败: {CmdCode}", msg.CmdCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ namespace SHH.CameraService;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 同步设备配置处理器
|
/// 同步设备配置处理器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SyncCameraHandler : ICommandHandler
|
public class DeviceConfigHandler : ICommandHandler
|
||||||
{
|
{
|
||||||
private readonly CameraManager _cameraManager;
|
private readonly CameraManager _cameraManager;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ public class SyncCameraHandler : ICommandHandler
|
|||||||
/// 构造函数
|
/// 构造函数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cameraManager"></param>
|
/// <param name="cameraManager"></param>
|
||||||
public SyncCameraHandler(CameraManager cameraManager)
|
public DeviceConfigHandler(CameraManager cameraManager)
|
||||||
{
|
{
|
||||||
_cameraManager = cameraManager;
|
_cameraManager = cameraManager;
|
||||||
}
|
}
|
||||||
@@ -13,11 +13,11 @@ namespace SHH.CameraService;
|
|||||||
/// 设备状态监控工作者 (gRPC 版)
|
/// 设备状态监控工作者 (gRPC 版)
|
||||||
/// 职责:监控相机状态并在状态变更或心跳周期内,通过 gRPC 批量上报至所有配置的端点
|
/// 职责:监控相机状态并在状态变更或心跳周期内,通过 gRPC 批量上报至所有配置的端点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DeviceStateMonitorWorker : BackgroundService
|
public class DeviceStatusHandler : BackgroundService
|
||||||
{
|
{
|
||||||
private readonly CameraManager _manager;
|
private readonly CameraManager _manager;
|
||||||
private readonly ServiceConfig _config;
|
private readonly ServiceConfig _config;
|
||||||
private readonly ILogger<DeviceStateMonitorWorker> _logger;
|
private readonly ILogger<DeviceStatusHandler> _logger;
|
||||||
|
|
||||||
// 状态存储:CameraId -> 状态载荷
|
// 状态存储:CameraId -> 状态载荷
|
||||||
private readonly ConcurrentDictionary<string, StatusEventPayload> _stateStore = new();
|
private readonly ConcurrentDictionary<string, StatusEventPayload> _stateStore = new();
|
||||||
@@ -25,10 +25,10 @@ public class DeviceStateMonitorWorker : BackgroundService
|
|||||||
private volatile bool _isDirty = false;
|
private volatile bool _isDirty = false;
|
||||||
private long _lastSendTick = 0;
|
private long _lastSendTick = 0;
|
||||||
|
|
||||||
public DeviceStateMonitorWorker(
|
public DeviceStatusHandler(
|
||||||
CameraManager manager,
|
CameraManager manager,
|
||||||
ServiceConfig config,
|
ServiceConfig config,
|
||||||
ILogger<DeviceStateMonitorWorker> logger)
|
ILogger<DeviceStatusHandler> logger)
|
||||||
{
|
{
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_config = config;
|
_config = config;
|
||||||
@@ -104,7 +104,6 @@ public class DeviceStateMonitorWorker : BackgroundService
|
|||||||
// 1. 构建 gRPC 请求包
|
// 1. 构建 gRPC 请求包
|
||||||
var request = new StatusBatchRequest
|
var request = new StatusBatchRequest
|
||||||
{
|
{
|
||||||
Protocol = "GRPC",
|
|
||||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
|
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -116,7 +115,6 @@ public class DeviceStateMonitorWorker : BackgroundService
|
|||||||
CameraId = item.CameraId,
|
CameraId = item.CameraId,
|
||||||
IsOnline = item.IsOnline,
|
IsOnline = item.IsOnline,
|
||||||
Reason = item.Reason,
|
Reason = item.Reason,
|
||||||
Timestamp = item.Timestamp
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
102
SHH.CameraService/GrpcImpls/Handlers/GatewayService.cs
Normal file
102
SHH.CameraService/GrpcImpls/Handlers/GatewayService.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
using Grpc.Net.Client;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using SHH.CameraSdk;
|
||||||
|
using SHH.Contracts.Grpc; // 引用 Proto 生成的命名空间
|
||||||
|
|
||||||
|
namespace SHH.CameraService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// gRPC 指令接收后台服务
|
||||||
|
/// 职责:
|
||||||
|
/// 1. 维护与 AiVideo 的 gRPC 长连接。
|
||||||
|
/// 2. 完成节点逻辑注册。
|
||||||
|
/// 3. 监听 Server Streaming 指令流并移交给 Dispatcher。
|
||||||
|
/// </summary>
|
||||||
|
public class GatewayService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly ILogger<GatewayService> _logger;
|
||||||
|
private readonly ServiceConfig _config;
|
||||||
|
private readonly CommandDispatcher _dispatcher;
|
||||||
|
|
||||||
|
public GatewayService(
|
||||||
|
ILogger<GatewayService> logger,
|
||||||
|
ServiceConfig config,
|
||||||
|
CommandDispatcher dispatcher)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_config = config;
|
||||||
|
_dispatcher = dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
// 预留系统启动缓冲时间,确保数据库和 SDK 已就绪
|
||||||
|
_logger.LogInformation("[gRPC Bus] 指令接收服务启动,等待环境预热...");
|
||||||
|
await Task.Delay(3000, stoppingToken);
|
||||||
|
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. 地址适配:将 tcp 转换为 http,并将 127.0.0.1 修正为 localhost 解决 Unimplemented 异常
|
||||||
|
var ep = _config.CommandEndpoints.First();
|
||||||
|
string targetUrl = ep.Uri.Replace("tcp://", "http://").Replace("127.0.0.1", "localhost");
|
||||||
|
|
||||||
|
using var channel = GrpcChannel.ForAddress(targetUrl);
|
||||||
|
var client = new GatewayProvider.GatewayProviderClient(channel);
|
||||||
|
|
||||||
|
// --- 第一步:发起节点逻辑注册 (Unary) ---
|
||||||
|
_logger.LogInformation("[gRPC Bus] 正在发起逻辑注册: {Url}", targetUrl);
|
||||||
|
var regResp = await client.RegisterInstanceAsync(new RegisterRequest
|
||||||
|
{
|
||||||
|
InstanceId = _config.AppId,
|
||||||
|
Version = "2.0.0-grpc",
|
||||||
|
ServerIp = "127.0.0.1",
|
||||||
|
StartTimeTicks = DateTime.Now.Ticks
|
||||||
|
}, cancellationToken: stoppingToken);
|
||||||
|
|
||||||
|
if (regResp.Success)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("[gRPC Bus] 注册成功。正在建立双向指令通道...");
|
||||||
|
|
||||||
|
// --- 第二步:开启 Server Streaming 指令流 ---
|
||||||
|
using var call = client.OpenCommandChannel(new CommandStreamRequest
|
||||||
|
{
|
||||||
|
InstanceId = _config.AppId
|
||||||
|
}, cancellationToken: stoppingToken);
|
||||||
|
|
||||||
|
// --- 第三步:循环读取服务端推送的指令 ---
|
||||||
|
// 只要服务端流未断开,此处会一直阻塞等待新消息
|
||||||
|
while (await call.ResponseStream.MoveNext(stoppingToken))
|
||||||
|
{
|
||||||
|
var protoMsg = call.ResponseStream.Current;
|
||||||
|
|
||||||
|
// 核心变更:不再直接处理业务,而是通过分发器进行路由
|
||||||
|
// 使用 _ = 异步处理,避免某个 Handler 执行过慢导致指令流阻塞
|
||||||
|
_ = _dispatcher.DispatchAsync(protoMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// 响应系统正常退出信号
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (RpcException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("[gRPC Bus] RPC 异常 (Status: {Code}): {Msg}", ex.StatusCode, ex.Message);
|
||||||
|
// 链路异常,进入重连等待阶段
|
||||||
|
await Task.Delay(5000, stoppingToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("[gRPC Bus] 非预期链路异常: {Msg},5秒后尝试重连", ex.Message);
|
||||||
|
await Task.Delay(5000, stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ using System.Diagnostics;
|
|||||||
|
|
||||||
namespace SHH.CameraService;
|
namespace SHH.CameraService;
|
||||||
|
|
||||||
public class NetworkStreamingWorker : BackgroundService
|
public class ImageMonitorController : BackgroundService
|
||||||
{
|
{
|
||||||
// 注入所有注册的目标(云端、大屏等),实现动态分发
|
// 注入所有注册的目标(云端、大屏等),实现动态分发
|
||||||
private readonly IEnumerable<StreamTarget> _targets;
|
private readonly IEnumerable<StreamTarget> _targets;
|
||||||
@@ -16,7 +16,7 @@ public class NetworkStreamingWorker : BackgroundService
|
|||||||
// 如果您确实需要 100,请注意带宽压力。此处我保留您要求的 100,但建议未来调优。
|
// 如果您确实需要 100,请注意带宽压力。此处我保留您要求的 100,但建议未来调优。
|
||||||
private readonly int[] _encodeParams = { (int)ImwriteFlags.JpegQuality, 100 };
|
private readonly int[] _encodeParams = { (int)ImwriteFlags.JpegQuality, 100 };
|
||||||
|
|
||||||
public NetworkStreamingWorker(IEnumerable<StreamTarget> targets)
|
public ImageMonitorController(IEnumerable<StreamTarget> targets)
|
||||||
{
|
{
|
||||||
_targets = targets;
|
_targets = targets;
|
||||||
}
|
}
|
||||||
@@ -46,10 +46,17 @@ public class GrpcSenderWorker : BackgroundService
|
|||||||
// 3. 核心搬运循环:从内存队列 (Channel) 读取数据
|
// 3. 核心搬运循环:从内存队列 (Channel) 读取数据
|
||||||
await foreach (var payload in _target.Channel.Reader.ReadAllAsync(stoppingToken))
|
await foreach (var payload in _target.Channel.Reader.ReadAllAsync(stoppingToken))
|
||||||
{
|
{
|
||||||
|
// 【畅通保障】检查数据时效性:丢弃超过 1 秒的积压帧
|
||||||
|
var delay = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - payload.CaptureTimestamp;
|
||||||
|
if (delay > 1000)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// 将业务 DTO 转换为 gRPC 原生 Request
|
// 将业务 DTO 转换为 gRPC 原生 Request
|
||||||
var request = new VideoFrameRequest
|
var request = new VideoFrameRequest
|
||||||
{
|
{
|
||||||
CameraId = payload.CameraId ?? "Unknown",
|
CameraId = payload.CameraId ?? "0",
|
||||||
CaptureTimestamp = payload.CaptureTimestamp,
|
CaptureTimestamp = payload.CaptureTimestamp,
|
||||||
OriginalWidth = payload.OriginalWidth,
|
OriginalWidth = payload.OriginalWidth,
|
||||||
OriginalHeight = payload.OriginalHeight,
|
OriginalHeight = payload.OriginalHeight,
|
||||||
@@ -66,6 +73,8 @@ public class GrpcSenderWorker : BackgroundService
|
|||||||
: ByteString.Empty
|
: ByteString.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
|
request.SubscriberIds.AddRange(payload.SubscriberIds);
|
||||||
|
|
||||||
// 处理诊断信息 map<string, string>
|
// 处理诊断信息 map<string, string>
|
||||||
if (payload.Diagnostics != null)
|
if (payload.Diagnostics != null)
|
||||||
{
|
{
|
||||||
@@ -49,7 +49,7 @@ public class Program
|
|||||||
InstanceId = config.AppId,
|
InstanceId = config.AppId,
|
||||||
Version = "2.0.0-grpc",
|
Version = "2.0.0-grpc",
|
||||||
ServerIp = "127.0.0.1",
|
ServerIp = "127.0.0.1",
|
||||||
WebApiPort = config.BasePort,
|
WebapiPort = config.BasePort,
|
||||||
StartTimeTicks = DateTime.Now.Ticks,
|
StartTimeTicks = DateTime.Now.Ticks,
|
||||||
ProcessId = Environment.ProcessId,
|
ProcessId = Environment.ProcessId,
|
||||||
Description = "Camera Service"
|
Description = "Camera Service"
|
||||||
@@ -79,9 +79,9 @@ public class Program
|
|||||||
builder.Services.AddHostedService<CameraEngineWorker>();
|
builder.Services.AddHostedService<CameraEngineWorker>();
|
||||||
|
|
||||||
// ★ 注册 gRPC 版本的状态监控工作者 (不讲道理,直接注册)
|
// ★ 注册 gRPC 版本的状态监控工作者 (不讲道理,直接注册)
|
||||||
builder.Services.AddHostedService<DeviceStateMonitorWorker>();
|
builder.Services.AddHostedService<DeviceStatusHandler>();
|
||||||
builder.Services.AddHostedService<ParentProcessSentinel>();
|
builder.Services.AddHostedService<ParentProcessSentinel>();
|
||||||
builder.Services.AddHostedService<GrpcCommandReceiverWorker>();
|
builder.Services.AddHostedService<GatewayService>();
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// 5. 视频流 Target 注册 (gRPC 模式)
|
// 5. 视频流 Target 注册 (gRPC 模式)
|
||||||
@@ -100,7 +100,7 @@ public class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.Services.AddSingleton<IEnumerable<StreamTarget>>(netTargets);
|
builder.Services.AddSingleton<IEnumerable<StreamTarget>>(netTargets);
|
||||||
builder.Services.AddHostedService<NetworkStreamingWorker>();
|
builder.Services.AddHostedService<ImageMonitorController>();
|
||||||
|
|
||||||
// 为每个 Target 绑定一个 gRPC 流发送者
|
// 为每个 Target 绑定一个 gRPC 流发送者
|
||||||
foreach (var target in netTargets)
|
foreach (var target in netTargets)
|
||||||
@@ -112,7 +112,7 @@ public class Program
|
|||||||
// 注册指令分发 (不再使用 NetMQ 的 CommandClientWorker)
|
// 注册指令分发 (不再使用 NetMQ 的 CommandClientWorker)
|
||||||
builder.Services.AddSingleton<InterceptorPipeline>();
|
builder.Services.AddSingleton<InterceptorPipeline>();
|
||||||
builder.Services.AddSingleton<CommandDispatcher>();
|
builder.Services.AddSingleton<CommandDispatcher>();
|
||||||
builder.Services.AddSingleton<ICommandHandler, SyncCameraHandler>();
|
builder.Services.AddSingleton<ICommandHandler, DeviceConfigHandler>();
|
||||||
builder.Services.AddSingleton<ICommandHandler, RemoveCameraHandler>();
|
builder.Services.AddSingleton<ICommandHandler, RemoveCameraHandler>();
|
||||||
|
|
||||||
ConfigureWebServices(builder, config);
|
ConfigureWebServices(builder, config);
|
||||||
|
|||||||
@@ -18,64 +18,7 @@ service GatewayProvider {
|
|||||||
rpc OpenCommandChannel (CommandStreamRequest) returns (stream CommandPayloadProto);
|
rpc OpenCommandChannel (CommandStreamRequest) returns (stream CommandPayloadProto);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 1. 注册相关 ---
|
// --- 通用指令推送通道 ---
|
||||||
message RegisterRequest {
|
|
||||||
// 进程 ID (用于区分同一台机器上的多个实例)
|
|
||||||
int32 process_id = 1;
|
|
||||||
// 调用进程句柄
|
|
||||||
int32 invoke_process_id = 2;
|
|
||||||
// 实例唯一标识符 (例如 "Stream_1")
|
|
||||||
string instance_id = 3;
|
|
||||||
// 软件版本号
|
|
||||||
string version = 4;
|
|
||||||
// 软件所在的局域网 IP
|
|
||||||
string server_ip = 5;
|
|
||||||
// WebAPI 监听端口
|
|
||||||
int32 webapi_port = 6;
|
|
||||||
// Grpc通讯端口
|
|
||||||
int32 grpc_port = 7;
|
|
||||||
// 启动时间
|
|
||||||
int64 start_time_ticks = 9;
|
|
||||||
// 描述信息
|
|
||||||
string description = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 2. 状态上报相关 ---
|
|
||||||
message StatusBatchRequest {
|
|
||||||
string protocol = 1;
|
|
||||||
int64 timestamp = 2;
|
|
||||||
repeated StatusEventItem items = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message StatusEventItem {
|
|
||||||
string camera_id = 1;
|
|
||||||
bool is_online = 2;
|
|
||||||
string reason = 3;
|
|
||||||
int64 timestamp = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 3. 视频流相关 ---
|
|
||||||
message VideoFrameRequest {
|
|
||||||
string camera_id = 1;
|
|
||||||
int64 capture_timestamp = 2;
|
|
||||||
int64 dispatch_timestamp = 3;
|
|
||||||
int32 original_width = 4;
|
|
||||||
int32 original_height = 5;
|
|
||||||
int32 target_width = 6;
|
|
||||||
int32 target_height = 7;
|
|
||||||
repeated string subscriber_ids = 8;
|
|
||||||
map<string, string> diagnostics = 9;
|
|
||||||
bool has_original_image = 10;
|
|
||||||
bool has_target_image = 11;
|
|
||||||
bytes original_image_bytes = 12;
|
|
||||||
bytes target_image_bytes = 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 4. 指令下发相关 (对应 C# CommandPayload) ---
|
|
||||||
message CommandStreamRequest {
|
|
||||||
string instance_id = 1; // 告知服务端我是哪个节点
|
|
||||||
}
|
|
||||||
|
|
||||||
message CommandPayloadProto {
|
message CommandPayloadProto {
|
||||||
string protocol = 1; // 协议类型,默认 "COMMAND"
|
string protocol = 1; // 协议类型,默认 "COMMAND"
|
||||||
string cmd_code = 2; // 指令代码,如 "Sync_Camera"
|
string cmd_code = 2; // 指令代码,如 "Sync_Camera"
|
||||||
@@ -88,6 +31,57 @@ message CommandPayloadProto {
|
|||||||
int64 expire_time = 9; // 过期时间戳
|
int64 expire_time = 9; // 过期时间戳
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 1. 注册相关 ---
|
||||||
|
message RegisterRequest {
|
||||||
|
int32 process_id = 1; // 进程 ID (用于区分同一台机器上的多个实例)
|
||||||
|
int32 invoke_process_id = 2; // 调用进程句柄
|
||||||
|
string instance_id = 3; // 实例唯一标识符 (例如 "Stream_1")
|
||||||
|
string version = 4; // 软件版本号
|
||||||
|
string server_ip = 5; // 软件所在的局域网 IP
|
||||||
|
int32 webapi_port = 6; // WebAPI 监听端口
|
||||||
|
int32 grpc_port = 7; // Grpc通讯端口
|
||||||
|
int64 start_time_ticks = 9; // 启动时间
|
||||||
|
string description = 10; // 描述信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. 状态上报相关 ---
|
||||||
|
message StatusBatchRequest {
|
||||||
|
int64 timestamp = 1; // 上报时间戳
|
||||||
|
repeated StatusEventItem items = 2; // 状态事件列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备状态变更通知包
|
||||||
|
message StatusEventItem {
|
||||||
|
string camera_id = 1; // 摄像头ID
|
||||||
|
bool is_online = 2; // 是否在线
|
||||||
|
string reason = 3; // 状态变更原因描述
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. 视频流传输协议 ---
|
||||||
|
// 职责:承载高频传输的实时视频帧、算法处理图及相关的 AI 诊断元数据
|
||||||
|
message VideoFrameRequest {
|
||||||
|
|
||||||
|
string camera_id = 1; // 摄像头唯一物理标识符
|
||||||
|
int64 capture_timestamp = 2; // 图像在传感器端的原始采集时间戳 (Ticks/Unixms)
|
||||||
|
int64 dispatch_timestamp = 3; // 图像在分析节点端的分发/外传时间戳 (用于测量网络传输耗时)
|
||||||
|
int32 original_width = 4; // 原始采集图像的宽度
|
||||||
|
int32 original_height = 5; // 原始采集图像的高度
|
||||||
|
int32 target_width = 6; // 算法处理(如缩放或裁剪)后的目标图像宽度
|
||||||
|
int32 target_height = 7; // 算法处理后的目标图像高度
|
||||||
|
repeated string subscriber_ids = 8; // 订阅此帧的应用标识列表 (例如: "UI", "AI", "Record")
|
||||||
|
map<string, string> diagnostics = 9; // 诊断与扩展元数据 键值对存储:例如 {"fps": "25", "bitrate": "4Mbps", "algo_latency": "12ms"}
|
||||||
|
bool has_original_image = 10; // 状态标志:包内是否包含原始图像二进制数据
|
||||||
|
bool has_target_image = 11; // 状态标志:包内是否包含算法处理图(或带 OSD 渲染的图)
|
||||||
|
bytes original_image_bytes = 12; // 原始图像二进制数据 (通常为 JPG/NV12 格式)
|
||||||
|
bytes target_image_bytes = 13; // 算法处理图/标注图二进制数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. 指令下发相关 (对应 C# CommandPayload) ---
|
||||||
|
message CommandStreamRequest {
|
||||||
|
string instance_id = 1; // 告知服务端我是哪个节点
|
||||||
|
int32 process_id = 2; // 告知服务端我是哪个进程
|
||||||
|
}
|
||||||
|
|
||||||
message GenericResponse {
|
message GenericResponse {
|
||||||
bool success = 1;
|
bool success = 1;
|
||||||
string message = 2;
|
string message = 2;
|
||||||
|
|||||||
Reference in New Issue
Block a user