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