Project tutorial

Distance Measurement Vehicle via Websocket © GPL3+

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

  • 10,958 views
  • 1 comment
  • 71 respects

Components and supplies

About this project

When you measure distance between two point general way is to use a ruler. But you can use a lot of other ways: by laser, map, foot or walking meter.

The walking meter is very useful when you are measuring curved (not straight) distance. But it might be very tired because you should walk the entire distance.

Here is a movie which is measuring distance. You can see that the vehicle is being controlled by the web browser and distance is displayed in the web page in real-time.

The remote-controlled vehicle is measuring distance

This project is about a distance measuring vehicle. It is remotely controlled through the WiFi network via Websocket and user can get the distance data from the vehicle in real-time via the Websocket.

Now, I am going to explain how I have implemented the system.

1. Assembling a vehicle

At first, I have assembled a vehicle from the DFRobot.

http://www.robotshop.com/en/dfrobot-4wd-arduino-platform-encoders.html

The vehicle kit was great because I could get everything from this kit. But when I assembled two encoders some parts were not fit so it was tough to fix.

2. Modifying a web page

I have implemented a web page by using a source code from hackster.io. It is a cool implementation of a joystick via Websocket.

https://www.hackster.io/iot_lover/arduino-web-based-joystick-02ca54

I have added a function to display distance data from the vehicle.

And I have modified that it transmits control data to the vehicle only when joystick position is changed over predefined offset because there were a lot of data transmission with original web page.

3. Programming Arduino Uno

I have programmed one source code for the Arduino Uno and PHPoC Wifi Shield.

The PHPoC Wifi Shield supports Websocket so user can implement a real-time web-based system with Arduino Uno.

Data from the web browser through PHPoC Shield's Websocket are text number from 0 to 100. But the PWM data for the DC motor driver are from 0 to 255. So there is a conversion routine after reading the data from the Websocket.

And distance is transmitted every 10cm through the Websocket. Distance is determined by counting the encoder on the wheel and calculating it based on the wheel's diameter.

So I needed to count the encoder. But calculated distance is so large than I expected. I found that there are a lot of noise when I reading the encoder state from pin 2. So I programmed software filter. The algorithm is followed:

  • Check the input port (digital port 2) ever 100us in ISR (Interrupt Service Routine) from the TimerOne library.
  • If the read state is same to former state it increase a number and it decrease it when the state is not same.
  • When the count is over to the predefined threshold it counts one.

Schematics

System Diagram
The system diagram
Motor vehicle2 cdsn4tn08p

Code

joystick.phpPHP
PHPoC code which controls the vehicle and monitor the current distance from the vehicle via Websocket.
I have modified it from the joystic application on hackster.io (https://www.hackster.io/iot_lover/arduino-web-based-joystick-02ca54)
<!DOCTYPE html>
<html>
<head>
<title>Arduino - PHPoC Shield</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 pos_old;

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
	document.getElementById("distance").innerHTML = e_msg.data;
}
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);
	pos_old = pos;
	
	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;
		
		if((Math.abs(pos_old.x - pos.x) > 5) || (Math.abs(pos_old.y - pos.y) > 5))
		{
			send_data();
			pos_old = pos;
		}
		update_view();
		
	}
}

function clear_distance()
{
	if(ws != null) ws.send("c\r\n");
}
window.onload = init;
</script>

</head>

<body>

<p>
<h1>Arduino - Web-based Joystick</h1>
</p>

<canvas id="remote"></canvas>

<h2>
<p>
Distance: <span id="distance">null</span>
</p>

<h2>
<p>
<input type="button" value="Clear Distance" id="ClickMe" onclick="clear_distance();" />
</p>

<p>
WebSocket : <span id="ws_state">null</span>
</p>
<button id="bt_connect" type="button" onclick="connect_onclick();">Connect</button>
</h2>

</body>
</html>
distance_measuring_machineArduino
This is C source code for Arduino Uno
#include "SPI.h"
#include "Phpoc.h"

#include "TimerOne.h"

volatile long encoder1 = 0, encoder2 = 0;
unsigned long encoder1_old = 0, encoder2_old = 0;
unsigned long uptime_old = 0;

//Arduino PWM Speed Control
int EL = 5;  
int ML = 4; 
int ER = 6;                      
int MR = 7;     

int amr, aml;

float max_speed;

bool state_old = 0;
int filter_count = 0;
int filter_threshold = 10;

PhpocServer server(80);



void setup() {
  Serial.begin(9600);
  while(!Serial)
    ;

  Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
  //Phpoc.begin();

  server.beginWebSocket("web_joystick");

  Serial.print("WebSocket server address : ");
  Serial.println(Phpoc.localIP());
  
  pinMode(ML, OUTPUT);   
  pinMode(MR, OUTPUT);
  digitalWrite(ML, LOW);
  digitalWrite(MR, HIGH); 

  pinMode(2, INPUT);
  digitalWrite(2, HIGH); // pull-up
  
  encoder1 = 0;
  encoder2 = 0;
  encoder1_old = 0;
  encoder2_old = 0;

  //attachInterrupt(digitalPinToInterrupt(2), ISR_di1, FALLING);
  Timer1.initialize(100); // 0.1m second
  Timer1.attachInterrupt(ISR_Timer1);
}

void loop() {
  double distance1;
  String str;
  char ch[32];
  
  // wait for a new client:
  PhpocClient client = server.available();

  if (client) {
    String data = client.readLine();

    if(data){
      if(data.substring(0, 1) == "c") 
      {
        Serial.println("Clearing encoder1...\r\n");
        encoder1 = 0;
        encoder1_old = 0;
        server.write("0");
        return;
      }
      int pos = data.indexOf(':');
      long x = data.substring(0, pos).toInt();
      long y = data.substring(pos+1).toInt();

      if(y < 0)
      {
        analogWrite(EL, 0);
        analogWrite(ER, 0);
        return;
      }
      else
      {
        max_speed = sqrt((float)x*(float)x/10000+(float)y*(float)y/10000);
        max_speed *= 255;
        //Serial.print("Max Speed: ");
        //Serial.println(max_speed);

        //Serial.print("encoder1 = ");
        //Serial.println(encoder1);
      }

      if(x == 0)
      {
        aml = (int)max_speed;
        amr = (int)max_speed;
      }
      else if(x > 0)
      {
        aml = (int)max_speed;
        amr = (int)(max_speed*(100-abs(x))/100);
      }
      else if(x < 0)
      {
        aml = (int)(max_speed*(100-abs(x))/100);
        amr = (int)max_speed;
      }
      
      analogWrite(EL, aml);
      analogWrite(ER, amr);
      
    }
  }
  if(encoder1 >= (encoder1_old + 10))
  {
    distance1 = (double)encoder1 * 0.0103;
    str = String(distance1, 2);
    str.toCharArray(ch, str.length());
    server.write(ch);
    Serial.print(encoder1);
    Serial.print(":");
    Serial.println(ch);
    encoder1_old = encoder1;
  }

}

void ISR_Timer1()
{
  bool state = 0;
  
  state = digitalRead(2);
  if(state != state_old)
  {
    filter_count++;
    if(filter_count > filter_threshold) 
    {
      state_old = state;
      encoder1++;
    }
  }
  if(state == state_old && filter_count > 0)
  {
    filter_count--;
  }
}

/*
void ISR_di1()
{
  unsigned long uptime_now;
  uptime_now = mills();
  if(uptime
  encoder1++;
}
*/

Comments

Similar projects you might like

Cayenne and SparkFun IoT 433 Mhz Sockets

Project tutorial by Giovanni Gentile

  • 326 views
  • 0 comments
  • 4 respects

Makers(Cubecon) #4 First Prototype

Project in progress by Alpha

  • 69 views
  • 0 comments
  • 2 respects

Mini CNC: A Handmade Masterpiece

Project showcase by Mr-M

  • 5,209 views
  • 2 comments
  • 17 respects

Arduino Controlled Piano Robot: PiBot

Project tutorial by Jeremy Yoo

  • 2,748 views
  • 1 comment
  • 13 respects
Add projectSign up / Login