增加大华设备对云台移动、缩放、聚集、光圈、校时、重启的支持

增加海康、大华对预置点的支持
This commit is contained in:
2026-03-03 13:55:37 +08:00
parent 0399871467
commit d1fc94be1c
15 changed files with 683 additions and 65 deletions

View 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);
}
});
}
}

View 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);
}
}

View 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 重启指令下发成功,设备即将断开连接。");
});
}
}

View 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);
}
});
}
}