├── HCNetSDK.dll ├── README.md └── pyhik.py /HCNetSDK.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakiron/pyhik/330c4b25c7efb1d5a9fc78934c4b4b36c7bc2aac/HCNetSDK.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pyhik 2 | ===== 3 | 4 | Python binding for Hikvision Network DVR SDK 5 | -------------------------------------------------------------------------------- /pyhik.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import ctypes,os,threading,time 3 | from ctypes.wintypes import DWORD 4 | from datetime import datetime 5 | 6 | __author__ = 'gmuralit' 7 | 8 | #################################################### 9 | ### Load the dll ### 10 | #################################################### 11 | HC = ctypes.WinDLL('HCNetSDK.dll') 12 | 13 | #################################################### 14 | ### Global variables ### 15 | #################################################### 16 | SERIALNO_LEN = 48 17 | STREAM_ID_LEN = 32 18 | __curdir__ = os.getcwd() 19 | __capdir__ = os.path.join(__curdir__,"capture") 20 | __logdir__ = os.path.join(__curdir__,"logs") 21 | exitFlag = 0 22 | 23 | #################################################### 24 | ### Data types to be used in the library ### 25 | #################################################### 26 | BOOL = ctypes.c_bool 27 | INT = ctypes.c_int 28 | LONG = ctypes.c_long 29 | BYTE = ctypes.c_ubyte 30 | WORD = ctypes.c_ushort 31 | CHARP = ctypes.c_char_p 32 | VOIDP = ctypes.c_void_p 33 | HWND = ctypes.c_uint 34 | 35 | CMPFUNC = ctypes.WINFUNCTYPE(None,LONG,DWORD,BYTE,DWORD,VOIDP) 36 | 37 | #################################################### 38 | ### Structures to be used in the library ### 39 | #################################################### 40 | class NET_DVR_DEVICEINFO_V30(ctypes.Structure): 41 | _fields_ = [("sSerialNumber",BYTE * SERIALNO_LEN), 42 | ("byAlarmInPortNum",BYTE), 43 | ("byAlarmOutPortNum",BYTE), 44 | ("byDiskNum",BYTE), 45 | ("byDVRType",BYTE), 46 | ("byChanNum",BYTE), 47 | ("byStartChan",BYTE), 48 | ("byAudioChanNum",BYTE), 49 | ("byIPChanNum",BYTE), 50 | ("byZeroChanNum",BYTE), 51 | ("byMainProto",BYTE), 52 | ("bySubProto",BYTE), 53 | ("bySupport",BYTE), 54 | ("bySupport1",BYTE), 55 | ("bySupport2",BYTE), 56 | ("wDevType",WORD), 57 | ("bySupport3",BYTE), 58 | ("byMultiStreamProto",BYTE), 59 | ("byStartDChan",BYTE), 60 | ("byStartDTalkChan",BYTE), 61 | ("byHighDChanNum",BYTE), 62 | ("bySupport4",BYTE), 63 | ("byRes2",BYTE * 10) 64 | ] 65 | LPNET_DVR_DEVICEINFO_V30 = ctypes.POINTER(NET_DVR_DEVICEINFO_V30) 66 | 67 | class NET_DVR_CLIENTINFO(ctypes.Structure): 68 | _fields_ = [ 69 | ("lChannel",LONG), 70 | ("lLinkMode",LONG), 71 | ("hPlayWnd",HWND), 72 | ("sMultiCastIP",CHARP), 73 | ("byProtoType",BYTE), 74 | ("byRes",BYTE*3) 75 | ] 76 | LPNET_DVR_CLIENTINFO = ctypes.POINTER(NET_DVR_CLIENTINFO) 77 | 78 | class NET_DVR_PREVIEWINFO(ctypes.Structure): 79 | _fields_ = [ 80 | ("lChannel",LONG), 81 | ("dwStreamType",DWORD), 82 | ("dwLinkMode",DWORD), 83 | ("hPlayWnd",HWND), 84 | ("bBlocked",DWORD), 85 | ("bPassbackRecord",DWORD), 86 | ("byPreviewMode",BYTE), 87 | ("byStreamID",BYTE * STREAM_ID_LEN), 88 | ("byProtoType",BYTE), 89 | ("byRes",BYTE * 222), 90 | ] 91 | LPNET_DVR_PREVIEWINFO = ctypes.POINTER(NET_DVR_PREVIEWINFO) 92 | 93 | class NET_DVR_SDKLOCAL_CFG(ctypes.Structure): 94 | _fields_ = [ 95 | ("byEnableAbilityParse",BYTE), 96 | ("byVoiceComMode",BYTE), 97 | ("byRes",BYTE*382), 98 | ("byProtectKey",BYTE*128) 99 | ] 100 | LPNET_DVR_SDKLOCAL_CFG = ctypes.POINTER(NET_DVR_SDKLOCAL_CFG) 101 | 102 | class NET_DVR_JPEGPARA(ctypes.Structure): 103 | _fields_ = [ 104 | ("wPicSize",WORD), 105 | ("wPicQuality",WORD) 106 | ] 107 | 108 | LPNET_DVR_JPEGPARA = ctypes.POINTER(NET_DVR_JPEGPARA) 109 | 110 | #################################################### 111 | ### Error codes ### 112 | #################################################### 113 | __errorcodes__ = { 114 | 0: 'No error', 115 | 3: 'SDK is not initialized', 116 | 7: 'Failed to connect to the device. The device is off-line, or connection timeout caused by network', 117 | 10: 'Timeout when receiving the data from the device', 118 | 12: 'API calling order error', 119 | 34: 'Failed to create a file, during local recording, saving picture, getting configuration file or downloading record file', 120 | 84: 'Load StreamTransClient.dll failed' 121 | } 122 | 123 | #################################################### 124 | ### SDK Information functions ### 125 | #################################################### 126 | def getSDKVersion(): 127 | """ 128 | Get SDK version information 129 | 130 | @Params 131 | None 132 | 133 | @Return 134 | SDK Version information [Call getLastError() to get the error code] 135 | """ 136 | _gsv = HC.NET_DVR_GetSDKVersion 137 | _gsv.restype = DWORD 138 | return hex(_gsv()) 139 | 140 | #################################################### 141 | ### SDK initialization and termination functions ### 142 | #################################################### 143 | def init(filePtr): 144 | """ 145 | Initialize the SDK. Call this function before using any of the other APIs 146 | 147 | @Params 148 | filePtr - File pointer to the SDK log file 149 | 150 | @Return 151 | None 152 | """ 153 | _init = HC.NET_DVR_Init 154 | _init.restype = BOOL 155 | if _init(): 156 | print(str(datetime.now())+"|INFO|SDK initialized successfully",file=filePtr) 157 | else : 158 | _m=str(datetime.now())+"|ERROR|SDK initialization failed. Error message: "+getErrorMsg(getLastError()) 159 | print(_m,file=filePtr) 160 | raise Exception(_m) 161 | 162 | def release(filePtr): 163 | """ 164 | Release the SDK. If init() was called, invoke this function at program exit 165 | 166 | @Params 167 | filePtr - File pointer to the SDK log file 168 | 169 | @Return 170 | None 171 | """ 172 | _release = HC.NET_DVR_Cleanup 173 | _release.restype = BOOL 174 | if _release(): 175 | print(str(datetime.now())+"|INFO|SDK released successfully",file=filePtr) 176 | else : 177 | _m=str(datetime.now())+"|ERROR|SDK release failed. Error message: "+getErrorMsg(getLastError()) 178 | print(_m,file=filePtr) 179 | raise Exception(_m) 180 | 181 | #################################################### 182 | ### Connection timeout functions ### 183 | #################################################### 184 | def setConnectTime(timeout,attemptCount,filePtr): 185 | """ 186 | Set network connection timeout and connection attempt times. Default timeout is 3s. 187 | 188 | @Params 189 | timeout - timeout, unit:ms, range:[300,75000] 190 | attemptCount - Number of attempts for connection 191 | filePtr - File pointer to the SDK log file 192 | 193 | @Return 194 | None 195 | """ 196 | _sct = HC.NET_DVR_SetConnectTime 197 | _sct.argtypes = [DWORD, DWORD] 198 | _sct.restype = BOOL 199 | if _sct(timeout,attemptCount): 200 | print(str(datetime.now())+"|INFO|Set connect time-"+str(timeout)+":"+str(attemptCount),file=filePtr) 201 | else: 202 | print(str(datetime.now())+"|ERROR|Set connect time failed",file=filePtr) 203 | 204 | def setReconnect(interval,enableReconnect,filePtr): 205 | """ 206 | Set reconnecting time interval. Default reconnect interval is 5 seconds. 207 | 208 | @Params 209 | interval - Reconnecting interval, unit:ms, default:30s 210 | enableReconnect - Enable or disable reconnect function, 0-disable, 1-enable(default) 211 | filePtr - File pointer to the SDK log file 212 | 213 | @Return 214 | None 215 | """ 216 | _sr = HC.NET_DVR_SetReconnect 217 | _sr.argtypes = [DWORD, DWORD] 218 | _sr.restype = BOOL 219 | if _sr(interval,enableReconnect): 220 | print(str(datetime.now())+"|INFO|Set reconnect time-"+str(interval)+":"+str(enableReconnect),file=filePtr) 221 | else: 222 | print(str(datetime.now())+"|ERROR|Set reconnect time failed",file=filePtr) 223 | 224 | #################################################### 225 | ### Error message functions ### 226 | #################################################### 227 | def getLastError(): 228 | """ 229 | The error code of last operation 230 | 231 | @Params 232 | None 233 | 234 | @Return 235 | Error code 236 | """ 237 | _gle = HC.NET_DVR_GetLastError 238 | _gle.restype = DWORD 239 | return _gle() 240 | 241 | def getErrorMsg(errorCode): 242 | """ 243 | Return the error message of last operation 244 | 245 | @Params 246 | errorCode - Error code from getLastError() 247 | 248 | @Return 249 | Error message 250 | """ 251 | return __errorcodes__[errorCode] 252 | 253 | #################################################### 254 | ### Device login functions ### 255 | #################################################### 256 | def login(dIP,dPort,username,password,filePtr): 257 | """ 258 | Login to the device 259 | 260 | @Params 261 | dIP - IP address of the device 262 | dPort - Port number of the device 263 | username - Username for login 264 | password - password 265 | filePtr - File pointer to the SDK log file 266 | 267 | @Return 268 | (userID, dInfo) - Unique user ID and device info, else -1 on failure [Call getLastError() to get the error code] 269 | """ 270 | _l = HC.NET_DVR_Login_V30 271 | _l.argtypes = [CHARP,WORD,CHARP,CHARP,LPNET_DVR_DEVICEINFO_V30] 272 | _l.restype = LONG 273 | _info = NET_DVR_DEVICEINFO_V30() 274 | _userId = _l(dIP,dPort,username,password,ctypes.byref(_info)) 275 | if _userId != -1: 276 | print(str(datetime.now())+"|INFO|Logged in successfully",file=filePtr) 277 | return _userId,_info 278 | else : 279 | _m = str(datetime.now())+"|INFO|Login failed. Error message: "+getErrorMsg(getLastError()) 280 | print(_m,file=filePtr) 281 | raise Exception(_m) 282 | 283 | def logout(userId,filePtr): 284 | """ 285 | Logout from the device 286 | 287 | @Params 288 | userID - User ID, returned from login() 289 | filePtr - File pointer to the SDK log file 290 | 291 | @Return 292 | None 293 | """ 294 | _lo = HC.NET_DVR_Logout 295 | _lo.argtypes = [LONG] 296 | _lo.restype = BOOL 297 | _ldir = os.path.join(__logdir__,'SDK.log') 298 | f = open(_ldir,'a') 299 | if _lo(userId): 300 | print(str(datetime.now())+"|INFO|Logged out successfully",file=filePtr) 301 | else : 302 | _m = str(datetime.now())+"|ERROR|Logout failed. Error message: "+getErrorMsg(getLastError()) 303 | print(_m,file=filePtr) 304 | raise Exception(_m) 305 | 306 | #################################################### 307 | ### Live view functions ### 308 | #################################################### 309 | def startRealPlay(userId,ipClientInfo,realDataCbk,userData,blocked): 310 | """ 311 | Starting live view 312 | 313 | @Params 314 | userId - return value of login() 315 | ipClientInfo - Live view parameter 316 | realDataCb - Real-time stream data callback function 317 | userData - User data 318 | blocked - Whether to set stream data requesting process blocked or not: 0-no, 1-yes 319 | 320 | @Return 321 | -1 on failure [Call getLastError() to get the error code] 322 | Other values - live view handle for use in stopRealPlay() 323 | """ 324 | _srp = HC.NET_DVR_RealPlay_V30 325 | if realDataCbk: 326 | _srp.argtypes = [LONG,LPNET_DVR_CLIENTINFO,CMPFUNC,VOIDP,BOOL] 327 | else: 328 | _srp.argtypes = [LONG,LPNET_DVR_CLIENTINFO,VOIDP,VOIDP,BOOL] 329 | _srp.restype = LONG 330 | return _srp(userId,ctypes.byref(ipClientInfo),realDataCbk,userData,blocked) 331 | 332 | def stopRealPlay(realHandle): 333 | """ 334 | Stopping live view 335 | 336 | @Params 337 | realHandle - live view handle, return value from startRealPlay() 338 | 339 | @Return 340 | TRUE on success 341 | FALSE on failure [Call getLastError() to get the error code] 342 | """ 343 | _strp = HC.NET_DVR_StopRealPlay 344 | _strp.argtypes = [LONG] 345 | _strp.restype = BOOL 346 | return _strp(realHandle) 347 | 348 | def getRealPlayerIndex(realHandle): 349 | """ 350 | Get player handle to use with other player SDK functions 351 | 352 | @Params 353 | realHandle - Live view handle, return value from startRealPlay() 354 | 355 | @Return 356 | -1 on failure [Call getLastError() to get the error code] 357 | Other values - live view handle 358 | """ 359 | _grpi = HC.NET_DVR_GetRealPlayerIndex 360 | _grpi.argtypes = [LONG] 361 | _grpi.restype = INT 362 | return _grpi(realHandle) 363 | 364 | #################################################### 365 | ### Capture picture functions ### 366 | #################################################### 367 | 368 | def captureJPEGPicture(userId,channelNo,jpegParam,fileName,filePtr): 369 | """ 370 | Capture a frame and save to file 371 | 372 | @Params 373 | userId - User Id, return value from login() 374 | channelNo - Channel index for capturing the picture 375 | jpegParam - Target JPEG picture parameters 376 | fileName - URL to save picture 377 | filePtr - File pointer to the logfile 378 | 379 | @Return 380 | TRUE on success 381 | FALSE on failure [Call getLastError() to get the error code] 382 | """ 383 | _cjp = HC.NET_DVR_CaptureJPEGPicture 384 | _cjp.argtypes = [LONG,LONG,LPNET_DVR_JPEGPARA,CHARP] 385 | _cjp.restype = BOOL 386 | if _cjp(userId,channelNo,ctypes.byref(jpegParam),fileName): 387 | print(str(datetime.now())+"|INFO|Picture captured successfully at "+fileName,file=filePtr) 388 | else: 389 | print(str(datetime.now())+"|ERROR|Picture capture failed. Error message: "+getErrorMsg(getLastError()),file=filePtr) 390 | 391 | 392 | #################################################### 393 | ### Callback functions ### 394 | #################################################### 395 | def setRealDataCallBack(lRealHandle,cbRealDataCbk,dwUser): 396 | """ 397 | Set callback function 398 | 399 | @Params 400 | lRealHandle - live view handle, return value from startRealPlay() 401 | cbRealDataCbk - Callback function 402 | dwUser - User data 403 | 404 | @Return 405 | TRUE on success 406 | FALSE on failure [Call getLastError() to get the error code] 407 | """ 408 | _srdcb = HC.NET_DVR_SetRealDataCallBack 409 | _srdcb.argtypes = [LONG,CMPFUNC,DWORD] 410 | _srdcb.restype = BOOL 411 | return _srdcb(lRealHandle,cbRealDataCbk,dwUser) 412 | 413 | #################################################### 414 | ### Helper functions ### 415 | #################################################### 416 | def struct2tuple(struct): 417 | """ 418 | Convert a structure to a tuple 419 | 420 | @Params 421 | struct - ctypes structure object 422 | 423 | @Return 424 | Tuple containing the values of all the fields in the struct 425 | """ 426 | _sf = NET_DVR_DEVICEINFO_V30._fields_ 427 | _dict = {} 428 | for _fn,_ft in _sf: 429 | _v = struct.__getattribute__(_fn) 430 | if(type(_v)) != int: 431 | _v = ctypes.cast(_v,ctypes.c_char_p).value 432 | _dict[_fn] = _v 433 | return _dict 434 | 435 | def logger(): 436 | """ 437 | Logger utility 438 | 439 | @Params 440 | None 441 | 442 | @Return 443 | None 444 | """ 445 | if not os.path.exists(__logdir__): 446 | os.makedirs(__logdir__) 447 | _ldir = os.path.join(__logdir__,'SDK.log') 448 | f = open(_ldir,'w') 449 | print(str(datetime.now())+"|INFO|"+_ldir+" created",file=f) 450 | 451 | 452 | def createDirectory(startName,count): 453 | """ 454 | Creates a directory, if not exists 455 | 456 | @Params 457 | startName - starting name for the directory [numbers] 458 | count - count of the directories to be created 459 | 460 | @Return 461 | None 462 | """ 463 | for _chan in range(startName,count): 464 | _cdir = os.path.join(__capdir__,str(_chan)) 465 | _ldir = os.path.join(__logdir__,str(_chan)+'chan.log') 466 | if not os.path.exists(_cdir): 467 | os.makedirs(_cdir) 468 | f = open(_ldir,'w') 469 | print(str(datetime.now())+"|INFO|"+_ldir+" created",file=f) 470 | 471 | def checkVideoStatus(userId,channelNo,jpegParam): 472 | """ 473 | Checks video status for the particular device, channel number and logs the information 474 | 475 | @Params 476 | userId - User Id, return value from login() 477 | channelNo - Channel index for capturing the picture 478 | jpegParam - Target JPEG picture parameters 479 | """ 480 | global exitFlag 481 | _loop = 1 482 | _cdir = os.path.join(__capdir__,str(channelNo)) 483 | _ldir = os.path.join(__logdir__,str(channelNo)+'chan.log') 484 | f=open(_ldir,'a') 485 | print(str(datetime.now())+"|INFO|Thread started for "+str(channelNo),file=f) 486 | 487 | while not exitFlag: 488 | print(str(datetime.now())+"|INFO|Thread called for "+str(channelNo),file=f) 489 | captureJPEGPicture(userId,channelNo,jpegParam,os.path.join(_cdir,str(channelNo)+'_'+str(_loop)+'.jpeg'),f) 490 | if _loop > 2: 491 | 492 | # Write code here for video status# 493 | 494 | os.remove(os.path.join(_cdir,str(channelNo)+'_'+str(_loop-2)+'.jpeg')) 495 | _loop+=1 496 | time.sleep(0.18) 497 | 498 | print(str(datetime.now())+"|INFO|Thread stopped for "+str(channelNo),file=f) 499 | 500 | class CThread(threading.Thread): 501 | def __init__(self,threadId,name,userId,channelNo,jpegParam): 502 | threading.Thread.__init__(self) 503 | self.threadId = threadId 504 | self.name = name 505 | self.userId = userId 506 | self.channelNo = channelNo 507 | self.jpegParam = jpegParam 508 | 509 | def run(self): 510 | checkVideoStatus(self.userId,self.channelNo,self.jpegParam) 511 | 512 | 513 | #################################################### 514 | ### Test functions ### 515 | #################################################### 516 | def startCapture16(ip,port,username,password,duration): 517 | """ 518 | Start the capture for 16 video channels of the device and log the video status for every capture 519 | 520 | @Params 521 | ip - IP address of the device 522 | port - Port of the device 523 | username - Username for login 524 | password - Password for login 525 | duration - Duration for the test run 526 | 527 | @Return 528 | None 529 | """ 530 | logger() 531 | f = open(os.path.join(__logdir__,'SDK.log'),'a') 532 | 533 | init(f) 534 | setConnectTime(2000,2,f) 535 | setReconnect(100,True,f) 536 | userId,deviceInfo = login(ip,port,username,password,f) 537 | 538 | dictDeviceInfo = struct2tuple(deviceInfo) 539 | startChan = dictDeviceInfo['byStartChan'] 540 | chanNum = dictDeviceInfo['byChanNum'] 541 | 542 | createDirectory(startChan,chanNum+1) 543 | 544 | jpegParam = NET_DVR_JPEGPARA() 545 | jpegParam.wPicSize = 2 546 | jpegParam.wPicQuality = 2 547 | 548 | for threadid in range(1,17): 549 | thread = CThread(threadid,'Channel'+str(threadid),userId,threadid,jpegParam) 550 | thread.start() 551 | 552 | 553 | startTime = int(time.time()) 554 | endTime = duration * 3600 555 | print(str(datetime.now())+"|INFO|Startime:"+str(startTime)+";ETA in "+str(endTime)+"s",file=f) 556 | 557 | while True: 558 | if int(time.time()) - startTime > endTime: 559 | global exitFlag 560 | exitFlag = 1 561 | break 562 | 563 | logout(userId,f) 564 | release(f) 565 | 566 | 567 | if __name__ == "__main__": 568 | startCapture16("10.78.203.159",8000,"admin","12345",0.0066) --------------------------------------------------------------------------------