增加大华设备对云台移动、缩放、聚集、光圈、校时、重启的支持
增加海康、大华对预置点的支持
This commit is contained in:
14
SHH.CameraSdk/Abstractions/IDahuaContext.cs
Normal file
14
SHH.CameraSdk/Abstractions/IDahuaContext.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace SHH.CameraSdk;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 海康驱动上下文
|
||||||
|
/// 作用:允许功能组件(如校时、云台)访问主驱动的核心数据,而无需公开给外部
|
||||||
|
/// </summary>
|
||||||
|
public interface IDahuaContext
|
||||||
|
{
|
||||||
|
/// <summary> 获取 SDK 登录句柄 (lUserId) </summary>
|
||||||
|
IntPtr GetUserId();
|
||||||
|
|
||||||
|
/// <summary> 获取设备 IP (用于日志) </summary>
|
||||||
|
string GetDeviceIp();
|
||||||
|
}
|
||||||
@@ -20,21 +20,38 @@ public interface ITimeSyncFeature
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRebootFeature
|
public interface IRebootFeature
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>发送重启指令</summary>
|
||||||
/// 发送重启指令
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>任务完成表示指令发送成功</returns>
|
|
||||||
Task RebootAsync();
|
Task RebootAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>能力接口:云台控制</summary>
|
||||||
/// 能力接口:云台控制
|
|
||||||
/// </summary>
|
|
||||||
public interface IPtzFeature
|
public interface IPtzFeature
|
||||||
{
|
{
|
||||||
// 原有的手动控制 (按下/松开)
|
/// <summary>原有的手动控制 (按下/松开)</summary>
|
||||||
Task PtzControlAsync(PtzAction action, bool stop, int speed = 4);
|
Task PtzControlAsync(PtzAction action, bool stop, int speed = 4);
|
||||||
|
|
||||||
// [新增] 点动控制 (自动复位)
|
/// <summary>点动控制 (自动复位)</summary>
|
||||||
Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4);
|
Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [功能接口] 预置点管理服务
|
||||||
|
/// <para>核心职责:抽象各品牌 SDK 的预置点操作,包括跳转、保存与删除</para>
|
||||||
|
/// </summary>
|
||||||
|
public interface IPresetFeature
|
||||||
|
{
|
||||||
|
/// <summary>跳转到指定预置点</summary>
|
||||||
|
/// <param name="presetIndex">预置点编号 (通常范围 1-255)</param>
|
||||||
|
/// <returns>异步任务</returns>
|
||||||
|
Task GotoPresetAsync(int presetIndex);
|
||||||
|
|
||||||
|
/// <summary>将当前位置保存为预置点</summary>
|
||||||
|
/// <param name="presetIndex">预置点编号 (若已存在则通常会覆盖)</param>
|
||||||
|
/// <returns>异步任务</returns>
|
||||||
|
Task SetPresetAsync(int presetIndex);
|
||||||
|
|
||||||
|
/// <summary>删除指定的预置点</summary>
|
||||||
|
/// <param name="presetIndex">预置点编号</param>
|
||||||
|
/// <returns>异步任务</returns>
|
||||||
|
Task RemovePresetAsync(int presetIndex);
|
||||||
|
}
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
using Lennox.LibYuvSharp;
|
using Lennox.LibYuvSharp;
|
||||||
using OpenCvSharp;
|
using OpenCvSharp;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using SHH.CameraSdk.DahuaFeatures;
|
||||||
|
using SHH.CameraSdk.HikFeatures;
|
||||||
|
using System;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using static SHH.CameraSdk.DahuaPlaySDK;
|
using static SHH.CameraSdk.DahuaPlaySDK;
|
||||||
@@ -12,12 +15,15 @@ namespace SHH.CameraSdk;
|
|||||||
/// [大华驱动] 工业级视频源实现 (依照官方 Demo 逻辑重构版)
|
/// [大华驱动] 工业级视频源实现 (依照官方 Demo 逻辑重构版)
|
||||||
/// <para>当前模块: AiVideo | 核心原则: 低耦合、高并发、零拷贝</para>
|
/// <para>当前模块: AiVideo | 核心原则: 低耦合、高并发、零拷贝</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DahuaVideoSource : BaseVideoSource
|
public class DahuaVideoSource : BaseVideoSource,
|
||||||
|
IDahuaContext, ITimeSyncFeature, IRebootFeature, IPtzFeature, IPresetFeature
|
||||||
{
|
{
|
||||||
protected override ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.DaHuaSdk);
|
|
||||||
|
|
||||||
#region --- 1. 静态资源与回调持有 (Static Resources) ---
|
#region --- 1. 静态资源与回调持有 (Static Resources) ---
|
||||||
|
|
||||||
|
/// <summary> 大华 SDK 专用日志实例 </summary>
|
||||||
|
protected override ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.DaHuaSdk);
|
||||||
|
|
||||||
|
/// <summary> 全局句柄映射表:用于静态异常回调分发至具体实例 </summary>
|
||||||
private static readonly ConcurrentDictionary<IntPtr, DahuaVideoSource> _instances = new();
|
private static readonly ConcurrentDictionary<IntPtr, DahuaVideoSource> _instances = new();
|
||||||
|
|
||||||
// 必须保持静态引用,防止被 GC 回收导致回调崩溃
|
// 必须保持静态引用,防止被 GC 回收导致回调崩溃
|
||||||
@@ -29,6 +35,11 @@ public class DahuaVideoSource : BaseVideoSource
|
|||||||
|
|
||||||
#region --- 2. 实例成员 (Instance Members) ---
|
#region --- 2. 实例成员 (Instance Members) ---
|
||||||
|
|
||||||
|
private readonly DahuaRebootProvider _rebootProvider;
|
||||||
|
private readonly DahuaTimeSyncProvider _timeProvider;
|
||||||
|
private readonly DahuaPtzProvider _ptzProvider;
|
||||||
|
private readonly DahuaPresetProvider _presetProvider;
|
||||||
|
|
||||||
private IntPtr _loginId = IntPtr.Zero;
|
private IntPtr _loginId = IntPtr.Zero;
|
||||||
private IntPtr _realPlayId = IntPtr.Zero;
|
private IntPtr _realPlayId = IntPtr.Zero;
|
||||||
private int _playPort = -1;
|
private int _playPort = -1;
|
||||||
@@ -42,9 +53,76 @@ public class DahuaVideoSource : BaseVideoSource
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public DahuaVideoSource(VideoSourceConfig config) : base(config) { }
|
#region --- 3. 构造函数 (Constructor) ---
|
||||||
|
|
||||||
#region --- 3. 生命周期实现 (Lifecycle Overrides) ---
|
/// <summary>大华视频源实现</summary>
|
||||||
|
/// <param name="config"></param>
|
||||||
|
public DahuaVideoSource(VideoSourceConfig config) : base(config)
|
||||||
|
{
|
||||||
|
_rebootProvider = new DahuaRebootProvider(this);
|
||||||
|
_timeProvider = new DahuaTimeSyncProvider(this);
|
||||||
|
_ptzProvider = new DahuaPtzProvider(this);
|
||||||
|
_presetProvider = new DahuaPresetProvider(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region --- 4. 接口实现:IHikContext & Features (Interface Impls) ---
|
||||||
|
|
||||||
|
/// <summary>获取登录句柄</summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IntPtr GetUserId() => _loginId; // 暴露父类或私有的 _loginId
|
||||||
|
|
||||||
|
/// <summary>获取设备IP</summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetDeviceIp() => Config.IpAddress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 核心逻辑:全部委托给 _timeProvider 处理,自己不写一行逻辑
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<DateTime> GetTimeAsync() => _timeProvider.GetTimeAsync();
|
||||||
|
|
||||||
|
/// <summary>设置设备时间</summary>
|
||||||
|
/// <param name="time"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task SetTimeAsync(DateTime time) => _timeProvider.SetTimeAsync(time);
|
||||||
|
|
||||||
|
/// <summary>重启设备</summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task RebootAsync() => _rebootProvider.RebootAsync();
|
||||||
|
|
||||||
|
/// <summary>PTZ 控制</summary>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
/// <param name="stop"></param>
|
||||||
|
/// <param name="speed"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task PtzControlAsync(PtzAction action, bool stop, int speed = 4)
|
||||||
|
=> _ptzProvider.PtzControlAsync(action, stop, speed);
|
||||||
|
|
||||||
|
/// <summary>PTZ 步长</summary>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
/// <param name="durationMs"></param>
|
||||||
|
/// <param name="speed"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4)
|
||||||
|
=> _ptzProvider.PtzStepAsync(action, durationMs, speed);
|
||||||
|
|
||||||
|
/// <summary>跳转到预置点</summary>
|
||||||
|
public Task GotoPresetAsync(int presetIndex)
|
||||||
|
=> _presetProvider.GotoPresetAsync(presetIndex);
|
||||||
|
|
||||||
|
/// <summary>设置/保存当前位置为预置点</summary>
|
||||||
|
public Task SetPresetAsync(int presetIndex)
|
||||||
|
=> _presetProvider.SetPresetAsync(presetIndex);
|
||||||
|
|
||||||
|
/// <summary>删除预置点</summary>
|
||||||
|
public Task RemovePresetAsync(int presetIndex)
|
||||||
|
=> _presetProvider.RemovePresetAsync(presetIndex);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region --- 5. 生命周期实现 (Lifecycle Overrides) ---
|
||||||
|
|
||||||
protected override async Task OnStartAsync(CancellationToken token)
|
protected override async Task OnStartAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
@@ -137,7 +215,7 @@ public class DahuaVideoSource : BaseVideoSource
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region --- 4. 核心逻辑:解码与分发 (Core Logic) ---
|
#region --- 6. 核心逻辑:解码与分发 (Core Logic) ---
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 静态回调:分发数据至具体实例
|
/// 静态回调:分发数据至具体实例
|
||||||
@@ -325,8 +403,8 @@ public class DahuaVideoSource : BaseVideoSource
|
|||||||
// 如果发现图像发蓝,请将 pU 和 pV 的位置对调
|
// 如果发现图像发蓝,请将 pU 和 pV 的位置对调
|
||||||
LibYuv.I420ToRGB24(
|
LibYuv.I420ToRGB24(
|
||||||
pY, width,
|
pY, width,
|
||||||
pU, width / 2,
|
|
||||||
pV, width / 2,
|
pV, width / 2,
|
||||||
|
pU, width / 2,
|
||||||
pDst, width * 3,
|
pDst, width * 3,
|
||||||
width, height
|
width, height
|
||||||
);
|
);
|
||||||
@@ -378,7 +456,7 @@ public class DahuaVideoSource : BaseVideoSource
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region --- 5. 静态初始化器 (Statics) ---
|
#region --- 7. 静态初始化器 (Statics) ---
|
||||||
|
|
||||||
private static void InitSdkGlobal()
|
private static void InitSdkGlobal()
|
||||||
{
|
{
|
||||||
|
|||||||
81
SHH.CameraSdk/Drivers/DaHua/Features/DahuaPresetProvider.cs
Normal file
81
SHH.CameraSdk/Drivers/DaHua/Features/DahuaPresetProvider.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using Serilog;
|
||||||
|
using Ayay.SerilogLogs;
|
||||||
|
|
||||||
|
namespace SHH.CameraSdk.DahuaFeatures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [大华功能组件] 预置点管理实现
|
||||||
|
/// <para>适配说明:使用 NETClient.PTZControl 接口,指令码为 10 (PTZ_POINT_CONTROL)</para>
|
||||||
|
/// </summary>
|
||||||
|
public class DahuaPresetProvider : IPresetFeature
|
||||||
|
{
|
||||||
|
private ILogger _sdkLog = Log.ForContext("SourceContext", LogModules.DaHuaSdk);
|
||||||
|
|
||||||
|
private readonly IDahuaContext _context;
|
||||||
|
|
||||||
|
// 大华底层预置点控制命令常量
|
||||||
|
private const uint PTZ_POINT_CONTROL = 10;
|
||||||
|
|
||||||
|
public DahuaPresetProvider(IDahuaContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>跳转到预置点</summary>
|
||||||
|
/// <param name="presetIndex">预置点编号 (1-255)</param>
|
||||||
|
public async Task GotoPresetAsync(int presetIndex)
|
||||||
|
{
|
||||||
|
await ExecutePresetAction(presetIndex, 2, "调用");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>设置/保存当前位置为预置点</summary>
|
||||||
|
public async Task SetPresetAsync(int presetIndex)
|
||||||
|
{
|
||||||
|
await ExecutePresetAction(presetIndex, 0, "保存");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>删除预置点</summary>
|
||||||
|
public async Task RemovePresetAsync(int presetIndex)
|
||||||
|
{
|
||||||
|
await ExecutePresetAction(presetIndex, 1, "删除");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>统一执行预置点动作</summary>
|
||||||
|
/// <param name="index">编号</param>
|
||||||
|
/// <param name="actionType">0:保存, 1:删除, 2:跳转</param>
|
||||||
|
/// <param name="actionName">日志描述</param>
|
||||||
|
private async Task ExecutePresetAction(int index, int actionType, string actionName)
|
||||||
|
{
|
||||||
|
IntPtr loginId = _context.GetUserId();
|
||||||
|
if (loginId == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
// Modified: [原因] 严格适配 NETClient.PTZControl 的 8 参数签名
|
||||||
|
// lParam1: 0 (无意义)
|
||||||
|
// lParam2: 预置点值 (nIndex)
|
||||||
|
// lParam3: 动作类型 (0-保存, 1-删除, 2-跳转)
|
||||||
|
bool result = NETClient.PTZControl(
|
||||||
|
loginId,
|
||||||
|
0, // nChannelID
|
||||||
|
PTZ_POINT_CONTROL, // dwPTZCommand = 10
|
||||||
|
0, // lParam1
|
||||||
|
index, // lParam2: 预置点编号
|
||||||
|
actionType, // lParam3: 动作类型
|
||||||
|
false, // dwStop: 预置点操作不涉及停止位
|
||||||
|
IntPtr.Zero // param4
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
string error = NETClient.GetLastError();
|
||||||
|
_sdkLog.Warning("[SDK] Dahua 预置点{Action}失败. Index: {Index}, Error: {Error}",
|
||||||
|
actionName, index, error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_sdkLog.Debug("[SDK] Dahua 预置点{Action}成功. Index: {Index}", actionName, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
100
SHH.CameraSdk/Drivers/DaHua/Features/DahuaPtzProvider.cs
Normal file
100
SHH.CameraSdk/Drivers/DaHua/Features/DahuaPtzProvider.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using Ayay.SerilogLogs;
|
||||||
|
using Serilog;
|
||||||
|
using SHH.CameraSdk.HikFeatures;
|
||||||
|
|
||||||
|
namespace SHH.CameraSdk.DahuaFeatures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [大华功能组件] 云台与镜头控制
|
||||||
|
/// <para>适配说明:严格匹配 NETClient.PTZControl(IntPtr, int, uint, int, int, int, bool, IntPtr) 接口</para>
|
||||||
|
/// </summary>
|
||||||
|
public class DahuaPtzProvider : IPtzFeature
|
||||||
|
{
|
||||||
|
private readonly IDahuaContext _context;
|
||||||
|
|
||||||
|
#region --- 大华 PTZ 命令常量 (对应 dwPTZCommand) ---
|
||||||
|
|
||||||
|
private const uint PTZ_UP = 0;
|
||||||
|
private const uint PTZ_DOWN = 1;
|
||||||
|
private const uint PTZ_LEFT = 2;
|
||||||
|
private const uint PTZ_RIGHT = 3;
|
||||||
|
private const uint PTZ_ZOOM_ADD = 4; // 变倍+
|
||||||
|
private const uint PTZ_ZOOM_DEC = 5; // 变倍-
|
||||||
|
private const uint PTZ_FOCUS_ADD = 6; // 聚焦+
|
||||||
|
private const uint PTZ_FOCUS_DEC = 7; // 聚焦-
|
||||||
|
private const uint PTZ_IRIS_ADD = 8; // 光圈+
|
||||||
|
private const uint PTZ_IRIS_DEC = 9; // 光圈-
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public DahuaPtzProvider(IDahuaContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PtzControlAsync(PtzAction action, bool stop, int speed)
|
||||||
|
{
|
||||||
|
IntPtr loginId = _context.GetUserId();
|
||||||
|
if (loginId == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
// 1. 映射指令
|
||||||
|
uint dwCommand = action switch
|
||||||
|
{
|
||||||
|
PtzAction.Up => PTZ_UP,
|
||||||
|
PtzAction.Down => PTZ_DOWN,
|
||||||
|
PtzAction.Left => PTZ_LEFT,
|
||||||
|
PtzAction.Right => PTZ_RIGHT,
|
||||||
|
PtzAction.ZoomIn => PTZ_ZOOM_ADD,
|
||||||
|
PtzAction.ZoomOut => PTZ_ZOOM_DEC,
|
||||||
|
PtzAction.FocusFar => PTZ_FOCUS_ADD,
|
||||||
|
PtzAction.FocusNear => PTZ_FOCUS_DEC,
|
||||||
|
PtzAction.IrisOpen => PTZ_IRIS_ADD,
|
||||||
|
PtzAction.IrisClose => PTZ_IRIS_DEC,
|
||||||
|
_ => 999
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dwCommand == 999) return;
|
||||||
|
|
||||||
|
// 2. 准备速度参数 (大华一般 1-8)
|
||||||
|
// Modified: [原因] 严格适配 8 参数接口。lParam1=水平速度, lParam2=垂直速度, lParam3=0
|
||||||
|
int s = Math.Clamp(speed, 1, 8);
|
||||||
|
int lParam1 = s;
|
||||||
|
int lParam2 = s;
|
||||||
|
int lParam3 = 0;
|
||||||
|
|
||||||
|
// 3. 调用你提供的接口
|
||||||
|
// Modified: [原因] 匹配签名: (IntPtr, int, uint, int, int, int, bool, IntPtr)
|
||||||
|
bool result = NETClient.PTZControl(
|
||||||
|
loginId,
|
||||||
|
0, // nChannelID
|
||||||
|
dwCommand, // dwPTZCommand
|
||||||
|
lParam1,
|
||||||
|
lParam2,
|
||||||
|
lParam3,
|
||||||
|
stop, // dwStop
|
||||||
|
IntPtr.Zero // param4
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
string error = NETClient.GetLastError();
|
||||||
|
Log.ForContext("SourceContext", LogModules.DaHuaSdk)
|
||||||
|
.Warning("[SDK] Dahua PTZ 失败. Action: {Action}, Stop: {Stop}, Error: {Error}, 可能操作太快.",
|
||||||
|
action, stop, error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PtzStepAsync(PtzAction action, int durationMs, int speed)
|
||||||
|
{
|
||||||
|
await PtzControlAsync(action, false, speed);
|
||||||
|
await Task.Delay(durationMs);
|
||||||
|
await PtzControlAsync(action, true, speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
SHH.CameraSdk/Drivers/DaHua/Features/DahuaRebootProvider.cs
Normal file
44
SHH.CameraSdk/Drivers/DaHua/Features/DahuaRebootProvider.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Serilog;
|
||||||
|
using Ayay.SerilogLogs;
|
||||||
|
|
||||||
|
namespace SHH.CameraSdk.DahuaFeatures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [大华功能组件] 远程重启实现
|
||||||
|
/// </summary>
|
||||||
|
public class DahuaRebootProvider : IRebootFeature
|
||||||
|
{
|
||||||
|
private ILogger _sdkLog = Log.ForContext("SourceContext", LogModules.DaHuaSdk);
|
||||||
|
|
||||||
|
private readonly IDahuaContext _context;
|
||||||
|
|
||||||
|
public DahuaRebootProvider(IDahuaContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>执行异步重启</summary>
|
||||||
|
public async Task RebootAsync()
|
||||||
|
{
|
||||||
|
// 1. 检查登录状态 (参照海康逻辑)
|
||||||
|
IntPtr loginId = _context.GetUserId();
|
||||||
|
if (loginId == IntPtr.Zero)
|
||||||
|
throw new InvalidOperationException("大华设备未登录或句柄失效,无法发送重启指令");
|
||||||
|
|
||||||
|
// 2. 执行 SDK 调用
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
bool result = NETClient.ControlDevice(loginId, EM_CtrlType.REBOOT, IntPtr.Zero, 5000);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
string err = NETClient.GetLastError();
|
||||||
|
_sdkLog.Error("[SDK] Dahua 重启指令下发失败. Error: {Error}", err);
|
||||||
|
|
||||||
|
throw new Exception($"大华重启指令发送失败,错误码: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_sdkLog.Information("[SDK] Dahua 重启指令下发成功,设备即将断开连接。");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
101
SHH.CameraSdk/Drivers/DaHua/Features/DahuaTimeSyncProvider.cs
Normal file
101
SHH.CameraSdk/Drivers/DaHua/Features/DahuaTimeSyncProvider.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Serilog;
|
||||||
|
using Ayay.SerilogLogs;
|
||||||
|
|
||||||
|
namespace SHH.CameraSdk.DahuaFeatures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [大华功能组件] 时间同步实现
|
||||||
|
/// <para>参照原代码逻辑重构,实现 EM_DEV_CFG_TYPE.TIMECFG 配置下发</para>
|
||||||
|
/// </summary>
|
||||||
|
public class DahuaTimeSyncProvider : ITimeSyncFeature
|
||||||
|
{
|
||||||
|
private readonly IDahuaContext _context;
|
||||||
|
|
||||||
|
public DahuaTimeSyncProvider(IDahuaContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取设备当前时间
|
||||||
|
/// </summary>
|
||||||
|
public async Task<DateTime> GetTimeAsync()
|
||||||
|
{
|
||||||
|
IntPtr loginId = _context.GetUserId();
|
||||||
|
if (loginId == IntPtr.Zero) throw new InvalidOperationException("大华设备未登录");
|
||||||
|
|
||||||
|
return await Task.Run(() =>
|
||||||
|
{
|
||||||
|
NET_TIME time = new NET_TIME();
|
||||||
|
uint retLen = 0;
|
||||||
|
int nSize = Marshal.SizeOf(typeof(NET_TIME));
|
||||||
|
IntPtr inPtr = Marshal.AllocHGlobal(nSize);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Optimized: [原因] 沿用原代码的 GetDevConfig 逻辑与 TIMECFG 指令
|
||||||
|
Marshal.StructureToPtr(time, inPtr, true);
|
||||||
|
bool result = NETClient.GetDevConfig(loginId, EM_DEV_CFG_TYPE.TIMECFG, -1, inPtr, (uint)nSize, ref retLen, 5000);
|
||||||
|
|
||||||
|
if (result && retLen == (uint)nSize)
|
||||||
|
{
|
||||||
|
time = (NET_TIME)Marshal.PtrToStructure(inPtr, typeof(NET_TIME));
|
||||||
|
// 使用你现有的 ToDateTime() 扩展方法
|
||||||
|
return time.ToDateTime();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string err = NETClient.GetLastError();
|
||||||
|
throw new Exception($"[SDK] Dahua 获取时间失败: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(inPtr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置设备时间 (校时)
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetTimeAsync(DateTime targetTime)
|
||||||
|
{
|
||||||
|
IntPtr loginId = _context.GetUserId();
|
||||||
|
if (loginId == IntPtr.Zero) throw new InvalidOperationException("大华设备未登录");
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
// Modified: [原因] 使用你原有的 FromDateTime 静态方法进行结构体转换
|
||||||
|
NET_TIME time = NET_TIME.FromDateTime(targetTime);
|
||||||
|
int nSize = Marshal.SizeOf(typeof(NET_TIME));
|
||||||
|
IntPtr inPtr = Marshal.AllocHGlobal(nSize);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Marshal.StructureToPtr(time, inPtr, true);
|
||||||
|
|
||||||
|
// Optimized: [原因] 沿用原代码的 SetDevConfig 逻辑
|
||||||
|
bool result = NETClient.SetDevConfig(loginId, EM_DEV_CFG_TYPE.TIMECFG, -1, inPtr, (uint)nSize, 5000);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", LogModules.DaHuaSdk)
|
||||||
|
.Information("[SDK] Dahua 校时成功 => {Time}", targetTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string err = NETClient.GetLastError();
|
||||||
|
Log.ForContext("SourceContext", LogModules.DaHuaSdk)
|
||||||
|
.Error("[SDK] Dahua 校时指令失败. Error: {Error}", err);
|
||||||
|
throw new Exception($"大华校时失败: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(inPtr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
using System;
|
namespace SHH.CameraSdk
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace SHH.CameraSdk
|
|
||||||
{
|
{
|
||||||
public static class NETClient
|
public static class NETClient
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
using Serilog;
|
||||||
|
using Ayay.SerilogLogs;
|
||||||
|
|
||||||
|
namespace SHH.CameraSdk.HikFeatures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [海康功能组件] 预置点管理实现
|
||||||
|
/// <para>适配说明:使用 NET_DVR_PTZPreset_Other 接口</para>
|
||||||
|
/// </summary>
|
||||||
|
public class HikPresetProvider : IPresetFeature
|
||||||
|
{
|
||||||
|
private readonly IHikContext _context;
|
||||||
|
|
||||||
|
#region --- 海康 PTZ 预置点命令常量 ---
|
||||||
|
|
||||||
|
private const uint SET_PRESET = 8; // 设置预置点
|
||||||
|
private const uint CLE_PRESET = 9; // 清除预置点
|
||||||
|
private const uint GOTO_PRESET = 39; // 转到预置点
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public HikPresetProvider(IHikContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>跳转到指定预置点</summary>
|
||||||
|
public async Task GotoPresetAsync(int presetIndex)
|
||||||
|
{
|
||||||
|
await ExecutePresetAction(presetIndex, GOTO_PRESET, "调用");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>将当前位置保存为预置点</summary>
|
||||||
|
public async Task SetPresetAsync(int presetIndex)
|
||||||
|
{
|
||||||
|
await ExecutePresetAction(presetIndex, SET_PRESET, "保存");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>删除指定的预置点</summary>
|
||||||
|
public async Task RemovePresetAsync(int presetIndex)
|
||||||
|
{
|
||||||
|
await ExecutePresetAction(presetIndex, CLE_PRESET, "删除");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>统一执行海康预置点 SDK 调用</summary>
|
||||||
|
private async Task ExecutePresetAction(int index, uint command, string actionName)
|
||||||
|
{
|
||||||
|
int userId = _context.GetUserId();
|
||||||
|
if (userId < 0) return;
|
||||||
|
|
||||||
|
// 位置是从 1 开始, 调用 0 会导致设备重启
|
||||||
|
if (index == 0) return;
|
||||||
|
|
||||||
|
// 海康工业相机通道号通常为 1
|
||||||
|
int channel = 1;
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
// Optimized: [原因] 使用 _Other 接口确保在单机多路并发下通道句柄准确
|
||||||
|
bool result = HikNativeMethods.NET_DVR_PTZPreset_Other(
|
||||||
|
userId,
|
||||||
|
channel,
|
||||||
|
command,
|
||||||
|
(uint)index
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
uint err = HikNativeMethods.NET_DVR_GetLastError();
|
||||||
|
Log.ForContext("SourceContext", LogModules.HikVisionSdk)
|
||||||
|
.Warning("[SDK] Hik 预置点{Action}失败. Index: {Index}, Error: {Error}",
|
||||||
|
actionName, index, err);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", LogModules.HikVisionSdk)
|
||||||
|
.Debug("[SDK] Hik 预置点{Action}成功. Index: {Index}", actionName, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
namespace SHH.CameraSdk.HikFeatures;
|
using Ayay.SerilogLogs;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace SHH.CameraSdk.HikFeatures;
|
||||||
|
|
||||||
public class HikRebootProvider : IRebootFeature
|
public class HikRebootProvider : IRebootFeature
|
||||||
{
|
{
|
||||||
|
private ILogger _sdkLog = Log.ForContext("SourceContext", LogModules.HikVisionSdk);
|
||||||
|
|
||||||
private readonly IHikContext _context;
|
private readonly IHikContext _context;
|
||||||
|
|
||||||
public HikRebootProvider(IHikContext context)
|
public HikRebootProvider(IHikContext context)
|
||||||
@@ -9,6 +14,7 @@ public class HikRebootProvider : IRebootFeature
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>执行异步重启</summary>
|
||||||
public async Task RebootAsync()
|
public async Task RebootAsync()
|
||||||
{
|
{
|
||||||
// 1. 检查登录状态
|
// 1. 检查登录状态
|
||||||
@@ -23,14 +29,12 @@ public class HikRebootProvider : IRebootFeature
|
|||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
uint err = HikNativeMethods.NET_DVR_GetLastError();
|
uint err = HikNativeMethods.NET_DVR_GetLastError();
|
||||||
|
_sdkLog.Error("[SDK] Hik 重启指令下发失败. Error: {Error}", err);
|
||||||
|
|
||||||
throw new Exception($"重启指令发送失败,错误码: {err}");
|
throw new Exception($"重启指令发送失败,错误码: {err}");
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 注意:
|
_sdkLog.Information("[SDK] Hik 重启指令下发成功,设备即将断开连接。");
|
||||||
// 重启指令发送成功后,设备会断开网络。
|
});
|
||||||
// 宿主类(HikVideoSource)的保活机制(KeepAlive)会检测到断线,
|
|
||||||
// 并自动开始尝试重连,直到设备重启完成上线。
|
|
||||||
// 所以这里我们不需要手动断开连接,交给底层自愈机制即可。
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,18 +349,14 @@ public static partial class HikNativeMethods
|
|||||||
[DllImport(DllName)]
|
[DllImport(DllName)]
|
||||||
public static extern uint NET_DVR_GetLastError();
|
public static extern uint NET_DVR_GetLastError();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>设置网络连接超时时间和连接尝试次数</summary>
|
||||||
/// 设置网络连接超时时间和连接尝试次数
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dwWaitTime">超时时间(毫秒),推荐 3000ms</param>
|
/// <param name="dwWaitTime">超时时间(毫秒),推荐 3000ms</param>
|
||||||
/// <param name="dwTryTimes">连接尝试次数,推荐 1 次</param>
|
/// <param name="dwTryTimes">连接尝试次数,推荐 1 次</param>
|
||||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||||
[DllImport(DllName)]
|
[DllImport(DllName)]
|
||||||
public static extern bool NET_DVR_SetConnectTime(uint dwWaitTime, uint dwTryTimes);
|
public static extern bool NET_DVR_SetConnectTime(uint dwWaitTime, uint dwTryTimes);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>设置自动重连功能</summary>
|
||||||
/// 设置自动重连功能
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dwInterval">重连间隔(毫秒),推荐 10000ms</param>
|
/// <param name="dwInterval">重连间隔(毫秒),推荐 10000ms</param>
|
||||||
/// <param name="bEnableRecon">是否启用重连:0-禁用,1-启用</param>
|
/// <param name="bEnableRecon">是否启用重连:0-禁用,1-启用</param>
|
||||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||||
@@ -461,9 +457,7 @@ public static partial class HikNativeMethods
|
|||||||
|
|
||||||
#region --- 异常回调接口 (Exception Callback Interfaces) ---
|
#region --- 异常回调接口 (Exception Callback Interfaces) ---
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>设置连接超时时间和重连策略(兼容旧版本)</summary>
|
||||||
/// 设置连接超时时间和重连策略(兼容旧版本)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dwInterval">重连间隔(毫秒),建议 3000</param>
|
/// <param name="dwInterval">重连间隔(毫秒),建议 3000</param>
|
||||||
/// <param name="bEnableRecon">是否启用重连:1-启用,0-禁用</param>
|
/// <param name="bEnableRecon">是否启用重连:1-启用,0-禁用</param>
|
||||||
/// <returns>设置成功返回 true,失败返回 false</returns>
|
/// <returns>设置成功返回 true,失败返回 false</returns>
|
||||||
@@ -512,11 +506,20 @@ public static partial class HikNativeMethods
|
|||||||
[DllImport(DllName)]
|
[DllImport(DllName)]
|
||||||
public static extern bool NET_DVR_SetDVRConfig(int lUserID, uint dwCommand, int lChannel, System.IntPtr lpInBuffer, uint dwInBufferSize);
|
public static extern bool NET_DVR_SetDVRConfig(int lUserID, uint dwCommand, int lChannel, System.IntPtr lpInBuffer, uint dwInBufferSize);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>设备重启</summary>
|
||||||
/// 设备重启
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lUserID"></param>
|
/// <param name="lUserID"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[DllImport(DllName)]
|
[DllImport(DllName)]
|
||||||
public static extern bool NET_DVR_RebootDVR(int lUserID);
|
public static extern bool NET_DVR_RebootDVR(int lUserID);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [海康 SDK 调用] 云台预置点配置(扩展)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lUserID">NET_DVR_Login_V40 的返回值</param>
|
||||||
|
/// <param name="lChannel">通道号 (工业相机通常为 1)</param>
|
||||||
|
/// <param name="dwPTZPresetCmd">预置点操作命令 (见下文枚举)</param>
|
||||||
|
/// <param name="dwPresetIndex">预置点序号 (1~255)</param>
|
||||||
|
/// <returns>TRUE表示成功,FALSE表示失败</returns>
|
||||||
|
[DllImport(DllName)]
|
||||||
|
public static extern bool NET_DVR_PTZPreset_Other(int lUserID, int lChannel, uint dwPTZPresetCmd, uint dwPresetIndex);
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ namespace SHH.CameraSdk;
|
|||||||
/// <para>✅ 4. [Feat C] 性能优化:在解码回调中使用 <see cref="Monitor.TryEnter"/> 竞争锁,有效规避在设备断开瞬间可能产生的驱动层死锁</para>
|
/// <para>✅ 4. [Feat C] 性能优化:在解码回调中使用 <see cref="Monitor.TryEnter"/> 竞争锁,有效规避在设备断开瞬间可能产生的驱动层死锁</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class HikVideoSource : BaseVideoSource,
|
public class HikVideoSource : BaseVideoSource,
|
||||||
IHikContext, ITimeSyncFeature, IRebootFeature, IPtzFeature
|
IHikContext, ITimeSyncFeature, IRebootFeature, IPtzFeature, IPresetFeature
|
||||||
{
|
{
|
||||||
#region --- 1. 静态资源与全局路由 (Static Resources) ---
|
#region --- 1. 静态资源与全局路由 (Static Resources) ---
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ public class HikVideoSource : BaseVideoSource,
|
|||||||
private readonly HikTimeSyncProvider _timeProvider;
|
private readonly HikTimeSyncProvider _timeProvider;
|
||||||
private readonly HikRebootProvider _rebootProvider;
|
private readonly HikRebootProvider _rebootProvider;
|
||||||
private readonly HikPtzProvider _ptzProvider;
|
private readonly HikPtzProvider _ptzProvider;
|
||||||
|
private readonly HikPresetProvider _presetProvider;
|
||||||
|
|
||||||
// SDK 句柄与资源
|
// SDK 句柄与资源
|
||||||
private int _userId = -1; // SDK 登录句柄
|
private int _userId = -1; // SDK 登录句柄
|
||||||
@@ -68,9 +69,7 @@ public class HikVideoSource : BaseVideoSource,
|
|||||||
|
|
||||||
#region --- 3. 构造函数 (Constructor) ---
|
#region --- 3. 构造函数 (Constructor) ---
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>海康视频源实现</summary>
|
||||||
/// 海康视频源实现
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="config"></param>
|
/// <param name="config"></param>
|
||||||
public HikVideoSource(VideoSourceConfig config) : base(config)
|
public HikVideoSource(VideoSourceConfig config) : base(config)
|
||||||
{
|
{
|
||||||
@@ -78,6 +77,7 @@ public class HikVideoSource : BaseVideoSource,
|
|||||||
_timeProvider = new HikTimeSyncProvider(this);
|
_timeProvider = new HikTimeSyncProvider(this);
|
||||||
_rebootProvider = new HikRebootProvider(this);
|
_rebootProvider = new HikRebootProvider(this);
|
||||||
_ptzProvider = new HikPtzProvider(this);
|
_ptzProvider = new HikPtzProvider(this);
|
||||||
|
_presetProvider = new HikPresetProvider(this);
|
||||||
|
|
||||||
// Modified: [Fix GC Crash] 移除此处的 new REALDATACALLBACK
|
// Modified: [Fix GC Crash] 移除此处的 new REALDATACALLBACK
|
||||||
// 直接使用构造函数初始化的 _realDataCallBack,保证委托地址在整个对象生命周期内不变
|
// 直接使用构造函数初始化的 _realDataCallBack,保证委托地址在整个对象生命周期内不变
|
||||||
@@ -89,15 +89,11 @@ public class HikVideoSource : BaseVideoSource,
|
|||||||
|
|
||||||
#region --- 4. 接口实现:IHikContext & Features (Interface Impls) ---
|
#region --- 4. 接口实现:IHikContext & Features (Interface Impls) ---
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>获取登录句柄</summary>
|
||||||
/// 获取登录句柄
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public int GetUserId() => _userId; // 暴露父类或私有的 _userId
|
public int GetUserId() => _userId; // 暴露父类或私有的 _userId
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>获取设备IP</summary>
|
||||||
/// 获取设备IP
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public string GetDeviceIp() => Config.IpAddress;
|
public string GetDeviceIp() => Config.IpAddress;
|
||||||
|
|
||||||
@@ -107,22 +103,16 @@ public class HikVideoSource : BaseVideoSource,
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task<DateTime> GetTimeAsync() => _timeProvider.GetTimeAsync();
|
public Task<DateTime> GetTimeAsync() => _timeProvider.GetTimeAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>设置设备时间</summary>
|
||||||
/// 设置设备时间
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="time"></param>
|
/// <param name="time"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task SetTimeAsync(DateTime time) => _timeProvider.SetTimeAsync(time);
|
public Task SetTimeAsync(DateTime time) => _timeProvider.SetTimeAsync(time);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>重启设备</summary>
|
||||||
/// 重启设备
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task RebootAsync() => _rebootProvider.RebootAsync();
|
public Task RebootAsync() => _rebootProvider.RebootAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>PTZ 控制</summary>
|
||||||
/// PTZ 控制
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="action"></param>
|
/// <param name="action"></param>
|
||||||
/// <param name="stop"></param>
|
/// <param name="stop"></param>
|
||||||
/// <param name="speed"></param>
|
/// <param name="speed"></param>
|
||||||
@@ -130,9 +120,7 @@ public class HikVideoSource : BaseVideoSource,
|
|||||||
public Task PtzControlAsync(PtzAction action, bool stop, int speed = 4)
|
public Task PtzControlAsync(PtzAction action, bool stop, int speed = 4)
|
||||||
=> _ptzProvider.PtzControlAsync(action, stop, speed);
|
=> _ptzProvider.PtzControlAsync(action, stop, speed);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>PTZ 步长</summary>
|
||||||
/// PTZ 步长
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="action"></param>
|
/// <param name="action"></param>
|
||||||
/// <param name="durationMs"></param>
|
/// <param name="durationMs"></param>
|
||||||
/// <param name="speed"></param>
|
/// <param name="speed"></param>
|
||||||
@@ -140,6 +128,18 @@ public class HikVideoSource : BaseVideoSource,
|
|||||||
public Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4)
|
public Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4)
|
||||||
=> _ptzProvider.PtzStepAsync(action, durationMs, speed);
|
=> _ptzProvider.PtzStepAsync(action, durationMs, speed);
|
||||||
|
|
||||||
|
/// <summary>跳转到预置点</summary>
|
||||||
|
public Task GotoPresetAsync(int presetIndex)
|
||||||
|
=> _presetProvider.GotoPresetAsync(presetIndex);
|
||||||
|
|
||||||
|
/// <summary>设置/保存当前位置为预置点</summary>
|
||||||
|
public Task SetPresetAsync(int presetIndex)
|
||||||
|
=> _presetProvider.SetPresetAsync(presetIndex);
|
||||||
|
|
||||||
|
/// <summary>删除预置点</summary>
|
||||||
|
public Task RemovePresetAsync(int presetIndex)
|
||||||
|
=> _presetProvider.RemovePresetAsync(presetIndex);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region --- 5. 生命周期重写 (Lifecycle Overrides) ---
|
#region --- 5. 生命周期重写 (Lifecycle Overrides) ---
|
||||||
|
|||||||
97
SHH.CameraService/GrpcImpls/Handlers/PresetControlHandler.cs
Normal file
97
SHH.CameraService/GrpcImpls/Handlers/PresetControlHandler.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using Ayay.SerilogLogs;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Serilog;
|
||||||
|
using SHH.CameraSdk;
|
||||||
|
using SHH.Contracts;
|
||||||
|
|
||||||
|
namespace SHH.CameraService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预置点控制指令处理器
|
||||||
|
/// 响应 gRpc 指令:ProtocolCodes.Preset_Control
|
||||||
|
/// </summary>
|
||||||
|
public class PresetControlHandler : ICommandHandler
|
||||||
|
{
|
||||||
|
private readonly ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
|
||||||
|
private readonly CameraManager _cameraManager;
|
||||||
|
|
||||||
|
/// <summary>指令名称(需与网关下发的 CmdCode 一致)</summary>
|
||||||
|
public string ActionName => ProtocolCodes.Device_Preset;
|
||||||
|
|
||||||
|
public PresetControlHandler(CameraManager cameraManager)
|
||||||
|
{
|
||||||
|
_cameraManager = cameraManager ?? throw new ArgumentNullException(nameof(cameraManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(JToken payload)
|
||||||
|
{
|
||||||
|
// 1. 解析预置点控制参数
|
||||||
|
// 假设 PresetControlDto 包含 DeviceId, PresetIndex, 和 Action (GOTO/SET/REMOVE)
|
||||||
|
var presetDto = payload.ToObject<PresetControlDto>();
|
||||||
|
if (presetDto == null || presetDto.DeviceId <= 0)
|
||||||
|
{
|
||||||
|
_sysLog.Warning("[Preset] 无效指令:参数缺失或设备ID非法");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取目标设备并校验能力
|
||||||
|
var device = _cameraManager.GetDevice(presetDto.DeviceId);
|
||||||
|
if (device == null)
|
||||||
|
{
|
||||||
|
_sysLog.Warning($"[Preset] 设备 {presetDto.DeviceId} 不存在");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!device.IsPhysicalOnline)
|
||||||
|
{
|
||||||
|
_sysLog.Warning($"[Preset] 设备 {presetDto.DeviceId} 未在线,无法执行预置点控制");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimized: [原因] 检查设备是否实现了预置点功能接口
|
||||||
|
if (!(device is IPresetFeature presetFeature))
|
||||||
|
{
|
||||||
|
_sysLog.Warning($"[Preset] 设备 {presetDto.DeviceId} ({device.Config.Name}) 不支持预置点功能");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 分发执行逻辑
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (presetDto.Action.ToUpper())
|
||||||
|
{
|
||||||
|
case "GOTO":
|
||||||
|
await presetFeature.GotoPresetAsync(presetDto.PresetIndex);
|
||||||
|
_sysLog.Information($"[Preset] 设备 {presetDto.DeviceId} 跳转至预置点: {presetDto.PresetIndex}");
|
||||||
|
break;
|
||||||
|
case "SET":
|
||||||
|
await presetFeature.SetPresetAsync(presetDto.PresetIndex);
|
||||||
|
_sysLog.Information($"[Preset] 设备 {presetDto.DeviceId} 设置当前位置为预置点: {presetDto.PresetIndex}");
|
||||||
|
break;
|
||||||
|
case "REMOVE":
|
||||||
|
await presetFeature.RemovePresetAsync(presetDto.PresetIndex);
|
||||||
|
_sysLog.Information($"[Preset] 设备 {presetDto.DeviceId} 删除预置点: {presetDto.PresetIndex}");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_sysLog.Warning($"[Preset] 未知操作类型: {presetDto.Action}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_sysLog.Error(ex, $"[Preset] 设备 {presetDto.DeviceId} 预置点操作失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>PresetControlDto 参数</summary>
|
||||||
|
public class PresetControlDto
|
||||||
|
{
|
||||||
|
/// <summary>设备ID</summary>
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>预置点编号 (1-255)</summary>
|
||||||
|
public int PresetIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>动作:GOTO, SET, REMOVE</summary>
|
||||||
|
public string Action { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -62,6 +62,7 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddSingleton<ICommandHandler, PtzControlHandler>();
|
services.AddSingleton<ICommandHandler, PtzControlHandler>();
|
||||||
services.AddSingleton<ICommandHandler, DeviceRebootHandler>();
|
services.AddSingleton<ICommandHandler, DeviceRebootHandler>();
|
||||||
services.AddSingleton<ICommandHandler, TimeSyncHandler>();
|
services.AddSingleton<ICommandHandler, TimeSyncHandler>();
|
||||||
|
services.AddSingleton<ICommandHandler, PresetControlHandler>();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -51,6 +51,9 @@
|
|||||||
/// <summary>时间同步指令</summary>
|
/// <summary>时间同步指令</summary>
|
||||||
public static string Device_TimeSync { get; } = "Device_TimeSync";
|
public static string Device_TimeSync { get; } = "Device_TimeSync";
|
||||||
|
|
||||||
|
/// <summary>预置点控制指令</summary>
|
||||||
|
public static string Device_Preset { get; } = "Device_Preset";
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user