phpoc_man
Published © GPL3+

XY Plotter Robot Drawing via Web

In this project, I am going to show how to control XY Plotter Robot via Web interface.

IntermediateShowcase (no instructions)4 days8,108
XY Plotter Robot Drawing via Web

Things used in this project

Hardware components

PHPoC Blue
PHPoC Blue
Provide web interface and control two step motors via PHPoC Motor Controller
×1
Makeblock XY Plotter
×1
PHPoC Stepper Motor Controller (S-type or T-type)
PHPoC Stepper Motor Controller (S-type or T-type)
×2
Servo Motor SG90 180 degree
DIYables Servo Motor SG90 180 degree
×1
Limit Switch
DIYables Limit Switch
×4
Jumper Wires
DIYables Jumper Wires
×1

Story

Read more

Code

Main task (task0.php)

PHP
This is server side code which is running in infinite loop on PHPoC, This code receive command from a websocket client and control motor based on that command.
<?php

include_once "/lib/sd_340.php";
include_once "/lib/sd_spc.php";
include_once "/lib/sn_tcp_ws.php";

define("PWM_PERIOD", 20000); // (20ms)
define("WIDTH_MIN", 600);
define("WIDTH_MAX", 2450);

define("SID_X",			13);
define("SID_Y",			14);

define("MAX_X", 		3421);
define("MAX_Y", 		4296);
define("TOUCH_OFFSET",	0);

define("PEN_UP",		0);
define("PEN_DOWN",		1);

define("CMD_PEN_UP",	0);
define("CMD_PEN_DOWN",	1);
define("CMD_MOVE",		2);

define("SPEED_X_COEF",		20);
define("SPEED_Y_COEF",		20);
define("SPEED_X_OFFSET",	50);
define("SPEED_Y_OFFSET",	50);
define("SPEED_X_MAX",		5000);
define("SPEED_Y_MAX",		5000);

define("ACCEL_X_COEF",		1500);
define("ACCEL_Y_COEF",		1000);
define("ACCEL_X_OFFSET",	0);
define("ACCEL_Y_OFFSET",	0);
define("ACCEL_X_MAX",		20000);
define("ACCEL_Y_MAX",		20000);

define("STATE_X_LOCK",	1);
define("STATE_X_RUN",	2);
define("STATE_Y_LOCK",	4);
define("STATE_Y_RUN",	8);

function step_cmd($sid, $cmd)
{
	$resp = spc_request($sid, 4, $cmd);

	if($resp)
	{
		$resp = explode(",", $resp);

		if((int)$resp[0] != 200)
		{
			echo "step_cmd : '$cmd' request error ", $resp[0], "\r\n";
			return false;
		}

		if(count($resp) > 1)
			return $resp[1];
		else
			return "";
	}
	else
		return false;
}

function spc_check_did($sid, $did)
{
	$resp = spc_request_csv($sid, 0, "get did");

	if($resp === false)
	{
		echo "spc_check_did: sid$sid - device not found\r\n";
		return false;
	}

	if($resp[1] != "40012403")
	{
		echo "spc_check_did: unknown device ", $resp[2], "\r\n";
		return false;
	}

	return true;
}

function pen_up()
{
	$angle = 110;

	$width = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * $angle / 180.0);

	if(($width >= WIDTH_MIN) && ($width <= WIDTH_MAX))
		ht_pwm_width(2, $width, PWM_PERIOD);
}

function pen_down()
{
	$angle = 180;

	$width = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * $angle / 180.0);

	if(($width >= WIDTH_MIN) && ($width <= WIDTH_MAX))
		ht_pwm_width(2, $width, PWM_PERIOD);
}

function xy_goto($x, $y)
{
	
	if($x < TOUCH_OFFSET)
		$x = TOUCH_OFFSET;
	if($x > (MAX_X - TOUCH_OFFSET))
		$x = MAX_X - TOUCH_OFFSET;
	
	if($y < TOUCH_OFFSET)
		$y = TOUCH_OFFSET;
	if($y > (MAX_Y - TOUCH_OFFSET))
		$y = MAX_Y - TOUCH_OFFSET;
	
	$x0 = (int)step_cmd(SID_X, "get pos");
	$y0 = (int)step_cmd(SID_Y, "get pos");
	
	$delta_x = $x - $x0; 
	$delta_y = $y - $y0;
	
	if($delta_x == 0 && $delta_y == 0)
		return;
	
	$speed_x = SPEED_X_COEF * abs($delta_x) + SPEED_X_OFFSET;
	$speed_y = SPEED_Y_COEF * abs($delta_y) + SPEED_Y_OFFSET;
	
	if($speed_x > SPEED_X_MAX)
		$speed_x = SPEED_X_MAX;
	
	if($speed_y > SPEED_Y_MAX)
		$speed_y = SPEED_Y_MAX;
	
	$accel_x = ACCEL_X_COEF * $speed_x + ACCEL_X_OFFSET;
	$accel_y = ACCEL_Y_COEF * $speed_y + ACCEL_Y_OFFSET;
	
	if($accel_x > ACCEL_X_MAX)
		$accel_x = ACCEL_X_MAX;
	
	if($accel_y > ACCEL_Y_MAX)
		$accel_y = ACCEL_Y_MAX;
	
	if($delta_x == 0)
	{
		step_cmd(SID_Y, "goto $y $speed_y $accel_y");
	}
	else if($delta_y == 0)
	{
		step_cmd(SID_X, "goto $x $speed_x $accel_x");
	}
	else
	{
		step_cmd(SID_X, "goto $x $speed_x $accel_x");
		step_cmd(SID_Y, "goto $y $speed_y $accel_y");
	}
}

function xy_state()
{
	$ret_val = 0;
	
	$x_state = (int)step_cmd(SID_X, "get state");
	$y_state = (int)step_cmd(SID_Y, "get state");
	
	if($x_state == 1) // motor is locked
		$ret_val |= STATE_X_LOCK;
	else if($x_state > 1) // motor is running
		$ret_val |= STATE_X_RUN;
		
	if($y_state == 1) // motor is locked
		$ret_val |= STATE_Y_LOCK;
	else if($y_state > 1) // motor is running
		$ret_val |= STATE_Y_RUN;
		
	return $ret_val;
}

function xy_wait()
{
	while(xy_state() & (STATE_X_RUN | STATE_Y_RUN))
		usleep(1);
}

function xy_init()
{
	$width = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * 110 / 180.0);
	ht_pwm_setup(2, $width, PWM_PERIOD);
	pen_up();
	
	spc_reset();
	spc_sync_baud(460800);

	if(!spc_check_did(SID_X, "40012403"))
		return;
	if(!spc_check_did(SID_Y, "40012403"))
		return;

	step_cmd(SID_X, "set vref stop 3");
	step_cmd(SID_X, "set vref drive 14");
	step_cmd(SID_X, "set mode half");
	step_cmd(SID_X, "set rsnc 120 250");
	
	step_cmd(SID_Y, "set vref stop 3");
	step_cmd(SID_Y, "set vref drive 14");
	step_cmd(SID_Y, "set mode half");
	step_cmd(SID_Y, "set rsnc 120 250");
	
	// move pen to (0, 0)
	step_cmd(SID_X, "goto -sw1 1600 20000");
	step_cmd(SID_Y, "goto -sw1 1600 20000");
	
	xy_wait();
	step_cmd(SID_X, "reset");
	step_cmd(SID_Y, "reset");
	
	xy_goto(TOUCH_OFFSET, TOUCH_OFFSET);
	xy_wait();
	
	// uncomment this block for the first run and change the value in line 45 of index.php
	/*
	// check max steps
	step_cmd(SID_X, "goto +sw0 1600 20000");
	step_cmd(SID_Y, "goto +sw0 1600 20000");
	
	xy_wait();
	// change this value in line 45 of index.php: var MAX_X = 3421, MAX_Y = 4296;
	echo "Max X:", step_cmd(SID_X, "get pos"), "\r\n";
	echo "Max Y:", step_cmd(SID_Y, "get pos"), "\r\n";
	*/
}

function send_position($pen_state)
{
	$x = (int)step_cmd(SID_X, "get pos");
	$y = (int)step_cmd(SID_Y, "get pos");
	$wbuf = "[$x, $y, $pen_state]";
	ws_write(0, $wbuf);
}

ws_setup(0, "xy_plotter", "csv.phpoc");
xy_init();

$pre_x = 0;
$pre_y = 0;
$cur_x = 0;
$cur_y = 0;
$rbuf = "";
$pen_state = CMD_PEN_UP;
$x_is_unlock = false;
$y_is_unlock = false;

while(1)
{
	if(ws_state(0) == TCP_CONNECTED)
	{
		$rlen = ws_read_line(0, $rbuf);
		
		if($rlen)
		{
			$data = explode(" ", $rbuf);
			$cmd = (int)$data[0];
			
			switch($cmd)
			{
				case CMD_PEN_DOWN:
					$x = (int)$data[1];
					$y = (int)$data[2];
					xy_goto($x, $y);
					xy_wait();
					
					pen_down();
					$pen_state = PEN_DOWN;
					
					send_position($pen_state);
					
					break;
				case CMD_PEN_UP:
					pen_up();
					$pen_state = PEN_UP;
					
					send_position($pen_state);
					
					break;
				case CMD_MOVE:
					$x = (int)$data[1];
					$y = (int)$data[2];
					xy_goto($x, $y);
					//xy_wait();
					
					break;
			}
		}
	}
	
	$cur_x = (int)step_cmd(SID_X, "get pos");
	$cur_y = (int)step_cmd(SID_Y, "get pos");
	
	if(abs($pre_x - $cur_x) > 10 || abs($pre_y - $cur_y) > 10)
	{
		$pre_x = $cur_x;
		$pre_y = $cur_y;
		send_position($pen_state);
	}
	
	$xy_state = xy_state();
	
	if(($xy_state & STATE_X_LOCK) && ($x_is_unlock == false))
	{
		step_cmd(SID_X, "unlock");
		$x_is_unlock = true;
	}
	else if(($xy_state & STATE_X_RUN))
	{
		step_cmd(SID_X, "eio set 0 mode lock");
		step_cmd(SID_X, "eio set 1 mode lock");
		$x_is_unlock = false;
	}
	
	if(($xy_state & STATE_Y_LOCK) && ($y_is_unlock == false))
	{
		step_cmd(SID_Y, "unlock");
		$y_is_unlock = true;
	}
	else if(($xy_state & STATE_Y_RUN))
	{
		step_cmd(SID_Y, "eio set 0 mode lock");
		step_cmd(SID_Y, "eio set 1 mode lock");
		$y_is_unlock = false;
	}
}

?>

Web interface (index.php)

HTML
This code is client side code. When receiving any request from a webclient (ex. Web browser), PHPoC will load index.php and run PHP script in side it, and then returns html and javascript to Web browser. Web browser will compile these two languages scripts. If this file contains a javascript-coded "websocket client", Web browser will acts as "websoket client" and connect to "websocket server"
<!DOCTYPE html>
<html>
<head>
<title>PHPoC - XY Plotter</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7">
<style>
body {
	text-align: center;
	background-color: #33C7F2;
}
#cvs_frame {
	margin-right: auto;
	margin-left: auto; 
	position: relative;
}
.canvas {
	position: absolute; 
	left: 0px;
	top: 0px;
	overflow-y: auto;
	overflow-x: hidden;
	-webkit-overflow-scrolling: touch; /* nice webkit native scroll */
}

#layer_1 {
	z-index: 2;
}
#layer_2 {
	z-index: 1;
}
#layer_3 {
	z-index: 0;
	background-color: #FFFFFF;
}

</style>
<script>
var PEN_UP = 0;
var PEN_DOWN = 1;
var CMD_PEN_UP = 0;
var CMD_PEN_DOWN = 1;
var CMD_MOVE = 2;
var RESOLUTION = 20;

var MAX_X = 3421, MAX_Y = 4296;

var ws = null;
var layer_1 = null, layer_2 = null, layer_3 = null;
var cvs_frame = null;
var ctx1 = null, ctx2 = null, ctx3 = null;

var canvas_width = 0, canvas_height = 0;
var x = 0, y = 0;
var touch_x = 0; touch_y = 0;
var cvs_pos_x = 0, cvs_pos_y = 0;
var pre_cvs_pos_x = 0, pre_cvs_pos_y = 0;
var pre_pen_state = PEN_UP;

function init()
{
	cvs_frame = document.getElementById("cvs_frame");
	
	layer_1 = document.getElementById("layer_1");
	layer_2 = document.getElementById("layer_2");
	layer_3 = document.getElementById("layer_3");
	
	var body = document.getElementsByTagName("BODY")[0];
	body.addEventListener("touchstart", disable_zoom);
	body.addEventListener("touchend", disable_zoom);
	body.addEventListener("touchmove", disable_zoom);
	body.addEventListener("mousedown", disable_zoom);
	body.addEventListener("mouseup", disable_zoom);
	
	layer_1.addEventListener("touchstart", mouse_down);
	layer_1.addEventListener("touchend", mouse_up);
	layer_1.addEventListener("touchmove", mouse_move);
	layer_1.addEventListener("mousedown", mouse_down);
	layer_1.addEventListener("mouseup", mouse_up);
	layer_1.addEventListener("mousemove", mouse_move);
	
	ctx1 = layer_1.getContext("2d");
	ctx1.translate(0, canvas_height);
	
	ctx2 = layer_2.getContext("2d");
	ctx2.translate(0, canvas_height);
	
	ctx3 = layer_3.getContext("2d");
	ctx3.translate(0, canvas_height);
	
	canvas_resize();
}
function canvas_clear()
{
	ctx1.clearRect(0, 0, canvas_width, -canvas_height);
	ctx2.clearRect(0, 0, canvas_width, -canvas_height);
	ctx3.clearRect(0, 0, canvas_width, -canvas_height);
}
function event_handler(event, type)
{
	var pre_x = x, pre_y = y;
	// convert coordinate
	if(event.targetTouches)
	{
		var targetTouches = event.targetTouches;
		
		touch_x = targetTouches[0].pageX - cvs_frame.offsetLeft;
		touch_y = targetTouches[0].pageY - cvs_frame.offsetTop - canvas_height;
	}
	else
	{
		touch_x = event.offsetX;
		touch_y = event.offsetY - canvas_height;
	}
	
	var temp_x = Math.round(touch_x / canvas_width * MAX_X);
	var temp_y = Math.round((-touch_y) / canvas_height * MAX_Y); 
	
	if(type == "MOVE")
	{
		var delta_x = temp_x - pre_x;
		var delta_y = temp_y - pre_y;
		var dist = Math.sqrt( Math.pow(delta_x, 2) + Math.pow(delta_y, 2) );
		
		if(dist < RESOLUTION)
			return false;
	}
	
	x = temp_x;
	y = temp_y;
	
	return true;
}
function ws_onmessage(e_msg)
{
	var arr = JSON.parse(e_msg.data);
	var pos_x			= arr[0];
	var pos_y			= arr[1];
	var cur_pen_state	= arr[2];
	
	pre_cvs_pos_x = cvs_pos_x;
	pre_cvs_pos_y = cvs_pos_y;
	
	cvs_pos_x = Math.round(pos_x * canvas_width / MAX_X);
	cvs_pos_y = -Math.round(pos_y * canvas_height / MAX_Y);
	
	ctx1.clearRect(0, 0, canvas_width, -canvas_height);
	ctx1.beginPath();
	ctx1.arc(cvs_pos_x, cvs_pos_y, 7, 0, 2*Math.PI);
	ctx1.fill();
	
	if(pre_pen_state == PEN_UP && cur_pen_state == PEN_DOWN)
		ctx2.beginPath();
	
	if(cur_pen_state == PEN_DOWN)
	{
		ctx2.lineTo(cvs_pos_x, cvs_pos_y);
		ctx2.stroke();
	}
	
	pre_pen_state = cur_pen_state;
}
function ws_onopen()
{
	document.getElementById("ws_state").innerHTML = "OPEN";
	document.getElementById("wc_conn").innerHTML = "Disconnect";
}
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;
}
function wc_onclick()
{
	if(ws == null)
	{
		ws = new WebSocket("ws://<?echo _SERVER("HTTP_HOST")?>/xy_plotter", "csv.phpoc");
		document.getElementById("ws_state").innerHTML = "CONNECTING";

		ws.onopen = ws_onopen;
		ws.onclose = ws_onclose;
		ws.onmessage = ws_onmessage;  
	}
	else
		ws.close();
}
function mouse_down()
{
	if(event.targetTouches)
	{
		event.preventDefault();
		if(event.targetTouches.length > 1)
			return;
	}
	
	event_handler(event, "DOWN");
	
	if(ws == null || ws.readyState != 1)
		return;

	ws.send(CMD_PEN_DOWN + " " + x + " " + y + "\r\n"); 
	
	draw_xy_line();
}
function mouse_up()
{
	if(event.targetTouches)
	{
		event.preventDefault();
	}
	
	if(ws == null || ws.readyState != 1)
		return;
			
	ws.send(CMD_PEN_UP + "\r\n"); 
}
function mouse_move()
{
	if(event.targetTouches)
	{
		event.preventDefault();
		if(event.targetTouches.length > 1)
			return;
	}
	
	if(!event_handler(event, "MOVE"))
		return;
	
	if(ws == null || ws.readyState != 1)
		return;
	
	ws.send(CMD_MOVE + " " + x + " " + y + "\r\n"); 
	
	draw_xy_line();
}
function disable_zoom()
{
	//event.preventDefault();
}
function canvas_resize()
{
	var width = Math.round(window.innerWidth*0.95);
	var height = Math.round(window.innerHeight*0.95) - 50;
	
	var temp_height = Math.round(width*MAX_Y/MAX_X);
	if(temp_height <= height)
	{
		canvas_width = width;
		canvas_height = temp_height;
	}
	else
	{
		canvas_width = height*MAX_X/MAX_Y;
		canvas_height = height;
	}
	
	cvs_frame.style.width = canvas_width + "px";
	cvs_frame.style.height = canvas_height + "px";
	cvs_frame.style.top = 0 + "px";
	cvs_frame.style.left = 100 + "px";
	
	layer_1.width = canvas_width;
	layer_1.height = canvas_height;
	ctx1.translate(0, canvas_height);
	ctx1.lineWidth = 5;
	ctx1.fillStyle = "green";
	
	layer_2.width = canvas_width;
	layer_2.height = canvas_height;
	ctx2.translate(0, canvas_height);
	ctx2.lineWidth = 3;
	ctx2.strokeStyle = "black";
	
	layer_3.width = canvas_width;
	layer_3.height = canvas_height;
	ctx3.translate(0, canvas_height);
	ctx3.strokeStyle = "00FFFF";
}
function draw_xy_line()
{
	ctx3.clearRect(0, 0, canvas_width, -canvas_height);
	
	ctx3.beginPath();
	ctx3.moveTo(0, touch_y);
	ctx3.lineTo(canvas_width, touch_y);
	ctx3.stroke();
	
	ctx3.beginPath();
	ctx3.moveTo(touch_x, 0);
	ctx3.lineTo(touch_x, -canvas_height);
	ctx3.stroke();
}
window.onload = init;
</script>
</head>

<body onresize="canvas_resize()">
<br>
<div id="cvs_frame">
	<canvas id="layer_1" class="canvas"></canvas>
	<canvas id="layer_2" class="canvas"></canvas>
	<canvas id="layer_3" class="canvas"></canvas>
</div>
<p>WebSocket : <span id="ws_state">null</span></p>
<button id="wc_conn" type="button" onclick="wc_onclick();">Connect</button>
<button type="button" onclick="canvas_clear();">Clear</button>
</body>
</html>

Credits

phpoc_man

phpoc_man

62 projects • 408 followers

Comments