Demo
Things Used In This Project
- 1 x PHPoC Blue
- 2 x Servo motors
- Jumper wires
Wiring Diagram
- Connect pin GND and VCC of two servo motors to GND and 5V of PHPoC, respectively.
- Connect pin signals of two servo motors to pin ht0 and pin ht1 of PHPoC, respectively.
Data Flow
Web browser ---> PHPoC
Web app on web browser will send the coordinate (after scaling) of touch or click event to PHPoC via WebSocket. When receiving the command, PHPoC controls two servo motors according to the data received from web browser
Source Code
Source code includes two files:
- index.php: This file is client side code. When receiving HTTP request from web browser, PHPoC interprets PHP script in this file, and then send the interpreted file to web browser. The interpreted file (contains HTML, CSS and JavaScript code) provides UI (User Interface), handling touch/click event from User and send data back to PHPoC via websocket.
<index.php>
PHP Code:
<!DOCTYPE html>
<html>
<head>
<title>PHPoC</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=0.7">
<meta charset="utf-8">
<style>
body { text-align: center; font-size: width/2pt; }
h1 { font-weight: bold; font-size: width/2pt; }
h2 { font-weight: bold; font-size: width/2pt; }
button { font-weight: bold; font-size: width/2pt; }
</style>
<script>
var canvas_width = 500, canvas_height = 500;
var radius_base = 150;
var radius_handle = 72;
var radius_shaft = 120;
var range = canvas_width/2 - 10;
var step = 18;
var ws;
var joystick = {x:0, y:0};
var click_state = 0;
var ratio = 1;
function init()
{
var width = window.innerWidth;
var height = window.innerHeight;
if(width < height)
ratio = (width - 50) / canvas_width;
else
ratio = (height - 50) / canvas_width;
canvas_width = Math.round(canvas_width*ratio);
canvas_height = Math.round(canvas_height*ratio);
radius_base = Math.round(radius_base*ratio);
radius_handle = Math.round(radius_handle*ratio);
radius_shaft = Math.round(radius_shaft*ratio);
range = Math.round(range*ratio);
step = Math.round(step*ratio);
var canvas = document.getElementById("remote");
//canvas.style.backgroundColor = "#999999";
canvas.width = canvas_width;
canvas.height = canvas_height;
canvas.addEventListener("touchstart", mouse_down);
canvas.addEventListener("touchend", mouse_up);
canvas.addEventListener("touchmove", mouse_move);
canvas.addEventListener("mousedown", mouse_down);
canvas.addEventListener("mouseup", mouse_up);
canvas.addEventListener("mousemove", mouse_move);
var ctx = canvas.getContext("2d");
ctx.translate(canvas_width/2, canvas_height/2);
ctx.shadowBlur = 20;
ctx.shadowColor = "LightGray";
ctx.lineCap="round";
ctx.lineJoin="round";
update_view();
}
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 + "/web_joystick", "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";
update_view();
}
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;
update_view();
}
function ws_onmessage(e_msg)
{
e_msg = e_msg || window.event; // MessageEvent
}
function send_data()
{
var x = joystick.x, y = joystick.y;
var joystick_range = range - radius_handle;
x = Math.round(x*100/joystick_range);
y = Math.round(-(y*100/joystick_range));
if(ws != null)
ws.send(x + ":" + y + "\r\n");
}
function update_view()
{
var x = joystick.x, y = joystick.y;
var canvas = document.getElementById("remote");
var ctx = canvas.getContext("2d");
ctx.clearRect(-canvas_width/2, -canvas_height/2, canvas_width, canvas_height);
ctx.lineWidth = 3;
ctx.strokeStyle="gray";
ctx.fillStyle = "LightGray";
ctx.beginPath();
ctx.arc(0, 0, range, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
ctx.strokeStyle="black";
ctx.fillStyle = "hsl(0, 0%, 35%)";
ctx.beginPath();
ctx.arc(0, 0, radius_base, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
ctx.strokeStyle="red";
var lineWidth = radius_shaft;
var pre_x = pre_y = 0;
var x_end = x/5;
var y_end = y/5;
var max_count = (radius_shaft - 10)/step;
var count = 1;
while(lineWidth >= 10)
{
var cur_x = Math.round(count * x_end / max_count);
var cur_y = Math.round(count * y_end / max_count);
console.log(cur_x);
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.lineTo(pre_x, pre_y);
ctx.lineTo(cur_x, cur_y);
ctx.stroke();
lineWidth -= step;
pre_x = cur_x;
pre_y = cur_y;
count++;
}
var x_start = Math.round(x / 3);
var y_start = Math.round(y / 3);
lineWidth += step;
ctx.beginPath();
ctx.lineTo(pre_x, pre_y);
ctx.lineTo(x_start, y_start);
ctx.stroke();
count = 1;
pre_x = x_start;
pre_y = y_start;
while(lineWidth < radius_shaft)
{
var cur_x = Math.round(x_start + count * (x - x_start) / max_count);
var cur_y = Math.round(y_start + count * (y - y_start) / max_count);
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.lineTo(pre_x, pre_y);
ctx.lineTo(cur_x, cur_y);
ctx.stroke();
lineWidth += step;
pre_x = cur_x;
pre_y = cur_y;
count++;
}
var grd = ctx.createRadialGradient(x, y, 0, x, y, radius_handle);
for(var i = 85; i >= 50; i-=5)
grd.addColorStop((85 - i)/35, "hsl(0, 100%, "+ i + "%)");
ctx.fillStyle = grd;
ctx.beginPath();
ctx.arc(x, y, radius_handle, 0, 2 * Math.PI);
ctx.fill();
}
function process_event(event)
{
var pos_x, pos_y;
if(event.offsetX)
{
pos_x = event.offsetX - canvas_width/2;
pos_y = event.offsetY - canvas_height/2;
}
else if(event.layerX)
{
pos_x = event.layerX - canvas_width/2;
pos_y = event.layerY - canvas_height/2;
}
else
{
pos_x = (Math.round(event.touches[0].pageX - event.touches[0].target.offsetLeft)) - canvas_width/2;
pos_y = (Math.round(event.touches[0].pageY - event.touches[0].target.offsetTop)) - canvas_height/2;
}
return {x:pos_x, y:pos_y}
}
function mouse_down()
{
if(ws == null)
return;
event.preventDefault();
var pos = process_event(event);
var delta_x = pos.x - joystick.x;
var delta_y = pos.y - joystick.y;
var dist = Math.sqrt(delta_x*delta_x + delta_y*delta_y);
if(dist > radius_handle)
return;
click_state = 1;
var radius = Math.sqrt(pos.x*pos.x + pos.y*pos.y);
if(radius <(range - radius_handle))
{
joystick = pos;
send_data();
update_view();
}
}
function mouse_up()
{
event.preventDefault();
click_state = 0;
}
function mouse_move()
{
if(ws == null)
return;
event.preventDefault();
if(!click_state)
return;
var pos = process_event(event);
var radius = Math.sqrt(pos.x*pos.x + pos.y*pos.y);
if(radius <(range - radius_handle))
{
joystick = pos;
send_data();
update_view();
}
}
window.onload = init;
</script>
</head>
<body>
<p>
<h1>PHPoC - Web-based Joystick</h1>
</p>
<canvas id="remote"></canvas>
<h2>
<p>
WebSocket : <span id="ws_state">null</span>
</p>
<button id="bt_connect" type="button" onclick="connect_onclick();">Connect</button>
</h2>
</body>
</html>
- task0.php: This file is server side code. It is run in infinite loop to receive and handle data from web browser, control servo motors.
PHP Code:
<?php
if(_SERVER("REQUEST_METHOD"))
exit; // avoid php execution via http request
include "/lib/sd_340.php";
include "/lib/sn_tcp_ws.php";
define("PWM_PERIOD", 20000); // 20000us (20ms)
define("WIDTH_MIN", 600);
define("WIDTH_MAX", 2450);
ht_pwm_setup(0, (WIDTH_MIN + WIDTH_MAX) / 2, PWM_PERIOD, "us");
ht_pwm_setup(1, (WIDTH_MIN + WIDTH_MAX) / 2, PWM_PERIOD, "us");
ws_setup(0, "web_joystick", "text.phpoc");
$rbuf = "";
while(1)
{
if(ws_state(0) == TCP_CONNECTED)
{
$rlen = ws_read_line(0, $rbuf);
if($rlen)
{
$data = explode(" ",$rbuf);
$x = (int) $data[0];
$y = (int) $data[1];
echo "x:$x, y:$y\r\n";
// scale from [-100; 100] to [0; 180]
$angle_x = ($x + 100) * 180 /200;
$angle_y = ($y + 100) * 180 /200;
$width_x = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * $angle_x / 180.0);
$width_y = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * $angle_y / 180.0);
ht_pwm_width(0, $width_x, PWM_PERIOD);
ht_pwm_width(1, $width_y, PWM_PERIOD);
}
}
}
?>
Similar Project but different hardware platform
This project https://www.hackster.io/iot_lover/ar...oystick-02ca54 does the same works but it used other hardware platform