kano-wand
Version:
NodeJS wrapper for interfacing with the Kano Harry Potter BLE wand
202 lines (164 loc) • 6.45 kB
JavaScript
var just = require('string-just');
var kano = require('./kano_info.json');
var async = require("async");
var gestureSpells = require("./gesture-spells");
const { Observable, Subject, ReplaySubject, from, of, range } = require('rxjs');
const { map, filter, switchMap } = require('rxjs/operators');
const Conv = require('./conversion');
const width = 800;
const height = 600
const conv = new Conv(width, height);
var gr = new gestureSpells()
class Wand {
constructor() {
this.buttonCharacteristic = null;
this.vibrateCharacteristic = null;
this.quaternionsCharacteristic = null;
this.quaternionsResetCharacteristic = null;
this.currentSpell = [];
this.buttonPressed = false;
this.timeUp = new Date();
this.timeDown = new Date();
this.resetTimeout = 0.2 // determins a quick press for wand reset (milliseconds)
this.spells = new Subject();
this.positions = new Subject();
}
static uInt8ToUInt16(byteA, byteB) {
const number = (((byteB & 0xff) << 8) | byteA);
const sign = byteB & (1 << 7);
if (sign) {
return 0xFFFF0000 | number;
}
return number;
}
processCharacteristic(characteristic) {
{
if (compareUUID(characteristic.uuid, kano.SENSOR.QUATERNIONS_CHAR)) {
console.log("found position");
this.quaternionsCharacteristic = characteristic;
}
if (compareUUID(characteristic.uuid, kano.IO.USER_BUTTON_CHAR)) {
console.log("found Button");
this.buttonCharacteristic = characteristic;
}
if (compareUUID(characteristic.uuid, kano.SENSOR.QUATERNIONS_RESET_CHAR)) {
console.log("found ResetChar");
this.quaternionsResetCharacteristic = characteristic;
}
if (compareUUID(characteristic.uuid, kano.IO.VIBRATOR_CHAR)) {
console.log("found vibrate");
this.vibrateCharacteristic = characteristic;
}
}
}
vibrate(pattern) {
var vibrate = Buffer.alloc(1);
vibrate.writeUInt8(pattern,0)
this.vibrateCharacteristic.write(vibrate, true);
}
init(peripheral) {
console.log("init");
var serviceUUIDs = [kano.SENSOR.SERVICE, kano.IO.SERVICE, kano.INFO.SERVICE];
const $this = this;
return new Promise((resolve, reject) => {
async.waterfall([
function(callback) {
peripheral.discoverServices(serviceUUIDs, callback);
},
function(services, callback) {
var tasks = []
services.forEach(function(service) {
tasks.push(function(callback) {
service.discoverCharacteristics([], callback);
})
})
async.parallel(tasks, callback);
},
function (characteristics, callback) {
characteristics = characteristics.flat();
characteristics.forEach(this.processCharacteristic, this)
callback(null, true);
}.bind(this),
this.subscribe_position.bind(this),
this.subscribe_button.bind(this),
async.apply(this.reset_position.bind(this))
], function (err, result) {
console.log("hello!!");
resolve(true);
});
});
}
subscribe_button(result, callback) {
console.log("Subscribe to Button")
this.buttonCharacteristic.on('read', this.onButtonUpdate.bind(this));
this.buttonCharacteristic.subscribe(callback);
}
onButtonUpdate(data, isNotification) {
const raw = data.readUIntBE(0, 1);
const pressed = raw == 1 ? true : false;
this.buttonPressed = pressed;
// timing
if (pressed) {
this.timeUp = new Date();
} else {
this.timeDown = new Date();
}
var seconds = (this.timeDown.getTime() - this.timeUp.getTime()) / 1000;
if (pressed) {
this.spell = null;
} else if (seconds < this.resetTimeout) { // not pressed
this.reset_position();
} else if (this.currentSpell.length > 5) { // not pressed
this.currentSpell = this.currentSpell.splice(5);
let flippedPositions = [];
this.currentSpell.forEach((entry) => {
flippedPositions.push(Wand.flipCord(entry));
})
const positions = this.currentSpell;
gr.recognise(flippedPositions)
.then((data) =>{
data.positions = flippedPositions;
this.spells.next(data);
});
this.currentSpell = [];
}
}
static flipCord(cords) {
const x = cords[0]
const y = cords [1]
const iy = height - (y);
return [x, iy];
}
subscribe_position(result, callback) {
console.log("Subscribe to Motion")
this.quaternionsCharacteristic.on('read', this.onMotionUpdate.bind(this));
this.quaternionsCharacteristic.subscribe(callback);
}
onMotionUpdate(data, isNotification) {
let y = data.readInt16LE(0);
let x = data.readInt16LE(2);
let w = data.readInt16LE(4);
let z = data.readInt16LE(6);
const pos = conv.position([x, y, z, w]);
let pitch = `Pitch: ${just.ljust(z.toString(), 16, " ")}`;
let roll = `Roll: ${just.ljust(w.toString(), 16, " ")}`;
// console.log(`${pitch}${roll}(x, y): (${x.toString()}, ${y.toString()})`)
// console.log(this.getXY(x, y))
if (this.buttonPressed) {
this.currentSpell.push([pos.x, pos.y]);
this.positions.next([pos.x, pos.y]);
}
}
reset_position() {
console.log("Reset Positoin");
var reset = Buffer.alloc(1);
reset.writeUInt8(1,0)
this.quaternionsResetCharacteristic.write(reset, true);
}
}
function compareUUID(val1, val2) {
val1 = val1.replaceAll("-", "").toLowerCase();
val2 = val2.replaceAll("-", "").toLowerCase();
return val1 === val2;
};
module.exports = Wand;