├── .gitignore ├── Docs └── CommsProtocol.pdf ├── LICENSE ├── PHPAnviz.php ├── README.md ├── anviz-server_x64 ├── anviz-server_x86 └── config.ini.example /.gitignore: -------------------------------------------------------------------------------- 1 | nbproject 2 | /nbproject/private/ 3 | test.php 4 | config.ini 5 | log.txt 6 | -------------------------------------------------------------------------------- /Docs/CommsProtocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtisler/PHPAnviz/2280c022f6e208427b076b2767ed4957d57fe61e/Docs/CommsProtocol.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 jtisler 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PHPAnviz.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright (c) 2016, Jerko Tisler 10 | * @license https://opensource.org/licenses/MIT MIT 11 | * 12 | */ 13 | class PHPAnviz { 14 | 15 | /** 16 | * CRC Table 17 | * @var array 18 | * @access protected 19 | */ 20 | protected $crc_table = [ 21 | 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, 0x8C48, 0x9DC1, 22 | 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, 0x1081, 0x0108, 0x3393, 0x221A, 23 | 0x56A5, 0x472C, 0x75B7, 0x643E, 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 24 | 0xF9FF, 0xE876, 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD, 25 | 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5, 0x3183, 0x200A, 26 | 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C, 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 27 | 0xFBEF, 0xEA66, 0xD8FD, 0xC974, 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 28 | 0x2732, 0x36BB, 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, 29 | 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A, 0xDECD, 0xCF44, 30 | 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72, 0x6306, 0x728F, 0x4014, 0x519D, 31 | 0x2522, 0x34AB, 0x0630, 0x17B9, 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 32 | 0x8A78, 0x9BF1, 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738, 33 | 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70, 0x8408, 0x9581, 34 | 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7, 0x0840, 0x19C9, 0x2B52, 0x3ADB, 35 | 0x4E64, 0x5FED, 0x6D76, 0x7CFF, 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 36 | 0xF1BF, 0xE036, 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, 37 | 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5, 0x2942, 0x38CB, 38 | 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, 0xB58B, 0xA402, 0x9699, 0x8710, 39 | 0xF3AF, 0xE226, 0xD0BD, 0xC134, 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 40 | 0x5CF5, 0x4D7C, 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3, 41 | 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB, 0xD68D, 0xC704, 42 | 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232, 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 43 | 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 44 | 0x8238, 0x93B1, 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, 45 | 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, 0x7BC7, 0x6A4E, 46 | 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78 47 | ]; 48 | 49 | /** 50 | * Anviz devices are calculating time since 2000-01-01 (for gathering records this it seems it's 2000-01-02 instead of 2000-01-01) 51 | */ 52 | const ANVIZ_EPOCH = 946764000; 53 | 54 | /** 55 | * Operation successfull 56 | */ 57 | const ACK_SUCCESS = 0x00; 58 | 59 | /** 60 | * Operation failed 61 | */ 62 | const ACK_FAIL = 0x01; 63 | 64 | /** 65 | * User full 66 | */ 67 | const ACK_FULL = 0x04; 68 | 69 | /** 70 | * User empty 71 | */ 72 | const ACK_EMPTY = 0x05; 73 | 74 | /** 75 | * User does not exist 76 | */ 77 | const ACK_NO_USER = 0x06; 78 | 79 | /** 80 | * Capture timeout 81 | */ 82 | const ACK_TIME_OUT = 0x08; 83 | 84 | /** 85 | * User already exists 86 | */ 87 | const ACK_USER_OCCUPIED = 0x0A; 88 | 89 | /** 90 | * Fingerprint already exists 91 | */ 92 | const ACK_FINGER_OCCUPIED = 0x0B; 93 | 94 | /** 95 | * Clear all records 96 | */ 97 | const CLEAR_ALL = 0x00; 98 | 99 | /** 100 | * Clear all "new records" flag 101 | */ 102 | const CLEAR_NEW = 0x01; 103 | 104 | /** 105 | * Clear the designated amount of "new records" flag 106 | */ 107 | const CLEAR_NEW_PARTIALY = 0x02; 108 | 109 | /** 110 | * Restart; retrieve all the records (The first data packet must send this data when retrieving all the records) 111 | */ 112 | const DOWNLOAD_ALL = 0x01; 113 | 114 | /** 115 | * Restart; retrieve new records (The first data packet must send this data when retrieving the new records) 116 | */ 117 | const DOWNLOAD_NEW = 0x02; 118 | 119 | /** 120 | * Device ID 121 | * @var hex 122 | * @access private 123 | */ 124 | private $id; 125 | 126 | /** 127 | * Device port 128 | * @var string 129 | * @access private 130 | */ 131 | private $port; 132 | 133 | /** 134 | * Instance of GearmanClient 135 | * @var GearmanClient 136 | * @access private 137 | */ 138 | private $client; 139 | 140 | /** 141 | * Config array 142 | * @var array 143 | * @access private 144 | */ 145 | private $config; 146 | 147 | /** 148 | * Constructor 149 | * @param int $id 150 | * @param int $port 151 | * @param string $configFilePath 152 | */ 153 | function __construct($id, $port, $configFilePath = '') { 154 | //Check if port has : prefix, if not add one 155 | $port = substr($port, 0, 1) != ":" ? ":" . $port : $port; 156 | 157 | $this->id = dechex($id); 158 | $this->port = $port; 159 | 160 | //Create config 161 | $this->config = $this->loadConfig($configFilePath); 162 | 163 | //Create instance of Gearman Client 164 | $this->client = new GearmanClient(); 165 | //Add server 166 | $this->client->addServer($this->config['gearman-server']); 167 | } 168 | 169 | /** 170 | * Calculate crc16 171 | * @param binary $b 172 | * @return string 173 | * @access private 174 | */ 175 | private function crc16($b) { 176 | $crc = 0xFFFF; 177 | 178 | for ($l = 0; $l < strlen($b); $l++) { 179 | $crc ^= ord($b[$l]); 180 | $crc = ($crc >> 8) ^ $this->crc_table[$crc & 255]; 181 | } 182 | 183 | $crc = strtoupper(dechex($crc)); 184 | 185 | //if crc has length less than 4 add leading zero 186 | $crc = sprintf("%04s", $crc); 187 | 188 | return($crc[2] . $crc[3] . $crc[0] . $crc[1]); 189 | } 190 | 191 | /** 192 | * Parse ini file and return it as array 193 | * @param string $configFilePath - custom path to config file 194 | * @return array 195 | * @access private 196 | */ 197 | private function loadConfig($configFilePath = '') { 198 | $configFile = $configFilePath == '' ? 'config.ini' : $configFilePath; 199 | 200 | return parse_ini_file($configFile); 201 | } 202 | 203 | /** 204 | * Converts hex to string 205 | * @param string $hex 206 | * @return string 207 | * @access private 208 | */ 209 | private function hex2str($hex) { 210 | $str = ''; 211 | for ($i = 0; $i < strlen($hex); $i+=2) { 212 | $str .= chr(hexdec(substr($hex, $i, 2))); 213 | } 214 | 215 | return $str; 216 | } 217 | 218 | /** 219 | * Convert response from: 220 | * 221 | * STX CH(device code) ACK(response) RET(return) LEN(data length) DATA CRC16 222 | * 0xA5 4 Bytes 1 Byte(command + 0x80) 1 Byte 2 Bytes 0-400 Bytes 2 Bytes 223 | * 224 | * to array 225 | * 226 | * @param string|array $response 227 | * @param int $type 228 | * @return array 229 | * @access private 230 | */ 231 | private function parseResponse($response, $type) { 232 | //if type is 1 it means that multiple commands were send, so we need to parse all responses we got 233 | if ($type == 1) { 234 | foreach ($response as $res) { 235 | 236 | $resArr = str_split($res, 2); 237 | 238 | $json['stx'] = implode(array_slice($resArr, 0, 1)); 239 | $json['ch'] = hexdec(implode(array_slice($resArr, 1, 4))); 240 | $json['ack'] = hexdec(implode(array_slice($resArr, 5, 1))); 241 | $json['ret'] = implode(array_slice($resArr, 6, 1)); 242 | $json['len'] = hexdec(implode(array_slice($resArr, 7, 2))); 243 | $json['data'] = array_slice($resArr, 9, $json['len']); 244 | $json['crc'] = implode(array_slice($resArr, -2, 2)); 245 | 246 | $output[] = $json; 247 | } 248 | } else { //type = 0, single response 249 | $resArr = str_split($response, 2); 250 | 251 | $json['stx'] = implode(array_slice($resArr, 0, 1)); 252 | $json['ch'] = hexdec(implode(array_slice($resArr, 1, 4))); 253 | $json['ack'] = hexdec(implode(array_slice($resArr, 5, 1))); 254 | $json['ret'] = implode(array_slice($resArr, 6, 1)); 255 | $json['len'] = hexdec(implode(array_slice($resArr, 7, 2))); 256 | $json['data'] = array_slice($resArr, 9, $json['len']); 257 | $json['crc'] = implode(array_slice($resArr, -2, 2)); 258 | 259 | $output = $json; 260 | } 261 | 262 | return $output; 263 | } 264 | 265 | /** 266 | * Convert command, data and length to this format: 267 | * 268 | * STX CH(device code) CMD(command) LEN(data length) DATA CRC16 269 | * 0xA5 4 Bytes 1 Byte 2 Bytes 0-400 Bytes 2 Bytes 270 | * 271 | * @param hex $command 272 | * @param string $data 273 | * @param int $len 274 | * @return string 275 | * @access private 276 | */ 277 | private function buildRequest($command, $data = '', $len = -1) { 278 | $len = $len == -1 ? strlen($data) / 2 : $len; 279 | $req = sprintf("A5%08s%02x%04x%s", $this->id, $command, $len, $data); 280 | $req .= $this->crc16(hex2bin($req)); 281 | 282 | return $req; 283 | } 284 | 285 | /** 286 | * Send commands to device and parse response/s 287 | * @param string|array $commands 288 | * @param int $type 289 | * @return parseResponse 290 | * @access private 291 | */ 292 | private function request($commands, $type = 0) { 293 | 294 | //if commands are string convert them to an array 295 | $commands = is_array($commands) ? $commands : [$commands]; 296 | 297 | //build request array 298 | $req = [ 299 | 'id' => $this->id, 300 | 'port' => $this->port, 301 | 'command' => $commands, 302 | 'type' => $type 303 | ]; 304 | 305 | //send request to gearman job server 306 | $res = $this->client->doNormal("Anviz", json_encode($req)); 307 | 308 | //if type is 1 that means that we might receive multiple responses so we must decode them to array 309 | if ($type == 1) { 310 | $res = json_decode($res); 311 | } 312 | 313 | //parse all the responses we've got from device 314 | return $this->parseResponse($res, $type); 315 | } 316 | 317 | /** 318 | * Get the firmware version, communication password, sleep time, volume, language, date 319 | * and time format, attendance state, language setting flag, command version 320 | * @return array|false 321 | * @access public 322 | */ 323 | public function getInfo1() { 324 | 325 | $commands = $this->buildRequest(0x30); 326 | 327 | $res = $this->request($commands); 328 | 329 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xB0) { 330 | $data = [ 331 | 'firmware_version' => $this->hex2str(implode(array_slice($res['data'], 0, 8))), 332 | 'pass_length' => hexdec($res['data'][8][0]), 333 | 'pass' => $res['data'][8][1] . implode(array_slice($res['data'], 9, 2)), 334 | 'sleep_time' => hexdec($res['data'][11]), 335 | 'volume' => hexdec($res['data'][12]), 336 | 'language' => hexdec($res['data'][13]), 337 | 'datetime_format' => $res['data'][14], 338 | 'attendance_state' => hexdec($res['data'][15]), 339 | 'language_setting_flag' => hexdec($res['data'][16]), 340 | 'command_version' => hexdec($res['data'][17]), 341 | ]; 342 | 343 | return $data; 344 | } 345 | 346 | return false; 347 | } 348 | 349 | /** 350 | * Set the communication password, sleep time, volume, language, date format, attendance state, and language setting flag. 351 | * @param string $pass 352 | * @param int|hex $sleep_time 353 | * @param int|hex $volume 354 | * @param int|hex $language 355 | * @param int|hex $dt_format 356 | * @param int|hex $attendance_state 357 | * @param int|hex $language_setting_flag 358 | * @return boolean 359 | * @access public 360 | */ 361 | public function setInfo1($pass = 0xFFFFFF, $sleep_time = 0xFF, $volume = 0xFF, $language = 0xFF, $dt_format = 0xFF, $attendance_state = 0xFF, $language_setting_flag = 0xFF) { 362 | $reserved = 0x00; 363 | 364 | if (!$sleep_time || $sleep_time == '' || !is_numeric($sleep_time) || $sleep_time > 250 || $sleep_time < 0) 365 | $sleep_time = 0xFF; 366 | 367 | if (!$volume || $volume == '' || !is_numeric($volume) || $volume > 5 || $volume < 0) 368 | $volume = 0xFF; 369 | 370 | if (!$language || $language == '' || !is_numeric($language) || $language > 16 || $language < 0) 371 | $language = 0xFF; 372 | 373 | if (!$attendance_state || $attendance_state == '' || !is_numeric($attendance_state) || $attendance_state > 15 || $attendance_state < 0) 374 | $attendance_state = 0xFF; 375 | 376 | 377 | $data = sprintf("%06x%02x%02x%02x%02x%02x%02x%02x", $pass, $sleep_time, $volume, $language, $dt_format, $attendance_state, $language_setting_flag, $reserved); 378 | 379 | $commands = $this->buildRequest(0x31, $data); 380 | 381 | $res = $this->request($commands); 382 | 383 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xB1) { 384 | return true; 385 | } 386 | 387 | return false; 388 | } 389 | 390 | /** 391 | * Get the T&A device Compare Precision, Fixed Wiegand Head Code, Wiegand Option, 392 | * Work code permission, real-time mode setting, FP auto update setting, relay mode, 393 | * Lock delay, Memory full alarm, Repeat attendance delay, door sensor delay, scheduled 394 | * bell delay. 395 | * @return array|false 396 | * @access public 397 | */ 398 | public function getInfo2() { 399 | $commands = $this->buildRequest(0x32); 400 | 401 | $res = $this->request($commands); 402 | 403 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xB2) { 404 | $data = [ 405 | 'fingerprint_comparison_precision' => hexdec($res['data'][0]), 406 | 'fixed_wiegand_head_code' => hexdec($res['data'][1]), 407 | 'wiegand_option' => hexdec($res['data'][2]), 408 | 'work_code_permission' => hexdec($res['data'][3]), 409 | 'real-time_mode_setting' => hexdec($res['data'][4]), 410 | 'fp_auto_update' => hexdec($res['data'][5]), 411 | 'relay_mode' => hexdec($res['data'][6]), 412 | 'lock_delay' => hexdec($res['data'][7]), 413 | 'memory_full_alarm' => hexdec(implode(array_slice($res['data'], 8, 2))), 414 | 'repeat_attendance_delay' => hexdec($res['data'][11]), 415 | 'door_sensor_delay' => hexdec($res['data'][12]), 416 | 'scheduled_bell_delay' => hexdec($res['data'][13]), 417 | 'reserved' => hexdec($res['data'][14]) 418 | ]; 419 | 420 | return $data; 421 | } 422 | 423 | return false; 424 | } 425 | 426 | /** 427 | * Set the T&A device Compare Precision, Fixed Wiegand Head Code, Wiegand Option, 428 | * Work code permission, real-time mode setting, FP auto update setting, relay mode, 429 | * Lock delay, Memory full alarm, Repeat attendance delay, door sensor delay, scheduled 430 | * bell delay. 431 | * @return array|false 432 | * @access public 433 | */ 434 | public function setInfo2() { 435 | // TO DO 436 | } 437 | 438 | /** 439 | * Get the date and time of T&A 440 | * @param string $format Date Time format. Default is 'Y-m-d H:i:s' 441 | * @return string|false 442 | * @access public 443 | */ 444 | public function getDateTime($format = 'Y-m-d H:i:s') { 445 | $commands = $this->buildRequest(0x38); 446 | 447 | $res = $this->request($commands); 448 | 449 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xB8) { 450 | $data = $res['data']; 451 | 452 | foreach ($data as $key => $value) { 453 | $data[$key] = hexdec($value); 454 | } 455 | 456 | $date = sprintf('20%02d-%02d-%02d %02d:%02d:%02d', $data[0], $data[1], $data[2], $data[3], $data[4], $data[5]); 457 | 458 | $output = date($format, strtotime($date)); 459 | 460 | return $output; 461 | } 462 | 463 | return false; 464 | } 465 | 466 | /** 467 | * Set the date and time of T&A 468 | * @param string $dateTime (optional) If not set, current datetime will be set 469 | * @return boolean 470 | * @access public 471 | */ 472 | public function setDateTime($dateTime = '') { 473 | if ($dateTime == '') { 474 | $ts = [ 475 | 0 => date('Y') - 2000, 476 | 1 => date('m'), 477 | 2 => date('d'), 478 | 3 => date('H'), 479 | 4 => date('i'), 480 | 5 => date('s') 481 | ]; 482 | } else { 483 | $unixTime = strtotime($dateTime); 484 | 485 | $ts = [ 486 | 0 => date('Y', $unixTime) - 2000, 487 | 1 => date('m', $unixTime), 488 | 2 => date('d', $unixTime), 489 | 3 => date('H', $unixTime), 490 | 4 => date('i', $unixTime), 491 | 5 => date('s', $unixTime) 492 | ]; 493 | } 494 | 495 | foreach ($ts as $key => $value) { 496 | $ts[$key] = sprintf('%02s', dechex($value)); 497 | } 498 | 499 | $data = implode($ts); 500 | 501 | $commands = $this->buildRequest(0x39, $data); 502 | 503 | $res = $this->request($commands); 504 | 505 | 506 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xB9) { 507 | return true; 508 | } 509 | 510 | return false; 511 | } 512 | 513 | /** 514 | * Get the IP address, subnet Mask, MAC address, Default gateway, Server IP address,Far limit, Com port NO., TCP/IP mode, DHCP limit. 515 | * @return array|boolean 516 | */ 517 | public function getTCPIPParameters() { 518 | 519 | $commands = $this->buildRequest(0x3A); 520 | 521 | $res = $this->request($commands); 522 | 523 | 524 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xBA) { 525 | 526 | $data = [ 527 | 'ip_address' => long2ip(hexdec(implode(array_slice($res['data'], 0, 4)))), 528 | 'subnet_mask' => long2ip(hexdec(implode(array_slice($res['data'], 4, 4)))), 529 | 'mac_address' => implode(array_slice($res['data'], 8, 6)), 530 | 'default_gateway' => long2ip(hexdec(implode(array_slice($res['data'], 14, 4)))), 531 | 'server_ip' => long2ip(hexdec(implode(array_slice($res['data'], 18, 4)))), 532 | 'far_limit' => hexdec($res['data'][22]), 533 | 'comm_port' => hexdec(implode(array_slice($res['data'], 23, 2))), 534 | 'tcpip_mode' => hexdec($res['data'][25]), 535 | 'dhcp_limit' => hexdec($res['data'][26]) 536 | ]; 537 | $output = $data; 538 | 539 | return $output; 540 | } 541 | 542 | return false; 543 | } 544 | 545 | function setTCPIPParameters() { 546 | //TODO 547 | } 548 | 549 | /** 550 | * Get record information, including the amount of Used User, Used FP, Used Password, Used Card, All Attendance Record, and New Record. 551 | * @return array|boolean 552 | * @access public 553 | */ 554 | public function getRecordInformation() { 555 | 556 | $commands = $this->buildRequest(0x3C); 557 | 558 | $res = $this->request($commands, 0); 559 | 560 | $output = []; 561 | 562 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xBC) { 563 | 564 | $data = [ 565 | 'user_amount' => hexdec(implode(array_slice($res['data'], 0, 3))), 566 | 'fp_amount' => hexdec(implode(array_slice($res['data'], 3, 3))), 567 | 'password_amount' => hexdec(implode(array_slice($res['data'], 6, 3))), 568 | 'card_amount' => hexdec(implode(array_slice($res['data'], 9, 3))), 569 | 'all_record_amount' => hexdec(implode(array_slice($res['data'], 12, 3))), 570 | 'new_record_amount' => hexdec(implode(array_slice($res['data'], 15, 3))), 571 | ]; 572 | 573 | $output = $data; 574 | 575 | return $output; 576 | } 577 | 578 | return false; 579 | } 580 | 581 | /** 582 | * download record, the downloading max number is 25 each time.(record data length: 25*14 = 350Byte) 583 | * @param hex $type @see constant DOWNLOAD_* 584 | * @return array 585 | */ 586 | public function downloadTARecords($type = PHPAnviz::DOWNLOAD_NEW) { 587 | $recordInfo = $this->getRecordInformation(); 588 | 589 | if ($type == PHPAnviz::DOWNLOAD_NEW) { 590 | $maxRecords = $recordInfo['new_record_amount']; 591 | $deleteAmount = $maxRecords; 592 | } else { 593 | $maxRecords = $recordInfo['all_record_amount']; 594 | $deleteAmount = 0x00; 595 | } 596 | 597 | $num = min(25, $maxRecords); 598 | 599 | $data = sprintf("%02x%02x", $type, $num); 600 | 601 | $commands[0] = $this->buildRequest(0x40, $data); 602 | $maxRecords -= $num; 603 | 604 | while ($maxRecords > 0) { 605 | $num = min(25, $maxRecords); 606 | $data = sprintf("%02x%02x", 0, $num); 607 | 608 | $commands[] = $this->buildRequest(0x40, $data); 609 | $maxRecords -= $num; 610 | } 611 | 612 | $resArr = $this->request($commands, 1); 613 | 614 | $data = []; 615 | 616 | foreach ($resArr as $res) { 617 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xC0) { 618 | 619 | for ($i = 0; $i < hexdec($res['data'][0]); $i++) { 620 | $event = [ 621 | 'user_code' => hexdec(implode(array_slice($res['data'], $i * 14 + 1, 5))), 622 | 'datetime' => date('Y-m-d H:i:s', hexdec(implode(array_slice($res['data'], $i * 14 + 6, 4))) + PHPAnviz::ANVIZ_EPOCH), 623 | 'backup_code' => hexdec($res['data'][$i * 14 + 10]), 624 | 'record_type' => bindec(substr(base_convert($res['data'][$i * 14 + 11], 16, 2), 1)), 625 | 'work_type' => hexdec(implode(array_slice($res['data'], $i * 14 + 12, 2))), 626 | ]; 627 | 628 | $data[] = $event; 629 | } 630 | 631 | if ($type == PHPAnviz::DOWNLOAD_NEW) { 632 | $this->clearRecords(PHPAnviz::CLEAR_NEW_PARTIALY, $deleteAmount); 633 | } 634 | } 635 | } 636 | 637 | 638 | 639 | return $data; 640 | } 641 | 642 | /** 643 | * download staff information 644 | * @return array 645 | * @access public 646 | */ 647 | public function downloadStaffInfo() { 648 | 649 | $recordInfo = $this->getRecordInformation(); 650 | 651 | $maxUsers = $recordInfo['user_amount']; 652 | $num = min(8, $maxUsers); 653 | $data = sprintf("%02x%02x", 0x01, $num); 654 | $commands[0] = $this->buildRequest(0x72, $data); 655 | $maxUsers -= $num; 656 | 657 | while ($maxUsers > 0) { 658 | $num = min(8, $maxUsers); 659 | $data = sprintf("%02x%02x", 0x00, $num); 660 | $commands[] = $this->buildRequest(0x72, $data); 661 | $maxUsers -= $num; 662 | } 663 | 664 | $resArr = $this->request($commands, 1); 665 | 666 | $final = []; 667 | 668 | foreach ($resArr as $res) { 669 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xF2) { 670 | for ($i = 0; $i < hexdec($res['data'][0]); $i++) { 671 | $employee = [ 672 | 'user_id' => hexdec(implode(array_slice($res['data'], $i * 40 + 1, 5))), 673 | 'pwd' => hexdec($res['data'][$i * 40 + 6][1] . implode(array_slice($res['data'], $i * 40 + 7, 2))), 674 | 'card_id' => hexdec(implode(array_slice($res['data'], $i * 40 + 9, 4))), 675 | 'name' => $this->hex2str(implode(array_slice($res['data'], $i * 40 + 13, 20))), 676 | 'department' => $res['data'][$i * 40 + 33], 677 | 'group' => $res['data'][$i * 40 + 34], 678 | 'attendance_mode' => $res['data'][$i * 40 + 35], 679 | 'fp_enroll_state' => implode(array_slice($res['data'], $i * 40 + 36, 2)), 680 | 'pwd_8_digit' => $res['data'][$i * 40 + 38], 681 | 'keep' => $res['data'][$i * 40 + 39], 682 | 'special_info' => $res['data'][$i * 40 + 40], 683 | ]; 684 | 685 | $final[] = $employee; 686 | } 687 | } 688 | } 689 | 690 | return $final; 691 | } 692 | 693 | /** 694 | * Add 00 before every byte 695 | * @param string $name 696 | * @return string 697 | * @access private 698 | */ 699 | private function fixName($name) { 700 | $i = 0; 701 | 702 | $newName = ''; 703 | 704 | while (strlen($newName) < 40) { 705 | $newName .= '00' . $name[$i] . $name[$i + 1]; 706 | $i += 2; 707 | } 708 | 709 | return $newName; 710 | } 711 | 712 | /** 713 | * upload staff information. If user data is empty, set it as 0xFF. For instance, card Id set as 0xFF if user don’t enroll card. 714 | * FP enroll state can not set, this value is 0 715 | * @param array $users 716 | * @return boolean 717 | * @access public 718 | */ 719 | function uploadStaffInfo($users) { 720 | 721 | $employees = []; 722 | 723 | foreach ($users as $user) { 724 | 725 | $name = unpack('H*', $user[3]); 726 | 727 | $name = $this->fixName($name[1]); 728 | 729 | $employee = [ 730 | 'user_id' => sprintf('%010x', $user[0]), 731 | 'pwd' => sprintf('%06x', $user[1]), 732 | 'card_id' => sprintf('%08x', $user[2]), //$user[2]); 733 | 'name' => sprintf('%040s', $name), 734 | 'department' => sprintf('%02x', 0xFF), 735 | 'group' => sprintf('%02x', 0xFF), 736 | 'attendance_mode' => 'FF', 737 | 'fp_enroll_state' => '0000', 738 | 'pwd_8_digit' => 'FF', 739 | 'keep' => 'FF', 740 | 'special_info' => 'FF', 741 | ]; 742 | 743 | $employees[] = implode($employee); 744 | } 745 | 746 | $data = sprintf("%02x", count($employees)) . implode($employees); 747 | 748 | $commands = $this->buildRequest(0x73, $data); 749 | 750 | $res = $this->request($commands); 751 | 752 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xF3) { 753 | return true; 754 | } 755 | 756 | return false; 757 | } 758 | 759 | /** 760 | * Download FP Template from T&A device 761 | * @param int $user 762 | * @param hex $backup_code 763 | * @return FP Template|false 764 | * @access public 765 | */ 766 | public function downloadFPTemplate($user, $backup_code = 0x01) { 767 | $data = sprintf('%010x%02x', $user, $backup_code); 768 | 769 | $commands = $this->buildRequest(0x44, $data); 770 | 771 | $res = $this->request($commands); 772 | 773 | $output = []; 774 | 775 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xC4) { 776 | return implode($res['data']); 777 | } 778 | 779 | return false; 780 | } 781 | 782 | /** 783 | * Get device ID which we set in device. 784 | * @return int|false 785 | * @access public 786 | */ 787 | public function getDeviceId() { 788 | $commands = $this->buildRequest(0x74); 789 | 790 | $res = $this->request($commands); 791 | 792 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS) { 793 | $data = hexdec(implode($res['data'])); 794 | 795 | return $data; 796 | } 797 | 798 | return false; 799 | } 800 | 801 | /** 802 | * Modify device ID in device menu 803 | * @param int $id 804 | * @return boolean 805 | * @access public 806 | */ 807 | public function setDeviceId($id) { 808 | $data = sprintf('%08x', $id); 809 | 810 | $res = $this->request(75, $data); 811 | 812 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS) { 813 | return true; 814 | } 815 | 816 | return false; 817 | } 818 | 819 | /** 820 | * Initialize all the user data area, clear all the staff info, FP data, password/card data 821 | * @return boolean 822 | * @access public 823 | */ 824 | public function clearUsers() { 825 | 826 | $commands[] = $this->buildRequest(0x4D); 827 | 828 | 829 | $res = $this->request($commands); 830 | 831 | 832 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xCD) { 833 | return true; 834 | } 835 | 836 | return false; 837 | } 838 | 839 | /** 840 | * Cancel all records, or cancel all/part new records sign. 841 | * @param hex $type @see const CLEAR_* 842 | * @param hex $amount 843 | * @return boolean 844 | * @access public 845 | */ 846 | public function clearRecords($type = PHPAnviz::CLEAR_NEW, $amount = 0xFFFF) { 847 | 848 | $data = sprintf("%02x%04x", $type, $amount); 849 | 850 | $commands = $this->buildRequest(0x4E, $data); 851 | 852 | $res = $this->request($commands); 853 | 854 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xCE) { 855 | return true; 856 | } 857 | 858 | return false; 859 | } 860 | 861 | /** 862 | * Force T&A device output signal to open door without verifying user 863 | * @return boolean 864 | * @access public 865 | */ 866 | public function openDoor() { 867 | $commands = $this->buildRequest(0x5E); 868 | 869 | $res = $this->request($commands); 870 | 871 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xDE) { 872 | return true; 873 | } 874 | 875 | return false; 876 | } 877 | 878 | /** 879 | * Get T&A states from device 880 | * @return array | boolean 881 | * @access public 882 | */ 883 | 884 | public function getAttendanceStateTable() { 885 | $commands = $this->buildRequest(0x70); 886 | 887 | $res = $this->request($commands); 888 | 889 | 890 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xF0) { 891 | 892 | $stateTable = array(); 893 | 894 | $numStates = hexdec($res['data'][0]); 895 | 896 | for ($i = 0; $i < $numStates; $i++) { 897 | $stateTable[$i] = $this->hex2str(implode(array_slice($res['data'], $i * 20 + 1, 20))); 898 | } 899 | 900 | return $stateTable; 901 | } 902 | 903 | return false; 904 | } 905 | 906 | /** 907 | * Set T&A state table. Max 16 different states. 908 | * @param array $states 909 | * @return boolean 910 | * @access public 911 | */ 912 | 913 | public function setAttendanceStateTable($table) { 914 | 915 | $data = sprintf('%02x', count($table)); 916 | 917 | $ansi = '0000000000000000000000000000000000000000'; 918 | 919 | $states = array_fill(0, 16, $ansi); 920 | 921 | $i = 0; 922 | 923 | foreach ($table as $string) { 924 | $word = ''; 925 | 926 | for($j = 0; $j < strlen($string); $j++) { 927 | $word .= str_pad(unpack('H*', $string[$j])[1], 4, '0000', STR_PAD_LEFT); 928 | } 929 | 930 | $states[$i++] = str_pad($word, 40, $ansi); 931 | } 932 | 933 | $data .= implode($states); 934 | 935 | $commands = $this->buildRequest(0x71, $data); 936 | 937 | $res = $this->request($commands); 938 | 939 | if ($res['ret'] == PHPAnviz::ACK_SUCCESS && $res['ack'] == 0xF1) { 940 | return true; 941 | } 942 | 943 | return false; 944 | } 945 | 946 | //for testing purpouse only 947 | public function test($command) { 948 | $commands = $this->buildRequest($command); 949 | $res = $this->request($commands); 950 | 951 | print_r($res); 952 | } 953 | 954 | } 955 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## PHPAnviz 2 | PHP library to access and control Anviz devices 3 | 4 | ### Version 5 | 0.9.0 6 | 7 | ### Installation 8 | ```sh 9 | $ apt-get install gearman-job-server php5-gearman libgearman-dev 10 | ``` 11 | 12 | After the installation is done run gearman job server 13 | ```sh 14 | $ gearmand -d 15 | ``` 16 | 17 | Set server permissions 18 | ```sh 19 | $ chmod +x anviz-server 20 | ``` 21 | 22 | I'm recommending you to get Supervisor and run server with it or start the server mannually: 23 | ```sh 24 | $ ./anviz-server_xYour_arch 25 | ``` 26 | 27 | You can download source of anviz-server here 28 | 29 | ### Code & Functions 30 | Before we start, open `config.ini.example` and change gearman-server address, save file and create `config.ini` 31 | 32 | ```sh 33 | $ cp confing.ini.example config.ini 34 | ``` 35 | 36 | #### Get DateTime 37 | 38 | ```php 39 | getDateTime("Y/m/d H:i:s"); 47 | ``` 48 | 49 | #### Set DateTime 50 | 51 | ```php 52 | //parameter datetime is optional, if not set method will send current timestamp to device 53 | $result = $anviz->setDateTime("2016-08-12 22:00:00"); //true if successful, false if failed 54 | ``` 55 | 56 | #### Get the firmware version, communication password, sleep time, volume, language, date and time format, attendance state, language setting flag, command version 57 | 58 | ```php 59 | $result = $anviz->getInfo1(); //array 60 | ``` 61 | 62 | #### Set the communication password, sleep time, volume, language, date format, attendance state, and language setting flag. 63 | 64 | ```php 65 | //returns true if successfull or false if failed 66 | //pass 0xFF if you don't want to update paramete 67 | $result = $anviz->setInfo1("12345", 10, 4, 1, 12, 0xFF, 0xFF); //true if success, false if failed 68 | ``` 69 | 70 | #### Get the T&A device Compare Precision, Fixed Wiegand Head Code, Wiegand Option, Work code permission, real-time mode setting, FP auto update setting, relay mode, Lock delay, Memory full alarm, Repeat attendance delay, door sensor delay, scheduled bell delay 71 | 72 | ```php 73 | $result = $anviz->getInfo2(); //array 74 | ``` 75 | 76 | #### Get the IP address, subnet Mask, MAC address, Default gateway, Server IP address,Far limit, Com port NO., TCP/IP mode, DHCP limit. 77 | 78 | ```php 79 | $result = $anviz->getTCPIPParameters(); //array 80 | ``` 81 | 82 | #### Get record information, including the amount of Used User, Used FP, Used Password, Used Card, All Attendance Record, and New Record. 83 | 84 | ```php 85 | $result = $anviz->getRecordInformation(); //array 86 | ``` 87 | 88 | #### Download Time attendance records 89 | 90 | ```php 91 | $result = $anviz->downloadTARecords(PHPAnviz::DOWNLOAD_NEW); //array of records 92 | ``` 93 | If you want to download all records pass `PHPAnviz::DOWNLOAD_ALL` or if you want to download new records only pass `PHPAnviz::DOWNLOAD_NEW` 94 | 95 | #### Download staff information (users) 96 | 97 | ```php 98 | $result = $anviz->downloadStaffInfo(); //array of users 99 | ``` 100 | 101 | #### Upload staff information (users) 102 | 103 | ```php 104 | $users = array( 105 | 0 => array( 106 | 'user_id' => 1, 107 | 'pwd' => '32015', 108 | 'card_id' => '77421231', 109 | 'name' => 'Test user 1', 110 | 'department' => 0xFF, 111 | 'group' => 1, 112 | 'attendance_mode' => 0xFF, 113 | 'pwd_8_digit' => 0xFF, 114 | 'keep' => 0, 115 | 'special_info' => 0xFF 116 | ), 117 | . 118 | . 119 | . 120 | n => array( 121 | 'user_id' => n, 122 | 'pwd' => '32235', 123 | 'card_id' => '23521231', 124 | 'name' => 'Test user n', 125 | 'department' => 0xFF, 126 | 'group' => 1, 127 | 'attendance_mode' => 0xFF, 128 | 'pwd_8_digit' => 0xFF, 129 | 'keep' => 0, 130 | 'special_info' => 0xFF 131 | ), 132 | ); 133 | 134 | $anviz->uploadStaffInfo($users); //true if successful, false if failed 135 | ``` 136 | 137 | #### Download Fingerprint template 138 | 139 | ```php 140 | //first parameter is user id, second parameter is finger print (1 for FP1, 2 for FP2) 141 | $template = $anviz->downloadFPTemplate(1, 1); //string 142 | ``` 143 | 144 | #### Get device id 145 | 146 | ```php 147 | $id = $anviz->getDeviceId(); //int 148 | ``` 149 | 150 | #### Set device id 151 | 152 | ```php 153 | $result = $anviz->setDeviceId(13); //true if successful, false if failed 154 | ``` 155 | 156 | #### Clear ALL users and their data 157 | 158 | ```php 159 | $result = $anviz->clearUsers(); //true if successful, false if failed 160 | ``` 161 | 162 | #### Clear Time Attendance records 163 | 164 | ```php 165 | $result = $anviz->clearRecords(PHPAnviz::CLEAR_NEW_PARTIALY, 24); //true if successful, false if failed 166 | ``` 167 | 168 | `PHPAnviz::CLEAR_ALL` -> if you want to delete all records 169 | 170 | `PHPAnviz::CLEAR_NEW` -> remove all "new record" signs 171 | 172 | `PHPAnviz::CLEAR_NEW_PARTIALY`, `int $n` -> remove first `$n` "new records" signs 173 | 174 | #### Force T&A device output signal to open door 175 | 176 | ```php 177 | $result = $anviz->openDoor(); //true if successful, false if failed 178 | ``` 179 | 180 | #### Get Attendance state table 181 | ```php 182 | $result = $anviz->getAttendanceStateTable(); //returns array of states (MAX 16) 183 | ``` 184 | 185 | #### Set Attendance state table 186 | ```php 187 | 188 | //MAX 16 elements 189 | $states = array('IN', 'OUT', 'BREAK'); 190 | 191 | $result = $anviz->setTAStateTable($states); //true if successful, false if failed 192 | -------------------------------------------------------------------------------- /anviz-server_x64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtisler/PHPAnviz/2280c022f6e208427b076b2767ed4957d57fe61e/anviz-server_x64 -------------------------------------------------------------------------------- /anviz-server_x86: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtisler/PHPAnviz/2280c022f6e208427b076b2767ed4957d57fe61e/anviz-server_x86 -------------------------------------------------------------------------------- /config.ini.example: -------------------------------------------------------------------------------- 1 | ;replace values in file and rename it to config.ini 2 | 3 | [config] 4 | gearman-server=your.server.address --------------------------------------------------------------------------------