(最近重新修改了下SDK,因为在两个车间各安装了一台设备)
再次使用下这张图
今天是海康设备SDK二次开发的第三天,也是最后一次,因为这次将进入功能开发程序阶段,前面两次已经做好了全部的前期工作 。
首先从导入包开始
提供的demo里面是这样的:
# coding=utf-8
import os
import platform
import time
import tkinter
from tkinter import *
from HCNetSDK import *
因为后期涉及到数据提取出来后要将数据进行清洗和修改,然后导入到sql中去,所以我进行了修改,添加了pandas和pymysql两个包,然后使用sqlalchemy进行数据库连接,修改如下:
# coding=utf-8
import os
import platform
import time
import tkinter
from tkinter import *
from HCNetSDK import *
import pandas
from pymysql import *
from sqlalchemy import create_engine
数据库连接设置一个全局连接:
engine = create_engine("mysql+pymysql://halo:root@localhost:3306/t_pcmespd?charset=utf8") # 定义pd数据导入sql的方式,开启mysql数据库
前面的一些函数定义不需要改动,使用给出的demo就可以了
# 系统环境标识
WINDOWS_FLAG = True
# 报警信息列表,报一次在回调中加1次记录
alarm_info = []
# 获取当前系统环境
def GetPlatform():
sysstr = platform.system()
print('' + sysstr)
if sysstr != "Windows":
global WINDOWS_FLAG
WINDOWS_FLAG = False
# 设置SDK初始化依赖库路径
def SetSDKInitCfg():
# 设置HCNetSDKCom组件库和SSL库加载路径
# print(os.getcwd())
if WINDOWS_FLAG:
strPath = os.getcwd().encode('gbk')
sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
sdk_ComPath.sPath = strPath
sdk.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
sdk.NET_DVR_SetSDKInitCfg(3, create_string_buffer(strPath + b'\libcrypto-1_1-x64.dll'))
sdk.NET_DVR_SetSDKInitCfg(4, create_string_buffer(strPath + b'\libssl-1_1-x64.dll'))
else:
strPath = os.getcwd().encode('utf-8')
sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
sdk_ComPath.sPath = strPath
sdk.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
sdk.NET_DVR_SetSDKInitCfg(3, create_string_buffer(strPath + b'/libcrypto.so.1.1'))
sdk.NET_DVR_SetSDKInitCfg(4, create_string_buffer(strPath + b'/libssl.so.1.1'))
最重要的一点,也是本次功能开发的重点模块来了,给出的demo中有好几个:
if lCommand == 0x4000:
print('移动侦测')
Alarm_struct = cast(pAlarmInfo,
LPNET_DVR_ALARMINFO_V30).contents # 当lCommand是COMM_ALARM时将pAlarmInfo强制转换为NET_DVR_ALARMINFO类型的指针再取值
single_alrm['dwAlarmType'] = hex(Alarm_struct.dwAlarmType)
single_alrm['byAlarmOutputNumber'] = Alarm_struct.byAlarmOutputNumber[0]
single_alrm['byChannel'] = Alarm_struct.byChannel[0]
if lCommand == 0x5002:
print('门禁触发报警')
Alarm_struct = cast(pAlarmInfo,
LPNET_DVR_ACS_ALARM_INFO).contents # 当lCommand是0x5002时将pAlarmInfo强制转换为NET_DVR_ACS_ALARM_INFO类型的指针再取值
single_alrm['dwSize'] = Alarm_struct.dwSize
single_alrm['dwMajor'] = hex(Alarm_struct.dwMajor)
single_alrm['dwMinor'] = hex(Alarm_struct.dwMinor)
single_alrm['dwPicDataLen'] = Alarm_struct.dwPicDataLen
localtime = time.asctime(time.localtime(time.time()))
single_alrm['localtime'] = localtime
single_alrm['AcsEventInfo'] = Alarm_struct.byAcsEventInfoExtend
# 抓拍图片
PicDataLen = Alarm_struct.dwPicDataLen
if PicDataLen != 0:
buff1 = string_at(Alarm_struct.pPicData, PicDataLen)
with open('../../pic/Acs_Capturetest.jpg', 'wb') as fp:
fp.write(buff1)
if lCommand == 0x5200:
print('身份证刷卡事件上传')
Alarm_struct = cast(pAlarmInfo,LPNET_DVR_ID_CARD_INFO_ALARM).contents
single_alrm['dwSize'] = Alarm_struct.dwSize
single_alrm['dwMajor'] = hex(Alarm_struct.dwMajor)
single_alrm['dwMinor'] = hex(Alarm_struct.dwMinor)
localtime = time.asctime(time.localtime(time.time()))
single_alrm['localtime'] = localtime
#抓拍图片
DataLen = Alarm_struct.dwCapturePicDataLen
if DataLen != 0:
buff1 = string_at(Alarm_struct.pCapturePicData, DataLen)
with open('../../pic/IDInfo_Capturetest.jpg', 'wb') as fp1:
fp1.write(buff1)
#身份证图片
CardPicLen = Alarm_struct.dwPicDataLen
if DataLen != 0:
buff2 = string_at(Alarm_struct.pPicData, CardPicLen)
with open('../../pic/IDInfo_IDPicTest.jpg', 'wb') as fp2:
fp2.write(buff2)
# ISAPI协议报警信息
if lCommand == 0x6009:
print('ISAPI协议报警信息上传')
Alarm_struct = cast(pAlarmInfo, LPNET_DVR_ALARM_ISAPI_INFO).contents
single_alrm['byDataType'] = Alarm_struct.byDataType
single_alrm['byPicturesNumber'] = Alarm_struct.byPicturesNumber
# 报警信息XML或者JSON数据
DataLen = Alarm_struct.dwAlarmDataLen
if DataLen != 0:
buffInfo = string_at(Alarm_struct.pAlarmData, DataLen)
with open('../../pic/IsapiInfo.txt', 'wb') as fpInfo:
fpInfo.write(buffInfo)
# 报警信息图片数据
nSize = 0
pNum = Alarm_struct.byPicturesNumber
if pNum > 0:
print(pNum)
STRUCT = NET_DVR_ALARM_ISAPI_PICDATA * pNum
PicStruct = cast(Alarm_struct.pPicPackData, POINTER(STRUCT)).contents
for nPicIndex in range(pNum):
nSize = PicStruct[nPicIndex].dwPicLen
print(nSize)
if nSize != 0:
buffInfo = string_at(PicStruct[nPicIndex].pPicData, nSize)
strName = str(PicStruct[nPicIndex].szFilename)
single_alrm['PicName'] = strName
sFileName = ('../../pic/ISAPI_Pic[%d].jpg' % nPicIndex)
with open(sFileName, 'wb') as fpPic:
fpPic.write(buffInfo)
alarm_info.append(single_alrm)
print(alarm_info[-1])
tt.insert(tkinter.END, alarm_info[-1])
tt.insert(tkinter.END, '\r\n---------------\r\n')
return True
这次开发的功能只用到了其中的第二个
当lCommand == 0x5002这个判断信号触发数据传递。
所以在数据提取这部分要在demo的基础上添加这部分的程序
if lCommand == 0x5002:
print('门禁触发报警')
Alarm_struct = cast(pAlarmInfo,
LPNET_DVR_ACS_ALARM_INFO).contents # 当lCommand是0x5002时将pAlarmInfo强制转换为NET_DVR_ACS_ALARM_INFO类型的指针再取值
single_alrm['dwSize'] = Alarm_struct.dwSize
single_alrm['dwMajor'] = hex(Alarm_struct.dwMajor)
single_alrm['dwMinor'] = hex(Alarm_struct.dwMinor)
single_alrm['dwPicDataLen'] = Alarm_struct.dwPicDataLen
localtime = time.asctime(time.localtime(time.time()))
single_alrm['localtime'] = localtime
single_alrm['AcsEventInfo'] = Alarm_struct.byAcsEventInfoExtend
# 抓拍图片
PicDataLen = Alarm_struct.dwPicDataLen
if PicDataLen != 0:
buff1 = string_at(Alarm_struct.pPicData, PicDataLen)
with open('../../pic/Acs_Capturetest.jpg', 'wb') as fp:
fp.write(buff1)
上面是demo中的内容,在localtime这里我做了修改,给出的格式是ISO格式,导入到sql时无法正确显示,所以直接更换传自带的时间格式,添加内容如下:
if Alarm_struct.byAcsEventInfoExtend == 1:
这里要着重讲解下,因为该设备的数据均是直接传递到内存中,在提取数据的时候要通过指针的方式来读取,demo中给出了一种读取指针数据的方式
但是,如果下面这一步效仿demo来,只返回一个内存地址,这个地方卡着痛苦了好几天,通过和海康的技术咨询,给出的是结构体错误,又反复的校验结构体,没发现什么问题,最后在海康开发手册里给出的C++demo种找到了灵感。
然后通过查找资料,将这部分改成python如下:
Alarm_struct_ACS = NET_DVR_ACS_EVENT_INFO_EXTEND() #定义事件详情
ctypes.memset(ctypes.byref(Alarm_struct_ACS),0,ctypes.sizeof(Alarm_struct_ACS)) #利用ctypes.memset方法设置Alarm_struct_ACS初始内存空间值为0
ctypes.memmove(ctypes.byref(Alarm_struct_ACS),Alarm_struct.pAcsEventInfoExtend,ctypes.sizeof(Alarm_struct))#利用ctypes.memmove方法将Alarm_struct中内存地址为pAcsEventInfoExtend的值赋给Alarm_struct_ACS
然后就成功获取到了所需要字段的值,至此本次项目最难的部分就给解决了,后面的就是按照需求一步一步的弄成自己想要的式样,整体写下来,海康给的SDK基本算是完整的,减少了大量的开发工作,只是其中也需要自己把开发手册和开发指南这两个资料看透看明白才能顺利的完成项目开发。
后面还加了个小步骤,因为需要设备实时接收到信息,所以TK开发的界面需要最小化至右下角,所以后面TK那部分我做了下小修改,如下:
menu = (MenuItem('显示', show_window, default=True), Menu.SEPARATOR, MenuItem('退出', quit_window))
image = Image.open("XXX.jpg")
icon = pystray.Icon("icon", image, "图标名称", menu)
win = tkinter.Tk()
# # 固定窗口大小
# win.resizable(0, 0)
# win.overrideredirect(True)
sw = win.winfo_screenwidth()
# 得到屏幕宽度
sh = win.winfo_screenheight()
# 得到屏幕高度
# 窗口宽高
ww = 512
wh = 384
x = (sw - ww) / 2
y = (sh - wh) / 2
win.geometry("%dx%d+%d+%d" % (ww, wh, x, y))
win.protocol('WM_DELETE_WINDOW', on_exit)
win.title('XXX')
win.iconbitmap("kd.ico")
threading.Thread(target=icon.run, daemon=True).start()
# # 创建退出按键
# b = Button(win, text='退出', command=win.quit)
# b.pack()
# 创建显示报警信息的文本框
tt = tkinter.Text(win, width=ww, height=wh, bg='white')
tt.pack()
这样实现了点击界面关闭按钮不会直接退出,而是退至后台托盘。
好了,看到这里给个关注,加个好友可以获得完整代码!
下个开发项目见。