├── src ├── Assets │ └── header.png ├── Lib │ ├── Helper │ │ ├── Pin.php │ │ ├── Ssr.php │ │ ├── Os.php │ │ ├── SerialNumber.php │ │ ├── Version.php │ │ ├── WorkCode.php │ │ ├── Platform.php │ │ ├── Time.php │ │ ├── EventMonitor.php │ │ ├── Attendance.php │ │ ├── Connect.php │ │ ├── Face.php │ │ ├── User.php │ │ ├── Fingerprint.php │ │ ├── Util.php │ │ └── Device.php │ └── ZKTeco.php └── Providers │ └── ZktecoServiceProvider.php ├── .github └── workflows ├── phpunit.xml ├── LICENSE ├── composer.json ├── examples ├── enroll_template_test.php ├── README.md └── AdvancedDeviceManagementExample.php ├── CONTRIBUTING.md ├── CHANGELOG.md ├── tests ├── EnhancedUserManagementTest.php └── AdvancedDeviceManagementTest.php └── README.md /src/Assets/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmrashed/zkteco/HEAD/src/Assets/header.png -------------------------------------------------------------------------------- /.github/workflows: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Set up PHP 15 | uses: shivammathur/setup-php@v2 16 | with: 17 | php-version: '8.0' # adjust to your PHP version 18 | - name: Install dependencies 19 | run: composer install 20 | - name: Run tests 21 | run: vendor/bin/phpunit 22 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests 10 | 11 | 12 | 13 | 14 | src 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Lib/Helper/Pin.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 18 | 19 | $command = Util::CMD_DEVICE; 20 | $command_string = '~PIN2Width'; 21 | 22 | return $self->_command($command, $command_string); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Lib/Helper/Ssr.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 18 | 19 | $command = Util::CMD_DEVICE; 20 | $command_string = '~SSR'; 21 | 22 | return $self->_command($command, $command_string); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Lib/Helper/Os.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 18 | 19 | $command = Util::CMD_DEVICE; 20 | $command_string = '~OS'; 21 | 22 | return $self->_command($command, $command_string); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Lib/Helper/SerialNumber.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 18 | 19 | $command = Util::CMD_DEVICE; 20 | $command_string = '~SerialNumber'; 21 | 22 | return $self->_command($command, $command_string); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Lib/Helper/Version.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; // Set the current section for internal tracking (optional) 22 | 23 | $command = Util::CMD_VERSION; // Version information command code 24 | $command_string = ''; // Empty command string (no additional data needed) 25 | 26 | return $self->_command($command, $command_string); // Use internal ZKTeco method to send the command 27 | } 28 | } -------------------------------------------------------------------------------- /src/Lib/Helper/WorkCode.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; // Set the current section for internal tracking (optional) 22 | 23 | $command = Util::CMD_DEVICE; // Device information command code 24 | $command_string = 'WorkCode'; // Specific data request: Work Code information 25 | 26 | return $self->_command($command, $command_string); // Use internal ZKTeco method to send the command 27 | } 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Jmrashed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jmrashed/zkteco", 3 | "version": "1.3.0", 4 | "description": "ZKTeco Package For Laravel. This package provides seamless integration with ZKTeco devices within Laravel applications, enabling communication with attendance devices such as fingerprint, face recognition, or RFID using UDP protocol.", 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Md Rasheduzzaman", 10 | "email": "jmrashed@gmail.com" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4":{ 15 | "Jmrashed\\Zkteco\\": "src" 16 | } 17 | }, 18 | "autoload-dev": { 19 | "psr-4": { 20 | "Tests\\": "tests/" 21 | } 22 | }, 23 | "minimum-stability": "dev", 24 | "require": { 25 | "php": "^8.0" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^10.0", 29 | "orchestra/testbench": "^6.0" 30 | }, 31 | "provides": { 32 | "jmrashed/zkteco": "1.3.0" 33 | }, 34 | "scripts": { 35 | "test": "phpunit", 36 | "test-coverage": "phpunit --coverage-html coverage" 37 | }, 38 | "suggests": { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Lib/Helper/Platform.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 18 | 19 | $command = Util::CMD_DEVICE; 20 | $command_string = '~Platform'; 21 | 22 | return $self->_command($command, $command_string); 23 | } 24 | 25 | /** 26 | * Get the version of the platform on the ZKTeco device. 27 | * 28 | * @param ZKTeco $self The instance of the ZKTeco class. 29 | * @return bool|mixed Returns the platform version if successful, false otherwise. 30 | */ 31 | static public function getVersion(ZKTeco $self) 32 | { 33 | $self->_section = __METHOD__; 34 | 35 | $command = Util::CMD_DEVICE; 36 | $command_string = '~ZKFPVersion'; 37 | 38 | return $self->_command($command, $command_string); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Lib/Helper/Time.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 19 | 20 | $command = Util::CMD_SET_TIME; 21 | $command_string = pack('I', Util::encodeTime($t)); 22 | 23 | return $self->_command($command, $command_string); 24 | } 25 | 26 | /** 27 | * Get the current time from the ZKTeco device. 28 | * 29 | * @param ZKTeco $self The instance of the ZKTeco class. 30 | * @return bool|mixed Returns the current time if successful, false otherwise. 31 | */ 32 | static public function get(ZKTeco $self) 33 | { 34 | $self->_section = __METHOD__; 35 | 36 | $command = Util::CMD_GET_TIME; 37 | $command_string = ''; 38 | 39 | $ret = $self->_command($command, $command_string); 40 | 41 | if ($ret) { 42 | return Util::decodeTime(hexdec(Util::reverseHex(bin2hex($ret)))); 43 | } else { 44 | return false; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/enroll_template_test.php: -------------------------------------------------------------------------------- 1 | \n"; 10 | echo "Example: php enroll_template_test.php 192.168.200.211 8888 0 finger_template.bin\n"; 11 | exit(1); 12 | } 13 | 14 | $ip = $argv[1]; 15 | $uid = (int)$argv[2]; 16 | $fingerId = (int)$argv[3]; 17 | $templateFile = $argv[4]; 18 | 19 | if (!file_exists($templateFile)) { 20 | echo "Template file not found: $templateFile\n"; 21 | exit(1); 22 | } 23 | 24 | $templateData = file_get_contents($templateFile); 25 | if ($templateData === false || strlen($templateData) === 0) { 26 | echo "Failed to read template file or file is empty.\n"; 27 | exit(1); 28 | } 29 | 30 | echo "Connecting to device $ip...\n"; 31 | $zk = new ZKTeco($ip); 32 | if (! $zk->connect()) { 33 | echo "Cannot connect to device at $ip\n"; 34 | exit(1); 35 | } 36 | 37 | echo "Disabling device to upload template...\n"; 38 | $zk->disableDevice(); 39 | 40 | echo "Enrolling fingerprint for UID=$uid fingerId=$fingerId...\n"; 41 | $ok = $zk->enrollFingerprint($uid, $fingerId, $templateData); 42 | 43 | $zk->enableDevice(); 44 | $zk->disconnect(); 45 | 46 | if ($ok) { 47 | echo "Success: fingerprint enrolled for UID={$uid} finger={$fingerId}\n"; 48 | exit(0); 49 | } else { 50 | echo "Failed to enroll fingerprint.\n"; 51 | exit(2); 52 | } 53 | -------------------------------------------------------------------------------- /src/Providers/ZktecoServiceProvider.php: -------------------------------------------------------------------------------- 1 | zk = $zk; 16 | } 17 | 18 | /** 19 | * Register event handler for specific event type. 20 | * 21 | * @param string $eventType Event type name. 22 | * @param callable $handler Event handler callback. 23 | */ 24 | public function on($eventType, callable $handler) 25 | { 26 | if (!isset($this->eventHandlers[$eventType])) { 27 | $this->eventHandlers[$eventType] = []; 28 | } 29 | 30 | $this->eventHandlers[$eventType][] = $handler; 31 | } 32 | 33 | /** 34 | * Start monitoring events. 35 | * 36 | * @param int $timeout Monitoring timeout in seconds (0 = infinite). 37 | * @return bool Success status. 38 | */ 39 | public function start($timeout = 0) 40 | { 41 | if ($this->isMonitoring) { 42 | return false; 43 | } 44 | 45 | $this->isMonitoring = true; 46 | 47 | return $this->zk->startEventMonitoring( 48 | [$this, 'handleEvent'], 49 | $timeout 50 | ); 51 | } 52 | 53 | /** 54 | * Stop monitoring events. 55 | * 56 | * @return bool Success status. 57 | */ 58 | public function stop() 59 | { 60 | if (!$this->isMonitoring) { 61 | return false; 62 | } 63 | 64 | $this->isMonitoring = false; 65 | 66 | return $this->zk->stopEventMonitoring(); 67 | } 68 | 69 | /** 70 | * Handle incoming event. 71 | * 72 | * @param array $event Event data. 73 | */ 74 | public function handleEvent($event) 75 | { 76 | $eventType = $event['type']; 77 | 78 | if (isset($this->eventHandlers[$eventType])) { 79 | foreach ($this->eventHandlers[$eventType] as $handler) { 80 | call_user_func($handler, $event); 81 | } 82 | } 83 | 84 | // Call generic handlers 85 | if (isset($this->eventHandlers['*'])) { 86 | foreach ($this->eventHandlers['*'] as $handler) { 87 | call_user_func($handler, $event); 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * Check if monitoring is active. 94 | * 95 | * @return bool Monitoring status. 96 | */ 97 | public function isMonitoring() 98 | { 99 | return $this->isMonitoring; 100 | } 101 | 102 | /** 103 | * Get registered event handlers. 104 | * 105 | * @return array Event handlers. 106 | */ 107 | public function getEventHandlers() 108 | { 109 | return $this->eventHandlers; 110 | } 111 | 112 | /** 113 | * Clear all event handlers. 114 | */ 115 | public function clearHandlers() 116 | { 117 | $this->eventHandlers = []; 118 | } 119 | 120 | /** 121 | * Remove event handler for specific event type. 122 | * 123 | * @param string $eventType Event type name. 124 | */ 125 | public function off($eventType) 126 | { 127 | unset($this->eventHandlers[$eventType]); 128 | } 129 | } -------------------------------------------------------------------------------- /src/Lib/Helper/Attendance.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; // Set the current section for internal tracking (optional) 25 | 26 | $command = Util::CMD_ATT_LOG_RRQ; // Attendance log read request command 27 | $command_string = ''; // Empty command string (no additional data needed) 28 | 29 | $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); 30 | if ($session === false) { 31 | return []; // Return empty array if session fails 32 | } 33 | 34 | $attData = Util::recData($self); // Read attendance data from the device 35 | 36 | $attendance = []; 37 | if (!empty($attData)) { 38 | $attData = substr($attData, 10); // Skip the first 10 bytes of data 39 | 40 | while (strlen($attData) > 40) { // Loop through each attendance record in the data 41 | $u = unpack('H78', substr($attData, 0, 39)); // Unpack 78 bytes of data as hexadecimal string 42 | 43 | $u1 = hexdec(substr($u[1], 4, 2)); // Extract first byte of user ID (low order) 44 | $u2 = hexdec(substr($u[1], 6, 2)); // Extract second byte of user ID (high order) 45 | $uid = $u1 + ($u2 * 256); // Combine user ID bytes 46 | 47 | $id = hex2bin(substr($u[1], 8, 18)); // Extract badge ID as binary string 48 | $id = str_replace(chr(0), '', $id); // Remove null bytes from badge ID 49 | 50 | $state = hexdec(substr($u[1], 56, 2)); // Extract attendance state 51 | 52 | $timestamp = Util::decodeTime(hexdec(Util::reverseHex(substr($u[1], 58, 8)))); // Decode timestamp from hex 53 | 54 | $type = hexdec(Util::reverseHex(substr($u[1], 66, 2))); // Extract attendance type 55 | 56 | $attendance[] = [ // Add record to the attendance array 57 | 'uid' => $uid, 58 | 'id' => $id, 59 | 'state' => $state, 60 | 'timestamp' => $timestamp, 61 | 'type' => $type 62 | ]; 63 | 64 | $attData = substr($attData, 40); // Move to the next attendance record data 65 | } 66 | } 67 | 68 | return $attendance; // Return the parsed attendance data 69 | } 70 | 71 | /** 72 | * Clears attendance data from the ZKTeco device. 73 | * 74 | * This method sends a command to the ZKTeco device to clear all stored attendance records. 75 | * 76 | * @param ZKTeco $self An instance of the ZKTeco class. 77 | * @return bool|mixed True on success, error message on failure. 78 | */ 79 | static public function clear(ZKTeco $self) 80 | { 81 | $self->_section = __METHOD__; // Set the current section for internal tracking (optional) 82 | 83 | $command = Util::CMD_CLEAR_ATT_LOG; // Clear attendance log command 84 | $command_string = ''; // Empty command string (no additional data needed) 85 | 86 | return $self->_command($command, $command_string); // Send the clear command 87 | } 88 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | All notable changes to `jmrashed/zkteco` will be documented in this file 3 | 4 | ## Version 1.3.0 at 5 October 2025 5 | ### What's Changed 6 | #### Bug Fixes 7 | - **FIXED**: enrollFingerprint method now properly handles raw template data from getFingerprint() 8 | - **FIXED**: Resolved "unpack(): Type H: not enough input" error when transferring fingerprint templates between devices 9 | - **FIXED**: Template data corruption issue caused by double header addition 10 | - **IMPROVED**: Enhanced template data validation and header detection 11 | - **IMPROVED**: Better error handling for fingerprint enrollment operations 12 | 13 | #### Technical Improvements 14 | - Enhanced Fingerprint::enroll() method with intelligent header detection 15 | - Added validation for existing template headers before processing 16 | - Improved compatibility for transferring templates between different ZKTeco devices 17 | - Better handling of both raw and parsed fingerprint template data 18 | 19 | ## Version 1.2.0 at 1 October 2025 20 | ### What's Changed 21 | #### Advanced Device Management Features 22 | - **NEW**: Real-time Event Monitoring - Monitor device events like attendance punches, door events, and system alerts 23 | - **NEW**: Door Control Functions - Remotely open, close, lock, and unlock doors with status monitoring 24 | - **NEW**: Custom LCD Message Display - Send custom formatted messages to device LCD screen with duration control 25 | - **NEW**: Time Zone Synchronization - Automatically sync device time with server timezone 26 | - **NEW**: Event Handler System - Advanced event handling with callback registration and filtering 27 | - **NEW**: Door Status Monitoring - Real-time door status including lock state, sensor status, and alarms 28 | - **NEW**: Event Monitor Class - Dedicated class for managing real-time event subscriptions 29 | - **IMPROVED**: Enhanced device communication with better error handling 30 | - **ADDED**: Comprehensive test suite for device management features 31 | 32 | #### Technical Improvements 33 | - Enhanced Device helper class with advanced control functions 34 | - Added EventMonitor helper class for event management 35 | - New constants for door actions and event types 36 | - Improved real-time communication protocols 37 | - Enhanced LCD message formatting and duration control 38 | - Production-ready event polling and parsing 39 | 40 | ## Version 1.1.0 at 1 October 2025 41 | ### What's Changed 42 | #### Enhanced User Management Features 43 | - **NEW**: Parse and Set Fingerprint Data - Enhanced fingerprint template parsing with quality assessment 44 | - **NEW**: Get/Set User Face Data - Complete face recognition template management 45 | - **NEW**: Retrieve User Card Number - Dedicated method to fetch user card numbers 46 | - **NEW**: Advanced User Role Management - Granular role control with permission system 47 | - **NEW**: Fingerprint Template Enrollment - Production-ready fingerprint enrollment system 48 | - **NEW**: Face Template Enrollment - Face recognition template enrollment capabilities 49 | - **NEW**: Quality Score Calculation - Template quality assessment for both fingerprint and face data 50 | - **NEW**: Role Permission System - Comprehensive permission management for different user roles 51 | - **IMPROVED**: Enhanced error handling and validation 52 | - **ADDED**: Comprehensive test suite for all new features 53 | - **ADDED**: PHPUnit configuration for automated testing 54 | 55 | #### Technical Improvements 56 | - Enhanced Fingerprint helper class with template parsing and enrollment 57 | - Enhanced Face helper class with complete template management 58 | - Enhanced User helper class with advanced role and card management 59 | - Added quality assessment algorithms for biometric templates 60 | - Improved code documentation and type hints 61 | - Production-ready error handling and validation 62 | 63 | ## Version 1.0.0 at 22 April 2024 64 | ### What's Changed 65 | - Initial Release V1.0.0 66 | 67 | -------------------------------------------------------------------------------- /src/Lib/Helper/Connect.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 22 | 23 | // Define command and other necessary variables. 24 | $command = Util::CMD_CONNECT; 25 | $command_string = ''; 26 | $chksum = 0; 27 | $session_id = 0; 28 | $reply_id = -1 + Util::USHRT_MAX; 29 | 30 | // Create the header for the command. 31 | $buf = Util::createHeader($command, $chksum, $session_id, $reply_id, $command_string); 32 | 33 | // Send the command to the ZKTeco device. 34 | socket_sendto($self->_zkclient, $buf, strlen($buf), 0, $self->_ip, $self->_port); 35 | 36 | try { 37 | // Attempt to receive data from the device. 38 | @socket_recvfrom($self->_zkclient, $self->_data_recv, 1024, 0, $self->_ip, $self->_port); 39 | 40 | // If data is received, process it. 41 | if (strlen($self->_data_recv) > 0) { 42 | // Unpack the received data to extract session ID. 43 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6', substr($self->_data_recv, 0, 8)); 44 | 45 | // Convert session ID from hex to decimal. 46 | $session = hexdec($u['h6'] . $u['h5']); 47 | 48 | // If session ID is empty, return false. 49 | if (empty($session)) { 50 | return false; 51 | } 52 | 53 | // Set the session ID in the ZKTeco instance. 54 | $self->_session_id = $session; 55 | 56 | // Check if the received data is valid. 57 | return Util::checkValid($self->_data_recv); 58 | } else { 59 | // If no data is received, return false. 60 | return false; 61 | } 62 | } catch (ErrorException $e) { 63 | // Catch any error exceptions and return false. 64 | return false; 65 | } catch (Exception $e) { 66 | // Catch any general exceptions and return false. 67 | return false; 68 | } 69 | } 70 | 71 | /** 72 | * Disconnects from the ZKTeco device. 73 | * 74 | * @param ZKTeco $self The instance of the ZKTeco class. 75 | * @return bool Returns true if the disconnection is successful, false otherwise. 76 | */ 77 | static public function disconnect(ZKTeco $self) 78 | { 79 | // Set the current section of the code. 80 | $self->_section = __METHOD__; 81 | 82 | // Define command and other necessary variables. 83 | $command = Util::CMD_EXIT; 84 | $command_string = ''; 85 | $chksum = 0; 86 | $session_id = $self->_session_id; 87 | 88 | // Unpack the data received during connection to extract reply ID. 89 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6/H2h7/H2h8', substr($self->_data_recv, 0, 8)); 90 | $reply_id = hexdec($u['h8'] . $u['h7']); 91 | 92 | // Create the header for the command. 93 | $buf = Util::createHeader($command, $chksum, $session_id, $reply_id, $command_string); 94 | 95 | // Send the command to the ZKTeco device. 96 | socket_sendto($self->_zkclient, $buf, strlen($buf), 0, $self->_ip, $self->_port); 97 | 98 | try { 99 | // Attempt to receive data from the device. 100 | @socket_recvfrom($self->_zkclient, $self->_data_recv, 1024, 0, $self->_ip, $self->_port); 101 | 102 | // Reset the session ID in the ZKTeco instance. 103 | $self->_session_id = 0; 104 | 105 | // Check if the received data is valid. 106 | return Util::checkValid($self->_data_recv); 107 | } catch (ErrorException $e) { 108 | // Catch any error exceptions and return false. 109 | return false; 110 | } catch (Exception $e) { 111 | // Catch any general exceptions and return false. 112 | return false; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/EnhancedUserManagementTest.php: -------------------------------------------------------------------------------- 1 | zk = $this->createMock(ZKTeco::class); 17 | } 18 | 19 | public function testParseFingerprintTemplate() 20 | { 21 | $rawData = chr(100) . chr(0) . chr(123) . chr(0) . chr(1) . chr(1) . str_repeat('A', 100); 22 | 23 | $result = $this->zk->parseFingerprintTemplate($rawData); 24 | 25 | $this->assertTrue($result['valid']); 26 | $this->assertEquals(100, $result['template_size']); 27 | $this->assertEquals(123, $result['uid']); 28 | $this->assertEquals(1, $result['finger_id']); 29 | $this->assertGreaterThan(0, $result['quality_score']); 30 | } 31 | 32 | public function testEnrollFingerprint() 33 | { 34 | $this->zk->method('_command')->willReturn(true); 35 | 36 | $result = $this->zk->enrollFingerprint($this->testUid, 1, 'template_data'); 37 | 38 | $this->assertTrue($result); 39 | } 40 | 41 | public function testGetFaceData() 42 | { 43 | $this->zk->method('_command')->willReturn(true); 44 | 45 | $result = $this->zk->getFaceData($this->testUid); 46 | 47 | $this->assertIsArray($result); 48 | } 49 | 50 | public function testSetFaceData() 51 | { 52 | $faceData = [ 53 | 50 => ['template' => 'face_template_data'] 54 | ]; 55 | 56 | $this->zk->method('_command')->willReturn(true); 57 | 58 | $result = $this->zk->setFaceData($this->testUid, $faceData); 59 | 60 | $this->assertTrue($result); 61 | } 62 | 63 | public function testEnrollFaceTemplate() 64 | { 65 | $this->zk->method('_command')->willReturn(true); 66 | 67 | $result = $this->zk->enrollFaceTemplate($this->testUid, 'face_template_data'); 68 | 69 | $this->assertTrue($result); 70 | } 71 | 72 | public function testGetUserCardNumber() 73 | { 74 | $this->zk->method('getUser')->willReturn([ 75 | 'user1' => [ 76 | 'uid' => $this->testUid, 77 | 'cardno' => '1234567890' 78 | ] 79 | ]); 80 | 81 | $result = $this->zk->getUserCardNumber($this->testUid); 82 | 83 | $this->assertEquals('1234567890', $result); 84 | } 85 | 86 | public function testSetUserRole() 87 | { 88 | $this->zk->method('getUser')->willReturn([ 89 | 'user1' => [ 90 | 'uid' => $this->testUid, 91 | 'userid' => 'user1', 92 | 'name' => 'Test User', 93 | 'password' => '1234', 94 | 'cardno' => '1234567890' 95 | ] 96 | ]); 97 | 98 | $this->zk->method('setUser')->willReturn(true); 99 | 100 | $result = $this->zk->setUserRole($this->testUid, Util::LEVEL_ADMIN); 101 | 102 | $this->assertTrue($result); 103 | } 104 | 105 | public function testGetUserRole() 106 | { 107 | $this->zk->method('getUser')->willReturn([ 108 | 'user1' => [ 109 | 'uid' => $this->testUid, 110 | 'role' => Util::LEVEL_ADMIN 111 | ] 112 | ]); 113 | 114 | $result = $this->zk->getUserRole($this->testUid); 115 | 116 | $this->assertIsArray($result); 117 | $this->assertEquals(Util::LEVEL_ADMIN, $result['role_id']); 118 | $this->assertEquals('Admin', $result['role_name']); 119 | $this->assertTrue($result['can_enroll']); 120 | $this->assertTrue($result['can_manage_users']); 121 | } 122 | 123 | public function testGetAvailableRoles() 124 | { 125 | $result = $this->zk->getAvailableRoles(); 126 | 127 | $this->assertIsArray($result); 128 | $this->assertArrayHasKey(Util::LEVEL_USER, $result); 129 | $this->assertArrayHasKey(Util::LEVEL_ADMIN, $result); 130 | $this->assertEquals('User', $result[Util::LEVEL_USER]['name']); 131 | $this->assertEquals('Administrator', $result[Util::LEVEL_ADMIN]['name']); 132 | } 133 | 134 | public function testInvalidFingerprintTemplate() 135 | { 136 | $result = $this->zk->parseFingerprintTemplate('invalid'); 137 | 138 | $this->assertFalse($result['valid']); 139 | $this->assertArrayHasKey('error', $result); 140 | } 141 | 142 | public function testEnrollFingerprintInvalidFinger() 143 | { 144 | $result = $this->zk->enrollFingerprint($this->testUid, 15, 'template_data'); 145 | 146 | $this->assertFalse($result); 147 | } 148 | 149 | public function testGetCardNumberUserNotFound() 150 | { 151 | $this->zk->method('getUser')->willReturn([]); 152 | 153 | $result = $this->zk->getUserCardNumber(99999); 154 | 155 | $this->assertFalse($result); 156 | } 157 | 158 | public function testSetRoleUserNotFound() 159 | { 160 | $this->zk->method('getUser')->willReturn([]); 161 | 162 | $result = $this->zk->setUserRole(99999, Util::LEVEL_ADMIN); 163 | 164 | $this->assertFalse($result); 165 | } 166 | } -------------------------------------------------------------------------------- /src/Lib/Helper/Face.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 21 | 22 | $command = Util::CMD_DEVICE; 23 | $command_string = 'FaceFunOn'; 24 | 25 | return $self->_command($command, $command_string); 26 | } 27 | 28 | /** 29 | * Retrieve face template data for a specific user. 30 | * 31 | * @param ZKTeco $self ZKTeco instance. 32 | * @param int $uid User ID. 33 | * @return array Face template data. 34 | */ 35 | static public function getData(ZKTeco $self, $uid) 36 | { 37 | $self->_section = __METHOD__; 38 | 39 | $data = []; 40 | 41 | // Face templates are typically stored with IDs 50-54 42 | for ($i = 0; $i < self::MAX_FACE_TEMPLATES; $i++) { 43 | $faceId = 50 + $i; 44 | $template = self::_getFaceTemplate($self, $uid, $faceId); 45 | 46 | if ($template['size'] > 0) { 47 | $data[$faceId] = [ 48 | 'template' => $template['data'], 49 | 'size' => $template['size'], 50 | 'quality' => self::_calculateQuality($template['data']) 51 | ]; 52 | } 53 | } 54 | 55 | return $data; 56 | } 57 | 58 | /** 59 | * Set face template data for a specific user. 60 | * 61 | * @param ZKTeco $self ZKTeco instance. 62 | * @param int $uid User ID. 63 | * @param array $faceData Face template data. 64 | * @return bool Success status. 65 | */ 66 | static public function setData(ZKTeco $self, $uid, array $faceData) 67 | { 68 | $self->_section = __METHOD__; 69 | 70 | $success = true; 71 | 72 | foreach ($faceData as $faceId => $data) { 73 | if (!self::_setFaceTemplate($self, $uid, $faceId, $data['template'])) { 74 | $success = false; 75 | } 76 | } 77 | 78 | return $success; 79 | } 80 | 81 | /** 82 | * Enroll a face recognition template for a user. 83 | * 84 | * @param ZKTeco $self ZKTeco instance. 85 | * @param int $uid User ID. 86 | * @param string $templateData Face template data. 87 | * @return bool Success status. 88 | */ 89 | static public function enrollTemplate(ZKTeco $self, $uid, $templateData) 90 | { 91 | $self->_section = __METHOD__; 92 | 93 | // Find next available face template slot 94 | $faceId = self::_findAvailableSlot($self, $uid); 95 | 96 | if ($faceId === false) { 97 | return false; // No available slots 98 | } 99 | 100 | return self::_setFaceTemplate($self, $uid, $faceId, $templateData); 101 | } 102 | 103 | /** 104 | * Get face template from device. 105 | * 106 | * @param ZKTeco $self ZKTeco instance. 107 | * @param int $uid User ID. 108 | * @param int $faceId Face template ID. 109 | * @return array Template data and size. 110 | */ 111 | private static function _getFaceTemplate(ZKTeco $self, $uid, $faceId) 112 | { 113 | $command = Util::CMD_USER_TEMP_RRQ; 114 | $byte1 = chr($uid % 256); 115 | $byte2 = chr($uid >> 8); 116 | $command_string = $byte1 . $byte2 . chr($faceId); 117 | 118 | $ret = ['size' => 0, 'data' => '']; 119 | 120 | $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); 121 | if ($session === false) { 122 | return $ret; 123 | } 124 | 125 | $data = Util::recData($self, 10, false); 126 | 127 | if (!empty($data)) { 128 | $ret['size'] = strlen($data); 129 | $ret['data'] = $data; 130 | } 131 | 132 | return $ret; 133 | } 134 | 135 | /** 136 | * Set face template on device. 137 | * 138 | * @param ZKTeco $self ZKTeco instance. 139 | * @param int $uid User ID. 140 | * @param int $faceId Face template ID. 141 | * @param string $templateData Template data. 142 | * @return bool Success status. 143 | */ 144 | private static function _setFaceTemplate(ZKTeco $self, $uid, $faceId, $templateData) 145 | { 146 | $command = Util::CMD_USER_TEMP_WRQ; 147 | 148 | $templateSize = strlen($templateData); 149 | $byte1 = chr($uid % 256); 150 | $byte2 = chr($uid >> 8); 151 | 152 | $prefix = chr($templateSize % 256) . chr($templateSize >> 8) . 153 | $byte1 . $byte2 . chr($faceId) . chr(1); 154 | 155 | $command_string = $prefix . $templateData; 156 | 157 | return $self->_command($command, $command_string); 158 | } 159 | 160 | /** 161 | * Find available face template slot for user. 162 | * 163 | * @param ZKTeco $self ZKTeco instance. 164 | * @param int $uid User ID. 165 | * @return int|false Available slot ID or false if none available. 166 | */ 167 | private static function _findAvailableSlot(ZKTeco $self, $uid) 168 | { 169 | for ($i = 0; $i < self::MAX_FACE_TEMPLATES; $i++) { 170 | $faceId = 50 + $i; 171 | $template = self::_getFaceTemplate($self, $uid, $faceId); 172 | 173 | if ($template['size'] == 0) { 174 | return $faceId; 175 | } 176 | } 177 | 178 | return false; 179 | } 180 | 181 | /** 182 | * Calculate face template quality score. 183 | * 184 | * @param string $templateData Template data. 185 | * @return int Quality score (0-100). 186 | */ 187 | private static function _calculateQuality($templateData) 188 | { 189 | if (empty($templateData)) { 190 | return 0; 191 | } 192 | 193 | $length = strlen($templateData); 194 | 195 | // Basic quality assessment based on template size and data distribution 196 | $sizeScore = min(100, ($length / self::FACE_TEMPLATE_SIZE) * 50); 197 | 198 | $complexity = 0; 199 | for ($i = 0; $i < min($length, 200); $i += 10) { 200 | $complexity += ord($templateData[$i]); 201 | } 202 | 203 | $complexityScore = min(50, ($complexity / min($length / 10, 20)) * 0.2); 204 | 205 | return (int)($sizeScore + $complexityScore); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /examples/AdvancedDeviceManagementExample.php: -------------------------------------------------------------------------------- 1 | connect()) { 19 | die("Failed to connect to device\n"); 20 | } 21 | 22 | echo "Connected to ZKTeco device successfully!\n"; 23 | 24 | // Example 1: Custom LCD Message Display 25 | echo "\n=== LCD Message Display Examples ===\n"; 26 | 27 | // Display welcome message 28 | $zk->displayCustomMessage('Welcome to Office!', 1, 0); 29 | echo "Displayed welcome message on line 1\n"; 30 | 31 | // Display temporary message 32 | $zk->displayCustomMessage('System Update: 15:30', 2, 10); 33 | echo "Displayed temporary message on line 2 (10 seconds)\n"; 34 | 35 | // Display multi-line information 36 | $zk->displayCustomMessage('Temperature: 24°C', 3, 0); 37 | $zk->displayCustomMessage('Humidity: 65%', 4, 0); 38 | echo "Displayed environmental info on lines 3-4\n"; 39 | 40 | // Example 2: Door Control Functions 41 | echo "\n=== Door Control Examples ===\n"; 42 | 43 | // Check door status 44 | $doorStatus = $zk->getDoorStatus(1); 45 | echo "Door Status: " . json_encode($doorStatus, JSON_PRETTY_PRINT) . "\n"; 46 | 47 | // Unlock and open door 48 | if ($zk->unlockDoor(1)) { 49 | echo "Door unlocked successfully\n"; 50 | $zk->displayCustomMessage('Door Unlocked', 1, 3); 51 | 52 | if ($zk->openDoor(1)) { 53 | echo "Door opened successfully\n"; 54 | 55 | // Wait 5 seconds then close and lock 56 | sleep(5); 57 | 58 | if ($zk->closeDoor(1)) { 59 | echo "Door closed successfully\n"; 60 | 61 | if ($zk->lockDoor(1)) { 62 | echo "Door locked successfully\n"; 63 | $zk->displayCustomMessage('Door Secured', 1, 3); 64 | } 65 | } 66 | } 67 | } 68 | 69 | // Example 3: Time Zone Synchronization 70 | echo "\n=== Time Zone Synchronization Examples ===\n"; 71 | 72 | // Get current device time 73 | $currentTime = $zk->getTime(); 74 | echo "Current device time: $currentTime\n"; 75 | 76 | // Sync with server timezone 77 | if ($zk->syncTimeZone()) { 78 | echo "Device time synchronized with server timezone\n"; 79 | $newTime = $zk->getTime(); 80 | echo "New device time: $newTime\n"; 81 | } 82 | 83 | // Sync with specific timezone 84 | if ($zk->syncTimeZone('America/New_York')) { 85 | echo "Device time synchronized with New York timezone\n"; 86 | } 87 | 88 | // Example 4: Real-time Event Monitoring 89 | echo "\n=== Real-time Event Monitoring Examples ===\n"; 90 | 91 | // Simple event monitoring with callback 92 | echo "Starting simple event monitoring for 30 seconds...\n"; 93 | 94 | $eventCallback = function($event) { 95 | echo "Event received: " . json_encode($event, JSON_PRETTY_PRINT) . "\n"; 96 | 97 | switch($event['type']) { 98 | case 'attendance': 99 | echo "User {$event['uid']} punched at {$event['timestamp']}\n"; 100 | break; 101 | case 'door_open': 102 | echo "Door opened by user {$event['uid']} at {$event['timestamp']}\n"; 103 | break; 104 | case 'alarm': 105 | echo "SECURITY ALARM at {$event['timestamp']}!\n"; 106 | break; 107 | } 108 | }; 109 | 110 | // Monitor events for 30 seconds 111 | $zk->startEventMonitoring($eventCallback, 30); 112 | 113 | // Example 5: Advanced Event Monitoring with Event Monitor 114 | echo "\n=== Advanced Event Monitoring Examples ===\n"; 115 | 116 | $monitor = $zk->getEventMonitor(); 117 | 118 | // Register specific event handlers 119 | $monitor->on('attendance', function($event) use ($zk) { 120 | echo "Attendance Event - User: {$event['uid']}, Time: {$event['timestamp']}\n"; 121 | 122 | // Display welcome message on LCD 123 | $zk->displayCustomMessage("Welcome User {$event['uid']}", 1, 5); 124 | 125 | // You could also: 126 | // - Log to database 127 | // - Send notifications 128 | // - Update attendance records 129 | }); 130 | 131 | $monitor->on('door_open', function($event) use ($zk) { 132 | echo "Door Open Event - User: {$event['uid']}, Time: {$event['timestamp']}\n"; 133 | 134 | // Display door status 135 | $zk->displayCustomMessage('Door Opened', 2, 3); 136 | }); 137 | 138 | $monitor->on('alarm', function($event) use ($zk) { 139 | echo "ALARM EVENT - Time: {$event['timestamp']}\n"; 140 | 141 | // Display alarm message 142 | $zk->displayCustomMessage('SECURITY ALERT!', 1, 0); 143 | 144 | // You could also: 145 | // - Send email/SMS alerts 146 | // - Trigger security protocols 147 | // - Log security incidents 148 | }); 149 | 150 | // Register handler for all events 151 | $monitor->on('*', function($event) { 152 | // Log all events to a file or database 153 | file_put_contents('device_events.log', 154 | date('Y-m-d H:i:s') . " - " . json_encode($event) . "\n", 155 | FILE_APPEND 156 | ); 157 | }); 158 | 159 | echo "Starting advanced event monitoring for 60 seconds...\n"; 160 | echo "Registered handlers for attendance, door_open, alarm, and all events\n"; 161 | 162 | // Start monitoring for 60 seconds 163 | $monitor->start(60); 164 | 165 | // Example 6: Complete Security System Integration 166 | echo "\n=== Complete Security System Example ===\n"; 167 | 168 | function securitySystemExample($zk) { 169 | // Initialize security system 170 | $zk->displayCustomMessage('Security System Active', 1, 0); 171 | 172 | $monitor = $zk->getEventMonitor(); 173 | 174 | // Attendance tracking 175 | $monitor->on('attendance', function($event) use ($zk) { 176 | $userId = $event['uid']; 177 | $timestamp = $event['timestamp']; 178 | 179 | // Simulate user lookup 180 | $userName = "User_$userId"; 181 | 182 | echo "Access granted for $userName at $timestamp\n"; 183 | $zk->displayCustomMessage("Welcome $userName", 1, 5); 184 | 185 | // Log attendance 186 | logAttendance($userId, $timestamp); 187 | }); 188 | 189 | // Security monitoring 190 | $monitor->on('alarm', function($event) use ($zk) { 191 | echo "SECURITY BREACH DETECTED!\n"; 192 | 193 | // Display alert 194 | $zk->displayCustomMessage('SECURITY ALERT!', 1, 0); 195 | 196 | // Lock all doors 197 | $zk->lockDoor(1); 198 | 199 | // Send notifications (simulate) 200 | sendSecurityAlert($event); 201 | }); 202 | 203 | // Door monitoring 204 | $monitor->on('door_open', function($event) use ($zk) { 205 | // Check if door should be open 206 | $doorStatus = $zk->getDoorStatus(1); 207 | 208 | if (!isAuthorizedAccess($event['uid'])) { 209 | echo "Unauthorized door access attempt!\n"; 210 | $zk->displayCustomMessage('Unauthorized Access!', 1, 10); 211 | } 212 | }); 213 | 214 | echo "Security system monitoring started...\n"; 215 | $monitor->start(120); // Monitor for 2 minutes 216 | } 217 | 218 | // Helper functions (simulate real implementations) 219 | function logAttendance($userId, $timestamp) { 220 | echo "Logged attendance for user $userId at $timestamp\n"; 221 | } 222 | 223 | function sendSecurityAlert($event) { 224 | echo "Security alert sent: " . json_encode($event) . "\n"; 225 | } 226 | 227 | function isAuthorizedAccess($userId) { 228 | // Simulate authorization check 229 | return $userId > 0; 230 | } 231 | 232 | // Run security system example 233 | securitySystemExample($zk); 234 | 235 | // Cleanup 236 | echo "\n=== Cleanup ===\n"; 237 | $zk->clearLCD(); 238 | $zk->stopEventMonitoring(); 239 | $zk->disconnect(); 240 | 241 | echo "Disconnected from device. Examples completed!\n"; -------------------------------------------------------------------------------- /tests/AdvancedDeviceManagementTest.php: -------------------------------------------------------------------------------- 1 | zk = $this->createMock(ZKTeco::class); 16 | } 17 | 18 | public function testDisplayCustomMessage() 19 | { 20 | $this->zk->method('_command')->willReturn(true); 21 | 22 | $result = $this->zk->displayCustomMessage('Welcome User', 1, 10); 23 | 24 | $this->assertTrue($result); 25 | } 26 | 27 | public function testDisplayCustomMessageInvalidLine() 28 | { 29 | $result = $this->zk->displayCustomMessage('Test', 5, 0); 30 | 31 | $this->assertFalse($result); 32 | } 33 | 34 | public function testDisplayCustomMessageTooLong() 35 | { 36 | $longMessage = str_repeat('A', 50); 37 | 38 | $result = $this->zk->displayCustomMessage($longMessage, 1, 0); 39 | 40 | $this->assertFalse($result); 41 | } 42 | 43 | public function testOpenDoor() 44 | { 45 | $this->zk->method('_command')->willReturn(true); 46 | 47 | $result = $this->zk->openDoor(1); 48 | 49 | $this->assertTrue($result); 50 | } 51 | 52 | public function testCloseDoor() 53 | { 54 | $this->zk->method('_command')->willReturn(true); 55 | 56 | $result = $this->zk->closeDoor(1); 57 | 58 | $this->assertTrue($result); 59 | } 60 | 61 | public function testLockDoor() 62 | { 63 | $this->zk->method('_command')->willReturn(true); 64 | 65 | $result = $this->zk->lockDoor(1); 66 | 67 | $this->assertTrue($result); 68 | } 69 | 70 | public function testUnlockDoor() 71 | { 72 | $this->zk->method('_command')->willReturn(true); 73 | 74 | $result = $this->zk->unlockDoor(1); 75 | 76 | $this->assertTrue($result); 77 | } 78 | 79 | public function testGetDoorStatus() 80 | { 81 | $mockStatusData = chr(1) . chr(0) . chr(1) . chr(0); // Door open, unlocked, sensor active 82 | $this->zk->method('_command')->willReturn($mockStatusData); 83 | 84 | $result = $this->zk->getDoorStatus(1); 85 | 86 | $this->assertIsArray($result); 87 | $this->assertTrue($result['door_open']); 88 | $this->assertFalse($result['door_locked']); 89 | $this->assertTrue($result['sensor_active']); 90 | $this->assertArrayHasKey('timestamp', $result); 91 | } 92 | 93 | public function testGetDoorStatusError() 94 | { 95 | $this->zk->method('_command')->willReturn(false); 96 | 97 | $result = $this->zk->getDoorStatus(1); 98 | 99 | $this->assertArrayHasKey('error', $result); 100 | } 101 | 102 | public function testSyncTimeZoneDefault() 103 | { 104 | $this->zk->method('setTime')->willReturn(true); 105 | 106 | $result = $this->zk->syncTimeZone(); 107 | 108 | $this->assertTrue($result); 109 | } 110 | 111 | public function testSyncTimeZoneCustom() 112 | { 113 | $this->zk->method('setTime')->willReturn(true); 114 | 115 | $result = $this->zk->syncTimeZone('America/New_York'); 116 | 117 | $this->assertTrue($result); 118 | } 119 | 120 | public function testSyncTimeZoneInvalidTimezone() 121 | { 122 | $result = $this->zk->syncTimeZone('Invalid/Timezone'); 123 | 124 | $this->assertFalse($result); 125 | } 126 | 127 | public function testGetRealTimeEvents() 128 | { 129 | $this->zk->method('_command')->willReturn(true); 130 | 131 | $result = $this->zk->getRealTimeEvents(5); 132 | 133 | $this->assertIsArray($result); 134 | } 135 | 136 | public function testGetRealTimeEventsFailure() 137 | { 138 | $this->zk->method('_command')->willReturn(false); 139 | 140 | $result = $this->zk->getRealTimeEvents(5); 141 | 142 | $this->assertIsArray($result); 143 | $this->assertEmpty($result); 144 | } 145 | 146 | public function testStartEventMonitoring() 147 | { 148 | $this->zk->method('_command')->willReturn(true); 149 | 150 | $callbackCalled = false; 151 | $callback = function($event) use (&$callbackCalled) { 152 | $callbackCalled = true; 153 | }; 154 | 155 | $result = $this->zk->startEventMonitoring($callback, 1); 156 | 157 | $this->assertTrue($result); 158 | } 159 | 160 | public function testStartEventMonitoringFailure() 161 | { 162 | $this->zk->method('_command')->willReturn(false); 163 | 164 | $callback = function($event) {}; 165 | 166 | $result = $this->zk->startEventMonitoring($callback, 1); 167 | 168 | $this->assertFalse($result); 169 | } 170 | 171 | public function testStopEventMonitoring() 172 | { 173 | $this->zk->method('_command')->willReturn(true); 174 | 175 | $result = $this->zk->stopEventMonitoring(); 176 | 177 | $this->assertTrue($result); 178 | } 179 | 180 | public function testDoorControlConstants() 181 | { 182 | $this->assertEquals(1, Util::DOOR_ACTION_OPEN); 183 | $this->assertEquals(2, Util::DOOR_ACTION_CLOSE); 184 | $this->assertEquals(3, Util::DOOR_ACTION_LOCK); 185 | $this->assertEquals(4, Util::DOOR_ACTION_UNLOCK); 186 | } 187 | 188 | public function testEventTypeConstants() 189 | { 190 | $this->assertEquals(1, Util::EVENT_TYPE_ATTENDANCE); 191 | $this->assertEquals(2, Util::EVENT_TYPE_DOOR_OPEN); 192 | $this->assertEquals(3, Util::EVENT_TYPE_DOOR_CLOSE); 193 | $this->assertEquals(4, Util::EVENT_TYPE_ALARM); 194 | $this->assertEquals(5, Util::EVENT_TYPE_USER_ENROLL); 195 | $this->assertEquals(6, Util::EVENT_TYPE_USER_DELETE); 196 | $this->assertEquals(7, Util::EVENT_TYPE_SYSTEM_START); 197 | $this->assertEquals(8, Util::EVENT_TYPE_SYSTEM_SHUTDOWN); 198 | } 199 | 200 | public function testEventParsing() 201 | { 202 | // Mock event data: 16 bytes with attendance event 203 | $eventData = str_repeat(chr(0), 8) . // Header 204 | chr(1) . // Event type (attendance) 205 | chr(123) . chr(0) . // UID (123) 206 | chr(100) . chr(200) . chr(50) . chr(25) . // Timestamp 207 | chr(1) . // State 208 | chr(0); // Padding 209 | 210 | // This would be tested in integration tests with actual device 211 | $this->assertIsString($eventData); 212 | $this->assertEquals(16, strlen($eventData)); 213 | } 214 | 215 | public function testDoorStatusParsing() 216 | { 217 | // Test door status parsing with various combinations 218 | $testCases = [ 219 | [chr(1) . chr(1) . chr(1) . chr(0), true, true, true, false], // Open, locked, sensor active 220 | [chr(0) . chr(0) . chr(0) . chr(0), false, false, false, false], // Closed, unlocked, sensor inactive 221 | [chr(3) . chr(1) . chr(1) . chr(0), true, true, true, true], // Open with alarm 222 | ]; 223 | 224 | foreach ($testCases as [$data, $expectedOpen, $expectedLocked, $expectedSensor, $expectedAlarm]) { 225 | $this->zk->method('_command')->willReturn($data); 226 | 227 | $result = $this->zk->getDoorStatus(1); 228 | 229 | $this->assertEquals($expectedOpen, $result['door_open']); 230 | $this->assertEquals($expectedLocked, $result['door_locked']); 231 | $this->assertEquals($expectedSensor, $result['sensor_active']); 232 | $this->assertEquals($expectedAlarm, $result['alarm_active']); 233 | } 234 | } 235 | } -------------------------------------------------------------------------------- /src/Lib/Helper/User.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 22 | 23 | if ( 24 | (int)$uid === 0 || 25 | (int)$uid > Util::USHRT_MAX || 26 | strlen($userid) > 9 || 27 | strlen($name) > 24 || 28 | strlen($password) > 8 || 29 | strlen($cardno) > 10 30 | ) { 31 | return false; 32 | } 33 | 34 | $command = Util::CMD_SET_USER; 35 | $byte1 = chr((int)($uid % 256)); 36 | $byte2 = chr((int)($uid >> 8)); 37 | $cardno = hex2bin(Util::reverseHex(dechex($cardno))); 38 | 39 | $command_string = implode('', [ 40 | $byte1, 41 | $byte2, 42 | chr($role), 43 | str_pad($password, 8, chr(0)), 44 | str_pad($name, 24, chr(0)), 45 | str_pad($cardno, 4, chr(0)), 46 | str_pad(chr(1), 9, chr(0)), 47 | str_pad($userid, 9, chr(0)), 48 | str_repeat(chr(0), 15) 49 | ]); 50 | // die($command_string); 51 | return $self->_command($command, $command_string); 52 | } 53 | 54 | /** 55 | * @param ZKTeco $self 56 | * @return array [userid, name, cardno, uid, role, password] 57 | */ 58 | static public function get(ZKTeco $self) 59 | { 60 | $self->_section = __METHOD__; 61 | 62 | $command = Util::CMD_USER_TEMP_RRQ; 63 | $command_string = chr(Util::FCT_USER); 64 | 65 | $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); 66 | if ($session === false) { 67 | return []; 68 | } 69 | 70 | $userData = Util::recData($self); 71 | 72 | $users = []; 73 | if (!empty($userData)) { 74 | $userData = substr($userData, 11); 75 | 76 | while (strlen($userData) > 72) { 77 | $u = unpack('H144', substr($userData, 0, 72)); 78 | 79 | $u1 = hexdec(substr($u[1], 2, 2)); 80 | $u2 = hexdec(substr($u[1], 4, 2)); 81 | $uid = $u1 + ($u2 * 256); 82 | $cardno = hexdec(substr($u[1], 78, 2) . substr($u[1], 76, 2) . substr($u[1], 74, 2) . substr($u[1], 72, 2)) . ' '; 83 | $role = hexdec(substr($u[1], 6, 2)) . ' '; 84 | $password = hex2bin(substr($u[1], 8, 16)) . ' '; 85 | $name = hex2bin(substr($u[1], 24, 74)) . ' '; 86 | $userid = hex2bin(substr($u[1], 98, 72)) . ' '; 87 | 88 | //Clean up some messy characters from the user name 89 | $password = explode(chr(0), $password, 2); 90 | $password = $password[0]; 91 | $userid = explode(chr(0), $userid, 2); 92 | $userid = $userid[0]; 93 | $name = explode(chr(0), $name, 3); 94 | $name = utf8_encode($name[0]); 95 | $cardno = str_pad($cardno, 11, '0', STR_PAD_LEFT); 96 | 97 | if ($name == '') { 98 | $name = $userid; 99 | } 100 | 101 | $users[$userid] = [ 102 | 'uid' => $uid, 103 | 'userid' => $userid, 104 | 'name' => $name, 105 | 'role' => intval($role), 106 | 'password' => $password, 107 | 'cardno' => $cardno, 108 | ]; 109 | 110 | $userData = substr($userData, 72); 111 | } 112 | } 113 | 114 | return $users; 115 | } 116 | 117 | /** 118 | * @param ZKTeco $self 119 | * @return bool|mixed 120 | */ 121 | static public function clear(ZKTeco $self) 122 | { 123 | $self->_section = __METHOD__; 124 | 125 | $command = Util::CMD_CLEAR_DATA; 126 | $command_string = ''; 127 | 128 | return $self->_command($command, $command_string); 129 | } 130 | 131 | /** 132 | * @param ZKTeco $self 133 | * @return bool|mixed 134 | */ 135 | static public function clearAdmin(ZKTeco $self) 136 | { 137 | $self->_section = __METHOD__; 138 | 139 | $command = Util::CMD_CLEAR_ADMIN; 140 | $command_string = ''; 141 | 142 | return $self->_command($command, $command_string); 143 | } 144 | 145 | /** 146 | * @param ZKTeco $self 147 | * @param integer $uid 148 | * @return bool|mixed 149 | */ 150 | static public function remove(ZKTeco $self, $uid) 151 | { 152 | $self->_section = __METHOD__; 153 | 154 | $command = Util::CMD_DELETE_USER; 155 | $byte1 = chr((int)($uid % 256)); 156 | $byte2 = chr((int)($uid >> 8)); 157 | $command_string = ($byte1 . $byte2); 158 | 159 | return $self->_command($command, $command_string); 160 | } 161 | 162 | /** 163 | * Get card number for a specific user. 164 | * 165 | * @param ZKTeco $self ZKTeco instance. 166 | * @param int $uid User ID. 167 | * @return string|false Card number or false if not found. 168 | */ 169 | static public function getCardNumber(ZKTeco $self, $uid) 170 | { 171 | $self->_section = __METHOD__; 172 | 173 | $users = self::get($self); 174 | 175 | foreach ($users as $user) { 176 | if ($user['uid'] == $uid) { 177 | return trim($user['cardno']); 178 | } 179 | } 180 | 181 | return false; 182 | } 183 | 184 | /** 185 | * Set advanced user role with granular permissions. 186 | * 187 | * @param ZKTeco $self ZKTeco instance. 188 | * @param int $uid User ID. 189 | * @param int $role Role level. 190 | * @param array $permissions Additional permissions. 191 | * @return bool Success status. 192 | */ 193 | static public function setRole(ZKTeco $self, $uid, $role, array $permissions = []) 194 | { 195 | $self->_section = __METHOD__; 196 | 197 | // Get current user data 198 | $users = self::get($self); 199 | $currentUser = null; 200 | 201 | foreach ($users as $user) { 202 | if ($user['uid'] == $uid) { 203 | $currentUser = $user; 204 | break; 205 | } 206 | } 207 | 208 | if (!$currentUser) { 209 | return false; 210 | } 211 | 212 | // Update user with new role 213 | return self::set( 214 | $self, 215 | $uid, 216 | $currentUser['userid'], 217 | $currentUser['name'], 218 | $currentUser['password'], 219 | $role, 220 | $currentUser['cardno'] 221 | ); 222 | } 223 | 224 | /** 225 | * Get detailed user role information. 226 | * 227 | * @param ZKTeco $self ZKTeco instance. 228 | * @param int $uid User ID. 229 | * @return array Role information with permissions. 230 | */ 231 | static public function getRole(ZKTeco $self, $uid) 232 | { 233 | $self->_section = __METHOD__; 234 | 235 | $users = self::get($self); 236 | 237 | foreach ($users as $user) { 238 | if ($user['uid'] == $uid) { 239 | return [ 240 | 'role_id' => $user['role'], 241 | 'role_name' => Util::getUserRole($user['role']), 242 | 'permissions' => self::_getRolePermissions($user['role']), 243 | 'can_enroll' => $user['role'] >= Util::LEVEL_ADMIN, 244 | 'can_manage_users' => $user['role'] >= Util::LEVEL_ADMIN, 245 | 'can_view_logs' => true 246 | ]; 247 | } 248 | } 249 | 250 | return []; 251 | } 252 | 253 | /** 254 | * Get all available user roles. 255 | * 256 | * @return array Available roles with descriptions. 257 | */ 258 | static public function getAvailableRoles() 259 | { 260 | return [ 261 | Util::LEVEL_USER => [ 262 | 'name' => 'User', 263 | 'description' => 'Standard user with basic access', 264 | 'permissions' => ['attendance', 'view_own_records'] 265 | ], 266 | Util::LEVEL_ADMIN => [ 267 | 'name' => 'Administrator', 268 | 'description' => 'Full administrative access', 269 | 'permissions' => ['all_access', 'user_management', 'system_config', 'reports'] 270 | ], 271 | 2 => [ 272 | 'name' => 'Supervisor', 273 | 'description' => 'Supervisory access with limited admin rights', 274 | 'permissions' => ['attendance', 'view_reports', 'manage_subordinates'] 275 | ], 276 | 3 => [ 277 | 'name' => 'Manager', 278 | 'description' => 'Management level access', 279 | 'permissions' => ['attendance', 'view_reports', 'user_management', 'department_config'] 280 | ] 281 | ]; 282 | } 283 | 284 | /** 285 | * Get permissions for a specific role. 286 | * 287 | * @param int $role Role ID. 288 | * @return array Permissions array. 289 | */ 290 | private static function _getRolePermissions($role) 291 | { 292 | $roles = self::getAvailableRoles(); 293 | return isset($roles[$role]) ? $roles[$role]['permissions'] : []; 294 | } 295 | } -------------------------------------------------------------------------------- /src/Lib/Helper/Fingerprint.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 19 | 20 | $data = []; 21 | // Fingers of the hands 22 | for ($i = 0; $i <= 9; $i++) { 23 | $finger = new Fingerprint(); 24 | $tmp = $finger->_getFinger($self, $uid, $i); 25 | if ($tmp['size'] > 0) { 26 | $data[$i] = $tmp['tpl']; 27 | } 28 | unset($tmp); 29 | } 30 | return $data; 31 | } 32 | 33 | /** 34 | * Set fingerprint data for a specific user on the ZKTeco device. 35 | * 36 | * @param ZKTeco $self The instance of the ZKTeco class. 37 | * @param int $uid Unique Employee ID in ZK device. 38 | * @param array $data Binary fingerprint data array (where key is finger ID (0-9) same like returned array from 'get' method). 39 | * @return int Count of added fingerprints. 40 | */ 41 | static public function set(ZKTeco $self, $uid, array $data) 42 | { 43 | $self->_section = __METHOD__; 44 | 45 | $count = 0; 46 | foreach ($data as $finger => $item) { 47 | $allowSet = true; 48 | $fingerPrint = new Fingerprint(); 49 | if ($fingerPrint->_checkFinger($self, $uid, $finger) === true) { 50 | $allowSet = $fingerPrint->_removeFinger($self, $uid, $finger); 51 | } 52 | if ($allowSet === true && $fingerPrint->_setFinger($self, $item) === true) { 53 | $count++; 54 | } 55 | } 56 | 57 | return $count; 58 | } 59 | 60 | /** 61 | * Remove fingerprint data for a specific user from the ZKTeco device. 62 | * 63 | * @param ZKTeco $self The instance of the ZKTeco class. 64 | * @param int $uid Unique Employee ID in ZK device. 65 | * @param array $data Fingers ID array (0-9). 66 | * @return int Count of deleted fingerprints. 67 | */ 68 | static public function remove(ZKTeco $self, $uid, array $data) 69 | { 70 | $self->_section = __METHOD__; 71 | 72 | $count = 0; 73 | foreach ($data as $finger) { 74 | $fingerPrint = new Fingerprint(); 75 | if ($fingerPrint->_checkFinger($self, $uid, $finger) === true) { 76 | if ($fingerPrint->_removeFinger($self, $uid, $finger) === true) { 77 | $count++; 78 | } 79 | } 80 | } 81 | 82 | return $count; 83 | } 84 | 85 | /** 86 | * Retrieve fingerprint data for a specific user and finger from the ZKTeco device. 87 | * 88 | * @param ZKTeco $self The instance of the ZKTeco class. 89 | * @param int $uid Unique Employee ID in ZK device. 90 | * @param int $finger Finger ID (0-9). 91 | * @return array An array containing the size of the fingerprint data and the actual data. 92 | */ 93 | private function _getFinger(ZKTeco $self, $uid, $finger) 94 | { 95 | $command = Util::CMD_USER_TEMP_RRQ; 96 | $byte1 = chr((int)($uid % 256)); 97 | $byte2 = chr((int)($uid >> 8)); 98 | $command_string = $byte1 . $byte2 . chr($finger); 99 | 100 | $ret = [ 101 | 'size' => 0, 102 | 'tpl' => '' 103 | ]; 104 | 105 | $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); 106 | if ($session === false) { 107 | return $ret; 108 | } 109 | 110 | $data = Util::recData($self, 10, false); 111 | 112 | if (!empty($data)) { 113 | $templateSize = strlen($data); 114 | $prefix = chr($templateSize % 256) . chr(round($templateSize / 256)) . $byte1 . $byte2 . chr($finger) . chr(1); 115 | $data = $prefix . $data; 116 | if (strlen($templateSize) > 0) { 117 | $ret['size'] = $templateSize; 118 | $ret['tpl'] = $data; 119 | } 120 | } 121 | 122 | return $ret; 123 | } 124 | 125 | /** 126 | * Set fingerprint data on the ZKTeco device. 127 | * 128 | * @param ZKTeco $self The instance of the ZKTeco class. 129 | * @param string $data Binary fingerprint data item. 130 | * @return bool|mixed Returns true if the fingerprint data is set successfully, false otherwise. 131 | */ 132 | private function _setFinger(ZKTeco $self, $data) 133 | { 134 | $command = Util::CMD_USER_TEMP_WRQ; 135 | $command_string = $data; 136 | 137 | return $self->_command($command, $command_string); 138 | } 139 | 140 | /** 141 | * Remove fingerprint data from the ZKTeco device. 142 | * 143 | * @param ZKTeco $self The instance of the ZKTeco class. 144 | * @param int $uid Unique Employee ID in ZK device. 145 | * @param int $finger Finger ID (0-9). 146 | * @return bool Returns true if the fingerprint data is removed successfully, false otherwise. 147 | */ 148 | private function _removeFinger(ZKTeco $self, $uid, $finger) 149 | { 150 | $command = Util::CMD_DELETE_USER_TEMP; 151 | $byte1 = chr((int)($uid % 256)); 152 | $byte2 = chr((int)($uid >> 8)); 153 | $command_string = ($byte1 . $byte2) . chr($finger); 154 | 155 | $self->_command($command, $command_string); 156 | $fingerPrint = new Fingerprint(); 157 | return !($fingerPrint->_checkFinger($self, $uid, $finger)); 158 | } 159 | 160 | /** 161 | * Check if fingerprint data exists for a specific user and finger on the ZKTeco device. 162 | * 163 | * @param ZKTeco $self The instance of the ZKTeco class. 164 | * @param int $uid Unique Employee ID in ZK device. 165 | * @param int $finger Finger ID (0-9). 166 | * @return bool Returns true if fingerprint data exists, false otherwise. 167 | */ 168 | private function _checkFinger(ZKTeco $self, $uid, $finger) 169 | { 170 | $fingerPrint = new Fingerprint(); 171 | $res = $fingerPrint->_getFinger($self, $uid, $finger); 172 | return (bool)($res['size'] > 0); 173 | } 174 | 175 | /** 176 | * Parse raw fingerprint template data into structured format. 177 | * 178 | * @param string $rawData Raw fingerprint template data. 179 | * @return array Parsed template with metadata. 180 | */ 181 | static public function parseTemplate($rawData) 182 | { 183 | if (empty($rawData) || strlen($rawData) < 6) { 184 | return ['valid' => false, 'error' => 'Invalid template data']; 185 | } 186 | 187 | $templateSize = ord($rawData[0]) + (ord($rawData[1]) << 8); 188 | $uid = ord($rawData[2]) + (ord($rawData[3]) << 8); 189 | $fingerId = ord($rawData[4]); 190 | $flag = ord($rawData[5]); 191 | 192 | $templateData = substr($rawData, 6); 193 | 194 | return [ 195 | 'valid' => true, 196 | 'template_size' => $templateSize, 197 | 'uid' => $uid, 198 | 'finger_id' => $fingerId, 199 | 'flag' => $flag, 200 | 'template_data' => $templateData, 201 | 'quality_score' => self::_calculateQuality($templateData) 202 | ]; 203 | } 204 | 205 | /** 206 | * Enroll a new fingerprint template for a user. 207 | * 208 | * @param ZKTeco $self ZKTeco instance. 209 | * @param int $uid User ID. 210 | * @param int $fingerId Finger ID (0-9). 211 | * @param string $templateData Template data (raw from getFingerprint or just template data). 212 | * @return bool Success status. 213 | */ 214 | static public function enroll(ZKTeco $self, $uid, $fingerId, $templateData) 215 | { 216 | $self->_section = __METHOD__; 217 | 218 | if ($fingerId < 0 || $fingerId > 9) { 219 | return false; 220 | } 221 | 222 | $fingerprint = new Fingerprint(); 223 | 224 | // Remove existing fingerprint if present 225 | if ($fingerprint->_checkFinger($self, $uid, $fingerId)) { 226 | $fingerprint->_removeFinger($self, $uid, $fingerId); 227 | } 228 | 229 | // Check if templateData already has header (from getFingerprint) 230 | if (strlen($templateData) >= 6) { 231 | // Check if it looks like it already has a header 232 | $possibleSize = ord($templateData[0]) + (ord($templateData[1]) << 8); 233 | $possibleUid = ord($templateData[2]) + (ord($templateData[3]) << 8); 234 | $possibleFingerId = ord($templateData[4]); 235 | 236 | // If header matches expected values, use data as-is 237 | if ($possibleUid == $uid && $possibleFingerId == $fingerId && 238 | $possibleSize == (strlen($templateData) - 6)) { 239 | return $fingerprint->_setFinger($self, $templateData); 240 | } 241 | } 242 | 243 | // Prepare template data with proper header (for raw template data) 244 | $templateSize = strlen($templateData); 245 | $byte1 = chr($uid % 256); 246 | $byte2 = chr($uid >> 8); 247 | 248 | $formattedData = chr($templateSize % 256) . chr($templateSize >> 8) . 249 | $byte1 . $byte2 . chr($fingerId) . chr(1) . $templateData; 250 | 251 | return $fingerprint->_setFinger($self, $formattedData); 252 | } 253 | 254 | /** 255 | * Calculate fingerprint template quality score. 256 | * 257 | * @param string $templateData Template data. 258 | * @return int Quality score (0-100). 259 | */ 260 | private static function _calculateQuality($templateData) 261 | { 262 | if (empty($templateData)) { 263 | return 0; 264 | } 265 | 266 | $length = strlen($templateData); 267 | $complexity = 0; 268 | 269 | // Simple quality calculation based on data complexity 270 | for ($i = 0; $i < min($length, 100); $i++) { 271 | $complexity += ord($templateData[$i]); 272 | } 273 | 274 | $quality = min(100, ($complexity / min($length, 100)) * 0.4); 275 | return (int)$quality; 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/Lib/Helper/Util.php: -------------------------------------------------------------------------------- 1 | (int)date('Y', $timestamp), 106 | 'month' => (int)date('m', $timestamp), 107 | 'day' => (int)date('d', $timestamp), 108 | 'hour' => (int)date('H', $timestamp), 109 | 'minute' => (int)date('i', $timestamp), 110 | 'second' => (int)date('s', $timestamp), 111 | ]; 112 | 113 | $d = (($t->year % 100) * 12 * 31 + (($t->month - 1) * 31) + $t->day - 1) * 114 | (24 * 60 * 60) + ($t->hour * 60 + $t->minute) * 60 + $t->second; 115 | 116 | return $d; 117 | } 118 | 119 | /** 120 | * Decode a timestamp retrieved from the timeclock 121 | * copied from zkemsdk.c - DecodeTime 122 | * 123 | * @param int|string $t 124 | * @return false|string Format: "Y-m-d H:i:s" 125 | */ 126 | static public function decodeTime($t) 127 | { 128 | $second = $t % 60; 129 | $t = $t / 60; 130 | 131 | $minute = $t % 60; 132 | $t = $t / 60; 133 | 134 | $hour = $t % 24; 135 | $t = $t / 24; 136 | 137 | $day = $t % 31 + 1; 138 | $t = $t / 31; 139 | 140 | $month = $t % 12 + 1; 141 | $t = $t / 12; 142 | 143 | $year = floor($t + 2000); 144 | 145 | $d = date('Y-m-d H:i:s', strtotime( 146 | $year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second 147 | )); 148 | 149 | return $d; 150 | } 151 | 152 | /** 153 | * @param string $hex 154 | * @return string 155 | */ 156 | static public function reverseHex($hex) 157 | { 158 | $tmp = ''; 159 | 160 | for ($i = strlen($hex); $i >= 0; $i--) { 161 | $tmp .= substr($hex, $i, 2); 162 | $i--; 163 | } 164 | 165 | return $tmp; 166 | } 167 | 168 | /** 169 | * Checks a returned packet to see if it returned self::CMD_PREPARE_DATA, 170 | * indicating that data packets are to be sent 171 | * Returns the amount of bytes that are going to be sent 172 | * 173 | * @param ZKTeco $self 174 | * @return bool|number 175 | */ 176 | static public function getSize(ZKTeco $self) 177 | { 178 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6/H2h7/H2h8', substr($self->_data_recv, 0, 8)); 179 | $command = hexdec($u['h2'] . $u['h1']); 180 | 181 | if ($command == self::CMD_PREPARE_DATA) { 182 | $u = unpack('H2h1/H2h2/H2h3/H2h4', substr($self->_data_recv, 8, 4)); 183 | $size = hexdec($u['h4'] . $u['h3'] . $u['h2'] . $u['h1']); 184 | return $size; 185 | } else { 186 | return false; 187 | } 188 | } 189 | 190 | /** 191 | * This function calculates the chksum of the packet to be sent to the 192 | * time clock 193 | * Copied from zkemsdk.c 194 | * 195 | * @inheritdoc 196 | */ 197 | static public function createChkSum($p) 198 | { 199 | $l = count($p); 200 | $chksum = 0; 201 | $i = $l; 202 | $j = 1; 203 | while ($i > 1) { 204 | $u = unpack('S', pack('C2', $p['c' . $j], $p['c' . ($j + 1)])); 205 | 206 | $chksum += $u[1]; 207 | 208 | if ($chksum > self::USHRT_MAX) { 209 | $chksum -= self::USHRT_MAX; 210 | } 211 | $i -= 2; 212 | $j += 2; 213 | } 214 | 215 | if ($i) { 216 | $chksum = $chksum + $p['c' . strval(count($p))]; 217 | } 218 | 219 | while ($chksum > self::USHRT_MAX) { 220 | $chksum -= self::USHRT_MAX; 221 | } 222 | 223 | if ($chksum > 0) { 224 | $chksum = -($chksum); 225 | } else { 226 | $chksum = abs($chksum); 227 | } 228 | 229 | $chksum -= 1; 230 | while ($chksum < 0) { 231 | $chksum += self::USHRT_MAX; 232 | } 233 | 234 | return pack('S', $chksum); 235 | } 236 | 237 | /** 238 | * This function puts a the parts that make up a packet together and 239 | * packs them into a byte string 240 | * 241 | * @inheritdoc 242 | */ 243 | static public function createHeader($command, $chksum, $session_id, $reply_id, $command_string) 244 | { 245 | $buf = pack('SSSS', $command, $chksum, $session_id, $reply_id) . $command_string; 246 | 247 | $buf = unpack('C' . (8 + strlen($command_string)) . 'c', $buf); 248 | 249 | $u = unpack('S', self::createChkSum($buf)); 250 | 251 | if (is_array($u)) { 252 | $u = reset($u); 253 | } 254 | $chksum = $u; 255 | 256 | $reply_id += 1; 257 | 258 | if ($reply_id >= self::USHRT_MAX) { 259 | $reply_id -= self::USHRT_MAX; 260 | } 261 | 262 | $buf = pack('SSSS', $command, $chksum, $session_id, $reply_id); 263 | 264 | return $buf . $command_string; 265 | 266 | } 267 | 268 | /** 269 | * Checks a returned packet to see if it returned Util::CMD_ACK_OK, 270 | * indicating success 271 | * 272 | * @inheritdoc 273 | */ 274 | static public function checkValid($reply) 275 | { 276 | $u = unpack('H2h1/H2h2', substr($reply, 0, 8)); 277 | 278 | $command = hexdec($u['h2'] . $u['h1']); 279 | /** TODO: Some device can return 'Connection unauthorized' then should check also */ 280 | if ($command == self::CMD_ACK_OK || $command == self::CMD_ACK_UNAUTH) { 281 | return true; 282 | } else { 283 | return false; 284 | } 285 | } 286 | 287 | /** 288 | * Get User Role string 289 | * @param integer $role 290 | * @return string 291 | */ 292 | static public function getUserRole($role) 293 | { 294 | switch ($role) { 295 | case self::LEVEL_USER: 296 | $ret = 'User'; 297 | break; 298 | case self::LEVEL_ADMIN: 299 | $ret = 'Admin'; 300 | break; 301 | default: 302 | $ret = 'Unknown'; 303 | } 304 | 305 | return $ret; 306 | } 307 | 308 | /** 309 | * Get Attendance State string 310 | * @param integer $state 311 | * @return string 312 | */ 313 | static public function getAttState($state) 314 | { 315 | switch ($state) { 316 | case self::ATT_STATE_FINGERPRINT: 317 | $ret = 'Fingerprint'; 318 | break; 319 | case self::ATT_STATE_PASSWORD: 320 | $ret = 'Password'; 321 | break; 322 | case self::ATT_STATE_CARD: 323 | $ret = 'Card'; 324 | break; 325 | default: 326 | $ret = 'Unknown'; 327 | } 328 | 329 | return $ret; 330 | } 331 | 332 | /** 333 | * Get Attendance Type string 334 | * @param integer $type 335 | * @return string 336 | */ 337 | static public function getAttType($type) 338 | { 339 | switch ($type) { 340 | case self::ATT_TYPE_CHECK_IN: 341 | $ret = 'Check-in'; 342 | break; 343 | case self::ATT_TYPE_CHECK_OUT: 344 | $ret = 'Check-out'; 345 | break; 346 | case self::ATT_TYPE_OVERTIME_IN: 347 | $ret = 'Overtime-in'; 348 | break; 349 | case self::ATT_TYPE_OVERTIME_OUT: 350 | $ret = 'Overtime-out'; 351 | break; 352 | default: 353 | $ret = 'Undefined'; 354 | } 355 | 356 | return $ret; 357 | } 358 | 359 | /** 360 | * Receive data from device 361 | * @param ZKTeco $self 362 | * @param int $maxErrors 363 | * @param bool $first if 'true' don't remove first 4 bytes for first row 364 | * @return string 365 | */ 366 | static public function recData(ZKTeco $self, $maxErrors = 10, $first = true) 367 | { 368 | $data = ''; 369 | $bytes = self::getSize($self); 370 | 371 | if ($bytes) { 372 | $received = 0; 373 | $errors = 0; 374 | 375 | while ($bytes > $received) { 376 | $ret = @socket_recvfrom($self->_zkclient, $dataRec, 1032, 0, $self->_ip, $self->_port); 377 | 378 | if ($ret === false) { 379 | if ($errors < $maxErrors) { 380 | //try again if false 381 | $errors++; 382 | sleep(1); 383 | continue; 384 | } else { 385 | //return empty if has maximum count of errors 386 | self::logReceived($self, $received, $bytes); 387 | unset($data); 388 | return ''; 389 | } 390 | } 391 | 392 | if ($first === false) { 393 | //The first 4 bytes don't seem to be related to the user 394 | $dataRec = substr($dataRec, 8); 395 | } 396 | 397 | $data .= $dataRec; 398 | $received += strlen($dataRec); 399 | 400 | unset($dataRec); 401 | $first = false; 402 | } 403 | 404 | //flush socket 405 | @socket_recvfrom($self->_zkclient, $dataRec, 1024, 0, $self->_ip, $self->_port); 406 | unset($dataRec); 407 | } 408 | 409 | return $data; 410 | } 411 | 412 | /** 413 | * @param ZKTeco $self 414 | * @param int $received 415 | * @param int $bytes 416 | */ 417 | static private function logReceived(ZKTeco $self, $received, $bytes) 418 | { 419 | self::logger($self, 'Received: ' . $received . ' of ' . $bytes . ' bytes'); 420 | } 421 | 422 | /** 423 | * Write log 424 | * @param ZKTeco $self 425 | * @param string $str 426 | */ 427 | static private function logger(ZKTeco $self, $str) 428 | { 429 | if (defined('ZK_LIB_LOG')) { 430 | //use constant if defined 431 | $log = ZK_LIB_LOG; 432 | } else { 433 | $dir = dirname(dirname(__FILE__)); 434 | $log = $dir . '/logs/error.log'; 435 | } 436 | 437 | $row = '<' . $self->_ip . '> [' . date('d.m.Y H:i:s') . '] '; 438 | $row .= (empty($self->_section) ? '' : '(' . $self->_section . ') '); 439 | $row .= $str; 440 | $row .= PHP_EOL; 441 | 442 | file_put_contents($log, $row, FILE_APPEND); 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /src/Lib/Helper/Device.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 18 | 19 | $command = Util::CMD_DEVICE; 20 | $command_string = '~DeviceName'; 21 | 22 | return $self->_command($command, $command_string); 23 | } 24 | 25 | /** 26 | * Enable the device. 27 | * 28 | * @param ZKTeco $self The instance of the ZKTeco class. 29 | * @return bool|mixed Returns true if the device is enabled successfully, false otherwise. 30 | */ 31 | static public function enable(ZKTeco $self) 32 | { 33 | $self->_section = __METHOD__; 34 | 35 | $command = Util::CMD_ENABLE_DEVICE; 36 | $command_string = ''; 37 | 38 | return $self->_command($command, $command_string); 39 | } 40 | 41 | /** 42 | * Disable the device. 43 | * 44 | * @param ZKTeco $self The instance of the ZKTeco class. 45 | * @return bool|mixed Returns true if the device is disabled successfully, false otherwise. 46 | */ 47 | static public function disable(ZKTeco $self) 48 | { 49 | $self->_section = __METHOD__; 50 | 51 | $command = Util::CMD_DISABLE_DEVICE; 52 | $command_string = chr(0) . chr(0); 53 | 54 | return $self->_command($command, $command_string); 55 | } 56 | 57 | /** 58 | * Power off the device. 59 | * 60 | * @param ZKTeco $self The instance of the ZKTeco class. 61 | * @return bool|mixed Returns true if the device is powered off successfully, false otherwise. 62 | */ 63 | public static function powerOff(ZKTeco $self) 64 | { 65 | $self->_section = __METHOD__; 66 | 67 | $command = Util::CMD_POWEROFF; 68 | $command_string = chr(0) . chr(0); 69 | return $self->_command($command, $command_string); 70 | } 71 | 72 | /** 73 | * Restart the device. 74 | * 75 | * @param ZKTeco $self The instance of the ZKTeco class. 76 | * @return bool|mixed Returns true if the device is restarted successfully, false otherwise. 77 | */ 78 | public static function restart(ZKTeco $self) 79 | { 80 | $self->_section = __METHOD__; 81 | 82 | $command = Util::CMD_RESTART; 83 | $command_string = chr(0) . chr(0); 84 | return $self->_command($command, $command_string); 85 | } 86 | 87 | /** 88 | * Sleep the device. 89 | * 90 | * @param ZKTeco $self The instance of the ZKTeco class. 91 | * @return bool|mixed Returns true if the device is put to sleep successfully, false otherwise. 92 | */ 93 | public static function sleep(ZKTeco $self) 94 | { 95 | $self->_section = __METHOD__; 96 | 97 | $command = Util::CMD_SLEEP; 98 | $command_string = chr(0) . chr(0); 99 | return $self->_command($command, $command_string); 100 | } 101 | 102 | /** 103 | * Resume the device from sleep. 104 | * 105 | * @param ZKTeco $self The instance of the ZKTeco class. 106 | * @return bool|mixed Returns true if the device is resumed successfully, false otherwise. 107 | */ 108 | public static function resume(ZKTeco $self) 109 | { 110 | $self->_section = __METHOD__; 111 | 112 | $command = Util::CMD_RESUME; 113 | $command_string = chr(0) . chr(0); 114 | return $self->_command($command, $command_string); 115 | } 116 | 117 | /** 118 | * Test the device's voice. 119 | * 120 | * @param ZKTeco $self The instance of the ZKTeco class. 121 | * @return bool|mixed Returns true if the device's voice test is successful, false otherwise. 122 | */ 123 | public static function testVoice(ZKTeco $self) 124 | { 125 | $self->_section = __METHOD__; 126 | 127 | $command = Util::CMD_TESTVOICE; 128 | $command_string = chr(0) . chr(0); 129 | return $self->_command($command, $command_string); 130 | } 131 | 132 | /** 133 | * Clear the device's LCD screen. 134 | * 135 | * @param ZKTeco $self The instance of the ZKTeco class. 136 | * @return bool|mixed Returns true if the LCD screen is cleared successfully, false otherwise. 137 | */ 138 | public static function clearLCD(ZKTeco $self) 139 | { 140 | $self->_section = __METHOD__; 141 | 142 | $command = Util::CMD_CLEAR_LCD; 143 | return $self->_command($command, ''); 144 | } 145 | 146 | /** 147 | * Write text into the device's LCD screen. 148 | * 149 | * @param ZKTeco $self The instance of the ZKTeco class. 150 | * @param int $rank Line number of text. 151 | * @param string $text Text which will be displayed on the LCD screen. 152 | * @return bool|mixed Returns true if the text is written to the LCD successfully, false otherwise. 153 | */ 154 | public static function writeLCD(ZKTeco $self, $rank, $text) 155 | { 156 | $self->_section = __METHOD__; 157 | 158 | $command = Util::CMD_WRITE_LCD; 159 | $byte1 = chr((int)($rank % 256)); 160 | $byte2 = chr((int)($rank >> 8)); 161 | $byte3 = chr(0); 162 | $command_string = $byte1.$byte2.$byte3.' '.$text; 163 | return $self->_command($command, $command_string); 164 | } 165 | 166 | /** 167 | * Display custom message on LCD screen with formatting options. 168 | * 169 | * @param ZKTeco $self ZKTeco instance. 170 | * @param string $message Message to display. 171 | * @param int $line Line number (1-4). 172 | * @param int $duration Display duration in seconds (0 = permanent). 173 | * @return bool Success status. 174 | */ 175 | public static function displayCustomMessage(ZKTeco $self, $message, $line = 1, $duration = 0) 176 | { 177 | $self->_section = __METHOD__; 178 | 179 | if ($line < 1 || $line > 4 || strlen($message) > 32) { 180 | return false; 181 | } 182 | 183 | $success = self::writeLCD($self, $line, $message); 184 | 185 | if ($success && $duration > 0) { 186 | // Schedule message clearing after duration 187 | self::_scheduleMessageClear($self, $line, $duration); 188 | } 189 | 190 | return $success; 191 | } 192 | 193 | /** 194 | * Open door remotely. 195 | * 196 | * @param ZKTeco $self ZKTeco instance. 197 | * @param int $doorId Door ID (default 1). 198 | * @return bool Success status. 199 | */ 200 | public static function openDoor(ZKTeco $self, $doorId = 1) 201 | { 202 | $self->_section = __METHOD__; 203 | 204 | $command = Util::CMD_DOOR_CONTROL; 205 | $command_string = chr($doorId) . chr(Util::DOOR_ACTION_OPEN) . chr(0) . chr(0); 206 | 207 | return $self->_command($command, $command_string); 208 | } 209 | 210 | /** 211 | * Close door remotely. 212 | * 213 | * @param ZKTeco $self ZKTeco instance. 214 | * @param int $doorId Door ID (default 1). 215 | * @return bool Success status. 216 | */ 217 | public static function closeDoor(ZKTeco $self, $doorId = 1) 218 | { 219 | $self->_section = __METHOD__; 220 | 221 | $command = Util::CMD_DOOR_CONTROL; 222 | $command_string = chr($doorId) . chr(Util::DOOR_ACTION_CLOSE) . chr(0) . chr(0); 223 | 224 | return $self->_command($command, $command_string); 225 | } 226 | 227 | /** 228 | * Lock door remotely. 229 | * 230 | * @param ZKTeco $self ZKTeco instance. 231 | * @param int $doorId Door ID (default 1). 232 | * @return bool Success status. 233 | */ 234 | public static function lockDoor(ZKTeco $self, $doorId = 1) 235 | { 236 | $self->_section = __METHOD__; 237 | 238 | $command = Util::CMD_DOOR_CONTROL; 239 | $command_string = chr($doorId) . chr(Util::DOOR_ACTION_LOCK) . chr(0) . chr(0); 240 | 241 | return $self->_command($command, $command_string); 242 | } 243 | 244 | /** 245 | * Unlock door remotely. 246 | * 247 | * @param ZKTeco $self ZKTeco instance. 248 | * @param int $doorId Door ID (default 1). 249 | * @return bool Success status. 250 | */ 251 | public static function unlockDoor(ZKTeco $self, $doorId = 1) 252 | { 253 | $self->_section = __METHOD__; 254 | 255 | $command = Util::CMD_DOOR_CONTROL; 256 | $command_string = chr($doorId) . chr(Util::DOOR_ACTION_UNLOCK) . chr(0) . chr(0); 257 | 258 | return $self->_command($command, $command_string); 259 | } 260 | 261 | /** 262 | * Get door status. 263 | * 264 | * @param ZKTeco $self ZKTeco instance. 265 | * @param int $doorId Door ID (default 1). 266 | * @return array Door status information. 267 | */ 268 | public static function getDoorStatus(ZKTeco $self, $doorId = 1) 269 | { 270 | $self->_section = __METHOD__; 271 | 272 | $command = Util::CMD_DOOR_STATUS; 273 | $command_string = chr($doorId) . chr(0) . chr(0) . chr(0); 274 | 275 | $result = $self->_command($command, $command_string); 276 | 277 | if ($result === false) { 278 | return ['error' => 'Failed to get door status']; 279 | } 280 | 281 | return self::_parseDoorStatus($result); 282 | } 283 | 284 | /** 285 | * Synchronize device time with server timezone. 286 | * 287 | * @param ZKTeco $self ZKTeco instance. 288 | * @param string $timezone Timezone identifier (e.g., 'America/New_York'). 289 | * @return bool Success status. 290 | */ 291 | public static function syncTimeZone(ZKTeco $self, $timezone = null) 292 | { 293 | $self->_section = __METHOD__; 294 | 295 | if ($timezone === null) { 296 | $timezone = date_default_timezone_get(); 297 | } 298 | 299 | try { 300 | $dateTime = new \DateTime('now', new \DateTimeZone($timezone)); 301 | $timeString = $dateTime->format('Y-m-d H:i:s'); 302 | 303 | return Time::set($self, $timeString); 304 | } catch (\Exception $e) { 305 | return false; 306 | } 307 | } 308 | 309 | /** 310 | * Get real-time events from device. 311 | * 312 | * @param ZKTeco $self ZKTeco instance. 313 | * @param int $timeout Timeout in seconds. 314 | * @return array Real-time events. 315 | */ 316 | public static function getRealTimeEvents(ZKTeco $self, $timeout = 30) 317 | { 318 | $self->_section = __METHOD__; 319 | 320 | $command = Util::CMD_REG_EVENT; 321 | $command_string = chr(1) . chr(0) . chr(0) . chr(0); // Enable real-time events 322 | 323 | $result = $self->_command($command, $command_string); 324 | 325 | if ($result === false) { 326 | return []; 327 | } 328 | 329 | return self::_pollEvents($self, $timeout); 330 | } 331 | 332 | /** 333 | * Start real-time event monitoring. 334 | * 335 | * @param ZKTeco $self ZKTeco instance. 336 | * @param callable $callback Callback function to handle events. 337 | * @param int $timeout Monitoring timeout in seconds. 338 | * @return bool Success status. 339 | */ 340 | public static function startEventMonitoring(ZKTeco $self, callable $callback, $timeout = 0) 341 | { 342 | $self->_section = __METHOD__; 343 | 344 | $command = Util::CMD_REG_EVENT; 345 | $command_string = chr(1) . chr(0) . chr(0) . chr(0); 346 | 347 | $result = $self->_command($command, $command_string); 348 | 349 | if ($result === false) { 350 | return false; 351 | } 352 | 353 | $startTime = time(); 354 | 355 | while ($timeout === 0 || (time() - $startTime) < $timeout) { 356 | $events = self::_pollEvents($self, 5); 357 | 358 | foreach ($events as $event) { 359 | call_user_func($callback, $event); 360 | } 361 | 362 | usleep(100000); // 100ms delay 363 | } 364 | 365 | return true; 366 | } 367 | 368 | /** 369 | * Stop real-time event monitoring. 370 | * 371 | * @param ZKTeco $self ZKTeco instance. 372 | * @return bool Success status. 373 | */ 374 | public static function stopEventMonitoring(ZKTeco $self) 375 | { 376 | $self->_section = __METHOD__; 377 | 378 | $command = Util::CMD_REG_EVENT; 379 | $command_string = chr(0) . chr(0) . chr(0) . chr(0); // Disable real-time events 380 | 381 | return $self->_command($command, $command_string); 382 | } 383 | 384 | /** 385 | * Schedule message clearing after duration. 386 | * 387 | * @param ZKTeco $self ZKTeco instance. 388 | * @param int $line Line number. 389 | * @param int $duration Duration in seconds. 390 | */ 391 | private static function _scheduleMessageClear(ZKTeco $self, $line, $duration) 392 | { 393 | // This would typically be handled by a background process or queue 394 | // For now, we'll use a simple approach 395 | register_shutdown_function(function() use ($self, $line, $duration) { 396 | sleep($duration); 397 | self::writeLCD($self, $line, ''); 398 | }); 399 | } 400 | 401 | /** 402 | * Parse door status response. 403 | * 404 | * @param string $data Raw door status data. 405 | * @return array Parsed door status. 406 | */ 407 | private static function _parseDoorStatus($data) 408 | { 409 | if (strlen($data) < 4) { 410 | return ['error' => 'Invalid door status data']; 411 | } 412 | 413 | $status = ord($data[0]); 414 | $lockStatus = ord($data[1]); 415 | $sensorStatus = ord($data[2]); 416 | 417 | return [ 418 | 'door_open' => ($status & 1) === 1, 419 | 'door_locked' => ($lockStatus & 1) === 1, 420 | 'sensor_active' => ($sensorStatus & 1) === 1, 421 | 'alarm_active' => ($status & 2) === 2, 422 | 'timestamp' => date('Y-m-d H:i:s') 423 | ]; 424 | } 425 | 426 | /** 427 | * Poll for real-time events. 428 | * 429 | * @param ZKTeco $self ZKTeco instance. 430 | * @param int $timeout Timeout in seconds. 431 | * @return array Events array. 432 | */ 433 | private static function _pollEvents(ZKTeco $self, $timeout) 434 | { 435 | $events = []; 436 | $startTime = time(); 437 | 438 | while ((time() - $startTime) < $timeout) { 439 | try { 440 | $ret = @socket_recvfrom($self->_zkclient, $data, 1024, MSG_DONTWAIT, $self->_ip, $self->_port); 441 | 442 | if ($ret !== false && strlen($data) > 8) { 443 | $event = self::_parseEvent($data); 444 | if ($event) { 445 | $events[] = $event; 446 | } 447 | } 448 | } catch (\Exception $e) { 449 | // Continue polling 450 | } 451 | 452 | usleep(50000); // 50ms delay 453 | } 454 | 455 | return $events; 456 | } 457 | 458 | /** 459 | * Parse real-time event data. 460 | * 461 | * @param string $data Raw event data. 462 | * @return array|null Parsed event or null if invalid. 463 | */ 464 | private static function _parseEvent($data) 465 | { 466 | if (strlen($data) < 16) { 467 | return null; 468 | } 469 | 470 | $eventType = ord($data[8]); 471 | $uid = ord($data[9]) + (ord($data[10]) << 8); 472 | $timestamp = ord($data[11]) + (ord($data[12]) << 8) + (ord($data[13]) << 16) + (ord($data[14]) << 24); 473 | $state = ord($data[15]); 474 | 475 | return [ 476 | 'type' => self::_getEventTypeName($eventType), 477 | 'uid' => $uid, 478 | 'timestamp' => Util::decodeTime($timestamp), 479 | 'state' => $state, 480 | 'raw_data' => bin2hex($data) 481 | ]; 482 | } 483 | 484 | /** 485 | * Get event type name. 486 | * 487 | * @param int $eventType Event type code. 488 | * @return string Event type name. 489 | */ 490 | private static function _getEventTypeName($eventType) 491 | { 492 | $eventTypes = [ 493 | 1 => 'attendance', 494 | 2 => 'door_open', 495 | 3 => 'door_close', 496 | 4 => 'alarm', 497 | 5 => 'user_enroll', 498 | 6 => 'user_delete', 499 | 7 => 'system_start', 500 | 8 => 'system_shutdown' 501 | ]; 502 | 503 | return $eventTypes[$eventType] ?? 'unknown'; 504 | } 505 | } 506 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![Packagist Downloads](https://img.shields.io/packagist/dt/jmrashed/zkteco) 4 | ![GitHub stars](https://img.shields.io/github/stars/jmrashed/zkteco) 5 | ![GitHub forks](https://img.shields.io/github/forks/jmrashed/zkteco) 6 | ![License](https://img.shields.io/github/license/jmrashed/zkteco) 7 | ![Latest Stable Version](https://img.shields.io/packagist/v/jmrashed/zkteco) 8 | ![GitHub issues](https://img.shields.io/github/issues/jmrashed/zkteco) 9 | ![GitHub closed issues](https://img.shields.io/github/issues-closed/jmrashed/zkteco) 10 | ![GitHub pull requests](https://img.shields.io/github/issues-pr/jmrashed/zkteco) 11 | ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/jmrashed/zkteco) 12 | 13 | 14 | 15 | 16 | 17 | This Laravel package provides convenient functions for interacting with ZKTeco devices, allowing seamless communication with attendance devices (such as fingerprint, face recognition, or RFID) using UDP protocol. It simplifies the process of reading and writing data directly to these devices from a web server without the need for additional programs. 18 | 19 | With this package, you can easily perform various activities with ZKTeco devices, such as retrieving attendance logs, setting user data, enabling or disabling device functions, and more, all within your Laravel application. 20 | 21 | Designed as a class-based library, you can simply create an object of the provided class and utilize its functions to interact with ZKTeco devices effortlessly. 22 | 23 | Key features include: 24 | 25 | - Reading and writing data to attendance devices using UDP protocol. 26 | - Seamless communication between web servers and attendance devices. 27 | - Simplified implementation for activities such as retrieving attendance logs, setting user data, and managing device functions. 28 | - Integration with Laravel framework for easy usage and compatibility. 29 | 30 | 31 | Experience streamlined communication and management of ZKTeco devices directly from your Laravel application with this ZKTeco Laravel package. 32 | 33 | 34 | 35 | The `jmrashed/zkteco` package provides easy to use functions to ZKTeco Device activities. 36 | 37 | ## Prerequisites 38 | 39 | - PHP installed on your system 40 | - Access to the `Jmrashed\Zkteco\Lib\ZKTeco` class 41 | - Knowledge of the ZKTeco device IP address and port (if different from the default) 42 | 43 | ## Installation 44 | 45 | To use the ZKTeco library, you need to include it in your PHP project. You can install it via Composer: 46 | ```bash 47 | composer require jmrashed/zkteco 48 | ``` 49 | 50 | ## Enabling PHP Sockets 51 | 52 | This guide outlines the steps to enable PHP sockets on your server. Sockets are essential for establishing communication channels between different processes or computers over a network. 53 | 54 | ### Prerequisites 55 | 56 | - PHP installed on your server 57 | - Access to the `php.ini` configuration file 58 | - Basic knowledge of server administration 59 | 60 | ### Steps 61 | 62 | 1. **Check PHP Installation**: Verify that PHP is installed on your server by running `php -v` in your terminal or command prompt. 63 | 64 | 2. **Enable Sockets Extension**: Edit the `php.ini` file to enable the sockets extension. Find the following line: 65 | 66 | ```ini 67 | ;extension=sockets 68 | ``` 69 | 70 | Remove the semicolon at the beginning of the line to uncomment it: 71 | 72 | ```ini 73 | extension=sockets 74 | ``` 75 | 76 | 3. **Restart Web Server**: After editing `php.ini`, restart your web server to apply the changes. Use the appropriate command based on your server software (e.g., Apache, Nginx). 77 | 78 | 4. **Verify Installation**: Create a PHP file (e.g., `test.php`) with the following contents: 79 | 80 | ```php 81 | connect(); 131 | ``` 132 | ## 2. Disconnect from Device 133 | ```php 134 | // Disconnect from the ZKTeco device 135 | // Returns a boolean indicating whether the disconnection was successful 136 | $disconnected = $zk->disconnect(); 137 | ``` 138 | ## 3. Enable Device 139 | ```php 140 | // Enable the ZKTeco device 141 | // Returns a boolean or mixed value indicating whether the device was enabled 142 | // Note: This method should be called after reading/writing any device information 143 | $enabled = $zk->enableDevice(); 144 | ``` 145 | Note: It's important to call the enableDevice() method after any read or write operation on the device. 146 | ## 4. Disable Device 147 | ```php 148 | // Disable the ZKTeco device 149 | // Returns a boolean or mixed value indicating whether the device was disabled 150 | // Note: This method should be called before reading or writing any device information 151 | $disabled = $zk->disableDevice(); 152 | ``` 153 | 154 | ## 5. Device Version 155 | ```php 156 | // Get the firmware version of the ZKTeco device 157 | // Returns a boolean or mixed value containing the device version information 158 | $version = $zk->version(); 159 | ``` 160 | 161 | 162 | ## 6. Device OS Version 163 | ```php 164 | // Get the operating system version of the ZKTeco device 165 | // Returns a boolean or mixed value containing the device OS version information 166 | $osVersion = $zk->osVersion(); 167 | ``` 168 | 169 | 170 | ## 7. Power Off 171 | ```php 172 | // Turn off the ZKTeco device 173 | // Returns a boolean or mixed value indicating whether the device shutdown was successful 174 | $shutdown = $zk->shutdown(); 175 | ``` 176 | 177 | 178 | ## 8. Restart 179 | ```php 180 | // Restart the ZKTeco device 181 | // Returns a boolean or mixed value indicating whether the device restart was successful 182 | $restart = $zk->restart(); 183 | ``` 184 | 185 | ## 9. Sleep 186 | ```php 187 | // Put the ZKTeco device into sleep mode 188 | // Returns a boolean or mixed value indicating whether the device entered sleep mode 189 | $sleep = $zk->sleep(); 190 | ``` 191 | 192 | ## 10. Resume 193 | ```php 194 | // Resume the ZKTeco device from sleep mode 195 | // Returns a boolean or mixed value indicating whether the device resumed from sleep mode 196 | $resume = $zk->resume(); 197 | ``` 198 | 199 | ## 11. Voice Test 200 | ```php 201 | // Test the voice functionality of the ZKTeco device by saying "Thank you" 202 | // Returns a boolean or mixed value indicating whether the voice test was successful 203 | $voiceTest = $zk->testVoice(); 204 | ``` 205 | 206 | 207 | ## 12. Platform 208 | ```php 209 | // Get the platform information of the ZKTeco device 210 | // Returns a boolean or mixed value containing the platform information 211 | $platform = $zk->platform(); 212 | ``` 213 | 214 | 215 | ## 13. Firmware Version 216 | ```php 217 | // Get the firmware version of the ZKTeco device 218 | // Returns a boolean or mixed value containing the firmware version information 219 | $fmVersion = $zk->fmVersion(); 220 | ``` 221 | 222 | 223 | 224 | 225 | ## 14. Work Code 226 | ```php 227 | // Get the work code information of the ZKTeco device 228 | // Returns a boolean or mixed value containing the work code information 229 | $workCode = $zk->workCode(); 230 | 231 | ``` 232 | 233 | 234 | 235 | ## 15. Device Name 236 | ```php 237 | // Get the name of the ZKTeco device 238 | // Returns a boolean or mixed value containing the device name information 239 | $deviceName = $zk->deviceName(); 240 | 241 | ``` 242 | 243 | 244 | ## 16. Get Device Time 245 | ```php 246 | // Get the current time of the ZKTeco device 247 | // Returns a boolean or mixed value containing the device time information 248 | // Format: "Y-m-d H:i:s" 249 | $deviceTime = $zk->getTime(); 250 | 251 | ``` 252 | 253 | 254 | ## 17. Set Device Time 255 | ```php 256 | // Set the time of the ZKTeco device 257 | // Parameters: 258 | // - string $t: Time string in format "Y-m-d H:i:s" 259 | // Returns a boolean or mixed value indicating whether the device time was successfully set 260 | $setTimeResult = $zk->setTime($timeString); 261 | ``` 262 | 263 | 264 | ## 18. Get Users 265 | ```php 266 | // Get the list of users stored in the ZKTeco device 267 | // Returns an array containing user information 268 | $users = $zk->getUser(); 269 | ``` 270 | 271 | ## 19. Set Users 272 | ```php 273 | // Set a user in the ZKTeco device 274 | // Parameters: 275 | // - int $uid: Unique ID (max 65535) 276 | // - int|string $userid: ID in DB (max length = 9, only numbers - depends device setting) 277 | // - string $name: User name (max length = 24) 278 | // - int|string $password: Password (max length = 8, only numbers - depends device setting) 279 | // - int $role: User role (default Util::LEVEL_USER) 280 | // - int $cardno: Card number (default 0, max length = 10, only numbers) 281 | // Returns a boolean or mixed value indicating whether the user was successfully set 282 | $setUserResult = $zk->setUser($uid, $userid, $name, $password, $role, $cardno); 283 | ``` 284 | 285 | ## 20. Clear All Admin 286 | ```php 287 | // Remove all admin users from the ZKTeco device 288 | // Returns a boolean or mixed value indicating whether all admin users were successfully removed 289 | $clearedAdmin = $zk->clearAdmin(); 290 | ``` 291 | 292 | ## 21. Clear All Users 293 | ```php 294 | // Remove all users from the ZKTeco device 295 | // Returns a boolean or mixed value indicating whether all users were successfully removed 296 | $clearedUsers = $zk->clearUsers(); 297 | ``` 298 | 299 | ## 22. Remove A User 300 | ```php 301 | // Remove a user from the ZKTeco device by UID 302 | // Parameters: 303 | // - integer $uid: User ID to remove 304 | // Returns a boolean or mixed value indicating whether the user was successfully removed 305 | $removedUser = $zk->removeUser($uid); 306 | ``` 307 | 308 | ## 23. Get Attendance Log 309 | ```php 310 | // Get the attendance log from the ZKTeco device 311 | // Returns an array containing attendance log information 312 | // Each entry in the array represents a single attendance record with fields: uid, id, state, timestamp, and type 313 | $attendanceLog = $zk->getAttendance(); 314 | ``` 315 | 316 | ## 24. Get Todays Attendance Log 317 | 318 | ### 24.1 getTodaysRecords() 319 | ```php 320 | // Get the today attendance log from the ZKTeco device 321 | // Returns an array containing attendance log information 322 | // Each entry in the array represents a single attendance record with fields: uid, id, state, timestamp, and type 323 | $attendanceLog = $zk->getTodaysRecords(); 324 | 325 | ``` 326 | ### Sample Response Example 327 | ```json 328 | array ( 329 | 'uid' => 33, 330 | 'id' => '108', 331 | 'state' => 1, 332 | 'timestamp' => '2024-04-24 18:13:47', 333 | 'type' => 1, 334 | ) 335 | ``` 336 | 337 | ### 24.2 Get today's Records 338 | ```php 339 | public function zkteco() 340 | { 341 | $zk = new ZKTeco('192.168.1.201'); 342 | $connected = $zk->connect(); 343 | $attendanceLog = $zk->getAttendance(); 344 | 345 | // Get today's date 346 | $todayDate = date('Y-m-d'); 347 | 348 | // Filter attendance records for today 349 | $todayRecords = []; 350 | foreach ($attendanceLog as $record) { 351 | // Extract the date from the timestamp 352 | $recordDate = substr($record['timestamp'], 0, 10); 353 | 354 | // Check if the date matches today's date 355 | if ($recordDate === $todayDate) { 356 | $todayRecords[] = $record; 357 | } 358 | } 359 | 360 | // Now $todayRecords contains attendance records for today 361 | Log::alert($todayRecords); 362 | } 363 | ``` 364 | ### 24.3 Get Latest Attendance with Limit 365 | ```php 366 | // Get the 5 latest attendance records 367 | $latestAttendance = $zk->getAttendance(5); 368 | ``` 369 | 370 | ## 24.4 Clear Attendance Log 371 | ```php 372 | // Clear the attendance log from the ZKTeco device 373 | // Returns a boolean or mixed value indicating whether the attendance log was successfully cleared 374 | $clearedAttendance = $zk->clearAttendance(); 375 | ``` 376 | 377 | # Enhanced User Management Features 378 | 379 | ## 25. Parse Fingerprint Template Data 380 | ```php 381 | // Parse raw fingerprint template data into structured format 382 | // Returns array with template metadata and quality score 383 | $parsedTemplate = $zk->parseFingerprintTemplate($rawTemplateData); 384 | 385 | // Example response: 386 | // [ 387 | // 'valid' => true, 388 | // 'template_size' => 512, 389 | // 'uid' => 123, 390 | // 'finger_id' => 1, 391 | // 'flag' => 1, 392 | // 'template_data' => '...', 393 | // 'quality_score' => 85 394 | // ] 395 | ``` 396 | 397 | ## 26. Enroll Fingerprint Template 398 | ```php 399 | // Enroll a new fingerprint template for a user 400 | // Parameters: uid, finger_id (0-9), template_data 401 | $enrolled = $zk->enrollFingerprint(123, 1, $templateData); 402 | ``` 403 | 404 | ## 27. Get User Face Data 405 | ```php 406 | // Retrieve face recognition templates for a user 407 | // Returns array of face templates with quality scores 408 | $faceData = $zk->getFaceData(123); 409 | 410 | // Example response: 411 | // [ 412 | // 50 => [ 413 | // 'template' => '...', 414 | // 'size' => 1024, 415 | // 'quality' => 92 416 | // ] 417 | // ] 418 | ``` 419 | 420 | ## 28. Set User Face Data 421 | ```php 422 | // Set face recognition templates for a user 423 | $faceData = [ 424 | 50 => ['template' => $faceTemplateData] 425 | ]; 426 | $result = $zk->setFaceData(123, $faceData); 427 | ``` 428 | 429 | ## 29. Enroll Face Template 430 | ```php 431 | // Enroll a face recognition template for a user 432 | // Automatically finds available slot 433 | $enrolled = $zk->enrollFaceTemplate(123, $faceTemplateData); 434 | ``` 435 | 436 | ## 30. Get User Card Number 437 | ```php 438 | // Retrieve the card number for a specific user 439 | $cardNumber = $zk->getUserCardNumber(123); 440 | // Returns: "1234567890" or false if not found 441 | ``` 442 | 443 | ## 31. Advanced User Role Management 444 | 445 | ### 31.1 Set User Role with Permissions 446 | ```php 447 | // Set advanced user role with granular permissions 448 | $permissions = ['attendance', 'reports', 'user_management']; 449 | $result = $zk->setUserRole(123, Util::LEVEL_ADMIN, $permissions); 450 | ``` 451 | 452 | ### 31.2 Get User Role Information 453 | ```php 454 | // Get detailed role information for a user 455 | $roleInfo = $zk->getUserRole(123); 456 | 457 | // Example response: 458 | // [ 459 | // 'role_id' => 14, 460 | // 'role_name' => 'Administrator', 461 | // 'permissions' => ['all_access', 'user_management', 'system_config'], 462 | // 'can_enroll' => true, 463 | // 'can_manage_users' => true, 464 | // 'can_view_logs' => true 465 | // ] 466 | ``` 467 | 468 | ### 31.3 Get Available Roles 469 | ```php 470 | // Get all available user roles and their descriptions 471 | $availableRoles = $zk->getAvailableRoles(); 472 | 473 | // Example response: 474 | // [ 475 | // 0 => [ 476 | // 'name' => 'User', 477 | // 'description' => 'Standard user with basic access', 478 | // 'permissions' => ['attendance', 'view_own_records'] 479 | // ], 480 | // 14 => [ 481 | // 'name' => 'Administrator', 482 | // 'description' => 'Full administrative access', 483 | // 'permissions' => ['all_access', 'user_management', 'system_config'] 484 | // ] 485 | // ] 486 | ``` 487 | 488 | ## 32. Quality Assessment 489 | 490 | ### 32.1 Fingerprint Quality 491 | ```php 492 | // Get fingerprint template with quality assessment 493 | $fingerprints = $zk->getFingerprint(123); 494 | foreach ($fingerprints as $fingerId => $template) { 495 | $parsed = $zk->parseFingerprintTemplate($template); 496 | echo "Finger {$fingerId} quality: {$parsed['quality_score']}%"; 497 | } 498 | ``` 499 | 500 | ### 32.2 Face Template Quality 501 | ```php 502 | // Get face templates with quality scores 503 | $faceData = $zk->getFaceData(123); 504 | foreach ($faceData as $faceId => $data) { 505 | echo "Face template {$faceId} quality: {$data['quality']}%"; 506 | } 507 | ``` 508 | 509 | # Advanced Device Management Features 510 | 511 | ## 33. Custom LCD Message Display 512 | 513 | ### 33.1 Display Custom Message 514 | ```php 515 | // Display custom message on LCD screen 516 | // Parameters: message, line (1-4), duration in seconds (0 = permanent) 517 | $result = $zk->displayCustomMessage('Welcome John!', 1, 10); 518 | ``` 519 | 520 | ### 33.2 Display Permanent Message 521 | ```php 522 | // Display permanent message (until manually cleared) 523 | $result = $zk->displayCustomMessage('System Maintenance', 2, 0); 524 | ``` 525 | 526 | ### 33.3 Clear LCD Screen 527 | ```php 528 | // Clear all LCD content 529 | $result = $zk->clearLCD(); 530 | ``` 531 | 532 | ## 34. Door Control Functions 533 | 534 | ### 34.1 Open Door 535 | ```php 536 | // Open door remotely 537 | $result = $zk->openDoor(1); // Door ID 1 538 | ``` 539 | 540 | ### 34.2 Close Door 541 | ```php 542 | // Close door remotely 543 | $result = $zk->closeDoor(1); 544 | ``` 545 | 546 | ### 34.3 Lock Door 547 | ```php 548 | // Lock door remotely 549 | $result = $zk->lockDoor(1); 550 | ``` 551 | 552 | ### 34.4 Unlock Door 553 | ```php 554 | // Unlock door remotely 555 | $result = $zk->unlockDoor(1); 556 | ``` 557 | 558 | ### 34.5 Get Door Status 559 | ```php 560 | // Get current door status 561 | $status = $zk->getDoorStatus(1); 562 | 563 | // Example response: 564 | // [ 565 | // 'door_open' => true, 566 | // 'door_locked' => false, 567 | // 'sensor_active' => true, 568 | // 'alarm_active' => false, 569 | // 'timestamp' => '2024-10-01 15:30:45' 570 | // ] 571 | ``` 572 | 573 | ## 35. Time Zone Synchronization 574 | 575 | ### 35.1 Sync with Server Timezone 576 | ```php 577 | // Sync device time with server's default timezone 578 | $result = $zk->syncTimeZone(); 579 | ``` 580 | 581 | ### 35.2 Sync with Custom Timezone 582 | ```php 583 | // Sync device time with specific timezone 584 | $result = $zk->syncTimeZone('America/New_York'); 585 | $result = $zk->syncTimeZone('Europe/London'); 586 | $result = $zk->syncTimeZone('Asia/Tokyo'); 587 | ``` 588 | 589 | ## 36. Real-time Event Monitoring 590 | 591 | ### 36.1 Get Real-time Events 592 | ```php 593 | // Get real-time events with timeout 594 | $events = $zk->getRealTimeEvents(30); // 30 seconds timeout 595 | 596 | // Example response: 597 | // [ 598 | // [ 599 | // 'type' => 'attendance', 600 | // 'uid' => 123, 601 | // 'timestamp' => '2024-10-01 15:30:45', 602 | // 'state' => 1, 603 | // 'raw_data' => '...' 604 | // ] 605 | // ] 606 | ``` 607 | 608 | ### 36.2 Start Event Monitoring with Callback 609 | ```php 610 | // Start monitoring with custom callback 611 | $callback = function($event) { 612 | echo "Event: {$event['type']} - User: {$event['uid']} - Time: {$event['timestamp']}\n"; 613 | 614 | // Handle different event types 615 | switch($event['type']) { 616 | case 'attendance': 617 | // Process attendance event 618 | break; 619 | case 'door_open': 620 | // Handle door open event 621 | break; 622 | case 'alarm': 623 | // Handle alarm event 624 | break; 625 | } 626 | }; 627 | 628 | // Start monitoring (runs for 60 seconds) 629 | $zk->startEventMonitoring($callback, 60); 630 | ``` 631 | 632 | ### 36.3 Advanced Event Monitoring 633 | ```php 634 | // Get event monitor instance for advanced handling 635 | $monitor = $zk->getEventMonitor(); 636 | 637 | // Register specific event handlers 638 | $monitor->on('attendance', function($event) { 639 | echo "Attendance event for user {$event['uid']}\n"; 640 | }); 641 | 642 | $monitor->on('door_open', function($event) { 643 | echo "Door opened by user {$event['uid']}\n"; 644 | }); 645 | 646 | $monitor->on('alarm', function($event) { 647 | echo "ALARM: {$event['timestamp']}\n"; 648 | // Send notification, log to database, etc. 649 | }); 650 | 651 | // Register handler for all events 652 | $monitor->on('*', function($event) { 653 | // Log all events to database 654 | logEventToDatabase($event); 655 | }); 656 | 657 | // Start monitoring 658 | $monitor->start(); // Runs indefinitely 659 | 660 | // Stop monitoring 661 | $monitor->stop(); 662 | ``` 663 | 664 | ### 36.4 Stop Event Monitoring 665 | ```php 666 | // Stop real-time event monitoring 667 | $result = $zk->stopEventMonitoring(); 668 | ``` 669 | 670 | ## 37. Event Types 671 | 672 | The system supports various event types: 673 | 674 | - **attendance**: User attendance punch (check-in/out) 675 | - **door_open**: Door opened event 676 | - **door_close**: Door closed event 677 | - **alarm**: Security alarm triggered 678 | - **user_enroll**: New user enrolled 679 | - **user_delete**: User deleted 680 | - **system_start**: Device started/rebooted 681 | - **system_shutdown**: Device shutdown 682 | 683 | ## 38. Door Control Constants 684 | 685 | ```php 686 | // Door action constants 687 | Util::DOOR_ACTION_OPEN // Open door 688 | Util::DOOR_ACTION_CLOSE // Close door 689 | Util::DOOR_ACTION_LOCK // Lock door 690 | Util::DOOR_ACTION_UNLOCK // Unlock door 691 | 692 | // Event type constants 693 | Util::EVENT_TYPE_ATTENDANCE // Attendance event 694 | Util::EVENT_TYPE_DOOR_OPEN // Door open event 695 | Util::EVENT_TYPE_DOOR_CLOSE // Door close event 696 | Util::EVENT_TYPE_ALARM // Alarm event 697 | Util::EVENT_TYPE_USER_ENROLL // User enrollment event 698 | Util::EVENT_TYPE_USER_DELETE // User deletion event 699 | Util::EVENT_TYPE_SYSTEM_START // System start event 700 | Util::EVENT_TYPE_SYSTEM_SHUTDOWN // System shutdown event 701 | ``` 702 | 703 | ## 39. Usage Examples 704 | 705 | ### 39.1 Complete Door Management 706 | ```php 707 | // Initialize device 708 | $zk = new ZKTeco('192.168.1.201'); 709 | $zk->connect(); 710 | 711 | // Check door status 712 | $status = $zk->getDoorStatus(1); 713 | if ($status['door_locked']) { 714 | // Unlock door for authorized access 715 | $zk->unlockDoor(1); 716 | $zk->displayCustomMessage('Door Unlocked', 1, 5); 717 | } 718 | 719 | // Open door 720 | $zk->openDoor(1); 721 | 722 | // Wait and close 723 | sleep(10); 724 | $zk->closeDoor(1); 725 | $zk->lockDoor(1); 726 | 727 | $zk->disconnect(); 728 | ``` 729 | 730 | ### 39.2 Real-time Monitoring System 731 | ```php 732 | $zk = new ZKTeco('192.168.1.201'); 733 | $zk->connect(); 734 | 735 | $monitor = $zk->getEventMonitor(); 736 | 737 | // Set up event handlers 738 | $monitor->on('attendance', function($event) { 739 | // Log attendance to database 740 | $user = getUserById($event['uid']); 741 | logAttendance($user, $event['timestamp'], $event['state']); 742 | 743 | // Display welcome message 744 | $zk->displayCustomMessage("Welcome {$user['name']}!", 1, 3); 745 | }); 746 | 747 | $monitor->on('alarm', function($event) { 748 | // Send alert notifications 749 | sendSecurityAlert($event); 750 | 751 | // Display alarm message 752 | $zk->displayCustomMessage('SECURITY ALERT!', 1, 0); 753 | }); 754 | 755 | // Start monitoring 756 | $monitor->start(); 757 | ``` 758 | # Change log 759 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 760 | 761 | # Contributing 762 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 763 | 764 | # Security 765 | If you discover any security-related issues, please email `jmrashed@gmail.com` instead of using the issue tracker. 766 | 767 | # License 768 | The [MIT License (MIT)](LICENSE.md). Please see License File for more information. 769 | 770 | 771 | 772 | # Conclusion 773 | This guide covers various methods provided by the ZKTeco library in PHP for interacting with ZKTeco devices. You can use these methods to perform various operations such as device management, user management, attendance tracking, and more. 774 | 775 | -------------------------------------------------------------------------------- /src/Lib/ZKTeco.php: -------------------------------------------------------------------------------- 1 | _ip = $ip; 45 | $this->_port = $port; 46 | 47 | // Create a UDP socket. 48 | $this->_zkclient = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); 49 | 50 | // Set the receive timeout to 60 seconds and 500 milliseconds. 51 | $timeout = array('sec' => 60, 'usec' => 500000); 52 | socket_set_option($this->_zkclient, SOL_SOCKET, SO_RCVTIMEO, $timeout); 53 | } 54 | 55 | /** 56 | * Create and send command to device 57 | * 58 | * @param string $command 59 | * @param string $command_string 60 | * @param string $type 61 | * @return bool|mixed 62 | */ 63 | public function _command($command, $command_string, $type = Util::COMMAND_TYPE_GENERAL) 64 | { 65 | $chksum = 0; 66 | $session_id = $this->_session_id; 67 | 68 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6/H2h7/H2h8', substr($this->_data_recv, 0, 8)); 69 | $reply_id = hexdec($u['h8'] . $u['h7']); 70 | 71 | $buf = Util::createHeader($command, $chksum, $session_id, $reply_id, $command_string); 72 | 73 | socket_sendto($this->_zkclient, $buf, strlen($buf), 0, $this->_ip, $this->_port); 74 | 75 | try { 76 | @socket_recvfrom($this->_zkclient, $this->_data_recv, 1024, 0, $this->_ip, $this->_port); 77 | 78 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6', substr($this->_data_recv, 0, 8)); 79 | 80 | $ret = false; 81 | $session = hexdec($u['h6'] . $u['h5']); 82 | 83 | if ($type === Util::COMMAND_TYPE_GENERAL && $session_id === $session) { 84 | $ret = substr($this->_data_recv, 8); 85 | } else if ($type === Util::COMMAND_TYPE_DATA && !empty($session)) { 86 | $ret = $session; 87 | } 88 | 89 | return $ret; 90 | } catch (ErrorException $e) { 91 | return false; 92 | } catch (Exception $e) { 93 | return false; 94 | } 95 | } 96 | 97 | /** 98 | * Connects to the device. 99 | * 100 | * @return bool True if successfully connected, otherwise false. 101 | */ 102 | public function connect() 103 | { 104 | // Call the static method connect of the Connect class, passing $this (current instance) 105 | // to connect to the device. 106 | return Connect::connect($this); 107 | } 108 | 109 | /** 110 | * Disconnects from the device. 111 | * 112 | * @return bool True if successfully disconnected, otherwise false. 113 | */ 114 | public function disconnect() 115 | { 116 | // Call the static method disconnect of the Connect class, passing $this (current instance) 117 | // to disconnect from the device. 118 | return Connect::disconnect($this); 119 | } 120 | 121 | /** 122 | * Retrieves the version information of the device. 123 | * 124 | * @return bool|mixed The version information of the device, or the result from Version::get if retrieval fails. 125 | */ 126 | public function version() 127 | { 128 | // Call the static method get of the Version class, passing $this (current instance) 129 | // to retrieve the version information of the device. 130 | return Version::get($this); 131 | } 132 | 133 | /** 134 | * Retrieves the operating system (OS) version from the device. 135 | * 136 | * @return bool|mixed The OS version from the device, or the result from Os::get if retrieval fails. 137 | */ 138 | public function osVersion() 139 | { 140 | // Call the static method get of the Os class, passing $this (current instance) 141 | // to retrieve the OS version from the device. 142 | return Os::get($this); 143 | } 144 | 145 | /** 146 | * Retrieves the platform information from the device. 147 | * 148 | * @return bool|mixed The platform information from the device, or the result from Platform::get if retrieval fails. 149 | */ 150 | public function platform() 151 | { 152 | // Call the static method get of the Platform class, passing $this (current instance) 153 | // to retrieve the platform information from the device. 154 | return Platform::get($this); 155 | } 156 | 157 | /** 158 | * Retrieves the firmware version of the device. 159 | * 160 | * @return bool|mixed The firmware version of the device, or the result from Platform::getVersion if retrieval fails. 161 | */ 162 | public function fmVersion() 163 | { 164 | // Call the static method getVersion of the Platform class, passing $this (current instance) 165 | // to retrieve the firmware version of the device. 166 | return Platform::getVersion($this); 167 | } 168 | 169 | /** 170 | * Retrieves the work code from the device. 171 | * 172 | * @return bool|mixed The work code from the device, or the result from WorkCode::get if retrieval fails. 173 | */ 174 | public function workCode() 175 | { 176 | // Call the static method get of the WorkCode class, passing $this (current instance) 177 | // to retrieve the work code from the device. 178 | return WorkCode::get($this); 179 | } 180 | 181 | /** 182 | * Retrieves the SSR (Self-Service Recorder) information from the device. 183 | * 184 | * @return bool|mixed The SSR information from the device, or the result from Ssr::get if retrieval fails. 185 | */ 186 | public function ssr() 187 | { 188 | // Call the static method get of the Ssr class, passing $this (current instance) 189 | // to retrieve the SSR information from the device. 190 | return Ssr::get($this); 191 | } 192 | 193 | /** 194 | * Retrieves the pin width of the device. 195 | * 196 | * @return bool|mixed The pin width of the device, or the result from Pin::width if retrieval fails. 197 | */ 198 | public function pinWidth() 199 | { 200 | // Call the static method width of the Pin class, passing $this (current instance) 201 | // to retrieve the pin width of the device. 202 | return Pin::width($this); 203 | } 204 | 205 | /** 206 | * Enables the face recognition function on the device. 207 | * 208 | * @return bool|mixed True if the face recognition function was successfully enabled, otherwise returns the result from Face::on. 209 | */ 210 | public function faceFunctionOn() 211 | { 212 | // Call the static method on of the Face class, passing $this (current instance) 213 | // to enable the face recognition function on the device. 214 | return Face::on($this); 215 | } 216 | 217 | /** 218 | * Retrieves the serial number of the device. 219 | * 220 | * @return bool|mixed The serial number of the device, or the result from SerialNumber::get if retrieval fails. 221 | */ 222 | public function serialNumber() 223 | { 224 | // Call the static method get of the SerialNumber class, passing $this (current instance) 225 | // to retrieve the serial number of the device. 226 | return SerialNumber::get($this); 227 | } 228 | 229 | /** 230 | * Retrieves the name of the device. 231 | * 232 | * @return bool|mixed The name of the device, or the result from Device::name if retrieval fails. 233 | */ 234 | public function deviceName() 235 | { 236 | // Call the static method name of the Device class, passing $this (current instance) 237 | // to retrieve the name of the device. 238 | return Device::name($this); 239 | } 240 | 241 | /** 242 | * Disables the device. 243 | * 244 | * @return bool|mixed True if the device was successfully disabled, otherwise returns the result from Device::disable. 245 | */ 246 | public function disableDevice() 247 | { 248 | // Call the static method disable of the Device class, passing $this (current instance) 249 | // to disable the device. 250 | return Device::disable($this); 251 | } 252 | 253 | /** 254 | * Enables the device. 255 | * 256 | * @return bool|mixed True if the device was successfully enabled, otherwise returns the result from Device::enable. 257 | */ 258 | public function enableDevice() 259 | { 260 | // Call the static method enable of the Device class, passing $this (current instance) 261 | // to enable the device. 262 | return Device::enable($this); 263 | } 264 | 265 | /** 266 | * Retrieves user data from the device. 267 | * 268 | * @return array An array containing user data for each user, structured as [userid, name, cardno, uid, role, password]. 269 | */ 270 | public function getUser() 271 | { 272 | // Call the static method get of the User class, passing $this (current instance) 273 | // to retrieve user data from the device. 274 | return User::get($this); 275 | } 276 | 277 | /** 278 | * Sets user data for the specified user. 279 | * 280 | * @param int $uid Unique ID (max 65535) of the user. 281 | * @param int|string $userid ID in DB (same as $uid, max length = 9, only numbers - depends on device setting). 282 | * @param string $name Name of the user (max length = 24). 283 | * @param int|string $password Password for the user (max length = 8, only numbers - depends on device setting). 284 | * @param int $role Default Util::LEVEL_USER. Role of the user (e.g., admin or regular user). 285 | * @param int $cardno Default 0 (max length = 10, only numbers). Card number associated with the user. 286 | * @return bool|mixed True if user data was successfully set, otherwise returns the result from User::set. 287 | */ 288 | public function setUser($uid, $userid, $name, $password, $role = Util::LEVEL_USER, $cardno = 0) 289 | { 290 | // Call the static method set of the User class, passing $this (current instance), 291 | // along with the user data: $uid, $userid, $name, $password, $role, and $cardno. 292 | return User::set($this, $uid, $userid, $name, $password, $role, $cardno); 293 | } 294 | 295 | /** 296 | * Removes all users from the device. 297 | * 298 | * @return bool|mixed True if all users were successfully removed, otherwise returns the result from User::clear. 299 | */ 300 | public function clearUsers() 301 | { 302 | // Call the static method clear of the User class, passing $this (current instance) 303 | // to remove all users from the device. 304 | return User::clear($this); 305 | } 306 | 307 | /** 308 | * Removes the admin privileges from the current user. 309 | * 310 | * @return bool|mixed True if the admin privileges were successfully removed, otherwise returns the result from User::clearAdmin. 311 | */ 312 | public function clearAdmin() 313 | { 314 | // Call the static method clearAdmin of the User class, passing $this (current instance) 315 | // to remove the admin privileges from the current user. 316 | return User::clearAdmin($this); 317 | } 318 | 319 | /** 320 | * Removes a user identified by the specified UID from the device. 321 | * 322 | * @param integer $uid The unique ID of the user to be removed. 323 | * @return bool|mixed True if the user was successfully removed, otherwise returns the result from User::remove. 324 | */ 325 | public function removeUser($uid) 326 | { 327 | // Call the static method remove of the User class, passing $this (current instance) and $uid (unique ID) 328 | // to remove the user from the device. 329 | return User::remove($this, $uid); 330 | } 331 | 332 | /** 333 | * Retrieves the fingerprint data array for the specified UID. 334 | * 335 | * @param integer $uid Unique ID (max 65535) of the user whose fingerprint data will be retrieved. 336 | * @return array Binary fingerprint data array, where the key is finger ID (0-9). 337 | */ 338 | public function getFingerprint($uid) 339 | { 340 | return Fingerprint::get($this, $uid); 341 | } 342 | 343 | /** 344 | * Sets the fingerprint data array for the specified UID. 345 | * 346 | * @param integer $uid Unique ID (max 65535) of the user for whom the fingerprints will be set. 347 | * @param array $data Binary fingerprint data array, where the key is finger ID (0-9) same as returned array from 'getFingerprint' method. 348 | * @return int The count of added fingerprints. 349 | */ 350 | public function setFingerprint($uid, array $data) 351 | { 352 | return Fingerprint::set($this, $uid, $data); 353 | } 354 | 355 | /** 356 | * Parses raw fingerprint template data into a structured format. 357 | * 358 | * @param string $rawData Raw fingerprint template data from device. 359 | * @return array Parsed fingerprint template with metadata. 360 | */ 361 | public function parseFingerprintTemplate($rawData) 362 | { 363 | return Fingerprint::parseTemplate($rawData); 364 | } 365 | 366 | /** 367 | * Enrolls a new fingerprint template for a user. 368 | * 369 | * @param integer $uid User ID. 370 | * @param integer $fingerId Finger ID (0-9). 371 | * @param string $templateData Fingerprint template data. 372 | * @return bool Success status. 373 | */ 374 | public function enrollFingerprint($uid, $fingerId, $templateData) 375 | { 376 | return Fingerprint::enroll($this, $uid, $fingerId, $templateData); 377 | } 378 | 379 | /** 380 | * Removes fingerprints associated with the specified UID and fingers ID array from the device. 381 | * 382 | * @param integer $uid Unique ID (max 65535) of the user whose fingerprints will be removed. 383 | * @param array $data Array containing the fingers ID (0-9) of the fingerprints to be removed. 384 | * @return int The count of deleted fingerprints. 385 | */ 386 | public function removeFingerprint($uid, array $data) 387 | { 388 | // Call the static method remove of the Fingerprint class, passing $this (current instance), 389 | // $uid (unique ID), and $data (fingers ID array) to remove the specified fingerprints. 390 | return Fingerprint::remove($this, $uid, $data); 391 | } 392 | 393 | /** 394 | * Retrieves the attendance log from the device. 395 | * 396 | * @return array An array containing attendance log entries, each entry structured as [uid, id, state, timestamp]. 397 | */ 398 | public function getAttendance() 399 | { 400 | // Call the static method get of the Attendance class, passing $this (current instance) 401 | // to retrieve the attendance log from the device. 402 | return Attendance::get($this); 403 | } 404 | 405 | // Modify the getAttendance() method in the ZKTeco class 406 | public function getAttendanceWithLimit($limit = 10) 407 | { 408 | // Call the static method get of the Attendance class, passing $this (current instance) 409 | // and $limit (number of latest records to retrieve) to retrieve the attendance log from the device. 410 | return Attendance::get($this, $limit); 411 | } 412 | 413 | public static function getTodaysRecords(ZKTeco $self) 414 | { 415 | // Get all attendance records from the device 416 | $attendanceData = self::get($self); 417 | 418 | // Get today's date 419 | $currentDate = date('Y-m-d'); 420 | 421 | // Filter attendance data for today 422 | $todaysAttendance = array_filter($attendanceData, function ($record) use ($currentDate) { 423 | // Assuming the date format in the attendance data is 'Y-m-d H:i:s' 424 | return substr($record['timestamp'], 0, 10) === $currentDate; 425 | }); 426 | 427 | return $todaysAttendance; 428 | } 429 | 430 | /** 431 | * Clears the attendance log of the device. 432 | * 433 | * @return bool|mixed True if the attendance log was successfully cleared, otherwise returns the result from Attendance::clear. 434 | */ 435 | public function clearAttendance() 436 | { 437 | // Call the static method clear of the Attendance class, passing $this (current instance) 438 | // to clear the attendance log of the device. 439 | return Attendance::clear($this); 440 | } 441 | 442 | /** 443 | * Sets the device time to the specified value. 444 | * 445 | * @param string $t The time to set, in the format "Y-m-d H:i:s". 446 | * @return bool|mixed True if the device time was successfully set, otherwise returns the result from Time::set. 447 | */ 448 | public function setTime($t) 449 | { 450 | // Call the static method set of the Time class, passing $this (current instance) 451 | // and the specified time $t to set the device time. 452 | return Time::set($this, $t); 453 | } 454 | 455 | /** 456 | * Retrieves the current time from the device. 457 | * 458 | * @return bool|mixed The current time in the format "Y-m-d H:i:s", or the result from Time::get. 459 | */ 460 | public function getTime() 461 | { 462 | // Call the static method get of the Time class, passing $this (current instance) 463 | // to retrieve the current time from the device. 464 | return Time::get($this); 465 | } 466 | 467 | /** 468 | * Shuts down the device. 469 | * 470 | * @return bool|mixed True if the device was successfully shut down, otherwise returns the result from Device::powerOff. 471 | */ 472 | public function shutdown() 473 | { 474 | // Call the static method powerOff of the Device class, passing $this (current instance) 475 | // to power off the device. 476 | return Device::powerOff($this); 477 | } 478 | 479 | /** 480 | * Restarts the device. 481 | * 482 | * @return bool|mixed True if the device restarted successfully, otherwise returns the result from Device::restart. 483 | */ 484 | public function restart() 485 | { 486 | // Call the static method restart of the Device class, passing $this (current instance) 487 | // to restart the device. 488 | return Device::restart($this); 489 | } 490 | 491 | /** 492 | * Puts the device into sleep mode. 493 | * 494 | * @return bool|mixed True if the device entered sleep mode successfully, otherwise returns the result from Device::sleep. 495 | */ 496 | public function sleep() 497 | { 498 | // Call the static method sleep of the Device class, passing $this (current instance) 499 | // to put the device into sleep mode. 500 | return Device::sleep($this); 501 | } 502 | 503 | /** 504 | * Resumes the device from sleep mode. 505 | * 506 | * @return bool|mixed True if the device was successfully resumed, otherwise returns the result from Device::resume. 507 | */ 508 | public function resume() 509 | { 510 | // Call the static method resume of the Device class, passing $this (current instance) 511 | // to resume the device from sleep mode. 512 | return Device::resume($this); 513 | } 514 | 515 | /** 516 | * Performs a voice test by producing the sound "Thank you". 517 | * 518 | * @return bool|mixed True if the voice test was successful, otherwise returns the result from Device::testVoice. 519 | */ 520 | public function testVoice() 521 | { 522 | // Call the static method testVoice of the Device class, passing $this (current instance) 523 | // to perform the voice test. 524 | return Device::testVoice($this); 525 | } 526 | 527 | /** 528 | * Clears the content displayed on the LCD screen. 529 | * 530 | * @return bool True if the content was successfully cleared, false otherwise. 531 | */ 532 | public function clearLCD() 533 | { 534 | // Call the static method clearLCD of the Device class, passing $this (current instance) 535 | // to identify the LCD screen that needs to be cleared. 536 | return Device::clearLCD($this); 537 | } 538 | 539 | /** 540 | * Writes a welcome message to the LCD screen. 541 | * 542 | * @return bool True if the message was successfully written, false otherwise. 543 | */ 544 | public function writeLCD() 545 | { 546 | // Call the static method writeLCD of the Device class, passing $this (current instance), 547 | // 2 (the LCD screen identifier), and "Welcome Jmrashed" (the message to display). 548 | return Device::writeLCD($this, 2, "Welcome Jmrashed"); 549 | } 550 | 551 | /** 552 | * Retrieves face template data for a specific user. 553 | * 554 | * @param integer $uid User ID. 555 | * @return array Face template data. 556 | */ 557 | public function getFaceData($uid) 558 | { 559 | return Face::getData($this, $uid); 560 | } 561 | 562 | /** 563 | * Sets face template data for a specific user. 564 | * 565 | * @param integer $uid User ID. 566 | * @param array $faceData Face template data. 567 | * @return bool Success status. 568 | */ 569 | public function setFaceData($uid, array $faceData) 570 | { 571 | return Face::setData($this, $uid, $faceData); 572 | } 573 | 574 | /** 575 | * Enrolls face recognition template for a user. 576 | * 577 | * @param integer $uid User ID. 578 | * @param string $templateData Face template data. 579 | * @return bool Success status. 580 | */ 581 | public function enrollFaceTemplate($uid, $templateData) 582 | { 583 | return Face::enrollTemplate($this, $uid, $templateData); 584 | } 585 | 586 | /** 587 | * Retrieves the card number for a specific user. 588 | * 589 | * @param integer $uid User ID. 590 | * @return string|false Card number or false if not found. 591 | */ 592 | public function getUserCardNumber($uid) 593 | { 594 | return User::getCardNumber($this, $uid); 595 | } 596 | 597 | /** 598 | * Sets advanced user role with granular permissions. 599 | * 600 | * @param integer $uid User ID. 601 | * @param integer $role Role level. 602 | * @param array $permissions Additional permissions array. 603 | * @return bool Success status. 604 | */ 605 | public function setUserRole($uid, $role, array $permissions = []) 606 | { 607 | return User::setRole($this, $uid, $role, $permissions); 608 | } 609 | 610 | /** 611 | * Retrieves detailed user role information including permissions. 612 | * 613 | * @param integer $uid User ID. 614 | * @return array Role information with permissions. 615 | */ 616 | public function getUserRole($uid) 617 | { 618 | return User::getRole($this, $uid); 619 | } 620 | 621 | /** 622 | * Retrieves all available user roles and their descriptions. 623 | * 624 | * @return array Available roles with descriptions. 625 | */ 626 | public function getAvailableRoles() 627 | { 628 | return User::getAvailableRoles(); 629 | } 630 | 631 | /** 632 | * Display custom message on device LCD screen. 633 | * 634 | * @param string $message Message to display. 635 | * @param int $line Line number (1-4). 636 | * @param int $duration Display duration in seconds (0 = permanent). 637 | * @return bool Success status. 638 | */ 639 | public function displayCustomMessage($message, $line = 1, $duration = 0) 640 | { 641 | return Device::displayCustomMessage($this, $message, $line, $duration); 642 | } 643 | 644 | /** 645 | * Open door remotely. 646 | * 647 | * @param int $doorId Door ID (default 1). 648 | * @return bool Success status. 649 | */ 650 | public function openDoor($doorId = 1) 651 | { 652 | return Device::openDoor($this, $doorId); 653 | } 654 | 655 | /** 656 | * Close door remotely. 657 | * 658 | * @param int $doorId Door ID (default 1). 659 | * @return bool Success status. 660 | */ 661 | public function closeDoor($doorId = 1) 662 | { 663 | return Device::closeDoor($this, $doorId); 664 | } 665 | 666 | /** 667 | * Lock door remotely. 668 | * 669 | * @param int $doorId Door ID (default 1). 670 | * @return bool Success status. 671 | */ 672 | public function lockDoor($doorId = 1) 673 | { 674 | return Device::lockDoor($this, $doorId); 675 | } 676 | 677 | /** 678 | * Unlock door remotely. 679 | * 680 | * @param int $doorId Door ID (default 1). 681 | * @return bool Success status. 682 | */ 683 | public function unlockDoor($doorId = 1) 684 | { 685 | return Device::unlockDoor($this, $doorId); 686 | } 687 | 688 | /** 689 | * Get door status information. 690 | * 691 | * @param int $doorId Door ID (default 1). 692 | * @return array Door status information. 693 | */ 694 | public function getDoorStatus($doorId = 1) 695 | { 696 | return Device::getDoorStatus($this, $doorId); 697 | } 698 | 699 | /** 700 | * Synchronize device time with server timezone. 701 | * 702 | * @param string $timezone Timezone identifier (e.g., 'America/New_York'). 703 | * @return bool Success status. 704 | */ 705 | public function syncTimeZone($timezone = null) 706 | { 707 | return Device::syncTimeZone($this, $timezone); 708 | } 709 | 710 | /** 711 | * Get real-time events from device. 712 | * 713 | * @param int $timeout Timeout in seconds. 714 | * @return array Real-time events. 715 | */ 716 | public function getRealTimeEvents($timeout = 30) 717 | { 718 | return Device::getRealTimeEvents($this, $timeout); 719 | } 720 | 721 | /** 722 | * Start real-time event monitoring with callback. 723 | * 724 | * @param callable $callback Callback function to handle events. 725 | * @param int $timeout Monitoring timeout in seconds (0 = infinite). 726 | * @return bool Success status. 727 | */ 728 | public function startEventMonitoring(callable $callback, $timeout = 0) 729 | { 730 | return Device::startEventMonitoring($this, $callback, $timeout); 731 | } 732 | 733 | /** 734 | * Stop real-time event monitoring. 735 | * 736 | * @return bool Success status. 737 | */ 738 | public function stopEventMonitoring() 739 | { 740 | return Device::stopEventMonitoring($this); 741 | } 742 | 743 | /** 744 | * Get event monitor instance for advanced event handling. 745 | * 746 | * @return EventMonitor Event monitor instance. 747 | */ 748 | public function getEventMonitor() 749 | { 750 | if ($this->_eventMonitor === null) { 751 | $this->_eventMonitor = new EventMonitor($this); 752 | } 753 | 754 | return $this->_eventMonitor; 755 | } 756 | 757 | } 758 | --------------------------------------------------------------------------------