├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── Wirefile ├── examples ├── info.js ├── interaction.js └── led.js ├── nuimo.js ├── package.json └── src └── device.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nathan Kunicki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuimo.js - A Node.js library for interacting with Nuimo devices 2 | 3 | UPDATE 1.0.0 - Changed: 4 | 5 | * Nuimo will now keep scanning after discovering a device. You must manually call stop() to cease. 6 | 7 | ### Installation 8 | 9 | Node.js v6.0+ required. 10 | 11 | ```javascript 12 | npm install nuimojs --save 13 | ``` 14 | 15 | Nuimo.js uses the Noble BLE library by Sandeep Mistry. On macOS everything should function out of the box. On Linux and Windows there are [certain dependencies which may need installed first](https://github.com/sandeepmistry/noble#prerequisites). 16 | 17 | Note: Nuimo.js has been tested on macOS 10.11 and Debian/Raspbian on the Raspberry Pi 3 Model B. It is also reported to work on Windows 10 (See [these notes](https://github.com/nathankunicki/nuimojs/issues/17)). 18 | 19 | ### Usage 20 | 21 | ```javascript 22 | let Nuimo = require("nuimojs"), 23 | nuimo = new Nuimo(); 24 | ``` 25 | 26 | Examples are available in the "examples" directory. 27 | 28 | 29 | ### Class: Nuimo 30 | 31 | Note: Subclass of EventEmitter. 32 | 33 | #### Constructor(whitelist) 34 | 35 | An optional whitelist of UUID's to discover. Leave undefined to discover all, or specify an array (For multiple devices) or string (For a single device). 36 | 37 | #### Methods 38 | 39 | ##### scan() 40 | 41 | Begins scanning for Nuimo devices. 42 | 43 | ##### stop() 44 | 45 | Stops scanning for Nuimo devices. 46 | 47 | ##### getConnectedDevices() 48 | 49 | Returns an array of all the connected devices (Items are instances of Device). 50 | 51 | ##### getConnectedDeviceByUUID(uuid) 52 | 53 | Return the Device matching the requested UUID if it is connected. 54 | 55 | #### Events 56 | 57 | ##### on("discover", callback(device)) 58 | 59 | Triggered when a new Nuimo device is discovered. 60 | 61 | 62 | ### Class: Device 63 | 64 | Note: Subclass of EventEmitter. 65 | 66 | #### Properties 67 | 68 | ##### uuid 69 | 70 | Device unique identifier. 71 | 72 | ##### batteryLevel 73 | 74 | The current battery level as a percentage between 0-100. 75 | 76 | ##### rssi 77 | 78 | Received Signal Strength Indicator (RSSI) value of the device. 79 | 80 | #### Methods 81 | 82 | ##### connect(callback()) 83 | 84 | Connects to a previously discovered Nuimo device. The callback is triggered when the device is ready for interacting with. 85 | 86 | ##### setLEDMatrix(matrix, brightness, timeout, options = {}) 87 | 88 | Outputs a pattern to the 9x9 LED matrix on the front of the device. 89 | 90 | Matrix is either: 91 | - An array of 81 items, each representing one of the 81 LED's, starting at the top left. Each item in the array should be either 0 or 1. 92 | - A buffer of 11 bytes, each bit representing one of the 81 LED's, with the last 7 of the 11th byte being unused. 93 | 94 | Brightness is a value between 0-255. Timeout is how long the pattern should appear for (In milliseconds). 95 | 96 | Two options are currently available (If options is undefined, both are disabled by default): 97 | - options.onionSkinning (boolean) - Allows smoother transitions between matrices. 98 | - options.builtinMatrix (boolean) - Use the inbuilt matrix (Undocumented, subject to change). 99 | 100 | Note: Options may also be specified as an integer representing a bitfield, with the following values: 101 | - ONION_SKINNING - 16 102 | - BUILTIN_MATRIX - 32 103 | 104 | Examples: 105 | ``` 106 | setLEDMatrix(yourmatrix, yourbrightness, yourtimeout); // Default 107 | ``` 108 | 109 | ``` 110 | setLEDMatrix(yourmatrix, yourbrightness, yourtimeout, { 111 | onionSkinning: true, 112 | builtinMatrix: true 113 | }); // Enable onion skinning and inbuilt matrix 114 | ``` 115 | 116 | ``` 117 | setLEDMatrix(yourmatrix, yourbrightness, yourtimeout, 16 + 32); // Using bitfield, enable onion skinning and inbuilt matrix 118 | ``` 119 | 120 | #### Events 121 | 122 | ##### on("connect", callback()) 123 | 124 | Triggered when the device is ready for interacting with. 125 | 126 | ##### on("disconnect", callback()) 127 | 128 | Triggered when the device is disconnected. 129 | 130 | ##### on("batteryLevelChange", callback(level)) 131 | 132 | Triggered when the battery level drops. (Note: Level is as a percentage between 0-100) 133 | 134 | ##### on("rssiChange", callback(rssi)) 135 | 136 | Triggered when the RSSI changes. 137 | 138 | *The following events are triggered when the device is interacted with:* 139 | 140 | ##### on("press", callback()) 141 | 142 | Triggered when the user presses down on the central button. 143 | 144 | ##### on("release", callback()) 145 | 146 | Triggered when the user releases the central button. 147 | 148 | ##### on("swipe", callback(direction)) 149 | 150 | Triggered when the user swipes in a direction on the central pad. Direction can be one of: Nuimo.Swipe.LEFT, Nuimo.Swipe.RIGHT, Nuimo.Swipe.UP, or Nuimo.Swipe.DOWN. 151 | 152 | Individual events are also triggered: "swipeLeft", "swipeRight", "swipeUp", "swipeDown". No direction is passed to the callback for these events. 153 | 154 | ##### on("touch", callback(area)) 155 | 156 | Triggered when the user touches an area on the central pad. Area can be one of: Nuimo.Area.LEFT, Nuimo.Area.RIGHT, Nuimo.Area.TOP, Nuimo.Area.BOTTOM, Nuimo.Area.LONGLEFT, Nuimo.Area.LONGRIGHT, Nuimo.Area.LONGTOP, or Nuimo.Area.LONGBOTTOM. 157 | 158 | Individual events are also triggered: "touchLeft", "touchRight", "touchTop", "touchBottom", "longTouchLeft", "longTouchRight", "longTouchTop", "longTouchBottom". No area is passed to the callback for these events. 159 | 160 | ##### on("rotate", callback(amount)) 161 | 162 | Triggered when the user rotates the outer ring. The amount is the amount of degrees the ring was rotated (Negative for counterclockwise). 163 | 164 | ##### on("fly", callback(direction, speed)) 165 | 166 | Triggered when the user waves their hand over the sensor. Direction is either Nuimo.Fly.LEFT or Nuimo.Fly.RIGHT. 167 | 168 | Individual events are also triggered: "flyLeft", "flyRight". No direction is passed to the callback for these events. 169 | 170 | Note: In earlier Nuimo firmware versions, speed could be 0, 1, or 2. In later versions, speed is always 0. 171 | 172 | ##### on("distance", callback(distance)) 173 | 174 | *Note: This was previously named "detect", which is now deprecated.* 175 | 176 | Triggered when a hand is detected over the sensor. The distance represents how close the hand is to the sensor (Between 0-255, 255 being farthest away). 177 | 178 | Note: This event is continuously triggered as long as a hand is detected. 179 | -------------------------------------------------------------------------------- /Wirefile: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Nuimo", 3 | "description": "Wirething module for interacting with Nuimo devices.", 4 | "author": "Nathan Kunicki ", 5 | "main": "./nuimo.js", 6 | "devices": [ 7 | { 8 | "name": "Nuimo", 9 | "type": "nuimo", 10 | "outputs": [ 11 | {"name": "Press", "event": "press"}, 12 | {"name": "Release", "event": "release"}, 13 | {"name": "Swipe Left", "event": "swipeLeft"}, 14 | {"name": "Swipe Right", "event": "swipeRight"}, 15 | {"name": "Swipe Up", "event": "swipeUp"}, 16 | {"name": "Swipe Down", "event": "swipeDown"}, 17 | {"name": "Rotate", "event": "rotate", "params": [ 18 | {"name": "Rotation Amount", "param": "amount", "type": "Number"} 19 | ]}, 20 | {"name": "Fly Left", "event": "flyLeft", "params": [ 21 | {"name": "Speed", "param": "speed", "type": "Number"} 22 | ]}, 23 | {"name": "Fly Right", "event": "flyRight", "params": [ 24 | {"name": "Speed", "param": "speed", "type": "Number"} 25 | ]}, 26 | {"name": "Detect Hand", "event": "detect", "params": [ 27 | {"name": "Distance", "param": "distance", "type": "Number"} 28 | ]} 29 | ], 30 | "inputs": [ 31 | {"name": "LED Matrix", "function": "setLEDMatrix", "params": [ 32 | {"name": "Pattern", "param": "matrixData", "type": "JSON"}, 33 | {"name": "Brightness", "param": "brightness", "type": "Number", "default": 255, "description": "Brightness between 0-255"}, 34 | {"name": "Timeout", "param": "timeout", "type": "Number", "default": 2000, "description": "Timeout in milliseconds (Between 0-25500)"} 35 | ]} 36 | ] 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /examples/info.js: -------------------------------------------------------------------------------- 1 | let Nuimo = require("../nuimo.js"), 2 | nuimo = new Nuimo(); 3 | 4 | nuimo.on("discover", (device) => { 5 | 6 | console.log(`Discovered Nuimo (${device.uuid})`); 7 | 8 | device.on("connect", () => { 9 | console.log("Nuimo connected"); 10 | }); 11 | 12 | device.on("disconnect", () => { 13 | console.log("Nuimo disconnected"); 14 | }); 15 | 16 | device.on("batteryLevelChange", (level) => { 17 | console.log(`Battery level changed to ${level}%`); 18 | }); 19 | 20 | device.on("rssiChange", (rssi) => { 21 | console.log(`Signal strength (RSSI) changed to ${rssi}`); 22 | }); 23 | 24 | device.connect(() => { 25 | console.log(`Battery level is ${device.batteryLevel}%`); 26 | console.log(`Signal strength (RSSI) is ${device.rssi}`); 27 | }); 28 | 29 | }); 30 | 31 | nuimo.scan(); -------------------------------------------------------------------------------- /examples/interaction.js: -------------------------------------------------------------------------------- 1 | let Nuimo = require("../nuimo.js"), 2 | nuimo = new Nuimo(); 3 | 4 | nuimo.on("discover", (device) => { 5 | 6 | console.log(`Discovered Nuimo (${device.uuid})`); 7 | 8 | device.on("connect", () => { 9 | console.log("Nuimo connected"); 10 | }); 11 | 12 | device.on("disconnect", () => { 13 | console.log("Nuimo disconnected"); 14 | }); 15 | 16 | device.on("press", () => { 17 | console.log("Button pressed"); 18 | }); 19 | 20 | device.on("release", () => { 21 | console.log("Button released"); 22 | }); 23 | 24 | device.on("swipe", (direction) => { 25 | switch (direction) { 26 | case (Nuimo.Swipe.LEFT): 27 | console.log("Swiped left"); break; 28 | case (Nuimo.Swipe.RIGHT): 29 | console.log("Swiped right"); break; 30 | case (Nuimo.Swipe.UP): 31 | console.log("Swiped up"); break; 32 | case (Nuimo.Swipe.DOWN): 33 | console.log("Swiped down"); break; 34 | } 35 | }); 36 | 37 | device.on("touch", (direction) => { 38 | switch (direction) { 39 | case (Nuimo.Area.LEFT): 40 | console.log("Touched left"); break; 41 | case (Nuimo.Area.RIGHT): 42 | console.log("Touched right"); break; 43 | case (Nuimo.Area.TOP): 44 | console.log("Touched top"); break; 45 | case (Nuimo.Area.BOTTOM): 46 | console.log("Touched bottom"); break; 47 | case (Nuimo.Area.LONGLEFT): 48 | console.log("Long touched left"); break; 49 | case (Nuimo.Area.LONGRIGHT): 50 | console.log("Long touched right"); break; 51 | case (Nuimo.Area.LONGTOP): 52 | console.log("Long touched top"); break; 53 | case (Nuimo.Area.LONGBOTTOM): 54 | console.log("Long touched bottom"); break; 55 | } 56 | }); 57 | 58 | device.on("rotate", (amount) => { 59 | console.log(`Rotated by ${amount}`); 60 | }); 61 | 62 | device.on("fly", (direction, speed) => { 63 | switch (direction) { 64 | case (Nuimo.Fly.LEFT): 65 | console.log(`Flew left by speed ${speed}`); break; 66 | case (Nuimo.Fly.RIGHT): 67 | console.log(`Flew right by speed ${speed}`); break; 68 | } 69 | }); 70 | 71 | device.on("detect", (distance) => { 72 | console.log(`Detected hand at distance ${distance}`); 73 | }); 74 | 75 | device.connect(); 76 | 77 | }); 78 | 79 | nuimo.scan(); 80 | -------------------------------------------------------------------------------- /examples/led.js: -------------------------------------------------------------------------------- 1 | let Nuimo = require("../nuimo.js"), 2 | nuimo = new Nuimo(); 3 | 4 | nuimo.on("discover", (device) => { 5 | 6 | console.log(`Discovered Nuimo (${device.uuid})`); 7 | 8 | device.on("connect", () => { 9 | console.log("Nuimo connected"); 10 | }); 11 | 12 | device.on("disconnect", () => { 13 | console.log("Nuimo disconnected"); 14 | }); 15 | 16 | device.on("press", () => { 17 | device.setLEDMatrix([ 18 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | 0, 0, 0, 1, 0, 0, 1, 1, 0, 21 | 0, 0, 0, 1, 0, 1, 0, 0, 0, 22 | 0, 0, 0, 1, 0, 0, 1, 0, 0, 23 | 0, 1, 0, 1, 0, 0, 0, 1, 0, 24 | 0, 0, 1, 0, 0, 1, 1, 0, 0, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0 27 | ], 255, 2000); 28 | }); 29 | 30 | device.connect(); 31 | 32 | }); 33 | 34 | nuimo.scan(); -------------------------------------------------------------------------------- /nuimo.js: -------------------------------------------------------------------------------- 1 | let fs = require("fs"), 2 | noble = require("noble"), 3 | debug = require('debug')('nuimojs'), 4 | EventEmitter = require("events").EventEmitter; 5 | 6 | let Device = require("./src/device.js"); 7 | 8 | let ready = false, 9 | wantScan = false; 10 | 11 | noble.on("stateChange", (state) => { 12 | ready = (state === "poweredOn"); 13 | if (ready) { 14 | if (wantScan) { 15 | noble.startScanning(); 16 | } 17 | } else { 18 | noble.stopScanning(); 19 | } 20 | }); 21 | 22 | class Nuimo extends EventEmitter { 23 | 24 | 25 | constructor (whitelist) { 26 | super(); 27 | this._connectedDevices = {}; 28 | this._useWhitelist = false; 29 | 30 | if (whitelist) { 31 | if (Array.isArray(whitelist)) { 32 | this._whitelist = whitelist; 33 | this._useWhitelist = true; 34 | } else if (typeof whitelist === "string") { 35 | this._whitelist = [whitelist]; 36 | this._useWhitelist = true; 37 | } 38 | } 39 | } 40 | 41 | 42 | static get Direction () { 43 | return Device.Direction; 44 | } 45 | 46 | 47 | static get Swipe () { 48 | return Device.Swipe; 49 | } 50 | 51 | 52 | static get Fly () { 53 | return Device.Fly; 54 | } 55 | 56 | 57 | static get Area () { 58 | return Device.Area; 59 | } 60 | 61 | 62 | static get Options () { 63 | return Device.Options; 64 | } 65 | 66 | 67 | static get wirething () { 68 | return JSON.parse(fs.readFileSync(`${__dirname}/Wirefile`)); 69 | } 70 | 71 | 72 | scan () { 73 | wantScan = true; 74 | 75 | noble.on("discover", (peripheral) => { 76 | 77 | let advertisement = peripheral.advertisement; 78 | 79 | if (advertisement.localName === "Nuimo") { 80 | 81 | if (this._useWhitelist && this._whitelist.indexOf(peripheral.uuid) < 0) { 82 | debug("Discovered device not in UUID whitelist"); 83 | return; 84 | } 85 | 86 | peripheral.removeAllListeners(); 87 | noble.stopScanning(); 88 | noble.startScanning(); 89 | 90 | let device = new Device(peripheral); 91 | 92 | device._peripheral.on("connect", () => { 93 | debug("Peripheral connected"); 94 | this._connectedDevices[device.uuid] = device; 95 | }); 96 | 97 | device._peripheral.on("disconnect", () => { 98 | debug("Peripheral disconnected"); 99 | delete this._connectedDevices[device.uuid]; 100 | 101 | if (wantScan) { 102 | noble.startScanning(); 103 | } 104 | 105 | device.emit("disconnect"); 106 | }); 107 | 108 | this.emit("discover", device); 109 | } 110 | }); 111 | 112 | if (ready) { 113 | noble.startScanning(); 114 | } 115 | } 116 | 117 | 118 | wirethingInit () { 119 | this.scan(); 120 | } 121 | 122 | 123 | stop () { 124 | wantScan = false; 125 | noble.stopScanning(); 126 | } 127 | 128 | 129 | getConnectedDeviceByUUID (uuid) { 130 | return this._connectedDevices[uuid]; 131 | } 132 | 133 | 134 | getConnectedDevices () { 135 | return Object.keys(this._connectedDevices).map((uuid) => { 136 | return this._connectedDevices[uuid]; 137 | }) 138 | } 139 | 140 | 141 | } 142 | 143 | module.exports = Nuimo; 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuimojs", 3 | "version": "1.0.3", 4 | "description": "Nuimo.js - A Node.js library for interacting with Nuimo devices", 5 | "main": "nuimo.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/nathankunicki/nuimojs.git" 12 | }, 13 | "author": "Nathan Kunicki ", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/nathankunicki/nuimojs/issues" 17 | }, 18 | "homepage": "https://github.com/nathankunicki/nuimojs#readme", 19 | "dependencies": { 20 | "noble": ">=1.8.0", 21 | "async": ">=2.0.1", 22 | "debug": ">=2.2.0" 23 | }, 24 | "engines": { 25 | "node": ">=6.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/device.js: -------------------------------------------------------------------------------- 1 | let async = require("async"), 2 | debug = require("debug")("nuimojs"), 3 | EventEmitter = require("events").EventEmitter; 4 | 5 | 6 | const Swipe = { 7 | LEFT: 0, 8 | RIGHT: 1, 9 | UP: 2, 10 | DOWN: 3 11 | }; 12 | 13 | const Fly = { 14 | LEFT: 0, 15 | RIGHT: 1 16 | }; 17 | 18 | const Area = { 19 | LEFT: 4, 20 | RIGHT: 5, 21 | TOP: 6, 22 | BOTTOM: 7, 23 | LONGLEFT: 8, 24 | LONGRIGHT: 9, 25 | LONGTOP: 10, 26 | LONGBOTTOM: 11 27 | }; 28 | 29 | // Direction is now deprecated, use Swipe, Fly, or Area instead 30 | const Direction = Swipe; 31 | 32 | // Configuration bits, see https://github.com/nathankunicki/nuimojs/pull/12 33 | const Options = { 34 | ONION_SKINNING: 16, 35 | BUILTIN_MATRIX: 32 36 | }; 37 | 38 | 39 | const UUID = { 40 | Service: { 41 | BATTERY_STATUS: "180f", 42 | DEVICE_INFORMATION: "180a", 43 | LED_MATRIX: "f29b1523cb1940f3be5c7241ecb82fd1", 44 | USER_INPUT_EVENTS: "f29b1525cb1940f3be5c7241ecb82fd2" 45 | }, 46 | Characteristic: { 47 | BATTERY_LEVEL: "2a19", 48 | DEVICE_INFORMATION: "2a29", 49 | FLY: "f29b1526cb1940f3be5c7241ecb82fd2", 50 | SWIPE: "f29b1527cb1940f3be5c7241ecb82fd2", 51 | ROTATION: "f29b1528cb1940f3be5c7241ecb82fd2", 52 | BUTTON_CLICK: "f29b1529cb1940f3be5c7241ecb82fd2" 53 | } 54 | }; 55 | 56 | 57 | class Device extends EventEmitter { 58 | 59 | 60 | constructor (peripheral) { 61 | super(); 62 | 63 | this.deviceType = "nuimo"; 64 | 65 | this._peripheral = peripheral; 66 | this._LEDCharacteristic = null; 67 | this._batteryLevel = 100; 68 | this._rssi = -100; // Initialize as -100 - no signal 69 | } 70 | 71 | 72 | static get Direction () { 73 | return Direction; 74 | } 75 | 76 | 77 | static get Swipe () { 78 | return Swipe; 79 | } 80 | 81 | 82 | static get Fly () { 83 | return Fly; 84 | } 85 | 86 | 87 | static get Area () { 88 | return Area; 89 | } 90 | 91 | 92 | static get Options () { 93 | return Options; 94 | } 95 | 96 | 97 | get uuid () { 98 | return this._peripheral.uuid; 99 | } 100 | 101 | 102 | get UUID () { 103 | return this.uuid; 104 | } 105 | 106 | 107 | get batteryLevel () { 108 | return this._batteryLevel; 109 | } 110 | 111 | 112 | get rssi () { 113 | return this._rssi; 114 | } 115 | 116 | 117 | get RSSI () { 118 | return this.rssi; 119 | } 120 | 121 | 122 | connect (callback) { 123 | 124 | let self = this; 125 | 126 | let batteryReady = false; 127 | let LEDReady = false; 128 | let userInputs = 0; 129 | 130 | this._peripheral.connect((err) => { 131 | 132 | this._rssi = this._peripheral.rssi; 133 | 134 | let rssiUpdateInterval = setInterval(() => { 135 | this._peripheral.updateRssi((err, rssi) => { 136 | if (!err) { 137 | if (this._rssi != rssi) { 138 | this._rssi = rssi; 139 | self.emit("rssiChange", rssi); 140 | } 141 | } 142 | }); 143 | }, 2000); 144 | 145 | self._peripheral.on("disconnect", () => { 146 | clearInterval(rssiUpdateInterval); 147 | }); 148 | 149 | self._peripheral.discoverServices([], (error, services) => { 150 | 151 | debug("Service discovery started"); 152 | 153 | let serviceIndex = 0; 154 | 155 | async.whilst( 156 | function () { 157 | return (serviceIndex < services.length); 158 | }, 159 | function (callback) { 160 | 161 | let service = services[serviceIndex]; 162 | 163 | service.discoverCharacteristics([], (error, characteristics) => { 164 | 165 | let characteristicIndex = 0; 166 | 167 | async.whilst( 168 | function () { 169 | return (characteristicIndex < characteristics.length); 170 | }, 171 | function (callback) { 172 | 173 | let characteristic = characteristics[characteristicIndex]; 174 | 175 | switch (service.uuid) { 176 | case UUID.Service.BATTERY_STATUS: 177 | batteryReady = true; 178 | debug("Found Battery characteristic"); 179 | self._subscribeToCharacteristic(characteristic, self._handleBatteryChange.bind(self)); 180 | characteristic.read(); 181 | break; 182 | case UUID.Service.LED_MATRIX: 183 | self._LEDCharacteristic = characteristic; 184 | LEDReady = true; 185 | debug("Found LED characteristic"); 186 | break; 187 | case UUID.Service.USER_INPUT_EVENTS: 188 | switch (characteristic.uuid) { 189 | case UUID.Characteristic.BUTTON_CLICK: 190 | debug("Found Button Click characteristic"); 191 | self._subscribeToCharacteristic(characteristic, self._handleClick.bind(self)); 192 | break; 193 | case UUID.Characteristic.FLY: 194 | debug("Found Fly characteristic"); 195 | self._subscribeToCharacteristic((characteristic), self._handleFlying.bind(self)); 196 | break; 197 | case UUID.Characteristic.ROTATION: 198 | debug("Found Rotation characteristic"); 199 | self._subscribeToCharacteristic(characteristic, self._handleRotation.bind(self)); 200 | break; 201 | case UUID.Characteristic.SWIPE: 202 | debug("Found Swipe characteristic"); 203 | self._subscribeToCharacteristic(characteristic, self._handleTouchSwipe.bind(self)); 204 | break; 205 | } 206 | userInputs++; 207 | break; 208 | } 209 | 210 | characteristicIndex++; 211 | return callback(); 212 | 213 | }, 214 | function (err) { 215 | serviceIndex++; 216 | return callback(); 217 | } 218 | ); 219 | }); 220 | }, 221 | function (err) { 222 | 223 | debug("Service discovery finished"); 224 | 225 | if (err !== null || batteryReady === false || LEDReady === false || userInputs < 5) { 226 | self._peripheral.disconnect(); 227 | debug("Force disconnect"); 228 | } else { 229 | debug("Emit connect"); 230 | self.emit("connect"); 231 | } 232 | 233 | if (callback) { 234 | return callback(); 235 | } 236 | } 237 | ); 238 | }); 239 | }); 240 | } 241 | 242 | 243 | disconnect () { 244 | this._peripheral.disconnect(); 245 | } 246 | 247 | 248 | setLEDMatrix (matrixData, brightness, timeout, options) { 249 | 250 | if (this._LEDCharacteristic) { 251 | let buf = Buffer.alloc(13); 252 | 253 | if (matrixData instanceof Buffer) { 254 | matrixData.copy(buf); 255 | } else { 256 | this._LEDArrayToBuffer(matrixData).copy(buf); 257 | } 258 | 259 | if (typeof options === "number") { 260 | buf[10] += options; 261 | } else if (typeof options === "object") { 262 | if (options.onion_skinning || options.onionSkinning) { 263 | buf[10] += Options.ONION_SKINNING; 264 | } 265 | if (options.builtin_matrix || options.builtinMatrix){ 266 | buf[10] += Options.BUILTIN_MATRIX; 267 | } 268 | } 269 | 270 | buf[11] = brightness; 271 | buf[12] = Math.floor(timeout / 100); 272 | 273 | this._LEDCharacteristic.write(buf, true); 274 | } else { 275 | this.emit("error", new Error("Not fully connected")); 276 | } 277 | } 278 | 279 | _LEDArrayToBuffer (arr) { 280 | let buf = Buffer.alloc(11); 281 | 282 | for (let i = 0; i < 11; i++) { 283 | buf[i] = parseInt(arr.slice(i*8, i*8+8).reverse().join(""), 2); 284 | } 285 | 286 | return buf; 287 | } 288 | 289 | _subscribeToCharacteristic (characteristic, callback) { 290 | characteristic.on("data", (data, isNotification) => { 291 | return callback(data); 292 | }); 293 | characteristic.subscribe((err) => { 294 | if (err) { 295 | this.emit("error", err); 296 | } 297 | }); 298 | } 299 | 300 | _handleBatteryChange (data) { 301 | this._batteryLevel = data[0]; 302 | debug("Battery level %s%", data[0]); 303 | this.emit("batteryLevelChange", data[0]); 304 | } 305 | 306 | _handleTouchSwipe (data) { 307 | let direction = data[0]; 308 | if (direction <= 3) { 309 | this.emit("swipe", direction); 310 | } else { 311 | this.emit("touch", direction); 312 | } 313 | switch (direction) { 314 | case (Swipe.LEFT): 315 | debug("Swipe left"); 316 | this.emit("swipeLeft"); 317 | break; 318 | case (Swipe.RIGHT): 319 | debug("Swipe right"); 320 | this.emit("swipeRight"); 321 | break; 322 | case (Swipe.UP): 323 | debug("Swipe up"); 324 | this.emit("swipeUp"); 325 | break; 326 | case (Swipe.DOWN): 327 | debug("Swipe down"); 328 | this.emit("swipeDown"); 329 | break; 330 | case (Area.LEFT): 331 | debug("Touch left"); 332 | this.emit("touchLeft"); 333 | break; 334 | case (Area.RIGHT): 335 | debug("Touch right"); 336 | this.emit("touchRight"); 337 | break; 338 | case (Area.TOP): 339 | debug("Touch top"); 340 | this.emit("touchTop"); 341 | break; 342 | case (Area.BOTTOM): 343 | debug("Touch bottom"); 344 | this.emit("touchBottom"); 345 | break; 346 | case (Area.LONGLEFT): 347 | debug("Long Touch left"); 348 | this.emit("longTouchLeft"); 349 | break; 350 | case (Area.LONGRIGHT): 351 | debug("Long Touch right"); 352 | this.emit("longTouchRight"); 353 | break; 354 | case (Area.LONGTOP): 355 | debug("Long Touch top"); 356 | this.emit("longTouchTop"); 357 | break; 358 | case (Area.LONGBOTTOM): 359 | debug("Long Touch bottom"); 360 | this.emit("longTouchBottom"); 361 | break; 362 | } 363 | } 364 | 365 | _handleClick (data) { 366 | if (data[0] === 0) { 367 | debug("Button released"); 368 | this.emit("release"); 369 | } else { 370 | debug("Button pressed"); 371 | this.emit("press"); 372 | } 373 | } 374 | 375 | _handleRotation (data) { 376 | let amount = data.readInt16LE(); 377 | debug("Rotate %s", amount); 378 | this.emit("rotate", amount); 379 | } 380 | 381 | _handleFlying (data) { 382 | 383 | let gesture = data[0], 384 | amount = data[1]; 385 | 386 | switch (gesture) { 387 | case 0: 388 | case 1: 389 | case 2: 390 | let direction = gesture, 391 | speed = amount; 392 | this.emit("fly", direction, speed); break; 393 | switch (direction) { 394 | case (Fly.LEFT): 395 | debug("Fly left %s", speed); 396 | this.emit("flyLeft", speed); 397 | break; 398 | case (Fly.RIGHT): 399 | debug("Fly right %s", speed); 400 | this.emit("flyRight", speed); 401 | break; 402 | } 403 | break; 404 | case 4: 405 | debug("Detect %s", amount); 406 | this.emit("detect", amount); 407 | this.emit("distance", amount); 408 | break; 409 | } 410 | } 411 | } 412 | 413 | module.exports = Device; 414 | --------------------------------------------------------------------------------