├── 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 | 
4 | 
5 | 
6 | 
7 | 
8 | 
9 | 
10 | 
11 | 
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 |
--------------------------------------------------------------------------------