Node.js + Wiimote = Fun

How to write a custom Node.js addon that interfaces with a Nintendo Wii Remote


Andrew Brampton
October 4th, 2013
NationJS

whoami

TOC

What is a wiimote?

Tech

So how does it work?

// Client (Browser) code

<script src="/socket.io/socket.io.js"></script>
1. var socket = io.connect();
2. socket.on( 'button', function( data ) {
3.   console.log('button was pressed ', data);
4. });

// Server (Node) code

 1. var wii = require('nodewii')
 2.  , express = require('express')
 3.  , app     = express()
 4.  , http    = require('http')
 5.  , server  = http.createServer(app)
 6.  , io      = require('socket.io').listen(server);
 7.
 8. var wiimote = new wii.WiiMote();
 9.
10. wiimote.connect( '00:00:00:00:00:00', function( err ) {
11.  console.log('connected');
12.
13.  wiimote.on( 'button', function( data ) {
14.    io.sockets.emit('button', data);
15.  });
16. });

So how do I write a addon?

What you need to learn

Build system

{
  "targets": [
    {
      "target_name": "nodewii",
      "sources": [ "src/base.cc", "src/wiimote.cc" ],
      "libraries": [ "-lcwiid", "-lbluetooth" ]
    }
  ]
}
        

Building

$ node-gyp configure
$ node-gyp build

base.cc

 1. #include <node.h>
 2. #include <v8.h>
 3.
 4. #include "../include/wiimote.h"
 5.
 6. void init(Handle<v8::Object> target) {
 7.  WiiMote::Initialize(target);
 8. }
 9. 
10. NODE_MODULE(nodewii, init);
  • Include v8.h
  • All addons need NODE_MODULE

Initialize(...)

 1. void WiiMote::Initialize (Handle<v8::Object> target) {
 2.   HandleScope scope;
 3.
 4.   Local<FunctionTemplate> t = FunctionTemplate::New(WiiMote::New);
 5.
 6.   constructor = Persistent<FunctionTemplate>::New(t);
 7.   constructor->SetClassName(String::NewSymbol("WiiMote"));
 8.
 9.   NODE_SET_PROTOTYPE_METHOD(constructor, "connect", Connect);
10.   NODE_SET_PROTOTYPE_METHOD(constructor, "disconnect", Disconnect);
11.
12.   NODE_DEFINE_CONSTANT_NAME(target, "BTN_A", CWIID_BTN_A);
13.   NODE_DEFINE_CONSTANT_NAME(target, "BTN_B", CWIID_BTN_B);
14.
15.   target->Set(String::NewSymbol("WiiMote"),
16.      constructor->GetFunction());
17. }

Initialize(...) (if it was JS)

1. Local<FunctionTemplate> t = FunctionTemplate::New(WiiMote::New);
2. 
3. constructor = Persistent<FunctionTemplate>::New(t);
4. constructor->SetClassName(String::NewSymbol("WiiMote"));
5. 
6. target->Set(String::NewSymbol("WiiMote"),
7.    constructor->GetFunction());
 8. module.WiiMote = function WiiMote() {
 9.   // calls C++ WiiMote::New()
10. }

Initialize(...) (if it was JS)

 1. NODE_SET_PROTOTYPE_METHOD(constructor, "connect", Connect);
 2. NODE_SET_PROTOTYPE_METHOD(constructor, "disconnect", Disconnect);
 3.
 4. NODE_DEFINE_CONSTANT_NAME(target, "BTN_A", CWIID_BTN_A);
 5. NODE_DEFINE_CONSTANT_NAME(target, "BTN_B", CWIID_BTN_B);
 6. module.WiiMote.connect = Connect;       // Calls C++ Connect(...)
 7. module.WiiMote.disconnect = Disconnect; // Calls C++ Disconnect(...)
 8. 
 9. module.BTN_A = 0x0008; //CWIID_BTN_A;
10. module.BTN_B = 0x0004; //CWIID_BTN_B;

Connect(...) - Arg validation

wiimote.connect( '00:00:00:00:00:00', function( err ) {
  console.log('wiimote connected');
});
 1. Handle<Value> WiiMote::Connect(const Arguments& args) {
 2.  HandleScope scope;
 3.
 4.  if(args.Length() == 0 || !args[0]->IsString()) {
 5.    return ThrowException(Exception::Error(
 6.      String::New("MAC address is required and must be a String."))
 7.    );
 8.  }
 9.  if(args.Length() == 1 || !args[1]->IsFunction()) {
10.    return ThrowException(Exception::Error(
11.      String::New("Callback is required and must be a Function."))
12.    );
14.  }
15.  String::Utf8Value mac(args[0]);
16.  Local<Function> callback = Local<Function>::Cast(args[1]);

Connect(...) - Do some async work

 1.   connect_request* ar = new connect_request();
 2.   ar->wiimote = wiimote;
 3.   str2ba(*mac, &ar->mac);
 4. 
 5.   uv_work_t* req = new uv_work_t;
 6.   req->data = ar;
 7. 
 8.   int r = uv_queue_work(uv_default_loop(), req,
 9.      UV_Connect, UV_AfterConnect);
10.   if (r != 0) {
11.      // Error handling
12.   }
13.
14.   return Undefined();
15. }

UV_Connect(...) - Do some blocking work

 1. void WiiMote::UV_Connect(uv_work_t* req) {
 2.   connect_request* ar = static_cast<connect_request* >(req->data);
 3. 
 4.    ar->wiimote = cwiid_open(&ar->mac, CWIID_FLAG_MESG_IFC)
 5.    if(ar->wiimote) {
 6.      ar->err = 0;
 7.    } else {
 8.      ar->err = -1;
 9.    }
10. }

UV_AfterConnect(...) - Do some after work

 1. void WiiMote::UV_AfterConnect(uv_work_t* req, int status) {
 2.   HandleScope scope;
 3. 
 4.   connect_request* ar = static_cast<connect_request* >(req->data);
 5. 
 6.   WiiMote * wiimote = ar->wiimote;
 7. 
 8.   if (ar->err == 0) {
 9.     // Setup the callback to receive events
10.     cwiid_set_data(wiimote->wiimote, wiimote);
11.     cwiid_set_mesg_callback(wiimote->wiimote,
12.        WiiMote::HandleMessages);
13.   }
14. }

UV_AfterConnect(..) - Do some callback work

 1.  TryCatch try_catch;
 2.
 3.  Local<Value> argv[1] = { Integer::New(ar->err) };
 4.  ar->callback->Call(Context::GetCurrent()->Global(), 1, argv);
 5.
 6.  if(try_catch.HasCaught())
 7.    FatalException(try_catch);
 8.
 9.  ar->callback.Dispose();
10.  delete ar;

So what is going on here?

HandleMessages(..) - libcwiid callback

1. void WiiMote::HandleMessages(cwiid_wiimote_t *wiimote, int len, union cwiid_mesg mesgs[], struct timespec *timestamp) {
2.   ...
3.   // We need to pass this over to the nodejs thread, so it can create V8 objects
4.   uv_work_t* uv = new uv_work_t;
5.   uv->data = req;
6.   int r = uv_queue_work(uv_default_loop(), uv, 
7.      UV_NOP, WiiMote::HandleMessagesAfter);

HandleButtonMessage(..)

1. void WiiMote::HandleButtonMessage(struct timespec *ts, cwiid_btn_mesg * msg) {
2.   HandleScope scope;
3. 
4.   Local btn = Integer::New(msg->buttons);
5. 
6.   Local argv[2] = { String::New("button"), btn };
7.   MakeCallback(self, "emit", ARRAY_SIZE(argv), argv);
8. }
1.  this.emit('button', CWIID_BTN_A);

So what is going on here?

Leasons learnt

Don't do any V8 on worker threads

V8 memory model is tricky

ar->callback = Persistent<Function>::New(callback);
wiimote->Ref();
wiimote->UnRef();

V8 memory model is tricky


Node is constantly changing!

Thanks - Questions?

Andrew Brampton - http://bramp.net
Get my contact by texting "bramp" to 99222


Genesys Labs is hiring!