From 77887d1e7ad6d521f76887902afd696d3692c90b Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Thu, 9 Jun 2016 11:20:39 +0700 Subject: [PATCH 01/10] add module to get serial number --- zk/base.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/zk/base.py b/zk/base.py index b0e0e8d..5edecef 100644 --- a/zk/base.py +++ b/zk/base.py @@ -182,6 +182,21 @@ class ZK(object): else: raise Exception("Invalid response") + def get_serialnumber(self): + command = 11 + command_string = '~SerialNumber' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + serialnumber = self.__data_recv[8:].split('=') + return serialnumber[-1] + else: + raise Exception("Invalid response") + def restart(self): ''' restart connected device From 577ade6c4b056777c8687c262dc01b76dbeadc6b Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Thu, 9 Jun 2016 13:37:19 +0700 Subject: [PATCH 02/10] fixing get_serialnumber method --- zk/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zk/base.py b/zk/base.py index 5edecef..1eab4c5 100644 --- a/zk/base.py +++ b/zk/base.py @@ -192,8 +192,8 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) if cmd_response.get('status'): - serialnumber = self.__data_recv[8:].split('=') - return serialnumber[-1] + serialnumber = self.__data_recv[8:].split('=')[-1].strip('\x00|\x01\x10x') + return serialnumber else: raise Exception("Invalid response") From 7b33ceeded925292b44ae18ec2f946546ab03f0b Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Fri, 10 Jun 2016 11:27:49 +0700 Subject: [PATCH 03/10] add function to clear data --- zk/base.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/zk/base.py b/zk/base.py index 1eab4c5..db7c4aa 100644 --- a/zk/base.py +++ b/zk/base.py @@ -183,7 +183,7 @@ class ZK(object): raise Exception("Invalid response") def get_serialnumber(self): - command = 11 + command = const.CMD_OPTIONS_RRQ command_string = '~SerialNumber' checksum = 0 session_id = self.__sesion_id @@ -360,12 +360,23 @@ class ZK(object): cmd_response = self.__send_command(command=command, command_string=command_string) print cmd_response - def clear_user(self): + def clear_data(self): ''' - Not implemented yet + clear all data (include: user, attendance report, finger database ) ''' + command = const.CMD_CLEAR_DATA + command_string = '' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 - pass + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + serialnumber = self.__data_recv[8:].split('=')[-1].strip('\x00|\x01\x10x') + return serialnumber + else: + raise Exception("Invalid response") def get_attendance(self): ''' From ef1944f4da32c5eae8e7e56023df15e661d72b5b Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Fri, 10 Jun 2016 14:16:22 +0700 Subject: [PATCH 04/10] typo --- zk/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zk/base.py b/zk/base.py index db7c4aa..87f9bd1 100644 --- a/zk/base.py +++ b/zk/base.py @@ -215,7 +215,7 @@ class ZK(object): else: raise Exception("Invalid response") - def power_off(self): + def poweroff(self): ''' shutdown connected device ''' From 21c685e8c5cfcf36673707d1fabb7cfb32b01162 Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Fri, 10 Jun 2016 15:35:55 +0700 Subject: [PATCH 05/10] update exception --- zk/base.py | 33 +++++++++++++++++++-------------- zk/exception.py | 8 ++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 zk/exception.py diff --git a/zk/base.py b/zk/base.py index 87f9bd1..1197fcf 100644 --- a/zk/base.py +++ b/zk/base.py @@ -3,6 +3,7 @@ from struct import pack, unpack from socket import socket, AF_INET, SOCK_DGRAM from zk import const +from zk.exception import ZKErrorResponse, ZKNetworkError from zk.user import User class ZK(object): @@ -60,8 +61,12 @@ class ZK(object): def __send_command(self, command, command_string, checksum, session_id, reply_id, response_size): buf = self.__create_header(command, command_string, checksum, session_id, reply_id) - self.__sock.sendto(buf, self.__address) - self.__data_recv = self.__sock.recv(response_size) + try: + self.__sock.sendto(buf, self.__address) + self.__data_recv = self.__sock.recv(response_size) + except Exception, e: + raise ZKNetworkError(str(e)) + self.__response = unpack('HHHH', self.__data_recv[:8])[0] self.__reply_id = unpack('HHHH', self.__data_recv[:8])[3] @@ -107,7 +112,7 @@ class ZK(object): self.__sesion_id = unpack('HHHH', self.__data_recv[:8])[2] return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def disconnect(self): ''' @@ -125,7 +130,7 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def disable_device(self): ''' @@ -143,7 +148,7 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def enable_device(self): ''' @@ -161,7 +166,7 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def get_firmware_version(self): ''' @@ -180,7 +185,7 @@ class ZK(object): firmware_version = self.__data_recv[8:].strip('\x00|\x01\x10x') return firmware_version else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def get_serialnumber(self): command = const.CMD_OPTIONS_RRQ @@ -195,7 +200,7 @@ class ZK(object): serialnumber = self.__data_recv[8:].split('=')[-1].strip('\x00|\x01\x10x') return serialnumber else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def restart(self): ''' @@ -213,7 +218,7 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def poweroff(self): ''' @@ -231,7 +236,7 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def test_voice(self): ''' @@ -249,7 +254,7 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def set_user(self, uid, name, privilege, password='', group_id='', user_id=''): ''' @@ -273,7 +278,7 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def get_users(self): ''' @@ -326,7 +331,7 @@ class ZK(object): userdata = userdata[72:] else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") return users @@ -376,7 +381,7 @@ class ZK(object): serialnumber = self.__data_recv[8:].split('=')[-1].strip('\x00|\x01\x10x') return serialnumber else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def get_attendance(self): ''' diff --git a/zk/exception.py b/zk/exception.py new file mode 100644 index 0000000..b6fd0ce --- /dev/null +++ b/zk/exception.py @@ -0,0 +1,8 @@ +class ZKError(Exception): + pass + +class ZKErrorResponse(ZKError): + pass + +class ZKNetworkError(ZKError): + pass From ac51194f574632da2f80ad43f9ee4d44b784b8ff Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Sat, 11 Jun 2016 13:58:49 +0700 Subject: [PATCH 06/10] add module delete user --- zk/base.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/zk/base.py b/zk/base.py index 1197fcf..a678d94 100644 --- a/zk/base.py +++ b/zk/base.py @@ -280,6 +280,23 @@ class ZK(object): else: raise ZKErrorResponse("Invalid response") + def delete_user(self, uid): + command = const.CMD_DELETE_USER + + uid = chr(uid % 256) + chr(uid >> 8) + + command_string = pack('2s', uid) + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + return True + else: + raise ZKErrorResponse("Invalid response") + def get_users(self): ''' get all users From 0bd1f62ac022f0e9003f8719727a5dd503a3ab0a Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Sun, 12 Jun 2016 15:46:20 +0700 Subject: [PATCH 07/10] add function to get attendance recorrd --- zk/base.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/zk/base.py b/zk/base.py index a678d94..7735230 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from datetime import datetime from struct import pack, unpack from socket import socket, AF_INET, SOCK_DGRAM @@ -93,6 +94,40 @@ class ZK(object): else: return 0 + def __reverse_hex(self, hex): + data = '' + for i in reversed( xrange( len(hex)/2 ) ): + data += hex[i*2:(i*2)+2] + return data + + def __decode_time(self, t): + """Decode a timestamp retrieved from the timeclock + + copied from zkemsdk.c - DecodeTime""" + t = t.encode('hex') + t = int(self.__reverse_hex(t), 16) + + second = t % 60 + t = t / 60 + + minute = t % 60 + t = t / 60 + + hour = t % 24 + t = t / 24 + + day = t % 31+1 + t = t / 31 + + month = t % 12+1 + t = t / 12 + + year = t + 2000 + + d = datetime(year, month, day, hour, minute, second) + + return d + def connect(self): ''' connect to device @@ -333,8 +368,8 @@ class ZK(object): userdata = userdata[11:] while len(userdata) >= 72: uid, privilege, password, name, sparator, group_id, user_id = unpack( '2s2s8s28sc7sx23s', userdata.ljust(72)[:72]) - u1 = int( uid[0].encode("hex"), 16) - u2 = int( uid[1].encode("hex"), 16) + u1 = int(uid[0].encode("hex"), 16) + u2 = int(uid[1].encode("hex"), 16) uid = u2 + (u1*256) name = unicode(name.strip('\x00|\x01\x10x'), errors='ignore') @@ -401,11 +436,46 @@ class ZK(object): raise ZKErrorResponse("Invalid response") def get_attendance(self): - ''' - Not implemented yet - ''' + command = const.CMD_ATTLOG_RRQ + command_string = '' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 + + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + attendances = [] + if cmd_response.get('status'): + if cmd_response.get('code') == const.CMD_PREPARE_DATA: + bytes = self.__get_data_size() + attendance_data = [] + while bytes > 0: + data_recv = self.__sock.recv(1032) + attendance_data.append(data_recv) + bytes -= 1024 + + data_recv = self.__sock.recv(8) + response = unpack('HHHH', data_recv[:8])[0] + if response == const.CMD_ACK_OK: + if attendance_data: + # The first 4 bytes don't seem to be related to the user + for x in xrange(len(attendance_data)): + if x > 0: + attendance_data[x] = attendance_data[x][8:] + + attendance_data = ''.join(attendance_data) + attendance_data = attendance_data[14:] + while len(attendance_data) >= 38: + uid, sparator, timestamp, status, space = unpack( '24sc4sc10s', attendance_data.ljust(40)[:40]) + uid = uid.strip('\x00|\x01\x10x') + timestamp = self.__decode_time(timestamp) + status = int(status.encode("hex"), 16) + attendance_data = attendance_data[40:] + else: + raise ZKErrorResponse("Invalid response") + + return attendances - pass def clear_attendance(self): ''' From 4662212bdcd1a6b7ba7d35a4797d562dcbb07f42 Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Sun, 12 Jun 2016 16:35:02 +0700 Subject: [PATCH 08/10] add function to get attendance record --- zk/attendance.py | 12 ++++++++++++ zk/base.py | 10 ++++++++-- zk/user.py | 5 ++++- 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 zk/attendance.py diff --git a/zk/attendance.py b/zk/attendance.py new file mode 100644 index 0000000..b609383 --- /dev/null +++ b/zk/attendance.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +class Attendance(object): + def __init__(self, user_id, timestamp, status): + self.user_id = user_id + self.timestamp = timestamp + self.status = status + + def __str__(self): + return self.user_id + + def __repr__(self): + return ': {}'.format(self.user_id) diff --git a/zk/base.py b/zk/base.py index 7735230..62637ef 100644 --- a/zk/base.py +++ b/zk/base.py @@ -5,6 +5,7 @@ from socket import socket, AF_INET, SOCK_DGRAM from zk import const from zk.exception import ZKErrorResponse, ZKNetworkError +from zk.attendance import Attendance from zk.user import User class ZK(object): @@ -466,10 +467,15 @@ class ZK(object): attendance_data = ''.join(attendance_data) attendance_data = attendance_data[14:] while len(attendance_data) >= 38: - uid, sparator, timestamp, status, space = unpack( '24sc4sc10s', attendance_data.ljust(40)[:40]) - uid = uid.strip('\x00|\x01\x10x') + user_id, sparator, timestamp, status, space = unpack( '24sc4sc10s', attendance_data.ljust(40)[:40]) + + user_id = user_id.strip('\x00|\x01\x10x') timestamp = self.__decode_time(timestamp) status = int(status.encode("hex"), 16) + + attendance = Attendance(user_id, timestamp, status) + attendances.append(attendance) + attendance_data = attendance_data[40:] else: raise ZKErrorResponse("Invalid response") diff --git a/zk/user.py b/zk/user.py index db01650..66a5e6d 100644 --- a/zk/user.py +++ b/zk/user.py @@ -9,5 +9,8 @@ class User(object): self.group_id = group_id self.user_id = user_id - def __str__(self): + def __repr__(self): return self.name + + def __repr__(self): + return ': {}'.format(self.name) \ No newline at end of file From c9a3d712f265d222b1b97474aace5f14cd12757d Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Wed, 15 Jun 2016 19:15:12 +0700 Subject: [PATCH 09/10] add method to clear attendance --- zk/base.py | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/zk/base.py b/zk/base.py index 62637ef..301156b 100644 --- a/zk/base.py +++ b/zk/base.py @@ -25,7 +25,7 @@ class ZK(object): ''' Puts a the parts that make up a packet together and packs them into a byte string ''' - buf = pack('HHHH', command, checksum, session_id, reply_id) + command_string + buf = pack('HHHH', command, checksum, session_id, reply_id) + command_string buf = unpack('8B'+'%sB' % len(command_string), buf) checksum = unpack('H', self.__create_checksum(buf))[0] reply_id += 1 @@ -50,18 +50,21 @@ class ZK(object): l -= 2 if l: checksum = checksum + p[-1] - + while checksum > const.USHRT_MAX: checksum -= const.USHRT_MAX - + checksum = ~checksum - + while checksum < 0: checksum += const.USHRT_MAX return pack('H', checksum) def __send_command(self, command, command_string, checksum, session_id, reply_id, response_size): + ''' + send command to the terminal + ''' buf = self.__create_header(command, command_string, checksum, session_id, reply_id) try: self.__sock.sendto(buf, self.__address) @@ -131,7 +134,7 @@ class ZK(object): def connect(self): ''' - connect to device + connect to the device ''' command = const.CMD_CONNECT @@ -152,7 +155,7 @@ class ZK(object): def disconnect(self): ''' - diconnect from connected device + diconnect from the connected device ''' command = const.CMD_EXIT @@ -170,7 +173,7 @@ class ZK(object): def disable_device(self): ''' - disable (lock) connected device, make sure no activity when process run + disable (lock) device, ensure no activity when process run ''' command = const.CMD_DISABLEDEVICE @@ -188,7 +191,7 @@ class ZK(object): def enable_device(self): ''' - re-enable connected device + re-enable the connected device ''' command = const.CMD_ENABLEDEVICE @@ -206,7 +209,7 @@ class ZK(object): def get_firmware_version(self): ''' - get firmware version name + return the firmware version ''' command = const.CMD_GET_VERSION @@ -224,6 +227,9 @@ class ZK(object): raise ZKErrorResponse("Invalid response") def get_serialnumber(self): + ''' + return the serial number + ''' command = const.CMD_OPTIONS_RRQ command_string = '~SerialNumber' checksum = 0 @@ -240,7 +246,7 @@ class ZK(object): def restart(self): ''' - restart connected device + restart the device ''' command = const.CMD_RESTART @@ -258,7 +264,7 @@ class ZK(object): def poweroff(self): ''' - shutdown connected device + shutdown the device ''' command = const.CMD_POWEROFF @@ -317,6 +323,9 @@ class ZK(object): raise ZKErrorResponse("Invalid response") def delete_user(self, uid): + ''' + delete specific user by uid + ''' command = const.CMD_DELETE_USER uid = chr(uid % 256) + chr(uid >> 8) @@ -335,7 +344,7 @@ class ZK(object): def get_users(self): ''' - get all users + return all user ''' command = const.CMD_USERTEMP_RRQ @@ -437,6 +446,9 @@ class ZK(object): raise ZKErrorResponse("Invalid response") def get_attendance(self): + ''' + return all attendance record + ''' command = const.CMD_ATTLOG_RRQ command_string = '' checksum = 0 @@ -485,7 +497,17 @@ class ZK(object): def clear_attendance(self): ''' - Not implemented yet + clear all attendance record ''' + command = const.CMD_CLEAR_ATTLOG + command_string = '' + checksum = 0 + session_id = self.__sesion_id + reply_id = self.__reply_id + response_size = 1024 - pass + cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) + if cmd_response.get('status'): + return True + else: + raise ZKErrorResponse("Invalid response") From 410ef01cbb5b96a0ab7b5ef8535a9f0dcd794b73 Mon Sep 17 00:00:00 2001 From: "Fanani M. Ihsan" Date: Wed, 15 Jun 2016 19:41:59 +0700 Subject: [PATCH 10/10] fixing method clear_data --- zk/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zk/base.py b/zk/base.py index 301156b..fe84925 100644 --- a/zk/base.py +++ b/zk/base.py @@ -440,8 +440,7 @@ class ZK(object): cmd_response = self.__send_command(command, command_string, checksum, session_id, reply_id, response_size) if cmd_response.get('status'): - serialnumber = self.__data_recv[8:].split('=')[-1].strip('\x00|\x01\x10x') - return serialnumber + return True else: raise ZKErrorResponse("Invalid response")