Products for USB Sensing and Control
Products for USB Sensing and Control

MURVV Mobile Robot with JavaScript

The use of Phidgets JavaScript libraries to control a wheeled robot


by Phidgets

Introduction

This project demonstrates the use of the Phidget JavaScript libraries from a web browser. To make things interesting we chose to control MURVV from a mobile device such as a phone or Surface. MURVV is a four wheeled vehicle with independent motors controlling Mecanum wheels, so that adds a little excitement to operate and complexity to implement.

For the control device, a simple interface was developed using the touch screen as joystick interfaces and streaming the video from a webcam on the roof of MURVV.


conn = jPhidgets.newConnection('ws://192.168.3.190:8080/phidgets', { name: 'MURVV' });
conn.connect();

w_fr = new DCMotor();
w_fr.setDeviceSerialNumber(146645);
w_fr.open({
	onOpen: onOpen,
	onClose: onClose,
	onError: onError
});

Hardware

MURVV is a “robot” driven by four independent wheels, each attached to a DC motor controlled by a Phidget 1065 motor controller. The four 1065 are attached to a Phidget 1073 SBC running the latest Phidget22 libraries and the Phidget22 network server. The 1073 has a USB WIFI adapter allowing MURVV to operate untethered.

Software

The only custom code for the project was written in JavaScript and executed on an iPhone 6. The Phidget JavaScript library was used to communicate with a Phidget22 network server configured to run as a webserver. The webserver delivered a single HTML page that was the control interface and related JavaScript control software. An iframe was used to stream video from the Phidget 1073 SBC on MURVV.

  • Address of the Phidget network server we are connected to
  • Any notices from the executing control software
  • Two circles as simple joysticks
  • Video stream from the webcam on top of MURVV

Connecting to a Phidget device server via JavaScript is fairly straight forward. The websocket address of the server is used to create a connection to the server, and then Phidget channels for the devices to be controlled are created.


function onOpen(ch) {
	console.log('motor opened');
	ch.setAcceleration(50.0);		/* set the rate of change of duty cycle */
}

function onClose (ch) {
	console.log('motor closed');
}

conn = jPhidgets.newConnection('ws://192.168.3.190:8080/phidgets', { name: 'MURVV' });
conn.connect();

w_fr = new DCMotor();
w_fr.setDeviceSerialNumber(146645);
w_fr.open({
	onOpen: onOpen,		/* called when the device attaches to the channel */
	onClose: onClose,	/* called when the device detaches from the channel */
});

In the above example, we show the creation of the right front wheel channel object. The serial number of the Phidget 1065 motor controller for that wheel on MURVV is 146645, so we set that as the device serial number of the channel prior to opening it. This ensures that only that motor will be associated with the channel.

The following HTML creates a canvas for the touch controls, and places an iframe with the video stream in the middle.


<body>
	<canvas class="controls" width="960" height="400" id="canvas"></canvas>
	<div class="video1">
	<iframe class="video1" src="http://192.168.3.190:81/?action=stream"></iframe>
	</div>
	<div class="info">
		Address:<label id="addr"></label><br>
		Notice:<label id="notice"></label><br>
	</div>
</body>



The JavaScript code below demonstrates how we define and render the controls and determine which control is being touched based on the X and Y coordinates provided by the touch events.


/*
* Init code.
*/
var canvas = document.getElementsByTagName('canvas')[0];
canvas.addEventListener('touchstart', handleStart, false);
canvas.addEventListener('touchend', handleEnd, false);
canvas.addEventListener('touchcancel', handleCancel, false);
canvas.addEventListener('touchmove', handleMove, false);
leftControl.x = canvas.width / 4 - 80;
leftControl.y = canvas.height / 2 + 20;
leftControl.r = 140;
rightControl.x = canvas.width / 4 + canvas.width / 2 + 80;
rightControl.y = canvas.height / 2 + 20;
rightControl.r = 140;
...

/*
* Draws the control circles
*/
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF0000';
ctx.arc(leftControl.x, leftControl.y, leftControl.r, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(rightControl.x, rightControl.y, rightControl.r, 0, Math.PI * 2);
ctx.stroke();
...

/*
* Determines which control the touch coordinates are for.
*/
var cx = leftControl.x;
var cy = leftControl.y;
var c = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy));
if (c < leftControl.r)
	return (leftControl);
var cx = rightControl.x;
var cy = rightControl.y;
var c = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy));
if (c < rightControl.r)
	return (rightControl);

Once we determine which control a touch event is for, we calculate the magnitude and direction of the touch relative to the center of the control. This information is used to determine how to drive each motor to achieve the motion indicated by the user interacting with the control.


var magnitude = Math.sqrt(c.x * c.x + c.y * c.y);
var direction = Math.atan2(c.x, c.y);

The above code calculates the magnitude as the distance of the current touch point from the center of the control circle using SQRT(X^2 + Y^2). The direction uses the atan2() function to determine the angle in radians of the point from an X axis originating from the center of the circle. This information is fed into a routine that calculates the duty cycle for each Mecanuum wheel. This math is specific to the wheel, and beyond the scope of the project to explain.


/* magnitude, direction, rotation */
function mecanumDrive(mag, dir, rot) {

	mag = mag * Math.sqrt(2.0);

	// The rollers are at 45 degree angles.
	var dirInRad = (dir + 45.0) * Math.PI / 180.0;
	var cosD = Math.cos(dirInRad);
	var sinD = Math.sin(dirInRad);

	var wheelSpeeds = [];
	wheelSpeeds.push(sinD * mag + rot);
	wheelSpeeds.push(cosD * mag - rot);
	wheelSpeeds.push(cosD * mag + rot);
	wheelSpeeds.push(sinD * mag - rot);

	var maxOutput = 0.4;	/* speed limiting coeff. */

	w_fl.setDutyCycle(wheelSpeeds[0] * maxOutput * -1);
	w_fr.setDutyCycle(wheelSpeeds[1] * maxOutput);
	w_bl.setDutyCycle(wheelSpeeds[2] * maxOutput * -1);
	w_br.setDutyCycle(wheelSpeeds[3] * maxOutput);
}

Conclusion

The project was implemented on an iPhone 6, and was also tested on a Microsoft Surface Pro using the Edge browser.