Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
Nowadays, many companies use RFID tag as an employee card which contains employee’s identification information. This tag can be used to lock/unlock the door or to monitor working time (starting/finishing time).
In this article, I am going to show how to use RFID tag to monitor working time of employees. When an employee taps his/her card (RFID tag) to a RFID reader, employee’s identification information from the card is sent to a remote mysql database. Identification information of the employee can be the tag’s UID or the designed ID which stored in user memory of tag. Refer to http://www.phpoc.com/forum/viewtopic.php?f=42&t=235 to see how to read UID, read/write user memory.
DemonstrationSystem ArchitecturePN532 is a RFID/NFC Reader.
Wiring between PHPoC and Adafruit PN532Main code: task0.php (see section code)
Libraries
- Mysql library: Official Library of PHPoC included in this package http://www.phpoc.com/common/download/li ... 160519.zip
- PN532 and RFID/NFC tag libraries
Install mysql server: you can install only mysql server or XAMPP packet on your computer (XAMPP download link https://www.apachefriends.org/index.html)
Create an username and password:
- Open cmd.exe
- Type following command:
cd c:\xampp\mysql\bin
- Input your root password
- Create a new username and password (which is used in the source code)
CREATE USER 'your_username'@'localhost' IDENTIFIED BY 'your_password';
- Create data base and table:
CREATE DATABASE employee;
You can combine with other tables to make this system more complete.
<?php
if(_SERVER("REQUEST_METHOD"))
exit; // avoid php execution via http request
include_once "/lib/sd_340.php";
include_once "/lib/sn_dns.php";
include_once "/lib/sn_mysql.php";
include_once "vd_pcd_pn532.php";
include_once "vd_mifare_classic.php";
//Check and print error messages from DB server
function chk_error($result)
{
if($result !== false)
{
$error = mysql_error($result);
if(strlen($error) != 0)
echo "Error: $error\r\n";
}
}
//Enter your DB Server's hostname or IP address!
$server_addr = "192.168.0.3";
//Enter your account information!
$user_name = "your_username";
$password = "your_password";
$uid = "";
$pre_uid = "";
reader_init(); //RFID reader init
//Connect to DB Server
if(mysql_connect($server_addr, $user_name, $password))
{
//Create a database named employee
//$result = mysql_query("CREATE DATABASE employee;");
//chk_error($result);
$result = mysql_select_db("employee");
chk_error($result);
//Create a table named working_time
//$result = mysql_query("CREATE TABLE tbl_working_time (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, employee_id VARCHAR(20) NOT NULL, date_time DATETIME(0) NOT NULL);");
//chk_error($result);
//mysql_close();
}
while(1)
{
if(reader_ISO14443A_is_present($uid))
{
if($pre_uid == $uid)
continue;
$pre_uid = $uid;
$uid = bin2hex($uid);
/**
Note: In this code, I use Tag's UID as employee ID.
To make it more flexible, You can wire your own design employee ID to user memory.
And then read it to update to database as employee ID.
**/
//Insert a record
$result = mysql_query("INSERT INTO tbl_working_time (employee_id, date_time) VALUES ('$uid', NOW());");
chk_error($result);
//Inquiry the last record
$result = mysql_query("SELECT * FROM tbl_working_time WHERE employee_id='$uid' ORDER BY date_time DESC LIMIT 1;");
chk_error($result);
//show result of the inquiry
$result_arr = mysql_fetch_row($result);
//Print the result
printf("%s -> %s -> %s\r\n", $result_arr[0], $result_arr[1], $result_arr[2]);
}
}
mysql_close();
?>
<?php
define("PN532_PREAMBLE", 0x00);
define("PN532_STARTCODE1", 0x00);
define("PN532_STARTCODE2", 0xFF);
define("PN532_POSTAMBLE", 0x00);
define("PN532_HOSTTOPN532", 0xD4);
define("PN532_PN532TOHOST", 0xD5);
/* PN532 Commands*/
//Miscellaneous
define("PN532_CMD_DIAGNOSE", 0x00);
define("PN532_CMD_GETFIRMWAREVERSION", 0x02);
define("PN532_CMD_GETGENERALSTATUS", 0x04);
define("PN532_CMD_READREGISTER", 0x06);
define("PN532_CMD_WRITEREGISTER", 0x08);
define("PN532_CMD_READGPIO", 0x0C);
define("PN532_CMD_WRITEGPIO", 0x0E);
define("PN532_CMD_SETSERIALBAUDRATE", 0x10);
define("PN532_CMD_SETPARAMETERS", 0x12);
define("PN532_CMD_SAMCONFIGURATION", 0x14);
define("PN532_CMD_POWERDOWN", 0x16);
//RF Communiction
define("PN532_CMD_RFCONFIGURATION", 0x32);
define("PN532_CMD_RFREGULATIONTEST", 0x58);
//Initiator
define("PN532_CMD_INJUMPFORDEP", 0x56);
define("PN532_CMD_INJUMPFORPSL", 0x46);
define("PN532_CMD_INLISTPASSIVETARGET", 0x4A);
define("PN532_CMD_INATR", 0x50);
define("PN532_CMD_INPSL", 0x4E);
define("PN532_CMD_INDATAEXCHANGE", 0x40);
define("PN532_CMD_INCOMMUNICATETHRU", 0x42);
define("PN532_CMD_INDESELECT", 0x44);
define("PN532_CMD_INRELEASE", 0x52);
define("PN532_CMD_INSELECT", 0x54);
define("PN532_CMD_INAUTOPOLL", 0x60);
//Target
define("PN532_CMD_TGINITASTARGET", 0x8C);
define("PN532_CMD_TGSETGENERALBYTES", 0x92);
define("PN532_CMD_TGGETDATA", 0x86);
define("PN532_CMD_TGSETDATA", 0x8E);
define("PN532_CMD_TGSETMETADATA", 0x94);
define("PN532_CMD_TGGETINITIATORCOMMAND", 0x88);
define("PN532_CMD_TGRESPONSETOINITIATOR", 0x90);
define("PN532_CMD_TGGETTARGETSTATUS", 0x8A);
define("PN532_RESP_INDATAEXCHANGE", 0x41);
define("PN532_RESP_INLISTPASSIVETARGET", 0x4B);
define("PN532_WAKEUP", 0x55);
define("PN532_SPI_STATUS_READ", "\x02");
define("PN532_SPI_DATA_WRITE", "\x01");
define("PN532_SPI_DATA_READ", "\x03");
define("PN532_SPI_READY", "\x01");
//define("PN532_SPI_READY", "\x80");
define("PN532_MAX_PACKET_LEN", 265);
//Baud rate and the modulation type
define("PN532_TAG_TYPE_ISO14443A", 0x00); //106 kbps type A (ISO/IEC14443 Type A)
define("PN532_TAG_TYPE_FELICA_212KBPS", 0x01); //212 kbps (FeliCa polling),
define("PN532_TAG_TYPE_FELICA_424KBPS", 0x02); //424 kbps (FeliCa polling)
define("PN532_TAG_TYPE_ISO14443B", 0x03); //106 kbps type B (ISO/IEC14443-3B)
define("PN532_TAG_TYPE_JEWEL", 0x04); //106 kbps Innovision Jewel tag
define("ACK_FRAME", "\x00\x00\xFF\x00\xFF\x00");
define("NACK_FRAME", "\x00\x00\xFF\xFF\x00\x00");
define("ERROR_FRAME", "\x00\x00\xFF\x01\xFF\x7F\x81\x00");
define("CS_PIN", 4);
/** Low level communication functions **/
function vd_helper_get_tick()
{
while(($pid = pid_open("/mmap/st9", O_NODIE)) == -EBUSY)
usleep(500);
if(!pid_ioctl($pid, "get state"))
pid_ioctl($pid, "start");
$tick = pid_ioctl($pid, "get count");
pid_close($pid);
return $tick;
}
function vd_pn532_spi_wait_response($timeout)
{
$tick = vd_helper_get_tick();
$timeout += $tick;
while($tick < $timeout)
{
$resp = "";
uio_out(0, CS_PIN, LOW);
usleep(2000);
spi_write(0, PN532_SPI_STATUS_READ);
usleep(1000);
spi_read(0, $resp, 1);
uio_out(0, CS_PIN, HIGH);
if($resp == PN532_SPI_READY)
return true;
$tick = vd_helper_get_tick();
}
return false;
}
function vd_pn532_spi_read(&$rbuf, $rlen)
{
uio_out(0, CS_PIN, LOW);
//usleep(2000);
spi_write(0, PN532_SPI_DATA_READ);
$rbuf = "";
spi_read(0, $rbuf, $rlen);
uio_out(0, CS_PIN, HIGH);
return true;
}
function vd_pn532_spi_send($frame)
{
$frame = PN532_SPI_DATA_WRITE.$frame;
uio_out(0, CS_PIN, LOW);
usleep(2000);
//spi_write(0, PN532_SPI_DATA_WRITE);
spi_write(0, $frame);
uio_out(0, CS_PIN, HIGH);
return true;
}
function vd_pn532_frame_create_normal_frame($data, &$frame)
{
/* Normal information frame*/
$frame = "";
// PREAMBLE 1 byte
$frame .= int2bin(PN532_PREAMBLE, 1);
// START CODE 2 bytes (0x00 and 0xFF)
$frame .= int2bin(PN532_STARTCODE1, 1);
$frame .= int2bin(PN532_STARTCODE2, 1);
$checksum = PN532_PREAMBLE + PN532_STARTCODE1 + PN532_STARTCODE2;
// LEN 1 byte indicating the number of bytes in the data field (data + 1 byte of TFI)
$len = strlen($data) + 1;
$frame .= int2bin($len, 1);
// LCS 1 byte Packet Length Checksum LCS byte that satisfies the relation: Lower byte of [LEN + LCS] = 0x00
$len_chksum = (~$len) + 1;
$frame .= int2bin($len_chksum, 1);
// TFI 1 byte frame identifier:D4h in case of a frame from the host to the PN532, D5h in case of a frame from the PN532 to the host
$frame .= int2bin(PN532_HOSTTOPN532, 1);
// DATA LEN-1 bytes of Packet Data Information. The first byte PD0 is the Command Code
$frame .= $data;
// DCS 1 Data Checksum DCS byte that satisfies the relation: Lower byte of [TFI + data] = 0x00
$checksum += PN532_HOSTTOPN532;
$data_len = strlen($data);
for($i = 0; $i < $data_len; $i++)
$checksum += bin2int($data, $i, 1);
$checksum = ~$checksum;
$frame .= int2bin($checksum, 1);
//POSTAMBLE 1 byte2
$frame .= int2bin(PN532_POSTAMBLE, 1);
return true;
}
function vd_pn532_frame_send($data)
{
$frame = "";
vd_pn532_frame_create_normal_frame($data, $frame);
vd_pn532_spi_send($frame);
}
function vd_pn532_frame_read(&$response, $rlen)
{
$rbuf = "";
$rlen += 8; // 8 bytes header and trailer
vd_pn532_spi_read($rbuf, $rlen);
$is_error_frame = strpos($rbuf, ERROR_FRAME);
if(is_bool($is_error_frame) && $is_error_frame == false)
{
$data_len = bin2int($rbuf, 3, 1) - 1;
// Remove frame header and trailer
$response = substr($rbuf, 6, $data_len);
return true;
}
$response = "";
return false;
}
function vd_pn532_handshake_send_NACK()
{
vd_pn532_spi_send(NACK_FRAME);
}
function vd_pn532_handshake_wait_read_ACK($timeout = 100)
{
if(vd_pn532_spi_wait_response($timeout))
{
$ACK = "";
vd_pn532_spi_read($ACK, 6);
if($ACK == ACK_FRAME)
return true;
else
return false;
}
return false;
}
/*
$response: buffter containd responded data from tag (command byte is removed)
$resp_len: is the expected lengh of response (not include 1 byte of $cmd)
*/
//function vd_pn532_cmd_send_get_response($cmd, $data, &$response, $resp_len = PN532_MAX_PACKET_LEN - 1)
function vd_pn532_cmd_send_get_response($cmd, $data, &$response, $resp_len = PN532_MAX_PACKET_LEN)
{
//add cmd
$rbuf = "";
$response = "";
$temp = int2bin($cmd, 1);
$wbuf = $temp.$data;
$resp_len +=1;
vd_pn532_frame_send($wbuf);
if(!vd_pn532_handshake_wait_read_ACK())
return false;
if(!vd_pn532_spi_wait_response(500))
return false;
if(!vd_pn532_frame_read($rbuf, $resp_len))
return false;
$resp_cmd = bin2int($rbuf, 0, 1);
if($resp_cmd != ($cmd + 1))
return false;
// Remove the command byte
$response = substr($rbuf, 1);
return true;
}
/** Generic PN532 functions**/
/*
Config Security Access Module
Reference: Page 89 of User Manual
*/
function pn532_config_SAM()
{
$data = "\x01"; // Normal mode, the SAM is not used; this is the default mode
$data .= "\x14"; // timeout 50ms * 20 = 1 second
$data .= "\x01"; // use IRQ pin!
$response = "";
return vd_pn532_cmd_send_get_response(PN532_CMD_SAMCONFIGURATION, $data, $response, 0);
}
function pn532_config_get_firmware_version()
{
$response = "";
$ret_val = vd_pn532_cmd_send_get_response(PN532_CMD_GETFIRMWAREVERSION, "", $response, 4);
if($ret_val)
{
$ver = bin2int($response, 1, 1);
$rev = bin2int($response, 2, 1);
$support = bin2int($response, 3, 1);
echo "pn532: Firmware $ver.$rev\r\n";
if($support&0x01)
echo "pn532: Support ISO/IEC 14443 Type A\r\n";
if($support&0x02)
echo "pn532: Support ISO/IEC 14443 Type B\r\n";
if($support&0x04)
echo "pn532: Support ISO18092\r\n";
}
else
echo "pn532: not respond firmware version\r\n";
return $ret_val;
}
/**============RF function==============*/
function pn532_config_set_passive_activation_retries($max_retries)
{
$data = "\x05"; // Config item 5 (MaxRetries)
$data .= "\xFF"; // MxRtyATR (default = 0xFF)
$data .= "\x01"; // MxRtyPSL (default = 0x01)
$data .= int2bin($max_retries, 1);
$response = "";
return vd_pn532_cmd_send_get_response(PN532_CMD_RFCONFIGURATION, $data, $response, 0);
}
/*=============ISO14443A functions=============*/
function pn532_read_passive_target_UID($card_baudrate, &$uid, $initiator_data = "")
{
$data = "\x01"; // max 1 cards at once (we can set this to 2)
$data .= int2bin($card_baudrate, 1);
$data .= $initiator_data;
$response = "";
$ret_val = vd_pn532_cmd_send_get_response(PN532_CMD_INLISTPASSIVETARGET, $data, $response);
if($ret_val)
{
$i = 0;
$num_target = bin2int($response, $i++, 1);
if($num_target == 0)
return false;
while($num_target > 0)
{
$i += 4;
$uid_len = bin2int($response, $i++, 1);
$uid = substr($response, $i, $uid_len);
$i += $uid_len;
if($i < strlen($response))
$ats_len = bin2int($response, $i++, 1);
else
break;
$i += $ats_len;
$num_target--;
}
}
return $ret_val;
}
//function pn532_data_exchange($raw_data, &$response, $rlen = PN532_MAX_PACKET_LEN - 2)
function pn532_data_exchange($raw_data, &$response, $rlen = PN532_MAX_PACKET_LEN)
{
$data = "\x01";
$data .= $raw_data;
$response = "";
$rbuf = "";
$rlen++; // include the status byte
$ret_val = vd_pn532_cmd_send_get_response(PN532_CMD_INDATAEXCHANGE, $data, $rbuf, $rlen);
if($ret_val)
{
$status = bin2int($rbuf, 0, 1);
if(($status & 0x3F) == 0)
$response = substr($rbuf, 1); //Remove the status byte
else
{
echo "PN532: Status indicate an error\r\n";
$ret_val = false;
}
}
return $ret_val;
}
function pn532_deselect_all()
{
$data = "\x00";
$rbuf = "";
$ret_val = vd_pn532_cmd_send_get_response(PN532_CMD_INDESELECT, $data, $rbuf, 1);
if($ret_val)
{
$status = bin2int($rbuf, 0, 1);
if(($status & 0x3F) == 0)
return true;
else
{
echo "PN532: Status indicate an error\r\n";
$ret_val = false;
}
}
return $ret_val;
}
function pn532_interface_init()
{
$pid = pid_open_nodie("/mmap/spi0", "spi_setup");
pid_ioctl($pid, "set div 256");
pid_ioctl($pid, "set mode 2");
pid_ioctl($pid, "set lsb 1");
pid_close($pid);
uio_setup(0, CS_PIN, "out high");
}
function reader_init()
{
pn532_interface_init();
uio_out(0, CS_PIN, LOW);
usleep(2000);
usleep(500000);
$data = int2bin(PN532_CMD_GETFIRMWAREVERSION, 1);
vd_pn532_frame_send($data);
vd_pn532_handshake_wait_read_ACK();
pn532_config_get_firmware_version();
pn532_config_SAM();
}
function reader_ISO14443A_is_present(&$uid)
{
return pn532_read_passive_target_UID(PN532_TAG_TYPE_ISO14443A, $uid, "");
}
//function reader_data_exchange($data, &$response, $rlen = PN532_MAX_PACKET_LEN - 2)
function reader_data_exchange($data, &$response, $rlen = PN532_MAX_PACKET_LEN)
{
return pn532_data_exchange($data, $response, $rlen);
}
?>
<?php
// Mifare Commands
define("MIFARE_CMD_AUTH_A", 0x60);
define("MIFARE_CMD_AUTH_B", 0x61);
define("MIFARE_CMD_READ", 0x30);
define("MIFARE_CMD_WRITE", 0xA0);
define("MIFARE_CMD_TRANSFER", 0xB0);
define("MIFARE_CMD_DECREMENT", 0xC0);
define("MIFARE_CMD_INCREMENT", 0xC1);
define("MIFARE_CMD_STORE", 0xC2);
// Mifare Classic functions
function vn_mifare_classic_is_first_block($block_addr)
{
// Test if we are in the small or big sectors
if($block_addr < 128)
return ($block_addr % 4 == 0);
else
return ($block_addr % 16 == 0);
}
function vn_mifare_classic_is_trailer_block($block_addr)
{
// Test if we are in the small or big sectors
if($block_addr < 128)
return (($block_addr + 1) % 4 == 0);
else
return (($block_addr + 1) % 16 == 0);
}
function mifare_classic_authenticate_block($uid, $block_addr, $key, $key_type = "A")
{
$mf_cmd = ($key_type == "A")? MIFARE_CMD_AUTH_A : MIFARE_CMD_AUTH_B;
$data = int2bin($mf_cmd, 1);
$data .= int2bin($block_addr, 1);
$data .= $key;
$data .= $uid;
$buf = "";
return reader_data_exchange($data, $buf, 0);
}
function mifare_classic_authenticate_sector($uid, $sector_addr, $key, $key_type = "A")
{
if($sector_addr < 32)
$block_addr = $sector_addr * 4 + 3;
else
$block_addr = 127 + ($sector_addr - 31) * 16;
return mifare_classic_authenticate_block($uid, $block_addr, $key, $key_type);
}
function mifare_classic_read_data_block($block_addr, &$rbuf)
{
$wbuf = int2bin(MIFARE_CMD_READ, 1);
$wbuf .= int2bin($block_addr, 1);
return reader_data_exchange($wbuf, $rbuf, 16);
}
function vd_mifare_classic_write_block($block_addr, $data)
{
if(strlen($data) > 16)
{
echo "Mifare: data size is over 16 bytes\r\n";
return false;
}
$wbuf = int2bin(MIFARE_CMD_WRITE, 1); // Mifare Write command = 0xA0
$wbuf .= int2bin($block_addr, 1); //Block Number (0..63 for 1K, 0..255 for 4K)
$wbuf .= $data;
$rbuf = "";
return reader_data_exchange($wbuf, $rbuf, 0);
}
function mifare_classic_write_data_block($block_addr, $data)
{
if(vn_mifare_classic_is_trailer_block($block_addr))
{
echo "Mifare: Access deny. This is trailer block, writing to this lock is dengerous since is contain key\r\n";
return false;
}
return vd_mifare_classic_write_block($block_addr, $data);
}
function mifare_classic_write_trailer_block($block_addr, $data)
{
if(!vn_mifare_classic_is_trailer_block($block_addr))
{
echo "Mifare: Access deny. This is not trailer block\r\n";
return false;
}
return vd_mifare_classic_write_block($block_addr, $data);
}
function mifare_classic_change_key($sector_addr, $new_key, $key_type = "A")
{
if(strlen($new_key) != 6)
{
echo "Mifare: size of key is not correct. the size should be 6 bytes\r\n";
return false;
}
if($sector_addr < 32)
$block_addr = $sector_addr * 4 + 3;
else
$block_addr = 127 + ($sector_addr - 31) * 16;
$rbuf = "";
$ret_val = mifare_classic_read_data_block($block_addr, $rbuf);
if(!$ret_val)
return false;
if($key_type == "A")
$data = substr_replace($rbuf, $new_key, 0, strlen($new_key));
else
$data = substr_replace($rbuf, $new_key, 10, strlen($new_key));
return mifare_classic_write_trailer_block($block_addr, $data);
}
?>
Comments