Outline:
  • Demonstration
  • System Components
  • Things used in this project
  • Wiring Diagram
  • The Working Flow
  • Source Codes


For irregular customers in parking lots, they have to pay parking fee per time rather than per month. It’s inconvenient to do payment by cash.

With RFID technology, we can build an automated payment system. In detail, amount of money is charged to a RFID tag. Each time for the parking, the customer just need to tap the tag to a device, the parking fee is subtracted from the charged money in the tag.



Demonstration





System Components

The system includes two devices:
  • Money charging device (to charge money to RFID card)
  • Parking fee payment device (to subtract money from card each time parking)

In the demonstration video, I combined two devices into one.

Main components of the two devices are PN532, PHPoC and MIFARE Classic Tags.
  • PN532 and PHPoC keep a role in charging money to the card, subtracting the parking fee from the card for each time parking.
  • MIFARE Classic Tags is to store the money.

Someone may ask a question: Is it possible for a tag to be modified on charging amount by an unauthorized person? The answer is no or too difficult to hack it because of the following reasons:
  • Reading/writing from/to the tag is protected by password.
  • Data is encrypted before writing to card using the crypto algorithm (in this project I use RC4 Algorithm).
  • A cyclic redundancy check (CRC) is used to detect accidental changes to raw data.


Things Used In This Project


Money Charging Device
  • PHPoC Blue or Black
  • PN532 NFC/RFID controller (in my case, I used Adafruit PN532 RFID/NFC Breakout)
  • Some MIFARE Classic Tags
  • PHPoC bread board (Optional)
  • Jumper wires



Parking Fee Payment Device
  • PHPoC Blue or Black
  • PN532 NFC/RFID controller (in my case, I used Adafruit PN532 RFID/NFC Breakout)
  • Some NFC/RFID tags
  • PHPoC bread board (Optional)
  • Servo motor
  • Jumper wires
  • Lego Brick


Wiring Diagram
Click image for larger version  Name:	Payment_Pin Wiring.PNG Views:	1 Size:	287.0 KB ID:	580
  • PHPoC----------- Adafruit PN532
  • 3V---------------3.3V (Red wire)
  • SCK --------------SCK (Yellow wire)
  • MISO------------MISO (Green wire)
  • MOSI-----------MOSI (Blue wire)
  • 4 -----------------SSEL (White wire)
  • GND-------------GND (Gray wire)
  • PHPoC----------- Servo Motor
  • GND-------------GND
  • 5v--------------- vcc
  • Ht0--------------- signal pin


Working Flow


Money Charging Device

The below image shows the working flow on server side (task0.php).Other part is a web-based user interface (index.php) which is not shown here.

Click image for larger version  Name:	Payment_Money Charging Device.PNG Views:	1 Size:	45.3 KB ID:	581

Parking Fee Payment Device

Click image for larger version  Name:	Payment_Parking Fee Payment Device.PNG Views:	2 Size:	52.1 KB ID:	582

The two devices above use two important functions: Read and write money to RFID tag. The below diagram depicts how it works.

Click image for larger version  Name:	Payment_Read Write Money.PNG Views:	1 Size:	67.6 KB ID:	584



Source Codes


Money Charging Device

Source codes for Money Charging Device includes two part:
  • Task0.php: This script runs in infinite loop and responsible for checking command and amount of charging money from websocket, and charging money to RFID tag.
  • Index.php: This script provides the user interface, send command and amount of charging money via websocket.

<task0.php>
PHP Code:
<?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($rbuf02); // Two first bytes of block 1 is length value.
        
$data_len $length 2// Two byte CRC

        
if(!$length)
            return 
0;

        
$data_buf substr($rbuf2);

        
$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_buf0$data_len);
        
$crc substr($data_buf$data_len2);

        
//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($crc2);

    
$length strlen($data);

    
//Append two byte data length
    
$data int2bin($length2) .$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$pointer16);

        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_addrDEFAULT_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($uid1$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);
    }                        
}
?>



<index.php>
PHP Code:
<?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>



Parking Fee Payment Device

This device doesn’t require interface. Therefore, it just includes only one part: task0.php.
<task0.php>
PHP Code:
<?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($rbuf02); // Two first bytes of block 1 is length value.
        
$data_len $length 2// Two byte CRC

        
if(!$length)
            return 
0;

        
$data_buf substr($rbuf2);

        
$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_buf0$data_len);
        
$crc substr($data_buf$data_len2);

        
//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($crc2);

    
$length strlen($data);

    
//Append two byte data length
    
$data int2bin($length2) .$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$pointer16);

        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_addrDEFAULT_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$widthPWM_PERIOD);    
}

ht_pwm_setup(0WIDTH_MINPWM_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($uid1$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.
    
}
}
?>



Combining Two Devices into One

We just need to combine the server side (task0.php). User Interface is kept as Money Charging Device. When websocket is connected, the devices acts Money Charging Device, otherwise the devices acts as Parking Fee Payment Device.

<task0.php>
PHP Code:
<?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("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("MODE_NORMAL",         0);
define("MODE_CHARGE",         1);

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($rbuf02); // Two first bytes of block 1 is length value.
        
$data_len $length 2// Two byte CRC

        
if(!$length)
            return 
0;

        
$data_buf substr($rbuf2);

        
$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_buf0$data_len);
        
$crc substr($data_buf$data_len2);

        
//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($crc2);

    
$length strlen($data);

    
//Append two byte data length
    
$data int2bin($length2) .$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$pointer16);

        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_addrDEFAULT_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$widthPWM_PERIOD);    
}

ht_pwm_setup(0WIDTH_MINPWM_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($uid1$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;        
    }

}

?>



You can get the full source code here: payment_system.zip

If you have any questions or something to discuss, don’t hesitate to leave a comment. I am glad to discuss with you.
Attached Files