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 796dd1e..fe84925 100644 --- a/zk/base.py +++ b/zk/base.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- +from datetime import datetime 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.attendance import Attendance from zk.user import User class ZK(object): @@ -59,9 +62,16 @@ class ZK(object): 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) - 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] @@ -88,9 +98,43 @@ 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 + connect to the device ''' command = const.CMD_CONNECT @@ -107,11 +151,11 @@ 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): ''' - diconnect from connected device + diconnect from the connected device ''' command = const.CMD_EXIT @@ -125,11 +169,11 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") 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 @@ -143,11 +187,11 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def enable_device(self): ''' - re-enable connected device + re-enable the connected device ''' command = const.CMD_ENABLEDEVICE @@ -161,11 +205,11 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def get_firmware_version(self): ''' - get firmware version name + return the firmware version ''' command = const.CMD_GET_VERSION @@ -180,11 +224,29 @@ 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): + ''' + return the serial number + ''' + command = const.CMD_OPTIONS_RRQ + 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('=')[-1].strip('\x00|\x01\x10x') + return serialnumber + else: + raise ZKErrorResponse("Invalid response") def restart(self): ''' - restart connected device + restart the device ''' command = const.CMD_RESTART @@ -198,11 +260,11 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") - def power_off(self): + def poweroff(self): ''' - shutdown connected device + shutdown the device ''' command = const.CMD_POWEROFF @@ -216,7 +278,7 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") def test_voice(self): ''' @@ -234,7 +296,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=''): ''' @@ -258,11 +320,31 @@ class ZK(object): if cmd_response.get('status'): return True else: - raise Exception("Invalid response") + 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) + + 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 + return all user ''' command = const.CMD_USERTEMP_RRQ @@ -296,8 +378,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') @@ -311,7 +393,7 @@ class ZK(object): userdata = userdata[72:] else: - raise Exception("Invalid response") + raise ZKErrorResponse("Invalid response") return users @@ -345,23 +427,86 @@ 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'): + return True + else: + raise ZKErrorResponse("Invalid response") def get_attendance(self): ''' - Not implemented yet + return all attendance record ''' + 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: + 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") + + return attendances - pass 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") 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 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