Demonstration




Things Used In This Project
Wiring Diagram

If Grove Expansion Board is used, it just needs to stack the Expansion Board on PHPoC and plugs grove interface (male-type) of camera to UART0 grove interface (female-type) of Grove Expansion Board
Otherwise, connect pin yellow, white, red and black of grove interface (male-type) of camera to pin U0RX, U0TX, PWR5 and GND of PHPoC, respectively.

Click image for larger version  Name:	grove_camera_wiring.JPG Views:	1 Size:	64.6 KB ID:	696



Data Flow

Click image for larger version  Name:	grove_camera_Data Flow.PNG Views:	1 Size:	30.3 KB ID:	697



Source Code

Click image for larger version  Name:	grove_camera_code.PNG Views:	1 Size:	75.8 KB ID:	698



The source code includes:

- phpoc.ini : contains memory configuration parameters. This file can be absent if using the default configuration. In this project, image data comes to the receiving UART buffer and is sent to the sending TCP buffer. Since the image size is big, it need to increase these buffer size via this file.

Code:
tcp0_txbuf_size = 4096 ; TCP send buffer
uart0_rxbuf_size = 1024 ; uart buffer size


- init.php : it's run when system boots and specifies which file is run next. (in this case is task0.php)

PHP Code:
<?php

system
("php task0.php");

?>


- index.php : This is client side code. When a web browser makes an HTTP request to PHPoC, PHPoC interprets PHP script part in this file and returns an HTML file to web browser. The return file contains java-script code which creates WebSocket connection and handles the incoming data from WebSocket (receiving image data and visualizing it on webpage)

PHP Code:
<!DOCTYPE html>
<html>
<head>
<title>PHPoC - Grove Camera</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=0.7">
<style>
body { text-align: center; font-size: 15pt; }
h1 { font-weight: bold; font-size: 25pt; }
h2 { font-weight: bold; font-size: 15pt; }
button { font-weight: bold; font-size: 15pt; }
</style>
<script>
var _CMD_CAMERA_DATA_START = 0x14;
var _CMD_CAMERA_DATA       = 0x15;
var _CMD_CAMERA_DATA_STOP  = 0x16;

var ws;
var camera_data = "";

function init()
{
    //var camera = document.getElementById("camera");
}
function connect_onclick()
{
    if(ws == null)
    {
        var ws_host_addr = "<?echo _SERVER("HTTP_HOST")?>";
        if((navigator.platform.indexOf("Win") != -1) && (ws_host_addr.charAt(0) == "["))
        {
            // network resource identifier to UNC path name conversion
            ws_host_addr = ws_host_addr.replace(/[\[\]]/g, '');
            ws_host_addr = ws_host_addr.replace(/:/g, "-");
            ws_host_addr += ".ipv6-literal.net";
        }
        ws = new WebSocket("ws://" + ws_host_addr + "/grove_camera", "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_onopen()
{
    document.getElementById("ws_state").innerHTML = "<font color='blue'>CONNECTED</font>";
    document.getElementById("bt_connect").innerHTML = "Disconnect";
}
function ws_onclose()
{
    document.getElementById("ws_state").innerHTML = "<font color='gray'>CLOSED</font>";
    document.getElementById("bt_connect").innerHTML = "Connect";
    ws.onopen = null;
    ws.onclose = null;
    ws.onmessage = null;
    ws = null;
}
function ws_onmessage(e_msg)
{
    var arr = JSON.parse(e_msg.data);

    switch(arr.cmd)
    {
        case _CMD_CAMERA_DATA_START:
            camera_data = "";
            break;

        case _CMD_CAMERA_DATA:
            camera_data += arr.data;
            break;

        case _CMD_CAMERA_DATA_STOP:
            document.getElementById("camera").src = "data:image/jpeg;base64,"+ hexToBase64(camera_data);
            break;
    }
}
function hexToBase64(str)
{
    return btoa(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}

window.onload = init;
</script>
</head>

<body>
<p>
<h1>PHPoC- Grove Camera</h1>
</p>
<img id="camera">
<h2>
<p>
WebSocket : <span id="ws_state">null</span><br>
</p>
<button id="bt_connect" type="button" onclick="connect_onclick();">Connect</button>
</h2>
</body>

</html>


- task0.php : This is server side code. it is run in infinite loop. It reads the image data from camera and sends it to web app via WebSocket

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 
"/lib/vd_uart_camera.php";

ws_setup(0"grove_camera""text.phpoc");
uart_setup(0115200);
camera_sync();
camera_setup(CT_JPEGPR_160x120JR_320x240);

while(
1)
{
    if(
ws_state(0) == TCP_CONNECTED)
    {
        
camera_loop();
    }
}

?>


- lib folder : contains libraries code, including sd_340.php, sn_tcp_ws.php and vd_uart_camera.php. sd_340.php and sn_tcp_ws.php are the official libraries from PHPoC. They are available here https://github.com/phpoc/psp/ . vd_uart_camera.php is user-contributed library . sd_340.php contains UART functions to communicate with Camera. sn_tcp_ws.php contains WebSocket functions to communicate with web app. vd_uart_camera.php contains functions to read data from camera.

vd_uart_camera.php
PHP Code:
<?php
// camera command
define("CAM_CMD_PREFIX",      0xAA);
define("CAM_CMD_SYNC",        0x0D);
define("CAM_CMD_ACK",         0x0E);
define("CAM_CMD_NAK",         0x0F);
define("CAM_CMD_INITIAL",     0x01);
define("CAM_CMD_DATA",        0x0A);
define("CAM_CMD_RESET",       0x08);
define("CAM_CMD_POWEROFF",    0x09);
define("CAM_CMD_BAUDRATE",    0x07);
define("CAM_CMD_PACKAGESIZE"0x06);
define("CAM_CMD_SNAPSHOT",    0x05);
define("CAM_CMD_GETPICTURE",  0x04);
define("CAM_CMD_LIGHTFREQ",   0x13);

//Color Type  
define("CT_GRAYSCALE_2"0x01);
define("CT_GRAYSCALE_4"0x02);
define("CT_GRAYSCALE_8"0x03);
define("CT_COLOR_12"0x05);
define("CT_COLOR_16"0x06);
define("CT_JPEG"0x07);

//Preview Resolution
define("PR_80x60"0x01);
define("PR_160x120"0x03);

// JPEG Resolution
define("JR_80x64",   0x01);
define("JR_160x128"0x03);
define("JR_320x240"0x05);
define("JR_640x480"0x07);

define("PIC_PKT_LEN",    512);        //data length of each read, dont set this too big because ram is limited

// server to client
define("_CMD_CAMERA_DATA_START"0x14);
define("_CMD_CAMERA_DATA",       0x15);
define("_CMD_CAMERA_DATA_STOP",  0x16);

$uart_id 0// default uart0, it can be change by using set_uart() function

function set_uart($id)
{
    global 
$uart_id;

    
$uart_id $id;
}

function 
clear_rx_buf()
{
    global 
$uart_id;

    
$rbuf "";
    
$len uart_read($uart_id$rbuf);

    
//log
    
if($len)
    {
        echo 
"clear:<<"bin2hex($rbuf), "\r\n";
    }

}

function 
send_cmd($cmd$CAM_CMD_len)
{
    global 
$uart_id;

    for (
$i 0$i $CAM_CMD_len$i++)
    {
        
$wbuf int2bin(($cmd[$i]&0xff),1);
        
uart_write($uart_id$wbuf1);
    }
}

function 
read_bytes(&$dest$len$timeout)
{
    global 
$uart_id;

    
$pid_st0 pid_open("/mmap/st0");
    
pid_ioctl($pid_st0"set mode free");
    
pid_ioctl($pid_st0"set dir up");
    
pid_ioctl($pid_st0"set div ms");
    
pid_ioctl($pid_st0"start");
    
$tick pid_ioctl($pid_st0"get count");

    
$rlen 0;

    while(
$tick $timeout)
    {
        if((
$rlen uart_readn($uart_id$dest$len)) == $len)
        {
            break;
        }
        
$tick pid_ioctl($pid_st0"get count");
    }

    
pid_close($pid_st0);
    return 
$rlen;
}

function 
camera_wait_for_ACK($time_out$command)
{
    
$resp "";
    if (
read_bytes($resp6$time_out) == 6)
    {
        if (
bin2int($resp01) == CAM_CMD_PREFIX
            
&& bin2int($resp11) == (CAM_CMD_ACK )
            && 
bin2int($resp21) == $command )
        {
            echo 
"camera: received ACK\r\n";
            return 
true;
        }
    }
    echo 
"camera: not receive ACK\r\n";
    return 
false;
}

function 
camera_sync()
{  
    
$cmd = array(CAM_CMD_PREFIXCAM_CMD_SYNC0x000x000x000x00);  
    
$resp "";

    echo 
"camera: CAM_CMD_SYNC...\r\n";

    
$retry_cnt 0;

    while (
1)
    {
        
clear_rx_buf();
        
send_cmd($cmd,6);
        if (
camera_wait_for_ACK(100CAM_CMD_SYNC))
            break;

        
$retry_cnt++;

        if(
$retry_cnt 100)
            
system("reboot php 100");

        
usleep(10000);
    }  
    
$cmd[1] = CAM_CMD_ACK ;
    
$cmd[2] = CAM_CMD_SYNC;
    
send_cmd($cmd6);
}

function 
camera_setup($color_type$preview_resolution$jpeg_resolution)
{
    
//initial
    
echo "camera: CAM_CMD_INITIAL...\r\n";
    
$cmd = array( CAM_CMD_PREFIXCAM_CMD_INITIAL 0x00$color_type$preview_resolution$jpeg_resolution );  
    
$resp "";

    
$retry_cnt 0;

    while (
1)
    {
        
clear_rx_buf();
        
send_cmd($cmd6);

        if (
camera_wait_for_ACK(100CAM_CMD_INITIAL))
            break;

        
$retry_cnt++;

        if(
$retry_cnt 100)
            
system("reboot php 100");
    }
    
// set packet size
    
echo "camera: set CAM_CMD_PACKAGESIZE\r\n";
    
$cmd = array( CAM_CMD_PREFIXCAM_CMD_PACKAGESIZE 0x08PIC_PKT_LEN 0xff, (PIC_PKT_LEN>>8) & 0xff ,0);

    
$retry_cnt 0;

    while (
1)
    {
        
clear_rx_buf();
        
send_cmd($cmd6);

        if (
camera_wait_for_ACK(100CAM_CMD_PACKAGESIZE))
            break;

        
$retry_cnt++;

        if(
$retry_cnt 100)
            
system("reboot php 100");
    }
}

function 
camera_reset($reset 0x01)
{
    
//initial
    
$cmd = array( CAM_CMD_PREFIXCAM_CMD_RESET 0x000x000x000x00 );  
    
$resp "";

    
$retry_cnt 0;

    while (
1)
    {
        
clear_rx_buf();
        
send_cmd($cmd6);

        if (
camera_wait_for_ACK(100CAM_CMD_RESET))
            break;

        
$retry_cnt++;

        if(
$retry_cnt 100)
            
system("reboot php 100");
    }
}

function 
camera_capture()
{
    
//snapshot.
    
echo "camera: CAM_CMD_SNAPSHOT...\r\n";
    
$cmd = array( CAM_CMD_PREFIXCAM_CMD_SNAPSHOT 0x000x000x00 ,0x00);
    
$resp "";

    
$retry_cnt 0;

    while (
1)
    {
        
clear_rx_buf();
        
send_cmd($cmd6);

        if (
camera_wait_for_ACK(100CAM_CMD_SNAPSHOT))
            break;

        
$retry_cnt++;

        if(
$retry_cnt 100)
            
system("reboot php 100");
    }
}

function 
camera_loop()
{
    
// send get picture command and get total size
    
echo "camera: get CAM_CMD_GETPICTURE...\r\n";
    
$cmd = array( CAM_CMD_PREFIXCAM_CMD_GETPICTURE 0x010x000x00 ,0x00);
    
$resp "";
    
$retry_cnt 0;

    while (
1)
    {
        
clear_rx_buf();
        
$retry_cnt++;
        
send_cmd($cmd6);

        if(
$retry_cnt 100)
            
system("reboot php 100");

        if (
camera_wait_for_ACK(100CAM_CMD_GETPICTURE))
        {
            if (
read_bytes($resp61000) != 6)
            {
                continue;
            }
            if (
bin2int($resp01) == CAM_CMD_PREFIX
                
&& bin2int($resp11) == (CAM_CMD_DATA )
                && 
bin2int($resp21) == 0x01)
            {
                
$pic_total_len = (bin2int($resp31)) | (bin2int($resp41) << 8) | (bin2int($resp51) << 16);
                echo 
"\r\npic_total_len: "$pic_total_len"\r\n";
                break;
            }
        }
    }

    
// get data
    
$pktCnt = ($pic_total_len) / (PIC_PKT_LEN 6);
    
$last_pkt_len PIC_PKT_LEN;

    
$mod $pic_total_len % (PIC_PKT_LEN-6);

    if (
$mod != 0)
    {
        
$pktCnt += 1;
        
$last_pkt_len $mod 6;
    }

    
$cmd = array( CAM_CMD_PREFIXCAM_CMD_ACK 0x000x000x000x00 );  
    
$pkt "";

    
// send start code to websocket client
    
$wbuf '{"cmd":' sprintf("%d"_CMD_CAMERA_DATA_START1) . '}';
    
ws_write(0$wbuf);

    for (
$i 0$i $pktCnt$i++)
    {
        
$cmd[4] = $i 0xff;
        
$cmd[5] = ($i >> 8) & 0xff;

        
$retry_cnt 0;
        
$retry true;

        if(
$i < ($pktCnt-1))
            
$len PIC_PKT_LEN ;
        else
        {
            
$len $last_pkt_len;
        }

        while(
$retry)
        {
            
usleep(6000);
            
clear_rx_buf();
            
$retry_cnt++;
            
send_cmd($cmd6);
            
$cnt read_bytes($pkt$len1200);            

            if(
$cnt)
            {
                
//checksum funtion
                /*
                $sum = 0;

                for ($y = 0; $y < $cnt - 2; $y++)
                {
                    $sum += bin2int($pkt, $y, 1);
                }

                $sum &= 0xff;

                if ($sum != bin2int($pkt, ($cnt-2), 1))
                {
                    echo "checksum error";
                    if ($retry_cnt < 100) $retry = true;
                    else
                    {
                        break;
                    }
                }
                else */
                
{
                    
$retry false;
                }
            }

            if(
$retry_cnt 100)
                
system("reboot php 100");
        }

        if(
$retry) break;

        
$data bin2hex(substr($pkt4$cnt-6));

        
// send data to websocket client
        
$wbuf '{"cmd":' sprintf("%d"_CMD_CAMERA_DATA1) . ',"data":"'$data '"}';
        
ws_write(0$wbuf);
    }
    
$cmd[4] = 0xf0;
    
$cmd[5] = 0xf0;
    
send_cmd($cmd6);

    
// send finish code to websocket client
    
$wbuf '{"cmd":' sprintf("%d"_CMD_CAMERA_DATA_STOP1) . '}';
    
ws_write(0$wbuf);
}
?>