Things used in this project

Hardware components:
Phpoc blue per 100x1000 euk22wtv9i
PHPoC PHPoC Blue
×1
Adafruit PN532 NFC/RFID controller breakout board
×1
Adafruit 13.56MHz RFID/NFC Card
×1
Servo (generic)
×1
Bread board b23zpsd8ys
PHPoC PHPoC Bread Board
×1

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

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

Reducing Arduino Power Consumption
Intermediate
  • 1,128
  • 98

Protip

One the most important feature of portable electronics should be long battery life. We can reduce the current drawn by several ways.

Drawing via Web Using Micro-Step Motor Controller and PHPoC
Intermediate
  • 758
  • 15

Whatever you draw on a webpage will be replicated on the drawing paper.

Distance Measurement Vehicle via Websocket
Intermediate
  • 3,358
  • 23

Full instructions

A vehicle measures distance with an encoder on its wheel. It is remotely controlled and transmits the distance via Websocket.

Drawing Route for Car on the Office's Map
Intermediate
  • 2,017
  • 36

Full instructions

This project helps control a step motor-based robot by drawing route on web-based map, making the robot carry your goods to destination.

RING PONG
Intermediate
  • 1,603
  • 7

A simple Ping Pong game played on a NeoPixel Ring with Arduino.

 Monitoring Working Time of Employees Using RFID
Intermediate
  • 526
  • 10

Full instructions

This helps monitor the working time of employees, storing information in database.

ProjectsCommunitiesTopicsContestsLiveAppsBetaFree StoreBlogAdd projectSign up / Login