// Shared functions for BLE TI SensorTags.
;(function()
{
/**
* @namespace
* @description JavaScript library for the TI SensorTag.
* @alias evothings.tisensortag.ble
* @public
*/
var sensortag = {}
// Add object to namespace.
evothings.tisensortag.ble = sensortag
/**
* @namespace
* @description Status constants.
* @alias evothings.tisensortag.ble.status
* @public
*/
var status = {}
// Add to namespace. This trick is needed for JSDoc,
// cannot use sensortag.status below, docs do not
// generate properly in this case.
sensortag.status = status
/**
* @description Scanning is ongoing.
* @public
*/
status.SCANNING = 'SCANNING'
/**
* @description Found SensorTag device.
* @public
*/
status.SENSORTAG_FOUND = 'SENSORTAG_FOUND'
/**
* @description Scanning timed out, no device found.
* @public
*/
status.SENSORTAG_NOT_FOUND = 'SENSORTAG_NOT_FOUND'
/**
* @description Connecting to physical device.
* @public
*/
status.CONNECTING = 'CONNECTING'
/**
* @description Connected to physical device.
* @public
*/
status.CONNECTED = 'CONNECTED'
/**
* @description Reading info about the device.
* @public
*/
status.READING_DEVICE_INFO = 'READING_DEVICE_INFO'
/**
* @description Info about the device is available.
* @public
*/
status.DEVICE_INFO_AVAILABLE = 'DEVICE_INFO_AVAILABLE'
/**
* @description Reading services of the device.
* @public
*/
status.READING_SERVICES = 'READING_SERVICES'
/**
* @description SensorTag device is connected and sensors are avaliable.
* @public
*/
status.SENSORTAG_ONLINE = 'SENSORTAG_ONLINE'
/**
* @namespace
* @description Error constants. There are additional
* error strings reported by the cordova-ble plugin
* and the easyble.js library.
* @alias evothings.tisensortag.ble.error
* @public
*/
var error = {}
// Add to namespace. This trick is needed for JSDoc,
// cannot use sensortag.error below, docs do not
// generate properly in this case.
sensortag.error = error
/**
* @description Scan failed.
* @public
*/
error.SCAN_FAILED = 'SCAN_FAILED'
/**
* Public. Create a SensorTag instance.
* @returns {@link evothings.tisensortag.SensorTagInstanceBLE}
* @private
*/
sensortag.addInstanceMethods = function(anInstance)
{
/**
* @namespace
* @alias evothings.tisensortag.SensorTagInstanceBLE
* @description Abstract SensorTag instance object.
* This object specifies the interface common to Bluetooth Smart
* SensorTags.
* @public
*/
var instance = anInstance
// UUIDs for services, characteristics, and descriptors.
instance.NOTIFICATION_DESCRIPTOR = '00002902-0000-1000-8000-00805f9b34fb'
instance.DEVICEINFO_SERVICE = '0000180a-0000-1000-8000-00805f9b34fb'
instance.FIRMWARE_DATA = '00002a26-0000-1000-8000-00805f9b34fb'
instance.MODELNUMBER_DATA = '00002a24-0000-1000-8000-00805f9b34fb'
instance.TEMPERATURE = {
SERVICE: 'f000aa00-0451-4000-b000-000000000000',
DATA: 'f000aa01-0451-4000-b000-000000000000',
CONFIG: 'f000aa02-0451-4000-b000-000000000000',
// Missing in HW rev. 1.2 (FW rev. 1.5)
PERIOD: 'f000aa03-0451-4000-b000-000000000000',
}
instance.HUMIDITY = {
SERVICE: 'f000aa20-0451-4000-b000-000000000000',
DATA: 'f000aa21-0451-4000-b000-000000000000',
CONFIG: 'f000aa22-0451-4000-b000-000000000000',
PERIOD: 'f000aa23-0451-4000-b000-000000000000',
}
instance.BAROMETER = {
SERVICE: 'f000aa40-0451-4000-b000-000000000000',
DATA: 'f000aa41-0451-4000-b000-000000000000',
CONFIG: 'f000aa42-0451-4000-b000-000000000000',
CALIBRATION: 'f000aa43-0451-4000-b000-000000000000',
PERIOD: 'f000aa44-0451-4000-b000-000000000000',
}
// Only in SensorTag CC2541.
instance.ACCELEROMETER = {
SERVICE: 'f000aa10-0451-4000-b000-000000000000',
DATA: 'f000aa11-0451-4000-b000-000000000000',
CONFIG: 'f000aa12-0451-4000-b000-000000000000',
PERIOD: 'f000aa13-0451-4000-b000-000000000000',
}
// Only in SensorTag CC2541.
instance.MAGNETOMETER = {
SERVICE: 'f000aa30-0451-4000-b000-000000000000',
DATA: 'f000aa31-0451-4000-b000-000000000000',
CONFIG: 'f000aa32-0451-4000-b000-000000000000',
PERIOD: 'f000aa33-0451-4000-b000-000000000000',
}
// Only in SensorTag CC2541.
instance.GYROSCOPE = {
SERVICE: 'f000aa50-0451-4000-b000-000000000000',
DATA: 'f000aa51-0451-4000-b000-000000000000',
CONFIG: 'f000aa52-0451-4000-b000-000000000000',
PERIOD: 'f000aa53-0451-4000-b000-000000000000',
}
// Only in SensorTag CC2650.
instance.LUXOMETER = {
SERVICE: 'f000aa70-0451-4000-b000-000000000000',
DATA: 'f000aa71-0451-4000-b000-000000000000',
CONFIG: 'f000aa72-0451-4000-b000-000000000000',
PERIOD: 'f000aa73-0451-4000-b000-000000000000',
}
// Only in SensorTag CC2650.
instance.MOVEMENT = {
SERVICE: 'f000aa80-0451-4000-b000-000000000000',
DATA: 'f000aa81-0451-4000-b000-000000000000',
CONFIG: 'f000aa82-0451-4000-b000-000000000000',
PERIOD: 'f000aa83-0451-4000-b000-000000000000',
}
instance.KEYPRESS = {
SERVICE: '0000ffe0-0000-1000-8000-00805f9b34fb',
DATA: '0000ffe1-0000-1000-8000-00805f9b34fb',
}
/**
* Internal. Services used by the application.
* @instance
* @private
*/
instance.requiredServices = []
/**
* Both CC2541 and CC2650.
* Public. Set the humidity temperature callback.
* @param fun - success callback called repeatedly: fun(data)
* @param interval - humidity rate in milliseconds.
* @instance
* @public
*/
instance.temperatureCallback = function(fun, interval)
{
instance.temperatureFun = fun
instance.temperatureConfig = [1] // on
instance.temperatureInterval = Math.max(300, interval)
instance.requiredServices.push(instance.TEMPERATURE.SERVICE)
return instance
}
/**
* Both CC2541 and CC2650.
* Public. Set the humidity notification callback.
* @param fun - success callback called repeatedly: fun(data)
* @param interval - humidity rate in milliseconds.
* @instance
* @public
*/
instance.humidityCallback = function(fun, interval)
{
instance.humidityFun = fun
instance.humidityConfig = [1] // on
instance.humidityInterval = Math.max(100, interval)
instance.requiredServices.push(instance.HUMIDITY.SERVICE)
return instance
}
/**
* Both CC2541 and CC2650.
* Public. Set the barometer notification callback.
* @param fun - success callback called repeatedly: fun(data)
* @param interval - barometer rate in milliseconds.
* @instance
* @public
*/
instance.barometerCallback = function(fun, interval)
{
instance.barometerFun = fun
instance.barometerConfig = [1] // on
instance.barometerInterval = Math.max(100, interval)
instance.requiredServices.push(instance.BAROMETER.SERVICE)
return instance
}
/**
* Both CC2541 and CC2650.
* Public. Set the keypress notification callback.
* @param fun - success callback called when a key is pressed: fun(data)
* @instance
* @public
*/
instance.keypressCallback = function(fun)
{
instance.keypressFun = fun
instance.requiredServices.push(instance.KEYPRESS.SERVICE)
return instance
}
/**
* Determine if a BLE device is a SensorTag.
* This version checks the general case using
* the advertised name.
* Specific versions for CC2541 and CC2650 uses
* advertisement data to determine tag type.
* @instance
* @public
*/
instance.deviceIsSensorTag = function(device)
{
return (device != null) &&
(device.name != null) &&
(device.name.indexOf('Sensor Tag') > -1 ||
device.name.indexOf('SensorTag') > -1)
}
/**
* Public. Connect to the nearest physical SensorTag device.
* @instance
* @public
* @deprecated Use evothings.tiseonsortag.ble.connectToNearestDevice
*/
instance.connectToClosestDevice = function()
{
return instance.connectToNearestDevice()
}
/**
* Public. Connect to the nearest physical SensorTag device.
* For this to work reliably SensorTags should be at least a
* couple of meters apart.
* @param scanTimeMilliseconds The time to scan for nearby
* SensorTags (optional, defaults to 3 seconds).
* @instance
* @public
*/
instance.connectToNearestDevice = function(scanTimeMilliseconds)
{
instance.callStatusCallback(sensortag.status.SCANNING)
instance.disconnectDevice()
evothings.easyble.stopScan()
evothings.easyble.reportDeviceOnce(false)
var nearestDevice = null
var strongestRSSI = -1000
var scanTimeoutStarted = false
var noTagFoundTimer = null
// Timer that connects to the nearest SensorTag efter the
// specified timeout.
function startConnectTimer()
{
// Set timeout period.
scanTimeMilliseconds = (('undefined' == typeof scanTimeMilliseconds)
? 1000 // Default scan time is 3 seconds
: scanTimeMilliseconds) // Use user-set value
// Start timer.
setTimeout(
function() {
if (nearestDevice)
{
evothings.easyble.stopScan()
instance.callStatusCallback(
sensortag.status.SENSORTAG_FOUND)
instance.connectToDevice(nearestDevice)
}
},
scanTimeMilliseconds)
}
// Timer that times out if no tag at all is found.
// Period is set to 10 seconds.
function startNoTagFoundTimer(device)
{
// Set scan timeout period.
var timeOut = 10000
// Start timer.
noTagFoundTimer = setTimeout(
function() {
if (!nearestDevice)
{
evothings.easyble.stopScan()
instance.callStatusCallback(
sensortag.status.SENSORTAG_NOT_FOUND)
}
},
timeOut)
}
function stopNoTagFoundTimer()
{
clearTimeout(noTagFoundTimer)
}
// Called when a device is found during scanning.
function deviceFound(device)
{
// Update the device if it is nearest so far
// and the RRSI value is valid. 127 is an invalid
// (unknown) RSSI value reported occasionally.
// deviceIsSensorTag is implemented in CC2541 and
// CC2650 object code.
if (device.rssi != 127 && instance.deviceIsSensorTag(device))
{
//console.log('deviceFound: ' + device.name)
if (device.rssi > strongestRSSI)
{
// If this is the first SensorTag found,
// start the timer that makes the connection.
if (!nearestDevice)
{
stopNoTagFoundTimer()
startConnectTimer()
}
nearestDevice = device
strongestRSSI = device.rssi
}
}
}
function scanError(errorCode)
{
instance.callErrorCallback(sensortag.error.SCAN_FAILED)
}
// Start timer that reports if no tag at all was found.
startNoTagFoundTimer()
// Start scanning.
evothings.easyble.startScan(deviceFound, scanError)
return instance
}
/**
* Start scanning for physical devices.
* @param foundCallback Function called when a SensorTag
* is found. It has the form foundCallback(device) where
* is a an object representing a BLE device object. You can
* inspect the device fields to determine properties such as
* RSSI, name etc. You can call deviceIsSensorTag(device) to
* determine if this is a SensorTag of the same type as the
* instance object.
* To connect to a SensorTag call connectToDevice(device).
* @instance
* @public
*/
instance.startScanningForDevices = function(foundCallback)
{
instance.callStatusCallback(sensortag.status.SCANNING)
instance.disconnectDevice()
evothings.easyble.stopScan()
evothings.easyble.reportDeviceOnce(false)
// Called when a device is found during scanning.
function deviceFound(device)
{
// 127 is an invalid (unknown) RSSI value reported occasionally.
if (device.rssi != 127)
{
foundCallback(device)
}
}
function scanError(errorCode)
{
instance.callErrorCallback(sensortag.error.SCAN_FAILED)
}
// Start scanning.
evothings.easyble.startScan(deviceFound, scanError)
return instance
}
/**
* Stop scanning for physical devices.
* @instance
* @public
*/
instance.stopScanningForDevices = function()
{
instance.callStatusCallback(sensortag.status.SENSORTAG_NOT_FOUND)
evothings.easyble.stopScan()
return instance
}
/**
* Connect to a SensorTag BLE device.
* @param device A Bluetooth Low Energy device object.
* @instance
* @public
*/
instance.connectToDevice = function(device)
{
instance.device = device
instance.callStatusCallback(sensortag.status.CONNECTING)
instance.device.connect(
function(device)
{
instance.callStatusCallback(sensortag.status.CONNECTED)
instance.readDeviceInfo()
},
instance.errorFun)
}
/**
* Public. Disconnect from the physical device.
* @instance
* @public
*/
instance.disconnectDevice = function()
{
if (instance.device)
{
instance.device.close()
instance.device = null
}
return instance
}
/**
* Internal. When connected we read device info and services.
* @instance
* @private
*/
instance.readDeviceInfo = function()
{
function readDeviceInfoService()
{
// Notify that status is reading device info.
instance.callStatusCallback(sensortag.status.READING_DEVICE_INFO)
// Read device information service.
instance.device.readServices(
[instance.DEVICEINFO_SERVICE],
gotDeviceInfoService,
instance.errorFun)
}
function gotDeviceInfoService(device)
{
// Reading of model is disabled. See comment below.
//readModelNumber()
readFirmwareVersion()
}
/*
Commented out unused code.
The value we get from the MODELNUMBER_DATA charaterictic
does not seem to be consistent.
We instead set model number in tisensortag-ble-cc2541.js
and tisensortag-ble-cc2650.js
function readModelNumber()
{
// Read model number.
instance.device.readCharacteristic(
instance.MODELNUMBER_DATA,
gotModelNumber,
instance.errorFun)
}
function gotModelNumber(data)
{
// Set model number.
var modelNumber = evothings.ble.fromUtf8(data)
console.log('devicemodel: ' + modelNumber)
if (-1 !== modelNumber.indexOf('CC2650'))
{
instance.deviceModel = 'CC2650'
}
else
{
instance.deviceModel = 'CC2541'
}
// Next read firmware version.
readFirmwareVersion()
}
*/
function readFirmwareVersion()
{
instance.device.readServiceCharacteristic(
instance.DEVICEINFO_SERVICE,
instance.FIRMWARE_DATA,
gotFirmwareVersion,
instance.errorFun)
}
function gotFirmwareVersion(data)
{
// Set firmware string.
var fw = evothings.ble.fromUtf8(data)
instance.firmwareString = fw.match(/\d+\.\d+\S?\b/g)[0] || ''
// Notify that device info is available.
instance.callStatusCallback(sensortag.status.DEVICE_INFO_AVAILABLE)
// Read services requested by the application.
readRequestedServices()
}
function readRequestedServices()
{
// Notify that status is reading services.
instance.callStatusCallback(sensortag.status.READING_SERVICES)
// Read services requested by the application.
instance.device.readServices(
instance.requiredServices,
instance.activateSensors,
instance.errorFun)
}
// Start by reading model number. Then read other
// data successively.
readDeviceInfoService()
}
/**
* Internal.
* @instance
* @private
*/
instance.activateSensors = function()
{
// Debug logging.
//console.log('-------------------- SERVICES --------------------')
//sensortag.logServices(instance.device)
//console.log('---------------------- END -----------------------')
// Call implementation method in sub module.
instance.activateSensorsImpl()
instance.callStatusCallback(sensortag.status.SENSORTAG_ONLINE)
}
/**
* Both CC2541 and CC2650.
* Public. Turn on IR temperature notification.
* @instance
* @public
*/
instance.temperatureOn = function()
{
instance.sensorOn(
instance.TEMPERATURE,
instance.temperatureConfig,
instance.temperatureInterval,
instance.temperatureFun
)
return instance
}
/**
* Both CC2541 and CC2650.
* Public. Turn off IR temperature notification.
* @instance
* @public
*/
instance.temperatureOff = function()
{
instance.sensorOff(instance.TEMPERATURE)
return instance
}
/**
* Both CC2541 and CC2650.
* Public. Turn on humidity notification.
* @instance
* @public
*/
instance.humidityOn = function()
{
instance.sensorOn(
instance.HUMIDITY,
instance.humidityConfig,
instance.humidityInterval,
instance.humidityFun
)
return instance
}
/**
* Both CC2541 and CC2650.
* Public. Turn off humidity notification.
* @instance
* @public
*/
instance.humidityOff = function()
{
instance.sensorOff(instance.HUMIDITY)
return instance
}
/**
* Public. Turn on barometer notification.
* @instance
* @public
*/
instance.barometerOn = function()
{
// Implemented in sub modules.
return instance
}
/**
* Both CC2541 and CC2650.
* Public. Turn off barometer notification.
* @instance
* @public
*/
instance.barometerOff = function()
{
instance.sensorOff(instance.BAROMETER)
return instance
}
/**
* Both CC2541 and CC2650.
* Public. Turn on keypress notification.
* @instance
* @public
*/
instance.keypressOn = function()
{
instance.sensorOn(
instance.KEYPRESS,
null, // Not used.
null, // Not used.
instance.keypressFun
)
return instance
}
/**
* Both CC2541 and CC2650.
* Public. Turn off keypress notification.
* @instance
* @public
*/
instance.keypressOff = function()
{
instance.sensorOff(instance.KEYPRESS)
return instance
}
/**
* Public. Used internally as a helper function for turning on
* sensor notification. You can call this function from the
* application to enable sensors using custom parameters.
* For advanced use.
* @instance
* @public
*/
instance.sensorOn = function(
service,
configValue,
periodValue,
notificationFunction)
{
// Only start sensor if a notification function has been set.
if (!notificationFunction) { return }
// Set sensor configuration to ON.
// If configValue is provided, service.CONFIG must be set.
configValue && instance.device.writeServiceCharacteristic(
service.SERVICE,
service.CONFIG,
new Uint8Array(configValue),
function() {},
instance.errorFun)
// Set sensor update period.
periodValue && instance.device.writeServiceCharacteristic(
service.SERVICE,
service.PERIOD,
new Uint8Array([periodValue / 10]),
function() {},
instance.errorFun)
// Set sensor notification to ON.
service.DATA && instance.device.writeServiceDescriptor(
service.SERVICE,
service.DATA,
instance.NOTIFICATION_DESCRIPTOR,
new Uint8Array([1,0]),
function() {
// Make sure value got written correctly.
// Also test readServiceDescriptor().
instance.device.readServiceDescriptor(service.SERVICE, service.DATA, instance.NOTIFICATION_DESCRIPTOR, function(data) {
//console.log('BLE descriptor data: ' + instance.dataToString(data))
}, function(errorCode)
{
console.log('BLE readServiceDescriptor error: ' + errorCode)
})
},
instance.errorFun)
// Start sensor notification.
service.DATA && instance.device.enableServiceNotification(
service.SERVICE,
service.DATA,
function(data) { notificationFunction(new Uint8Array(data)) },
instance.errorFun)
return instance
}
instance.dataToString = function(data)
{
var str = '['
data = new Uint8Array(data)
for(var i=0; i<data.length; i++) {
if(i > 0)
str += ','
str += data[i]
}
str += ']'
return str
}
/**
* Helper function for turning off sensor notification.
* @instance
* @public
*/
instance.sensorOff = function(service)
{
// TODO: Check that sensor notification function is set.
// Set sensor configuration to OFF
service.CONFIG && instance.device.writeServiceCharacteristic(
service.SERVICE,
service.CONFIG,
new Uint8Array([0]),
function() {},
instance.errorFun)
// Set sensor notification to OFF.
service.DATA && instance.device.writeServiceDescriptor(
service.SERVICE,
service.DATA,
instance.NOTIFICATION_DESCRIPTOR,
new Uint8Array([0,0]),
function() {
// Make sure value got written correctly.
// Also test readServiceDescriptor().
instance.device.readServiceDescriptor(service.SERVICE, service.DATA, instance.NOTIFICATION_DESCRIPTOR, function(data) {
//console.log('BLE descriptor data: ' + instance.dataToString(data))
}, function(errorCode)
{
console.log('BLE readServiceDescriptor error: ' + errorCode)
})
},
instance.errorFun)
service.DATA && instance.device.disableServiceNotification(
service.SERVICE,
service.DATA,
function() {
//console.log("disableServiceNotification success")
},
instance.errorFun)
return instance
}
/**
* Calculate humidity values from raw data.
* @param data - an Uint8Array.
* @return Object with fields: humidityTemperature, relativeHumidity.
* @instance
* @public
*/
instance.getHumidityValues = function(data)
{
// Calculate the humidity temperature (Celsius).
var tData = evothings.util.littleEndianToInt16(data, 0)
var tc = -46.85 + 175.72 / 65536.0 * tData
// Calculate the relative humidity.
var hData = (evothings.util.littleEndianToInt16(data, 2) & ~0x03)
var h = -6.0 + 125.00 / 65536.0 * hData
// Return result.
return { humidityTemperature: tc, relativeHumidity: h }
}
// Finally, return the SensorTag instance object.
return instance
}
})()