diff --git a/EC.Util/CameraSDK/Common/CameraException.cs b/EC.Util/CameraSDK/Common/CameraException.cs new file mode 100644 index 0000000..a63503c --- /dev/null +++ b/EC.Util/CameraSDK/Common/CameraException.cs @@ -0,0 +1,59 @@ +using System.Text; + +namespace EC.Util.CameraSDK; + +/// +/// 相机异常 +/// +public class CameraException : Exception +{ + public CameraException() : base() + { + } + + public CameraException(string? message) : base(message) + { + } + + public CameraException(string? message, Exception? innerException) : base(message, innerException) + { + } + + protected class CameraExceptionObj + { + public CameraInfo? CameraInfo { get; set; } + + public int Code { get; set; } + + public string? Msg { get; set; } + + public override string? ToString() + { + StringBuilder builder = new(); + builder.Append($"Ip:{CameraInfo?.Ip}, Manufactor:{CameraInfo?.Manufactor}, Code:{Code}"); + if (!string.IsNullOrEmpty(Msg)) builder.Append($", Msg:{Msg}"); + return builder.ToString(); + } + } + + public static CameraException New(CameraInfo cameraInfo, int code) + { + CameraExceptionObj obj = new() + { + CameraInfo = cameraInfo, + Code = code + }; + return new CameraException(obj.ToString()); + } + + public static CameraException New(CameraInfo cameraInfo, int code, string msg) + { + CameraExceptionObj obj = new() + { + CameraInfo = cameraInfo, + Code = code, + Msg = msg + }; + return new CameraException(obj.ToString()); + } +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/Common/CameraFactory.cs b/EC.Util/CameraSDK/Common/CameraFactory.cs new file mode 100644 index 0000000..e54edcb --- /dev/null +++ b/EC.Util/CameraSDK/Common/CameraFactory.cs @@ -0,0 +1,16 @@ +namespace EC.Util.CameraSDK; + +public class CameraFactory +{ + public static ICameraSdk BuildCameraSdk(CameraInfo info) + { + ICameraSdk sdk = (info.Manufactor) switch + { + CameraManufactor.HiK => new HiKSdk(info), + CameraManufactor.DaHua => new DaHuaSdk(info), + CameraManufactor.YuShi => new YuShiSdk(info), + _ => throw new NotSupportedException(), + }; + return sdk; + } +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/Common/CameraStruct.cs b/EC.Util/CameraSDK/Common/CameraStruct.cs new file mode 100644 index 0000000..2105df2 --- /dev/null +++ b/EC.Util/CameraSDK/Common/CameraStruct.cs @@ -0,0 +1,142 @@ +namespace EC.Util.CameraSDK; + +/// +/// 相机信息 +/// +public class CameraInfo +{ + #region Fields + + /// + /// id + /// + public string Id { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; + + /// + /// 相机厂商 + /// + public CameraManufactor Manufactor { get; set; } + + /// + /// ip 地址 + /// + public string Ip { get; set; } = string.Empty; + + /// + /// 端口 + /// + public int Port { get; set; } + + /// + /// 账号 + /// + public string UserName { get; set; } = string.Empty; + + /// + /// 密码 + /// + public string Password { get; set; } = string.Empty; + + #endregion Fields + + public static CameraInfo New(CameraManufactor manufactor, string ip, int port, string username, string password) + { + CameraInfo info = new() { Manufactor = manufactor, Ip = ip, Port = port, UserName = username, Password = password }; + if (port <= 0) throw new Exception("Camera manufactor not support."); + return info; + } + + public static CameraInfo New(CameraManufactor manufactor, string ip, string username, string password) + { + CameraInfo info = new() { Manufactor = manufactor, Ip = ip, UserName = username, Password = password }; + int port = manufactor switch + { + CameraManufactor.HiK => (int)CameraPort.HiK, + CameraManufactor.DaHua => (int)CameraPort.DaHua, + CameraManufactor.YuShi => (int)CameraPort.YuShi, + _ => -1, + }; + if (port <= 0) throw new Exception("Camera manufactor not support."); + info.Port = port; + return info; + } + + public static CameraInfo New(int manufactor, string ip, int port, string username, string password) + { + CameraManufactor cm = (CameraManufactor)manufactor; + CameraInfo info = new() { Manufactor = cm, Ip = ip, Port = port, UserName = username, Password = password }; + if (port <= 0) throw new Exception("Camera manufactor not support."); + return info; + } + + public static CameraInfo New(int manufactor, string ip, string username, string password) + { + CameraManufactor cm = (CameraManufactor)manufactor; + CameraInfo info = new() { Manufactor = cm, Ip = ip, UserName = username, Password = password }; + int port = cm switch + { + CameraManufactor.HiK => (int)CameraPort.HiK, + CameraManufactor.DaHua => (int)CameraPort.DaHua, + CameraManufactor.YuShi => (int)CameraPort.YuShi, + _ => -1, + }; + if (port <= 0) throw new Exception("Camera manufactor not support."); + info.Port = port; + return info; + } +} + +/// +/// 相机厂商 +/// +public enum CameraManufactor : int +{ + HiK = 1, + DaHua = 2, + YuShi = 3, +} + +/// +/// 相机默认连接端口 +/// +public enum CameraPort : int +{ + HiK = 8000, + DaHua = 37777, + YuShi = 80, +} + +/// +/// Ptz 信息 +/// +public class PtzInfo +{ + #region Fields + + public double Pan { get; set; } + + public double Tilt { get; set; } + + public double Zoom { get; set; } + + #endregion Fields + + public PtzInfo(double pan, double tilt, double zoom) + { + Pan = pan; + Tilt = tilt; + Zoom = zoom; + } + + public static PtzInfo Default + { + get { return new(0, 0, 0); } + } + + public static PtzInfo New(double pan, double tilt, double zoom) + { + return new PtzInfo(pan, tilt, zoom); + } +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/Common/ICameraSdk.cs b/EC.Util/CameraSDK/Common/ICameraSdk.cs new file mode 100644 index 0000000..a294f8d --- /dev/null +++ b/EC.Util/CameraSDK/Common/ICameraSdk.cs @@ -0,0 +1,72 @@ +namespace EC.Util.CameraSDK; + +public abstract class ICameraSdk +{ + #region Fields + + public CameraInfo CameraInfo { get; } + + #endregion Fields + + public ICameraSdk(CameraInfo cameraInfo) + { + CameraInfo = cameraInfo; + } + + #region Base Method + + /// + /// 初始化资源 + /// + /// + public abstract bool Init(); + + /// + /// 释放资源 + /// + /// + public abstract bool Destory(); + + /// + /// 连接是否成功 + /// + /// + public abstract bool ConnectSuccess(); + + /// + /// 构建异常 + /// + internal abstract void BuildException(); + + #endregion Base Method + + #region Ptz Method + + /// + /// 获取 ptz + /// + /// + public abstract PtzInfo GetPtzInfo(); + + /// + /// 获取 ptz + /// + /// + public abstract bool TryGetPtzInfo(out PtzInfo ptzInfo); + + public abstract bool PtzMove(int cmd, int stop, int speed); + + public abstract bool PtzPreset(int cmd, int presetId); + + #endregion Ptz Method + + #region Video Method + + public abstract void StartPlay(IntPtr hwnd); + + public abstract void StopPlay(); + + public abstract bool IsPlaying(); + + #endregion Video Method +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/DaHua/DaHuaOriSdk.cs b/EC.Util/CameraSDK/DaHua/DaHuaOriSdk.cs new file mode 100644 index 0000000..6bedde1 --- /dev/null +++ b/EC.Util/CameraSDK/DaHua/DaHuaOriSdk.cs @@ -0,0 +1,1527 @@ +//#define Win32 +#define Win64 + +//#define Linux32 +//#define Linux64 +using System.Runtime.InteropServices; + +namespace EC.Util.CameraSDK; + +public static class DaHuaOriSdk +{ + #region Fields + +#if Win32 + public const string LibSdkPath = @"./libs/dahua/win32/dhnetsdk.dll"; +#elif Win64 + public const string LibSdkPath = @"./libs/dahua/win64/dhnetsdk.dll"; +#elif Linux32 + public const string LibSdkPath = @"./libs/dahua/linux32/libdhnetsdk.so"; +#elif Linux64 + public const string LibSdkPath = @"./libs/dahua/linux64/libdhnetsdk.so"; +#endif + + private const bool Debug = true; + + #endregion Fields + + static DaHuaOriSdk() + { + GlobalInit(); + } + + #region Global + + public static bool InitSuccess { get; private set; } + + public static bool GlobalInit() + { + if (InitSuccess) return true; + bool ret = CLIENT_InitEx(null, IntPtr.Zero, IntPtr.Zero); + InitSuccess = ret; + if (!ret) throw new Exception("DaHuaOriSDK global init failure."); + return ret; + } + + public static bool GlobalDestory() + { + if (!InitSuccess) return true; + CLIENT_Cleanup(); + InitSuccess = false; + return true; + } + + #endregion Global + + #region Sdk Const + + public const int PtzSpeedMin = 1; + public const int PtzSpeedMax = 8; + + #endregion Sdk Const + + #region Sdk Enum + + /// + /// login device mode enumeration + /// 登陆设备方式枚举 + /// + public enum EM_LOGIN_SPAC_CAP_TYPE + { + /// + /// TCP login, default + /// TCP登陆, 默认方式 + /// + TCP = 0, + + /// + /// No criteria login + /// 无条件登陆 + /// + ANY = 1, + + /// + /// auto sign up login + /// 主动注册的登入 + /// + SERVER_CONN = 2, + + /// + /// multicast login, default + /// 组播登陆 + /// + MULTICAST = 3, + + /// + /// UDP method login + /// UDP方式下的登入 + /// + UDP = 4, + + /// + /// only main connection login + /// 只建主连接下的登入 + /// + MAIN_CONN_ONLY = 6, + + /// + /// SSL encryption login + /// SSL加密方式登陆 + /// + SSL = 7, + + /// + /// login IVS box remote device + /// 登录智能盒远程设备 + /// + INTELLIGENT_BOX = 9, + + /// + /// login device do not config + /// 登录设备后不做取配置操作 + /// + NO_CONFIG = 10, + + /// + /// USB key device login + /// 用U盾设备的登入 + /// + U_LOGIN = 11, + + /// + /// LDAP login + /// LDAP方式登录 + /// + LDAP = 12, + + /// + /// AD login + /// AD(ActiveDirectory)登录方式 + /// + AD = 13, + + /// + /// Radius login + /// Radius 登录方式 + /// + RADIUS = 14, + + /// + /// Socks5 login + /// Socks5登陆方式 + /// + SOCKET_5 = 15, + + /// + /// cloud login + /// 云登陆方式 + /// + CLOUD = 16, + + /// + /// dual authentication loin + /// 二次鉴权登陆方式 + /// + AUTH_TWICE = 17, + + /// + /// TS stream client login + /// TS码流客户端登陆方式 + /// + TS = 18, + + /// + /// web private login + /// 为P2P登陆方式 + /// + P2P = 19, + + /// + /// mobile client login + /// 手机客户端登陆 + /// + MOBILE = 20, + + /// + /// invalid login + /// 无效的登陆方式 + /// + INVALID = 21, + } + + /// + /// device type enumeration + /// 设备类型枚举 + /// + public enum EM_NET_DEVICE_TYPE + { + /// + /// Unknow + // 未知 + /// + NET_PRODUCT_NONE = 0, + + /// + /// Non real-time MACE + /// 非实时MACE + /// + NET_DVR_NONREALTIME_MACE, + + /// + /// Non real-time + /// 非实时 + /// + NET_DVR_NONREALTIME, + + /// + /// Network video server + /// 网络视频服务器 + /// + NET_NVS_MPEG1, + + /// + /// MPEG1 2-ch DVR + /// MPEG1二路录像机 + /// + NET_DVR_MPEG1_2, + + /// + /// MPEG1 8-ch DVR + /// MPEG1八路录像机 + /// + NET_DVR_MPEG1_8, + + /// + /// MPEG4 8-ch DVR + /// MPEG4八路录像机 + /// + NET_DVR_MPEG4_8, + + /// + /// MPEG4 16-ch DVR + /// MPEG4 十六路录像机 + /// + NET_DVR_MPEG4_16, + + /// + /// LB series DVR + /// LB系列录像机 + /// + NET_DVR_MPEG4_SX2, + + /// + /// GB series DVR + /// GB系列录像机 + /// + NET_DVR_MEPG4_ST2, + + /// + /// HB series DVR + /// HB系列录像机 + /// + NET_DVR_MEPG4_SH2, + + /// + /// GBE series DVR + /// GBE系列录像机 + /// + NET_DVR_MPEG4_GBE, + + /// + /// II network video server + /// II代网络视频服务器 + /// + NET_DVR_MPEG4_NVSII, + + /// + /// New standard configuration protocol + /// 新标准配置协议 + /// + NET_DVR_STD_NEW, + + /// + /// DDNS server + /// DDNS服务器 + /// + NET_DVR_DDNS, + + /// + /// ATM series + /// ATM机 + /// + NET_DVR_ATM, + + /// + /// 2nd non real-time NB series DVR + /// 二代非实时NB系列机器 + /// + NET_NB_SERIAL, + + /// + /// LN series + /// LN系列产品 + /// + NET_LN_SERIAL, + + /// + /// BAV series + /// BAV系列产品 + /// + NET_BAV_SERIAL, + + /// + /// SDIP series + /// SDIP系列产品 + /// + NET_SDIP_SERIAL, + + /// + /// IPC series + /// IPC系列产品 + /// + NET_IPC_SERIAL, + + /// + /// NVS B series + /// NVS B系列 + /// + NET_NVS_B, + + /// + /// NVS H series + /// NVS H系列 + /// + NET_NVS_C, + + /// + /// NVS S series + /// NVS S系列 + /// + NET_NVS_S, + + /// + /// NVS E series + /// NVS E系列 + /// + NET_NVS_E, + + /// + /// Search device type from QueryDevState. it is in string format + /// 从QueryDevState中查询设备类型,以字符串格式 + /// + NET_DVR_NEW_PROTOCOL, + + /// + /// NVD + /// 解码器 + /// + NET_NVD_SERIAL, + + /// + /// N5 + /// N5 + /// + NET_DVR_N5, + + /// + /// HDVR + /// 混合DVR + /// + NET_DVR_MIX_DVR, + + /// + /// SVR series + /// SVR系列 + /// + NET_SVR_SERIAL, + + /// + /// SVR-BS + /// SVR-BS + /// + NET_SVR_BS, + + /// + /// NVR series + /// NVR系列 + /// + NET_NVR_SERIAL, + + /// + /// N51 + /// N51 + /// + NET_DVR_N51, + + /// + /// ITSE Intelligent Analysis Box + /// ITSE 智能分析盒 + /// + NET_ITSE_SERIAL, + + /// + /// Intelligent traffic camera equipment + /// 智能交通像机设备 + /// + NET_ITC_SERIAL, + + /// + /// radar speedometer HWS + /// 雷达测速仪HWS + /// + NET_HWS_SERIAL, + + /// + /// portable video record + /// 便携式音视频录像机 + /// + NET_PVR_SERIAL, + + /// + /// IVS(intelligent video server series) + /// IVS(智能视频服务器系列) + /// + NET_IVS_SERIAL, + + /// + /// universal intelligent detect video server series + /// 通用智能视频侦测服务器 + /// + NET_IVS_B, + + /// + /// face recognisation server + /// 人脸识别服务器 + /// + NET_IVS_F, + + /// + /// video quality diagnosis server + /// 视频质量诊断服务器 + /// + NET_IVS_V, + + /// + /// matrix + /// 矩阵 + /// + NET_MATRIX_SERIAL, + + /// + /// N52 + /// N52 + /// + NET_DVR_N52, + + /// + /// N56 + /// N56 + /// + NET_DVR_N56, + + /// + /// ESS + /// ESS + /// + NET_ESS_SERIAL, + + /// + /// 人数统计服务器 + /// + NET_IVS_PC, + + /// + /// pc-nvr + /// pc-nvr + /// + NET_PC_NVR, + + /// + /// screen controller + /// 大屏控制器 + /// + NET_DSCON, + + /// + /// network video storage server + /// 网络视频存储服务器 + /// + NET_EVS, + + /// + /// an embedded intelligent video analysis system + /// 嵌入式智能分析视频系统 + /// + NET_EIVS, + + /// + /// DVR-N6 + /// DVR-N6 + /// + NET_DVR_N6, + + /// + /// K-Lite Codec Pack + /// 万能解码器 + /// + NET_UDS, + + /// + /// Bank alarm host + /// 银行报警主机 + /// + NET_AF6016, + + /// + /// Video network alarm host + /// 视频网络报警主机 + /// + NET_AS5008, + + /// + /// Network alarm host + /// 网络报警主机 + /// + NET_AH2008, + + /// + /// Alarm host series + /// 报警主机系列 + /// + NET_A_SERIAL, + + /// + /// Access control series of products + /// 门禁系列产品 + /// + NET_BSC_SERIAL, + + /// + /// NVS series product + /// NVS系列产品 + /// + NET_NVS_SERIAL, + + /// + /// VTO series product + /// VTO系列产品 + /// + NET_VTO_SERIAL, + + /// + /// VTNC series product + /// VTNC系列产品 + /// + NET_VTNC_SERIAL, + + /// + /// TPC series product, it is the thermal device + /// TPC系列产品, 即热成像设备 + /// + NET_TPC_SERIAL, + + /// + /// ASM series product + /// 无线中继设备 + /// + NET_ASM_SERIAL, + + /// + /// VTS series product + /// 管理机 + /// + NET_VTS_SERIAL, + + /// + /// Alarm host-ARC2016C + /// 报警主机ARC2016C + /// + NET_ARC2016C, + + /// + /// ASA Attendance machine + /// 考勤机 + /// + NET_ASA, + + /// + /// Industry terminal walkie-talkie + /// 行业对讲终端 + /// + NET_VTT_SERIAL, + + /// + /// Alarm column + /// 报警柱 + /// + NET_VTA_SERIAL, + + /// + /// SIP Server + /// SIP服务器 + /// + NET_VTNS_SERIAL, + + /// + /// Indoor unit + /// 室内机 + /// + NET_VTH_SERIAL, + } + + /// + /// 查询设备信息参数 + /// + public enum EM_DEVICE_STATE + { + /// + /// Query device online state(return a DWORD value, 1-online, 0-offline) + /// 查询设备的在线状态(返回一个DWORD, 1表示在线, 0表示断线) + /// + ONLINE = 0x0035, + + /// + /// Query ptz state(struct DH_PTZ_LOCATION_INFO) + /// 查询云台状态信息(对应结构体 DH_PTZ_LOCATION_INFO) + /// + PTZ_LOCATION = 0x0036, + } + + /// + /// PTZ control command enumeration + /// 云台控制命令 + /// + public enum EM_EXTPTZ_ControlType : int + { + /// + /// Up + /// 上 + /// + UP = 0, + + /// + /// Down + /// 下 + /// + DOWN, + + /// + /// Left + /// 左 + /// + LEFT, + + /// + /// Right + /// 右 + /// + RIGHT, + + /// + /// +Zoom in + /// 变倍+ + /// + ZOOM_ADD, + + /// + /// -Zoom out + /// 变倍- + /// + ZOOM_DEC, + + /// + /// +Focus + /// 调焦+ + /// + FOCUS_ADD, + + /// + /// -Focus + /// 调焦- + /// + FOCUS_DEC, + + /// + /// + Aperture + /// 光圈+ + /// + APERTURE_ADD, + + /// + /// -Aperture + /// 光圈- + /// + APERTURE_DEC, + + /// + /// Go to preset + /// 转至预置点 + /// + POINT_MOVE, + + /// + /// Set + /// 设置 + /// + POINT_SET_CONTROL, + + /// + /// Delete + /// 删除 + /// + POINT_DEL_CONTROL, + + /// + /// Tour + /// 点间巡航 + /// + POINT_LOOP_CONTROL, + + /// + /// Light and wiper + /// 灯光雨刷 + /// + LAMP_CONTROL, + + /// + /// Upper left + /// 左上 + /// + LEFTTOP = 0x20, + + /// + /// Upper right + /// 右上 + /// + RIGHTTOP, + + /// + /// Down left + /// 左下 + /// + LEFTDOWN, + + /// + /// Down right + /// 右下 + /// + RIGHTDOWN, + + /// + /// Add preset to tour tour preset value + /// 加入预置点到巡航 巡航线路 预置点值 + /// + ADDTOLOOP, + + /// + /// Delete preset in tour tour preset value + /// 删除巡航中预置点 巡航线路 预置点值 + /// + DELFROMLOOP, + + /// + /// Delete tour tour + /// 清除巡航 巡航线路 + /// + CLOSELOOP, + + /// + /// Begin pan rotation + /// 开始水平旋转 + /// + STARTPANCRUISE, + + /// + /// Stop pan rotation + /// 停止水平旋转 + /// + STOPPANCRUISE, + + /// + /// Set left limit + /// 设置左边界 + /// + SETLEFTBORDER, + + /// + /// Set right limit + /// 设置右边界 + /// + SETRIGHTBORDER, + + /// + /// Begin scanning + /// 开始线扫 + /// + STARTLINESCAN, + + /// + /// Stop scanning + /// 停止线扫 + /// + CLOSELINESCAN, + + /// + /// Start mode mode line + /// 设置模式开始 模式线路 + /// + SETMODESTART, + + /// + /// Stop mode mode line + /// 设置模式结束 模式线路 + /// + SETMODESTOP, + + /// + /// Enable mode Mode line + /// 运行模式 模式线路 + /// + RUNMODE, + + /// + /// Disable mode Mode line + /// 停止模式 模式线路 + /// + STOPMODE, + + /// + /// Delete mode Mode line + /// 清除模式 模式线路 + /// + DELETEMODE, + + /// + /// Flip + /// 翻转命令 + /// + REVERSECOMM, + + /// + /// 3D position X address(8192) Y address(8192) zoom(4) + /// 快速定位 水平坐标(8192) 垂直坐标(8192) 变倍(4) + /// + FASTGOTO, + + /// + /// auxiliary open Auxiliary point(param4 corresponding struct PTZ_CONTROL_AUXILIARY,param1、param2、param3 is invalid,dwStop set to FALSE) + /// 辅助开关开 辅助点(param4对应 PTZ_CONTROL_AUXILIARY,param1、param2、param3无效,dwStop设置为FALSE) + /// + AUXIOPEN, + + /// + /// Auxiliary close Auxiliary point(param4 corresponding struct PTZ_CONTROL_AUXILIARY,param1、param2、param3 is invalid,dwStop set to FALSE) + /// 辅助开关关 辅助点(param4对应 PTZ_CONTROL_AUXILIARY,param1、param2、param3无效,dwStop设置为FALSE) + /// + AUXICLOSE, + + /// + /// Open dome menu + /// 打开球机菜单 + /// + OPENMENU = 0x36, + + /// + /// Close menu + /// 关闭菜单 + /// + CLOSEMENU, + + /// + /// Confirm menu + /// 菜单确定 + /// + MENUOK, + + /// + /// Cancel menu + /// 菜单取消 + /// + MENUCANCEL, + + /// + /// menu up + /// 菜单上 + /// + MENUUP, + + /// + /// menu down + /// 菜单下 + /// + MENUDOWN, + + /// + /// menu left + /// 菜单左 + /// + MENULEFT, + + /// + /// Menu right + /// 菜单右 + /// + MENURIGHT, + + /// + /// Alarm activate PTZ parm1:Alarm input channel;parm2:Alarm activation type 1-preset 2-scan 3-tour;parm 3:activation value,such as preset value. + /// 报警联动云台 parm1:报警输入通道;parm2:报警联动类型1-预置点2-线扫3-巡航;parm3:联动值,如预置点号 + /// + ALARMHANDLE = 0x40, + + /// + /// Matrix switch parm1:monitor number(video output number);parm2:video input number;parm3:matrix number + /// 矩阵切换 parm1:监视器号(视频输出号);parm2:视频输入号;parm3:矩阵号 + /// + MATRIXSWITCH = 0x41, + + /// + /// Light controller + /// 灯光控制器 + /// + LIGHTCONTROL, + + /// + /// 3D accurately positioning parm1:Pan degree(0~3600); parm2: tilt coordinates(0~900); parm3:zoom(1~128) + /// 三维精确定位 parm1:水平角度(0~3600);parm2:垂直坐标(0~900);parm3:变倍(1~128) + /// + EXACTGOTO, + + /// + /// Reset 3D positioning as zero + /// 三维定位重设零位 + /// + RESETZERO, + + /// + /// Absolute motion control command,param4 corresponding struct NET_PTZ_CONTROL_ABSOLUTELY + /// 绝对移动控制命令,param4对应结构 NET_PTZ_CONTROL_ABSOLUTELY + /// + MOVE_ABSOLUTELY, + + /// + /// Continuous motion control command,param4 corresponding struct NET_PTZ_CONTROL_CONTINUOUSLY + /// 持续移动控制命令,param4对应结构 NET_PTZ_CONTROL_CONTINUOUSLY + /// + MOVE_CONTINUOUSLY, + + /// + /// PTZ control command, at a certain speed to preset locu,parm4 corresponding struct NET_PTZ_CONTROL_GOTOPRESET + /// 云台控制命令,以一定速度转到预置位点,parm4对应结构NET_PTZ_CONTROL_GOTOPRESET + /// + GOTOPRESET, + + /// + /// Set to horizon(param4 corresponding struct NET_PTZ_VIEW_RANGE_INFO) + /// 设置可视域(param4对应结构 NET_PTZ_VIEW_RANGE_INFO) + /// + SET_VIEW_RANGE = 0x49, + + /// + /// Absolute focus(param4 corresponding struct NET_PTZ_FOCUS_ABSOLUTELY) + /// 绝对聚焦(param4对应结构NET_PTZ_FOCUS_ABSOLUTELY) + /// + FOCUS_ABSOLUTELY = 0x4A, + + /// + /// Level fan sweep(param4 corresponding NET_PTZ_CONTROL_SECTORSCAN,param1,param2,param3 is invalid) + /// 水平扇扫(param4对应PTZ_CONTROL_SECTORSCAN,param1、param2、param3无效) + /// + HORSECTORSCAN = 0x4B, + + /// + /// Vertical sweep fan(param4 corresponding NET_PTZ_CONTROL_SECTORSCAN,param1,param2,param3 is invalid) + /// 垂直扇扫(param4对应PTZ_CONTROL_SECTORSCAN,param1、param2、param3无效) + /// + VERSECTORSCAN = 0x4C, + + /// + /// Set absolute focus, focus on value, param1 for focal length, range: [0255], param2 as the focus, scope: [0255], param3, param4 is invalid + /// 设定绝对焦距、聚焦值,param1为焦距,范围:[0,255],param2为聚焦,范围:[0,255],param3、param4无效 + /// + SET_ABS_ZOOMFOCUS = 0x4D, + + /// + /// Control fish eye PTZ,param4corresponding to structure NET_PTZ_CONTROL_SET_FISHEYE_EPTZ + /// 控制鱼眼电子云台,param4对应结构 PTZ_CONTROL_SET_FISHEYE_EPTZ + /// + SET_FISHEYE_EPTZ = 0x4E, + + /// + /// Track start control(param4 corresponding to structure NET_PTZ_CONTROL_SET_TRACK_CONTROL,dwStop set as FALSE, param1、param2、param3 is invalid) + /// 轨道机开始控制(param4对应结构体为 PTZ_CONTROL_SET_TRACK_CONTROL,dwStop传FALSE, param1、param2、param3无效) + /// + SET_TRACK_START = 0x4F, + + /// + /// Track stop control (param4 corresponding to structure NET_PTZ_CONTROL_SET_TRACK_CONTROL,dwStop set as FALSE,param1、param2、param3 is invalid) + /// 轨道机停止控制(param4对应结构体为 PTZ_CONTROL_SET_TRACK_CONTROL,dwStop传FALSE,param1、param2、param3无效) + /// + SET_TRACK_STOP = 0x50, + + /// + /// Up + TELE param1=speed (1-8) + /// 上 + TELE param1=速度(1-8),下同 + /// + UP_TELE = 0x70, + + /// + /// Down + TELE + /// 下 + TELE + /// + DOWN_TELE, + + /// + /// Left + TELE + /// 左 + TELE + /// + LEFT_TELE, + + /// + /// Right + TELE + /// 右 + TELE + /// + RIGHT_TELE, + + /// + /// Upper left + TELE + /// 左上 + TELE + /// + LEFTUP_TELE, + + /// + /// Down left + TELE + /// 左下 + TELE + /// + LEFTDOWN_TELE, + + /// + /// Upper right + TELE + /// 右上 + TELE + /// + TIGHTUP_TELE, + + /// + /// Down right + TELE + /// 右下 + TELE + /// + RIGHTDOWN_TELE, + + /// + /// Up + WIDE param1=speed (1-8) + /// 上 + WIDE param1=速度(1-8),下同 + /// + UP_WIDE, + + /// + /// Down + WIDE + /// 下 + WIDE + /// + DOWN_WIDE, + + /// + /// Left + WIDE + /// 左 + WIDE + /// + LEFT_WIDE, + + /// + /// Right + WIDE + /// 右 + WIDE + /// + RIGHT_WIDE, + + /// + /// Upper left + WIDE + /// 左上 + WIDE + /// + LEFTUP_WIDE, + + /// + /// Down left+ WIDE + /// 左下 + WIDE + /// + LEFTDOWN_WIDE, + + /// + /// Upper right + WIDE + /// 右上 + WIDE + /// + TIGHTUP_WIDE, + + /// + /// Down right + WIDE + /// 右下 + WIDE + /// + RIGHTDOWN_WIDE, + + /// + /// Go to preset point and take a picture + /// 至预置点并抓图 + /// + GOTOPRESETSNAP = 0x80, + + /// + /// Calibrate the PTZ direction (two-way calibration) + /// 校准云台方向(双方向校准) + /// + DIRECTIONCALIBRATION = 0x82, + + /// + /// Calibrate the PTZ direction (one-way calibration) param4 -> NET_IN_CALIBRATE_SINGLEDIRECTION + /// 校准云台方向(单防线校准), param4 -> NET_IN_CALIBRATE_SINGLEDIRECTION + /// + SINGLEDIRECTIONCALIBRATION = 0x83, + + /// + /// Relative positioning of PTZ, param4 -> NET_IN_MOVERELATIVELY_INFO + /// 云台相对定位, param4 -> NET_IN_MOVERELATIVELY_INFO + /// + MOVE_RELATIVELY = 0x84, + + /// + /// Set direction for PTZ, param4 -> NET_IN_SET_DIRECTION_INFO + /// 设置云台方向, param4 -> NET_IN_SET_DIRECTION_INFO + /// + SET_DIRECTION = 0x85, + + /// + /// Precisely and absolutely movement control command, param4 -> NET_IN_PTZBASE_MOVEABSOLUTELY_INFO use CFG_CAP_CMD_PTZ command to get the capability of PTZ + /// if CFG_PTZ_PROTOCOL_CAPS_INFO -> bSupportReal equals TRUE means this device supports this feature + /// 精准绝对移动控制命令, param4 -> NET_IN_PTZBASE_MOVEABSOLUTELY_INFO(通过 CFG_CAP_CMD_PTZ 命令获取云台能力集( CFG_PTZ_PROTOCOL_CAPS_INFO ) + /// 若bSupportReal为TRUE则设备支持该操作) + /// + BASE_MOVE_ABSOLUTELY = 0x86, + + /// + /// Continuously movement control command, param4 -> NET_IN_PTZBASE_MOVECONTINUOUSLY_INFO. use CFG_CAP_CMD_PTZ command to get the capability of PTZ + /// if CFG_PTZ_PROTOCOL_CAPS_INFO -> stuMoveContinuously equals -> stuType.bSupportExtra equals TRUE means this device supports this feature + /// 云台连续移动控制命令, param4 -> NET_IN_PTZBASE_MOVECONTINUOUSLY_INFO. 通过 CFG_CAP_CMD_PTZ 命令获取云台能力集 + /// 若 CFG_PTZ_PROTOCOL_CAPS_INFO 中 stuMoveContinuously 字段的 为 TRUE, 表示设备支持该操作 + /// + BASE_MOVE_CONTINUOUSLY, + + /// + /// Maximum command value + /// 最大命令值 + /// + TOTAL, + } + + /// + /// 预置点状态枚举 + /// + public enum EM_DH_PTZ_PRESET_STATUS + { + UNKNOWN, // 未知 + REACH, // 预置点到达 + UNREACH, // 预置点未到达 + } + + /// + /// realplay type + /// 监视类型 + /// + public enum EM_RealPlayType + { + /// + /// Real-time preview + /// 实时预览 + /// + Realplay = 0, + + /// + /// Multiple-channel preview + /// 多画面预览 + /// + Multiplay, + + /// + /// Real-time monitor-main stream. It is the same as EM_RealPlayType.Realplay + /// 实时监视-主码流,等同于EM_RealPlayType.Realplay + /// + Realplay_0, + + /// + /// Real-time monitor -- extra stream 1 + /// 实时监视-从码流1 + /// + Realplay_1, + + /// + /// Real-time monitor -- extra stream 2 + /// 实时监视-从码流2 + /// + Realplay_2, + + /// + /// Real-time monitor -- extra stream 3 + /// 实时监视-从码流3 + /// + Realplay_3, + + /// + /// Multiple-channel preview--1-window + /// 多画面预览-1画面 + /// + Multiplay_1, + + /// + /// Multiple-channel preview--4-window + /// 多画面预览-4画面 + /// + Multiplay_4, + + /// + /// Multiple-channel preview--8-window + /// 多画面预览-8画面 + /// + Multiplay_8, + + /// + /// Multiple-channel preview--9-window + /// 多画面预览-9画面 + /// + Multiplay_9, + + /// + /// Multiple-channel preview--16-window + /// 多画面预览-16画面 + /// + Multiplay_16, + + /// + /// Multiple-channel preview--6-window + /// 多画面预览-6画面 + /// + Multiplay_6, + + /// + /// Multiple-channel preview--12-window + /// 多画面预览-12画面 + /// + Multiplay_12, + + /// + /// Multiple-channel preview--25-window + /// 多画面预览-25画面 + /// + Multiplay_25, + + /// + /// Multiple-channel preview--36-window + /// 多画面预览-36画面 + /// + Multiplay_36, + + /// + /// test stream + /// 带宽测试码流 + /// + Realplay_Test = 255, + } + + #endregion Sdk Enum + + #region Sdk Struct + + /// + /// CLIENT_LoginWithHighLevelSecurity 输入参数 + /// + public struct NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY + { + public uint dwSize;// 结构体大小 + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string szIP; // IP + + public int nPort; // 端口 + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string szUserName; // 用户名 + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string szPassword; // 密码 + + public EM_LOGIN_SPAC_CAP_TYPE emSpecCap; // 登录模式 + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] byReserved; // 字节对齐 + + public IntPtr pCapParam; // 见 CLIENT_LoginEx 接口 pCapParam 与 nSpecCap 关系 + } + + /// + /// device information structure + /// 设备信息结构体 + /// + public struct NET_DEVICEINFO_Ex + { + /// + /// serial number + /// 序列号 + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 48)] + public string sSerialNumber; + + /// + /// count of alarm input + /// 报警输入个数 + /// + public int nAlarmInPortNum; + + /// + /// count of alarm output + /// 报警输出个数 + /// + public int nAlarmOutPortNum; + + /// + /// number of disk + /// 硬盘个数 + /// + public int nDiskNum; + + /// + /// device type, refer to EM_NET_DEVICE_TYPE + /// 设备类型,见枚举NET_DEVICE_TYPE + /// + public EM_NET_DEVICE_TYPE nDVRType; + + /// + /// number of channel + /// 通道个数 + /// + public int nChanNum; + + /// + /// Online Timeout, Not Limited Access to 0, not 0 Minutes Limit Said + /// 在线超时时间,为0表示不限制登陆,非0表示限制的分钟数 + /// + public byte byLimitLoginTime; + + /// + /// When login failed due to password error, notice user by this parameter.This parameter is invalid when remaining login times is zero + /// 当登陆失败原因为密码错误时,通过此参数通知用户,剩余登陆次数,为0时表示此参数无效 + /// + public byte byLeftLogTimes; + + /// + /// keep bytes for aligned + /// 保留字节,字节对齐 + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public byte[] bReserved; + + /// + /// when log in failed,the left time for users to unlock (seconds), -1 indicate the device haven't set the parameter + /// 当登陆失败,用户解锁剩余时间(秒数), -1表示设备未设置该参数 + /// + public int nLockLeftTime; + + /// + /// reserved + /// 保留字节 + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] + public byte[] Reserved; + } + + /// + /// CLIENT_LoginWithHighLevelSecurity 输出参数 + /// + public struct NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY + { + public uint dwSize;// 结构体大小 + public NET_DEVICEINFO_Ex stuDeviceInfo; // 设备信息 + public int nError; // 错误码,见 CLIENT_Login 接口错误码 + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 132)] + public byte[] byReserved; // 保留字节 + } + + /// + /// 云台定位中非归一化坐标和变倍 + /// + public struct NET_PTZSPACE_UNNORMALIZED + { + public int nPosX; // x坐标 + public int nPosY; // y坐标 + public int nZoom; // 放大倍率 + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 52)] + public byte[] byReserved; // 预留字节 + } + + /// + /// 云台定位信息 + /// + //云台定位信息 + public struct DH_PTZ_LOCATION_INFO + { + public int nChannelID; // 通道号 + public int nPTZPan; // 云台水平运动位置,有效范围:[0,3600] + public int nPTZTilt; // 云台垂直运动位置,有效范围:[-1800,1800] + public int nPTZZoom; // 云台光圈变动位置,有效范围:[0,128] + public byte bState; // 云台运动状态, 0-未知 1-运动 2-空闲 + public byte bAction; // 云台动作,255-未知,0-预置点,1-线扫,2-巡航,3-巡迹,4-水平旋转,5-普通移动,6-巡迹录制,7-全景云台扫描,8-热度图 + + // 9-精确定位,10-设备校正,11-智能配置,12-云台重启 + public byte bFocusState; // 云台聚焦状态, 0-未知, 1-运动状态, 2-空闲 + + public byte bEffectiveInTimeSection; // 在时间段内预置点状态是否有效 + + //如果当前上报的预置点是时间段内的预置点,则为1,其他情况为0 + public int nPtzActionID; // 巡航ID号 + + public uint dwPresetID; // 云台所在预置点编号 + public float fFocusPosition; // 聚焦位置 + public byte bZoomState; // 云台ZOOM状态,0-未知,1-ZOOM,2-空闲 + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] bReserved; // 对齐 + + public uint dwSequence; // 包序号,用于校验是否丢包 + public uint dwUTC; // 对应的UTC(1970-1-1 00:00:00)秒数。 + public EM_DH_PTZ_PRESET_STATUS emPresetStatus; // 预置点位置 + + /// + /// 保留字段 + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 248)] + public int[] reserved; + }; + + #endregion Sdk Struct + + #region Common Method + + /// + /// network disconnection callback function original shape + /// 断线回调函数 + /// + /// user LoginID:Login's returns value 登陆ID + /// device IP 设备IP + /// device prot 设备端口 + /// user data from Init function 用户数据 + public delegate void fDisConnectCallBack(IntPtr lLoginID, IntPtr pchDVRIP, int nDVRPort, IntPtr dwUser); + + /// + /// network re-connection callback function original shape + /// 重连回调函数 + /// + /// user LoginID:Login's returns value 登陆ID + /// device IP,string type 设备IP + /// device prot 设备端口 + /// user data from SetAutoReconnect function 用户数据 + public delegate void fHaveReConnectCallBack(IntPtr lLoginID, IntPtr pchDVRIP, int nDVRPort, IntPtr dwUser); + + [DllImport(LibSdkPath)] + public static extern bool CLIENT_InitEx(fDisConnectCallBack? cbDisConnect, IntPtr dwUser, IntPtr lpInitParam); + + [DllImport(LibSdkPath)] + public static extern void CLIENT_Cleanup(); + + [DllImport(LibSdkPath)] + public static extern int CLIENT_GetLastError(); + + [DllImport(LibSdkPath)] + public static extern IntPtr CLIENT_LoginWithHighLevelSecurity(ref NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY pstInParam, ref NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY pstOutParam); + + [DllImport(LibSdkPath)] + public static extern bool CLIENT_Logout(IntPtr lLoginID); + + [DllImport(LibSdkPath)] + public static extern void CLIENT_SetAutoReconnect(fHaveReConnectCallBack cbAutoConnect, IntPtr dwUser); + + [DllImport(LibSdkPath)] + public static extern bool CLIENT_QueryDevState(IntPtr lLoginID, int nType, IntPtr pBuf, int nBufLen, ref int pRetLen, int waittime); + + /// + /// PTZ control + /// PTZ控制接口 + /// + /// user LoginID:Login's returns value 登陆ID,Login返回值 + /// channel number 通道号 + /// PTZ control commands 控制命令 + /// Parameter1 details refer to EM_EXTPTZ_ControlType 控制命令的参数1 + /// Parameter2 details refer to EM_EXTPTZ_ControlType 控制命令的参数2 + /// Parameter3 details refer to EM_EXTPTZ_ControlType 控制命令的参数3 + /// stop or not, effective to PTZ eight-directions operation and lens operation. During other operation, this parameter should fill in false 是否停止 + /// support PTZ control extensive command,support these commands: 控制命令的参数4 + /// EM_EXTPTZ_ControlType.MOVE_ABSOLUTELY:Absolute motion control commands,param4 corresponding struct NET_PTZ_CONTROL_ABSOLUTELY + /// EM_EXTPTZ_ControlType.MOVE_CONTINUOUSLY:Continuous motion control commands,param4 corresponding struct NET_PTZ_CONTROL_CONTINUOUSLY + /// EM_EXTPTZ_ControlType.GOTOPRESET:PTZ control command, at a certain speed to preset locus,parm4 corresponding struct NET_PTZ_CONTROL_GOTOPRESET + /// EM_EXTPTZ_ControlType.SET_VIEW_RANGE:Set to horizon(param4 corresponding struct NET_PTZ_VIEW_RANGE_INFO + /// EM_EXTPTZ_ControlType.FOCUS_ABSOLUTELY:Absolute focus(param4 corresponding struct NET_PTZ_FOCUS_ABSOLUTELY + /// EM_EXTPTZ_ControlType.HORSECTORSCAN:Level fan sweep(param4 corresponding NET_PTZ_CONTROL_SECTORSCAN,param1、param2、param3 is invalid + /// EM_EXTPTZ_ControlType.VERSECTORSCAN:Vertical sweep fan(param4 corresponding NET_PTZ_CONTROL_SECTORSCAN,param1、param2、param3 is invalid + /// EM_EXTPTZ_ControlType.SET_FISHEYE_EPTZ:Control fish eye PTZ,param4corresponding to structure NET_PTZ_CONTROL_SET_FISHEYE_EPTZ + /// EM_EXTPTZ_ControlType.SET_TRACK_START/SET_TRACK_STOP:param4 corresponding to structure NET_PTZ_CONTROL_SET_TRACK_CONTROL,dwStop set as FALSE,param1、param2、param3 is invalid + /// failed return false, successful return true 失败返回false 成功返回true + [DllImport(LibSdkPath)] + public static extern bool CLIENT_DHPTZControlEx2(IntPtr lLoginID, int nChannelID, uint dwPTZCommand, int lParam1, int lParam2, int lParam3, bool dwStop, IntPtr param4); + + /// + /// start real-time monitor.support 32bit and 64bit + /// 开始实时监视.支持32位和64位 + /// + /// user LoginID:Login's returns value 登陆ID,Login返回值 + /// real time monitor channel NO.(from 0). 通道号 + /// display window handle. When value is 0(IntPtr.Zero), data are not decoded or displayed 显示窗口句柄 + /// realplay type 监视类型 + /// failed return 0, successful return the real time monitorID(real time monitor handle),as parameter of related function. 失败返回0,成功返回大于0的值 + [DllImport(LibSdkPath)] + public static extern IntPtr CLIENT_RealPlayEx(IntPtr lLoginID, int nChannelID, IntPtr hWnd, EM_RealPlayType rType); + + /// + /// stop real time monitoring + /// 关闭实时监视 + /// + /// monitor handle StartRealPlay returns value 监视ID StartRealPlay返回值 + /// failed return false, successful return true 失败返回false 成功返回true + [DllImport(LibSdkPath)] + public static extern bool CLIENT_StopRealPlayEx(IntPtr lRealHandle); + + #endregion Common Method +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/DaHua/DaHuaSdk.cs b/EC.Util/CameraSDK/DaHua/DaHuaSdk.cs new file mode 100644 index 0000000..607f759 --- /dev/null +++ b/EC.Util/CameraSDK/DaHua/DaHuaSdk.cs @@ -0,0 +1,180 @@ +using System.Runtime.InteropServices; + +namespace EC.Util.CameraSDK; + +public class DaHuaSdk : ICameraSdk +{ + #region Fields + + private IntPtr LoginId { get; set; } = IntPtr.Zero; + + private int Channel { get; } = 1; + + #endregion Fields + + public DaHuaSdk(CameraInfo cameraInfo) : base(cameraInfo) + { + } + + #region Base Method + + public override bool Init() + { + bool ret = ConnectSuccess(); + if (ret) return true; + DaHuaOriSdk.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY stuInParam = new(); + stuInParam.dwSize = (uint)Marshal.SizeOf(stuInParam); + stuInParam.szIP = CameraInfo.Ip; + stuInParam.nPort = CameraInfo.Port; + stuInParam.szUserName = CameraInfo.UserName; + stuInParam.szPassword = CameraInfo.Password; + stuInParam.emSpecCap = DaHuaOriSdk.EM_LOGIN_SPAC_CAP_TYPE.TCP; + stuInParam.pCapParam = IntPtr.Zero; + DaHuaOriSdk.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY stuOutParam = new(); + stuOutParam.dwSize = (uint)Marshal.SizeOf(stuOutParam); + LoginId = DaHuaOriSdk.CLIENT_LoginWithHighLevelSecurity(ref stuInParam, ref stuOutParam); + ret = ConnectSuccess(); + if (ret) DaHuaOriSdk.CLIENT_SetAutoReconnect(delegate (IntPtr lLoginID, IntPtr pchDVRIP, int nDVRPort, IntPtr dwUser) + { + LoginId = lLoginID; + }, IntPtr.Zero); + else BuildException(); + return ret; + } + + public override bool Destory() + { + bool ret = ConnectSuccess(); + if (!ret) return true; + ret = DaHuaOriSdk.CLIENT_Logout(LoginId); + if (ret) LoginId = IntPtr.Zero; + else BuildException(); + return ret; + } + + public override bool ConnectSuccess() + { + return LoginId != IntPtr.Zero; + } + + internal override void BuildException() + { + uint errCode = (uint)DaHuaOriSdk.CLIENT_GetLastError(); + if (errCode == 0) return; + errCode -= 0x80000000; + throw CameraException.New(CameraInfo, (int)errCode); + } + + #endregion Base Method + + #region Ptz Method + + private static class GPIParams + { + public static int Size { get; private set; } + public static Type Type { get; private set; } + + static GPIParams() + { + DaHuaOriSdk.DH_PTZ_LOCATION_INFO info = new(); + Size = Marshal.SizeOf(info); + Type = info.GetType(); + } + } + + public override PtzInfo GetPtzInfo() + { + bool ret = ConnectSuccess(); + if (!ret) return PtzInfo.Default; + + DaHuaOriSdk.DH_PTZ_LOCATION_INFO entity = new(); + int nBufLen = GPIParams.Size; + int pRetLen = 0; + IntPtr ptrBuf = Marshal.AllocHGlobal(GPIParams.Size); + Marshal.StructureToPtr(entity, ptrBuf, true); + try + { + ret = DaHuaOriSdk.CLIENT_QueryDevState(LoginId, (int)DaHuaOriSdk.EM_DEVICE_STATE.PTZ_LOCATION, ptrBuf, nBufLen, ref pRetLen, 3000); + if (!ret) { BuildException(); return PtzInfo.Default; } + object? objBuf = Marshal.PtrToStructure(ptrBuf, GPIParams.Type); + if (objBuf == null) return PtzInfo.Default; + entity = (DaHuaOriSdk.DH_PTZ_LOCATION_INFO)objBuf; + return PtzInfo.New(entity.nPTZPan, entity.nPTZTilt, entity.nPTZZoom); + } + finally + { + Marshal.FreeHGlobal(ptrBuf); + } + } + + public override bool TryGetPtzInfo(out PtzInfo ptzInfo) + { + bool ret = ConnectSuccess(); + if (!ret) { ptzInfo = PtzInfo.Default; return false; } + + DaHuaOriSdk.DH_PTZ_LOCATION_INFO entity = new(); + int nBufLen = GPIParams.Size; + int pRetLen = 0; + IntPtr ptrBuf = Marshal.AllocHGlobal(GPIParams.Size); + Marshal.StructureToPtr(entity, ptrBuf, true); + try + { + ret = DaHuaOriSdk.CLIENT_QueryDevState(LoginId, (int)DaHuaOriSdk.EM_DEVICE_STATE.PTZ_LOCATION, ptrBuf, nBufLen, ref pRetLen, 3000); + if (!ret) { BuildException(); ptzInfo = PtzInfo.Default; return false; } + object? objBuf = Marshal.PtrToStructure(ptrBuf, GPIParams.Type); + if (objBuf == null) { ptzInfo = PtzInfo.Default; return false; } + entity = (DaHuaOriSdk.DH_PTZ_LOCATION_INFO)objBuf; + ptzInfo = PtzInfo.New(entity.nPTZPan, entity.nPTZTilt, entity.nPTZZoom); + return true; + } + finally + { + Marshal.FreeHGlobal(ptrBuf); + } + } + + public override bool PtzMove(int cmd, int stop, int speed) + { + if (!ConnectSuccess()) return false; + bool stopret = stop == 1; + bool ret = DaHuaOriSdk.CLIENT_DHPTZControlEx2(LoginId, Channel, (uint)cmd, speed, speed, 0, stopret, IntPtr.Zero); + if (!ret) BuildException(); + return ret; + } + + public override bool PtzPreset(int cmd, int presetId) + { + if (!ConnectSuccess()) return false; + bool ret = DaHuaOriSdk.CLIENT_DHPTZControlEx2(LoginId, Channel, (uint)DaHuaOriSdk.EM_EXTPTZ_ControlType.POINT_MOVE, 0, presetId, 0, false, IntPtr.Zero); + if (!ret) BuildException(); + return ret; + } + + #endregion Ptz Method + + #region Video Method + + private IntPtr RealplayHandle { get; set; } = IntPtr.Zero; + + public override void StartPlay(IntPtr hwnd) + { + if (!ConnectSuccess() || IsPlaying()) return; + RealplayHandle = DaHuaOriSdk.CLIENT_RealPlayEx(LoginId, Channel, hwnd, DaHuaOriSdk.EM_RealPlayType.Realplay); + if (RealplayHandle == IntPtr.Zero) BuildException(); + } + + public override void StopPlay() + { + if (!IsPlaying()) return; + bool ret = DaHuaOriSdk.CLIENT_StopRealPlayEx(RealplayHandle); + RealplayHandle = IntPtr.Zero; + if (!ret) BuildException(); + } + + public override bool IsPlaying() + { + return RealplayHandle != IntPtr.Zero; + } + + #endregion Video Method +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/HiK/HiKOriSdk.cs b/EC.Util/CameraSDK/HiK/HiKOriSdk.cs new file mode 100644 index 0000000..f435066 --- /dev/null +++ b/EC.Util/CameraSDK/HiK/HiKOriSdk.cs @@ -0,0 +1,402 @@ +//#define Win32 +#define Win64 + +//#define Linux32 +//#define Linux64 +using System.Runtime.InteropServices; + +namespace EC.Util.CameraSDK; + +public class HiKOriSdk +{ + #region Fields + +#if Win32 + public const string LibSdkPath = @"./libs/hik/win32/HCNetSDK.dll"; +#elif Win64 + public const string LibSdkPath = @"./libs/hik/win64/HCNetSDK.dll"; +#elif Linux32 + public const string LibSdkPath = @"./libs/hik/linux32/libhcnetsdk.so"; +#elif Linux64 + public const string LibSdkPath = @"./libs/hik/linux64/libhcnetsdk.so"; +#endif + + private const bool Debug = true; + + #endregion Fields + + static HiKOriSdk() + { + GlobalInit(); + } + + #region Global + + public static bool InitSuccess { get; private set; } + + public static bool GlobalInit() + { + if (InitSuccess) return true; + bool ret = NET_DVR_Init(); + InitSuccess = ret; + if (!ret) throw new Exception("HiKOriSdk global init failure."); + if (Debug) NET_DVR_SetLogToFile(3, Path.Combine("./log", "hikSdkLog"), true); + return ret; + } + + public static bool GlobalDestory() + { + if (!InitSuccess) return true; + bool ret = NET_DVR_Cleanup(); + if (ret) InitSuccess = false; + return ret; + } + + #endregion Global + + #region Sdk Const + + public const int SERIALNO_LEN = 48; //序列号长度 + + public const int PtzSpeedMin = 1; + public const int PtzSpeedMax = 7; + + public const int NET_DVR_DEV_ADDRESS_MAX_LEN = 129; + public const int NET_DVR_LOGIN_USERNAME_MAX_LEN = 64; + public const int NET_DVR_LOGIN_PASSWD_MAX_LEN = 64; + + #region 用于 NET_DVR_SetDVRConfig 和 NET_DVR_GetDVRConfig + + public const int NET_DVR_SET_PTZPOS = 292; //云台设置PTZ位置 + public const int NET_DVR_GET_PTZPOS = 293; //云台获取PTZ位置 + public const int NET_DVR_GET_PTZSCOPE = 294; //云台获取PTZ范围 + + #endregion 用于 NET_DVR_SetDVRConfig 和 NET_DVR_GetDVRConfig + + /********************预览回调函数*********************/ + public const int NET_DVR_SYSHEAD = 1;//系统头数据 + public const int NET_DVR_STREAMDATA = 2;//视频流数据(包括复合流和音视频分开的视频流数据) + public const int NET_DVR_AUDIOSTREAMDATA = 3;//音频流数据 + public const int NET_DVR_STD_VIDEODATA = 4;//标准视频流数据 + public const int NET_DVR_STD_AUDIODATA = 5;//标准音频流数据 + + #region PtzMove + + public const int ZOOM_IN = 11;// 焦距以速度SS变大(倍率变大) + public const int ZOOM_OUT = 12;// 焦距以速度SS变小(倍率变小) + public const int FOCUS_NEAR = 13;// 焦点以速度SS前调 + public const int FOCUS_FAR = 14;// 焦点以速度SS后调 + public const int IRIS_OPEN = 15;// 光圈以速度SS扩大 + public const int IRIS_CLOSE = 16;// 光圈以速度SS缩小 + + public const int TILT_UP = 21; /* 云台以SS的速度上仰 */ + public const int TILT_DOWN = 22; /* 云台以SS的速度下俯 */ + public const int PAN_LEFT = 23; /* 云台以SS的速度左转 */ + public const int PAN_RIGHT = 24; /* 云台以SS的速度右转 */ + public const int UP_LEFT = 25; /* 云台以SS的速度上仰和左转 */ + public const int UP_RIGHT = 26; /* 云台以SS的速度上仰和右转 */ + public const int DOWN_LEFT = 27; /* 云台以SS的速度下俯和左转 */ + public const int DOWN_RIGHT = 28; /* 云台以SS的速度下俯和右转 */ + public const int PAN_AUTO = 29; /* 云台以SS的速度左右自动扫描 */ + public const int GOTO_PRESET = 39; /* 快球转到预置点 */ + + #endregion PtzMove + + #endregion Sdk Const + + #region Sdk Struct + + //NET_DVR_Login()参数结构 + [StructLayout(LayoutKind.Sequential)] + public struct NET_DVR_DEVICEINFO + { + [MarshalAs( + UnmanagedType.ByValArray, + SizeConst = SERIALNO_LEN, + ArraySubType = UnmanagedType.I1 + )] + public byte[] sSerialNumber; //序列号 + + public byte byAlarmInPortNum; //DVR报警输入个数 + public byte byAlarmOutPortNum; //DVR报警输出个数 + public byte byDiskNum; //DVR硬盘个数 + public byte byDVRType; //DVR类型, 1:DVR 2:ATM DVR 3:DVS ...... + public byte byChanNum; //DVR 通道个数 + public byte byStartChan; //起始通道号,例如DVS-1,DVR - 1 + } + + //NET_DVR_Login_V30()参数结构 + [StructLayout(LayoutKind.Sequential)] + public struct NET_DVR_DEVICEINFO_V30 + { + [MarshalAs( + UnmanagedType.ByValArray, + SizeConst = SERIALNO_LEN, + ArraySubType = UnmanagedType.I1 + )] + public byte[] sSerialNumber; //序列号 + + public byte byAlarmInPortNum; //报警输入个数 + public byte byAlarmOutPortNum; //报警输出个数 + public byte byDiskNum; //硬盘个数 + public byte byDVRType; //设备类型, 1:DVR 2:ATM DVR 3:DVS ...... + public byte byChanNum; //模拟通道个数 + public byte byStartChan; //起始通道号,例如DVS-1,DVR - 1 + public byte byAudioChanNum; //语音通道数 + public byte byIPChanNum; //最大数字通道个数,低位 + public byte byZeroChanNum; //零通道编码个数 //2010-01-16 + public byte byMainProto; //主码流传输协议类型 0-private, 1-rtsp,2-同时支持private和rtsp + public byte bySubProto; //子码流传输协议类型0-private, 1-rtsp,2-同时支持private和rtsp + public byte bySupport; //能力,位与结果为0表示不支持,1表示支持, + + //bySupport & 0x1, 表示是否支持智能搜索 + //bySupport & 0x2, 表示是否支持备份 + //bySupport & 0x4, 表示是否支持压缩参数能力获取 + //bySupport & 0x8, 表示是否支持多网卡 + //bySupport & 0x10, 表示支持远程SADP + //bySupport & 0x20, 表示支持Raid卡功能 + //bySupport & 0x40, 表示支持IPSAN 目录查找 + //bySupport & 0x80, 表示支持rtp over rtsp + public byte bySupport1; // 能力集扩充,位与结果为0表示不支持,1表示支持 + + //bySupport1 & 0x1, 表示是否支持snmp v30 + //bySupport1 & 0x2, 支持区分回放和下载 + //bySupport1 & 0x4, 是否支持布防优先级 + //bySupport1 & 0x8, 智能设备是否支持布防时间段扩展 + //bySupport1 & 0x10, 表示是否支持多磁盘数(超过33个) + //bySupport1 & 0x20, 表示是否支持rtsp over http + //bySupport1 & 0x80, 表示是否支持车牌新报警信息2012-9-28, 且还表示是否支持NET_DVR_IPPARACFG_V40结构体 + public byte bySupport2; /*能力,位与结果为0表示不支持,非0表示支持 + + bySupport2 & 0x1, 表示解码器是否支持通过URL取流解码 + bySupport2 & 0x2, 表示支持FTPV40 + bySupport2 & 0x4, 表示支持ANR + bySupport2 & 0x8, 表示支持CCD的通道参数配置 + bySupport2 & 0x10, 表示支持布防报警回传信息(仅支持抓拍机报警 新老报警结构) + bySupport2 & 0x20, 表示是否支持单独获取设备状态子项 + bySupport2 & 0x40, 表示是否是码流加密设备*/ + public ushort wDevType; //设备型号 + public byte bySupport3; //能力集扩展,位与结果为0表示不支持,1表示支持 + + //bySupport3 & 0x1, 表示是否多码流 + // bySupport3 & 0x4 表示支持按组配置, 具体包含 通道图像参数、报警输入参数、IP报警输入、输出接入参数、 + // 用户参数、设备工作状态、JPEG抓图、定时和时间抓图、硬盘盘组管理 + //bySupport3 & 0x8为1 表示支持使用TCP预览、UDP预览、多播预览中的"延时预览"字段来请求延时预览(后续都将使用这种方式请求延时预览)。而当bySupport3 & 0x8为0时,将使用 "私有延时预览"协议。 + //bySupport3 & 0x10 表示支持"获取报警主机主要状态(V40)"。 + //bySupport3 & 0x20 表示是否支持通过DDNS域名解析取流 + + public byte byMultiStreamProto; //是否支持多码流,按位表示,0-不支持,1-支持,bit1-码流3,bit2-码流4,bit7-主码流,bit-8子码流 + public byte byStartDChan; //起始数字通道号,0表示无效 + public byte byStartDTalkChan; //起始数字对讲通道号,区别于模拟对讲通道号,0表示无效 + public byte byHighDChanNum; //数字通道个数,高位 + public byte bySupport4; + public byte byLanguageType; // 支持语种能力,按位表示,每一位0-不支持,1-支持 + + // byLanguageType 等于0 表示 老设备 + // byLanguageType & 0x1表示支持中文 + // byLanguageType & 0x2表示支持英文 + [MarshalAs( + UnmanagedType.ByValArray, + SizeConst = 9, + ArraySubType = UnmanagedType.I1 + )] + public byte[] byRes2; //保留 + } + + public delegate void LOGINRESULTCALLBACK(int lUserID, int dwResult, IntPtr lpDeviceInfo, IntPtr pUser); + + [StructLayout(LayoutKind.Sequential)] + public struct NET_DVR_USER_LOGIN_INFO + { + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = NET_DVR_DEV_ADDRESS_MAX_LEN, ArraySubType = UnmanagedType.I1)] + public byte[] sDeviceAddress; + + public byte byUseTransport; + public ushort wPort; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = NET_DVR_LOGIN_USERNAME_MAX_LEN, ArraySubType = UnmanagedType.I1)] + public byte[] sUserName; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = NET_DVR_LOGIN_PASSWD_MAX_LEN, ArraySubType = UnmanagedType.I1)] + public byte[] sPassword; + + public LOGINRESULTCALLBACK cbLoginResult; + public IntPtr pUser; + public bool bUseAsynLogin; + public byte byProxyType; //0:不使用代理,1:使用标准代理,2:使用EHome代理 + public byte byUseUTCTime; //0-不进行转换,默认,1-接口上输入输出全部使用UTC时间,SDK完成UTC时间与设备时区的转换,2-接口上输入输出全部使用平台本地时间,SDK完成平台本地时间与设备时区的转换 + public byte byLoginMode; //0-Private, 1-ISAPI, 2-自适应 + public byte byHttps; //0-不适用tls,1-使用tls 2-自适应 + public int iProxyID; //代理服务器序号,添加代理服务器信息时,相对应的服务器数组下表值 + public byte byVerifyMode; //认证方式,0-不认证,1-双向认证,2-单向认证;认证仅在使用TLS的时候生效; + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 119, ArraySubType = UnmanagedType.I1)] + public byte[] byRes3; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NET_DVR_DEVICEINFO_V40 + { + public NET_DVR_DEVICEINFO_V30 struDeviceV30; + public byte bySupportLock; //设备支持锁定功能,该字段由SDK根据设备返回值来赋值的。bySupportLock为1时,dwSurplusLockTime和byRetryLoginTime有效 + public byte byRetryLoginTime; //剩余可尝试登陆的次数,用户名,密码错误时,此参数有效 + public byte byPasswordLevel; //admin密码安全等级0-无效,1-默认密码,2-有效密码,3-风险较高的密码。当用户的密码为出厂默认密码(12345)或者风险较高的密码时,上层客户端需要提示用户更改密码。 + public byte byProxyType;//代理类型,0-不使用代理, 1-使用socks5代理, 2-使用EHome代理 + public uint dwSurplusLockTime; //剩余时间,单位秒,用户锁定时,此参数有效 + public byte byCharEncodeType; //字符编码类型(SDK所有接口返回的字符串编码类型,透传接口除外):0- 无字符编码信息(老设备),1- GB2312(简体中文),2- GBK,3- BIG5(繁体中文),4- Shift_JIS(日文),5- EUC-KR(韩文),6- UTF-8,7- ISO8859-1,8- ISO8859-2,9- ISO8859-3,…,依次类推,21- ISO8859-15(西欧) + public byte bySupportDev5;//支持v50版本的设备参数获取,设备名称和设备类型名称长度扩展为64字节 + public byte bySupport; //能力集扩展,位与结果:0- 不支持,1- 支持 + + // bySupport & 0x1: 保留 + // bySupport & 0x2: 0-不支持变化上报 1-支持变化上报 + public byte byLoginMode; //登录模式 0-Private登录 1-ISAPI登录 + + public int dwOEMCode; + public int iResidualValidity; //该用户密码剩余有效天数,单位:天,返回负值,表示密码已经超期使用,例如“-3表示密码已经超期使用3天” + public byte byResidualValidity; // iResidualValidity字段是否有效,0-无效,1-有效 + + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 243, ArraySubType = UnmanagedType.I1)] + public byte[] byRes2; + } + + //球机位置信息 + [StructLayout(LayoutKind.Sequential)] + public struct NET_DVR_PTZPOS + { + public ushort wAction; //获取时该字段无效 + public ushort wPanPos; //水平参数 + public ushort wTiltPos; //垂直参数 + public ushort wZoomPos; //变倍参数 + } + + //球机范围信息 + [StructLayout(LayoutKind.Sequential)] + public struct NET_DVR_PTZSCOPE + { + public ushort wPanPosMin; //水平参数min + public ushort wPanPosMax; //水平参数max + public ushort wTiltPosMin; //垂直参数min + public ushort wTiltPosMax; //垂直参数max + public ushort wZoomPosMin; //变倍参数min + public ushort wZoomPosMax; //变倍参数max + } + + //邦诺CVR + public const int MAX_ID_COUNT = 256; + + public const int MAX_STREAM_ID_COUNT = 1024; + public const int STREAM_ID_LEN = 32; + public const int PLAN_ID_LEN = 32; + + //预览V40接口 + [StructLayout(LayoutKind.Sequential)] + public struct NET_DVR_PREVIEWINFO + { + public int lChannel; //通道号 + public uint dwStreamType; // 码流类型,0-主码流,1-子码流,2-码流3,3-码流4 等以此类推 + public uint dwLinkMode; // 0:TCP方式,1:UDP方式,2:多播方式,3 - RTP方式,4-RTP/RTSP,5-RSTP/HTTP + public IntPtr hPlayWnd; //播放窗口的句柄,为NULL表示不播放图象 + public bool bBlocked; //0-非阻塞取流, 1-阻塞取流, 如果阻塞SDK内部connect失败将会有5s的超时才能够返回,不适合于轮询取流操作. + public bool bPassbackRecord; //0-不启用录像回传,1启用录像回传 + public byte byPreviewMode; //预览模式,0-正常预览,1-延迟预览 + + [MarshalAs( + UnmanagedType.ByValArray, + SizeConst = STREAM_ID_LEN, + ArraySubType = UnmanagedType.I1 + )] + public byte[] byStreamID; //流ID,lChannel为0xffffffff时启用此参数 + + public byte byProtoType; //应用层取流协议,0-私有协议,1-RTSP协议 + public byte byRes1; + public byte byVideoCodingType; //码流数据编解码类型 0-通用编码数据 1-热成像探测器产生的原始数据(温度数据的加密信息,通过去加密运算,将原始数据算出真实的温度值) + public uint dwDisplayBufNum; //播放库播放缓冲区最大缓冲帧数,范围1-50,置0时默认为1 + public byte byNPQMode; //NPQ是直连模式,还是过流媒体 0-直连 1-过流媒体 + + [MarshalAs( + UnmanagedType.ByValArray, + SizeConst = 215, + ArraySubType = UnmanagedType.I1 + )] + public byte[] byRes; + } + + #endregion Sdk Struct + + #region Common Method + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool NET_DVR_Init(); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool NET_DVR_Cleanup(); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool NET_DVR_SetLogToFile(int bLogEnable, string strLogDir, bool bAutoDel); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern uint NET_DVR_GetLastError(); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern int NET_DVR_Login_V30(string sDVRIP, int wDVRPort, string sUserName, string sPassword, ref NET_DVR_DEVICEINFO_V30 lpDeviceInfo); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern int NET_DVR_Login_V40(ref NET_DVR_USER_LOGIN_INFO pLoginInfo, ref NET_DVR_DEVICEINFO_V40 lpDeviceInfo); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool NET_DVR_Logout(int iUserID); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool NET_DVR_SetReconnect(uint dwInterval, int bEnableRecon); + + //参数配置 begin + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool NET_DVR_GetDVRConfig(int lUserID, uint dwCommand, int lChannel, IntPtr lpOutBuffer, uint dwOutBufferSize, ref uint lpBytesReturned); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool NET_DVR_PTZControlWithSpeed_Other(int lUserID, int lChannel, uint dwPTZCommand, uint dwStop, uint dwSpeed); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool NET_DVR_PTZPreset_Other(int lUserID, int lChannel, uint dwPTZPresetCmd, uint dwPresetIndex); + + /********************************************************* + Function: REALDATACALLBACK + Desc: 预览回调 + Input: lRealHandle 当前的预览句柄 + dwDataType 数据类型 + pBuffer 存放数据的缓冲区指针 + dwBufSize 缓冲区大小 + pUser 用户数据 + Output: + Return: void + **********************************************************/ + + public delegate void RealDataCallBack(int lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser); + + /********************************************************* + Function: NET_DVR_RealPlay_V40 + Desc: 实时预览扩展接口。 + Input: lUserID [in] NET_DVR_Login()或NET_DVR_Login_V30()的返回值 + lpPreviewInfo [in] 预览参数 + fRealDataCallBack_V30 [in] 码流数据回调函数 + pUser [in] 用户数据 + Output: + Return: 1表示失败,其他值作为NET_DVR_StopRealPlay等函数的句柄参数 + **********************************************************/ + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern int NET_DVR_RealPlay_V40(int iUserID, ref NET_DVR_PREVIEWINFO lpPreviewInfo, RealDataCallBack fRealDataCallBack_V30, IntPtr pUser); + + /********************************************************* + Function: NET_DVR_StopRealPlay + Desc: 停止预览。 + Input: lRealHandle [in] 预览句柄,NET_DVR_RealPlay或者NET_DVR_RealPlay_V30的返回值 + Output: + Return: + **********************************************************/ + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool NET_DVR_StopRealPlay(int iRealHandle); + + #endregion Common Method +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/HiK/HiKSdk.cs b/EC.Util/CameraSDK/HiK/HiKSdk.cs new file mode 100644 index 0000000..a6acd39 --- /dev/null +++ b/EC.Util/CameraSDK/HiK/HiKSdk.cs @@ -0,0 +1,275 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace EC.Util.CameraSDK; + +public class HiKSdk : ICameraSdk +{ + #region Fields + + private int LoginId { get; set; } = -1; + + private int Channel { get; } = 1; + + #endregion Fields + + public HiKSdk(CameraInfo cameraInfo) : base(cameraInfo) + { + } + + #region Base Method + + public override bool Init() + { + bool ret = ConnectSuccess(); + if (ret) return true; + HiKOriSdk.NET_DVR_USER_LOGIN_INFO loginInfo = new() + { + bUseAsynLogin = false, + sDeviceAddress = new byte[129], + wPort = (ushort)CameraInfo.Port, + sUserName = new byte[64], + sPassword = new byte[64] + }; + Encoding.Default.GetBytes(CameraInfo.Ip).CopyTo(loginInfo.sDeviceAddress, 0); + Encoding.Default.GetBytes(CameraInfo.UserName).CopyTo(loginInfo.sUserName, 0); + Encoding.Default.GetBytes(CameraInfo.Password).CopyTo(loginInfo.sPassword, 0); + HiKOriSdk.NET_DVR_DEVICEINFO_V40 deviceInfo = new(); + LoginId = HiKOriSdk.NET_DVR_Login_V40(ref loginInfo, ref deviceInfo); + ret = ConnectSuccess(); + if (ret) HiKOriSdk.NET_DVR_SetReconnect(10000, 1); + else BuildException(); + //HiKOriSDK.NET_DVR_DEVICEINFO_V30 deviceInfo = new(); + //LoginId = HiKOriSDK.NET_DVR_Login_V30(CameraInfo.Ip, CameraInfo.Port, CameraInfo.UserName, CameraInfo.Password, ref deviceInfo); + return ret; + } + + public override bool Destory() + { + bool ret = ConnectSuccess(); + if (!ret) return true; + ret = HiKOriSdk.NET_DVR_Logout(LoginId); + if (ret) LoginId = -1; + else BuildException(); + return ret; + } + + public override bool ConnectSuccess() + { + return LoginId >= 0; + } + + internal override void BuildException() + { + uint errCode = HiKOriSdk.NET_DVR_GetLastError(); + if (errCode == 0) return; + throw CameraException.New(CameraInfo, (int)errCode); + } + + private void BuildPlayCtrlException(int nPort) + { + string err = $"PlayCtrlSdk failed, error code={PlayCtrlSdk.PlayM4_GetLastError(nPort)}"; + throw CameraException.New(CameraInfo, -1, err); + } + + #endregion Base Method + + #region Ptz Method + + private static class GPIParams + { + public static int Size { get; private set; } + public static Type Type { get; private set; } + + static GPIParams() + { + HiKOriSdk.NET_DVR_PTZPOS ptzPos = new(); + Size = Marshal.SizeOf(ptzPos); + Type = ptzPos.GetType(); + } + } + + public override PtzInfo GetPtzInfo() + { + bool ret = ConnectSuccess(); + if (!ret) return PtzInfo.Default; + + HiKOriSdk.NET_DVR_PTZPOS entity = new(); + int dwSize = GPIParams.Size; + uint dwReturned = 0; + IntPtr ptrBuf = Marshal.AllocHGlobal(dwSize); + Marshal.StructureToPtr(entity, ptrBuf, true); + try + { + ret = HiKOriSdk.NET_DVR_GetDVRConfig(LoginId, HiKOriSdk.NET_DVR_GET_PTZPOS, 0, ptrBuf, (uint)dwSize, ref dwReturned); + if (!ret) { BuildException(); return PtzInfo.Default; } + object? objBuf = Marshal.PtrToStructure(ptrBuf, GPIParams.Type); + if (objBuf == null) return PtzInfo.Default; + entity = (HiKOriSdk.NET_DVR_PTZPOS)objBuf; + return PtzInfo.New(entity.wPanPos, entity.wTiltPos, entity.wZoomPos); + } + finally + { + Marshal.FreeHGlobal(ptrBuf); + } + } + + public override bool TryGetPtzInfo(out PtzInfo ptzInfo) + { + bool ret = ConnectSuccess(); + if (!ret) { ptzInfo = PtzInfo.Default; return false; } + + HiKOriSdk.NET_DVR_PTZPOS entity = new(); + int dwSize = GPIParams.Size; + uint dwReturned = 0; + IntPtr ptrBuf = Marshal.AllocHGlobal(dwSize); + Marshal.StructureToPtr(entity, ptrBuf, true); + try + { + ret = HiKOriSdk.NET_DVR_GetDVRConfig(LoginId, HiKOriSdk.NET_DVR_GET_PTZPOS, 0, ptrBuf, (uint)dwSize, ref dwReturned); + if (!ret) { BuildException(); ptzInfo = PtzInfo.Default; return false; } + object? objBuf = Marshal.PtrToStructure(ptrBuf, GPIParams.Type); + if (objBuf == null) { ptzInfo = PtzInfo.Default; return false; } + entity = (HiKOriSdk.NET_DVR_PTZPOS)objBuf; + ptzInfo = PtzInfo.New(entity.wPanPos, entity.wTiltPos, entity.wZoomPos); + return true; + } + finally + { + Marshal.FreeHGlobal(ptrBuf); + } + } + + public override bool PtzMove(int cmd, int stop, int speed) + { + if (!ConnectSuccess()) return false; + bool ret = HiKOriSdk.NET_DVR_PTZControlWithSpeed_Other(LoginId, Channel, (uint)cmd, (uint)stop, (uint)speed); + if (!ret) BuildException(); + return ret; + } + + public override bool PtzPreset(int cmd, int presetId) + { + if (!ConnectSuccess()) return false; + bool ret = HiKOriSdk.NET_DVR_PTZPreset_Other(LoginId, Channel, (uint)cmd, (uint)presetId); + if (!ret) BuildException(); + return ret; + } + + #endregion Ptz Method + + #region Video Method + + private int RealplayHandle { get; set; } = -1; + + private int RealpalyPort { get; set; } = -1; + + private IntPtr Hwnd { get; set; } + + public override void StartPlay(IntPtr hwnd) + { + if (!ConnectSuccess() || IsPlaying()) return; + Hwnd = hwnd; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) StartPlayWindows(); + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) StartPlayLinux(); + else Hwnd = IntPtr.Zero; + } + + private void StartPlayWindows() + { + HiKOriSdk.NET_DVR_PREVIEWINFO previewInfo = new() + { + hPlayWnd = Hwnd, //预览窗口 + lChannel = 1, //预览的设备通道 + dwStreamType = 0, //码流类型:0-主码流,1-子码流,2-码流3,3-码流4,以此类推 + dwLinkMode = 0, //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP + bBlocked = true, //0- 非阻塞取流,1- 阻塞取流 + }; + IntPtr pUser = new(); //用户数据 + RealplayHandle = HiKOriSdk.NET_DVR_RealPlay_V40(LoginId, ref previewInfo, null, pUser); + if (RealplayHandle < 0) BuildException(); + } + + private void StartPlayLinux() + { + if (RealpalyPort < 0) + { + int nPort = -1; + bool ret = PlayCtrlSdk.PlayM4_GetPort(ref nPort); + if (!ret) BuildPlayCtrlException(nPort); + RealpalyPort = nPort; + } + HiKOriSdk.NET_DVR_PREVIEWINFO previewInfo = new() + { + hPlayWnd = IntPtr.Zero, //预览窗口 + lChannel = 1, //预览的设备通道 + dwStreamType = 0, //码流类型:0-主码流,1-子码流,2-码流3,3-码流4,以此类推 + dwLinkMode = 0, //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP + bBlocked = true, //0- 非阻塞取流,1- 阻塞取流 + }; + RealplayHandle = HiKOriSdk.NET_DVR_RealPlay_V40(LoginId, ref previewInfo, RealDataCallBack, IntPtr.Zero); + if (RealplayHandle < 0) BuildException(); + } + + private void RealDataCallBack(int lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser) + { + if (dwBufSize <= 0) return; + switch (dwDataType) + { + case HiKOriSdk.NET_DVR_SYSHEAD: + try + { + PlayCtrlSdk.PlayM4_SetStreamOpenMode(RealpalyPort, 0); + PlayCtrlSdk.PlayM4_OpenStream(RealpalyPort, pBuffer, dwBufSize, 2 * 1024 * 1024); + if (!PlayCtrlSdk.PlayM4_Play(RealpalyPort, Hwnd)) BuildPlayCtrlException(RealpalyPort); + } + catch (Exception) + { + StopPlay(); + throw; + } + break; + + case HiKOriSdk.NET_DVR_STREAMDATA: + PlayCtrlSdk.PlayM4_InputData(RealpalyPort, pBuffer, dwBufSize); + break; + } + } + + public override void StopPlay() + { + if (!IsPlaying()) return; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) StopPlayWindows(); + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) StopPlayLinux(); + Hwnd = IntPtr.Zero; + } + + public void StopPlayWindows() + { + bool ret = HiKOriSdk.NET_DVR_StopRealPlay(RealplayHandle); + RealplayHandle = -1; + if (!ret) BuildException(); + } + + public void StopPlayLinux() + { + bool ret = HiKOriSdk.NET_DVR_StopRealPlay(RealplayHandle); + RealplayHandle = -1; + if (RealpalyPort >= 0) + { + //if(!PlayCtrlSdk.PlayM4_Stop(RealpalyPort)) BuildPlayCtrlException(RealpalyPort); + PlayCtrlSdk.PlayM4_Stop(RealpalyPort); + PlayCtrlSdk.PlayM4_CloseStream(RealpalyPort); + PlayCtrlSdk.PlayM4_FreePort(RealpalyPort); + RealpalyPort = -1; + } + if (!ret) BuildException(); + } + + public override bool IsPlaying() + { + return RealplayHandle >= 0; + } + + #endregion Video Method +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/HiK/PlayCtrlSdk.cs b/EC.Util/CameraSDK/HiK/PlayCtrlSdk.cs new file mode 100644 index 0000000..9af7477 --- /dev/null +++ b/EC.Util/CameraSDK/HiK/PlayCtrlSdk.cs @@ -0,0 +1,77 @@ +//#define Win32 +#define Win64 + +//#define Linux32 +//#define Linux64 +using System.Runtime.InteropServices; + +namespace EC.Util.CameraSDK; + +public class PlayCtrlSdk +{ + #region Fields + +#if Win32 + public const string LibSdkPath = @"./libs/hik/win32/PlayCtrl.dll"; +#elif Win64 + public const string LibSdkPath = @"./libs/hik/win64/PlayCtrl.dll"; +#elif Linux32 + public const string LibSdkPath = @"./libs/hik/linux32/libPlayCtrl.so"; +#elif Linux64 + public const string LibSdkPath = @"./libs/hik/linux64/libPlayCtrl.so"; +#endif + + #endregion Fields + + static PlayCtrlSdk() + { + } + + #region Sdk Struct + + public struct FRAME_INFO + { + public int nWidth; + public int nHeight; + public int nStamp; + public int nType; + public int nFrameRate; + public uint dwFrameNum; + } + + #endregion Sdk Struct + + #region Sdk Method + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool PlayM4_GetPort(ref int nPort); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool PlayM4_FreePort(int nPort); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern uint PlayM4_GetLastError(int nPort); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool PlayM4_SetStreamOpenMode(int nPort, uint nMode); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool PlayM4_OpenStream(int nPort, IntPtr pFileHeadBuf, uint nSize, uint nBufPoolSize); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool PlayM4_CloseStream(int nPort); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool PlayM4_SetDisplayBuf(int nPort, uint nNum); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool PlayM4_InputData(int nPort, IntPtr pBuf, uint nSize); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool PlayM4_Play(int nPort, IntPtr hWnd); + + [DllImport(LibSdkPath, CallingConvention = CallingConvention.StdCall)] + public static extern bool PlayM4_Stop(int nPort); + + #endregion Sdk Method +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/YuShi/YuShiOriSdk.cs b/EC.Util/CameraSDK/YuShi/YuShiOriSdk.cs new file mode 100644 index 0000000..ea9bbcf --- /dev/null +++ b/EC.Util/CameraSDK/YuShi/YuShiOriSdk.cs @@ -0,0 +1,244 @@ +//#define Win32 +#define Win64 + +//#define Linux32 +//#define Linux64 +using System.Runtime.InteropServices; + +namespace EC.Util.CameraSDK; + +public class YuShiOriSdk +{ + #region Fields + +#if Win32 + public const string LibSdkPath = @"./libs/yushi/win32/NetDEVSDK.dll"; +#elif Win64 + public const string LibSdkPath = @"./libs/yushi/win64/NetDEVSDK.dll"; +#elif Linux32 + public const string LibSdkPath = @"./libs/yushi/linux64/libNetDEVSDK.so"; +#elif Linux64 + public const string LibSdkPath = @"./libs/yushi/linux64/libNetDEVSDK.so"; +#endif + + private const bool Debug = true; + + #endregion Fields + + static YuShiOriSdk() + { + GlobalInit(); + } + + #region Global + + public static bool InitSuccess { get; private set; } + + public static bool GlobalInit() + { + if (InitSuccess) return true; + bool ret = NETDEV_Init(); + InitSuccess = ret; + if (!ret) throw new Exception("YuShiOriSDK global init failure."); + return ret; + } + + public static bool GlobalDestory() + { + if (!InitSuccess) return true; + bool ret = NETDEV_Cleanup(); + if (ret) InitSuccess = false; + return ret; + } + + #endregion Global + + #region Sdk Const + + public const int TRUE = 1; + public const int FALSE = 0; + + public const int PtzSpeedMin = 1; + public const int PtzSpeedMax = 9; + + /* Common length */ + public const int NETDEV_LEN_64 = 64; + public const int NETDEV_LEN_128 = 128; + public const int NETDEV_LEN_132 = 132; + public const int NETDEV_LEN_260 = 260; + + /* NETDEV_PTZ_E */ + public const int IRISCLOSE_STOP = 0x0101; /* Iris close stop */ + public const int IRISCLOSE = 0x0102; /* Iris close */ + public const int IRISOPEN_STOP = 0x0103; /* Iris open stop */ + public const int IRISOPEN = 0x0104; /* Iris open */ + public const int FOCUSNEAR_STOP = 0x0201; /* Focus near stop */ + public const int FOCUSNEAR = 0x0202; /* Focus near */ + public const int FOCUSFAR_STOP = 0x0203; /* Focus far stop */ + public const int FOCUSFAR = 0x0204; /* Focus far */ + public const int ZOOMTELE_STOP = 0x0301; /* Zoom in stop */ + public const int ZOOMTELE = 0x0302; /* Zoom in */ + public const int ZOOMWIDE_STOP = 0x0303; /* Zoom out stop */ + public const int ZOOMWIDE = 0x0304; /* Zoom out */ + public const int TILTUP = 0x0402; /* Tilt up */ + public const int TILTDOWN = 0x0404; /* Tilt down */ + public const int PANRIGHT = 0x0502; /* Pan right */ + public const int PANLEFT = 0x0504; /* Pan left */ + public const int LEFTUP = 0x0702; /* Move up left */ + public const int LEFTDOWN = 0x0704; /* Move down left */ + public const int RIGHTUP = 0x0802; /* Move up right */ + public const int RIGHTDOWN = 0x0804; /* Move down right */ + public const int ALLSTOP = 0x0901; /* All-stop command word */ + public const int FOCUS_AND_IRIS_STOP = 0x0907; /* Focus & Iris-stop command word */ + public const int MOVE_STOP = 0x0908; /* move stop command word */ + public const int ZOOM_STOP = 0x0909; /* zoom stop command word */ + /* NETDEV_PTZ_PRESETCMD_E */ + public const int PRESET_SET = 0; /* Set preset */ + public const int PRESET_CLE = 1; /* Clear preset */ + public const int PRESET_GOTO = 2; /* Go to preset */ + + public enum NETDEV_LIVE_STREAM_INDEX_E : int + { + NETDEV_LIVE_STREAM_INDEX_MAIN = 0, /* Main stream */ + NETDEV_LIVE_STREAM_INDEX_SUB = 1, /* Sub stream */ + NETDEV_LIVE_STREAM_INDEX_THIRD = 2, /* Third stream */ + + NETDEV_LIVE_STREAM_INDEX_INVALID + } + + public enum NETDEV_PROTOCAL_E : int + { + NETDEV_TRANSPROTOCAL_RTPUDP = 0, /* UDP */ + NETDEV_TRANSPROTOCAL_RTPTCP = 1 /* TCP */ + } + + public enum NETDEV_PICTURE_FLUENCY_E + { + NETDEV_PICTURE_REAL = 0, /* Real-time first */ + NETDEV_PICTURE_FLUENCY = 1, /* Fluency first */ + NETDEV_PICTURE_BALANCE_NEW = 3, /* Balance */ + NETDEV_PICTURE_RTMP_FLUENCY = 4, /* RTMP fluency first */ + + NETDEV_PICTURE_FLUENCY_INVALID = 0xff /* Invalid value */ + } + + #endregion Sdk Const + + #region Sdk Struct + + [StructLayout(LayoutKind.Sequential)] + public struct NETDEV_DEVICE_LOGIN_INFO_S + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NETDEV_LEN_260)] + public string szIPAddr; /* IP地址/域名 */ + + public int dwPort; /* 端口号 */ + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NETDEV_LEN_132)] + public string szUserName; /* 用户名 */ + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NETDEV_LEN_128)] + public string szPassword; /* 密码 */ + + public int dwLoginProto; /* 登录协议, 参见NETDEV_LOGIN_PROTO_E */ + public int dwDeviceType; /* 设备类型, 参见NETDEV_DEVICE_TYPE_E */ + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] + public byte[] byRes; /* Reserved */ + }; + + [StructLayout(LayoutKind.Sequential)] + public struct NETDEV_SELOG_INFO_S + { + public int dwSELogCount; + public int dwSELogTime; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public byte[] byRes; + }; + + [StructLayout(LayoutKind.Sequential)] + public struct NETDEV_VIDEO_CHL_DETAIL_INFO_S + { + public int dwChannelID; + public int bPtzSupported; /* Whether ptz is supported */ + public int enStatus; /* Channel status */ + public int dwStreamNum; /* Number of streams */ + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NETDEV_LEN_64)] + public string szChnName; /* Device serial number */ + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] szReserve; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NETDEV_PTZ_STATUS_S + { + public float fPanTiltX; /* 绝对水平坐标 Absolute horizontal coordinates*/ + public float fPanTiltY; /* 绝对竖直坐标 Absolute vertical coordinates*/ + public float fZoomX; /* 绝对聚焦倍数 Absolute multiples*/ + public int enPanTiltStatus;/* 云台状态 PTZ Status*/ + public int enZoomStatus; /* 聚焦状态 Focus Status*/ + }; + + [StructLayout(LayoutKind.Sequential)] + public struct NETDEV_PREVIEWINFO_S + { + public int dwChannelID; /* ID Channel ID */ + public int dwStreamType; /* #NETDEV_LIVE_STREAM_INDEX_E Stream type, see enumeration #NETDEV_LIVE_STREAM_INDEX_E */ + public int dwLinkMode; /* #NETDEV_PROTOCAL_E Transport protocol, see enumeration #NETDEV_PROTOCAL_E */ + public IntPtr hPlayWnd; /* Play window handle */ + public int dwFluency; /* #NETDEV_PICTURE_FLUENCY_E image play fluency*/ + public int dwStreamMode; /* #NETDEV_STREAM_MODE_E start stream mode see #NETDEV_STREAM_MODE_E*/ + public int dwLiveMode; /* #NETDEV_PULL_STREAM_MODE_E Rev. Flow pattern */ + public int dwDisTributeCloud; /* #NETDEV_DISTRIBUTE_CLOUD_SRV_E distribution */ + public int dwallowDistribution; /* allow or no distribution*/ + public int dwTransType; /* 传输类型,参见枚举# NETDEV_TRANS_TYPE_E */ + public int dwStreamProtocol; /* 起流协议,参见枚举# NETDEV_START_STREAM_PROT_E */ + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 236)] + public byte[] szReserve; /* Reserved */ + } + + #endregion Sdk Struct + + #region Common Method + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern bool NETDEV_Init(); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern bool NETDEV_Cleanup(); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern int NETDEV_GetLastError(); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern IntPtr NETDEV_Login_V30(ref NETDEV_DEVICE_LOGIN_INFO_S pstDevLoginInfo, ref NETDEV_SELOG_INFO_S pstSELogInfo); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern bool NETDEV_Logout(IntPtr lpUserID); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern int NETDEV_QueryVideoChlDetailList(IntPtr lpUserID, ref int pdwChlCount, IntPtr pstVideoChlList); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern bool NETDEV_PTZGetStatus(IntPtr lpUserID, int dwChannelID, ref NETDEV_PTZ_STATUS_S pstPTZStaus); + + //public boolean NETDEV_GetDevConfig(Pointer lpUserID, int dwChannelID, int dwCommand, Pointer lpOutBuffer, int dwOutBufferSize, IntByReference pdwBytesReturned); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern int NETDEV_PTZControl_Other(IntPtr lpUserID, int dwChannelID, int dwPTZCommand, int dwSpeed); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern int NETDEV_PTZPreset_Other(IntPtr lpUserID, int dwChannelID, int dwPTZPresetCmd, byte[] szPresetName, int dwPresetID); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern IntPtr NETDEV_RealPlay(IntPtr lpUserID, ref NETDEV_PREVIEWINFO_S pstPreviewInfo, IntPtr cbPlayDataCallBack, IntPtr lpUserData); + + [DllImport(LibSdkPath, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern int NETDEV_StopRealPlay(IntPtr lpRealHandle); + + #endregion Common Method +} \ No newline at end of file diff --git a/EC.Util/CameraSDK/YuShi/YuShiSdk.cs b/EC.Util/CameraSDK/YuShi/YuShiSdk.cs new file mode 100644 index 0000000..eb7d3d1 --- /dev/null +++ b/EC.Util/CameraSDK/YuShi/YuShiSdk.cs @@ -0,0 +1,136 @@ +namespace EC.Util.CameraSDK; + +public class YuShiSdk : ICameraSdk +{ + #region Fields + + private IntPtr LoginId { get; set; } = IntPtr.Zero; + + private int Channel { get; } = 1; + + #endregion Fields + + public YuShiSdk(CameraInfo cameraInfo) : base(cameraInfo) + { + } + + #region Base Method + + public override bool Init() + { + bool ret = ConnectSuccess(); + if (ret) return true; + YuShiOriSdk.NETDEV_DEVICE_LOGIN_INFO_S loginInfo = new(); + loginInfo.szIPAddr = CameraInfo.Ip; + loginInfo.dwPort = CameraInfo.Port; + loginInfo.szUserName = CameraInfo.UserName; + loginInfo.szPassword = CameraInfo.Password; + YuShiOriSdk.NETDEV_SELOG_INFO_S logInfo = new(); + LoginId = YuShiOriSdk.NETDEV_Login_V30(ref loginInfo, ref logInfo); + ret = ConnectSuccess(); + if(!ret) BuildException(); + return ret; + } + + public override bool Destory() + { + bool ret = ConnectSuccess(); + if (!ret) return true; + ret = YuShiOriSdk.NETDEV_Logout(LoginId); + if (ret) LoginId = IntPtr.Zero; + else BuildException(); + return ret; + } + + public override bool ConnectSuccess() + { + return LoginId != IntPtr.Zero; + } + + internal override void BuildException() + { + int errCode = YuShiOriSdk.NETDEV_GetLastError(); + if (errCode == 0) return; + throw CameraException.New(CameraInfo, errCode); + } + + #endregion Base Method + + #region Ptz Method + + public override PtzInfo GetPtzInfo() + { + bool ret = ConnectSuccess(); + if (!ret) return PtzInfo.Default; + YuShiOriSdk.NETDEV_PTZ_STATUS_S entity = new(); + ret = YuShiOriSdk.NETDEV_PTZGetStatus(LoginId, Channel, ref entity); + if (!ret) { BuildException(); return PtzInfo.Default; } + return PtzInfo.New(entity.fPanTiltX, entity.fPanTiltY, entity.fZoomX); + } + + public override bool TryGetPtzInfo(out PtzInfo ptzInfo) + { + bool ret = ConnectSuccess(); + if (!ret) { ptzInfo = PtzInfo.Default; return false; } + YuShiOriSdk.NETDEV_PTZ_STATUS_S entity = new(); + ret = YuShiOriSdk.NETDEV_PTZGetStatus(LoginId, Channel, ref entity); + if (!ret) { BuildException(); ptzInfo = PtzInfo.Default; return false; } + ptzInfo = PtzInfo.New(entity.fPanTiltX, entity.fPanTiltY, entity.fZoomX); + return true; + } + + public override bool PtzMove(int cmd, int stop, int speed) + { + if (!ConnectSuccess()) return false; + if (stop == 1) cmd = YuShiOriSdk.ALLSTOP; + int retNum = YuShiOriSdk.NETDEV_PTZControl_Other(LoginId, Channel, cmd, speed); + bool ret = retNum == YuShiOriSdk.TRUE; + if (!ret) BuildException(); + return ret; + } + + public override bool PtzPreset(int cmd, int presetId) + { + if (!ConnectSuccess()) return false; + int retNum = YuShiOriSdk.NETDEV_PTZPreset_Other(LoginId, Channel, cmd, Array.Empty(), presetId); + bool ret = retNum == YuShiOriSdk.TRUE; + if (!ret) BuildException(); + return ret; + } + + #endregion Ptz Method + + #region Video Method + + private IntPtr RealplayHandle { get; set; } = IntPtr.Zero; + + public override void StartPlay(IntPtr hwnd) + { + if (!ConnectSuccess() || IsPlaying()) return; + YuShiOriSdk.NETDEV_PREVIEWINFO_S stPreviewInfo = new() + { + hPlayWnd = hwnd, + dwChannelID = Channel, + dwStreamType = 0, // YuShiOriSDK.NETDEV_LIVE_STREAM_INDEX_E + dwLinkMode = 1, // YuShiOriSDK.NETDEV_PROTOCAL_E + dwFluency = 0, // YuShiOriSDK.NETDEV_PICTURE_FLUENCY_E + }; + RealplayHandle = YuShiOriSdk.NETDEV_RealPlay(LoginId, ref stPreviewInfo, IntPtr.Zero, IntPtr.Zero); + if (RealplayHandle == IntPtr.Zero) BuildException(); + } + + public override void StopPlay() + { + if (!IsPlaying()) return; + int ret = YuShiOriSdk.NETDEV_StopRealPlay(RealplayHandle); + RealplayHandle = IntPtr.Zero; + if (ret == YuShiOriSdk.FALSE) BuildException(); + } + + public override bool IsPlaying() + { + return RealplayHandle != IntPtr.Zero; + } + + #endregion Video Method +} \ No newline at end of file diff --git a/EC.Util/Common/JsonUtil.cs b/EC.Util/Common/JsonUtil.cs new file mode 100644 index 0000000..a04587b --- /dev/null +++ b/EC.Util/Common/JsonUtil.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; + +namespace EC.Util.Common; + +public static class JsonUtil +{ + public static object ToJson(string Json) + { + object? obj = JsonConvert.DeserializeObject(Json); + return obj ?? new(); + } + + public static string ToJson(object obj) + { + IsoDateTimeConverter timeConverter = new() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }; + return JsonConvert.SerializeObject(obj, timeConverter); + } + + public static string ToJson(object obj, string datetimeformats) + { + IsoDateTimeConverter timeConverter = new() { DateTimeFormat = datetimeformats }; + return JsonConvert.SerializeObject(obj, timeConverter); + } + + public static T ToObject(string Json) + { + T? obj = JsonConvert.DeserializeObject(Json); + return obj ?? Activator.CreateInstance(); + } + + public static List ToList(string Json) + { + List? list = JsonConvert.DeserializeObject>(Json); + return list ?? new List(); + } + + public static JObject ToJObject(string Json) + { + return Json != null ? JObject.Parse(Json.Replace(" ", "")) : JObject.Parse("{}"); + } +} \ No newline at end of file diff --git a/EC.Util/Common/LogUnit.cs b/EC.Util/Common/LogUnit.cs new file mode 100644 index 0000000..f31b657 --- /dev/null +++ b/EC.Util/Common/LogUnit.cs @@ -0,0 +1,40 @@ +using log4net; +using log4net.Config; + +namespace EC.Util.Common; + +public class LogUnit +{ + private static readonly ILog logger; + + static LogUnit() + { + string fileName = Path.Combine("config", "log4net.config"); + if (!File.Exists(fileName)) throw new FileNotFoundException(fileName); + XmlConfigurator.Configure(new FileInfo(fileName)); + logger = LogManager.GetLogger(typeof(LogUnit)); + } + + public static void Init() + { } + + public static void Debug(string msg) => logger.Debug(msg); + + public static void Info(string msg) => logger.Info(msg); + + public static void Warn(string msg) => logger.Warn(msg); + + public static void Fatal(string msg) => logger.Fatal(msg); + + public static void Error(string msg) => logger.Error(msg); + + public static void Error(Exception e) => logger.Error(e); + + public static void Error(object obj, string msg) => logger.Error(obj.GetType(), new Exception(msg)); + + public static void Error(object obj, Exception e) => logger.Error(obj.GetType(), e); + + public static void Error(Type type, string msg) => logger.Error(type, new Exception(msg)); + + public static void Error(Type type, Exception e) => logger.Error(type, e); +} \ No newline at end of file diff --git a/EC.Util/Common/NetworkUtil.cs b/EC.Util/Common/NetworkUtil.cs new file mode 100644 index 0000000..b0caad0 --- /dev/null +++ b/EC.Util/Common/NetworkUtil.cs @@ -0,0 +1,36 @@ +using System.Net.Sockets; + +namespace EC.Util.Common; + +public static class NetworkUtil +{ + public static string LocalAddr(this TcpClient client) + { + return client.Client.LocalEndPoint?.ToString() ?? ":"; + } + + public static string LocalIp(this TcpClient client) + { + return LocalAddr(client).Split(':')[0]; + } + + public static string LocalPort(this TcpClient client) + { + return LocalAddr(client).Split(':')[1]; + } + + public static string ClientAddr(this TcpClient client) + { + return client.Client.RemoteEndPoint?.ToString() ?? ":"; + } + + public static string ClientIp(this TcpClient client) + { + return ClientAddr(client).Split(':')[0]; + } + + public static string ClientPort(this TcpClient client) + { + return ClientAddr(client).Split(':')[1]; + } +} \ No newline at end of file diff --git a/EC.Util/Common/TaskUtil.cs b/EC.Util/Common/TaskUtil.cs new file mode 100644 index 0000000..c9f9ed8 --- /dev/null +++ b/EC.Util/Common/TaskUtil.cs @@ -0,0 +1,40 @@ +namespace EC.Util.Common; + +public class TaskUtil +{ + public static Task Run(Action action) + { + Task.Run(action); + return Task.Run(action); + } + + /// + /// 相比于 Run,直接捕获并记录异常 + /// + /// + /// + public static Task RunCatch(Action action) + { + return Task.Run(() => + { + try + { + action(); + } + catch (Exception e) + { + LogUnit.Error(typeof(TaskUtil), e); + } + }); + } + + /// + /// 相比于 Run,能及时抛出并全局捕获异常 + /// + /// + /// + public static Task LongRun(Action action) + { + return Task.Factory.StartNew(action, TaskCreationOptions.LongRunning); + } +} \ No newline at end of file diff --git a/EC.Util/Common/VerifyUtil.cs b/EC.Util/Common/VerifyUtil.cs new file mode 100644 index 0000000..d575597 --- /dev/null +++ b/EC.Util/Common/VerifyUtil.cs @@ -0,0 +1,176 @@ +using System.Text.RegularExpressions; + +namespace EC.Util.Common; + +/// +/// 验证工具类 +/// +public class VerifyUtil +{ + /// + /// 验证是否为空 + /// + /// + /// + public static bool IsEmpty(string str) + { + return string.IsNullOrEmpty(str); + } + + /// + /// 验证是否为邮件 + /// + /// + /// + public static bool IsEmail(string emailStr) + { + if (string.IsNullOrEmpty(emailStr)) return false; + string match = @"^([a-zA-Z0-9]+[_|\-|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\-|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$"; + Regex r = new(match, RegexOptions.Compiled | RegexOptions.IgnoreCase); + return r.IsMatch(emailStr.Trim()); + } + + /// + /// 验证是否为数字 + /// + /// + /// + public static bool IsNumber(string numberStr) + { + if (string.IsNullOrEmpty(numberStr)) return false; + string match = @"^(0|[1-9][0-9]*)$"; + Regex r = new(match, RegexOptions.Compiled | RegexOptions.IgnoreCase); + return r.IsMatch(numberStr.Trim()); + } + + /// + /// 验证是否为浮点数 + /// + /// + /// + public static bool IsDecimal(string decimalStr) + { + if (string.IsNullOrEmpty(decimalStr)) return false; + string match = @"^(-?\d+)(\.\d+)?$"; + Regex r = new(match, RegexOptions.Compiled | RegexOptions.IgnoreCase); + return r.IsMatch(decimalStr.Trim()); + } + + /// + /// 验证是否为手机号码 + /// + /// + /// + public static bool IsMobile(string mobileStr) + { + if (string.IsNullOrEmpty(mobileStr)) return false; + string match = @"^[1]+[3,5,7,8]+\d{9}"; + Regex r = new(match, RegexOptions.Compiled | RegexOptions.IgnoreCase); + return r.IsMatch(mobileStr.Trim()); + } + + /// + /// 验证是否为电话号码 + /// + /// + /// + public static bool IsTel(string telStr) + { + if (string.IsNullOrEmpty(telStr)) return false; + string match = @"^(\+86\s{1,1})?((\d{3,4}\-)\d{7,8})$"; + Regex r = new(match, RegexOptions.Compiled | RegexOptions.IgnoreCase); + return r.IsMatch(telStr.Trim()); + } + + /// + /// 验证是否为Ip地址 + /// + /// + /// + public static bool IsIp(string ipStr) + { + if (string.IsNullOrEmpty(ipStr)) return false; + string match = @"^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$"; + Regex r = new(match); + return r.IsMatch(ipStr.Trim()); + } + + /// + /// 验证是否为身份证 + /// + /// 身份证号 + /// + public static bool CheckIDCard(string idcardStr) + { + if (string.IsNullOrEmpty(idcardStr)) return false; + if (idcardStr.Length == 18) return CheckIDCard18(idcardStr); + else if (idcardStr.Length == 15) return CheckIDCard15(idcardStr); + return false; + } + + /// + /// 18位身份证验证 + /// + /// 身份证号 + /// + private static bool CheckIDCard18(string Id) + { + long n = 0; + if (long.TryParse(Id.Remove(17), out n) == false || n < Math.Pow(10, 16) || long.TryParse(Id.Replace('x', '0').Replace('X', '0'), out n) == false) + { + return false;//数字验证 + } + string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91"; + if (address.IndexOf(Id.Remove(2)) == -1) + { + return false;//省份验证 + } + string birth = Id.Substring(6, 8).Insert(6, "-").Insert(4, "-"); + DateTime time = new DateTime(); + if (DateTime.TryParse(birth, out time) == false) + { + return false;//生日验证 + } + string[] arrVarifyCode = ("1,0,x,9,8,7,6,5,4,3,2").Split(','); + string[] Wi = ("7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2").Split(','); + char[] Ai = Id.Remove(17).ToCharArray(); + int sum = 0; + for (int i = 0; i < 17; i++) + { + sum += int.Parse(Wi[i]) * int.Parse(Ai[i].ToString()); + } + int y = -1; + Math.DivRem(sum, 11, out y); + if (arrVarifyCode[y] != Id.Substring(17, 1).ToLower()) + { + return false;//校验码验证 + } + return true;//符合GB11643-1999标准 + } + + /// + /// 15位身份证验证 + /// + /// 身份证号 + /// + private static bool CheckIDCard15(string Id) + { + long n = 0; + if (long.TryParse(Id, out n) == false || n < Math.Pow(10, 14)) + { + return false;//数字验证 + } + string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91"; + if (address.IndexOf(Id.Remove(2)) == -1) + { + return false;//省份验证 + } + string birth = Id.Substring(6, 6).Insert(4, "-").Insert(2, "-"); + DateTime time = new DateTime(); + if (DateTime.TryParse(birth, out time) == false) + { + return false;//生日验证 + } + return true;//符合15位身份证标准 + } +} \ No newline at end of file diff --git a/EC.Util/EC.Util.csproj b/EC.Util/EC.Util.csproj new file mode 100644 index 0000000..ae9e75f --- /dev/null +++ b/EC.Util/EC.Util.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + PreserveNewest + + + + + + + + diff --git a/EC.Util/Port/SerialPortBuilder.cs b/EC.Util/Port/SerialPortBuilder.cs new file mode 100644 index 0000000..db3dda6 --- /dev/null +++ b/EC.Util/Port/SerialPortBuilder.cs @@ -0,0 +1,60 @@ +namespace EC.Util.Port; + +/// +/// 串口构建类 +/// +public class SerialPortBuilder +{ + /// + /// + /// + /// + public static YcSerialPort CreateSerialPort(SerialPortParam param) + { + return new YcSerialPort(param); + } + + /// + /// + /// + public static YcSerialPort CreateSerialPort(string comName) + { + var param = new SerialPortParam + { + ComName = comName + }; + return new YcSerialPort(param); + } + + /// + /// + /// + /// + /// + public static YcSerialPort CreateSerialPort(string comName, int baudRate) + { + var param = new SerialPortParam + { + ComName = comName, + BaudRate = baudRate + }; + return new YcSerialPort(param); + } + + /// + /// + /// + /// + /// + /// + public static YcSerialPort CreateSerialPort(string comName, int baudRate, int receivedBytesThreshold) + { + var param = new SerialPortParam + { + ComName = comName, + BaudRate = baudRate, + ReceivedBytesThreshold = receivedBytesThreshold + }; + return new YcSerialPort(param); + } +} \ No newline at end of file diff --git a/EC.Util/Port/SerialPortParam.cs b/EC.Util/Port/SerialPortParam.cs new file mode 100644 index 0000000..d386c80 --- /dev/null +++ b/EC.Util/Port/SerialPortParam.cs @@ -0,0 +1,56 @@ +namespace EC.Util.Port; + +/// +/// 串口参数类 +/// +public class SerialPortParam +{ + #region Fields + + /// + /// 串口名称 + /// + public string ComName { get; set; } + + /// + ///波特率 + /// + public int BaudRate { get; set; } + + /// + /// 奇偶校验0-4=no,odd,even,mark,space + /// + public int Parity { get; set; } + + /// + /// 校验位对应值 + /// + public int ParityValue { get; set; } + + /// + ///数据位 默认 + /// + public int DataBits { get; set; } + + /// + /// 停止位 + /// + public int StopBits { get; set; } + + /// + /// ?Byte时,触发一次事件。设置为1时,易造成线程死锁 + /// + public int ReceivedBytesThreshold { get; set; } + + #endregion Fields + + public SerialPortParam() + { + ComName = string.Empty; + BaudRate = 9600; + Parity = 0; + DataBits = 8; + StopBits = 1; + ReceivedBytesThreshold = 128; + } +} \ No newline at end of file diff --git a/EC.Util/Port/YcSerialPort.cs b/EC.Util/Port/YcSerialPort.cs new file mode 100644 index 0000000..1195d59 --- /dev/null +++ b/EC.Util/Port/YcSerialPort.cs @@ -0,0 +1,166 @@ +using System.IO.Ports; +using System.Text.RegularExpressions; + +namespace EC.Util.Port; + +/// +/// 串口类 +/// +public class YcSerialPort +{ + private SerialPort _port;//声明一个串口 + private readonly SerialPortParam _param; + + public long RecCount { get; set; }//接收计数 + + public long SendCount { get; set; }//发送计数 + + public YcSerialPort() + { + } + + public YcSerialPort(SerialPortParam param) + { + _param = param; + } + + /// + /// 打开串口 + /// + /// + public bool OpenCom() + { + if (IsOpen()) + CloseCom(); + if (string.IsNullOrEmpty(_param.ComName)) + throw new Exception("请插入串口设备!"); + + _port = new SerialPort + { + PortName = _param.ComName,//串口名 + BaudRate = _param.BaudRate,//波特率 + DataBits = _param.DataBits,//数据位 + Parity = (Parity)_param.Parity,//奇偶校验0-4=no,odd,even,mark,space + StopBits = (StopBits)Convert.ToInt32(_param.StopBits),//停止位 + ReadTimeout = 500,//读超时 + WriteTimeout = 2000,//写超时 + //ReadBufferSize = 1024, + //WriteBufferSize = 1024, + ReceivedBytesThreshold = _param.ReceivedBytesThreshold,//128Byte时触发一次事件,设置为1时易造成线程死锁。 + RtsEnable = true, + DtrEnable = true, + }; + _port.DataReceived += RecData; + //port.DataReceived += new SerialDataReceivedEventHandler(Comm_DataReceived); + _port.Open(); + return IsOpen(); + } + + /// + /// 关闭串口 + /// + public bool CloseCom() + { + if (IsOpen()) + { + _port.Close(); + _port = null; + } + return !IsOpen(); + } + + public bool IsOpen() + { + if (_port == null) + return false; + if (!_port.IsOpen) + return false; + return true; + } + + public event EventHandler OnRecData; //定义一个委托类型的事件 + + private void RecData(object sender, EventArgs e) + { + var count = _port.BytesToRead;//获取缓存区字节数 + var buf = new byte[count];//存储串口数据用 + _port.Read(buf, 0, count);//读取缓冲数据 + RecCount += count;//接收计数 + OnRecData?.Invoke(this, buf); + } + + /// + /// 发送 Ascii + /// + /// + /// + public void SendAscii(string text, bool newLine = false) + { + //定义一个变量,记录发送了几个字节 + int count; + + if (newLine) + { + _port.Write(text + "\r\n"); + count = text.Length + 2; + } + else//不包含换行符 + { + _port.Write(text); + count = text.Length; + } + + SendCount += count;//累加发送字节数 + } + + /// + /// 发送 Hex + /// + /// + /// + public void SendHex(string text, bool newLine = false) + { + int count;//定义一个变量,记录发送了几个字节 + var mc = Regex.Matches(text, @"(?i)[\da-f]{2}");//正则得到有效的十六进制数(?i)对大小写不敏感,[/da-f],{2}连续出现2次 + var buf = new List();//填充到这个临时列表中 + foreach (Match m in mc) + { + buf.Add(byte.Parse(m.Value, System.Globalization.NumberStyles.HexNumber));//依次添加到列表中 + } + _port.Write(buf.ToArray(), 0, buf.Count);//转换列表为数组后发送 + if (newLine) + { + _port.Write("\r\n"); + count = buf.Count + 2; + } + else//不包含换行符 + { + count = buf.Count; + } + + SendCount += count;//累加发送字节数 + } + + /// + /// 发送 Hex + /// + /// + /// + public void SendHex(byte[] buf, bool newline = false) + { + int count;//定义一个变量,记录发送了几个字节 + var bufLen = buf.Count(); + _port.Write(buf, 0, bufLen); + if (newline) + { //记录发送的字节数 + _port.WriteLine("\n"); + count = bufLen + 2; + } + else + { + count = bufLen; + } + + SendCount += count;//累加发送字节数 + } +} \ No newline at end of file diff --git a/EC.Util/config/log4net.config b/EC.Util/config/log4net.config new file mode 100644 index 0000000..9b7fbb3 --- /dev/null +++ b/EC.Util/config/log4net.config @@ -0,0 +1,29 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/JiLinApp/JiLinApp.csproj b/JiLinApp/JiLinApp.csproj index b924979..473b4ed 100644 --- a/JiLinApp/JiLinApp.csproj +++ b/JiLinApp/JiLinApp.csproj @@ -23,4 +23,9 @@ + + + + + diff --git a/JiLinApp/Program.cs b/JiLinApp/Program.cs index b4744d0..1b6e6c6 100644 --- a/JiLinApp/Program.cs +++ b/JiLinApp/Program.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Media; using Avalonia.ReactiveUI; +using EC.Util.Common; using JiLinApp.Core.Avalonia; using ReactiveUI; using System; @@ -22,10 +23,9 @@ internal class Program AppBuilder appBuilder = BuildAvaloniaApp(); appBuilder.StartWithClassicDesktopLifetime(args); } - catch (Exception) + catch (Exception e) { - //The global try-catch - throw; + LogUnit.Error(e); } } @@ -54,18 +54,29 @@ internal class Program private static void GlobalInit() { + // Active to initialize + LogUnit.Init(); + // HandleExceptions // Exceptions from another thread + AppDomain.CurrentDomain.UnhandledException += (sender, e) => + { + if (e.ExceptionObject is Exception ex) LogUnit.Error(ex); + }; TaskScheduler.UnobservedTaskException += (sender, e) => { e.SetObserved(); + LogUnit.Error(e.Exception); }; // Exceptions from Reactive UI - RxAppExceptionHandler.Instance.OnExceptionHandler += e => - { - }; + RxAppExceptionHandler.Instance.OnExceptionHandler += LogUnit.Error; RxApp.DefaultExceptionHandler = RxAppExceptionHandler.Instance; } + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + throw new NotImplementedException(); + } + #endregion extend } \ No newline at end of file diff --git a/JiLinCpApp.sln b/JiLinCpApp.sln index 6831c9c..ce259ec 100644 --- a/JiLinCpApp.sln +++ b/JiLinCpApp.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.4.33205.214 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiLinApp", "JiLinApp\JiLinApp.csproj", "{9D1063BD-2C84-4FF3-90CF-B16159B3E803}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EC.Util", "EC.Util\EC.Util.csproj", "{B5D7ED30-805C-4A5D-BD41-D2F910806340}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {9D1063BD-2C84-4FF3-90CF-B16159B3E803}.Debug|Any CPU.Build.0 = Debug|Any CPU {9D1063BD-2C84-4FF3-90CF-B16159B3E803}.Release|Any CPU.ActiveCfg = Release|Any CPU {9D1063BD-2C84-4FF3-90CF-B16159B3E803}.Release|Any CPU.Build.0 = Release|Any CPU + {B5D7ED30-805C-4A5D-BD41-D2F910806340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5D7ED30-805C-4A5D-BD41-D2F910806340}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5D7ED30-805C-4A5D-BD41-D2F910806340}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5D7ED30-805C-4A5D-BD41-D2F910806340}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE