增加云台移动、缩放、聚集、光圈、校时、重启对 AiVideo 项目的支持
This commit is contained in:
@@ -4,6 +4,10 @@ namespace SHH.CameraSdk;
|
||||
|
||||
public class PtzControlDto
|
||||
{
|
||||
/// <summary> 目标设备ID </summary>
|
||||
public long DeviceId { get; set; }
|
||||
|
||||
/// <summary> 云台动作(上/下/左/右/变焦等,对应 PtzAction 枚举) </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public PtzAction Action { get; set; }
|
||||
|
||||
@@ -12,11 +16,9 @@ public class PtzControlDto
|
||||
/// </summary>
|
||||
public bool Stop { get; set; }
|
||||
|
||||
/// <summary> 控制速度(1-8,默认4) </summary>
|
||||
public int Speed { get; set; } = 4;
|
||||
|
||||
/// <summary>
|
||||
/// [新增] 点动耗时 (毫秒)
|
||||
/// 如果此值 > 0,则忽略 Stop 参数,执行 "开始 -> 等待 -> 停止" 的原子操作
|
||||
/// </summary>
|
||||
/// <summary> 点动模式:动作持续时长(毫秒),>0 时启用点动 </summary>
|
||||
public int Duration { get; set; } = 0;
|
||||
}
|
||||
@@ -304,7 +304,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region --- 6. 资源清理 (Disposal) ---
|
||||
|
||||
public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
|
||||
@@ -5,15 +5,51 @@
|
||||
/// </summary>
|
||||
public enum PtzAction
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
ZoomIn, // 放大
|
||||
ZoomOut, // 缩小
|
||||
FocusNear, // 聚焦近
|
||||
FocusFar, // 聚焦远
|
||||
IrisOpen, // 光圈大
|
||||
IrisClose, // 光圈小
|
||||
Wiper, // 雨刷
|
||||
/// <summary>云台向左</summary>
|
||||
Left = 0,
|
||||
|
||||
/// <summary>云台向上</summary>
|
||||
Up = 1,
|
||||
|
||||
/// <summary>云台向右</summary>
|
||||
Right = 2,
|
||||
|
||||
/// <summary>云台向下</summary>
|
||||
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,
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace SHH.CameraSdk.HikFeatures;
|
||||
using Serilog;
|
||||
|
||||
namespace SHH.CameraSdk.HikFeatures;
|
||||
|
||||
public class HikPtzProvider : IPtzFeature
|
||||
{
|
||||
@@ -9,6 +11,15 @@ public class HikPtzProvider : IPtzFeature
|
||||
_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)
|
||||
{
|
||||
int userId = _context.GetUserId();
|
||||
@@ -17,10 +28,18 @@ public class HikPtzProvider : IPtzFeature
|
||||
// 1. 映射指令
|
||||
uint hikCommand = action switch
|
||||
{
|
||||
PtzAction.Up => HikNativeMethods.TILT_UP,
|
||||
PtzAction.Down => HikNativeMethods.TILT_DOWN,
|
||||
PtzAction.Left => HikNativeMethods.PAN_LEFT,
|
||||
PtzAction.Up => HikNativeMethods.TILT_UP,
|
||||
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.ZoomOut => HikNativeMethods.ZOOM_OUT,
|
||||
PtzAction.FocusNear => HikNativeMethods.FOCUS_NEAR,
|
||||
@@ -36,19 +55,28 @@ public class HikPtzProvider : IPtzFeature
|
||||
// 2. 转换停止标志 (海康: 0=开始, 1=停止)
|
||||
uint dwStop = stop ? 1u : 0u;
|
||||
|
||||
// 3. 限制速度范围 (1-7)
|
||||
uint dwSpeed = (uint)Math.Clamp(speed, 1, 7);
|
||||
|
||||
// 4. 调用 SDK
|
||||
// 3. 调用 SDK
|
||||
await Task.Run(() =>
|
||||
{
|
||||
// Channel 默认为 1
|
||||
bool result = HikNativeMethods.NET_DVR_PTZControlWithSpeed_Other(userId, 1, hikCommand, dwStop, dwSpeed);
|
||||
bool result;
|
||||
// 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)
|
||||
{
|
||||
// 这里通常不抛异常,因为云台繁忙或到头是常态,记录日志即可
|
||||
// Console.WriteLine($"PTZ Error: {HikNativeMethods.NET_DVR_GetLastError()}");
|
||||
// Modified: 统一记录到 D:\Logs
|
||||
uint errorCode = HikNativeMethods.NET_DVR_GetLastError();
|
||||
Log.Warning("Ayay.AiVideo: PTZ {Action} failed. Stop: {Stop}, ErrorCode: {ErrorCode}",
|
||||
action, stop, errorCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -431,6 +431,18 @@ public static partial class HikNativeMethods
|
||||
#endregion
|
||||
|
||||
#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>
|
||||
/// 云台控制(带速度)
|
||||
|
||||
@@ -32,7 +32,7 @@ public static class Bootstrapper
|
||||
|
||||
// 视频流地址 (格式: IP,Port,Type,Desc)
|
||||
"--uris", "localhost,9001,调试PC;",
|
||||
"--uris", "localhost,9002,调试PC;",
|
||||
//"--uris", "localhost,9002,调试PC;",
|
||||
|
||||
// 日志中心配置 (格式: IP,Port,Desc)
|
||||
"--sequris", "58.216.225.5,20026,日志处置中心;",
|
||||
|
||||
69
SHH.CameraService/GrpcImpls/Handlers/DeviceRebootHandler.cs
Normal file
69
SHH.CameraService/GrpcImpls/Handlers/DeviceRebootHandler.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
77
SHH.CameraService/GrpcImpls/Handlers/PtzControlHandler.cs
Normal file
77
SHH.CameraService/GrpcImpls/Handlers/PtzControlHandler.cs
Normal 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} 控制失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
79
SHH.CameraService/GrpcImpls/Handlers/TimeSyncHandler.cs
Normal file
79
SHH.CameraService/GrpcImpls/Handlers/TimeSyncHandler.cs
Normal 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})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,9 @@ public static class ServiceCollectionExtensions
|
||||
services.AddSingleton<CommandDispatcher>();
|
||||
services.AddSingleton<ICommandHandler, DeviceConfigHandler>();
|
||||
services.AddSingleton<ICommandHandler, RemoveCameraHandler>();
|
||||
services.AddSingleton<ICommandHandler, PtzControlHandler>();
|
||||
services.AddSingleton<ICommandHandler, DeviceRebootHandler>();
|
||||
services.AddSingleton<ICommandHandler, TimeSyncHandler>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -42,6 +42,15 @@
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user