phpoc_man
Published © GPL3+

Smart Payment System for Parking Area Using RFID

This project makes a simple, cheap and secure payment system using RFID that is suitable for small businesses.

IntermediateShowcase (no instructions)8,008
Smart Payment System for Parking Area Using RFID

Things used in this project

Hardware components

PHPoC Blue
PHPoC Blue
×1
Adafruit PN532 NFC/RFID controller breakout board
×1
Adafruit 13.56MHz RFID/NFC Card
×1
PHPoC Bread Board
PHPoC Bread Board
×1
Servo Motor SG90 180 degree
DIYables Servo Motor SG90 180 degree
×1
Jumper Wires
DIYables Jumper Wires
×1

Story

Read more

Code

User Interface (index.php)

PHP
It is user interface code. It is used for Money Charging Device or two-in-one device
<?php
define("STATUS_CODE_MONEY_AMOUNT",         '0');
define("STATUS_CODE_READ_ERROR",         '1');
define("STATUS_CODE_TAG_STATUS",         '2');
define("STATUS_CODE_CHARGE_STATUS",     '3');
?>
<html>
<head>
<title>PHPoC / NFC-RFID Reader</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7">
<style>
td {color:gray;}

table{
    margin-right: auto;
    margin-left: auto;
    padding-bottom: 50px;
    padding-top: 50px;
    width: 1000px; 
    background: url(background.png) no-repeat; 
    background-size: contain;
    position: relative;
    border: 1px solid #000;
}

td, span, input, button{
    font-weight: bold;
    font-family:courier;
    font-size:40px; 
    padding: 5px
}

span, input {color:orange;}

.field {
    width: 400px; 
    text-align: right;
}

table tr.separator { height: 10px; }

</style>
<script>
var ws;
var wc_max_len = 32768;
var cmd = 1;

function init() 
{    
    show_block("div_charge");
}

function ws_onopen()
{
    document.getElementById("ws_state").innerHTML = "OPEN";
    document.getElementById("wc_conn").innerHTML = "Disconnect";
    
    document.getElementById("charge_status").innerHTML = "None";
}
function ws_onclose()
{
    document.getElementById("ws_state").innerHTML = "CLOSED";
    document.getElementById("wc_conn").innerHTML = "Connect";

    ws.onopen = null;
    ws.onclose = null;
    ws.onmessage = null;
    ws = null;
    
    show_block("div_charge");
    document.getElementById("tag_status").innerHTML = "Unavailable";
    document.getElementById("current_amount").innerHTML = "";
    document.getElementById("btn_charge").disabled = true;
}

function wc_onclick()
{
    if(ws == null)
    {
        ws = new WebSocket("ws://<?echo _SERVER("HTTP_HOST")?>/WebConsole", "text.phpoc");
        document.getElementById("ws_state").innerHTML = "CONNECTING";

        ws.onopen = ws_onopen;
        ws.onclose = ws_onclose;
        ws.onmessage = ws_onmessage;
    }
    else
        ws.close();
}

function ws_onmessage(e_msg)
{
    var obj  = JSON.parse(e_msg.data);
    
    console.log(e_msg.data);
    
    switch(obj.code)
    {
        case <?php echo STATUS_CODE_MONEY_AMOUNT?>: 
            document.getElementById("current_amount").innerHTML = obj.data;
            document.getElementById("tag_status").innerHTML = "Available";
            document.getElementById("btn_charge").disabled = false;
            break;
        case <?php echo STATUS_CODE_READ_ERROR?>: 
            document.getElementById("current_amount").innerHTML = obj.data;
            document.getElementById("tag_status").innerHTML = "Available";
            document.getElementById("btn_charge").disabled = true;
            break;
        case <?php echo STATUS_CODE_TAG_STATUS?>: 
            document.getElementById("tag_status").innerHTML = obj.data;
            document.getElementById("current_amount").innerHTML = "";
            document.getElementById("btn_charge").disabled = true;
            break;
        case <?php echo STATUS_CODE_CHARGE_STATUS?>:
            document.getElementById("charge_status").innerHTML = obj.data;
            show_block("div_charge");
            break;        
    }
}

function show_chargeIU()
{
    document.getElementById("btn_send").disabled = false;
    document.getElementById("charge_status").innerHTML = "None";
    show_block("div_input");
}

function send_data()
{
    document.getElementById("btn_send").disabled = true;
    var data =  document.getElementById("charge_amount").value;
    send_command(cmd, data);
}

function send_command(cmd, data) 
{    
    if(ws != null)
        if(ws.readyState == 1)
            ws.send(cmd + " " + data + "\r\n"); 
    
    console.log("cmd:"+cmd);
}

function show_block(className)
{
    var array = document.getElementsByClassName(className);
    for (i = 0; i < array.length; i++) 
    {
        array[i].style.display = "block";
    }
    
    className = (className == "div_input") ? "div_charge" : "div_input";
    
    array = document.getElementsByClassName(className);
    for (i = 0; i < array.length; i++) 
    {
        array[i].style.display = "none";
    }
}

window.onload = init;
</script>
</head>
<body>
<h2>
<center>
<button id="wc_conn" type="button" onclick="wc_onclick();" style=" margin: 15px;">Connect</button>
</center>
<table>
    <tr>
        <td class="field">Web Socket:</td>
        <td><span id="ws_state">CLOSED</span></td>
    </tr>
    <tr class="separator"><td></td><td></td></tr>
    <tr>
        <td class="field">Tag Status:</td>
        <td><span id="tag_status">Unavailable</span></td>
    </tr>
    <tr>
        <td class="field">Current Amount:</td>
        <td><span id="current_amount"></span> KRW</td>
    </tr>
    <tr class="separator"><td></td><td></td></tr>
    <tr>
        <td class="field">
            <div class="div_input" style="display:none">Charge Amount:</div>
            <div class="div_charge" style="display:block">Charge Status:</div>
        </td>
        <td>
            <div class="div_input" style="display:none">
                <input type="number" id="charge_amount" value=0 style="width:200px">
                KRW 
                <button type="button" id="btn_send" onclick="send_data();">Send</button>
            </div>    
            <div class="div_charge" style="display:block">
                <span id="charge_status">None </span>
                <button type="button" id="btn_charge" onclick="show_chargeIU();" disabled>Charge</button>
            </div>
        </td>
    </tr>
    <tr>
        <td class="field"></td>
        <td>
        </td>    
    </tr>
</table>

</h2>
</body>
</html>

task0.php (for Money Charging Device)

PHP
<?php 

if(_SERVER("REQUEST_METHOD"))
    exit; // avoid php execution via http request

include_once "/lib/sd_340.php";
include_once "/lib/sn_tcp_ws.php";
include_once "vd_pcd_pn532.php";
include_once "vd_mifare_classic.php";

define("DEFAULT_KEY",   "\xFF\xFF\xFF\xFF\xFF\xFF"); // Default Tag Key

define("STATUS_CODE_MONEY_AMOUNT",         '0');
define("STATUS_CODE_READ_ERROR",         '1');
define("STATUS_CODE_TAG_STATUS",         '2');
define("STATUS_CODE_CHARGE_STATUS",     '3');

/*Tag Key to read/write data from/to Tag*/
$tag_key = "\xEF\xFF\xFF\xFF\xFF\xFF"; //Change your desired tag key here. It must be 6 bytes in length

/*RC4 Key to encript data before writing  to the tag and decript data after reading from the tag*/
$rc4_key = "\x01\x03\x05\x06\x1A\AA"; //Change your desired key here

function read_money_from_tag($uid)
{
    global $tag_key, $rc4_key;
    
    $block_addr = 1;
    $rbuf = "";
    
    $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
        
    if($ret_val)
    {
        $ret_val = mifare_classic_read_data_block($block_addr, $rbuf);        
         
        if(!$ret_val)
            return false;
        
        $length = bin2int($rbuf, 0, 2); // Two first bytes of block 1 is length value.
        $data_len = $length - 2; // Two byte CRC
        
        if(!$length)
            return 0;
        
        $data_buf = substr($rbuf, 2);
        
        $block_addr++;
        
        while($length > 0) //each block is 16 byte
        {    
            if(vn_mifare_classic_is_trailer_block($block_addr))
                $block_addr++; //Ignore trailer block;
            
            if(vn_mifare_classic_is_first_block($block_addr))
            {
                //When moving to new sector we need to do authenticaiton again.
                $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
                
                if(!$ret_val)
                    return false;
            }
            
            $ret_val = mifare_classic_read_data_block($block_addr, $rbuf);
            
            if(!$ret_val)
                    return false;
                
            $data_buf .= $rbuf;
            
            $length -= 16;
            $block_addr++;
        }
        
        $data = substr($data_buf, 0, $data_len);
        $crc = substr($data_buf, $data_len, 2);
        
        //check CRC
        $crc_data = (int)system("crc 16 %1", $data);
        
        $crc_data = int2bin($crc_data,2);
        
        if($crc != $crc_data)
            return false;
        
        // decryption 
        $rc4 = system("rc4 init %1", $rc4_key);  // initialize
        $money = system("rc4 crypt %1 %2", $rc4, $data);  // decryption
        
        return (int)$money;
    }
    else    
    {
        echo "Authentication Error\r\n";
        return false;
    }
}

function write_money_to_tag($uid, $money)
{
    global $tag_key, $rc4_key;
    
    //format int to string.
    $money = sprintf("%d", $money);
    
    // encryption
    $rc4 = system("rc4 init %1", $rc4_key);  // initialize
    $data = system("rc4 crypt %1 %2", $rc4, $money);  // encryption
    
    //Calculate CRC
    $crc = (int)system("crc 16 %1", $data);
    $data .= int2bin($crc, 2);
    
    $length = strlen($data);
    
    //Append two byte data length
    $data = int2bin($length, 2) .$data;
    $length += 2;
    
    $block_addr = 1;
    $pointer = 0;
    
    $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
        
    if(!$ret_val)
        return false;
    
    while($pointer < $length) //each block is 16 byte
    {
        if(vn_mifare_classic_is_trailer_block($block_addr))
                $block_addr++; //Ignore trailer block;
            
        if(vn_mifare_classic_is_first_block($block_addr))
        {
            //When moving to new sector we need to do authenticaiton again.
            $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
            
            if(!$ret_val)
                return false;
        }
        
        $wbuf = substr($data, $pointer, 16);
        
        if(strlen($wbuf) < 16)
            $wbuf .= str_repeat("\x00", (16 - strlen($wbuf)));
        
        $ret_val = mifare_classic_write_data_block($block_addr, $wbuf);
        
        if(!$ret_val)
                return false;
        
        $pointer += 16;
        $block_addr++;
    }
    
    return true;
}

function check_new_tag($uid)
{
    global $tag_key;
    
    //check whether tag is new.
    for($sector_addr = 0; $sector_addr < 15; $sector_addr++)
    {
        $ret_val = mifare_classic_authenticate_sector($uid, $sector_addr, DEFAULT_KEY, "A");
        
        if($ret_val)
        {
            //Change key
            $ret_val = mifare_classic_change_key($sector_addr, $tag_key, "A");
    
            if(!$ret_val)
                echo "Mifare: sector $sector_addr changed key unsuccessfully\r\n";        
        }
        else
        {
            break;
        }
    }
    
    reader_ISO14443A_is_present($uid);
}
 
reader_init();
ws_setup(0, "WebConsole", "text.phpoc");

$uid = "";
$rbuf = "";

$pre_uid = "";

while(1)
{
    if(ws_state(0) != TCP_CONNECTED) // charging mode
        continue;
            
    if(!reader_ISO14443A_is_present($uid))
    {

        $wbuf = '{"code": '. STATUS_CODE_TAG_STATUS .', "data":"Unavailable"}';
        ws_write(0, $wbuf);
        // delete receiving buffer if existed
        $rlen = ws_read(0, $rbuf);
        
        usleep(500000);
        
        continue;
    }
    
    if($pre_uid != $uid)
    {    
        check_new_tag($uid);
        
        //Recheck tag key        
        $ret_val = mifare_classic_authenticate_block($uid, 1, $tag_key, "A");
        
        if($ret_val)
        {
            echo "System works with tag key\r\n";
        }
        else
            exit("System does not work with tag key\r\n");
        
        $pre_uid = $uid;
    }
        
    $current_money = read_money_from_tag($uid);
    
    if($current_money !== false)
        $wbuf = '{"code": '. STATUS_CODE_MONEY_AMOUNT .', "data":' . "$current_money" . '}';
    else
        $wbuf = '{"code": '. STATUS_CODE_READ_ERROR .', "data":"Read error!"}';

    ws_write(0, $wbuf);
    
    //Check command from app.
    $rlen = ws_read_line(0, $rbuf);
    
    if($rlen)
    {
        $data_array = explode(" ", $rbuf);
        $cmd = (int)$data_array[0];
        $money = (int)$data_array[1];
        
        $new_money = $current_money + $money;

        if(write_money_to_tag($uid, $new_money))
        {
            $wbuf = '{"code": '. STATUS_CODE_CHARGE_STATUS .', "data":"Successful"}';
        }
        else
        {
            $wbuf = '{"code": '. STATUS_CODE_CHARGE_STATUS .', "data":"Fail"}';
        }
        
        ws_write(0, $wbuf);
    }                        
}
?>

task0.php (for Parking Fee Payment Device)

PHP
<?php 

if(_SERVER("REQUEST_METHOD"))
    exit; // avoid php execution via http request

include_once "/lib/sd_340.php";
include_once "vd_pcd_pn532.php";
include_once "vd_mifare_classic.php";

define("PWM_PERIOD", 20000); // 20000us (20ms)
define("WIDTH_MIN", 600);
define("WIDTH_MAX", 2450);

define("DEFAULT_KEY",   "\xFF\xFF\xFF\xFF\xFF\xFF"); // Default Tag Key

define("STATUS_CODE_MONEY_AMOUNT",         '0');
define("STATUS_CODE_READ_ERROR",         '1');
define("STATUS_CODE_TAG_STATUS",         '2');
define("STATUS_CODE_CHARGE_STATUS",     '3');

define("PARKING_FEE", 2000); //unit: KRW

/*Tag Key to read/write data from/to Tag*/
$tag_key = "\xEF\xFF\xFF\xFF\xFF\xFF"; //Change your desired tag key here. It must be 6 bytes in length

/*RC4 Key to encript data before writing  to the tag and decript data after reading from the tag*/
$rc4_key = "\x01\x03\x05\x06\x1A\AA"; //Change your desired key here

function read_money_from_tag($uid)
{
    global $tag_key, $rc4_key;
    
    $block_addr = 1;
    $rbuf = "";
    
    $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
        
    if($ret_val)
    {
        $ret_val = mifare_classic_read_data_block($block_addr, $rbuf);        
         
        if(!$ret_val)
            return false;
        
        $length = bin2int($rbuf, 0, 2); // Two first bytes of block 1 is length value.
        $data_len = $length - 2; // Two byte CRC
        
        if(!$length)
            return 0;
        
        $data_buf = substr($rbuf, 2);
        
        $block_addr++;
        
        while($length > 0) //each block is 16 byte
        {    
            if(vn_mifare_classic_is_trailer_block($block_addr))
                $block_addr++; //Ignore trailer block;
            
            if(vn_mifare_classic_is_first_block($block_addr))
            {
                //When moving to new sector we need to do authenticaiton again.
                $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
                
                if(!$ret_val)
                    return false;
            }
            
            $ret_val = mifare_classic_read_data_block($block_addr, $rbuf);
            
            if(!$ret_val)
                    return false;
                
            $data_buf .= $rbuf;
            
            $length -= 16;
            $block_addr++;
        }
        
        $data = substr($data_buf, 0, $data_len);
        $crc = substr($data_buf, $data_len, 2);
        
        //check CRC
        $crc_data = (int)system("crc 16 %1", $data);
        
        $crc_data = int2bin($crc_data,2);
        
        if($crc != $crc_data)
            return false;
        
        // decryption 
        $rc4 = system("rc4 init %1", $rc4_key);  // initialize
        $money = system("rc4 crypt %1 %2", $rc4, $data);  // decryption
        
        return (int)$money;
    }
    else    
    {
        echo "Authentication Error\r\n";
        return false;
    }
}

function write_money_to_tag($uid, $money)
{
    global $tag_key, $rc4_key;
    
    //format int to string.
    $money = sprintf("%d", $money);
    
    // encryption
    $rc4 = system("rc4 init %1", $rc4_key);  // initialize
    $data = system("rc4 crypt %1 %2", $rc4, $money);  // encryption
    
    //Calculate CRC
    $crc = (int)system("crc 16 %1", $data);
    $data .= int2bin($crc, 2);
    
    $length = strlen($data);
    
    //Append two byte data length
    $data = int2bin($length, 2) .$data;
    $length += 2;
    
    $block_addr = 1;
    $pointer = 0;
    
    $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
        
    if(!$ret_val)
        return false;
    
    while($pointer < $length) //each block is 16 byte
    {
        if(vn_mifare_classic_is_trailer_block($block_addr))
                $block_addr++; //Ignore trailer block;
            
        if(vn_mifare_classic_is_first_block($block_addr))
        {
            //When moving to new sector we need to do authenticaiton again.
            $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
            
            if(!$ret_val)
                return false;
        }
        
        $wbuf = substr($data, $pointer, 16);
        
        if(strlen($wbuf) < 16)
            $wbuf .= str_repeat("\x00", (16 - strlen($wbuf)));
        
        $ret_val = mifare_classic_write_data_block($block_addr, $wbuf);
        
        if(!$ret_val)
                return false;
        
        $pointer += 16;
        $block_addr++;
    }
    
    return true;
}

function check_new_tag($uid)
{
    global $tag_key;
    
    //check whether tag is new.
    for($sector_addr = 0; $sector_addr < 15; $sector_addr++)
    {
        $ret_val = mifare_classic_authenticate_sector($uid, $sector_addr, DEFAULT_KEY, "A");
        
        if($ret_val)
        {
            //Change key
            $ret_val = mifare_classic_change_key($sector_addr, $tag_key, "A");
    
            if(!$ret_val)
                echo "Mifare: sector $sector_addr changed key unsuccessfully\r\n";        
        }
        else
        {
            break;
        }
    }
    
    reader_ISO14443A_is_present($uid);
}

function servo_set_angle($angle) 
{
    $width = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * $angle / 180.0);

    if(($width >= WIDTH_MIN) && ($width <= WIDTH_MAX))
        ht_pwm_width(0, $width, PWM_PERIOD);    
}

ht_pwm_setup(0, WIDTH_MIN, PWM_PERIOD, "us");
servo_set_angle(45);
 
reader_init();

$uid = "";
$rbuf = "";
$pre_uid = "";

while(1)
{        
    if(!reader_ISO14443A_is_present($uid))
    {        
        usleep(500000);        
        continue;
    }
    
    if($pre_uid != $uid)
    {    
        check_new_tag($uid);
        
        //Recheck tag key        
        $ret_val = mifare_classic_authenticate_block($uid, 1, $tag_key, "A");
        
        if($ret_val)
        {
            echo "System works with tag key\r\n";
        }
        else
            echo("System does not work with tag key\r\n");
        
        $pre_uid = $uid;
    }
        
    $current_money = read_money_from_tag($uid);
    
    if($current_money !== false)
    {
        if($current_money >= PARKING_FEE)
        {
            $new_money = $current_money - PARKING_FEE;

            if(write_money_to_tag($uid, $new_money))
            {
                // TODO: open barrier
                servo_set_angle(135);
                sleep(5);
                // close barrier
                servo_set_angle(45);
            }
            else
            {
                //TODO: Alert: system error.
            }
        }
        else
        {
            //TODO: Alert: Not enough Money.
        }
    }
    else
    {
        //TODO: Alert: system error.
    }
}
?>

task0.php (Combining Two Devices into One)

PHP
It is use the same UI (index.php) as Money Charging Device
define("PARKING_FEE", 2000); //unit: KRW

/*Tag Key to read/write data from/to Tag*/
$tag_key = "\xEF\xFF\xFF\xFF\xFF\xFF"; //Change your desired tag key here. It must be 6 bytes in length

/*RC4 Key to encript data before writing  to the tag and decript data after reading from the tag*/
$rc4_key = "\x01\x03\x05\x06\x1A\AA"; //Change your desired key here

function read_money_from_tag($uid)
{
    global $tag_key, $rc4_key;
    
    $block_addr = 1;
    $rbuf = "";
    
    $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
        
    if($ret_val)
    {
        $ret_val = mifare_classic_read_data_block($block_addr, $rbuf);        
         
        if(!$ret_val)
            return false;
        
        $length = bin2int($rbuf, 0, 2); // Two first bytes of block 1 is length value.
        $data_len = $length - 2; // Two byte CRC
        
        if(!$length)
            return 0;
        
        $data_buf = substr($rbuf, 2);
        
        $block_addr++;
        
        while($length > 0) //each block is 16 byte
        {    
            if(vn_mifare_classic_is_trailer_block($block_addr))
                $block_addr++; //Ignore trailer block;
            
            if(vn_mifare_classic_is_first_block($block_addr))
            {
                //When moving to new sector we need to do authenticaiton again.
                $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
                
                if(!$ret_val)
                    return false;
            }
            
            $ret_val = mifare_classic_read_data_block($block_addr, $rbuf);
            
            if(!$ret_val)
                    return false;
                
            $data_buf .= $rbuf;
            
            $length -= 16;
            $block_addr++;
        }
        
        $data = substr($data_buf, 0, $data_len);
        $crc = substr($data_buf, $data_len, 2);
        
        //check CRC
        $crc_data = (int)system("crc 16 %1", $data);
        
        $crc_data = int2bin($crc_data,2);
        
        if($crc != $crc_data)
            return false;
        
        // decryption 
        $rc4 = system("rc4 init %1", $rc4_key);  // initialize
        $money = system("rc4 crypt %1 %2", $rc4, $data);  // decryption
        
        return (int)$money;
    }
    else    
    {
        echo "Authentication Error\r\n";
        return false;
    }
}

function write_money_to_tag($uid, $money)
{
    global $tag_key, $rc4_key;
    
    //format int to string.
    $money = sprintf("%d", $money);
    
    // encryption
    $rc4 = system("rc4 init %1", $rc4_key);  // initialize
    $data = system("rc4 crypt %1 %2", $rc4, $money);  // encryption
    
    //Calculate CRC
    $crc = (int)system("crc 16 %1", $data);
    $data .= int2bin($crc, 2);
    
    $length = strlen($data);
    
    //Append two byte data length
    $data = int2bin($length, 2) .$data;
    $length += 2;
    
    $block_addr = 1;
    $pointer = 0;
    
    $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
        
    if(!$ret_val)
        return false;
    
    while($pointer < $length) //each block is 16 byte
    {
        if(vn_mifare_classic_is_trailer_block($block_addr))
                $block_addr++; //Ignore trailer block;
            
        if(vn_mifare_classic_is_first_block($block_addr))
        {
            //When moving to new sector we need to do authenticaiton again.
            $ret_val = mifare_classic_authenticate_block($uid, $block_addr, $tag_key, "A");
            
            if(!$ret_val)
                return false;
        }
        
        $wbuf = substr($data, $pointer, 16);
        
        if(strlen($wbuf) < 16)
            $wbuf .= str_repeat("\x00", (16 - strlen($wbuf)));
        
        $ret_val = mifare_classic_write_data_block($block_addr, $wbuf);
        
        if(!$ret_val)
                return false;
        
        $pointer += 16;
        $block_addr++;
    }
    
    return true;
}

function check_new_tag($uid)
{
    global $tag_key;
    
    //check whether tag is new.
    for($sector_addr = 0; $sector_addr < 15; $sector_addr++)
    {
        $ret_val = mifare_classic_authenticate_sector($uid, $sector_addr, DEFAULT_KEY, "A");
        
        if($ret_val)
        {
            //Change key
            $ret_val = mifare_classic_change_key($sector_addr, $tag_key, "A");
    
            if(!$ret_val)
                echo "Mifare: sector $sector_addr changed key unsuccessfully\r\n";        
        }
        else
        {
            break;
        }
    }
    
    reader_ISO14443A_is_present($uid);
}

function servo_set_angle($angle) 
{
    $width = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * $angle / 180.0);

    if(($width >= WIDTH_MIN) && ($width <= WIDTH_MAX))
        ht_pwm_width(0, $width, PWM_PERIOD);    
}

ht_pwm_setup(0, WIDTH_MIN, PWM_PERIOD, "us");
servo_set_angle(45);
 
reader_init();
ws_setup(0, "WebConsole", "text.phpoc");

$uid = "";
$rbuf = "";

$mode = MODE_NORMAL;
$pre_uid = "";

while(1)
{
    if(ws_state(0) == TCP_CONNECTED) // charging mode
        $mode = MODE_CHARGE;
    else
        $mode = MODE_NORMAL;
            
    if(!reader_ISO14443A_is_present($uid))
    {
        if($mode == MODE_CHARGE)
        {
            $wbuf = '{"code": '. STATUS_CODE_TAG_STATUS .', "data":"Unavailable"}';
            ws_write(0, $wbuf);
            // delete receiving buffer if existed
            $rlen = ws_read(0, $rbuf);
        }
        
        usleep(500000);
        
        continue;
    }
    
    if($pre_uid != $uid)
    {    
        check_new_tag($uid);
        
        //Recheck tag key        
        $ret_val = mifare_classic_authenticate_block($uid, 1, $tag_key, "A");
        
        if($ret_val)
        {
            echo "System works with tag key\r\n";
        }
        else
            echo("System does not work with tag key\r\n");
        
        $pre_uid = $uid;
    }
        
    $current_money = read_money_from_tag($uid);
    
    switch($mode)
    {
        case MODE_NORMAL:
            if($current_money !== false)
            {
                if($current_money >= PARKING_FEE)
                {
                    $new_money = $current_money - PARKING_FEE;

                    if(write_money_to_tag($uid, $new_money))
                    {
                        // TODO: open barrier
                        servo_set_angle(135);
                        sleep(5);
                        // close barrier
                        servo_set_angle(45);
                    }
                    else
                    {
                        //TODO: Alert: system error.
                    }
                }
                else
                {
                    //TODO: Alert: Not enough Money.
                }
            }
            else
            {
                //TODO: Alert: system error.
            }
            break;
            
        case MODE_CHARGE:
            if($current_money !== false)
                $wbuf = '{"code": '. STATUS_CODE_MONEY_AMOUNT .', "data":' . "$current_money" . '}';
            else
                $wbuf = '{"code": '. STATUS_CODE_READ_ERROR .', "data":"Read error!"}';
    
            ws_write(0, $wbuf);
            
            //Check command from app.
            $rlen = ws_read_line(0, $rbuf);
            
            if($rlen)
            {
                $data_array = explode(" ", $rbuf);
                $cmd = (int)$data_array[0];
                $money = (int)$data_array[1];
                
                $new_money = $current_money + $money;

                if(write_money_to_tag($uid, $new_money))
                {
                    $wbuf = '{"code": '. STATUS_CODE_CHARGE_STATUS .', "data":"Successful"}';
                }
                else
                {
                    $wbuf = '{"code": '. STATUS_CODE_CHARGE_STATUS .', "data":"Fail"}';
                }
                
                ws_write(0, $wbuf);
            }    
            
            break;        
    }
    
}

?>

Library for RFID reader (vd_pcd_pn532.php)

PHP
<?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);
}

?>

Library for NFC tag (vd_mifare_classic.php)

PHP
<?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);
}

?>

Credits

phpoc_man

phpoc_man

62 projects • 405 followers

Comments