Joon-Ho Son


Flask Wii

https://github.com/sonjoonho/flask-wii

After stumbling across a neat little project called web-riimote by konaraddio I thought it would be fun to recreate the features of this app from scratch, and without peeking at konaraddio’s code. I had been using Flask a lot recently, so that was my tool of choice.

Device orientation data is collected from the controller (a mobile device) using the Device Orientation API which makes it extrordinarily simple to extract.

if (window.DeviceOrientationEvent) { // Check if the API is supported on this device.

  window.addEventListener("deviceorientation", function(eventData) {       

    let alpha = eventData.alpha;
    let beta = eventData.beta;
    let gamma = eventData.gamma;
                                                                        
  }, false);

}

Now all we have to do is send this data to the server.

To support the sending of realtime device orientation data from the client to the server, we need to use WebSockets. We will implement this using the JavaScript library SocketIO, which uses WebSockets where available, but also has the advantages of providing failovers in the event that WebSockets are not supported. SocketIO also allows the use of namespaces and rooms to separate communication channels.

let socket = io.connect('https://' + document.domain + ':' + location.port + "/wii");

let room = ... // Whatever the room number is

socket.emit("orientation", {room: room, angles: {alpha: alpha, beta: beta, gamma: gamma}});

Receiving socket events from the Flask server is also made easy with the Flask-SocketIO extension. We can receive, handle, and emit socket events from the server.

@socketio.on("orientation", namespace="/wii")
def orientation(data):
    sid = request.sid 

    room = data["room"]
    angles = data["angles"]

    position = calculate_pos(angles["alpha"], angles["beta"])
    emit("position", {"sid": sid, "position": position, "angles": angles}, room=room)

The controller, the center of the main display, and the edge of the main display form a right angle triangle. Then we can the tangent trigonometric ratio to calculate the position of the cursor on the screen. I feel like this could be proved, but it works fine for the demo. The calculate_pos function performs this calculation and returns a dictionary of the form {"x": x_pos, "y": y_pos}.

The server then emits the position data to the server, which then handles the cursor position.

let socket = io.connect("https://" + document.domain + ":" + location.port + "/wii"); 

const room = ... // The same room as the controller



// A separate "client_join" event is defined, distinct from the default "connected" event
// This is to differentiate between the main display connecting to the server, and the controller connecting
socket.on("client_join", function(data) {
  console.log("New client with sid: " + data.sid)
  // Spawn a new cursor
  CursorObject(data.sid);
});

// Each cursor object is named uniquely to support multiple users in a room
let CursorObject = function(sid) {
  this.cursor = document.createElement("img");
  this.cursor.setAttribute("class", "cursor");
  this.cursor.setAttribute("id", "cursor"+sid);
  this.cursor.setAttribute("src", "/static/server/cursor.png");
  this.cursor.setAttribute("width", "50");
  this.cursor.style.position = "absolute";
  document.body.appendChild(this.cursor);
};


let screenWidth = window.screen.width * window.devicePixelRatio;
let screenHeight = window.screen.height * window.devicePixelRatio;

socket.on("position", function(data) {
  position = data.position;
  sid = data.sid

  let cursor = document.getElementById("cursor" + sid); 

  cursorPosition = {
    x: position.x * screenWidth,
    y: position.y * screenHeight
  };

  cursor.style.left = (cursorPosition.x) + "px";
  cursor.style.top = (cursorPosition.y) + "px";
  cursor.style.transform = "rotate(" + gamma + "deg)";
});

And that’s pretty much it! Obviously, this is very much a skeleton, and doesn’t include any of the backend routing and features such as steering and controller button presses, nor the hours of troubleshooting networking bugs that occur for no discernable reason; but this encompasses much of the basic architecture. For a more in-depth look, visit the GitHub repo (linked at the top of this article).

Built With

Tools

Acknowledgements

Future Work