UNPKG

15 kBJavaScriptView Raw
1var Board = require("./board"),
2 events = require("events"),
3 util = require("util");
4
5var Devices, Change, Update;
6
7// Event type alias map
8var aliases = {
9 down: ["down", "press", "tap", "impact", "hit"],
10 up: ["up", "release"],
11 hold: ["hold"]
12};
13
14// all private instances
15var priv = new Map();
16
17// hold time out for buttons.
18var holdTimeout = new Map();
19
20// keeps data between cycles and fires change event
21// if data changes
22var last = new Map();
23
24
25
26
27/**
28 * Wii
29 * @constructor
30 *
31 * five.Wii({
32 * device: "RVL-004",
33 * holdtime: ms before firing a hold event on a button,
34 * freq: ms to throttle the read data loop
35 * threshold: difference of change to qualify for a change event
36 * });
37 *
38 * Available events:
39 * "data" - firehose.
40 * "down", "press", "tap", "impact", "hit" - button press
41 * "up", "release" - button release
42 * "hold" - button hold
43 *
44 * @param {Object} opts [description]
45 *
46 */
47
48function Wii(opts) {
49 /* istanbul ignore if */
50 if (!(this instanceof Wii)) {
51 return new Wii(opts);
52 }
53
54 Board.Component.call(this, opts);
55
56 // Derive device definition from Devices
57 var device = Devices[opts.device];
58 var address = device.address;
59 var bytes = device.bytes;
60 var delay = device.delay;
61 var data = device.data.bind(this);
62 var setup = device.setup;
63 var preread = device.preread;
64
65 // Wii controller instance properties
66 this.freq = opts.freq || 100;
67
68 // Button instance properties
69 this.holdtime = opts.holdtime || 500;
70 this.threshold = opts.threshold || 10;
71
72 // Initialize components
73 device.initialize.call(this);
74
75 // Set initial "last data" byte array
76 last.set(this, [0, 0, 0, 0, 0, 0, 0]);
77
78 // Set up I2C data connection
79 this.io.i2cConfig(opts);
80
81 // Iterate and write each set of setup instructions
82 setup.forEach(function(bytes) {
83 this.io.i2cWrite(address, bytes);
84 }, this);
85
86 // Unthrottled i2c read request loop
87 setInterval(function() {
88
89 // Send this command to get all sensor data and store into
90 // the 6-byte register within Wii controller.
91 // This must be execute before reading data from the Wii.
92
93 // Iterate and write each set of setup instructions
94 preread.forEach(function(bytes) {
95 this.io.i2cWrite(address, bytes);
96 }, this);
97
98
99 // Request six bytes of data from the controller
100 this.io.i2cReadOnce(address, bytes, data);
101
102 // Use the high-frequency data read loop as the change event
103 // emitting loop. This drastically improves change event
104 // frequency and sensitivity
105 //
106 // Emit change events if any delta is greater than
107 // the threshold
108
109 // RVL-005 does not have a read method at this time.
110 /* istanbul ignore else */
111 if (typeof device.read !== "undefined") {
112 device.read.call(this);
113 }
114 }.bind(this), delay || this.freq);
115
116 // Throttled "read" event loop
117 setInterval(function() {
118 var event = new Board.Event({
119 target: this
120 });
121
122 this.emit("data", event);
123
124 }.bind(this), this.freq);
125}
126
127Wii.Components = {};
128
129// A nunchuck button (c or z.)
130Wii.Components.Button = function(which, controller) {
131 /* istanbul ignore if */
132 if (!(this instanceof Wii.Components.Button)) {
133 return new Wii.Components.Button(which, controller);
134 }
135
136 // c or z.
137 this.which = which;
138
139 // reference to parent controller
140 this.controller = controller;
141
142 // Set initial values for state tracking
143 var state = {
144 isDown: false
145 };
146 priv.set(this, state);
147
148 Object.defineProperties(this, {
149 // is the button up (not pressed)?
150 isUp: {
151 get: function() {
152 return !state.isDown;
153 }
154 },
155
156 // is the button pressed?
157 isDown: {
158 get: function() {
159 return state.isDown;
160 }
161 }
162 });
163};
164
165Wii.Components.Joystick = function(controller) {
166 /* istanbul ignore if */
167 if (!(this instanceof Wii.Components.Joystick)) {
168 return new Wii.Components.Joystick(controller);
169 }
170
171 this.controller = controller;
172
173 var state, accessors;
174
175 // Initialize empty state object
176 state = {};
177
178 // Initialize empty accessors object
179 accessors = {};
180
181 // Enumerate Joystick properties
182 ["x", "y", "dx", "dy"].forEach(function(key) {
183
184 state[key] = 0;
185
186 // Define accessors for each property in Joystick list
187 accessors[key] = {
188 get: function() {
189 return state[key];
190 }
191 };
192 }, this);
193
194 // Store private state cache
195 priv.set(this, state);
196
197 // Register newly defined accessors
198 Object.defineProperties(this, accessors);
199};
200
201Wii.Components.Accelerometer = function(controller) {
202 /* istanbul ignore if */
203 if (!(this instanceof Wii.Components.Accelerometer)) {
204 return new Wii.Components.Accelerometer(controller);
205 }
206
207 this.controller = controller;
208
209 var state, accessors;
210
211 // Initialize empty state object
212 state = {};
213
214 // Initialize empty accessors object
215 accessors = {};
216
217 // Enumerate Joystick properties
218 ["x", "y", "z", "dx", "dy", "dz"].forEach(function(key) {
219
220 state[key] = 0;
221
222 // Define accessors for each property in Joystick list
223 accessors[key] = {
224 get: function() {
225 return state[key];
226 }
227 };
228 }, this);
229
230 // Store private state cache
231 priv.set(this, state);
232
233 // Register newly defined accessors
234 Object.defineProperties(this, accessors);
235};
236
237util.inherits(Wii, events.EventEmitter);
238util.inherits(Wii.Components.Button, events.EventEmitter);
239util.inherits(Wii.Components.Joystick, events.EventEmitter);
240util.inherits(Wii.Components.Accelerometer, events.EventEmitter);
241
242
243// Regular Wiimote driver bytes will be encoded 0x17
244
245function decodeByte(x) {
246 return (x ^ 0x17) + 0x17;
247}
248
249// Change handlers for disparate controller event types
250//
251// Note: Change.* methods are |this| sensitive,
252// therefore, call sites must use:
253//
254// Change.button.call( instance, data );
255//
256// Change.component.call( instance, data );
257//
258//
259Change = {
260
261 // Fire a "down", "up" or "hold" (and aliases) event
262 // for a button context
263 button: function(key) {
264 // |this| is button context set by calling as:
265 // Change.button.call( button instance, event key );
266 //
267
268 // Enumerate all button event aliases,
269 // fire matching types
270 aliases[key].forEach(function(type) {
271 var event = new Board.Event({
272 // |this| value is a button instance
273 target: this,
274 type: type
275 });
276
277 // fire button event on the button itself
278 this.emit(type, event);
279
280 // fire button event on the controller
281 this.controller.emit(type, event);
282 }, this);
283 },
284
285 // Fire a "change" event on a component context
286 component: function(coordinate) {
287 // |this| is component context set by calling as:
288 // Change.component.call( component instance, coordinate, val );
289 //
290
291 ["axischange", "change"].forEach(function(type) {
292 var event;
293
294 if (this._events && this._events[type]) {
295 event = new Board.Event({
296 // |this| value is a button instance
297 target: this,
298 type: type,
299 axis: coordinate,
300 // Check dx/dy/dz change to determine direction
301 direction: this["d" + coordinate] < 0 ? -1 : 1
302 });
303
304 // Fire change event on actual component
305 this.emit(type, event);
306
307 // Fire change on controller
308 this.controller.emit(type, event);
309 }
310 }, this);
311 }
312};
313
314// Update handlers for disparate controller event types
315//
316// Note: Update.* methods are |this| sensitive,
317// therefore, call sites must use:
318//
319// Update.button.call( button instance, boolean down );
320//
321// Update.component.call( component instance, coordinate, val );
322//
323//
324
325Update = {
326 // Set "down" state for button context.
327 button: function(isDown) {
328 // |this| is button context set by calling as:
329 // Update.button.call( button instance, boolean down );
330 //
331
332 var state, isFireable;
333
334 // Derive state from private cache
335 state = priv.get(this);
336
337 // if this is a state change, mark this
338 // change as fireable.
339 isFireable = false;
340
341 if (isDown !== state.isDown) {
342 isFireable = true;
343 }
344
345 state.isDown = isDown;
346
347 if (isFireable) {
348 // start hold timeout for broadcasting hold.
349 holdTimeout.set(this, setTimeout(function() {
350 if (state.isDown) {
351 Change.button.call(this, "hold");
352 }
353 }.bind(this), this.controller.holdtime));
354
355 Change.button.call(this, isDown ? "down" : "up");
356 }
357 },
358
359 // Set "coordinate value" state for component context.
360 component: function(coordinate, val) {
361 // |this| is component context set by calling as:
362 // Update.component.call( component instance, coordinate, val );
363 //
364
365 var state = priv.get(this);
366 state["d" + coordinate] = val - state[coordinate];
367 state[coordinate] = val;
368 }
369};
370
371
372Devices = {
373
374 // Nunchuk
375 "RVL-004": {
376 address: 0x52,
377 bytes: 6,
378 delay: 100,
379 setup: [
380 [0x40, 0x00]
381 ],
382 preread: [
383 [0x00]
384 ],
385 // device.read.call(this);
386 read: function() {
387 var axes = ["x", "y", "z"];
388
389 [
390 this.joystick,
391 this.accelerometer
392 ].forEach(function(component) {
393 axes.forEach(function(axis) {
394 var delta = "d" + axis;
395 if (typeof component[delta] !== "undefined") {
396 if (Math.abs(component[delta]) > this.threshold) {
397 Change.component.call(component, axis);
398 }
399 }
400 }, this);
401 }, this);
402 },
403 // Call as:
404 // device.initialize.call(this);
405 initialize: function() {
406 this.joystick = new Wii.Components.Joystick(this);
407 this.accelerometer = new Wii.Components.Accelerometer(this);
408 this.c = new Wii.Components.Button("c", this);
409 this.z = new Wii.Components.Button("z", this);
410 },
411 data: function(data) {
412 // TODO: Shift state management to weakmap, this
413 // should only update an entry in the map
414 //
415
416 if (data[0] !== 254 && data[1] !== 254 && data[2] !== 254) {
417
418 // Byte 0x00 : X-axis data of the joystick
419 Update.component.call(
420 this.joystick,
421 "x", decodeByte(data[0]) << 2
422 );
423
424 // Byte 0x01 : Y-axis data of the joystick
425 Update.component.call(
426 this.joystick,
427 "y", decodeByte(data[1]) << 2
428 );
429
430 // Byte 0x02 : X-axis data of the accellerometer sensor
431 Update.component.call(
432 this.accelerometer,
433 "x", decodeByte(data[2]) << 2
434 );
435
436 // Byte 0x03 : Y-axis data of the accellerometer sensor
437 Update.component.call(
438 this.accelerometer,
439 "y", decodeByte(data[3]) << 2
440 );
441
442 // Byte 0x04 : Z-axis data of the accellerometer sensor
443 Update.component.call(
444 this.accelerometer,
445 "z", decodeByte(data[4]) << 2
446 );
447
448 // Update Z button
449 // Grab the first bit of the sixth byte
450 Update.button.call(
451 this.z, (decodeByte(data[5]) & 0x01) === 0
452 );
453
454 // Update C button
455 // Grab the second bit of the sixth byte
456 Update.button.call(
457 this.c, (decodeByte(data[5]) & 0x02) === 0
458 );
459
460 // Update last data array cache
461 last.set(this, data);
462 }
463 }
464 },
465
466 // Classic Controller
467 "RVL-005": {
468 address: 0x52,
469 bytes: 6,
470 delay: 100,
471 setup: [
472 [0x40, 0x00]
473 ],
474 preread: [
475 [0x00]
476 ],
477
478 // read: function( this ) {
479 // var axes = [ "x", "y", "z" ];
480
481 // [ this.joystick.left, this.joystick.right ].forEach(function( component ) {
482 // axes.forEach( function( axis ) {
483 // var delta = "d" + axis;
484 // if ( typeof component[ delta ] !== "undefined" ) {
485 // if ( Math.abs( component[ delta ] ) > this.threshold ) {
486 // Change.component.call( component, axis );
487 // }
488 // }
489 // }, this );
490 // }, this );
491 // },
492 initialize: function() {
493
494 this.joystick = {
495 left: new Wii.Components.Joystick(this),
496 right: new Wii.Components.Joystick(this)
497 };
498
499 // obj.direction_pad = new Wii.DirectionPad( obj );
500 [
501 "y", "x", "up", "down", "left", "right",
502 "a", "b", "l", "r", "zl", "zr", "start", "home", "select"
503 ].forEach(function(id) {
504
505 this[id] = new Wii.Components.Button(id, this);
506
507 }, this);
508 },
509 data: function(data) {
510 // TODO: Shift state management to weakmap, this
511 // should only update an entry in the map
512 if (data[0] !== 254 && data[1] !== 254 && data[2] !== 254) {
513
514 // LEFT/RIGHT
515 Update.button.call(
516 this.l, (decodeByte(data[4]) & 0x20) === 0
517 );
518
519 Update.button.call(
520 this.r, (decodeByte(data[4]) & 0x02) === 0
521 );
522
523 // Direction
524 Update.button.call(
525 this.up, (decodeByte(data[5]) & 0x01) === 0
526 );
527
528 Update.button.call(
529 this.left, (decodeByte(data[5]) & 0x02) === 0
530 );
531
532 Update.button.call(
533 this.down, (decodeByte(data[4]) & 0x40) === 0
534 );
535
536 Update.button.call(
537 this.right, (decodeByte(data[4]) & 0x80) === 0
538 );
539
540 // Z*
541 Update.button.call(
542 this.zr, (decodeByte(data[5]) & 0x04) === 0
543 );
544
545 Update.button.call(
546 this.zl, (decodeByte(data[5]) & 0x80) === 0
547 );
548
549 // X/Y
550 Update.button.call(
551 this.x, (decodeByte(data[5]) & 0x08) === 0
552 );
553
554 Update.button.call(
555 this.y, (decodeByte(data[5]) & 0x20) === 0
556 );
557
558 // A/B
559 Update.button.call(
560 this.a, (decodeByte(data[5]) & 0x10) === 0
561 );
562
563 Update.button.call(
564 this.b, (decodeByte(data[5]) & 0x40) === 0
565 );
566
567 // MENU
568 Update.button.call(
569 this.select, (decodeByte(data[4]) & 0x10) === 0
570 );
571
572 Update.button.call(
573 this.start, (decodeByte(data[4]) & 0x04) === 0
574 );
575
576 Update.button.call(
577 this.home, (decodeByte(data[4]) & 0x08) === 0
578 );
579
580
581 Update.component.call(
582 this.joystick.left,
583 "x", decodeByte(data[0]) & 0x3f
584 );
585
586 // Byte 0x01 : Y-axis data of the joystick
587 Update.component.call(
588 this.joystick.left,
589 "y", decodeByte(data[0]) & 0x3f
590 );
591
592 Update.component.call(
593 this.joystick.right,
594 "x", ((data[0] & 0xc0) >> 3) + ((data[1] & 0xc0) >> 5) + ((data[2] & 0x80) >> 7)
595 );
596
597 Update.component.call(
598 this.joystick.right,
599 "y", data[2] & 0x1f
600 );
601
602 // Update last data array cache
603 last.set(this, data);
604 }
605 }
606 }
607};
608
609
610Wii.Nunchuk = function(opts) {
611 opts = opts || {};
612 opts.device = "RVL-004";
613
614 return new Wii(opts);
615};
616
617Wii.Classic = function(opts) {
618 opts = opts || {};
619 opts.device = "RVL-005";
620
621 return new Wii(opts);
622};
623
624module.exports = Wii;