增加云台移动、缩放、聚集、光圈、校时、重启对 AiVideo 项目的支持

This commit is contained in:
2026-03-02 13:57:10 +08:00
parent c8f25aeba5
commit 0399871467
11 changed files with 343 additions and 28 deletions

View File

@@ -4,6 +4,10 @@ namespace SHH.CameraSdk;
public class PtzControlDto public class PtzControlDto
{ {
/// <summary> 目标设备ID </summary>
public long DeviceId { get; set; }
/// <summary> 云台动作(上/下/左/右/变焦等,对应 PtzAction 枚举) </summary>
[JsonConverter(typeof(JsonStringEnumConverter))] [JsonConverter(typeof(JsonStringEnumConverter))]
public PtzAction Action { get; set; } public PtzAction Action { get; set; }
@@ -12,11 +16,9 @@ public class PtzControlDto
/// </summary> /// </summary>
public bool Stop { get; set; } public bool Stop { get; set; }
/// <summary> 控制速度1-8默认4 </summary>
public int Speed { get; set; } = 4; public int Speed { get; set; } = 4;
/// <summary> /// <summary> 点动模式:动作持续时长(毫秒),>0 时启用点动 </summary>
/// [新增] 点动耗时 (毫秒)
/// 如果此值 > 0则忽略 Stop 参数,执行 "开始 -> 等待 -> 停止" 的原子操作
/// </summary>
public int Duration { get; set; } = 0; public int Duration { get; set; } = 0;
} }

View File

@@ -304,7 +304,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
} }
#endregion #endregion
#region --- 6. (Disposal) --- #region --- 6. (Disposal) ---
public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult(); public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();

View File

@@ -5,15 +5,51 @@
/// </summary> /// </summary>
public enum PtzAction public enum PtzAction
{ {
Up, /// <summary>云台向左</summary>
Down, Left = 0,
Left,
Right, /// <summary>云台向上</summary>
ZoomIn, // 放大 Up = 1,
ZoomOut, // 缩小
FocusNear, // 聚焦近 /// <summary>云台向右</summary>
FocusFar, // 聚焦远 Right = 2,
IrisOpen, // 光圈大
IrisClose, // 光圈小 /// <summary>云台向下</summary>
Wiper, // 雨刷 Down = 3,
/// <summary>云台左上</summary>
LeftUp = 4,
/// <summary>云台左下</summary>
LeftDown = 5,
/// <summary>云台右上</summary>
RightUp = 6,
/// <summary>云台右下</summary>
RightDown = 7,
/// <summary>云台自动</summary>
Auto = 8,
/// <summary>放大</summary>
ZoomIn = 9,
/// <summary>缩小</summary>
ZoomOut = 10,
/// <summary>聚焦近</summary>
FocusNear = 11,
/// <summary>聚焦远</summary>
FocusFar = 12,
/// <summary>光圈大</summary>
IrisOpen = 13,
/// <summary>光圈小</summary>
IrisClose = 14,
/// <summary>雨刷</summary>
Wiper = 15,
} }

View File

@@ -1,4 +1,6 @@
namespace SHH.CameraSdk.HikFeatures; using Serilog;
namespace SHH.CameraSdk.HikFeatures;
public class HikPtzProvider : IPtzFeature public class HikPtzProvider : IPtzFeature
{ {
@@ -9,6 +11,15 @@ public class HikPtzProvider : IPtzFeature
_context = context; _context = context;
} }
/// <summary>
/// 判断该动作是否支持速度参数
/// </summary>
private static bool IsSpeedSupported(PtzAction action)
{
// Optimized: 方向和缩放支持速度,其余(雨刷/聚焦/光圈)为开关量或标准调节
return action <= PtzAction.ZoomOut;
}
public async Task PtzControlAsync(PtzAction action, bool stop, int speed) public async Task PtzControlAsync(PtzAction action, bool stop, int speed)
{ {
int userId = _context.GetUserId(); int userId = _context.GetUserId();
@@ -17,10 +28,18 @@ public class HikPtzProvider : IPtzFeature
// 1. 映射指令 // 1. 映射指令
uint hikCommand = action switch uint hikCommand = action switch
{ {
PtzAction.Up => HikNativeMethods.TILT_UP,
PtzAction.Down => HikNativeMethods.TILT_DOWN,
PtzAction.Left => HikNativeMethods.PAN_LEFT, PtzAction.Left => HikNativeMethods.PAN_LEFT,
PtzAction.Up => HikNativeMethods.TILT_UP,
PtzAction.Right => HikNativeMethods.PAN_RIGHT, PtzAction.Right => HikNativeMethods.PAN_RIGHT,
PtzAction.Down => HikNativeMethods.TILT_DOWN,
PtzAction.LeftUp => HikNativeMethods.UP_LEFT, // 海康 SDK: 左上
PtzAction.LeftDown => HikNativeMethods.DOWN_LEFT, // 海康 SDK: 左下
PtzAction.RightUp => HikNativeMethods.UP_RIGHT, // 海康 SDK: 右上
PtzAction.RightDown => HikNativeMethods.DOWN_RIGHT, // 海康 SDK: 右下
PtzAction.Auto => HikNativeMethods.PAN_AUTO,
PtzAction.ZoomIn => HikNativeMethods.ZOOM_IN, PtzAction.ZoomIn => HikNativeMethods.ZOOM_IN,
PtzAction.ZoomOut => HikNativeMethods.ZOOM_OUT, PtzAction.ZoomOut => HikNativeMethods.ZOOM_OUT,
PtzAction.FocusNear => HikNativeMethods.FOCUS_NEAR, PtzAction.FocusNear => HikNativeMethods.FOCUS_NEAR,
@@ -36,19 +55,28 @@ public class HikPtzProvider : IPtzFeature
// 2. 转换停止标志 (海康: 0=开始, 1=停止) // 2. 转换停止标志 (海康: 0=开始, 1=停止)
uint dwStop = stop ? 1u : 0u; uint dwStop = stop ? 1u : 0u;
// 3. 限制速度范围 (1-7) // 3. 调用 SDK
uint dwSpeed = (uint)Math.Clamp(speed, 1, 7);
// 4. 调用 SDK
await Task.Run(() => await Task.Run(() =>
{ {
// Channel 默认为 1 bool result;
bool result = HikNativeMethods.NET_DVR_PTZControlWithSpeed_Other(userId, 1, hikCommand, dwStop, dwSpeed); // Optimized: 接口分流逻辑,确保不同类型的动作调用正确的 SDK 接口
if (IsSpeedSupported(action))
{
uint dwSpeed = (uint)Math.Clamp(speed, 1, 7);
result = HikNativeMethods.NET_DVR_PTZControlWithSpeed_Other(userId, 1, hikCommand, dwStop, dwSpeed);
}
else
{
// 对于雨刷、聚焦、光圈,使用不带速度的接口
result = HikNativeMethods.NET_DVR_PTZControl_Other(userId, 1, hikCommand, dwStop);
}
if (!result) if (!result)
{ {
// 这里通常不抛异常,因为云台繁忙或到头是常态,记录日志即可 // Modified: 统一记录到 D:\Logs
// Console.WriteLine($"PTZ Error: {HikNativeMethods.NET_DVR_GetLastError()}"); uint errorCode = HikNativeMethods.NET_DVR_GetLastError();
Log.Warning("Ayay.AiVideo: PTZ {Action} failed. Stop: {Stop}, ErrorCode: {ErrorCode}",
action, stop, errorCode);
} }
}); });
} }

View File

@@ -431,6 +431,18 @@ public static partial class HikNativeMethods
#endregion #endregion
#region --- PTZ (PTZ Control Interfaces) --- #region --- PTZ (PTZ Control Interfaces) ---
/// <summary>
/// 云台控制
/// 功能:控制云台旋转、镜头缩放、光圈调节等操作
/// </summary>
/// <param name="lUserID"></param>
/// <param name="lChannel"></param>
/// <param name="dwPTZCommand"></param>
/// <param name="dwStop"></param>
/// <returns></returns>
[DllImport(DllName)]
public static extern bool NET_DVR_PTZControl_Other(int lUserID, int lChannel, uint dwPTZCommand, uint dwStop);
/// <summary> /// <summary>
/// 云台控制(带速度) /// 云台控制(带速度)

View File

@@ -32,7 +32,7 @@ public static class Bootstrapper
// 视频流地址 (格式: IP,Port,Type,Desc) // 视频流地址 (格式: IP,Port,Type,Desc)
"--uris", "localhost,9001,调试PC;", "--uris", "localhost,9001,调试PC;",
"--uris", "localhost,9002,调试PC;", //"--uris", "localhost,9002,调试PC;",
// 日志中心配置 (格式: IP,Port,Desc) // 日志中心配置 (格式: IP,Port,Desc)
"--sequris", "58.216.225.5,20026,日志处置中心;", "--sequris", "58.216.225.5,20026,日志处置中心;",

View File

@@ -0,0 +1,69 @@
using Ayay.SerilogLogs;
using Newtonsoft.Json.Linq;
using Serilog;
using SHH.CameraSdk;
using SHH.Contracts;
namespace SHH.CameraService;
/// <summary>
/// 设备重启指令处理器
/// 响应 gRpc 指令ProtocolCodes.Device_Reboot
/// </summary>
public class DeviceRebootHandler : ICommandHandler
{
private readonly ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
private readonly CameraManager _cameraManager;
/// <summary>指令名称(需在 ProtocolCodes 中定义 Device_Reboot</summary>
public string ActionName => ProtocolCodes.Device_Reboot;
public DeviceRebootHandler(CameraManager cameraManager)
{
_cameraManager = cameraManager ?? throw new ArgumentNullException(nameof(cameraManager));
}
public async Task ExecuteAsync(JToken payload)
{
// 1. 解析参数(假设重启指令至少包含 DeviceId
// Optimized: 使用通用的基础 DTO 或直接解析 DeviceId避免为简单的重启创建过多复杂 DTO
var deviceId = payload["DeviceId"]?.Value<int>() ?? 0;
if (deviceId <= 0)
{
_sysLog.Warning("[Reboot] 无效指令设备ID非法");
return;
}
// 2. 获取设备实例
var device = _cameraManager.GetDevice(deviceId);
if (device == null)
{
_sysLog.Warning($"[Reboot] 设备 {deviceId} 不存在");
return;
}
// 3. 校验重启能力 (参考 PtzControlHandler 模式)
// Modified: 根据 ISyncFeature.cs 定义,检查是否实现了 IRebootFeature 接口
if (!(device is IRebootFeature rebootFeature))
{
_sysLog.Warning($"[Reboot] 设备 {deviceId} 不支持远程重启接口");
return;
}
// 4. 执行重启
try
{
_sysLog.Information($"[Reboot] 正在向设备 {deviceId} 发送重启指令...");
// 调用接口定义的异步方法
await rebootFeature.RebootAsync();
_sysLog.Information($"[Reboot] 设备 {deviceId} 重启指令发送成功");
}
catch (Exception ex)
{
_sysLog.Error($"[Reboot] 设备 {deviceId} 重启失败: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,77 @@
using Ayay.SerilogLogs;
using Newtonsoft.Json.Linq;
using Serilog;
using SHH.CameraSdk;
using SHH.Contracts;
namespace SHH.CameraService;
/// <summary>
/// 云台控制指令处理器
/// 响应 gRpc 指令ProtocolCodes.Control_Ptz
/// </summary>
public class PtzControlHandler : ICommandHandler
{
private readonly ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
private readonly CameraManager _cameraManager;
/// <summary>指令名称(需与网关下发的 CmdCode 一致)</summary>
public string ActionName => ProtocolCodes.Ptz_Control; // 需在 ProtocolCodes 中新增该常量
public PtzControlHandler(CameraManager cameraManager)
{
_cameraManager = cameraManager ?? throw new ArgumentNullException(nameof(cameraManager));
}
public async Task ExecuteAsync(JToken payload)
{
// 1. 解析 PTZ 控制参数(与网关下发的 JSON 结构匹配)
var ptzDto = payload.ToObject<PtzControlDto>();
if (ptzDto == null || ptzDto.DeviceId <= 0)
{
_sysLog.Warning("[PTZ] 无效指令参数缺失或设备ID非法");
return;
}
// 2. 获取目标设备并校验能力
var device = _cameraManager.GetDevice(ptzDto.DeviceId);
if (device == null)
{
_sysLog.Warning($"[PTZ] 设备 {ptzDto.DeviceId} 不存在");
return;
}
if (!device.IsPhysicalOnline)
{
_sysLog.Warning($"[PTZ] 设备 {ptzDto.DeviceId} 未在线,无法执行云台控制");
return;
}
if (!(device is IPtzFeature ptzFeature))
{
_sysLog.Warning($"[PTZ] 设备 {ptzDto.DeviceId} 不支持云台控制");
return;
}
// 3. 执行云台控制(根据指令类型选择手动/点动模式)
try
{
if (ptzDto.Duration > 0)
{
// 点动模式自动复位如持续300ms向上转动
int safeDuration = Math.Clamp(ptzDto.Duration, 50, 2000); // 限制单次最长2秒
await ptzFeature.PtzStepAsync(ptzDto.Action, safeDuration, ptzDto.Speed);
_sysLog.Information($"[PTZ] 设备 {ptzDto.DeviceId} 点动控制:{ptzDto.Action},时长 {safeDuration}ms速度 {ptzDto.Speed}");
}
else
{
// 手动模式:按下/松开(如按住向上、松开停止)
await ptzFeature.PtzControlAsync(ptzDto.Action, ptzDto.Stop, ptzDto.Speed);
string actionDesc = ptzDto.Stop ? "停止" : "启动";
_sysLog.Information($"[PTZ] 设备 {ptzDto.DeviceId} 手动控制:{ptzDto.Action} {actionDesc},速度 {ptzDto.Speed}");
}
}
catch (Exception ex)
{
_sysLog.Error(ex, $"[PTZ] 设备 {ptzDto.DeviceId} 控制失败");
}
}
}

View File

@@ -0,0 +1,79 @@
using Ayay.SerilogLogs;
using Newtonsoft.Json.Linq;
using Serilog;
using SHH.CameraSdk;
using SHH.Contracts;
namespace SHH.CameraService;
/// <summary>
/// 设备时间同步处理器
/// 响应 gRpc 指令ProtocolCodes.Time_Sync
/// </summary>
public class TimeSyncHandler : ICommandHandler
{
private readonly ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
private readonly CameraManager _cameraManager;
// Optimized: 需在 ProtocolCodes 中定义此常量,确保与网关下发的 CmdCode 对应
public string ActionName => ProtocolCodes.Device_TimeSync;
public TimeSyncHandler(CameraManager cameraManager)
{
_cameraManager = cameraManager ?? throw new ArgumentNullException(nameof(cameraManager));
}
public async Task ExecuteAsync(JToken payload)
{
// 1. 解析基础参数
var deviceId = payload["DeviceId"]?.Value<int>() ?? 0;
var method = payload["Method"]?.Value<string>()?.ToLower(); // "get" 或 "set"
if (deviceId <= 0)
{
_sysLog.Warning("[TimeSync] 无效指令设备ID非法");
return;
}
// 2. 获取设备并校验能力
var device = _cameraManager.GetDevice(deviceId);
if (device == null)
{
_sysLog.Warning($"[TimeSync] 设备 {deviceId} 不存在");
return;
}
// Modified: 根据 ISyncFeature.cs 校验是否实现 ITimeSyncFeature 接口
if (!(device is ITimeSyncFeature syncFeature))
{
_sysLog.Warning($"[TimeSync] 设备 {deviceId} 不支持时间同步功能");
return;
}
// 3. 业务逻辑分支
try
{
if (method == "set")
{
// 解析待设置的时间,若无则默认使用服务器当前系统时间
var targetTime = payload["SyncTime"]?.Value<DateTime>() ?? DateTime.Now;
// 调用接口设置时间
await syncFeature.SetTimeAsync(targetTime);
_sysLog.Information($"[TimeSync] 设备 {deviceId} 时间已同步为: {targetTime:yyyy-MM-dd HH:mm:ss}");
}
else
{
// 执行获取时间
var deviceTime = await syncFeature.GetTimeAsync();
_sysLog.Information($"[TimeSync] 获取设备 {deviceId} 当前时间成功: {deviceTime:yyyy-MM-dd HH:mm:ss}");
// TODO: 若需要将结果返回给网关,需在此处调用 GatewayService 异步回传
}
}
catch (Exception ex)
{
_sysLog.Error(ex, $"[TimeSync] 设备 {deviceId} 操作失败 ({method})");
}
}
}

View File

@@ -59,6 +59,9 @@ public static class ServiceCollectionExtensions
services.AddSingleton<CommandDispatcher>(); services.AddSingleton<CommandDispatcher>();
services.AddSingleton<ICommandHandler, DeviceConfigHandler>(); services.AddSingleton<ICommandHandler, DeviceConfigHandler>();
services.AddSingleton<ICommandHandler, RemoveCameraHandler>(); services.AddSingleton<ICommandHandler, RemoveCameraHandler>();
services.AddSingleton<ICommandHandler, PtzControlHandler>();
services.AddSingleton<ICommandHandler, DeviceRebootHandler>();
services.AddSingleton<ICommandHandler, TimeSyncHandler>();
} }
#endregion #endregion

View File

@@ -42,6 +42,15 @@
/// </summary> /// </summary>
public static string Remove_Camera { get; } = "Remove_Camera"; public static string Remove_Camera { get; } = "Remove_Camera";
/// <summary>云台控制指令</summary>
public static string Ptz_Control { get; } = "Ptz_Control";
/// <summary>设备重启指令</summary>
public static string Device_Reboot { get; } = "Device_Reboot";
/// <summary>时间同步指令</summary>
public static string Device_TimeSync { get; } = "Device_TimeSync";
#endregion #endregion
} }
} }