├── index.js ├── .eslintrc.json ├── CHANGELOG.md ├── LICENSE ├── .gitignore ├── package.json ├── tester.js ├── test └── test.js ├── README.md └── movehub-async.js /index.js: -------------------------------------------------------------------------------- 1 | const { Boost } = require('./movehub-async'); 2 | 3 | const boost = new Boost(); 4 | boost.afterInitialization(); 5 | 6 | module.exports = boost; -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 9, 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | "no-const-assign": "warn", 15 | "no-this-before-super": "warn", 16 | "no-undef": "warn", 17 | "no-unreachable": "warn", 18 | "no-unused-vars": [1, {"args": "none"}], 19 | "constructor-super": "warn", 20 | "valid-typeof": "warn", 21 | "indent": ["error", 2, { "SwitchCase": 1 }], 22 | "quotes": ["error", "single"] 23 | } 24 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [0.5.0] - 2018-09-30 6 | 7 | - Drive until method 8 | - Turn until method 9 | 10 | ## [0.4.0] - 2018-04-07 11 | 12 | - Add drive method for driving specified distance 13 | - Add turn method for turning specified angle 14 | 15 | ## [0.3.3] - 2018-03-17 16 | 17 | - Add dutyCycle default values to motor methods 18 | - Update JSDocs 19 | 20 | ## [0.3.2] - 2018-03-16 21 | 22 | - npm README update 23 | 24 | ## [0.3.1] - 2018-03-16 25 | 26 | - Fix motorAngle wait 27 | - Update JSDocs 28 | 29 | ## [0.3.0] - 2018-03-16 30 | 31 | - Asynchronous motor methods 32 | 33 | ## [0.2.0] - 2018-03-12 34 | 35 | - Simple getHubAsync connection method 36 | 37 | ## [0.1.1] - 2018-03-11 38 | 39 | - JSDoc comments 40 | 41 | ## [0.1.0] - 2018-03-10 42 | 43 | - Functions for connection creation 44 | - Led control method -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tomi Tuhkanen 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "movehub-async", 3 | "version": "0.5.0", 4 | "description": "Asynchronous methods for the Lego Boost Move Hub package", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run eslint && npm run mocha", 8 | "mocha": "mocha", 9 | "eslint": "eslint index.js movehub-async.js tester.js ./test/test.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/ttu/node-movehub-async.git" 14 | }, 15 | "keywords": [ 16 | "Lego", 17 | "Boost", 18 | "Move", 19 | "Hub", 20 | "BLE", 21 | "Bluetooth", 22 | "Robotics", 23 | "Async" 24 | ], 25 | "author": "Tomi Tuhkanen", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/ttu/node-movehub-async/issues" 29 | }, 30 | "homepage": "https://github.com/ttu/node-movehub-async#readme", 31 | "dependencies": { 32 | "movehub": "^0.4.1" 33 | }, 34 | "devDependencies": { 35 | "eslint": "^5.4.0", 36 | "mocha": "^5.2.0", 37 | "mockery": "^2.1.0", 38 | "sinon": "^7.1.1" 39 | }, 40 | "engines": { 41 | "node": ">=7.6.0" 42 | }, 43 | "engineStrict": true 44 | } 45 | -------------------------------------------------------------------------------- /tester.js: -------------------------------------------------------------------------------- 1 | const boost = require('./index'); 2 | 3 | (async () => { 4 | // await boost.bleReadyAsync(); 5 | // const connectDetails = await boost.hubFoundAsync(); 6 | // const hub = await boost.connectAsync(connectDetails); 7 | 8 | const hub = await boost.getHubAsync(); 9 | 10 | hub.on('error', err => { 11 | console.log('error', err); 12 | }); 13 | 14 | hub.on('disconnect', () => { 15 | console.log('disconnect'); 16 | }); 17 | 18 | hub.on('distance', distance => { 19 | console.log('distance', distance); 20 | }); 21 | 22 | hub.on('rssi', rssi => { 23 | console.log('rssi', rssi); 24 | }); 25 | 26 | hub.on('port', portObject => { 27 | console.log('port', JSON.stringify(portObject, null, 1)); 28 | }); 29 | 30 | hub.on('color', color => { 31 | console.log('color', color); 32 | }); 33 | 34 | hub.on('tilt', tilt => { 35 | console.log('tilt', JSON.stringify(tilt, null, 1)); 36 | }); 37 | 38 | hub.on('rotation', rotation => { 39 | console.log('rotation', JSON.stringify(rotation, null, 1)); 40 | }); 41 | 42 | // Insert commands here 43 | await hub.ledAsync('red'); 44 | await hub.ledAsync('yellow'); 45 | await hub.ledAsync('green'); 46 | 47 | // Turn 180 degrees 48 | await hub.motorAngleAsync('A', 980, 100, true); 49 | 50 | await hub.motorTimeMultiAsync(2, 100, 100, true); 51 | 52 | // Turn 180 degrees 53 | await hub.motorAngleAsync('B', 980, 100, true); 54 | 55 | await hub.ledAsync('red'); 56 | })(); -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const mockery = require('mockery'); 3 | const sinon = require('sinon'); 4 | 5 | const boostStub = sinon.stub(); 6 | boostStub.prototype.on = () => {}; 7 | 8 | const hubStub = sinon.stub(); 9 | hubStub.prototype.on = () => {}; 10 | 11 | mockery.enable(); 12 | mockery.registerAllowable('../movehub-async'); 13 | mockery.registerMock('movehub/movehub', { Boost: boostStub, Hub: hubStub }); 14 | 15 | const { Boost, Hub } = require('../movehub-async'); 16 | 17 | describe('Hub', function() { 18 | describe('#useMetric', function() { 19 | it('useMetrics default', function() { 20 | const hub = new Hub(); 21 | hub.afterInitialization(); 22 | 23 | assert.equal(hub.useMetric, true); 24 | }); 25 | it('useMetricUnits', function() { 26 | const hub = new Hub(); 27 | hub.afterInitialization(); 28 | 29 | hub.useMetricUnits(); 30 | assert.equal(hub.useMetric, true); 31 | }); 32 | it('useImperialUnits', function() { 33 | const hub = new Hub(); 34 | hub.afterInitialization(); 35 | 36 | hub.useImperialUnits(); 37 | assert.equal(hub.useMetric, false); 38 | }); 39 | }); 40 | describe('#drive', function() { 41 | it('correct values to motorAngleMultiAsync', function() { 42 | let values = []; 43 | Hub.prototype.motorAngleMultiAsync = (...rest) => values = [ ...rest ]; 44 | 45 | const hub = new Hub(); 46 | hub.afterInitialization(); 47 | 48 | hub.drive(100); 49 | assert.equal(values[0], 2850); 50 | assert.equal(values[1], 25); 51 | assert.equal(values[2], 25); 52 | assert.equal(values[3], true); 53 | }); 54 | }); 55 | describe('#turn', function() { 56 | it('correct values to motorAngleMultiAsync', function() { 57 | let values = []; 58 | Hub.prototype.motorAngleMultiAsync = (...rest) => values = [ ...rest ]; 59 | 60 | const hub = new Hub(); 61 | hub.afterInitialization(); 62 | 63 | hub.turn(90); 64 | assert.equal(values[0], 230.4); 65 | assert.equal(values[1], 20); 66 | assert.equal(values[2], -20); 67 | assert.equal(values[3], true); 68 | }); 69 | }); 70 | }); 71 | 72 | describe('Boost', function() { 73 | describe('#hubFoundAsync', function() { 74 | it('find hub as hubDetails is set', async function() { 75 | const boost = new Boost(); 76 | boost.afterInitialization(); 77 | 78 | boost.hubDetails = 'foundSet'; 79 | await boost.hubFoundAsync(); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # movehub async 2 | 3 | [![npm version](https://badge.fury.io/js/movehub-async.svg)](https://badge.fury.io/js/movehub-async) 4 | 5 | Simple to use asynchronous methods for the [Move Hub](https://github.com/hobbyquaker/node-movehub) 6 | 7 | _Move Hub is central controller block of [LEGO® Boost Robotics Set](https://www.lego.com/en-us/boost)._ 8 | 9 | ## Setup 10 | 11 | * Install [Noble prerequisites](https://github.com/noble/noble#prerequisites) 12 | 13 | * Install movehub-async 14 | 15 | ```sh 16 | $ npm install movehub-async 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | const boost = require('movehub-async'); 23 | 24 | const hub = await boost.getHubAsync(); 25 | 26 | // Turn light from red to green 27 | await hub.ledAsync('red'); 28 | await hub.ledAsync('yellow'); 29 | await hub.ledAsync('green'); 30 | 31 | // Turn A & B motors for 10 seconds with power 20 32 | await hub.motorTimeMultiAsync(10, 20, 20); 33 | // Turn motor C 600 degrees with power 5 34 | await hub.motorAngleAsync('C', 600, 5); 35 | ``` 36 | 37 | It is also possible to wait that motor execution has stopped 38 | 39 | ```js 40 | await hub.ledAsync('red'); 41 | // Continue when led is red 42 | await hub.motorTimeMultiAsync(10, 20, 20, true); 43 | // Continue 10 sec later 44 | await hub.motorTimeMultiAsync(5, 20, 20, true); 45 | // Continue 5 sec later 46 | await hub.motorAngleAsync('C', 800, 50, true); 47 | // Continue some time later 48 | await hub.ledAsync('green'); 49 | // Continue when led is green 50 | ``` 51 | 52 | Package contains also simple methods to drive for a specified distance and turn a specified angle. By default drive and turn methods will wait the execution has stopped. 53 | 54 | ```js 55 | // Drive 2 meters forward 56 | await hub.drive(200); 57 | // After 2 meter drive, turn 90 degrees to the right 58 | await hub.turn(90); 59 | // Drive 1 meter backwards 60 | await hub.drive(-100); 61 | // Turn 180 degrees to the left 62 | await hub.turn(-180); 63 | ``` 64 | 65 | ## API 66 | 67 | Check complete non-async API definition from [Lego Boost Move Hub](https://github.com/hobbyquaker/node-movehub). Asynchronous version of the method has an _Async_-suffix in the name, e.g. `motorTimeMulti` -> `motorTimeMultiAsync`. 68 | 69 | ## Boost 70 | 71 | ### boost.getHubAsync() 72 | 73 | Create a connection to the Hub. Internally calls `bleReadyAsync`, `hubFoundAsync` and `connectAsync`. 74 | 75 | ```js 76 | const hub = await boost.getHubAsync(); 77 | ``` 78 | 79 | ### boost.bleReadyAsync() 80 | 81 | Wait for BLE device to be ready. 82 | 83 | ```js 84 | await boost.bleReadyAsync(); 85 | ``` 86 | 87 | ### boost.hubFoundAsync() 88 | 89 | Wait for MoveHub found event. 90 | 91 | ```js 92 | const connectDetails = await boost.hubFoundAsync(); 93 | ``` 94 | 95 | ### boost.connectAsync(connectDetails) 96 | 97 | Initialize and wait for the connection to the Hub. 98 | 99 | ```js 100 | const hub = await boost.connectAsync(connectDetails); 101 | ``` 102 | 103 | ## Hub 104 | 105 | ### hub.ledAsync(color) 106 | 107 | Control the LED on the Move Hub. 108 | 109 | ```js 110 | await hub.ledAsync('red'); 111 | ``` 112 | 113 | ### hub.motorTimeAsync(port, seconds, dutyCycle = 100, wait = false) 114 | 115 | Run a motor for specific time. Await returns when command is sent to Hub. 116 | 117 | ```js 118 | await hub.motorTimeAsync('C', 5, 50); 119 | // Continue almost immediately when command is sent to Hub 120 | 121 | await hub.motorTimeAsync('C', 5, 50, true); 122 | // Continue 5 seconds later 123 | ``` 124 | 125 | ### hub.motorTimeMultiAsync(seconds, dutyCycleA = 100, dutyCycleB = 100, wait = false) 126 | 127 | Run both motors (A and B) for specific time. Await returns when command is sent to Hub. 128 | 129 | ```js 130 | // Drive forward for 10 seconds 131 | await hub.motorTimeMultiAsync(10, 20, 20, true); 132 | // Continue 10 seconds later 133 | ``` 134 | 135 | ### hub.motorAngleAsync(port, angle, dutyCycle = 100, wait = false) 136 | 137 | Turn a motor by specific angle. Await returns when command is sent to Hub. 138 | 139 | ```js 140 | // Turn ~180 degrees 141 | await hub.motorAngleAsync('B', 980, 100, true); 142 | // Continue after the turn 143 | ``` 144 | 145 | ### hub.motorAngleMultiAsync(angle, dutyCycleA = 100, dutyCycleB = 100, wait = false) 146 | 147 | Turn both motors (A and B) by specific angle. Await returns when command is sent to Hub. 148 | 149 | ```js 150 | // Drive forward 151 | await hub.motorAngleMultiAsync(500, 100, 100); 152 | // Continue immediately after command is sent to Hub 153 | ``` 154 | 155 | ### hub.drive(centimeters, wait = true) 156 | 157 | Drive specified distance. By default drive-method's return promise will resolve when the distance has been driven. 158 | 159 | __Note:__ Drive method is implemented with Lego Boost Vernie 160 | 161 | ```js 162 | // Drive forward 2 meters 163 | await hub.drive(200); 164 | // Continue after drive is finished 165 | ``` 166 | 167 | ### hub.useMetricUnits() 168 | 169 | Use metric untis in drive-method. Metric is default. 170 | 171 | ```js 172 | // Drive forward 200 cm 173 | await hub.drive(200); 174 | 175 | hub.useImperialUnits(); 176 | 177 | // Drive forward 200 inches 178 | await hub.drive(200); 179 | ``` 180 | 181 | ### hub.useImperialUnits() 182 | 183 | Use imperial units with drive-method. 184 | 185 | ### hub.setFrictionModifier(modifier) 186 | 187 | If drive method's distance is not correct, friction modifier can be changed. 188 | 189 | ```js 190 | // Drive forward 100cm 191 | await hub.drive(100); 192 | 193 | // Distance was only 90cm, update modifier 194 | hub.setFrictionModifier(1.1); 195 | 196 | // Drive 100cm 197 | await hub.drive(100); 198 | ``` 199 | 200 | ### hub.turn(degrees, wait = true) 201 | 202 | Turn specified angle to either right (positive number) or left (negative number). By default turn-method's promise will resolve when the angle has been turned. 203 | 204 | __Note:__ Turn method is implemented with Lego Boost Vernie 205 | 206 | ```js 207 | const hub = await boost.getHubAsync(); 208 | // Drive 1 meter square 209 | await hub.drive(100); 210 | await hub.turn(90); 211 | await hub.drive(100); 212 | await hub.turn(90); 213 | await hub.drive(100); 214 | await hub.turn(90); 215 | await hub.drive(100); 216 | await hub.turn(90); 217 | ``` 218 | 219 | ### hub.driveUntil(distance = 0, wait = true) 220 | 221 | Drive until the sensor shows an object in defined distance. The distance sensor is not very sensitive or accurate. By default the bot will stop when the sensor notices a wall for the first time. Sensor distance values are usualy between 110-50. 222 | 223 | ```js 224 | await hub.driveUntil(); 225 | ``` 226 | 227 | ### hub.turnUntil(direction = 1, wait = true) 228 | 229 | Turn until sensor doesn't detect any blocking object. 1 or any positive number turns to the right (default) and 0 or any negative number turns to the left. 230 | 231 | ```js 232 | // Turn to the right 233 | await hub.turnUntil(); 234 | // Turn to the right 235 | await hub.turnUntil(1); 236 | // Turn to the left 237 | await hub.turnUntil(0); 238 | ``` 239 | 240 | ## Example project 241 | 242 | [lego-boost-ai](https://github.com/ttu/lego-boost-ai) has a simple AI and manual controls for Lego Boost. 243 | 244 | ## Unit tests 245 | 246 | Run ESLint and Mocha tests. 247 | 248 | ```sh 249 | $ npm run test 250 | ``` 251 | 252 | Run only Mocha tests. 253 | 254 | ```sh 255 | $ npm run mocha 256 | ``` 257 | 258 | ## Tester 259 | 260 | [tester.js](https://github.com/ttu/node-movehub-async/blob/master/tester.js) 261 | 262 | ## Use development version 263 | 264 | It is possible to use development version from GitHub, which may contain unreleased features. 265 | 266 | ```sh 267 | $ npm install git+https://git@github.com/ttu/node-movehub-async.git 268 | ``` 269 | 270 | ## Changelog 271 | 272 | [Changelog](https://github.com/ttu/node-movehub-async/blob/master/CHANGELOG.md) 273 | 274 | ## Contributing 275 | 276 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 277 | 278 | ## Disclaimer 279 | 280 | LEGO and BOOST are Trademarks from The LEGO Company, which do not support this project. 281 | 282 | I'm not responsible for any damage on your LEGO BOOST devices - use it at your own risk. 283 | 284 | ## License 285 | 286 | Licensed under the [MIT](https://github.com/ttu/node-movehub-async/blob/master/LICENSE) License. -------------------------------------------------------------------------------- /movehub-async.js: -------------------------------------------------------------------------------- 1 | const { Boost, Hub } = require('movehub/movehub'); 2 | 3 | const CALLBACK_TIMEOUT_MS = 1000 / 3; 4 | const METRIC_MODIFIER = 28.5; 5 | const IMPERIAL_MODIFIER = METRIC_MODIFIER / 4; 6 | const TURN_MODIFIER = 2.56; 7 | const DRIVE_SPEED = 25; 8 | const TURN_SPEED = 20; 9 | const DEFAULT_STOP_DISTANCE = 105; 10 | const DEFAULT_CLEAR_DISTANCE = 120; 11 | 12 | const waitForValueToSet = function(valueName, compareFunc = (valueName) => this[valueName], timeoutMs = 0) { 13 | if (compareFunc.bind(this)(valueName)) return Promise.resolve(this[valueName]); 14 | 15 | return new Promise((resolve, reject) => { 16 | setTimeout(async () => resolve(await waitForValueToSet.bind(this)(valueName, compareFunc, timeoutMs)), timeoutMs + 100); 17 | }); 18 | }; 19 | 20 | /** 21 | * Disconnect Hub 22 | * @method Hub#disconnectAsync 23 | * @returns {Promise} disconnection successful 24 | */ 25 | Hub.prototype.disconnectAsync = function() { 26 | this.disconnect(); 27 | return waitForValueToSet.bind(this)('hubDisconnected'); 28 | }; 29 | 30 | /** 31 | * Execute this method after new instance of Hub is created 32 | * @method Hub#afterInitialization 33 | */ 34 | Hub.prototype.afterInitialization = function() { 35 | this.hubDisconnected = null; 36 | this.portData = { 37 | A: { angle: 0 }, 38 | B: { angle: 0 }, 39 | AB: { angle: 0 }, 40 | C: { angle: 0 }, 41 | D: { angle: 0 }, 42 | LED: { angle: 0 } 43 | }; 44 | this.useMetric = true; 45 | this.modifier = 1; 46 | 47 | this.on('rotation', rotation => this.portData[rotation.port].angle = rotation.angle); 48 | this.on('disconnect', () => this.hubDisconnected = true); 49 | this.on('distance', distance => this.distance = distance); 50 | }; 51 | 52 | /** 53 | * Control the LED on the Move Hub 54 | * @method Hub#ledAsync 55 | * @param {boolean|number|string} color 56 | * If set to boolean `false` the LED is switched off, if set to `true` the LED will be white. 57 | * Possible string values: `off`, `pink`, `purple`, `blue`, `lightblue`, `cyan`, `green`, `yellow`, `orange`, `red`, 58 | * `white` 59 | * @returns {Promise} 60 | */ 61 | Hub.prototype.ledAsync = function(color) { 62 | return new Promise((resolve, reject) => { 63 | this.led(color, () => { 64 | // Callback is executed when command is sent and it will take some time before MoveHub executes the command 65 | setTimeout(resolve, CALLBACK_TIMEOUT_MS); 66 | }); 67 | }); 68 | } 69 | 70 | /** 71 | * Run a motor for specific time 72 | * @method Hub#motorTimeAsync 73 | * @param {string|number} port possible string values: `A`, `B`, `AB`, `C`, `D`. 74 | * @param {number} seconds 75 | * @param {number} [dutyCycle=100] motor power percentage from `-100` to `100`. If a negative value is given rotation 76 | * is counterclockwise. 77 | * @param {boolean} [wait=false] will promise wait unitll motorTime run time has elapsed 78 | * @returns {Promise} 79 | */ 80 | Hub.prototype.motorTimeAsync = function(port, seconds, dutyCycle = 100, wait = false) { 81 | return new Promise((resolve, reject) => { 82 | this.motorTime(port, seconds, dutyCycle, () => { 83 | setTimeout(resolve, wait ? CALLBACK_TIMEOUT_MS + seconds * 1000 : CALLBACK_TIMEOUT_MS); 84 | }); 85 | }); 86 | } 87 | 88 | /** 89 | * Run both motors (A and B) for specific time 90 | * @method Hub#motorTimeMultiAsync 91 | * @param {number} seconds 92 | * @param {number} [dutyCycleA=100] motor power percentage from `-100` to `100`. If a negative value is given rotation 93 | * is counterclockwise. 94 | * @param {number} [dutyCycleB=100] motor power percentage from `-100` to `100`. If a negative value is given rotation 95 | * is counterclockwise. 96 | * @param {boolean} [wait=false] will promise wait unitll motorTime run time has elapsed 97 | * @returns {Promise} 98 | */ 99 | Hub.prototype.motorTimeMultiAsync = function(seconds, dutyCycleA = 100, dutyCycleB = 100, wait = false) { 100 | return new Promise((resolve, reject) => { 101 | this.motorTimeMulti(seconds, dutyCycleA, dutyCycleB, () => { 102 | setTimeout(resolve, wait ? CALLBACK_TIMEOUT_MS + seconds * 1000 : CALLBACK_TIMEOUT_MS); 103 | }); 104 | }); 105 | } 106 | 107 | /** 108 | * Turn a motor by specific angle 109 | * @method Hub#motorAngleAsync 110 | * @param {string|number} port possible string values: `A`, `B`, `AB`, `C`, `D`. 111 | * @param {number} angle - degrees to turn from `0` to `2147483647` 112 | * @param {number} [dutyCycle=100] motor power percentage from `-100` to `100`. If a negative value is given 113 | * rotation is counterclockwise. 114 | * @param {boolean} [wait=false] will promise wait unitll motorAngle has turned 115 | * @returns {Promise} 116 | */ 117 | Hub.prototype.motorAngleAsync = function(port, angle, dutyCycle = 100, wait = false) { 118 | return new Promise((resolve, reject) => { 119 | this.motorAngle(port, angle, dutyCycle, async () => { 120 | if (wait) { 121 | let beforeTurn; 122 | do { 123 | beforeTurn = this.portData[port].angle; 124 | await new Promise(res => setTimeout(res, CALLBACK_TIMEOUT_MS)) 125 | } while(this.portData[port].angle !== beforeTurn) 126 | resolve(); 127 | } else { 128 | setTimeout(resolve, CALLBACK_TIMEOUT_MS); 129 | } 130 | }); 131 | }); 132 | } 133 | 134 | /** 135 | * Turn both motors (A and B) by specific angle 136 | * @method Hub#motorAngleMultiAsync 137 | * @param {number} angle degrees to turn from `0` to `2147483647` 138 | * @param {number} [dutyCycleA=100] motor power percentage from `-100` to `100`. If a negative value is given 139 | * rotation is counterclockwise. 140 | * @param {number} [dutyCycleB=100] motor power percentage from `-100` to `100`. If a negative value is given 141 | * rotation is counterclockwise. 142 | * @param {boolean} [wait=false] will promise wait unitll motorAngle has turned 143 | * @returns {Promise} 144 | */ 145 | Hub.prototype.motorAngleMultiAsync = function(angle, dutyCycleA = 100, dutyCycleB = 100, wait = false) { 146 | return new Promise((resolve, reject) => { 147 | this.motorAngleMulti(angle, dutyCycleA, dutyCycleB, async () => { 148 | if (wait) { 149 | let beforeTurn; 150 | do { 151 | beforeTurn = this.portData['AB'].angle; 152 | await new Promise(res => setTimeout(res, CALLBACK_TIMEOUT_MS)) 153 | } while(this.portData['AB'].angle !== beforeTurn) 154 | resolve(); 155 | } else { 156 | setTimeout(resolve, CALLBACK_TIMEOUT_MS); 157 | } 158 | }); 159 | }); 160 | } 161 | 162 | /** 163 | * Use metric units (default) 164 | * @method Hub#useMetricUnits 165 | */ 166 | Hub.prototype.useMetricUnits = function() { 167 | this.useMetric = true; 168 | } 169 | 170 | /** 171 | * Use imperial units 172 | * @method Hub#useImperialUnits 173 | */ 174 | Hub.prototype.useImperialUnits = function() { 175 | this.useMetric = false; 176 | } 177 | 178 | /** 179 | * Set friction modifier 180 | * @method Hub#setFrictionModifier 181 | * @param {number} modifier friction modifier 182 | */ 183 | Hub.prototype.setFrictionModifier = function(modifier) { 184 | this.modifier = modifier; 185 | } 186 | 187 | /** 188 | * Drive specified distance 189 | * @method Hub#drive 190 | * @param {number} distance distance in centimeters (default) or inches. Positive is forward and negative is backward. 191 | * @param {boolean} [wait=true] will promise wait untill the drive has completed. 192 | * @returns {Promise} 193 | */ 194 | Hub.prototype.drive = function(distance, wait = true) { 195 | const angle = Math.abs(distance) * ((this.useMetric ? METRIC_MODIFIER : IMPERIAL_MODIFIER) * this.modifier); 196 | const dutyCycleA = DRIVE_SPEED * (distance > 0 ? 1 : -1); 197 | const dutyCycleB = DRIVE_SPEED * (distance > 0 ? 1 : -1); 198 | return this.motorAngleMultiAsync(angle, dutyCycleA, dutyCycleB, wait); 199 | } 200 | 201 | /** 202 | * Turn robot specified degrees 203 | * @method Hub#turn 204 | * @param {number} degrees degrees to turn. Negative is to the left and positive to the right. 205 | * @param {boolean} [wait=true] will promise wait untill the turn has completed. 206 | * @returns {Promise} 207 | */ 208 | Hub.prototype.turn = function(degrees, wait = true) { 209 | const angle = Math.abs(degrees) * TURN_MODIFIER; 210 | const dutyCycleA = TURN_SPEED * (degrees > 0 ? 1 : -1); 211 | const dutyCycleB = TURN_SPEED * (degrees > 0 ? -1 : 1); 212 | return this.motorAngleMultiAsync(angle, dutyCycleA, dutyCycleB, wait); 213 | } 214 | 215 | /** 216 | * Drive untill sensor shows object in defined distance 217 | * @method Hub#driveUntil 218 | * @param {number} [distance=0] distance in centimeters (default) or inches when to stop. Distance sensor is not very sensitive or accurate. 219 | * By default will stop when sensor notices wall for the first time. Sensor distance values are usualy between 110-50. 220 | * @param {boolean} [wait=true] will promise wait untill the bot will stop. 221 | * @returns {Promise} 222 | */ 223 | Hub.prototype.driveUntil = async function(distance = 0, wait = true) { 224 | const distanceCheck = distance !== 0 ? (this.useMetric ? distance : distance * 2.54) : DEFAULT_STOP_DISTANCE; 225 | this.motorTimeMulti(60, DRIVE_SPEED, DRIVE_SPEED); 226 | if (wait) { 227 | await waitForValueToSet.bind(this)('distance', () => distanceCheck >= this.distance); 228 | await this.motorAngleMultiAsync(0); 229 | } 230 | else { 231 | return waitForValueToSet.bind(this)('distance', () => distanceCheck >= this.distance).then(_ => this.motorAngleMulti(0, 0, 0)); 232 | } 233 | } 234 | 235 | /** 236 | * Turn until there is no object in sensors sight 237 | * @method Hub#turnUntil 238 | * @param {number} [direction=1] direction to turn to. 1 (or any positive) is to the right and 0 (or any negative) is to the left. 239 | * @param {boolean} [wait=true] will promise wait untill the bot will stop. 240 | * @returns {Promise} 241 | */ 242 | Hub.prototype.turnUntil = async function(direction = 1, wait = true) { 243 | const directionModifier = direction > 0 ? 1 : -1; 244 | this.turn(360 * directionModifier, false); 245 | if (wait) { 246 | await waitForValueToSet.bind(this)('distance', () => this.distance >= DEFAULT_CLEAR_DISTANCE); 247 | await this.turn(0, false) 248 | } 249 | else { 250 | return waitForValueToSet.bind(this)('distance', () => this.distance >= DEFAULT_CLEAR_DISTANCE).then(_ => this.turn(0, false)); 251 | } 252 | } 253 | 254 | /** 255 | * Get BLE status when BLE is ready to be used 256 | * @method Boost#bleReadyAsync 257 | * @returns {Promise} ble status `true`/`false` when ble is ready 258 | */ 259 | Boost.prototype.bleReadyAsync = function() { 260 | return new Promise(async (resolve, reject) => { 261 | var ready = await waitForValueToSet.bind(this)('bleReadyStatus'); 262 | if (ready) 263 | resolve(ready); 264 | else 265 | reject(ready); 266 | }); 267 | }; 268 | 269 | /** 270 | * Get Hub details when hub is found 271 | * @method Boost#hubFoundAsync 272 | * @returns {Promise<{uudi: string, address:string, localName: string}>} Hub details 273 | */ 274 | Boost.prototype.hubFoundAsync = function() { 275 | return waitForValueToSet.bind(this)('hubDetails'); 276 | }; 277 | 278 | /** 279 | * @method Boost#connectAsync 280 | * @param {object} hubDetails MAC Address of the Hub 281 | * @param {string} hubDetails.uuid 282 | * @param {string} hubDetails.address 283 | * @param {string} hubDetails.localName 284 | * @returns {Promise} Hub object 285 | */ 286 | Boost.prototype.connectAsync = function(hubDetails) { 287 | return new Promise((resolve, reject) => { 288 | this.connect(hubDetails.address, async (err, hub) => { 289 | if (err) { 290 | reject(err); 291 | } else { 292 | hub.afterInitialization(); 293 | await waitForValueToSet.bind(hub)('connected'); 294 | resolve(hub); 295 | } 296 | }); 297 | }); 298 | }; 299 | 300 | /** 301 | * Connect to a MoveHub and get Hub instance 302 | * @method Boost#getHubAsync 303 | * @returns {Promise} Hub object 304 | */ 305 | Boost.prototype.getHubAsync = async function() { 306 | await this.bleReadyAsync(); 307 | const connectDetails = await this.hubFoundAsync(); 308 | return await this.connectAsync(connectDetails); 309 | }; 310 | 311 | /** 312 | * Execute this method after new instance is created 313 | * @method Boost#afterInitialization 314 | */ 315 | Boost.prototype.afterInitialization = function() { 316 | this.bleReadyStatus = null; 317 | this.hubDetails = null; 318 | 319 | this.on('ble-ready', status => (this.bleReadyStatus = status)); 320 | this.on('hub-found', hubDetails => (this.hubDetails = hubDetails)); 321 | }; 322 | 323 | module.exports.Boost = Boost; 324 | module.exports.Hub = Hub; --------------------------------------------------------------------------------