diff --git a/zk/base.py b/zk/base.py index 286f3fd..d43a3d4 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import sys -#from builtins import str from datetime import datetime from socket import AF_INET, SOCK_DGRAM, SOCK_STREAM, socket, timeout from struct import pack, unpack @@ -8,10 +7,11 @@ import codecs from zk import const from zk.attendance import Attendance -from zk.exception import ZKErrorResponse, ZKNetworkError +from zk.exception import ZKErrorConnection, ZKErrorResponse, ZKNetworkError from zk.user import User from zk.finger import Finger + def safe_cast(val, to_type, default=None): #https://stackoverflow.com/questions/6330071/safe-casting-in-python try: @@ -21,9 +21,10 @@ def safe_cast(val, to_type, default=None): def make_commkey(key, session_id, ticks=50): - """take a password and session_id and scramble them to send to the time - clock. - copied from commpro.c - MakeKey""" + """ + take a password and session_id and scramble them to send to the machine. + copied from commpro.c - MakeKey + """ key = int(key) session_id = int(session_id) k = 0 @@ -55,16 +56,15 @@ def make_commkey(key, session_id, ticks=50): k[3] ^ B) return k + class ZK_helper(object): - """ helper class """ + """ + helper class + """ def __init__(self, ip, port=4370): self.address = (ip, port) self.ip = ip self.port = port - #self.timeout = timeout - #self.password = password # passint - #self.firmware = int(firmware) #TODO check minor version? - #self.tcp = tcp def test_ping(self): """ @@ -83,34 +83,43 @@ class ZK_helper(object): def test_tcp(self): self.client = socket(AF_INET, SOCK_STREAM) - self.client.settimeout(10) # fixed test + self.client.settimeout(10) res = self.client.connect_ex(self.address) self.client.close() return res - def test_udp(self): # WIP: + def test_udp(self): self.client = socket(AF_INET, SOCK_DGRAM) - self.client.settimeout(10) # fixed test + self.client.settimeout(10) + class ZK(object): - """ Clase ZK """ + """ + ZK main class + """ def __init__(self, ip, port=4370, timeout=60, password=0, force_udp=False, ommit_ping=False, verbose=False, encoding='UTF-8'): - """ initialize instance """ - self.is_connect = False - self.is_enabled = True #let's asume - self.helper = ZK_helper(ip, port) + """ + initialize instance + """ + User.encoding = encoding self.__address = (ip, port) self.__sock = socket(AF_INET, SOCK_DGRAM) self.__sock.settimeout(timeout) self.__timeout = timeout self.__password = password # passint - #self.firmware = int(firmware) #dummy + self.__session_id = 0 + self.__reply_id = const.USHRT_MAX - 1 + self.__data_recv = None + self.__data = None + + self.is_connect = False + self.is_enabled = True + self.helper = ZK_helper(ip, port) self.force_udp = force_udp self.ommit_ping = ommit_ping self.verbose = verbose self.encoding = encoding - User.encoding = encoding - self.tcp = False + self.tcp = not force_udp self.users = 0 self.fingers = 0 self.records = 0 @@ -128,17 +137,14 @@ class ZK(object): self.next_user_id='1' self.user_packet_size = 28 # default zk6 self.end_live_capture = False - self.__session_id = 0 - self.__reply_id = const.USHRT_MAX-1 - self.__data_recv = None - self.__data = None def __nonzero__(self): - """ for boolean test""" + """ + for boolean test + """ return self.is_connect def __create_socket(self): - """ based on self.tcp""" if self.tcp: self.__sock = socket(AF_INET, SOCK_STREAM) self.__sock.settimeout(self.__timeout) @@ -148,18 +154,17 @@ class ZK(object): self.__sock.settimeout(self.__timeout) def __create_tcp_top(self, packet): - """ witch the complete packet set top header """ + """ + witch the complete packet set top header + """ length = len(packet) top = pack(' 1: @@ -197,18 +202,23 @@ class ZK(object): return pack('H', checksum) def __test_tcp_top(self, packet): - """ return size!""" + """ + return size! + """ if len(packet)<=8: - return 0 # invalid packet + return 0 tcp_header = unpack(' max_uid: max_uid = uid password = (password.split(b'\x00')[0]).decode(self.encoding, errors='ignore') name = (name.split(b'\x00')[0]).decode(self.encoding, errors='ignore').strip() - #card = unpack('I', card)[0] #or hex value? group_id = str(group_id) user_id = str(user_id) #TODO: check card value and find in ver8 @@ -1045,26 +994,19 @@ class ZK(object): else: while len(userdata) >= 72: uid, privilege, password, name, card, group_id, user_id = unpack(' max_uid: max_uid = uid - #card = int(unpack('I', separator)[0]) if not name: name = "NN-%s" % user_id user = User(uid, name, privilege, password, group_id, user_id, card) users.append(user) userdata = userdata[72:] - #get limits! max_uid += 1 self.next_uid = max_uid self.next_user_id = str(max_uid) - #re check while True: if any(u for u in users if u.user_id == self.next_user_id): max_uid += 1 @@ -1074,19 +1016,18 @@ class ZK(object): return users def cancel_capture(self): - ''' + """ cancel capturing finger - ''' + """ command = const.CMD_CANCELCAPTURE cmd_response = self.__send_command(command) return bool(cmd_response.get('status')) def verify_user(self): - ''' + """ start verify finger mode (after capture) - ''' + """ command = const.CMD_STARTVERIFY - # uid = chr(uid % 256) + chr(uid >> 8) cmd_response = self.__send_command(command) if cmd_response.get('status'): return True @@ -1094,7 +1035,9 @@ class ZK(object): raise ZKErrorResponse("Cant Verify") def reg_event(self, flags): - """ reg events, """ + """ + reg events + """ command = const.CMD_REG_EVENT command_string = pack ("I", flags) cmd_response = self.__send_command(command, command_string) @@ -1102,66 +1045,62 @@ class ZK(object): raise ZKErrorResponse("cant' reg events %i" % flags) def set_sdk_build_1(self): - """ """ command = const.CMD_OPTIONS_WRQ command_string = b"SDKBuild=1" cmd_response = self.__send_command(command, command_string) if not cmd_response.get('status'): - return False #raise ZKErrorResponse("can't set sdk build ") + return False return True def enroll_user(self, uid=0, temp_id=0, user_id=''): - ''' + """ start enroll user we need user_id (uid2) - ''' + """ command = const.CMD_STARTENROLL done = False if not user_id: - #we need user_id (uid2) users = self.get_users() users = list(filter(lambda x: x.uid==uid, users)) if len(users) >= 1: user_id = users[0].user_id - else: #double? posibly empty - return False #can't enroll + else: + return False if self.tcp: - command_string = pack('<24sbb',str(user_id).encode(), temp_id, 1) # el 1 es misterio + command_string = pack('<24sbb',str(user_id).encode(), temp_id, 1) else: - command_string = pack(' 16: #not empty + if len(data_recv) > 16: res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] if self.verbose: print("res %i" % res) if res == 0 or res == 6 or res == 4: - # 6 timeout, 4 mismatch error, 0 can't start(why?) if self.verbose: print ("posible timeout o reg Fallido") break else: - if len(data_recv) > 8: #not empty + if len(data_recv) > 8: res = unpack("H", data_recv.ljust(16,b"\x00")[8:10])[0] if self.verbose: print("res %i" % res) if res == 6 or res == 4: - if self.verbose: print ("posible timeout o reg Fallido") + if self.verbose: print ("posible timeout") break if self.verbose: print ("A:%i esperando 2do regevent" % attempts) - data_recv = self.__sock.recv(1032) # timeout? tarda bastante... + data_recv = self.__sock.recv(1032) self.__ack_ok() if self.verbose: print (codecs.encode(data_recv, 'hex')) if self.tcp: - if len(data_recv) > 8: #not empty + if len(data_recv) > 8: res = unpack("H", data_recv.ljust(24,b"\x00")[16:18])[0] if self.verbose: print("res %i" % res) if res == 6 or res == 4: @@ -1171,7 +1110,7 @@ class ZK(object): if self.verbose: print ("ok, continue?") attempts -= 1 else: - if len(data_recv) > 8: #not empty + if len(data_recv) > 8: res = unpack("H", data_recv.ljust(16,b"\x00")[8:10])[0] if self.verbose: print("res %i" % res) if res == 6 or res == 4: @@ -1181,8 +1120,7 @@ class ZK(object): if self.verbose: print ("ok, continue?") attempts -= 1 if attempts == 0: - if self.verbose: print ("esperando 3er regevent") - data_recv = self.__sock.recv(1032) # timeout? tarda bastante... + data_recv = self.__sock.recv(1032) self.__ack_ok() if self.verbose: print (codecs.encode(data_recv, 'hex')) if self.tcp: @@ -1191,9 +1129,9 @@ class ZK(object): res = unpack("H", data_recv.ljust(16,b"\x00")[8:10])[0] if self.verbose: print("res %i" % res) if res == 5: - if self.verbose: print ("huella duplicada") + if self.verbose: print ("finger duplicate") if res == 6 or res == 4: - if self.verbose: print ("posible timeout o reg Fallido") + if self.verbose: print ("posible timeout") if res == 0: size = unpack("H", data_recv.ljust(16,b"\x00")[10:12])[0] pos = unpack("H", data_recv.ljust(16,b"\x00")[12:14])[0] @@ -1205,7 +1143,7 @@ class ZK(object): self.verify_user() return done - def live_capture(self, new_timeout=10):# generator! + def live_capture(self, new_timeout=10): """ try live capture of events""" was_enabled = self.is_enabled users = self.get_users() @@ -1214,8 +1152,8 @@ class ZK(object): if not self.is_enabled: self.enable_device() if self.verbose: print ("start live_capture") - self.reg_event(const.EF_ATTLOG) #0xFFFF - self.__sock.settimeout(new_timeout) # default 1 minute test? + self.reg_event(const.EF_ATTLOG) + self.__sock.settimeout(new_timeout) self.end_live_capture = False while not self.end_live_capture: try: @@ -1232,7 +1170,7 @@ class ZK(object): data = data_recv[8:] if not header[0] == const.CMD_REG_EVENT: if self.verbose: print("not event! %x" % header[0]) - continue # or raise error? + continue if not len(data): if self.verbose: print ("empty") continue @@ -1248,8 +1186,8 @@ class ZK(object): uid = int(user_id) else: uid = tuser[0].uid - yield Attendance(user_id, timestamp, status, punch, uid)#punch test? - elif len(data) == 36 or len(data) == 32: #class 2 attendance + yield Attendance(user_id, timestamp, status, punch, uid) + elif len(data) == 36 or len(data) == 32: user_id, status, punch, timehex = unpack('<24sBB6s', data[:32]) user_id = (user_id.split(b'\x00')[0]).decode(errors='ignore') timestamp = self.__decode_timehex(timehex) @@ -1270,19 +1208,21 @@ class ZK(object): break if self.verbose: print ("exit gracefully") self.__sock.settimeout(self.__timeout) - self.reg_event(0) # TODO: test + self.reg_event(0) if not was_enabled: self.disable_device() - def clear_data(self, clear_type=5): # FCT_USER - ''' + def clear_data(self, clear_type=5): + """ clear all data (include: user, attendance report, finger database ) 2 = FCT_FINGERTMP - ''' + """ command = const.CMD_CLEAR_DATA command_string = pack("B", clear_type) cmd_response = self.__send_command(command, command_string) if cmd_response.get('status'): + self.is_connect = False + self.next_uid = 1 return True else: raise ZKErrorResponse("can't clear data") @@ -1294,18 +1234,18 @@ class ZK(object): must return data, broken """ data = [] - tcp_length = self.__test_tcp_top(data_recv) # tcp header + tcp_length = self.__test_tcp_top(data_recv) if self.verbose: print ("tcp_length {}, size {}".format(tcp_length, size)) if tcp_length <= 0: if self.verbose: print ("Incorrect tcp packet") return None, b"" - if (tcp_length - 8) < size: #broken on smaller DATAs + if (tcp_length - 8) < size: if self.verbose: print ("tcp length too small... retrying") resp, bh = self.__recieve_tcp_data(data_recv, tcp_length - 8) data.append(resp) size -= len(resp) if self.verbose: print ("new tcp DATA packet to fill misssing {}".format(size)) - data_recv = bh + self.__sock.recv(size + 16 ) #ideal limit + header + data_recv = bh + self.__sock.recv(size + 16 ) if self.verbose: print ("new tcp DATA starting with {} bytes".format(len(data_recv))) resp, bh = self.__recieve_tcp_data(data_recv, size) data.append(resp) @@ -1313,78 +1253,67 @@ class ZK(object): return b''.join(data), bh recieved = len(data_recv) if self.verbose: print ("recieved {}, size {}".format(recieved, size)) - #if (tcp_length - 16) > (recieved - 16): #broken first DATA - # #reparse as more data packets? - # if self.verbose: print ("trying".format(recieved, size)) - # _data, bh = self.__recieve_tcp_data(data_recv, tcp_length-16) - #analize first response response = unpack('HHHH', data_recv[8:16])[0] - if recieved >= (size + 32): #complete with ACK_OK included + if recieved >= (size + 32): if response == const.CMD_DATA: - resp = data_recv[16 : size + 16] # no ack? + resp = data_recv[16 : size + 16] if self.verbose: print ("resp complete len {}".format(len(resp))) return resp, data_recv[size + 16:] else: if self.verbose: print("incorrect response!!! {}".format(response)) - return None, b"" #broken - else: # response DATA incomplete (or missing ACK_OK) + return None, b"" + else: if self.verbose: print ("try DATA incomplete (actual valid {})".format(recieved-16)) - data.append(data_recv[16 : size + 16 ]) # w/o DATA tcp and header - size -= recieved - 16 # w/o DATA tcp and header + data.append(data_recv[16 : size + 16 ]) + size -= recieved - 16 broken_header = b"" - if size < 0: #broken ack header? + if size < 0: broken_header = data_recv[size:] - if self.verbose: print ("broken", (broken_header).encode('hex')) #TODO python3 - if size > 0: #need raw data to complete + if self.verbose: print ("broken", (broken_header).encode('hex')) + if size > 0: data_recv = self.__recieve_raw_data(size) - data.append(data_recv) # w/o tcp and header + data.append(data_recv) return b''.join(data), broken_header - #get cmd_ack_ok on __rchunk - def __recieve_raw_data(self, size): """ partial data ? """ data = [] if self.verbose: print ("expecting {} bytes raw data".format(size)) while size > 0: - data_recv = self.__sock.recv(size) #ideal limit? + data_recv = self.__sock.recv(size) recieved = len(data_recv) if self.verbose: print ("partial recv {}".format(recieved)) if recieved < 100 and self.verbose: print (" recv {}".format(codecs.encode(data_recv, 'hex'))) - data.append(data_recv) # w/o tcp and header + data.append(data_recv) size -= recieved if self.verbose: print ("still need {}".format(size)) return b''.join(data) def __recieve_chunk(self): """ recieve a chunk """ - if self.__response == const.CMD_DATA: # less than 1024!!! - if self.tcp: #MUST CHECK TCP SIZE + if self.__response == const.CMD_DATA: + if self.tcp: if self.verbose: print ("_rc_DATA! is {} bytes, tcp length is {}".format(len(self.__data), self.__tcp_length)) if len(self.__data) < (self.__tcp_length - 8): need = (self.__tcp_length - 8) - len(self.__data) if self.verbose: print ("need more data: {}".format(need)) more_data = self.__recieve_raw_data(need) return b''.join([self.__data, more_data]) - else: #enough data + else: if self.verbose: print ("Enough data") return self.__data - else: #UDP + else: if self.verbose: print ("_rc len is {}".format(len(self.__data))) - return self.__data #without headers + return self.__data elif self.__response == const.CMD_PREPARE_DATA: data = [] - size = self.__get_data_size() # from prepare data response... + size = self.__get_data_size() if self.verbose: print ("recieve chunk: prepare data size is {}".format(size)) if self.tcp: - if self.verbose: print ("recieve chunk: len data is {}".format(len(self.__data))) - #ideally 8 bytes of PREPARE_DATA only... - #but sometimes it comes with data... - - if len(self.__data) >= (8 + size): #prepare data with actual data! should be 8+size+32 - data_recv = self.__data[8:] # no need for more data! test, maybe -32 + if len(self.__data) >= (8 + size): + data_recv = self.__data[8:] else: - data_recv = self.__data[8:] + self.__sock.recv(size + 32) #could have two commands + data_recv = self.__data[8:] + self.__sock.recv(size + 32) resp, broken_header = self.__recieve_tcp_data(data_recv, size) data.append(resp) # get CMD_ACK_OK @@ -1392,14 +1321,13 @@ class ZK(object): data_recv = broken_header + self.__sock.recv(16) else: data_recv = broken_header - #could be broken if len(data_recv) < 16: print ("trying to complete broken ACK %s /16" % len(data_recv)) - if self.verbose: print (data_recv.encode('hex')) #todo python3 + if self.verbose: print (data_recv.encode('hex')) data_recv += self.__sock.recv(16 - len(data_recv)) #TODO: CHECK HERE_! if not self.__test_tcp_top(data_recv): if self.verbose: print ("invalid chunk tcp ACK OK") - return None #b''.join(data) # incomplete? + return None response = unpack('HHHH', data_recv[8:16])[0] if response == const.CMD_ACK_OK: if self.verbose: print ("chunk tcp ACK OK!") @@ -1409,30 +1337,30 @@ class ZK(object): return None return resp - #else udp - while True: #limitado por respuesta no por tamaño + while True: data_recv = self.__sock.recv(1024+8) response = unpack('<4H', data_recv[:8])[0] if self.verbose: print ("# packet response is: {}".format(response)) if response == const.CMD_DATA: - data.append(data_recv[8:]) #header turncated - size -= 1024 #UDP + data.append(data_recv[8:]) + size -= 1024 elif response == const.CMD_ACK_OK: - break #without problem. + break else: - #truncado! continuar? if self.verbose: print ("broken!") break if self.verbose: print ("still needs %s" % size) return b''.join(data) else: if self.verbose: print ("invalid response %s" % self.__response) - return None #("can't get user template") + return None def __read_chunk(self, start, size): - """ read a chunk from buffer """ + """ + read a chunk from buffer + """ for _retries in range(3): - command = 1504 #CMD_READ_BUFFER + command = 1504 command_string = pack('= 8:#TODO RETEST ZK6!!! + attendance_data = attendance_data[4:] + if record_size == 8: + while len(attendance_data) >= 8: uid, status, timestamp, punch = unpack('HB4sB', attendance_data.ljust(8, b'\x00')[:8]) if self.verbose: print (codecs.encode(attendance_data[:8], 'hex')) attendance_data = attendance_data[8:] tuser = list(filter(lambda x: x.uid == uid, users)) if not tuser: - user_id = str(uid) #TODO revisar pq + user_id = str(uid) else: user_id = tuser[0].user_id timestamp = self.__decode_time(timestamp) - attendance = Attendance(user_id, timestamp, status, punch, uid) # punch? + attendance = Attendance(user_id, timestamp, status, punch, uid) attendances.append(attendance) - elif record_size == 16: # extended - while len(attendance_data) >= 16: #TODO RETEST ZK6 + elif record_size == 16: + while len(attendance_data) >= 16: user_id, timestamp, status, punch, reserved, workcode = unpack(' [uid:{:>3}, fid:{}, size:{:>4} v:{} t:{}]".format(self.uid, self.fid, self.size, self.valid, codecs.encode(self.template, 'hex')) +