Skip to content

Instantly share code, notes, and snippets.

@robwormald
Created November 25, 2014 20:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robwormald/cc93a6c74f1c837b4d98 to your computer and use it in GitHub Desktop.
Save robwormald/cc93a6c74f1c837b4d98 to your computer and use it in GitHub Desktop.
angular.module('FITapp.deviceManager', [])
.controller('DeviceManagerController',function($scope,FITBluetoothLEManager){
$scope.deviceManager = FITBluetoothLEManager;
})
.controller('DeviceController',function($scope,$state,FITBluetoothLEManager,$ionicSideMenuDelegate){
$scope.fitDevice = FITBluetoothLEManager.devices.find(function(dvice){
return dvice.address == $state.params.deviceAddress;
});
$scope.leftSideMenu = $ionicSideMenuDelegate
$scope.leftSideMenu.canDragContent(false)
})
.factory('FITError',function($timeout){
function FITError(message) {
this.name = "FITError";
this.message = message || "Unknown FIT Error!";
}
FITError.prototype = new Error();
FITError.prototype.constructor = MyError;
return FITError;
})
.factory('FITBluetoothDevice',function($q,$timeout,FITBluetoothProfiles,FITKeyPressSensor,FITAccelerometer){
function FITBluetoothDevice(manager,discoveryData){
Object.defineProperty(this,'manager',{
enumerable: false,
value: manager
})
this.address = discoveryData.address;
this.deviceName = discoveryData.name;
this.signalStrength = discoveryData.rssi;
this.connected = false;
this.connecting = false;
this.Profiles = {
KeyService: new FITKeyPressSensor(this,FITBluetoothProfiles.SIMPLEKEY_UUID_SERVICE,FITBluetoothProfiles.SIMPLEKEY_UUID_DATA),
Accelerometer: new FITAccelerometer(this,FITBluetoothProfiles.ACCELEROMETER_UUID_SERVICE,FITBluetoothProfiles.ACCELEROMETER_UUID_DATA,FITBluetoothProfiles.ACCELEROMETER_UUID_CONF,FITBluetoothProfiles.ACCELEROMETER_UUID_PERIOD)
};
}
FITBluetoothDevice.prototype.connect = function(){
this.connecting = true;
var self = this;
return self.manager.connectToDevice(self.address).then(function(connectionInfo){
$timeout(function(){
self.onConnect(connectionInfo);
},0)
},self.onConnectFailure,self.onConnectionStateChange);
}
FITBluetoothDevice.prototype.readRSSI = function(){
var self = this;
return self.manager.readRSSI(self).then(function(rssi){
return rssi;
});
}
FITBluetoothDevice.prototype.init = function(){
var self = this;
for(var profileKey in self.Profiles){
console.log(profileKey)
var profile = self.Profiles[profileKey];
var service = self.services.find(function(svc){
console.log(svc)
if(typeof profile.UUID_SERVICE.test != 'undefined'){
return profile.UUID_SERVICE.test(svc.uuid);
}
return svc.uuid == profile.UUID_SERVICE;
})
if(service){
profile.init(service);
}
}
self.initialized = true;
}
FITBluetoothDevice.prototype.readServices = function(){
var self = this;
return self.manager.readServicesFromDevice(self).then(function(services){
var serviceChars = $q.all(services.map(function(svcInfo){
return self.manager.readServiceCharacteristics(self,svcInfo).then(function(chars){
svcInfo.characteristics = chars;
return $q.all(svcInfo.characteristics.map(function(char){
return self.manager.readCharacteristicDescriptors(self,svcInfo,char).then(function(charDetails){
char.descriptors = charDetails;
return char;
});
})).then(function(info){
return services;
})
})
}));
return serviceChars.then(function(){
self.services = services;
self.init();
console.log('got all device service info',self);
return services;
})
});
}
FITBluetoothDevice.prototype.updateServices = function(discoveredServices){
var self = this;
var svcs = discoveredServices.map(function(serviceInfo){
return self.readService(serviceInfo.handle);
})
this.services = svcs;
}
FITBluetoothDevice.prototype.readService = function(serviceHandle){
var self = this;
}
FITBluetoothDevice.prototype.onConnect = function(connectionInfo){
console.log('BLE :: successfully connected',connectionInfo);
this.connected = true;
this.connecting = false;
this.handle = connectionInfo.deviceHandle;
return connectionInfo;
}
FITBluetoothDevice.prototype.onConnectFailure = function(connectionInfo){
console.log('BLE :: connection failure',connectionInfo);
this.error = connectionInfo;
this.connecting = false;
this.connected = false;
}
FITBluetoothDevice.prototype.onConnectionStateChange = function(connectionInfo){
console.log('BLE :: connection state changed',connectionInfo);
}
return FITBluetoothDevice;
})
.factory('FITKeyPressSensor',function($q,$timeout,FITBluetoothProfiles,FITBluetoothBaseProfile){
var SimpleKey = function (fitDevice) {
FITBluetoothBaseProfile.call(this,
"SimpleKey",
fitDevice,
FITBluetoothProfiles.SIMPLEKEY_UUID_SERVICE,
FITBluetoothProfiles.SIMPLEKEY_UUID_DATA);
};
SimpleKey.Keys = {
RIGHT: 0x01,
LEFT: 0x02,
CENTER: 0x04
};
SimpleKey.prototype = new FITBluetoothBaseProfile();
SimpleKey.prototype.constructor = SimpleKey;
// Remove unsupported prototype functions
delete SimpleKey.prototype.enable;
delete SimpleKey.prototype.disable;
return SimpleKey;
})
.constant('FITBluetoothProfiles',{
GATT_CLIENT_CHAR_CFG_UUID: /^(0000)?2902(-0000-1000-8000-00805f9b34fb)?$/,
ACCELEROMETER_UUID_SERVICE: "f000aa10-0451-4000-b000-000000000000",
ACCELEROMETER_UUID_DATA: /^f000aa11-0451-4000-b000-000000000000$/,
ACCELEROMETER_UUID_CONF: "f000aa12-0451-4000-b000-000000000000",
ACCELEROMETER_UUID_PERIOD: "f000aa13-0451-4000-b000-000000000000",
BAROMETRICPRESSURE_UUID_SERVICE: "f000aa40-0451-4000-b000-000000000000",
BAROMETRICPRESSURE_UUID_DATA: /^f000aa41-0451-4000-b000-000000000000$/,
BAROMETRICPRESSURE_UUID_CONF: "f000aa42-0451-4000-b000-000000000000",
BAROMETRICPRESSURE_UUID_PERIOD: "f000aa44-0451-4000-b000-000000000000",
BAROMETRICPRESSURE_UUID_CALIBRATION: /^f000aa43-0451-4000-b000-000000000000$/,
GYROSCOPE_UUID_SERVICE: "f000aa50-0451-4000-b000-000000000000",
GYROSCOPE_UUID_DATA: /^f000aa51-0451-4000-b000-000000000000$/,
GYROSCOPE_UUID_CONF: "f000aa52-0451-4000-b000-000000000000",
GYROSCOPE_UUID_PERIOD: "f000aa53-0451-4000-b000-000000000000",
HUMIDITY_UUID_SERVICE: "f000aa20-0451-4000-b000-000000000000",
HUMIDITY_UUID_DATA: /^f000aa21-0451-4000-b000-000000000000$/,
HUMIDITY_UUID_CONF: "f000aa22-0451-4000-b000-000000000000",
HUMIDITY_UUID_PERIOD: "f000aa23-0451-4000-b000-000000000000",
IRTEMPERATURE_UUID_SERVICE: "f000aa00-0451-4000-b000-000000000000",
IRTEMPERATURE_UUID_DATA: /^f000aa01-0451-4000-b000-000000000000$/,
IRTEMPERATURE_UUID_CONF: "f000aa02-0451-4000-b000-000000000000",
IRTEMPERATURE_UUID_PERIOD: "f000aa03-0451-4000-b000-000000000000",
MAGNETOMETER_UUID_SERVICE: "f000aa30-0451-4000-b000-000000000000",
MAGNETOMETER_UUID_DATA: /^f000aa31-0451-4000-b000-000000000000$/,
MAGNETOMETER_UUID_CONF: "f000aa32-0451-4000-b000-000000000000",
MAGNETOMETER_UUID_PERIOD: "f000aa33-0451-4000-b000-000000000000",
SIMPLEKEY_UUID_SERVICE: /^(0000)?ffe0(-0000-1000-8000-00805f9b34fb)?$/,
SIMPLEKEY_UUID_DATA: /^(0000)?ffe1(-0000-1000-8000-00805f9b34fb)?$/,
GUID_PATTERN: /([a-f0-9]{8})-?([a-f0-9]{4})-?([a-f0-9]{4})-?([a-f0-9]{4})-?([a-f0-9]{12})/,
GUID_REPLACEMENT: '$1-$2-$3-$4-$5'
})
.factory('FITBluetoothBaseProfile',['FITBluetoothProfiles','$timeout','$q',function(Constants,$timeout,$q){
/**
* The base class representing each sensor
* @param {string} name The name of the sensor
* @param {FITBluetoothDevice} fitDevice The fitDevice object this sensor belongs to
* @param {uuid} UUID_DATA The UUID of the data characteristic
* @param {uuid} UUID_CONF The UUID of the configuration characteristic
* @param {uuid} UUID_PERIOD The UUID of the period characteristic
*/
var FITBluetoothBaseProfile = function (name, fitDevice, UUID_SERVICE, UUID_DATA, UUID_CONF, UUID_PERIOD) {
this.identifier = name;
this.device = fitDevice;
this.UUID_SERVICE = UUID_SERVICE;
this.UUID_DATA = UUID_DATA;
this.UUID_CONF = UUID_CONF;
this.UUID_PERIOD = UUID_PERIOD;
this.enabled = false;
this.characteristics = {
config: null,
period: null,
data: null,
};
this.descriptors = {
notification: null
};
this._listeners = [];
};
/**
* Initializes the sensor with the FITBluetoothBaseProfile service information
* @param {service} service The SensorTag service object
*/
FITBluetoothBaseProfile.prototype.init = function (service) {
for (var ci in service.characteristics) {
var characteristic = service.characteristics[ci],
cGuid = characteristic.uuid.replace(Constants.GUID_PATTERN, Constants.GUID_REPLACEMENT);
switch (cGuid) {
case this.UUID_CONF:
this.characteristics.config = characteristic;
break;
case this.UUID_PERIOD:
this.characteristics.period = characteristic;
break;
}
if(this.UUID_DATA.test(cGuid)) {
this.characteristics.data = characteristic;
for (var di in characteristic.descriptors) {
var descriptor = characteristic.descriptors[di],
dGuid = descriptor.uuid.replace(Constants.GUID_PATTERN, Constants.GUID_REPLACEMENT);
if (Constants.GATT_CLIENT_CHAR_CFG_UUID.test(dGuid)) {
this.descriptors.notification = descriptor;
}
}
}
}
$timeout(function(){
this.initialized = true;
},0)
console.log("Initialized");
};
/**
* Logs a message on behalf of the sensor
* @param {string} message The message to log
*/
FITBluetoothBaseProfile.prototype.log = function (message) {
this.log(this.identifier + ': ' + message);
};
/**
* Enables the sensor
* @param {Integer} [value] The value to initialize the sensor with
*/
FITBluetoothBaseProfile.prototype.enable = function (value) {
var ON = 1,
self = this;
return self.device.manager.writeCharacteristic(
self.device,
self.characteristics.config,
new Uint8Array([value || ON]),
function () {
self.enabled = true;
self.log("Enabled sensor...");
}
)
};
/**
* Disables the sensor
*/
FITBluetoothBaseProfile.prototype.disable = function (value) {
var OFF = 0,
self = this;
self.device.manager.writeCharacteristic(
self.characteristics.config,
new Uint8Array([OFF]),
function () {
self.enabled = false;
self.log("Disabled");
},
function (errorCode) {
self.log("disable error: " + errorCode);
}
);
};
/**
* Toggle sensor status
*/
FITBluetoothBaseProfile.prototype.toggleStatus = function (newVal) {
self = this
console.log(newVal)
if(newVal == true){
self.enableNotification()
self.enable(newVal)
}
else if(newVal == false){
self.disableNotification()
self.disable(newVal)
}
};
/**
* Enables notifications for the sensor
*/
FITBluetoothBaseProfile.prototype.enableNotification = function () {
var ENABLE_NOTIFICATIONS = [1, 0],
self = this;
function writeEnableDescriptor(){
if(!self.descriptors.notification){
return $q.when(true);
}
return self.device.manager.writeDescriptor(
self.device,
self.descriptors.notification,
new Uint8Array(ENABLE_NOTIFICATIONS)
).then(function(){
return true;
console.log('wrote notification enable success')
},function(){
console.log('wrote notification enable error')
})
}
writeEnableDescriptor().then(function(ready){
return self.device.manager.enableNotification(
self.device,
self.characteristics.data,
function() {
self.onDataNotify.apply(self, arguments);
}).then(function(){
console.log('notifications enabled');
return true;
},function(err){
console.log('error subscribing to notifications');
return false;
})
})
};
/**
* Disables notifications for the sensor
*/
FITBluetoothBaseProfile.prototype.disableNotification = function () {
var DISABLE_NOTIFICATIONS = [0, 0],
self = this;
function writeDisableDescriptor(){
if(!self.descriptors.notification){
return $q.when(true);
}
return self.device.manager.writeDescriptor(
self.device,
self.descriptors.notification,
new Uint8Array(DISABLE_NOTIFICATIONS),
function () {
self.log("Notifications disabled");
},
function (errorCode) {
self.log("disableNotification write error: " + errorCode);
}
);
}
writeDisableDescriptor().then(function(ready){
return self.device.manager.disableNotification(
self.device,
self.characteristics.data,
function() {
// Do nothing
},
function (errorCode) {
self.log("disableNotification subscribe error: " + errorCode);
}
);
})
};
/**
* Adds a notification listener
* @param {callback} callback The callback to receive updates from the sensor
* @returns {handle} The callback handle
*/
FITBluetoothBaseProfile.prototype.addListener = function (callback) {
this._listeners.push(callback);
// Return a handle
return this._listeners.length - 1;
};
/**
* Removes a notification listener
* @param {handle} handle The handle for the callback
*/
FITBluetoothBaseProfile.prototype.removeListener = function (handle) {
this._listeners.splice(handle, 1);
};
/**
* Notifies all the listeners
*/
FITBluetoothBaseProfile.prototype.onDataNotify = function () {
console.log('data:',arguments);
for (var li in this._listeners) {
var listener = this._listeners[li];
listener.apply(this, arguments);
}
};
return FITBluetoothBaseProfile;
}])
.factory('FITBluetoothLEManager',function($timeout,$q,$cacheFactory,$log,FITBluetoothDevice){
var exec = cordova.require('cordova/exec');
var connectionState = {
'STATE_DISCONNECTED': 0,
'STATE_CONNECTING': 1,
'STATE_CONNECTED': 2,
'STATE_DISCONNECTING': 4
};
function FITBluetoothLEManager(){
this.devices = [];
this.deviceCache = $cacheFactory('FIT_DEVICE_CACHE');
}
FITBluetoothLEManager.prototype.startScanning = function(){
var self = this;
self.scanning = true;
console.log('starting scan for devices...');
exec(function(deviceInfo){
$timeout(function(){
self.didDiscoverDevice(deviceInfo);
},0);
},function(err){
$timeout(function(){
self.scanDidFailWithError(err);
},0);
},'BLE','startScan',[]);
}
FITBluetoothLEManager.prototype.stopScanning = function(){
this.scanning = false;
exec(null,null,'BLE','stopScan',[]);
}
FITBluetoothLEManager.prototype.scanForDevices = function(options){
var self = this
self.startScanning()
$timeout(function(){
self.stopScanning()
},2000)
}
FITBluetoothLEManager.prototype.scanForDevice = function(deviceAddress,options){
}
FITBluetoothLEManager.prototype.didDiscoverDevice = function(deviceInfo){
var self = this;
console.log('discovered device ',deviceInfo);
var deviceInstance = self.deviceCache.get(deviceInfo.address);
if(!deviceInstance){
deviceInstance = new FITBluetoothDevice(self,deviceInfo);
self.deviceCache.put(deviceInfo.address,deviceInstance);
}
if(!self.devices.find(function(dvc){
return dvc.address == deviceInstance.address;
})){
self.devices.push(deviceInstance);
}
}
FITBluetoothLEManager.prototype.scanDidFailWithError = function(deviceError){
this.scanning = false;
}
FITBluetoothLEManager.prototype.readRSSI = function(btDevice){
var qPing = $q.defer();
exec(function(ping){
$timeout(function(){
return qPing.resolve(ping);
},0)
},function(err){
qPing.reject(err);
},'BLE','rssi',[btDevice.handle]);
return qPing.promise;
}
FITBluetoothLEManager.prototype.readServicesFromDevice = function(btDevice){
var self = this;
var qServices = $q.defer();
exec(function(discoveredServices){
console.log('services:',discoveredServices);
$timeout(function(){
self.services = discoveredServices;
qServices.resolve(discoveredServices);
},0)
},function(err){
console.log(err);
$timeout(function(){
qServices.reject(err);
},0)
},'BLE','services',[btDevice.handle]);
return qServices.promise;
}
FITBluetoothLEManager.prototype.readServiceCharacteristics = function(btDevice,btDeviceService){
var self = this;
var qServiceChars = $q.defer();
exec(function(discoveredCharacteristics){
console.log('characteristics:',discoveredCharacteristics);
$timeout(function(){
btDeviceService.characteristics = discoveredCharacteristics;
qServiceChars.resolve(btDeviceService.characteristics);
},0)
},function(err){
console.log(err);
$timeout(function(){
qServiceChars.reject(err);
},0)
},'BLE','characteristics',[btDevice.handle,btDeviceService.handle]);
return qServiceChars.promise;
}
FITBluetoothLEManager.prototype.readCharacteristicDescriptors = function(btDevice,btDeviceService,btDeviceServiceCharacteristic){
var self = this;
var qServiceCharDescriptors = $q.defer();
exec(function(discoveredDescriptors){
console.log('descriptors:',discoveredDescriptors);
$timeout(function(){
btDeviceServiceCharacteristic.descriptors = discoveredDescriptors;
qServiceCharDescriptors.resolve(btDeviceServiceCharacteristic.descriptors);
},0)
},function(err){
console.log(err);
$timeout(function(){
qServiceCharDescriptors.reject(err);
},0)
},'BLE','characteristics',[btDevice.handle,btDeviceService.handle,btDeviceServiceCharacteristic.handle]);
return qServiceCharDescriptors.promise;
}
FITBluetoothLEManager.prototype.writeDescriptor = function(btDevice,btDeviceServiceDescriptor,data){
var self = this;
var qWriteResult = $q.defer();
exec(function(writeSuccess){
console.log('write success:',writeSuccess);
$timeout(function(){
qWriteResult.resolve(writeSuccess);
},0)
},function(err){
console.log(err);
$timeout(function(){
qWriteResult.reject(err);
},0)
},'BLE','writeDescriptor',[btDevice.handle,btDeviceServiceDescriptor.handle,data.buffer]);
return qWriteResult.promise;
}
FITBluetoothLEManager.prototype.writeCharacteristic = function(btDevice,btDeviceCharacteristic,data){
var self = this;
var qWriteResult = $q.defer();
exec(function(writeSuccess){
console.log('write success:',writeSuccess);
$timeout(function(){
qWriteResult.resolve(writeSuccess);
},0)
},function(err){
console.log(err);
$timeout(function(){
qWriteResult.reject(err);
},0)
},'BLE','writeCharacteristic',[btDevice.handle,btDeviceCharacteristic.handle,data.buffer]);
return qWriteResult.promise;
}
FITBluetoothLEManager.prototype.enableNotification = function(btDevice,btDeviceCharacteristic,listener){
var self = this;
var timer = $timeout(function(){
return true;
},3000);
exec(function(data){
listener.call(this,data);
},function(err){
console.log(err);
},'BLE','enableNotification',[btDevice.handle,btDeviceCharacteristic.handle]);
return timer;
}
FITBluetoothLEManager.prototype.connectToDevice = function(deviceAddress){
this.stopScanning();
var connection = $q.defer();
exec(function(connectionEvent){
if(connectionEvent.state == connectionState.STATE_CONNECTING){
return connection.notify(connectionEvent);
};
if(connectionEvent.state == connectionState.STATE_CONNECTED){
return connection.resolve(connectionEvent);
}
},function(err){
connection.reject(err);
},'BLE','connect',[deviceAddress]);
return connection.promise;
}
var manager = new FITBluetoothLEManager();
return manager;
})
.factory('FITDeviceCache',function(){
})
.factory('FITDevices',function(FitDeviceConfig){
function FITDeviceManager(){
}
FITDeviceManager.scanForDevices = function(){
}
return FITDeviceManager;
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment