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
Andrew Brampton
October 4th, 2013
NationJS
<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. });
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. });
{ "targets": [ { "target_name": "nodewii", "sources": [ "src/base.cc", "src/wiimote.cc" ], "libraries": [ "-lcwiid", "-lbluetooth" ] } ] }
$ node-gyp configure
$ node-gyp build
var wii = require('/build/nodewii.node')
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);
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. }
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. }
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;
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]);
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. }
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. }
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. }
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;
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);
1. void WiiMote::HandleButtonMessage(struct timespec *ts, cwiid_btn_mesg * msg) { 2. HandleScope scope; 3. 4. Localbtn = 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);
ar->callback = Persistent<Function>::New(callback); wiimote->Ref(); wiimote->UnRef();