Things Used In This Project
- PHPoC Blue or Black
- PHPoC Grove Expansion Board (optional)
- Grove Serial Camera
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.
Data Flow
Source Code
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(0, 115200);
camera_sync();
camera_setup(CT_JPEG, PR_160x120, JR_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, $wbuf, 1);
}
}
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($resp, 6, $time_out) == 6)
{
if (bin2int($resp, 0, 1) == CAM_CMD_PREFIX
&& bin2int($resp, 1, 1) == (CAM_CMD_ACK )
&& bin2int($resp, 2, 1) == $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_PREFIX, CAM_CMD_SYNC, 0x00, 0x00, 0x00, 0x00);
$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(100, CAM_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($cmd, 6);
}
function camera_setup($color_type, $preview_resolution, $jpeg_resolution)
{
//initial
echo "camera: CAM_CMD_INITIAL...\r\n";
$cmd = array( CAM_CMD_PREFIX, CAM_CMD_INITIAL , 0x00, $color_type, $preview_resolution, $jpeg_resolution );
$resp = "";
$retry_cnt = 0;
while (1)
{
clear_rx_buf();
send_cmd($cmd, 6);
if (camera_wait_for_ACK(100, CAM_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_PREFIX, CAM_CMD_PACKAGESIZE , 0x08, PIC_PKT_LEN & 0xff, (PIC_PKT_LEN>>8) & 0xff ,0);
$retry_cnt = 0;
while (1)
{
clear_rx_buf();
send_cmd($cmd, 6);
if (camera_wait_for_ACK(100, CAM_CMD_PACKAGESIZE))
break;
$retry_cnt++;
if($retry_cnt > 100)
system("reboot php 100");
}
}
function camera_reset($reset = 0x01)
{
//initial
$cmd = array( CAM_CMD_PREFIX, CAM_CMD_RESET , 0x00, 0x00, 0x00, 0x00 );
$resp = "";
$retry_cnt = 0;
while (1)
{
clear_rx_buf();
send_cmd($cmd, 6);
if (camera_wait_for_ACK(100, CAM_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_PREFIX, CAM_CMD_SNAPSHOT , 0x00, 0x00, 0x00 ,0x00);
$resp = "";
$retry_cnt = 0;
while (1)
{
clear_rx_buf();
send_cmd($cmd, 6);
if (camera_wait_for_ACK(100, CAM_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_PREFIX, CAM_CMD_GETPICTURE , 0x01, 0x00, 0x00 ,0x00);
$resp = "";
$retry_cnt = 0;
while (1)
{
clear_rx_buf();
$retry_cnt++;
send_cmd($cmd, 6);
if($retry_cnt > 100)
system("reboot php 100");
if (camera_wait_for_ACK(100, CAM_CMD_GETPICTURE))
{
if (read_bytes($resp, 6, 1000) != 6)
{
continue;
}
if (bin2int($resp, 0, 1) == CAM_CMD_PREFIX
&& bin2int($resp, 1, 1) == (CAM_CMD_DATA )
&& bin2int($resp, 2, 1) == 0x01)
{
$pic_total_len = (bin2int($resp, 3, 1)) | (bin2int($resp, 4, 1) << 8) | (bin2int($resp, 5, 1) << 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_PREFIX, CAM_CMD_ACK , 0x00, 0x00, 0x00, 0x00 );
$pkt = "";
// send start code to websocket client
$wbuf = '{"cmd":' . sprintf("%d", _CMD_CAMERA_DATA_START, 1) . '}';
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($cmd, 6);
$cnt = read_bytes($pkt, $len, 1200);
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($pkt, 4, $cnt-6));
// send data to websocket client
$wbuf = '{"cmd":' . sprintf("%d", _CMD_CAMERA_DATA, 1) . ',"data":"'. $data . '"}';
ws_write(0, $wbuf);
}
$cmd[4] = 0xf0;
$cmd[5] = 0xf0;
send_cmd($cmd, 6);
// send finish code to websocket client
$wbuf = '{"cmd":' . sprintf("%d", _CMD_CAMERA_DATA_STOP, 1) . '}';
ws_write(0, $wbuf);
}
?>