/** * * User: Martin Donnelly * Date: 2016-05-24 * Time: 14:21 * */ var MANUFACTUREDECODER = function() { 'use strict'; this.getManID = function(data) { return ('0000' + ((data[1] << 8) | data[0]).toString(16)).slice(-4).toUpperCase(); }, this.decodeIbeacon = function(data) { // Not decoding anything yet. // https://support.kontakt.io/hc/en-gb/articles/201492492-iBeacon-advertising-packet-structure var bin = data; var obj = { msg: '(iBeacon)'}; // obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4); obj.manID = this.getManID(bin); var uuid = []; uuid.push(bin[4].toString(16) + bin[5].toString(16) + bin[6].toString(16) + bin[7].toString(16)) ; uuid.push(bin[8].toString(16) + bin[9].toString(16)) ; uuid.push(bin[10].toString(16) + bin[11].toString(16)) ; uuid.push(bin[12].toString(16) + bin[13].toString(16)) ; uuid.push(bin[14].toString(16) + bin[15].toString(16) + bin[16].toString(16) + bin[17].toString(16) + bin[18].toString(16) + bin[19].toString(16)) ; obj.uuid = uuid.join('-'); return obj; }; this.decodeSiliconLabsSensorPuck = function(data) { var bin = data; var obj = {data:{}}; //obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4); obj.manID = this.getManID(bin); obj.a = (bin[3] << 8) | bin[2]; obj.b = (bin[5] << 8) | bin[4]; obj.data.humidity = (bin[7] << 8) | bin[6]; obj.data.temp = (bin[9] << 8) | bin[8]; obj.c = (bin[11] << 8) | bin[10]; obj.d = (bin[13] << 8) | bin[12]; obj.msg = 'Humidity: ' + (obj.data.humidity / 10) + ', temp: ' + (obj.data.temp / 10); return obj; }; this.decodeSansible = function(data) { var bin = data; var obj = {data:{}}; // obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4); obj.manID = this.getManID(bin); obj.data.p1 = ((bin[2] << 16) | bin[3] << 8 | bin[4]); obj.data.p2 = ((bin[5] << 16) | bin[6] << 8 | bin[7]); obj.msg = 'Left: ' + (obj.data.p1 / 100) + ' hPa, Right: ' + (obj.data.p2 / 100) + ' hPa'; return obj; }; }; /* * 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 = {}; void 0; /* var manSpec = bin.map(function(i) { return i.toString(16) + ' '; }); console.log('manSpec: ', manSpec); */ manID = app.manufactureDecoder.getManID(bin); void 0; 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: { void 0; 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(''); void 0; } if (parsed.hasOwnProperty('255')) { otherData = app.handle255(parsed['255']); void 0; _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) { void 0; }); var _t = [5000,60000,200][mode]; setTimeout(ble.stopScan, _t, function() { void 0; 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() { void 0; $('#ripple').hide(); }); }, writeFile: function(fileEntry, dataObj) { // Create a FileWriter object for our FileEntry (log.txt). fileEntry.createWriter(function(fileWriter) { fileWriter.onwriteend = function() { void 0; // ReadFile(fileEntry); }; fileWriter.onerror = function(e) { void 0; }; // 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) { void 0; fs.root.getFile(filename, { create: true, exclusive: false }, function(fileEntry) { void 0; // FileEntry.name == 'someFile.txt' // fileEntry.fullPath == '/someFile.txt' void 0; 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]; void 0; 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; void 0; $('
', {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(); void 0; var tID = 'd-' + deviceId.replace(/:/g, ''); var onConnect = function(a) { var services = []; services = a.services; void 0; 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 { void 0; } } 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'; void 0; void 0; }); } }, onError: function(reason) { void 0; // 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();