/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* global mui, bt_company_ids, ble, LocalFileSystem, capabilityManager, MANUFACTUREDECODER */ var app = { stop: false, log: {}, activeServices: {}, list: {}, foundDevices: {}, manufactureDecoder: new MANUFACTUREDECODER(), // Application Constructor initialize: function() { this.bindEvents(); }, arrayBufferToIntArray: function(buffer) { var result; if (buffer) { var typedArray = new Uint8Array(buffer); result = []; for (var i = 0; i < typedArray.length; i++) { result[i] = typedArray[i]; } } return result; }, parseAdvertisingData: function(bytes) { var length, type, data, i = 0, advertisementData = {}; while (length !== 0) { length = bytes[i] & 0xFF; i++; type = bytes[i] & 0xFF; i++; data = bytes.slice(i, i + length - 1); // Length includes type byte, but not length byte i += length - 2; // Move to end of data i++; advertisementData[type] = data; } return advertisementData; }, handle255: function(buffer) { 'use strict'; var company; var cid; var manID; var bin = buffer; var decoded = {}; console.log('Block255', bin); /* var manSpec = bin.map(function(i) { return i.toString(16) + ' '; }); console.log('manSpec: ', manSpec); */ manID = app.manufactureDecoder.getManID(bin); console.log('ManID:', manID); cid = '0x' + manID; company = bt_company_ids.find(cid); switch (manID) { case '004c': { decoded = app.manufactureDecoder.decodeIbeacon(bin); decoded.company = company; break; } case '1235': { decoded = app.manufactureDecoder.decodeSiliconLabsSensorPuck(bin); decoded.company = company; break; } case '0060': { decoded = app.manufactureDecoder.decodeSansible(bin); decoded.company = company; break; } default: { console.log('Unknown manID: ', manID); decoded = {company: company}; } } return decoded; }, makeHexBuffer: function(buffer) { 'use strict'; var hexBuffer = buffer.map(function(i) { return ('00' + i.toString(16)).slice(-2) + ','; }); return hexBuffer; }, makeChars: function(buffer) { 'use strict'; var output = buffer.map(function(i) { return String.fromCharCode(i); }); return output; }, buildNewDeviceResultPanel: function(device) { 'use strict'; var newPanel, newRow; var otherData = device.otherData; var newId = 'd-' + device.id.replace(/:/g, ''); var title = device.hasOwnProperty('name') ? device.name : '*** Unknown'; newPanel = $('
',{id: newId,class: 'mui-panel deviceRow', style: 'min-height:75px;'}); newRow = $('
',{class: 'mui-row'}); newRow.append($('
',{class: 'mui-col-xs-12 mui--text-title',text: device.id})); newPanel.append(newRow); newRow = $('
',{class: 'mui-row'}); newRow.append($('
',{class: 'mui-col-xs-3',text: 'Title:'})); newRow.append($('
',{class: 'mui-col-xs-3',text: title})); newRow.append($('
',{class: 'mui-col-xs-3',text: 'RSSI:'})); newRow.append($('
',{class: 'mui-col-xs-3',text: device.rssi})); newPanel.append(newRow); if (typeof otherData !== 'undefined' && otherData !== null) { if (otherData.hasOwnProperty('msg')) { newRow = $('
',{class: 'mui-row'}); newRow.append($('
',{class: 'mui-col-xs-3',text: 'Data:'})); newRow.append($('
',{class: 'mui-col-xs-8',text: otherData.msg})); newPanel.append(newRow); } } return newPanel; }, extractPData: function(prev) { 'use strict'; if (typeof prev === 'undefined' || prev === null) { return {}; } return prev.pData; }, processPData: function(newData, oldData) { 'use strict'; var output = {}; var wa = []; if (newData.data === null) { return {}; } for (var key in newData.data) { if (newData.data.hasOwnProperty(key)) { if (Object.keys(oldData).indexOf(key) !== -1) { wa = oldData[key]; } if (wa.length === 99) { wa = wa.slice(1); } wa.push(newData.data[key]); output[key] = wa; } } return output; }, doScan: function(mode) { 'use strict'; $('#ripple').show(); if (mode !== 2) { $('#tbody').empty(); } var otherData = null; var msgText = ''; ble.startScan([], function(device) { var parsed; var hexBuffer; var advertBuffer; var newTR; var newId; var _device = device; var $newID; _device.pData = {}; otherData = null; msgText = ''; newId = 'd-' + _device.id.replace(/:/g, ''); this.list[newId] = _device.id; if (_device.hasOwnProperty('advertising')) { advertBuffer = app.arrayBufferToIntArray(_device.advertising); hexBuffer = app.makeHexBuffer(advertBuffer); parsed = app.parseAdvertisingData(advertBuffer); // Console.log(parsed); if (parsed.hasOwnProperty('9')) { var name = app.makeChars(parsed['9']); _device.name = name.join(''); console.log('Name: ', name.join('')); } if (parsed.hasOwnProperty('255')) { otherData = app.handle255(parsed['255']); console.log(otherData); _device.otherData = otherData; } _device.advertBuffer = advertBuffer; _device.hexBuffer = hexBuffer; _device.parsed = parsed; } newTR = app.buildNewDeviceResultPanel(device); $newID = $('div#' + newId); if ($newID.length > 0) { $newID.replaceWith(newTR); } else { $('#scanResults').append(newTR); } // $('#output').append(JSON.stringify(device) + '
'); var oldPdata = app.extractPData(app.log[newId]); var newPData = app.processPData(otherData, oldPdata); // Console.log(newPData); _device.pData = newPData; app.log[newId] = _device; app.foundDevices[newId] = _device; // Console.log(JSON.stringify(_device)); }.bind(this), function(e) { console.error(e); }); var _t = [5000,60000,200][mode]; setTimeout(ble.stopScan, _t, function() { console.log('Scan complete'); if (mode === 1) { app.saveLog(); $('#ripple').hide(); } if (mode === 2) { if (!app.stop) { setTimeout(function() { app.doScan(2); }.bind(this), 200); } else { app.saveLog(); $('#ripple').hide(); } } }, function() { console.log('stopScan failed'); $('#ripple').hide(); }); }, writeFile: function(fileEntry, dataObj) { // Create a FileWriter object for our FileEntry (log.txt). fileEntry.createWriter(function(fileWriter) { fileWriter.onwriteend = function() { console.log('Successful file write...'); // ReadFile(fileEntry); }; fileWriter.onerror = function(e) { console.error('Failed file write: ' + e.toString()); }; // If data object is not passed in, // create a new Blob instead. if (!dataObj) { dataObj = new Blob(['some file data'], { type: 'text/plain' }); } fileWriter.write(dataObj); }); }, saveLog: function() { 'use strict'; var dt = new Date().toISOString().replace(/:|-/g,'').replace(/(\.\w+)/g,''); var payload = JSON.stringify(app.log); var filename = 'sensortoy-' + dt + '.json'; // Var dataObj = new Blob(payload, { type: 'text/plain' }); window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) { console.log('file system open: ' + fs.name); fs.root.getFile(filename, { create: true, exclusive: false }, function(fileEntry) { console.log('fileEntry is file?' + fileEntry.isFile.toString()); // FileEntry.name == 'someFile.txt' // fileEntry.fullPath == '/someFile.txt' console.log('Path: ', fileEntry.fullPath); app.writeFile(fileEntry, payload); app.log = []; }, app.onError); }, app.onError); }, forceStop: function() { 'use strict'; app.stop = true; $('#scan').show(); $('#stop').hide(); }, // Bind Event Listeners // // Bind any events that are required on startup. Common events are: // 'load', 'deviceready', 'offline', and 'online'. bindEvents: function() { var self = this; document.addEventListener('deviceready', this.onDeviceReady, false); $('#scan').on('click', function() { 'use strict'; this.stop = false; this.doScan(2); $('#scan').hide(); $('#stop').show(); }.bind(this)); $('#stop').on('click', function() { 'use strict'; app.forceStop(); }.bind(this)); $('#longScan').on('click', function() { 'use strict'; this.doScan(1); }.bind(this)); $('#scanResults').on('click', 'div.mui-panel.deviceRow', function() { 'use strict'; var tID = $(this).context.id; var id = self.list[tID]; console.log(tID, id); app.forceStop(); self.connect(id); }); }, addTab: function(tID) { var appTabs = $('#app-tabs'); var panes = $('#tab-panes'); var paneID = 'pane-' + tID; var _device = app.foundDevices[tID]; var _name = _device.name || _device.id; console.log('Found:', _device); $('
', {class: 'mui-tabs__pane', id: paneID}).appendTo(panes); var li = $('
  • ').append($('',{'data-mui-toggle': 'tab', 'data-mui-controls': paneID, text: _name })); appTabs.append(li); return paneID; }, // Deviceready Event Handler // // The scope of 'this' is the event. In order to call the 'receivedEvent' // function, we must explicitly call 'app.receivedEvent(...);' onDeviceReady: function() { }, doAnimate: function() { 'use strict'; // Console.log('Animate..'); for (var t = 0; t < app.activeServices.length;t++) { app.activeServices[t].animateGraph(); } window.requestAnimFrame(app.doAnimate.bind(this)); }, connect: function(deviceId) { $('#results').slideUp(); console.log('Connect to ', deviceId); var tID = 'd-' + deviceId.replace(/:/g, ''); var onConnect = function(a) { var services = []; services = a.services; console.log('Searching services for ', tID); var usedServices = []; var target = app.addTab(tID); var _params = { deviceID: deviceId, target: target }; for (var t = 0; t < services.length; t++) { var ident = services[t].toUpperCase(); var SERVICE = capabilityManager.discover(ident); if (SERVICE !== null) { var newService = new SERVICE(_params); newService.startService(); usedServices.push(newService); } else { console.error('Unknown service: ', ident); } } app.activeServices[tID] = usedServices; mui.tabs.activate(target); window.requestAnimFrame(app.doAnimate.bind(this)); }; if (!app.activeServices.hasOwnProperty(tID)) { ble.connect(deviceId, onConnect, function(e) { 'use strict'; console.log(e); console.error(e); }); } }, onError: function(reason) { console.error('ERROR: ' + reason); // Real apps should use notification.alert }, updateGyro: function(g) { 'use strict'; } }; window.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })(); app.initialize();