Control a light attached to an arduino using a mobile phone's web browser.

From the Blog

Sep
07

RGB LED controlled by mobile phone browser

Posted by ajfisher on September 7th, 2011 at 4:01 pm

As a web oriented person I tend to think web-first whenever I think of mobile. I posted a little while back about how to get a mobile phone to pull it’s orientation data out using some javascript and pass that data to a web server. This has evolved somewhat now and I have a packaged Git Hub project that shows you how to do it properly.

One of the demo projects is controlling an RGB light using the web browser’s gyro in order to get it’s orientation then mapping that to the three colour channels.

There are three core parts to the application:

Arduino / light set up:

In this scenario I’m just using a serial connection for simplicity. All the arduino is doing is looking for data that’s packaged up as a sequence of bytes that looks like this:

Byte 0 (int) 255 header byte
Byte 1 (int) 255 header byte
Byte 2 (int)  0-180 val for x
Byte 3 (int)  high byte for y
Byte 4 (int)  low byte for y
Byte 5 (int)  high byte for z
Byte 6 (int)  low byte for z

The code simply looks at the incoming data stream, looks for the double 255 bytes then reads the next 5 bytes into a buffer and converts them into variables. Check out the code on github.

Once it has the data it then maps to the RGB LED appropriately.

Web Browser

Modern mobile browsers (Firefox on Android, Mobile safari on iOS) give you access to the device API. This give us device motion and device orientation events that will fire every time the device moves. Using the orientation event we can trap the data about the way the device is facing then send that back via web sockets to the server.

var socket;
var room = "light";
 
var last_sent = (new Date()).getTime();
var threshold = 100; // msec between sends
 
window.addEventListener("deviceorientation", update_gyro, true);
 
function update_gyro(e) {
	// gets the gyro position
    var x, y, z = 0;
    var o = deviceOrientation(e);
 
	update_text(o.gamma, o.beta, o.alpha);
    if ((new Date()).getTime() - last_sent > threshold) {
		socket.send({room: room, action: 'movement', x: o.gamma, y: o.beta, z: o.alpha, method: 'orientation'});
    }
}
 
function update_text(x, y, z) {
    // updates the text on the screen
    $("#xval").text(x);
    $("#yval").text(y);
    $("#zval").text(z);
 
}
 
$(function() {
 
	var started = false;
	// socket tester.
	$("#test").click (function () {
		socket.send({room: room, action: 'test'});
		z++;
	});
 
	// this is the browser test version
	$(window).bind("mousemove", function(e) {
		var x = e.pageX;
		var y = e.pageY;
		var z = 255;
		socket.send({room: room, action: 'movement', x: x, y: y, z: z, method: 'mouse'});
		update_text(x, y, z);
	});
 
	// now we do the mobile version.
 
	socket = new io.Socket();
	socket.connect();
	socket.on('connect', function() {
		socket.subscribe(room);
	});
 
    socket.on('message', function(data) {
        switch (data.action) {
            case 'bcast':
                console.log(data.message)
                //console.log(
                break;
        }
    });
 
});

All this code is doing is simply hooking up to the socketio server (discussed below) and subscribing to a room. From there we register the event handler for the orientation events and then take the data, normalise it to make it consistent across devices and then pass it back via a websockets message to the socketio server.

Django SocketIO

The Django web sockets server is pretty much the glue in the middle. We create a view which takes web sockets messages. It does the pre-processing on the data from the phone to split it up and write it to the serial port using the correct message protocol we defined earlier.

@events.on_message(channel="^light")
def message(request, socket, message):
    #import pdb; pdb.set_trace()
    message = message[0]
    if message["action"] == "movement":
        socket.send({"action": "ack"})
 
        # pick up the values from the socket message
        x = int(message["x"])
        y = int(message["y"])
        z = int(message["z"])
 
        # now normalise the values as needed
        if message["method"] == "orientation":
            # put the vals back into +ive integer range as needed
            x += 90 #normalise 0-180
            y += 360 # normalise 0-360
 
        if x > 180:
            x = 180
        if y > 360:
            y = 360
        if z >= 360:
            z = 0
 
        # work out the y bytes
        # leaving this like this even though it's normalised back to 180 deg.
        # just in case there's any more changes to firefox.
        yh = y >> 8
        if y > 255:
            yl = y - 256
        else:
            yl = y
        # work out the z bytes
        zh = z >> 8
        if z > 255 :
            zl = z - 256
        else:
            zl = z
 
        print "x: %s y: %s z: %s" % (x, y, z)
        try:
            ser.write("%s%s%s%s%s%s%s" % (chr(255), chr(255), chr(x), chr(yh), chr(yl), chr(zh), chr(zl)))
        except:
            #do  nothing - this is a good test anyway.
            print "Doing nothing as no serial connection: %s" % SERIAL_INTERFACE
 
    elif message["action"] == "test":
        # this is a test of the socket
        print "Test of the socket"
        socket.send({"action": "bcast", "message": "got a test"})

 

Nothing too difficult with this, we register the function as a message handler and then check the message as it comes in, do some work on the data and then write it out to the serial connection with a test if something’s wrong.

This is a really basic proof of concept that can be done with a handful of components but shows the fundamentals of how this interaction can work. From here we could control servos or just about any sort of actuator you’d like.