Created
February 8, 2016 13:24
-
-
Save leefsmp/2e2d75cf729f3a3e3363 to your computer and use it in GitHub Desktop.
SpheroSvc Service: wraps calls to the sphero API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///////////////////////////////////////////////////////////////////// | |
// Copyright (c) Autodesk, Inc. All rights reserved | |
// Written by Philippe Leefsma 2016 - ADN/Developer Technical Services | |
// | |
// Permission to use, copy, modify, and distribute this software in | |
// object code form for any purpose and without fee is hereby granted, | |
// provided that the above copyright notice appears in all copies and | |
// that both that copyright notice and the limited warranty and | |
// restricted rights notice below appear in all supporting | |
// documentation. | |
// | |
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. | |
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF | |
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. | |
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE | |
// UNINTERRUPTED OR ERROR FREE. | |
///////////////////////////////////////////////////////////////////// | |
import noble from 'noble'; | |
import sphero from 'sphero'; | |
import EventEmitter from 'events'; | |
/////////////////////////////////////////////////////////////////// | |
// SpheroSvc Service: wraps calls to the sphero API | |
// Keeps track of detected devices | |
// Allows connecting a specific device and send commands to it | |
// Handles the logic for the device: blink LED, automated path, ... | |
// | |
/////////////////////////////////////////////////////////////////// | |
export default class SpheroSvc extends EventEmitter { | |
/////////////////////////////////////////////////////////////////// | |
// Constructor | |
// | |
/////////////////////////////////////////////////////////////////// | |
constructor() { | |
super(); | |
this.detectedDevices = {}; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Scan for local BLE sphero devices | |
// | |
/////////////////////////////////////////////////////////////////// | |
scan(scanTimeout, filters) { | |
var _thisSvc = this; | |
function _discover(peripheral) { | |
var deviceId = peripheral.id; | |
var address = peripheral.address; | |
var name = peripheral.advertisement.localName; | |
if (name) { | |
filters.forEach((filter)=> { | |
if (filter == "*" || name.indexOf(filter) > -1) { | |
var blinker = new Blinker(_thisSvc, deviceId); | |
var device = { | |
deviceId: deviceId, | |
connected: false, | |
address: address, | |
name: name, | |
blinker: blinker, | |
pathId: null | |
}; | |
_thisSvc.emit('DEVICE_DETECTED', device); | |
_thisSvc.detectedDevices[deviceId] = device; | |
} | |
}); | |
} | |
} | |
function _startScan() { | |
noble.on('discover', _discover); | |
noble.startScanning(); | |
//scan for 30 sec ... | |
setTimeout(()=>{ | |
noble.removeListener('discover', _discover); | |
console.log('Stop scanning'); | |
noble.stopScanning(); | |
}, scanTimeout || 30000); | |
} | |
try { | |
// returns already connected devices as they wont | |
// appear in the scan | |
for(var deviceId in _thisSvc.detectedDevices) { | |
var device = _thisSvc.detectedDevices[deviceId]; | |
if(device.connected){ | |
_thisSvc.emit('DEVICE_DETECTED', device); | |
} | |
} | |
_startScan(); | |
} | |
catch (ex){ | |
noble.on('stateChange', function(state) { | |
if (state === 'poweredOn') { | |
_startScan(); | |
} | |
else { | |
console.log('Stop scanning'); | |
noble.stopScanning(); | |
} | |
}); | |
} | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Returns device by id | |
// | |
/////////////////////////////////////////////////////////////////// | |
getDeviceById(deviceId) { | |
var _thisSvc = this; | |
if(_thisSvc.detectedDevices[deviceId]){ | |
return _thisSvc.detectedDevices[deviceId]; | |
} | |
return null; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Connects device by BLE id | |
// Returns device if already connected | |
// | |
/////////////////////////////////////////////////////////////////// | |
connect(deviceId) { | |
var _thisSvc = this; | |
function _onConnect(driver){ | |
driver.color(0x0000FF); | |
setTimeout(function () { | |
driver.color(0x00); | |
}, 3000); | |
} | |
var promise = new Promise((resolve, reject)=> { | |
try { | |
if(!_thisSvc.detectedDevices[deviceId]) { | |
reject('Invalid device id'); | |
} | |
var device = _thisSvc.detectedDevices[deviceId]; | |
if(device.connected && device.driver !== null){ | |
_onConnect(device.driver); | |
return resolve(device); | |
} | |
var driver = sphero(deviceId); | |
driver.connect(()=> { | |
console.log('Device connected: ' + | |
deviceId); | |
_onConnect(driver); | |
//collision handler | |
driver.on("collision", (data)=> { | |
driver.color(0xFF0000); | |
driver.stop(function () { | |
setTimeout(function () { | |
driver.color(0x00); | |
}, 1000); | |
}); | |
}); | |
// Activate collision | |
driver.detectCollisions(); | |
// Auto stop the Sphero when it detects it | |
// has become disconnected | |
driver.stopOnDisconnect(); | |
_thisSvc.detectedDevices[deviceId].connected = true; | |
_thisSvc.detectedDevices[deviceId].driver = driver; | |
return resolve(_thisSvc.detectedDevices[deviceId]); | |
}); | |
//kicks the connection | |
noble.startScanning(); | |
} | |
catch (ex) { | |
reject(ex); | |
} | |
}); | |
return promise; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Disconnects specific device | |
// | |
/////////////////////////////////////////////////////////////////// | |
disconnect(deviceId) { | |
var _thisSvc = this; | |
var promise = new Promise((resolve, reject)=> { | |
try { | |
if (!_thisSvc.detectedDevices[deviceId]) { | |
reject('Invalid device id'); | |
} | |
var device = _thisSvc.detectedDevices[deviceId]; | |
if (device.connected && device.driver !== null) { | |
device.driver.disconnect(()=> { | |
_thisSvc.detectedDevices[deviceId].connected = false; | |
_thisSvc.detectedDevices[deviceId].driver = null; | |
return resolve('device disconnected'); | |
}); | |
} | |
else { | |
return resolve('device not connected'); | |
} | |
} | |
catch (ex) { | |
reject(ex); | |
} | |
}); | |
return promise; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Shutdown controller: disconnect all devices | |
// | |
/////////////////////////////////////////////////////////////////// | |
shutdown() { | |
var _thisSvc = this; | |
var promise = new Promise(async(resolve, reject)=> { | |
try { | |
for(var deviceId in _thisSvc.detectedDevices) { | |
await _thisSvc.disconnect(); | |
return resolve('ALL devices disconnected'); | |
} | |
} | |
catch (ex) { | |
reject(ex); | |
} | |
}); | |
return promise; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Roll device | |
// | |
/////////////////////////////////////////////////////////////////// | |
roll(deviceId, speed, heading) { | |
var _thisSvc = this; | |
var promise = new Promise((resolve, reject)=> { | |
try { | |
var device = _thisSvc.getDeviceById(deviceId); | |
if (!device) { | |
return reject('Invalid deviceId'); | |
} | |
device.driver.roll(speed, heading, ()=>{ | |
resolve('done'); | |
}); | |
} | |
catch (ex) { | |
return reject(ex); | |
} | |
}); | |
return promise; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Set device heading | |
// | |
/////////////////////////////////////////////////////////////////// | |
setHeading(deviceId, heading) { | |
var _thisSvc = this; | |
var promise = new Promise((resolve, reject)=> { | |
try { | |
var device = _thisSvc.getDeviceById(deviceId); | |
if (!device) { | |
return reject('Invalid deviceId'); | |
} | |
device.driver.roll(0, heading, 2, ()=> { | |
setTimeout(()=> { | |
device.driver.setHeading(0, ()=> { | |
device.driver.roll(0, 0, 1, ()=> { | |
resolve('done'); | |
}); | |
}); | |
}, 300); | |
}); | |
//device.driver.setBackLed(255); | |
} | |
catch (ex) { | |
return reject(ex); | |
} | |
}); | |
return promise; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Set LED color | |
// | |
/////////////////////////////////////////////////////////////////// | |
color(deviceId, color) { | |
var _thisSvc = this; | |
var promise = new Promise((resolve, reject)=> { | |
try { | |
var device = _thisSvc.getDeviceById(deviceId); | |
if (!device) { | |
return reject('Invalid deviceId'); | |
} | |
device.driver.color(color, ()=>{ | |
resolve('done'); | |
}); | |
} | |
catch (ex) { | |
return reject(ex); | |
} | |
}); | |
return promise; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Blink LED | |
// | |
/////////////////////////////////////////////////////////////////// | |
blink(deviceId, enabled, period, color) { | |
var _thisSvc = this; | |
var promise = new Promise((resolve, reject)=> { | |
try { | |
var device = _thisSvc.getDeviceById(deviceId); | |
if (!device) { | |
return reject('Invalid deviceId'); | |
} | |
device.blinker.setPeriod(period); | |
device.blinker.setColor(color); | |
device.blinker.enable(enabled); | |
resolve('done'); | |
} | |
catch (ex) { | |
return reject(ex); | |
} | |
}); | |
return promise; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Starts moving device based on path function | |
// | |
/////////////////////////////////////////////////////////////////// | |
startPath(deviceId, pathFunc, speed, freq) { | |
var _thisSvc = this; | |
var promise = new Promise((resolve, reject)=> { | |
try { | |
var device = _thisSvc.getDeviceById(deviceId); | |
if (!device) { | |
return reject('Invalid deviceId'); | |
} | |
var pathId = guid(); | |
_thisSvc.detectedDevices[deviceId].pathId = pathId; | |
var hiTimer = new HiTimer(); | |
function step() { | |
if(_thisSvc.detectedDevices[deviceId].pathId != pathId) { | |
device.driver.roll(0, 0); | |
return; | |
} | |
var heading = pathFunc( | |
hiTimer.getElapsedMs() * 0.001); | |
device.driver.roll(speed, heading); | |
setTimeout(step, freq); | |
} | |
step(); | |
resolve('done'); | |
} | |
catch (ex) { | |
return reject(ex); | |
} | |
}); | |
return promise; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Stops moving device along path | |
// | |
/////////////////////////////////////////////////////////////////// | |
stopPath(deviceId) { | |
var _thisSvc = this; | |
var promise = new Promise((resolve, reject)=> { | |
try { | |
var device = _thisSvc.getDeviceById(deviceId); | |
if (!device) { | |
return reject('Invalid deviceId'); | |
} | |
device.pathId = 0; | |
resolve('done'); | |
} | |
catch (ex) { | |
return reject(ex); | |
} | |
}); | |
return promise; | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Return a square path function | |
// | |
/////////////////////////////////////////////////////////////////// | |
createSquarePathFn(lentgh, speed) { | |
var squarePathFn = function(t) { | |
var state = (Math.floor(speed * 0.01 * t/lentgh) % 4); | |
return state * 90; | |
} | |
return squarePathFn; | |
} | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Hi Precision timer for node.js | |
// | |
/////////////////////////////////////////////////////////////////// | |
class HiTimer { | |
constructor() { | |
this._hrstart = process.hrtime(); | |
} | |
reset() { | |
this._hrstart = process.hrtime(); | |
} | |
getElapsedMs() { | |
var hrend = process.hrtime(this._hrstart); | |
return hrend[0] * 1000 + hrend[1]/1000000; | |
} | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Utility to blink the LED | |
// | |
/////////////////////////////////////////////////////////////////// | |
function Blinker(bb8Svc, deviceId) { | |
var _blinker = this; | |
_blinker.color = 0; | |
_blinker.intervalId = 0; | |
_blinker.enabled = false; | |
_blinker.period = 0; | |
function blink(color) { | |
bb8Svc.color(deviceId, color); | |
setTimeout(function() { | |
bb8Svc.color(deviceId, 0); | |
}, 50); | |
} | |
function run() { | |
clearInterval(_blinker.intervalId); | |
if(_blinker.enabled) { | |
if(_blinker.period > 0) { | |
_blinker.intervalId = setInterval( | |
function () { | |
blink(_blinker.color); | |
}, _blinker.period); | |
} | |
else { | |
bb8Svc.color( | |
deviceId, | |
_blinker.color); | |
} | |
} | |
else { | |
bb8Svc.color(deviceId, 0); | |
} | |
} | |
_blinker.enable = function(enabled){ | |
_blinker.enabled = enabled; | |
run(); | |
} | |
_blinker.setPeriod = function(t) { | |
_blinker.period = t; | |
run(); | |
} | |
_blinker.setColor = function(color){ | |
_blinker.color = color; | |
if(_blinker.period == 0 && _blinker.enabled) { | |
bb8Svc.color( | |
deviceId, | |
_blinker.color); | |
} | |
} | |
} | |
/////////////////////////////////////////////////////////////////// | |
// Utility to generate random unique guid's | |
// | |
/////////////////////////////////////////////////////////////////// | |
function guid(size) { | |
size = size || 14; | |
var pattern = ''; | |
for(var i=1; i <= size; ++i) | |
pattern += (i%5 ? 'x' : '-'); | |
var d = new Date().getTime(); | |
var guid = pattern.replace( | |
/[xy]/g, | |
function (c) { | |
var r = (d + Math.random() * 16) % 16 | 0; | |
d = Math.floor(d / 16); | |
return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16); | |
}); | |
return guid; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment