diff --git a/SHH.CameraSdk/Controllers/Dto/PtzControlDto.cs b/SHH.CameraSdk/Controllers/Dto/PtzControlDto.cs index 9e7f175..773ce9e 100644 --- a/SHH.CameraSdk/Controllers/Dto/PtzControlDto.cs +++ b/SHH.CameraSdk/Controllers/Dto/PtzControlDto.cs @@ -4,6 +4,10 @@ namespace SHH.CameraSdk; public class PtzControlDto { + /// 目标设备ID + public long DeviceId { get; set; } + + /// 云台动作(上/下/左/右/变焦等,对应 PtzAction 枚举) [JsonConverter(typeof(JsonStringEnumConverter))] public PtzAction Action { get; set; } @@ -12,11 +16,9 @@ public class PtzControlDto /// public bool Stop { get; set; } + /// 控制速度(1-8,默认4) public int Speed { get; set; } = 4; - /// - /// [新增] 点动耗时 (毫秒) - /// 如果此值 > 0,则忽略 Stop 参数,执行 "开始 -> 等待 -> 停止" 的原子操作 - /// + /// 点动模式:动作持续时长(毫秒),>0 时启用点动 public int Duration { get; set; } = 0; } \ No newline at end of file diff --git a/SHH.CameraSdk/Core/Manager/CameraManager.cs b/SHH.CameraSdk/Core/Manager/CameraManager.cs index 05be56e..e092603 100644 --- a/SHH.CameraSdk/Core/Manager/CameraManager.cs +++ b/SHH.CameraSdk/Core/Manager/CameraManager.cs @@ -304,7 +304,7 @@ public class CameraManager : IDisposable, IAsyncDisposable } #endregion - + #region --- 6. 资源清理 (Disposal) --- public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult(); diff --git a/SHH.CameraSdk/Drivers/HikVision/Features/FeaturesEnums.cs b/SHH.CameraSdk/Drivers/HikVision/Features/FeaturesEnums.cs index 6bf8e09..1a2ec84 100644 --- a/SHH.CameraSdk/Drivers/HikVision/Features/FeaturesEnums.cs +++ b/SHH.CameraSdk/Drivers/HikVision/Features/FeaturesEnums.cs @@ -5,15 +5,51 @@ /// public enum PtzAction { - Up, - Down, - Left, - Right, - ZoomIn, // 放大 - ZoomOut, // 缩小 - FocusNear, // 聚焦近 - FocusFar, // 聚焦远 - IrisOpen, // 光圈大 - IrisClose, // 光圈小 - Wiper, // 雨刷 + /// 云台向左 + Left = 0, + + /// 云台向上 + Up = 1, + + /// 云台向右 + Right = 2, + + /// 云台向下 + Down = 3, + + /// 云台左上 + LeftUp = 4, + + /// 云台左下 + LeftDown = 5, + + /// 云台右上 + RightUp = 6, + + /// 云台右下 + RightDown = 7, + + /// 云台自动 + Auto = 8, + + /// 放大 + ZoomIn = 9, + + /// 缩小 + ZoomOut = 10, + + /// 聚焦近 + FocusNear = 11, + + /// 聚焦远 + FocusFar = 12, + + /// 光圈大 + IrisOpen = 13, + + /// 光圈小 + IrisClose = 14, + + /// 雨刷 + Wiper = 15, } \ No newline at end of file diff --git a/SHH.CameraSdk/Drivers/HikVision/Features/HikPtzProvider.cs b/SHH.CameraSdk/Drivers/HikVision/Features/HikPtzProvider.cs index 580f8ec..704ab9c 100644 --- a/SHH.CameraSdk/Drivers/HikVision/Features/HikPtzProvider.cs +++ b/SHH.CameraSdk/Drivers/HikVision/Features/HikPtzProvider.cs @@ -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; } + /// + /// 判断该动作是否支持速度参数 + /// + 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); } }); } diff --git a/SHH.CameraSdk/Drivers/HikVision/HikNativeMethods.cs b/SHH.CameraSdk/Drivers/HikVision/HikNativeMethods.cs index 4299773..63af1b0 100644 --- a/SHH.CameraSdk/Drivers/HikVision/HikNativeMethods.cs +++ b/SHH.CameraSdk/Drivers/HikVision/HikNativeMethods.cs @@ -431,6 +431,18 @@ public static partial class HikNativeMethods #endregion #region --- PTZ 控制接口 (PTZ Control Interfaces) --- + + /// + /// 云台控制 + /// 功能:控制云台旋转、镜头缩放、光圈调节等操作 + /// + /// + /// + /// + /// + /// + [DllImport(DllName)] + public static extern bool NET_DVR_PTZControl_Other(int lUserID, int lChannel, uint dwPTZCommand, uint dwStop); /// /// 云台控制(带速度) diff --git a/SHH.CameraService/Bootstrapper.cs b/SHH.CameraService/Bootstrapper.cs index 3e9c526..1f0f6ad 100644 --- a/SHH.CameraService/Bootstrapper.cs +++ b/SHH.CameraService/Bootstrapper.cs @@ -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,日志处置中心;", diff --git a/SHH.CameraService/GrpcImpls/Handlers/DeviceRebootHandler.cs b/SHH.CameraService/GrpcImpls/Handlers/DeviceRebootHandler.cs new file mode 100644 index 0000000..a0c45a3 --- /dev/null +++ b/SHH.CameraService/GrpcImpls/Handlers/DeviceRebootHandler.cs @@ -0,0 +1,69 @@ +using Ayay.SerilogLogs; +using Newtonsoft.Json.Linq; +using Serilog; +using SHH.CameraSdk; +using SHH.Contracts; + +namespace SHH.CameraService; + +/// +/// 设备重启指令处理器 +/// 响应 gRpc 指令:ProtocolCodes.Device_Reboot +/// +public class DeviceRebootHandler : ICommandHandler +{ + private readonly ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core); + private readonly CameraManager _cameraManager; + + /// 指令名称(需在 ProtocolCodes 中定义 Device_Reboot) + 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() ?? 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}"); + } + } +} \ No newline at end of file diff --git a/SHH.CameraService/GrpcImpls/Handlers/PtzControlHandler.cs b/SHH.CameraService/GrpcImpls/Handlers/PtzControlHandler.cs new file mode 100644 index 0000000..7361fbb --- /dev/null +++ b/SHH.CameraService/GrpcImpls/Handlers/PtzControlHandler.cs @@ -0,0 +1,77 @@ +using Ayay.SerilogLogs; +using Newtonsoft.Json.Linq; +using Serilog; +using SHH.CameraSdk; +using SHH.Contracts; + +namespace SHH.CameraService; + +/// +/// 云台控制指令处理器 +/// 响应 gRpc 指令:ProtocolCodes.Control_Ptz +/// +public class PtzControlHandler : ICommandHandler +{ + private readonly ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core); + private readonly CameraManager _cameraManager; + + /// 指令名称(需与网关下发的 CmdCode 一致) + 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(); + 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} 控制失败"); + } + } +} \ No newline at end of file diff --git a/SHH.CameraService/GrpcImpls/Handlers/TimeSyncHandler.cs b/SHH.CameraService/GrpcImpls/Handlers/TimeSyncHandler.cs new file mode 100644 index 0000000..9be4fd2 --- /dev/null +++ b/SHH.CameraService/GrpcImpls/Handlers/TimeSyncHandler.cs @@ -0,0 +1,79 @@ +using Ayay.SerilogLogs; +using Newtonsoft.Json.Linq; +using Serilog; +using SHH.CameraSdk; +using SHH.Contracts; + +namespace SHH.CameraService; + +/// +/// 设备时间同步处理器 +/// 响应 gRpc 指令:ProtocolCodes.Time_Sync +/// +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() ?? 0; + var method = payload["Method"]?.Value()?.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.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})"); + } + } +} \ No newline at end of file diff --git a/SHH.CameraService/Utils/ServiceCollectionExtensions.cs b/SHH.CameraService/Utils/ServiceCollectionExtensions.cs index 21aab4a..8bedcd5 100644 --- a/SHH.CameraService/Utils/ServiceCollectionExtensions.cs +++ b/SHH.CameraService/Utils/ServiceCollectionExtensions.cs @@ -59,6 +59,9 @@ public static class ServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } #endregion diff --git a/SHH.Contracts.Grpc/Payloads/ProtocolCodes.cs b/SHH.Contracts.Grpc/Payloads/ProtocolCodes.cs index d908855..4d29643 100644 --- a/SHH.Contracts.Grpc/Payloads/ProtocolCodes.cs +++ b/SHH.Contracts.Grpc/Payloads/ProtocolCodes.cs @@ -42,6 +42,15 @@ /// public static string Remove_Camera { get; } = "Remove_Camera"; + /// 云台控制指令 + public static string Ptz_Control { get; } = "Ptz_Control"; + + /// 设备重启指令 + public static string Device_Reboot { get; } = "Device_Reboot"; + + /// 时间同步指令 + public static string Device_TimeSync { get; } = "Device_TimeSync"; + #endregion } } \ No newline at end of file