Friday, January 22, 2016

Transform your Pebble Watch into a mouse

Here's a neat little project that I created for my Pebble smartwatch:  MouseRock.

No it's not about Rock'n Roll but about using the Pebble as a mouse for a computer.  I must admit, it's not a very efficient way to interact with a computer but it was fun to experiment with.

The Pebble App

Use CloudPebble.net to create the watch app in Javascript.  Nothing fancy but the following source code was created using Pebble.js

var UI = require('ui');
var serverIP = "http://192.168.0.121:8000";
var Accel = require('ui/accel');

serverIP = localStorage.getItem("serverip");
Accel.config({
  rate:10,
  samples: 1,
  subscribe:true
});
Accel.on('data', function(e) {
  var x = 0;
  var y = 0;
  if (e.accel.x  > 200) x = e.accel.x / 50;
  if (e.accel.y  > 200) y = e.accel.y / 50 *-1;
  if (e.accel.x  < -200) x = e.accel.x / 50;
  if (e.accel.y  < -200) y = e.accel.y / 50 *-1;
  if (x !== 0 || y !== 0){
    //console.log("Z: " + e.accel.z);
    getXML(serverIP + "/?mousex="+x+"&mousey="+y);
  }

});
function getXML(url) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", url, true);
    xmlhttp.send();
}
var main = new UI.Card({
  title: 'PebbleRock',
  icon: 'images/menu_icon.png',
  subtitle: 'Current server',
  body: serverIP,
  subtitleColor: 'indigo', // Named colors
  bodyColor: '#9a0036' // Hex colors
});
main.show();
main.on("click","up",function(){
  getXML(serverIP + "/?mouseclick=1");
});
main.on("click","down",function(){
  getXML(serverIP + "/?mouserightclick=1");
});
main.on("click","select",function(){

});
The basic idea is to retrieve the data from the accelerometer and send the information to the computer via the HTTP protocol.  Some tweaking has been included for a better mouse handling.  Left and Right buttons have been integrated using the "click" event...

The MouseRock Server

Now that you've created the watch app, you need to create the computer server to handle the data from the Pebble.  Again, nothing fancy, just a simple HTTP server built in Java.  Here's the source code...

package pebblemouse;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.awt.event.InputEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
/**
 *
 * @author patrick
 */
public class MouseRock {
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
        server.createContext("/", new MyHandler());
        server.setExecutor(null); // creates a default executor
        System.out.println("MouseRock Server:");
        System.out.println("Server Address: http://" + InetAddress.getLocalHost().getHostName() + ":" + 8000);
        System.out.println("----------------------------");
        System.out.println("Enter this URL into your MouseRock configuration page.");
        server.start();
    }
    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            try {
                java.awt.Robot robot = new java.awt.Robot();
                int currentX = (int) java.awt.MouseInfo.getPointerInfo().getLocation().getX();
                int currentY = (int) java.awt.MouseInfo.getPointerInfo().getLocation().getY();
                String response = "Command received...";
                if (t.getRequestURI().getQuery() != null) {
                    Map<String, String> maps = queryToMap(t.getRequestURI().getQuery());
                    for (String param : maps.keySet()) {
                        String value = maps.get(param);
                        switch (param) {
                            case "mousex":
                                int x = new Double(value).intValue();
                                currentX += x;
                                robot.mouseMove(currentX, currentY);
                                //System.out.println("Mouse X");
                                break;
                            case "mousey":
                                int y = new Double(value).intValue();
                                currentY += y;
                                robot.mouseMove(currentX, currentY);
                                //System.out.println("Mouse Y");
                                break;
                            case "mouseclick":
                                robot.mousePress(InputEvent.BUTTON1_MASK);
                                robot.mouseRelease(InputEvent.BUTTON1_MASK);
                                System.out.println("Mouse Clicked");
                                break;
                            case "mouserightclick":
                                robot.mousePress(InputEvent.BUTTON3_MASK);
                                robot.mouseRelease(InputEvent.BUTTON3_MASK);
                                System.out.println("Mouse Right Clicked");
                                break;
                        }
                    }
                }
                t.sendResponseHeaders(200, response.length());
                OutputStream os = t.getResponseBody();
                os.write(response.getBytes());
                os.close();
            } catch (Exception ex) {
                System.out.println(ex.getMessage());
            }
        }
    }
    public static Map<String, String> queryToMap(String query) {
        Map<String, String> result = new HashMap<>();
        for (String param : query.split("&")) {
            String pair[] = param.split("=");
            if (pair.length > 1) {
                result.put(pair[0], pair[1]);
            } else {
                result.put(pair[0], "");
            }
        }
        return result;
    }
}
What the server does is get the Mouse X, Mouse Y values sent from the Pebble smartwatch and uses the Robot class to move the mouse.  The server is moving the mouse by adding/subtracting a value to the current mouse location.  Since the Pebble watch will send a lot of data, those values must be small increments.  Buttons were also handled by activating them when the "Up" and "Down" were pressed on the Pebble.

Since the server was coded in Java, it should work on any operating system: Linux, Windows or OSX.

The Result

This hack won't replace a real mouse as it is a bit cumbersome to use.  But it's a fun experiment to create a motion activated mouse.

Here's a video of the experiment: