diff --git a/config.xml b/config.xml index 16cac18..ade9ca1 100644 --- a/config.xml +++ b/config.xml @@ -26,4 +26,6 @@ + + diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..e69de29 diff --git a/notes/notes.md b/notes/notes.md index 154245a..f50ce14 100644 --- a/notes/notes.md +++ b/notes/notes.md @@ -18,3 +18,6 @@ Silicon Labs Sensor Puck google science journal + + +[https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-file/](Cordova file plugin) diff --git a/platforms/android/.gradle/2.2.1/taskArtifacts/cache.properties.lock b/platforms/android/.gradle/2.2.1/taskArtifacts/cache.properties.lock index d54e870..29c55c8 100644 Binary files a/platforms/android/.gradle/2.2.1/taskArtifacts/cache.properties.lock and b/platforms/android/.gradle/2.2.1/taskArtifacts/cache.properties.lock differ diff --git a/platforms/android/.gradle/2.2.1/taskArtifacts/fileHashes.bin b/platforms/android/.gradle/2.2.1/taskArtifacts/fileHashes.bin index 3946df2..d4ff90c 100644 Binary files a/platforms/android/.gradle/2.2.1/taskArtifacts/fileHashes.bin and b/platforms/android/.gradle/2.2.1/taskArtifacts/fileHashes.bin differ diff --git a/platforms/android/.gradle/2.2.1/taskArtifacts/fileSnapshots.bin b/platforms/android/.gradle/2.2.1/taskArtifacts/fileSnapshots.bin index bb3c507..280f843 100644 Binary files a/platforms/android/.gradle/2.2.1/taskArtifacts/fileSnapshots.bin and b/platforms/android/.gradle/2.2.1/taskArtifacts/fileSnapshots.bin differ diff --git a/platforms/android/.gradle/2.2.1/taskArtifacts/taskArtifacts.bin b/platforms/android/.gradle/2.2.1/taskArtifacts/taskArtifacts.bin index e29395c..3eaa6dd 100644 Binary files a/platforms/android/.gradle/2.2.1/taskArtifacts/taskArtifacts.bin and b/platforms/android/.gradle/2.2.1/taskArtifacts/taskArtifacts.bin differ diff --git a/platforms/android/AndroidManifest.xml b/platforms/android/AndroidManifest.xml index ae8c96d..a0214dd 100644 --- a/platforms/android/AndroidManifest.xml +++ b/platforms/android/AndroidManifest.xml @@ -14,4 +14,5 @@ + diff --git a/platforms/android/android.json b/platforms/android/android.json index d9911be..0591744 100644 --- a/platforms/android/android.json +++ b/platforms/android/android.json @@ -11,6 +11,10 @@ { "xml": "", "count": 1 + }, + { + "xml": "", + "count": 1 } ], "/widget": [ @@ -36,6 +40,12 @@ "xml": "", "count": 1 } + ], + "/*": [ + { + "xml": "", + "count": 1 + } ] } } @@ -50,6 +60,9 @@ }, "cordova-plugin-ble-central": { "PACKAGE_NAME": "org.censis.sensortoy" + }, + "cordova-plugin-file": { + "PACKAGE_NAME": "org.censis.sensortoy" } }, "dependent_plugins": {}, @@ -60,11 +73,163 @@ "clobbers": [ "ble" ] + }, + { + "file": "plugins/cordova-plugin-file/www/DirectoryEntry.js", + "id": "cordova-plugin-file.DirectoryEntry", + "clobbers": [ + "window.DirectoryEntry" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/DirectoryReader.js", + "id": "cordova-plugin-file.DirectoryReader", + "clobbers": [ + "window.DirectoryReader" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/Entry.js", + "id": "cordova-plugin-file.Entry", + "clobbers": [ + "window.Entry" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/File.js", + "id": "cordova-plugin-file.File", + "clobbers": [ + "window.File" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileEntry.js", + "id": "cordova-plugin-file.FileEntry", + "clobbers": [ + "window.FileEntry" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileError.js", + "id": "cordova-plugin-file.FileError", + "clobbers": [ + "window.FileError" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileReader.js", + "id": "cordova-plugin-file.FileReader", + "clobbers": [ + "window.FileReader" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileSystem.js", + "id": "cordova-plugin-file.FileSystem", + "clobbers": [ + "window.FileSystem" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileUploadOptions.js", + "id": "cordova-plugin-file.FileUploadOptions", + "clobbers": [ + "window.FileUploadOptions" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileUploadResult.js", + "id": "cordova-plugin-file.FileUploadResult", + "clobbers": [ + "window.FileUploadResult" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileWriter.js", + "id": "cordova-plugin-file.FileWriter", + "clobbers": [ + "window.FileWriter" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/Flags.js", + "id": "cordova-plugin-file.Flags", + "clobbers": [ + "window.Flags" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/LocalFileSystem.js", + "id": "cordova-plugin-file.LocalFileSystem", + "clobbers": [ + "window.LocalFileSystem" + ], + "merges": [ + "window" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/Metadata.js", + "id": "cordova-plugin-file.Metadata", + "clobbers": [ + "window.Metadata" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/ProgressEvent.js", + "id": "cordova-plugin-file.ProgressEvent", + "clobbers": [ + "window.ProgressEvent" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/fileSystems.js", + "id": "cordova-plugin-file.fileSystems" + }, + { + "file": "plugins/cordova-plugin-file/www/requestFileSystem.js", + "id": "cordova-plugin-file.requestFileSystem", + "clobbers": [ + "window.requestFileSystem" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js", + "id": "cordova-plugin-file.resolveLocalFileSystemURI", + "merges": [ + "window" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/browser/isChrome.js", + "id": "cordova-plugin-file.isChrome", + "runs": true + }, + { + "file": "plugins/cordova-plugin-file/www/android/FileSystem.js", + "id": "cordova-plugin-file.androidFileSystem", + "merges": [ + "FileSystem" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/fileSystems-roots.js", + "id": "cordova-plugin-file.fileSystems-roots", + "runs": true + }, + { + "file": "plugins/cordova-plugin-file/www/fileSystemPaths.js", + "id": "cordova-plugin-file.fileSystemPaths", + "merges": [ + "cordova" + ], + "runs": true } ], "plugin_metadata": { "cordova-plugin-whitelist": "1.2.2", "cordova-plugin-compat": "1.0.0", - "cordova-plugin-ble-central": "1.1.0" + "cordova-plugin-ble-central": "1.1.0", + "cordova-plugin-file": "4.2.0" } } \ No newline at end of file diff --git a/platforms/android/assets/www/cordova_plugins.js b/platforms/android/assets/www/cordova_plugins.js index 19691c0..dc8b27b 100644 --- a/platforms/android/assets/www/cordova_plugins.js +++ b/platforms/android/assets/www/cordova_plugins.js @@ -6,6 +6,157 @@ module.exports = [ "clobbers": [ "ble" ] + }, + { + "file": "plugins/cordova-plugin-file/www/DirectoryEntry.js", + "id": "cordova-plugin-file.DirectoryEntry", + "clobbers": [ + "window.DirectoryEntry" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/DirectoryReader.js", + "id": "cordova-plugin-file.DirectoryReader", + "clobbers": [ + "window.DirectoryReader" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/Entry.js", + "id": "cordova-plugin-file.Entry", + "clobbers": [ + "window.Entry" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/File.js", + "id": "cordova-plugin-file.File", + "clobbers": [ + "window.File" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileEntry.js", + "id": "cordova-plugin-file.FileEntry", + "clobbers": [ + "window.FileEntry" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileError.js", + "id": "cordova-plugin-file.FileError", + "clobbers": [ + "window.FileError" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileReader.js", + "id": "cordova-plugin-file.FileReader", + "clobbers": [ + "window.FileReader" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileSystem.js", + "id": "cordova-plugin-file.FileSystem", + "clobbers": [ + "window.FileSystem" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileUploadOptions.js", + "id": "cordova-plugin-file.FileUploadOptions", + "clobbers": [ + "window.FileUploadOptions" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileUploadResult.js", + "id": "cordova-plugin-file.FileUploadResult", + "clobbers": [ + "window.FileUploadResult" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/FileWriter.js", + "id": "cordova-plugin-file.FileWriter", + "clobbers": [ + "window.FileWriter" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/Flags.js", + "id": "cordova-plugin-file.Flags", + "clobbers": [ + "window.Flags" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/LocalFileSystem.js", + "id": "cordova-plugin-file.LocalFileSystem", + "clobbers": [ + "window.LocalFileSystem" + ], + "merges": [ + "window" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/Metadata.js", + "id": "cordova-plugin-file.Metadata", + "clobbers": [ + "window.Metadata" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/ProgressEvent.js", + "id": "cordova-plugin-file.ProgressEvent", + "clobbers": [ + "window.ProgressEvent" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/fileSystems.js", + "id": "cordova-plugin-file.fileSystems" + }, + { + "file": "plugins/cordova-plugin-file/www/requestFileSystem.js", + "id": "cordova-plugin-file.requestFileSystem", + "clobbers": [ + "window.requestFileSystem" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js", + "id": "cordova-plugin-file.resolveLocalFileSystemURI", + "merges": [ + "window" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/browser/isChrome.js", + "id": "cordova-plugin-file.isChrome", + "runs": true + }, + { + "file": "plugins/cordova-plugin-file/www/android/FileSystem.js", + "id": "cordova-plugin-file.androidFileSystem", + "merges": [ + "FileSystem" + ] + }, + { + "file": "plugins/cordova-plugin-file/www/fileSystems-roots.js", + "id": "cordova-plugin-file.fileSystems-roots", + "runs": true + }, + { + "file": "plugins/cordova-plugin-file/www/fileSystemPaths.js", + "id": "cordova-plugin-file.fileSystemPaths", + "merges": [ + "cordova" + ], + "runs": true } ]; module.exports.metadata = @@ -13,7 +164,8 @@ module.exports.metadata = { "cordova-plugin-whitelist": "1.2.2", "cordova-plugin-compat": "1.0.0", - "cordova-plugin-ble-central": "1.1.0" + "cordova-plugin-ble-central": "1.1.0", + "cordova-plugin-file": "4.2.0" }; // BOTTOM OF METADATA }); \ No newline at end of file diff --git a/platforms/android/assets/www/css/app.css b/platforms/android/assets/www/css/app.css index 35061bc..190097b 100644 --- a/platforms/android/assets/www/css/app.css +++ b/platforms/android/assets/www/css/app.css @@ -278,3 +278,30 @@ projector: cast -webkit-line-clamp: 1; -webkit-box-orient: vertical; } + +.pulser { + display: block; + margin-top:10%; + border-radius: 100px; + width: 30px; + height: 30px; + border: 10px solid #C5F4EB; + -webkit-animation: pulse 0.75s ease-in infinite; + -moz-animation: pulse 0.75s ease-in infinite; + animation: pulse 0.75s ease-in infinite; +} +@-webkit-keyframes pulse { + 0% { -webkit-transform: scale(0); } + 85% { opacity: 1; } + 100% { -webkit-transform: scale(1); -webkit-filter: blur(5px); opacity: 0; } +} +@-moz-keyframes pulse { + 0% { -moz-transform: scale(0); } + 85% { opacity: 1; } + 100% { -moz-transform: scale(1); -moz-filter: blur(5px); opacity: 0; } +} +@keyframes pulse { + 0% { transform: scale(0); } + 85% { opacity: 1; } + 100% { transform: scale(1); filter: blur(5px); opacity: 0; } +} diff --git a/platforms/android/assets/www/css/mui.custom.css b/platforms/android/assets/www/css/mui.custom.css index cfe1de1..79597a4 100644 --- a/platforms/android/assets/www/css/mui.custom.css +++ b/platforms/android/assets/www/css/mui.custom.css @@ -1331,7 +1331,7 @@ th { text-transform: uppercase; font-weight: 500; font-size: 14px; - color: rgba(0, 0, 0, 0.87); + color: rgba(255, 255, 255, 0.87); cursor: default; height: 48px; line-height: 48px; diff --git a/platforms/android/assets/www/index.html b/platforms/android/assets/www/index.html index e3c90a7..058c17c 100644 --- a/platforms/android/assets/www/index.html +++ b/platforms/android/assets/www/index.html @@ -41,16 +41,36 @@ rel="stylesheet"> - + + + + Sensor Toy +
+ +
+
- +
+
+ + +
+ +
+ +
+
+
+ @@ -74,17 +94,21 @@ + + + - + + diff --git a/platforms/android/assets/www/js/device/CC2650/cc2650_accelerometer.js b/platforms/android/assets/www/js/device/CC2650/cc2650_accelerometer.js index 65d2ca2..8a0648a 100644 --- a/platforms/android/assets/www/js/device/CC2650/cc2650_accelerometer.js +++ b/platforms/android/assets/www/js/device/CC2650/cc2650_accelerometer.js @@ -37,7 +37,7 @@ var CC2650_ACCEL = function(deviceId) { }; this.onAccelerometerData = function(data) { - console.log(data); + // Console.log(data); var message; var a = new Int16Array(data); @@ -68,19 +68,19 @@ var CC2650_ACCEL = function(deviceId) { this.state = message; - this.$result[this.frames.gyroID + '-x'].text(this.sensorMpu9250GyroConvert(a[0]).toFixed(5)); - this.$result[this.frames.gyroID + '-y'].text(this.sensorMpu9250GyroConvert(a[1]).toFixed(5)); - this.$result[this.frames.gyroID + '-z'].text(this.sensorMpu9250GyroConvert(a[2]).toFixed(5)); + this.$result[this.frames.gyroID + '-x'].text(this.sensorMpu9250GyroConvert(a[0]).toFixed(5)); + this.$result[this.frames.gyroID + '-y'].text(this.sensorMpu9250GyroConvert(a[1]).toFixed(5)); + this.$result[this.frames.gyroID + '-z'].text(this.sensorMpu9250GyroConvert(a[2]).toFixed(5)); - this.$result[this.frames.accelID + '-x'].text(this.sensorMpu9250AccConvert(a[3]).toFixed(5)); - this.$result[this.frames.accelID + '-y'].text(this.sensorMpu9250AccConvert(a[4]).toFixed(5)); - this.$result[this.frames.accelID + '-z'].text(this.sensorMpu9250AccConvert(a[5]).toFixed(5)); + this.$result[this.frames.accelID + '-x'].text(this.sensorMpu9250AccConvert(a[3]).toFixed(5)); + this.$result[this.frames.accelID + '-y'].text(this.sensorMpu9250AccConvert(a[4]).toFixed(5)); + this.$result[this.frames.accelID + '-z'].text(this.sensorMpu9250AccConvert(a[5]).toFixed(5)); - this.$result[this.frames.magID + '-x'].text(a[3]); - this.$result[this.frames.magID + '-y'].text(a[4]); - this.$result[this.frames.magID + '-z'].text(a[5]); + this.$result[this.frames.magID + '-x'].text(a[3]); + this.$result[this.frames.magID + '-y'].text(a[4]); + this.$result[this.frames.magID + '-z'].text(a[5]); - console.log(this.state); + // Console.log(this.state); }; this.startService = function() { @@ -93,8 +93,6 @@ var CC2650_ACCEL = function(deviceId) { ble.startNotification(this.deviceID, this.serviceDef.service, this.serviceDef.data, this.onAccelerometerData.bind(this), this.onError); - - // Turn accelerometer on var configData = new Uint16Array(1); // Turn on gyro, accel, and mag, 2G range, Disable wake on motion @@ -115,6 +113,11 @@ var CC2650_ACCEL = function(deviceId) { }; + this.animateGraph = function() { + // Nothing to animate yet + + return -1; + }; this.insertFrame = function(mode) { diff --git a/platforms/android/assets/www/js/device/CC2650/cc2650_barometer.js b/platforms/android/assets/www/js/device/CC2650/cc2650_barometer.js index 6123dde..dc02554 100644 --- a/platforms/android/assets/www/js/device/CC2650/cc2650_barometer.js +++ b/platforms/android/assets/www/js/device/CC2650/cc2650_barometer.js @@ -22,11 +22,9 @@ var CC2650_BAR = function(deviceId) { period: 'F000AA43-0451-4000-B000-000000000000' }; - + this.data = {temp: [], pressure: []}; this.$result = {temp: null, pressure: null}; - - this.startService = function() { 'use strict'; if (this.deviceID !== null) { @@ -43,7 +41,6 @@ var CC2650_BAR = function(deviceId) { ble.write(this.deviceID, this.serviceDef.service, this.serviceDef.configuration, barometerConfig.buffer, function() { console.log('Started barometer.'); },this.onError); - } }; @@ -56,30 +53,47 @@ var CC2650_BAR = function(deviceId) { this.onBarometerData = function(data) { var pStr; var tStr; - console.log(data); + // Console.log(data); var message; var a = new Uint8Array(data); //0-2 Temp //3-5 Pressure - tStr = this.sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)) + '°C'; - pStr = this.sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)) + 'hPa'; + var temp, pressure; + temp = this.sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)); + pressure = this.sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)); + tStr = temp + '°C'; + pStr = pressure + 'hPa'; message = 'Temperature
' + tStr + 'Pressure
' + pStr ; + // This.data.temp = this.storeData(parseInt(temp), this.data.temp); + // this.data.pressure = this.storeData(parseInt(pressure), this.data.pressure); + + this.data.temp = this.storeData(temp, this.data.temp); + this.data.pressure = this.storeData(pressure, this.data.pressure); + + this.$result.temp.text(tStr); this.$result.pressure.text(pStr); this.state = message; - console.log('Barometer:', this.state); + // Console.log('Barometer:', this.state); }; + this.animateGraph = function() { + this.simpleGraph(this.data.temp, 'temp'); + this.simpleGraph(this.data.pressure, 'pressure'); + }; + + this.insertFrame = function() { var self = this; - console.log('Overloading...'); + var blankChart; + // Call the parent displayForm first... this.superClass_.insertFrame.call(self); @@ -95,6 +109,24 @@ var CC2650_BAR = function(deviceId) { $('
', { class: 'mui-col-xs-3 mui--text-white', id: pressure}).appendTo(row); this.$id.append(row); + + var tabBody = $('
'); + newTR = $(''); - newTR.append($(''); + var newTR = $(''); - newTR.append($('
').text(device.id)); + newTR.append($('').text(device.id)); - if (device.hasOwnProperty('name')) { - newTR.append($('').text(device.name)); - } else { - newTR.append($('').text('*** Unknown')); - } + if (device.hasOwnProperty('advertising')) { - newTR.append($('').text(device.rssi)); + advertBuffer = app.arrayBufferToIntArray(device.advertising); + /*var hexBuffer = advertBuffer.map(function(i) { + return ('00' + i.toString(16)).slice(-2) + ','; + });*/ - $('#tbody').append(newTR); + hexBuffer = app.makeHexBuffer(advertBuffer); - $('#output').append(JSON.stringify(device) + '
'); + parsed = app.parseAdvertisingData(advertBuffer); - }.bind(this), function(e) { - 'use strict'; - console.error(e); - }); + //Console.log(parsed); + + if (parsed.hasOwnProperty('9')) { + + /*var name = parsed['9'].map(function(i) { + + return String.fromCharCode(i); + });*/ + + 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; + } + + if (typeof otherData !== 'undefined' && otherData !== null) { + if (otherData.hasOwnProperty('msg')) { + msgText = ' - ' + otherData.msg; + } + } + + if (device.hasOwnProperty('name')) { + newTR.append($('
').text(device.name + msgText)); + } else { + newTR.append($('').text('*** Unknown' + msgText)); + } + + newTR.append($('').text(device.rssi)); + + + if ($('tr#' + newId).length > 0) { + $('tr#' + newId).replaceWith(newTR); + } else { + $('#tbody').append(newTR); + } + + + //$('#output').append(JSON.stringify(device) + '
'); + + app.log[newId] = _device; + + console.log(JSON.stringify(_device)); + + }.bind(this), function(e) { + 'use strict'; + console.error(e); + }); + + var _t = [5000,60000,200][mode]; setTimeout(ble.stopScan, - 5000, - function() { console.log('Scan complete'); }, - function() { console.log('stopScan failed'); } - ); + _t, + function() { console.log('Scan complete'); + + if (mode === 1) { + app.saveLog(); + $('#ripple').hide(); + } + + if (mode === 2) { + if (app.stop == false) { + 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.doScan(); - }.bind(this)); + 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)); - $('#tbody').on('click','tr', function() { - 'use strict'; - var tID = $(this).context.id; + $('#longScan').on('click', function() { + 'use strict'; + this.doScan(1); + }.bind(this)); - var id = self.list[tID]; + $('#tbody').on('click', 'tr', function() { + 'use strict'; + var tID = $(this).context.id; - console.log(tID, id); + var id = self.list[tID]; - self.connect(id); - }); + console.log(tID, id); + app.forceStop(); + self.connect(id); + }); - }, - // Deviceready Event Handler + }, // 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() { - - }, serviceDiscovery: function(services) { 'use strict'; console.log(services); + }, sensorMpu9250GyroConvert: function(data) { + return data / (65536 / 500); }, - sensorMpu9250GyroConvert: function(data) { - return data / (65536 / 500); - }, sensorMpu9250AccConvert: function(data) { - // Change /2 to match accel range...i.e. 16 g would be /16 - return data / (32768 / 2); + // Change /2 to match accel range...i.e. 16 g would be /16 + return data / (32768 / 2); + }, + + doAnimate: function() { + 'use strict'; + // Console.log('Animate..'); + for (var t = 0; t < app.activeServices.length;t++) { + app.activeServices[t].animateGraph(); } - ,connect: function(deviceId) { + window.requestAnimFrame(app.doAnimate.bind(this)); + } - $('#results').slideUp(); - console.log('Connect to ', deviceId); + , connect: function(deviceId) { - var onConnect = function(a) { - var services = []; + $('#results').slideUp(); + console.log('Connect to ', deviceId); - 'use strict'; + var onConnect = function(a) { + var services = []; - console.log('A:', a); + 'use strict'; - services = a.services; + console.log('A:', a); - for (var t = 0; t < services.length;t++) { + services = a.services; - var ident = services[t].toUpperCase(); + for (var t = 0; t < services.length; t++) { - switch (ident) { - case '180F': - var batteryStat = new BATTERY(deviceId); - batteryStat.startService(); - batteryStat.readBatteryState(); - app.activeServices.push(batteryStat); + var ident = services[t].toUpperCase(); - break; - case 'FFE0': - var buttonState = new BUTTON(deviceId); - buttonState.startService(); - app.activeServices.push(buttonState); - break; + switch (ident) { + case '180F': + var batteryStat = new BATTERY(deviceId); + batteryStat.startService(); + // batteryStat.readBatteryState(); + app.activeServices.push(batteryStat); - case 'F000AA80-0451-4000-B000-000000000000': - var cc2650_accel = new CC2650_ACCEL(deviceId); - cc2650_accel.startService(); - app.activeServices.push(cc2650_accel); - break; + break; + case 'FFE0': + var buttonState = new BUTTON(deviceId); + buttonState.startService(); + app.activeServices.push(buttonState); + break; - case 'F000AA40-0451-4000-B000-000000000000': - var cc2650_bar = new CC2650_BAR(deviceId); - cc2650_bar.startService(); - app.activeServices.push(cc2650_bar); - break; + case 'F000AA80-0451-4000-B000-000000000000': + var cc2650_accel = new CC2650_ACCEL(deviceId); + cc2650_accel.startService(); + app.activeServices.push(cc2650_accel); + break; - case 'F000AA70-0451-4000-B000-000000000000': - var cc2650_lux = new CC2650_LUX(deviceId); - cc2650_lux.startService(); - app.activeServices.push(cc2650_lux); - break; + case 'F000AA40-0451-4000-B000-000000000000': + var cc2650_bar = new CC2650_BAR(deviceId); + cc2650_bar.startService(); + app.activeServices.push(cc2650_bar); + break; - default: - console.log('Unknown service: ', ident); - } - } + case 'F000AA70-0451-4000-B000-000000000000': + var cc2650_lux = new CC2650_LUX(deviceId); + cc2650_lux.startService(); + app.activeServices.push(cc2650_lux); + break; + + case 'F000AA00-0451-4000-B000-000000000000': + var cc2650_tmp = new CC2650_TMP(deviceId); + cc2650_tmp.startService(); + app.activeServices.push(cc2650_tmp); + break; - }; + case 'F000AA20-0451-4000-B000-000000000000': + var cc2650_hum = new CC2650_HUM(deviceId); + cc2650_hum.startService(); + app.activeServices.push(cc2650_hum); + break; - ble.connect(deviceId, onConnect, function(e) { - 'use strict'; - console.error(e); - }); + default: + console.error('Unknown service: ', ident); + } + } - }, - onError: function(reason) { - console.error('ERROR: ' + reason); // Real apps should use notification.alert - }, - updateGyro: function(g) { + // Starting animation.. + window.requestAnimFrame(app.doAnimate.bind(this)); + }; + + ble.connect(deviceId, onConnect, function(e) { + 'use strict'; + 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(); diff --git a/platforms/android/assets/www/js/standards/capability.js b/platforms/android/assets/www/js/standards/capability.js index a1914d7..49147fd 100644 --- a/platforms/android/assets/www/js/standards/capability.js +++ b/platforms/android/assets/www/js/standards/capability.js @@ -18,6 +18,10 @@ var CAPABILITY = function(p) { this.deviceID = null; + this.data = []; + this.ctx = null; + this.first = false; + }; CAPABILITY.prototype.setInternalID = function() { @@ -64,7 +68,174 @@ CAPABILITY.prototype.onError = function(e) { console.error(e); }; +CAPABILITY.prototype.storeData = function(data, alt) { + if (!this.first) { + this.first = true; + return []; + } + var target = alt || this.data; + + if (target.length === 99) { + target = target.slice(1); + } + + target.push(data); + if (alt) { + return target; + } else { + this.data = target; + } + + +}; + +CAPABILITY.prototype.startGraph = function(id) { + + var c; + c = id[0].getContext('2d'); + this.ctx = c; + + this.ctx.fillStyle = '#ffffff'; + this.ctx.fillRect(0,0,300,150); +}; + +CAPABILITY.prototype.generateBlankGraph = function(subID) { + + var _subID = subID || ''; + var xmlns = 'http://www.w3.org/2000/svg'; + + var svgID = this.frameID + _subID + '-svg'; + var text1ID = this.frameID + _subID + '-txt1'; + var lineID = this.frameID + _subID + '-line'; + + var svg = document.createElementNS(xmlns,'svg'); + + svg.setAttributeNS(xmlns,'id',svgID); + svg.setAttributeNS(xmlns,'width',300); + svg.setAttributeNS(xmlns,'height',150); + svg.setAttributeNS(xmlns,'fill', 'blue'); + + var line = document.createElementNS(xmlns,'line'); + + line.setAttributeNS(null,'x1','46'); + line.setAttributeNS(null,'y1','12'); + line.setAttributeNS(null,'x2','280'); + line.setAttributeNS(null,'y2', '12'); + line.setAttributeNS(null,'style','stroke:#bad649;stroke-width:2;'); + + svg.appendChild(line); + + line = document.createElementNS(xmlns,'line'); + + line.setAttributeNS(null,'x1','46'); + line.setAttributeNS(null,'y1','136'); + line.setAttributeNS(null,'x2','280'); + line.setAttributeNS(null,'y2', '136'); + line.setAttributeNS(null,'style','stroke:#bad649;stroke-width:2;'); + + svg.appendChild(line); + + var text = document.createElementNS(xmlns,'text'); + + text.setAttributeNS(null,'id',text1ID); + text.setAttributeNS(null,'x','36'); + text.setAttributeNS(null,'y','15'); + text.setAttributeNS(null,'text-anchor', 'end'); + text.setAttributeNS(null,'style','font-family:"Ubuntu Condensed",sans-serif;font-size:12;fill: #bad649;text-align:right;'); + text.textContent = '000'; + + svg.appendChild(text); + + text = document.createElementNS(xmlns,'text'); + + text.setAttributeNS(null,'id','text2'); + text.setAttributeNS(null,'x','36'); + text.setAttributeNS(null,'y','140'); + text.setAttributeNS(null,'text-anchor', 'end'); + text.setAttributeNS(null,'style','font-family:"Ubuntu Condensed",sans-serif;font-size:12;fill: #bad649;text-align:right;'); + text.textContent = '0'; + + svg.appendChild(text); + + var polyline = document.createElementNS(xmlns,'polyline'); + + polyline.setAttributeNS(null,'id',lineID); + polyline.setAttributeNS(null,'fill','none'); + polyline.setAttributeNS(null,'stroke','#e5f7fd'); + //#e5f7fd + // old #B5C7FF + polyline.setAttributeNS(null,'text-anchor', 'end'); + polyline.setAttributeNS(null,'stroke-width','2'); + + svg.appendChild(polyline); + + return svg; + +}; + +CAPABILITY.prototype.animateGraph = function() { + //This.simpleGraph(this.data); +}; + + +CAPABILITY.prototype.simpleGraph = function(data, subID) { + + var _subID; + var _data; + var text1ID; + var lineID; + + _data = data || this.data; + + _subID = subID || ''; + + lineID = [this.frameID , _subID , '-line'].join(''); + text1ID = [this.frameID , _subID , '-txt1'].join(''); + + if (_data.length > 0) { + var ceiling = _data.reduce(function(p, v) { + return (p > v ? p : v); + }); + + /* Var floor = _data.reduce(function(p, v) { + return (p < v ? p : v); + }); + */ + + var calcArray = []; + + var ceilingLimit = Math.floor(ceiling / 10) * 10; + if (ceilingLimit < ceiling) { + ceilingLimit = Math.floor((ceiling + (ceiling * 0.25)) / 10) * 10; + } + + if (ceilingLimit < 30) { + ceilingLimit = 30; + } + + var scale = 124 / ceilingLimit; + // Var xstep = (280 - 46) / 100; + var xstep = 2.34; + var startX = 46 + (100 - _data.length) * xstep; + + for (var x = 0;x < _data.length;x++) { + + calcArray.push((startX + (x * xstep)).toFixed(2) + ',' + (136 - ((_data[x]) * scale)).toFixed(2)); + + } + + var elm = document.getElementById(lineID); + + elm.setAttribute('points',calcArray.join(' ')); + + elm = document.getElementById(text1ID); + + elm.textContent = ceilingLimit; + + } + +}; diff --git a/platforms/android/assets/www/test.html b/platforms/android/assets/www/test.html index c332c56..dff6050 100644 --- a/platforms/android/assets/www/test.html +++ b/platforms/android/assets/www/test.html @@ -1,19 +1,31 @@ + - + + + diff --git a/platforms/ios/www/js/device/CC2650/cc2650_accelerometer.js b/platforms/ios/www/js/device/CC2650/cc2650_accelerometer.js index 65d2ca2..996e6ef 100644 --- a/platforms/ios/www/js/device/CC2650/cc2650_accelerometer.js +++ b/platforms/ios/www/js/device/CC2650/cc2650_accelerometer.js @@ -115,6 +115,11 @@ var CC2650_ACCEL = function(deviceId) { }; + this.animateGraph = function() { + // nothing to animate yet + + return -1; + }; this.insertFrame = function(mode) { diff --git a/platforms/ios/www/js/device/CC2650/cc2650_barometer.js b/platforms/ios/www/js/device/CC2650/cc2650_barometer.js index 814ef83..16dae7d 100644 --- a/platforms/ios/www/js/device/CC2650/cc2650_barometer.js +++ b/platforms/ios/www/js/device/CC2650/cc2650_barometer.js @@ -22,16 +22,14 @@ var CC2650_BAR = function(deviceId) { period: 'F000AA43-0451-4000-B000-000000000000' }; - + this.data = {temp: [], pressure: []}; this.$result = {temp: null, pressure: null}; - - this.startService = function() { 'use strict'; if (this.deviceID !== null) { - console.log('Starting CC2650 Accelerometer Service on ', this.deviceID); + console.log('Starting CC2650 Barometer Service on ', this.deviceID); console.log(this.serviceDef); this.insertFrame(); @@ -43,7 +41,6 @@ var CC2650_BAR = function(deviceId) { ble.write(this.deviceID, this.serviceDef.service, this.serviceDef.configuration, barometerConfig.buffer, function() { console.log('Started barometer.'); },this.onError); - } }; @@ -62,12 +59,22 @@ var CC2650_BAR = function(deviceId) { //0-2 Temp //3-5 Pressure - tStr = this.sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)) + '°C'; - pStr = this.sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)) + 'hPa'; + var temp, pressure; + temp = this.sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)); + pressure = this.sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)); + tStr = temp + '°C'; + pStr = pressure + 'hPa'; message = 'Temperature
' + tStr + 'Pressure
' + pStr ; +// this.data.temp = this.storeData(parseInt(temp), this.data.temp); +// this.data.pressure = this.storeData(parseInt(pressure), this.data.pressure); + + this.data.temp = this.storeData(temp, this.data.temp); + this.data.pressure = this.storeData(pressure, this.data.pressure); + + this.$result.temp.text(tStr); this.$result.pressure.text(pStr); @@ -76,9 +83,16 @@ var CC2650_BAR = function(deviceId) { console.log('Barometer:', this.state); }; + this.animateGraph = function() { + this.simpleGraph(this.data.temp, 'temp'); + this.simpleGraph(this.data.pressure, 'pressure'); + }; + + this.insertFrame = function() { var self = this; + var blankChart; console.log('Overloading...'); // Call the parent displayForm first... this.superClass_.insertFrame.call(self); @@ -95,6 +109,16 @@ var CC2650_BAR = function(deviceId) { $('
', { class: 'mui-col-xs-3 mui--text-white', id: pressure}).appendTo(row); this.$id.append(row); + + blankChart = this.generateBlankGraph('temp'); + + this.$id.append(blankChart); + + blankChart = this.generateBlankGraph('pressure'); + + this.$id.append(blankChart); + + this.$result.temp = $('#' + temp); this.$result.pressure = $('#' + pressure); diff --git a/platforms/ios/www/js/index.js b/platforms/ios/www/js/index.js index 9e68dba..7901dd6 100644 --- a/platforms/ios/www/js/index.js +++ b/platforms/ios/www/js/index.js @@ -21,198 +21,306 @@ var app = { activeServices: [], - serviceList: { - 1800: 'Generic Access', - 1801: 'Generic Attribute', - '180a': 'Device Information', - FFE0: 'Button', - 'F000AA00-0451-4000-B000-000000000000': 'Temperature', - 'F000AA20-0451-4000-B000-000000000000': 'Humidity', - 'F000AA80-0451-4000-B000-000000000000': 'Accelerometer', - 'F000AA40-0451-4000-B000-000000000000': 'Barometer' - - }, list: {}, - button: { - service: 'FFE0', - data: 'FFE1', // Bit 2: side key, Bit 1- right key, Bit 0 –left key - }, - accelerometer: { - service: 'F000AA80-0451-4000-B000-000000000000', - data: 'F000AA81-0451-4000-B000-000000000000', // Read/notify 3 bytes X : Y : Z - notification: 'F0002902-0451-4000-B000-000000000000', - configuration: 'F000AA82-0451-4000-B000-000000000000', // Read/write 1 byte - period: 'F000AA83-0451-4000-B000-000000000000' // Read/write 1 byte Period = [Input*10]ms - }, - barometer: { - service: 'F000AA40-0451-4000-B000-000000000000', - data: 'F000AA41-0451-4000-B000-000000000000', - notification: 'F0002902-0451-4000-B000-000000000000', - configuration: 'F000AA42-0451-4000-B000-000000000000', - period: 'F000AA43-0451-4000-B000-000000000000' - }, + manufactureDecoder: new MANUFACTUREDECODER(), // Application Constructor initialize: function() { - this.bindEvents(); + 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 bin = buffer; + + console.log('Block255', bin); + var manSpec = bin.map(function(i) { + + return i.toString(16) + ' '; + }); + + console.log('manSpec: ', manSpec); + var manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4); + + console.log('ManID:', manID); + + switch (manID) { + case '004c': + return app.manufactureDecoder.decodeIbeacon(bin); + break; + case '1235': + return app.manufactureDecoder.decodeSiliconLabsSensorPuck(bin); + break; + default: + console.log('Unknown manID: ', manID); + } + return {} + }, doScan: function() { 'use strict'; + $('#ripple').show(); $('#tbody').empty(); + var otherData = null; + var msgText = ''; ble.startScan([], function(device) { - console.log(JSON.stringify(device)); + otherData = null; + msgText = ''; + console.log(JSON.stringify(device)); - var newId = device.id.replace(/:/g, ''); - console.log(newId); + var newId = device.id.replace(/:/g, ''); + console.log(newId); - this.list[newId] = device.id; + this.list[newId] = device.id; - var newTR = $('
').text(device.id)); + newTR.append($('').text(device.id)); - if (device.hasOwnProperty('name')) { - newTR.append($('').text(device.name)); - } else { - newTR.append($('').text('*** Unknown')); - } + if (device.hasOwnProperty('advertising')) { - newTR.append($('').text(device.rssi)); + var advertBuffer = app.arrayBufferToIntArray(device.advertising); - $('#tbody').append(newTR); + var parsed = app.parseAdvertisingData(advertBuffer); - $('#output').append(JSON.stringify(device) + '
'); + //console.log(parsed); - }.bind(this), function(e) { - 'use strict'; - console.error(e); + if (parsed.hasOwnProperty('9')) { + + var name = parsed['9'].map(function(i) { + + return String.fromCharCode(i); }); + console.log('Name: ', name.join('')); + + } + + if (parsed.hasOwnProperty('255')) { + + otherData = app.handle255(parsed['255']); + console.log(otherData); + + } + } + + if (typeof otherData !== 'undefined' && otherData !== null) + { + if (otherData.hasOwnProperty('msg')) { + msgText = ' - ' + otherData.msg; + } + } + + if (device.hasOwnProperty('name')) { + newTR.append($('
').text(device.name + msgText)); + } else { + newTR.append($('').text('*** Unknown' + msgText)); + } + + newTR.append($('').text(device.rssi)); + + $('#tbody').append(newTR); + + $('#output').append(JSON.stringify(device) + '
'); + + }.bind(this), function(e) { + 'use strict'; + console.error(e); + }); setTimeout(ble.stopScan, - 5000, - function() { console.log('Scan complete'); }, - function() { console.log('stopScan failed'); } - ); + 5000, + function() { console.log('Scan complete'); + $('#ripple').hide(); + }, + function() { console.log('stopScan failed'); + $('#ripple').hide(); + }); - }, - // Bind Event Listeners + }, // 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.doScan(); - }.bind(this)); + var self = this; + document.addEventListener('deviceready', this.onDeviceReady, false); + $('#scan').on('click', function() { + 'use strict'; + this.doScan(); + }.bind(this)); + $('#tbody').on('click', 'tr', function() { + 'use strict'; + var tID = $(this).context.id; + var id = self.list[tID]; - $('#tbody').on('click','tr', function() { - 'use strict'; - var tID = $(this).context.id; + console.log(tID, id); - var id = self.list[tID]; + self.connect(id); + }); - console.log(tID, id); - - self.connect(id); - }); - - - }, - // Deviceready Event Handler + }, // 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() { - - }, serviceDiscovery: function(services) { 'use strict'; console.log(services); + }, sensorMpu9250GyroConvert: function(data) { + return data / (65536 / 500); }, - sensorMpu9250GyroConvert: function(data) { - return data / (65536 / 500); - }, sensorMpu9250AccConvert: function(data) { - // Change /2 to match accel range...i.e. 16 g would be /16 - return data / (32768 / 2); + // Change /2 to match accel range...i.e. 16 g would be /16 + return data / (32768 / 2); + }, + + doAnimate: function() { + 'use strict'; + console.log('Animate..'); + for (var t = 0; t < app.activeServices.length;t++) { + app.activeServices[t].animateGraph(); } - ,connect: function(deviceId) { + window.requestAnimFrame(app.doAnimate.bind(this)); + } - $('#results').slideUp(); - console.log('Connect to ', deviceId); + , connect: function(deviceId) { - var onConnect = function(a) { - var services = []; + $('#results').slideUp(); + console.log('Connect to ', deviceId); - 'use strict'; + var onConnect = function(a) { + var services = []; - console.log('A:', a); + 'use strict'; - services = a.services; + console.log('A:', a); - for (var t = 0; t < services.length;t++) { + services = a.services; - var ident = services[t].toUpperCase(); + for (var t = 0; t < services.length; t++) { - switch (ident) { - case '180F': - var batteryStat = new BATTERY(deviceId); - batteryStat.startService(); - batteryStat.readBatteryState(); - app.activeServices.push(batteryStat); + var ident = services[t].toUpperCase(); - break; - case 'FFE0': - var buttonState = new BUTTON(deviceId); - buttonState.startService(); - app.activeServices.push(buttonState); - break; + switch (ident) { + case '180F': + var batteryStat = new BATTERY(deviceId); + batteryStat.startService(); + batteryStat.readBatteryState(); + app.activeServices.push(batteryStat); - case 'F000AA80-0451-4000-B000-000000000000': - var cc2650_accel = new CC2650_ACCEL(deviceId); - cc2650_accel.startService(); - app.activeServices.push(cc2650_accel); - break; + break; + case 'FFE0': + var buttonState = new BUTTON(deviceId); + buttonState.startService(); + app.activeServices.push(buttonState); + break; - case 'F000AA40-0451-4000-B000-000000000000': - var cc2650_bar = new CC2650_BAR(deviceId); - cc2650_bar.startService(); - app.activeServices.push(cc2650_bar); - break; + case 'F000AA80-0451-4000-B000-000000000000': + var cc2650_accel = new CC2650_ACCEL(deviceId); + cc2650_accel.startService(); + app.activeServices.push(cc2650_accel); + break; - default: - console.log('Unknown service: ', ident); - } - } + case 'F000AA40-0451-4000-B000-000000000000': + var cc2650_bar = new CC2650_BAR(deviceId); + cc2650_bar.startService(); + app.activeServices.push(cc2650_bar); + break; + + case 'F000AA70-0451-4000-B000-000000000000': + var cc2650_lux = new CC2650_LUX(deviceId); + cc2650_lux.startService(); + app.activeServices.push(cc2650_lux); + break; + + case 'F000AA00-0451-4000-B000-000000000000': + var cc2650_tmp = new CC2650_TMP(deviceId); + cc2650_tmp.startService(); + app.activeServices.push(cc2650_tmp); + break; - }; + case 'F000AA20-0451-4000-B000-000000000000': + var cc2650_hum = new CC2650_HUM(deviceId); + cc2650_hum.startService(); + app.activeServices.push(cc2650_hum); + break; - ble.connect(deviceId, onConnect, function(e) { - 'use strict'; - console.error(e); - }); + default: + console.log('Unknown service: ', ident); + } + } - }, - onError: function(reason) { - console.error('ERROR: ' + reason); // Real apps should use notification.alert - }, - updateGyro: function(g) { + // Starting animation.. + window.requestAnimFrame(app.doAnimate.bind(this)); + }; + + ble.connect(deviceId, onConnect, function(e) { + 'use strict'; + 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(); diff --git a/platforms/ios/www/js/standards/capability.js b/platforms/ios/www/js/standards/capability.js index a1914d7..66a83e0 100644 --- a/platforms/ios/www/js/standards/capability.js +++ b/platforms/ios/www/js/standards/capability.js @@ -18,6 +18,10 @@ var CAPABILITY = function(p) { this.deviceID = null; + this.data = []; + this.ctx = null; + this.first = false; + }; CAPABILITY.prototype.setInternalID = function() { @@ -64,7 +68,174 @@ CAPABILITY.prototype.onError = function(e) { console.error(e); }; +CAPABILITY.prototype.storeData = function(data, alt) { + if (!this.first) { + this.first = true; + return []; + } + var target = alt || this.data; + + if (target.length === 99) { + target = target.slice(1); + } + + target.push(data); + if (alt) { + return target; + } else { + this.data = target; + } + + +}; + +CAPABILITY.prototype.startGraph = function(id) { + + var c; + c = id[0].getContext('2d'); + this.ctx = c; + + this.ctx.fillStyle = '#ffffff'; + this.ctx.fillRect(0,0,300,150); +}; + +CAPABILITY.prototype.generateBlankGraph = function(subID) { + + var _subID = subID || ''; + var xmlns = 'http://www.w3.org/2000/svg'; + + var svgID = this.frameID + _subID + '-svg'; + var text1ID = this.frameID + _subID + '-txt1'; + var lineID = this.frameID + _subID + '-line'; + + var svg = document.createElementNS(xmlns,'svg'); + + svg.setAttributeNS(xmlns,'id',svgID); + svg.setAttributeNS(xmlns,'width',300); + svg.setAttributeNS(xmlns,'height',150); + svg.setAttributeNS(xmlns,'fill', 'blue'); + + var line = document.createElementNS(xmlns,'line'); + + line.setAttributeNS(null,'x1','46'); + line.setAttributeNS(null,'y1','12'); + line.setAttributeNS(null,'x2','280'); + line.setAttributeNS(null,'y2', '12'); + line.setAttributeNS(null,'style','stroke:#bad649;stroke-width:2;'); + + svg.appendChild(line); + + line = document.createElementNS(xmlns,'line'); + + line.setAttributeNS(null,'x1','46'); + line.setAttributeNS(null,'y1','136'); + line.setAttributeNS(null,'x2','280'); + line.setAttributeNS(null,'y2', '136'); + line.setAttributeNS(null,'style','stroke:#bad649;stroke-width:2;'); + + svg.appendChild(line); + + var text = document.createElementNS(xmlns,'text'); + + text.setAttributeNS(null,'id',text1ID); + text.setAttributeNS(null,'x','36'); + text.setAttributeNS(null,'y','15'); + text.setAttributeNS(null,'text-anchor', 'end'); + text.setAttributeNS(null,'style','font-family:sans-serif;font-size:12;fill: #bad649;text-align:right;'); + text.textContent = '000'; + + svg.appendChild(text); + + text = document.createElementNS(xmlns,'text'); + + text.setAttributeNS(null,'id','text2'); + text.setAttributeNS(null,'x','36'); + text.setAttributeNS(null,'y','140'); + text.setAttributeNS(null,'text-anchor', 'end'); + text.setAttributeNS(null,'style','font-family:sans-serif;font-size:12;fill: #bad649;text-align:right;'); + text.textContent = '0'; + + svg.appendChild(text); + + var polyline = document.createElementNS(xmlns,'polyline'); + + polyline.setAttributeNS(null,'id',lineID); + polyline.setAttributeNS(null,'fill','none'); + polyline.setAttributeNS(null,'stroke','#e5f7fd'); + //#e5f7fd + // old #B5C7FF + polyline.setAttributeNS(null,'text-anchor', 'end'); + polyline.setAttributeNS(null,'stroke-width','2'); + + svg.appendChild(polyline); + + return svg; + +}; + +CAPABILITY.prototype.animateGraph = function() { + //this.simpleGraph(this.data); +}; + + +CAPABILITY.prototype.simpleGraph = function(data, subID) { + + var text1ID; + var lineID; + + var _data = data || this.data; + + + var _subID = subID || ''; + + lineID = this.frameID + _subID + '-line'; + text1ID = this.frameID + _subID + '-txt1'; + + console.log('Drawing: ', subID); + if (_data.length > 0) { + var ceiling = _data.reduce(function(p, v) { + return (p > v ? p : v); + }); + + var floor = _data.reduce(function(p, v) { + return (p < v ? p : v); + }); + + + var calcArray = []; + + var ceilingLimit = Math.floor(ceiling / 10) * 10; + if (ceilingLimit < ceiling) { + ceilingLimit = Math.floor((ceiling + (ceiling * 0.25)) / 10) * 10; + } + + if (ceilingLimit < 30) { + ceilingLimit = 30; + } + + var scale = 124 / ceilingLimit; + var xstep = (300 - 46) / 100; + var startX = 46 + (100 - _data.length) * xstep; + + for (var x = 0;x < _data.length;x++) { + + calcArray.push((startX + (x * 2.54)).toFixed(2) + ',' + (136 - ((_data[x]) * scale)).toFixed(2)); + + } + + var elm = document.getElementById(lineID); + + elm.setAttribute('points',calcArray.join(' ')); + + elm = document.getElementById(text1ID); + + elm.textContent = ceilingLimit; + + } + + +}; diff --git a/platforms/ios/www/test.html b/platforms/ios/www/test.html index 618da50..dff6050 100644 --- a/platforms/ios/www/test.html +++ b/platforms/ios/www/test.html @@ -1,168 +1,34 @@ - - - - - - - - - - - - - - - Sensor Toy - - - - -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
Gyroscope - B0:B4:48:BE:17:87
-
-
-
X
-
Y
-
Z
-
-
-
-0.19073 -
-
-0.15259 -
-
-0.07629 -
-
-
-
-
-
Accelerometer - B0:B4:48:BE:17:87
-
-
-
X
-
Y
-
Z
-
-
-
-0.00073 -
-
0.00153 -
-
-0.25513 -
-
-
-
-
-
Magnetometer - B0:B4:48:BE:17:87
-
-
-
X
-
Y
-
Z
-
-
-
-12 -
-
25 -
-
-4180 -
-
-
-
-
-
Button - B0:B4:48:BE:17:87
-
-
-
Status:
-
-
-
-
- - - - - - - - - - + + +a[0] | (a[1] << 8) diff --git a/plugins/android.json b/plugins/android.json index eadc370..8f83f6e 100644 --- a/plugins/android.json +++ b/plugins/android.json @@ -12,6 +12,9 @@ }, "cordova-plugin-ble-central": { "PACKAGE_NAME": "org.censis.sensortoy" + }, + "cordova-plugin-file": { + "PACKAGE_NAME": "org.censis.sensortoy" } }, "dependent_plugins": { diff --git a/plugins/fetch.json b/plugins/fetch.json index 799c50b..d7cae6e 100644 --- a/plugins/fetch.json +++ b/plugins/fetch.json @@ -22,5 +22,13 @@ }, "is_top_level": false, "variables": {} + }, + "cordova-plugin-file": { + "source": { + "type": "registry", + "id": "cordova-plugin-file" + }, + "is_top_level": true, + "variables": {} } } \ No newline at end of file diff --git a/plugins/ios.json b/plugins/ios.json index eadc370..8f83f6e 100644 --- a/plugins/ios.json +++ b/plugins/ios.json @@ -12,6 +12,9 @@ }, "cordova-plugin-ble-central": { "PACKAGE_NAME": "org.censis.sensortoy" + }, + "cordova-plugin-file": { + "PACKAGE_NAME": "org.censis.sensortoy" } }, "dependent_plugins": { diff --git a/www/css/app.css b/www/css/app.css index 35061bc..190097b 100644 --- a/www/css/app.css +++ b/www/css/app.css @@ -278,3 +278,30 @@ projector: cast -webkit-line-clamp: 1; -webkit-box-orient: vertical; } + +.pulser { + display: block; + margin-top:10%; + border-radius: 100px; + width: 30px; + height: 30px; + border: 10px solid #C5F4EB; + -webkit-animation: pulse 0.75s ease-in infinite; + -moz-animation: pulse 0.75s ease-in infinite; + animation: pulse 0.75s ease-in infinite; +} +@-webkit-keyframes pulse { + 0% { -webkit-transform: scale(0); } + 85% { opacity: 1; } + 100% { -webkit-transform: scale(1); -webkit-filter: blur(5px); opacity: 0; } +} +@-moz-keyframes pulse { + 0% { -moz-transform: scale(0); } + 85% { opacity: 1; } + 100% { -moz-transform: scale(1); -moz-filter: blur(5px); opacity: 0; } +} +@keyframes pulse { + 0% { transform: scale(0); } + 85% { opacity: 1; } + 100% { transform: scale(1); filter: blur(5px); opacity: 0; } +} diff --git a/www/css/mui.custom.css b/www/css/mui.custom.css index cfe1de1..79597a4 100644 --- a/www/css/mui.custom.css +++ b/www/css/mui.custom.css @@ -1331,7 +1331,7 @@ th { text-transform: uppercase; font-weight: 500; font-size: 14px; - color: rgba(0, 0, 0, 0.87); + color: rgba(255, 255, 255, 0.87); cursor: default; height: 48px; line-height: 48px; diff --git a/www/css/progress.css b/www/css/progress.css new file mode 100644 index 0000000..c2b6981 --- /dev/null +++ b/www/css/progress.css @@ -0,0 +1,94 @@ +/* Progress Bar */ +.progress { + position: relative; + height: 4px; + display: block; + width: 100%; + background-color: #acece6; + border-radius: 2px; + background-clip: padding-box; + /* margin: 0.5rem 0 1rem 0; */ + overflow: hidden; } + +.progress .determinate { + position: absolute; + background-color: inherit; + top: 0; + bottom: 0; + background-color: #26a69a; + transition: width .3s linear; } + +.progress .indeterminate { + background-color: #26a69a; } + +.progress .indeterminate:before { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left: 0; + bottom: 0; + will-change: left, right; + -webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite; + animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite; } + +.progress .indeterminate:after { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left: 0; + bottom: 0; + will-change: left, right; + -webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite; + animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite; + -webkit-animation-delay: 1.15s; + animation-delay: 1.15s; } + +@-webkit-keyframes indeterminate { + 0% { + left: -35%; + right: 100%; } + 60% { + left: 100%; + right: -90%; } + 100% { + left: 100%; + right: -90%; } + } + +@keyframes indeterminate { + 0% { + left: -35%; + right: 100%; } + 60% { + left: 100%; + right: -90%; } + 100% { + left: 100%; + right: -90%; } + } + +@-webkit-keyframes indeterminate-short { + 0% { + left: -200%; + right: 100%; } + 60% { + left: 107%; + right: -8%; } + 100% { + left: 107%; + right: -8%; } + } + +@keyframes indeterminate-short { + 0% { + left: -200%; + right: 100%; } + 60% { + left: 107%; + right: -8%; } + 100% { + left: 107%; + right: -8%; } + } diff --git a/www/css/ripple.css b/www/css/ripple.css new file mode 100644 index 0000000..b470db6 --- /dev/null +++ b/www/css/ripple.css @@ -0,0 +1,195 @@ + +@-webkit-keyframes uil-ripple { + 0% { + width: 0; + height: 0; + opacity: 0; + margin: 0 0 0 0; + } + 33% { + width: 44%; + height: 44%; + margin: -22% 0 0 -22%; + opacity: 1; + } + 100% { + width: 88%; + height: 88%; + margin: -44% 0 0 -44%; + opacity: 0; + } +} +@-webkit-keyframes uil-ripple { + 0% { + width: 0; + height: 0; + opacity: 0; + margin: 0 0 0 0; + } + 33% { + width: 44%; + height: 44%; + margin: -22% 0 0 -22%; + opacity: 1; + } + 100% { + width: 88%; + height: 88%; + margin: -44% 0 0 -44%; + opacity: 0; + } +} +@-moz-keyframes uil-ripple { + 0% { + width: 0; + height: 0; + opacity: 0; + margin: 0 0 0 0; + } + 33% { + width: 44%; + height: 44%; + margin: -22% 0 0 -22%; + opacity: 1; + } + 100% { + width: 88%; + height: 88%; + margin: -44% 0 0 -44%; + opacity: 0; + } +} +@-ms-keyframes uil-ripple { + 0% { + width: 0; + height: 0; + opacity: 0; + margin: 0 0 0 0; + } + 33% { + width: 44%; + height: 44%; + margin: -22% 0 0 -22%; + opacity: 1; + } + 100% { + width: 88%; + height: 88%; + margin: -44% 0 0 -44%; + opacity: 0; + } +} +@-moz-keyframes uil-ripple { + 0% { + width: 0; + height: 0; + opacity: 0; + margin: 0 0 0 0; + } + 33% { + width: 44%; + height: 44%; + margin: -22% 0 0 -22%; + opacity: 1; + } + 100% { + width: 88%; + height: 88%; + margin: -44% 0 0 -44%; + opacity: 0; + } +} +@-webkit-keyframes uil-ripple { + 0% { + width: 0; + height: 0; + opacity: 0; + margin: 0 0 0 0; + } + 33% { + width: 44%; + height: 44%; + margin: -22% 0 0 -22%; + opacity: 1; + } + 100% { + width: 88%; + height: 88%; + margin: -44% 0 0 -44%; + opacity: 0; + } +} +@-o-keyframes uil-ripple { + 0% { + width: 0; + height: 0; + opacity: 0; + margin: 0 0 0 0; + } + 33% { + width: 44%; + height: 44%; + margin: -22% 0 0 -22%; + opacity: 1; + } + 100% { + width: 88%; + height: 88%; + margin: -44% 0 0 -44%; + opacity: 0; + } +} +@keyframes uil-ripple { + 0% { + width: 0; + height: 0; + opacity: 0; + margin: 0 0 0 0; + } + 33% { + width: 44%; + height: 44%; + margin: -22% 0 0 -22%; + opacity: 1; + } + 100% { + width: 88%; + height: 88%; + margin: -44% 0 0 -44%; + opacity: 0; + } +} +.uil-ripple-css { + background: none; + position: relative; + width: 200px; + height: 200px; +} +.uil-ripple-css div { + position: absolute; + top: 50%; + left: 50%; + margin: 0; + width: 0; + height: 0; + opacity: 0; + border-radius: 50%; + border-width: 12px; + border-style: solid; + -ms-animation: uil-ripple 2s ease-out infinite; + -moz-animation: uil-ripple 2s ease-out infinite; + -webkit-animation: uil-ripple 2s ease-out infinite; + -o-animation: uil-ripple 2s ease-out infinite; + animation: uil-ripple 2s ease-out infinite; +} +.uil-ripple-css div:nth-of-type(1) { + border-color: #afafb7; +} +.uil-ripple-css div:nth-of-type(2) { + border-color: #5cffd6; + -ms-animation-delay: 1s; + -moz-animation-delay: 1s; + -webkit-animation-delay: 1s; + -o-animation-delay: 1s; + animation-delay: 1s; +} diff --git a/www/fonts/Material_Icons-normal-400.woff b/www/fonts/Material_Icons-normal-400.woff new file mode 100644 index 0000000..c6b6610 Binary files /dev/null and b/www/fonts/Material_Icons-normal-400.woff differ diff --git a/www/fonts/Ubuntu-normal-300.woff b/www/fonts/Ubuntu-normal-300.woff new file mode 100644 index 0000000..eeff903 Binary files /dev/null and b/www/fonts/Ubuntu-normal-300.woff differ diff --git a/www/fonts/Ubuntu-normal-400.woff b/www/fonts/Ubuntu-normal-400.woff new file mode 100644 index 0000000..f8c1d67 Binary files /dev/null and b/www/fonts/Ubuntu-normal-400.woff differ diff --git a/www/fonts/Ubuntu-normal-500.woff b/www/fonts/Ubuntu-normal-500.woff new file mode 100644 index 0000000..1166e73 Binary files /dev/null and b/www/fonts/Ubuntu-normal-500.woff differ diff --git a/www/fonts/Ubuntu-normal-700.woff b/www/fonts/Ubuntu-normal-700.woff new file mode 100644 index 0000000..a387b46 Binary files /dev/null and b/www/fonts/Ubuntu-normal-700.woff differ diff --git a/www/fonts/Ubuntu_Condensed-normal-400.woff b/www/fonts/Ubuntu_Condensed-normal-400.woff new file mode 100644 index 0000000..47864fe Binary files /dev/null and b/www/fonts/Ubuntu_Condensed-normal-400.woff differ diff --git a/www/fonts/fonts.css b/www/fonts/fonts.css new file mode 100644 index 0000000..1bd87c9 --- /dev/null +++ b/www/fonts/fonts.css @@ -0,0 +1,41 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(Material_Icons-normal-400.woff) format('woff'); +} + +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 300; + src: url(Ubuntu-normal-300.woff) format('woff'); +} + +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 400; + src: url(Ubuntu-normal-400.woff) format('woff'); +} + +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 500; + src: url(Ubuntu-normal-500.woff) format('woff'); +} + +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: 700; + src: url(Ubuntu-normal-700.woff) format('woff'); +} + +@font-face { + font-family: 'Ubuntu Condensed'; + font-style: normal; + font-weight: 400; + src: url(Ubuntu_Condensed-normal-400.woff) format('woff'); +} \ No newline at end of file diff --git a/www/index.html b/www/index.html index 7d82767..058c17c 100644 --- a/www/index.html +++ b/www/index.html @@ -41,16 +41,36 @@ rel="stylesheet"> - + + + + Sensor Toy +
+ +
+
- +
+
+ + +
+ +
+ +
+
+
+ @@ -74,12 +94,15 @@ + + + diff --git a/www/js/device/CC2650/cc2650_accelerometer.js b/www/js/device/CC2650/cc2650_accelerometer.js index 65d2ca2..8a0648a 100644 --- a/www/js/device/CC2650/cc2650_accelerometer.js +++ b/www/js/device/CC2650/cc2650_accelerometer.js @@ -37,7 +37,7 @@ var CC2650_ACCEL = function(deviceId) { }; this.onAccelerometerData = function(data) { - console.log(data); + // Console.log(data); var message; var a = new Int16Array(data); @@ -68,19 +68,19 @@ var CC2650_ACCEL = function(deviceId) { this.state = message; - this.$result[this.frames.gyroID + '-x'].text(this.sensorMpu9250GyroConvert(a[0]).toFixed(5)); - this.$result[this.frames.gyroID + '-y'].text(this.sensorMpu9250GyroConvert(a[1]).toFixed(5)); - this.$result[this.frames.gyroID + '-z'].text(this.sensorMpu9250GyroConvert(a[2]).toFixed(5)); + this.$result[this.frames.gyroID + '-x'].text(this.sensorMpu9250GyroConvert(a[0]).toFixed(5)); + this.$result[this.frames.gyroID + '-y'].text(this.sensorMpu9250GyroConvert(a[1]).toFixed(5)); + this.$result[this.frames.gyroID + '-z'].text(this.sensorMpu9250GyroConvert(a[2]).toFixed(5)); - this.$result[this.frames.accelID + '-x'].text(this.sensorMpu9250AccConvert(a[3]).toFixed(5)); - this.$result[this.frames.accelID + '-y'].text(this.sensorMpu9250AccConvert(a[4]).toFixed(5)); - this.$result[this.frames.accelID + '-z'].text(this.sensorMpu9250AccConvert(a[5]).toFixed(5)); + this.$result[this.frames.accelID + '-x'].text(this.sensorMpu9250AccConvert(a[3]).toFixed(5)); + this.$result[this.frames.accelID + '-y'].text(this.sensorMpu9250AccConvert(a[4]).toFixed(5)); + this.$result[this.frames.accelID + '-z'].text(this.sensorMpu9250AccConvert(a[5]).toFixed(5)); - this.$result[this.frames.magID + '-x'].text(a[3]); - this.$result[this.frames.magID + '-y'].text(a[4]); - this.$result[this.frames.magID + '-z'].text(a[5]); + this.$result[this.frames.magID + '-x'].text(a[3]); + this.$result[this.frames.magID + '-y'].text(a[4]); + this.$result[this.frames.magID + '-z'].text(a[5]); - console.log(this.state); + // Console.log(this.state); }; this.startService = function() { @@ -93,8 +93,6 @@ var CC2650_ACCEL = function(deviceId) { ble.startNotification(this.deviceID, this.serviceDef.service, this.serviceDef.data, this.onAccelerometerData.bind(this), this.onError); - - // Turn accelerometer on var configData = new Uint16Array(1); // Turn on gyro, accel, and mag, 2G range, Disable wake on motion @@ -115,6 +113,11 @@ var CC2650_ACCEL = function(deviceId) { }; + this.animateGraph = function() { + // Nothing to animate yet + + return -1; + }; this.insertFrame = function(mode) { diff --git a/www/js/device/CC2650/cc2650_barometer.js b/www/js/device/CC2650/cc2650_barometer.js index 097c92a..dc02554 100644 --- a/www/js/device/CC2650/cc2650_barometer.js +++ b/www/js/device/CC2650/cc2650_barometer.js @@ -22,7 +22,7 @@ var CC2650_BAR = function(deviceId) { period: 'F000AA43-0451-4000-B000-000000000000' }; - + this.data = {temp: [], pressure: []}; this.$result = {temp: null, pressure: null}; this.startService = function() { @@ -53,30 +53,47 @@ var CC2650_BAR = function(deviceId) { this.onBarometerData = function(data) { var pStr; var tStr; - console.log(data); + // Console.log(data); var message; var a = new Uint8Array(data); //0-2 Temp //3-5 Pressure - tStr = this.sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)) + '°C'; - pStr = this.sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)) + 'hPa'; + var temp, pressure; + temp = this.sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)); + pressure = this.sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)); + tStr = temp + '°C'; + pStr = pressure + 'hPa'; message = 'Temperature
' + tStr + 'Pressure
' + pStr ; + // This.data.temp = this.storeData(parseInt(temp), this.data.temp); + // this.data.pressure = this.storeData(parseInt(pressure), this.data.pressure); + + this.data.temp = this.storeData(temp, this.data.temp); + this.data.pressure = this.storeData(pressure, this.data.pressure); + + this.$result.temp.text(tStr); this.$result.pressure.text(pStr); this.state = message; - console.log('Barometer:', this.state); + // Console.log('Barometer:', this.state); }; + this.animateGraph = function() { + this.simpleGraph(this.data.temp, 'temp'); + this.simpleGraph(this.data.pressure, 'pressure'); + }; + + this.insertFrame = function() { var self = this; - console.log('Overloading...'); + var blankChart; + // Call the parent displayForm first... this.superClass_.insertFrame.call(self); @@ -92,6 +109,24 @@ var CC2650_BAR = function(deviceId) { $('
', { class: 'mui-col-xs-3 mui--text-white', id: pressure}).appendTo(row); this.$id.append(row); + + var tabBody = $('
'); + newTR = $(''); newTR.append($('
').text(device.id)); - if (device.hasOwnProperty('name')) { - newTR.append($('').text(device.name)); + 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; } - else { - newTR.append($('').text('*** Unknown')); + + if (typeof otherData !== 'undefined' && otherData !== null) { + if (otherData.hasOwnProperty('msg')) { + msgText = ' - ' + otherData.msg; + } + } + + if (device.hasOwnProperty('name')) { + newTR.append($('').text(device.name + msgText)); + } else { + newTR.append($('').text('*** Unknown' + msgText)); } newTR.append($('').text(device.rssi)); - $('#tbody').append(newTR); - $('#output').append(JSON.stringify(device) + '
'); + if ($('tr#' + newId).length > 0) { + $('tr#' + newId).replaceWith(newTR); + } else { + $('#tbody').append(newTR); + } + + + //$('#output').append(JSON.stringify(device) + '
'); + + app.log[newId] = _device; + + console.log(JSON.stringify(_device)); }.bind(this), function(e) { 'use strict'; console.error(e); }); - setTimeout(ble.stopScan, - 5000, - function() { console.log('Scan complete'); }, - function() { console.log('stopScan failed'); }); + var _t = [5000,60000,200][mode]; - }, // Bind Event Listeners + + 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'. @@ -75,7 +312,21 @@ var app = { document.addEventListener('deviceready', this.onDeviceReady, false); $('#scan').on('click', function() { 'use strict'; - this.doScan(); + 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)); $('#tbody').on('click', 'tr', function() { @@ -86,6 +337,7 @@ var app = { console.log(tID, id); + app.forceStop(); self.connect(id); }); @@ -107,6 +359,16 @@ var app = { sensorMpu9250AccConvert: function(data) { // Change /2 to match accel range...i.e. 16 g would be /16 return data / (32768 / 2); + }, + + 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) { @@ -131,50 +393,54 @@ var app = { case '180F': var batteryStat = new BATTERY(deviceId); batteryStat.startService(); - batteryStat.readBatteryState(); + // batteryStat.readBatteryState(); app.activeServices.push(batteryStat); - break; + break; case 'FFE0': var buttonState = new BUTTON(deviceId); buttonState.startService(); app.activeServices.push(buttonState); - break; + break; case 'F000AA80-0451-4000-B000-000000000000': var cc2650_accel = new CC2650_ACCEL(deviceId); cc2650_accel.startService(); app.activeServices.push(cc2650_accel); - break; + break; case 'F000AA40-0451-4000-B000-000000000000': var cc2650_bar = new CC2650_BAR(deviceId); cc2650_bar.startService(); app.activeServices.push(cc2650_bar); - break; + break; case 'F000AA70-0451-4000-B000-000000000000': var cc2650_lux = new CC2650_LUX(deviceId); cc2650_lux.startService(); app.activeServices.push(cc2650_lux); - break; + break; case 'F000AA00-0451-4000-B000-000000000000': var cc2650_tmp = new CC2650_TMP(deviceId); cc2650_tmp.startService(); app.activeServices.push(cc2650_tmp); - break; + break; + + case 'F000AA20-0451-4000-B000-000000000000': var cc2650_hum = new CC2650_HUM(deviceId); cc2650_hum.startService(); app.activeServices.push(cc2650_hum); - break; + break; default: - console.log('Unknown service: ', ident); + console.error('Unknown service: ', ident); } } + // Starting animation.. + window.requestAnimFrame(app.doAnimate.bind(this)); }; ble.connect(deviceId, onConnect, function(e) { @@ -190,4 +456,16 @@ var app = { } }; + +window.requestAnimFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; +})(); + + + app.initialize(); diff --git a/www/js/mandecoder.js b/www/js/mandecoder.js new file mode 100644 index 0000000..1530a32 --- /dev/null +++ b/www/js/mandecoder.js @@ -0,0 +1,69 @@ +/** + * + * 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 = {}; + //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.humidity = (bin[7] << 8) | bin[6]; + obj.temp = (bin[9] << 8) | bin[8]; + obj.c = (bin[11] << 8) | bin[10]; + obj.d = (bin[13] << 8) | bin[12]; + + obj.msg = 'Humidity: ' + (obj.humidity / 10) + ', temp: ' + (obj.temp / 10); + + return obj; + }; + + + this.decodeSansible = function(data) { + var bin = data; + var obj = {}; + // obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4); + obj.manID = this.getManID(bin); + + obj.p1 = ((bin[2] << 16) | bin[3] << 8 | bin[4]) + obj.p2 = ((bin[5] << 16) | bin[6] << 8 | bin[7]) + + obj.msg = 'Left: ' + (obj.p1 / 100) + ' hPa, Right: ' + (obj.p2 / 100) + ' hPa'; + return obj; + + }; + + +}; diff --git a/www/js/standards/battery.js b/www/js/standards/battery.js index 8a17160..0b8ad9a 100644 --- a/www/js/standards/battery.js +++ b/www/js/standards/battery.js @@ -13,39 +13,48 @@ var BATTERY = function() { this.name = 'Battery'; this.capabilityID = '180F'; this.serviceDef = { - service: '180F', - level: '2A19' - }; + service: '180F', level: '2A19' + }; this.onBatteryLevelChange = function(data) { - console.log(data); - var a = new Uint8Array(data); - this.state = a[0]; - console.log('onBatteryLevelChange', this.state); - }; + console.log(data); + var a = new Uint8Array(data); + this.state = a[0]; + console.log('onBatteryLevelChange', this.state); + }; this.readBatteryState = function() { - console.log('readBatteryState'); - ble.read(this.deviceID, this.serviceDef.service, this.serviceDef.level, this.onReadBatteryLevel.bind(this), this.onError); - }; + console.log('readBatteryState'); + ble.read(this.deviceID, + this.serviceDef.service, + this.serviceDef.level, + this.onReadBatteryLevel.bind(this), + this.onError); + }; this.onReadBatteryLevel = function(data) { - console.log(data); - var a = new Uint8Array(data); - this.state = a[0]; - console.log('onReadBatteryLevel', this.state); - }; + console.log(data); + var a = new Uint8Array(data); + this.state = a[0]; + console.log('onReadBatteryLevel', this.state); + }; this.startService = function() { - 'use strict'; - if (this.deviceID !== null) { - console.log('Starting Battery Service'); - ble.startNotification(this.deviceID, this.serviceDef.service, this.serviceDef.level, this.onBatteryLevelChange.bind(this), this.onError); - } + 'use strict'; + if (this.deviceID !== null) { + console.log('Starting Battery Service on ', this.deviceID); + console.log(this.serviceDef); - this.insertFrame(); - }; + this.insertFrame(); + + ble.startNotification(this.deviceID, + this.serviceDef.service, + this.serviceDef.level, + this.onBatteryLevelChange.bind(this), + this.onError); + } + }; }; diff --git a/www/js/standards/bluetooth_company_identifiers.js b/www/js/standards/bluetooth_company_identifiers.js new file mode 100644 index 0000000..72fc3f2 --- /dev/null +++ b/www/js/standards/bluetooth_company_identifiers.js @@ -0,0 +1,3232 @@ +/** + * + * User: Martin Donnelly + * Date: 2016-05-26 + * Time: 11:47 + * + */ +var bt_company_ids = { + find: function(id) { + var _id = id; + var item = this.entries.filter(function(obj) { + return obj.hexadecimal === _id; + }); + return item; + }, + entries: [ + { + decimal: '65535', + hexadecimal: '0xFFFF', + name: 'reserved' + }, + { + decimal: '0', + hexadecimal: '0x0000', + name: 'Ericsson Technology Licensing' + }, + { + decimal: '1', + hexadecimal: '0x0001', + name: 'Nokia Mobile Phones' + }, + { + decimal: '2', + hexadecimal: '0x0002', + name: 'Intel Corp.' + }, + { + decimal: '3', + hexadecimal: '0x0003', + name: 'IBM Corp.' + }, + { + decimal: '4', + hexadecimal: '0x0004', + name: 'Toshiba Corp.' + }, + { + decimal: '5', + hexadecimal: '0x0005', + name: '3Com' + }, + { + decimal: '6', + hexadecimal: '0x0006', + name: 'Microsoft' + }, + { + decimal: '7', + hexadecimal: '0x0007', + name: 'Lucent' + }, + { + decimal: '8', + hexadecimal: '0x0008', + name: 'Motorola' + }, + { + decimal: '9', + hexadecimal: '0x0009', + name: 'Infineon Technologies AG' + }, + { + decimal: '10', + hexadecimal: '0x000A', + name: 'Cambridge Silicon Radio' + }, + { + decimal: '11', + hexadecimal: '0x000B', + name: 'Silicon Wave' + }, + { + decimal: '12', + hexadecimal: '0x000C', + name: 'Digianswer A/S' + }, + { + decimal: '13', + hexadecimal: '0x000D', + name: 'Texas Instruments Inc.' + }, + { + decimal: '14', + hexadecimal: '0x000E', + name: 'Ceva, Inc. (formerly Parthus Technologies, Inc.)' + }, + { + decimal: '15', + hexadecimal: '0x000F', + name: 'Broadcom Corporation' + }, + { + decimal: '16', + hexadecimal: '0x0010', + name: 'Mitel Semiconductor' + }, + { + decimal: '17', + hexadecimal: '0x0011', + name: 'Widcomm, Inc' + }, + { + decimal: '18', + hexadecimal: '0x0012', + name: 'Zeevo, Inc.' + }, + { + decimal: '19', + hexadecimal: '0x0013', + name: 'Atmel Corporation' + }, + { + decimal: '20', + hexadecimal: '0x0014', + name: 'Mitsubishi Electric Corporation' + }, + { + decimal: '21', + hexadecimal: '0x0015', + name: 'RTX Telecom A/S' + }, + { + decimal: '22', + hexadecimal: '0x0016', + name: 'KC Technology Inc.' + }, + { + decimal: '23', + hexadecimal: '0x0017', + name: 'NewLogic' + }, + { + decimal: '24', + hexadecimal: '0x0018', + name: 'Transilica, Inc.' + }, + { + decimal: '25', + hexadecimal: '0x0019', + name: 'Rohde & Schwarz GmbH & Co. KG' + }, + { + decimal: '26', + hexadecimal: '0x001A', + name: 'TTPCom Limited' + }, + { + decimal: '27', + hexadecimal: '0x001B', + name: 'Signia Technologies, Inc.' + }, + { + decimal: '28', + hexadecimal: '0x001C', + name: 'Conexant Systems Inc.' + }, + { + decimal: '29', + hexadecimal: '0x001D', + name: 'Qualcomm' + }, + { + decimal: '30', + hexadecimal: '0x001E', + name: 'Inventel' + }, + { + decimal: '31', + hexadecimal: '0x001F', + name: 'AVM Berlin' + }, + { + decimal: '32', + hexadecimal: '0x0020', + name: 'BandSpeed, Inc.' + }, + { + decimal: '33', + hexadecimal: '0x0021', + name: 'Mansella Ltd' + }, + { + decimal: '34', + hexadecimal: '0x0022', + name: 'NEC Corporation' + }, + { + decimal: '35', + hexadecimal: '0x0023', + name: 'WavePlus Technology Co., Ltd.' + }, + { + decimal: '36', + hexadecimal: '0x0024', + name: 'Alcatel' + }, + { + decimal: '37', + hexadecimal: '0x0025', + name: 'NXP Semiconductors (formerly Philips Semiconductors)' + }, + { + decimal: '38', + hexadecimal: '0x0026', + name: 'C Technologies' + }, + { + decimal: '39', + hexadecimal: '0x0027', + name: 'Open Interface' + }, + { + decimal: '40', + hexadecimal: '0x0028', + name: 'R F Micro Devices' + }, + { + decimal: '41', + hexadecimal: '0x0029', + name: 'Hitachi Ltd' + }, + { + decimal: '42', + hexadecimal: '0x002A', + name: 'Symbol Technologies, Inc.' + }, + { + decimal: '43', + hexadecimal: '0x002B', + name: 'Tenovis' + }, + { + decimal: '44', + hexadecimal: '0x002C', + name: 'Macronix International Co. Ltd.' + }, + { + decimal: '45', + hexadecimal: '0x002D', + name: 'GCT Semiconductor' + }, + { + decimal: '46', + hexadecimal: '0x002E', + name: 'Norwood Systems' + }, + { + decimal: '47', + hexadecimal: '0x002F', + name: 'MewTel Technology Inc.' + }, + { + decimal: '48', + hexadecimal: '0x0030', + name: 'ST Microelectronics' + }, + { + decimal: '49', + hexadecimal: '0x0031', + name: 'Synopsis' + }, + { + decimal: '50', + hexadecimal: '0x0032', + name: 'Red-M (Communications) Ltd' + }, + { + decimal: '51', + hexadecimal: '0x0033', + name: 'Commil Ltd' + }, + { + decimal: '52', + hexadecimal: '0x0034', + name: 'Computer Access Technology Corporation (CATC)' + }, + { + decimal: '53', + hexadecimal: '0x0035', + name: 'Eclipse (HQ Espana) S.L.' + }, + { + decimal: '54', + hexadecimal: '0x0036', + name: 'Renesas Technology Corp.' + }, + { + decimal: '55', + hexadecimal: '0x0037', + name: 'Mobilian Corporation' + }, + { + decimal: '56', + hexadecimal: '0x0038', + name: 'Terax' + }, + { + decimal: '57', + hexadecimal: '0x0039', + name: 'Integrated System Solution Corp.' + }, + { + decimal: '58', + hexadecimal: '0x003A', + name: 'Matsushita Electric Industrial Co., Ltd.' + }, + { + decimal: '59', + hexadecimal: '0x003B', + name: 'Gennum Corporation' + }, + { + decimal: '60', + hexadecimal: '0x003C', + name: 'Research In Motion' + }, + { + decimal: '61', + hexadecimal: '0x003D', + name: 'IPextreme, Inc.' + }, + { + decimal: '62', + hexadecimal: '0x003E', + name: 'Systems and Chips, Inc.' + }, + { + decimal: '63', + hexadecimal: '0x003F', + name: 'Bluetooth SIG, Inc.' + }, + { + decimal: '64', + hexadecimal: '0x0040', + name: 'Seiko Epson Corporation' + }, + { + decimal: '65', + hexadecimal: '0x0041', + name: 'Integrated Silicon Solution Taiwan, Inc.' + }, + { + decimal: '66', + hexadecimal: '0x0042', + name: 'CONWISE Technology Corporation Ltd' + }, + { + decimal: '67', + hexadecimal: '0x0043', + name: 'PARROT SA' + }, + { + decimal: '68', + hexadecimal: '0x0044', + name: 'Socket Mobile' + }, + { + decimal: '69', + hexadecimal: '0x0045', + name: 'Atheros Communications, Inc.' + }, + { + decimal: '70', + hexadecimal: '0x0046', + name: 'MediaTek, Inc.' + }, + { + decimal: '71', + hexadecimal: '0x0047', + name: 'Bluegiga' + }, + { + decimal: '72', + hexadecimal: '0x0048', + name: 'Marvell Technology Group Ltd.' + }, + { + decimal: '73', + hexadecimal: '0x0049', + name: '3DSP Corporation' + }, + { + decimal: '74', + hexadecimal: '0x004A', + name: 'Accel Semiconductor Ltd.' + }, + { + decimal: '75', + hexadecimal: '0x004B', + name: 'Continental Automotive Systems' + }, + { + decimal: '76', + hexadecimal: '0x004C', + name: 'Apple, Inc.' + }, + { + decimal: '77', + hexadecimal: '0x004D', + name: 'Staccato Communications, Inc.' + }, + { + decimal: '78', + hexadecimal: '0x004E', + name: 'Avago Technologies' + }, + { + decimal: '79', + hexadecimal: '0x004F', + name: 'APT Licensing Ltd.' + }, + { + decimal: '80', + hexadecimal: '0x0050', + name: 'SiRF Technology' + }, + { + decimal: '81', + hexadecimal: '0x0051', + name: 'Tzero Technologies, Inc.' + }, + { + decimal: '82', + hexadecimal: '0x0052', + name: 'J&M Corporation' + }, + { + decimal: '83', + hexadecimal: '0x0053', + name: 'Free2move AB' + }, + { + decimal: '84', + hexadecimal: '0x0054', + name: '3DiJoy Corporation' + }, + { + decimal: '85', + hexadecimal: '0x0055', + name: 'Plantronics, Inc.' + }, + { + decimal: '86', + hexadecimal: '0x0056', + name: 'Sony Ericsson Mobile Communications' + }, + { + decimal: '87', + hexadecimal: '0x0057', + name: 'Harman International Industries, Inc.' + }, + { + decimal: '88', + hexadecimal: '0x0058', + name: 'Vizio, Inc.' + }, + { + decimal: '89', + hexadecimal: '0x0059', + name: 'Nordic Semiconductor ASA' + }, + { + decimal: '90', + hexadecimal: '0x005A', + name: 'EM Microelectronic-Marin SA' + }, + { + decimal: '91', + hexadecimal: '0x005B', + name: 'Ralink Technology Corporation' + }, + { + decimal: '92', + hexadecimal: '0x005C', + name: 'Belkin International, Inc.' + }, + { + decimal: '93', + hexadecimal: '0x005D', + name: 'Realtek Semiconductor Corporation' + }, + { + decimal: '94', + hexadecimal: '0x005E', + name: 'Stonestreet One, LLC' + }, + { + decimal: '95', + hexadecimal: '0x005F', + name: 'Wicentric, Inc.' + }, + { + decimal: '96', + hexadecimal: '0x0060', + name: 'RivieraWaves S.A.S' + }, + { + decimal: '97', + hexadecimal: '0x0061', + name: 'RDA Microelectronics' + }, + { + decimal: '98', + hexadecimal: '0x0062', + name: 'Gibson Guitars' + }, + { + decimal: '99', + hexadecimal: '0x0063', + name: 'MiCommand Inc.' + }, + { + decimal: '100', + hexadecimal: '0x0064', + name: 'Band XI International, LLC' + }, + { + decimal: '101', + hexadecimal: '0x0065', + name: 'Hewlett-Packard Company' + }, + { + decimal: '102', + hexadecimal: '0x0066', + name: '9Solutions Oy' + }, + { + decimal: '103', + hexadecimal: '0x0067', + name: 'GN Netcom A/S' + }, + { + decimal: '104', + hexadecimal: '0x0068', + name: 'General Motors' + }, + { + decimal: '105', + hexadecimal: '0x0069', + name: 'A&D Engineering, Inc.' + }, + { + decimal: '106', + hexadecimal: '0x006A', + name: 'MindTree Ltd.' + }, + { + decimal: '107', + hexadecimal: '0x006B', + name: 'Polar Electro OY' + }, + { + decimal: '108', + hexadecimal: '0x006C', + name: 'Beautiful Enterprise Co., Ltd.' + }, + { + decimal: '109', + hexadecimal: '0x006D', + name: 'BriarTek, Inc.' + }, + { + decimal: '110', + hexadecimal: '0x006E', + name: 'Summit Data Communications, Inc.' + }, + { + decimal: '111', + hexadecimal: '0x006F', + name: 'Sound ID' + }, + { + decimal: '112', + hexadecimal: '0x0070', + name: 'Monster, LLC' + }, + { + decimal: '113', + hexadecimal: '0x0071', + name: 'connectBlue AB' + }, + { + decimal: '114', + hexadecimal: '0x0072', + name: 'ShangHai Super Smart Electronics Co. Ltd.' + }, + { + decimal: '115', + hexadecimal: '0x0073', + name: 'Group Sense Ltd.' + }, + { + decimal: '116', + hexadecimal: '0x0074', + name: 'Zomm, LLC' + }, + { + decimal: '117', + hexadecimal: '0x0075', + name: 'Samsung Electronics Co. Ltd.' + }, + { + decimal: '118', + hexadecimal: '0x0076', + name: 'Creative Technology Ltd.' + }, + { + decimal: '119', + hexadecimal: '0x0077', + name: 'Laird Technologies' + }, + { + decimal: '120', + hexadecimal: '0x0078', + name: 'Nike, Inc.' + }, + { + decimal: '121', + hexadecimal: '0x0079', + name: 'lesswire AG' + }, + { + decimal: '122', + hexadecimal: '0x007A', + name: 'MStar Semiconductor, Inc.' + }, + { + decimal: '123', + hexadecimal: '0x007B', + name: 'Hanlynn Technologies' + }, + { + decimal: '124', + hexadecimal: '0x007C', + name: 'A & R Cambridge' + }, + { + decimal: '125', + hexadecimal: '0x007D', + name: 'Seers Technology Co. Ltd' + }, + { + decimal: '126', + hexadecimal: '0x007E', + name: 'Sports Tracking Technologies Ltd.' + }, + { + decimal: '127', + hexadecimal: '0x007F', + name: 'Autonet Mobile' + }, + { + decimal: '128', + hexadecimal: '0x0080', + name: 'DeLorme Publishing Company, Inc.' + }, + { + decimal: '129', + hexadecimal: '0x0081', + name: 'WuXi Vimicro' + }, + { + decimal: '130', + hexadecimal: '0x0082', + name: 'Sennheiser Communications A/S' + }, + { + decimal: '131', + hexadecimal: '0x0083', + name: 'TimeKeeping Systems, Inc.' + }, + { + decimal: '132', + hexadecimal: '0x0084', + name: 'Ludus Helsinki Ltd.' + }, + { + decimal: '133', + hexadecimal: '0x0085', + name: 'BlueRadios, Inc.' + }, + { + decimal: '134', + hexadecimal: '0x0086', + name: 'equinox AG' + }, + { + decimal: '135', + hexadecimal: '0x0087', + name: 'Garmin International, Inc.' + }, + { + decimal: '136', + hexadecimal: '0x0088', + name: 'Ecotest' + }, + { + decimal: '137', + hexadecimal: '0x0089', + name: 'GN ReSound A/S' + }, + { + decimal: '138', + hexadecimal: '0x008A', + name: 'Jawbone' + }, + { + decimal: '139', + hexadecimal: '0x008B', + name: 'Topcorn Positioning Systems, LLC' + }, + { + decimal: '140', + hexadecimal: '0x008C', + name: 'Gimbal Inc. (formerly Qualcomm Labs, Inc. and Qualcomm Retail Solutions, Inc.)' + }, + { + decimal: '141', + hexadecimal: '0x008D', + name: 'Zscan Software' + }, + { + decimal: '142', + hexadecimal: '0x008E', + name: 'Quintic Corp.' + }, + { + decimal: '143', + hexadecimal: '0x008F', + name: 'Stollman E+V GmbH' + }, + { + decimal: '144', + hexadecimal: '0x0090', + name: 'Funai Electric Co., Ltd.' + }, + { + decimal: '145', + hexadecimal: '0x0091', + name: 'Advanced PANMOBIL Systems GmbH & Co. KG' + }, + { + decimal: '146', + hexadecimal: '0x0092', + name: 'ThinkOptics, Inc.' + }, + { + decimal: '147', + hexadecimal: '0x0093', + name: 'Universal Electronics, Inc.' + }, + { + decimal: '148', + hexadecimal: '0x0094', + name: 'Airoha Technology Corp.' + }, + { + decimal: '149', + hexadecimal: '0x0095', + name: 'NEC Lighting, Ltd.' + }, + { + decimal: '150', + hexadecimal: '0x0096', + name: 'ODM Technology, Inc.' + }, + { + decimal: '151', + hexadecimal: '0x0097', + name: 'ConnecteDevice Ltd.' + }, + { + decimal: '152', + hexadecimal: '0x0098', + name: 'zer01.tv GmbH' + }, + { + decimal: '153', + hexadecimal: '0x0099', + name: 'i.Tech Dynamic Global Distribution Ltd.' + }, + { + decimal: '154', + hexadecimal: '0x009A', + name: 'Alpwise' + }, + { + decimal: '155', + hexadecimal: '0x009B', + name: 'Jiangsu Toppower Automotive Electronics Co., Ltd.' + }, + { + decimal: '156', + hexadecimal: '0x009C', + name: 'Colorfy, Inc.' + }, + { + decimal: '157', + hexadecimal: '0x009D', + name: 'Geoforce Inc.' + }, + { + decimal: '158', + hexadecimal: '0x009E', + name: 'Bose Corporation' + }, + { + decimal: '159', + hexadecimal: '0x009F', + name: 'Suunto Oy' + }, + { + decimal: '160', + hexadecimal: '0x00A0', + name: 'Kensington Computer Products Group' + }, + { + decimal: '161', + hexadecimal: '0x00A1', + name: 'SR-Medizinelektronik' + }, + { + decimal: '162', + hexadecimal: '0x00A2', + name: 'Vertu Corporation Limited' + }, + { + decimal: '163', + hexadecimal: '0x00A3', + name: 'Meta Watch Ltd.' + }, + { + decimal: '164', + hexadecimal: '0x00A4', + name: 'LINAK A/S' + }, + { + decimal: '165', + hexadecimal: '0x00A5', + name: 'OTL Dynamics LLC' + }, + { + decimal: '166', + hexadecimal: '0x00A6', + name: 'Panda Ocean Inc.' + }, + { + decimal: '167', + hexadecimal: '0x00A7', + name: 'Visteon Corporation' + }, + { + decimal: '168', + hexadecimal: '0x00A8', + name: 'ARP Devices Limited' + }, + { + decimal: '169', + hexadecimal: '0x00A9', + name: 'Magneti Marelli S.p.A' + }, + { + decimal: '170', + hexadecimal: '0x00AA', + name: 'CAEN RFID srl' + }, + { + decimal: '171', + hexadecimal: '0x00AB', + name: 'Ingenieur-Systemgruppe Zahn GmbH' + }, + { + decimal: '172', + hexadecimal: '0x00AC', + name: 'Green Throttle Games' + }, + { + decimal: '173', + hexadecimal: '0x00AD', + name: 'Peter Systemtechnik GmbH' + }, + { + decimal: '174', + hexadecimal: '0x00AE', + name: 'Omegawave Oy' + }, + { + decimal: '175', + hexadecimal: '0x00AF', + name: 'Cinetix' + }, + { + decimal: '176', + hexadecimal: '0x00B0', + name: 'Passif Semiconductor Corp' + }, + { + decimal: '177', + hexadecimal: '0x00B1', + name: 'Saris Cycling Group, Inc' + }, + { + decimal: '178', + hexadecimal: '0x00B2', + name: 'Bekey A/S' + }, + { + decimal: '179', + hexadecimal: '0x00B3', + name: 'Clarinox Technologies Pty. Ltd.' + }, + { + decimal: '180', + hexadecimal: '0x00B4', + name: 'BDE Technology Co., Ltd.' + }, + { + decimal: '181', + hexadecimal: '0x00B5', + name: 'Swirl Networks' + }, + { + decimal: '182', + hexadecimal: '0x00B6', + name: 'Meso international' + }, + { + decimal: '183', + hexadecimal: '0x00B7', + name: 'TreLab Ltd' + }, + { + decimal: '184', + hexadecimal: '0x00B8', + name: 'Qualcomm Innovation Center, Inc. (QuIC)' + }, + { + decimal: '185', + hexadecimal: '0x00B9', + name: 'Johnson Controls, Inc.' + }, + { + decimal: '186', + hexadecimal: '0x00BA', + name: 'Starkey Laboratories Inc.' + }, + { + decimal: '187', + hexadecimal: '0x00BB', + name: 'S-Power Electronics Limited' + }, + { + decimal: '188', + hexadecimal: '0x00BC', + name: 'Ace Sensor Inc' + }, + { + decimal: '189', + hexadecimal: '0x00BD', + name: 'Aplix Corporation' + }, + { + decimal: '190', + hexadecimal: '0x00BE', + name: 'AAMP of America' + }, + { + decimal: '191', + hexadecimal: '0x00BF', + name: 'Stalmart Technology Limited' + }, + { + decimal: '192', + hexadecimal: '0x00C0', + name: 'AMICCOM Electronics Corporation' + }, + { + decimal: '193', + hexadecimal: '0x00C1', + name: 'Shenzhen Excelsecu Data Technology Co.,Ltd' + }, + { + decimal: '194', + hexadecimal: '0x00C2', + name: 'Geneq Inc.' + }, + { + decimal: '195', + hexadecimal: '0x00C3', + name: 'adidas AG' + }, + { + decimal: '196', + hexadecimal: '0x00C4', + name: 'LG Electronics' + }, + { + decimal: '197', + hexadecimal: '0x00C5', + name: 'Onset Computer Corporation' + }, + { + decimal: '198', + hexadecimal: '0x00C6', + name: 'Selfly BV' + }, + { + decimal: '199', + hexadecimal: '0x00C7', + name: 'Quuppa Oy.' + }, + { + decimal: '200', + hexadecimal: '0x00C8', + name: 'GeLo Inc' + }, + { + decimal: '201', + hexadecimal: '0x00C9', + name: 'Evluma' + }, + { + decimal: '202', + hexadecimal: '0x00CA', + name: 'MC10' + }, + { + decimal: '203', + hexadecimal: '0x00CB', + name: 'Binauric SE' + }, + { + decimal: '204', + hexadecimal: '0x00CC', + name: 'Beats Electronics' + }, + { + decimal: '205', + hexadecimal: '0x00CD', + name: 'Microchip Technology Inc.' + }, + { + decimal: '206', + hexadecimal: '0x00CE', + name: 'Elgato Systems GmbH' + }, + { + decimal: '207', + hexadecimal: '0x00CF', + name: 'ARCHOS SA' + }, + { + decimal: '208', + hexadecimal: '0x00D0', + name: 'Dexcom, Inc.' + }, + { + decimal: '209', + hexadecimal: '0x00D1', + name: 'Polar Electro Europe B.V.' + }, + { + decimal: '210', + hexadecimal: '0x00D2', + name: 'Dialog Semiconductor B.V.' + }, + { + decimal: '211', + hexadecimal: '0x00D3', + name: 'Taixingbang Technology (HK) Co,. LTD.' + }, + { + decimal: '212', + hexadecimal: '0x00D4', + name: 'Kawantech' + }, + { + decimal: '213', + hexadecimal: '0x00D5', + name: 'Austco Communication Systems' + }, + { + decimal: '214', + hexadecimal: '0x00D6', + name: 'Timex Group USA, Inc.' + }, + { + decimal: '215', + hexadecimal: '0x00D7', + name: 'Qualcomm Technologies, Inc.' + }, + { + decimal: '216', + hexadecimal: '0x00D8', + name: 'Qualcomm Connected Experiences, Inc.' + }, + { + decimal: '217', + hexadecimal: '0x00D9', + name: 'Voyetra Turtle Beach' + }, + { + decimal: '218', + hexadecimal: '0x00DA', + name: 'txtr GmbH' + }, + { + decimal: '219', + hexadecimal: '0x00DB', + name: 'Biosentronics' + }, + { + decimal: '220', + hexadecimal: '0x00DC', + name: 'Procter & Gamble' + }, + { + decimal: '221', + hexadecimal: '0x00DD', + name: 'Hosiden Corporation' + }, + { + decimal: '222', + hexadecimal: '0x00DE', + name: 'Muzik LLC' + }, + { + decimal: '223', + hexadecimal: '0x00DF', + name: 'Misfit Wearables Corp' + }, + { + decimal: '224', + hexadecimal: '0x00E0', + name: 'Google' + }, + { + decimal: '225', + hexadecimal: '0x00E1', + name: 'Danlers Ltd' + }, + { + decimal: '226', + hexadecimal: '0x00E2', + name: 'Semilink Inc' + }, + { + decimal: '227', + hexadecimal: '0x00E3', + name: 'inMusic Brands, Inc' + }, + { + decimal: '228', + hexadecimal: '0x00E4', + name: 'L.S. Research Inc.' + }, + { + decimal: '229', + hexadecimal: '0x00E5', + name: 'Eden Software Consultants Ltd.' + }, + { + decimal: '230', + hexadecimal: '0x00E6', + name: 'Freshtemp' + }, + { + decimal: '231', + hexadecimal: '0x00E7', + name: 'KS Technologies' + }, + { + decimal: '232', + hexadecimal: '0x00E8', + name: 'ACTS Technologies' + }, + { + decimal: '233', + hexadecimal: '0x00E9', + name: 'Vtrack Systems' + }, + { + decimal: '234', + hexadecimal: '0x00EA', + name: 'Nielsen-Kellerman Company' + }, + { + decimal: '235', + hexadecimal: '0x00EB', + name: 'Server Technology, Inc.' + }, + { + decimal: '236', + hexadecimal: '0x00EC', + name: 'BioResearch Associates' + }, + { + decimal: '237', + hexadecimal: '0x00ED', + name: 'Jolly Logic, LLC' + }, + { + decimal: '238', + hexadecimal: '0x00EE', + name: 'Above Average Outcomes, Inc.' + }, + { + decimal: '239', + hexadecimal: '0x00EF', + name: 'Bitsplitters GmbH' + }, + { + decimal: '240', + hexadecimal: '0x00F0', + name: 'PayPal, Inc.' + }, + { + decimal: '241', + hexadecimal: '0x00F1', + name: 'Witron Technology Limited' + }, + { + decimal: '242', + hexadecimal: '0x00F2', + name: 'Aether Things Inc. (formerly Morse Project Inc.)' + }, + { + decimal: '243', + hexadecimal: '0x00F3', + name: 'Kent Displays Inc.' + }, + { + decimal: '244', + hexadecimal: '0x00F4', + name: 'Nautilus Inc.' + }, + { + decimal: '245', + hexadecimal: '0x00F5', + name: 'Smartifier Oy' + }, + { + decimal: '246', + hexadecimal: '0x00F6', + name: 'Elcometer Limited' + }, + { + decimal: '247', + hexadecimal: '0x00F7', + name: 'VSN Technologies Inc.' + }, + { + decimal: '248', + hexadecimal: '0x00F8', + name: 'AceUni Corp., Ltd.' + }, + { + decimal: '249', + hexadecimal: '0x00F9', + name: 'StickNFind' + }, + { + decimal: '250', + hexadecimal: '0x00FA', + name: 'Crystal Code AB' + }, + { + decimal: '251', + hexadecimal: '0x00FB', + name: 'KOUKAAM a.s.' + }, + { + decimal: '252', + hexadecimal: '0x00FC', + name: 'Delphi Corporation' + }, + { + decimal: '253', + hexadecimal: '0x00FD', + name: 'ValenceTech Limited' + }, + { + decimal: '254', + hexadecimal: '0x00FE', + name: 'Reserved' + }, + { + decimal: '255', + hexadecimal: '0x00FF', + name: 'Typo Products, LLC' + }, + { + decimal: '256', + hexadecimal: '0x0100', + name: 'TomTom International BV' + }, + { + decimal: '257', + hexadecimal: '0x0101', + name: 'Fugoo, Inc' + }, + { + decimal: '258', + hexadecimal: '0x0102', + name: 'Keiser Corporation' + }, + { + decimal: '259', + hexadecimal: '0x0103', + name: 'Bang & Olufsen A/S' + }, + { + decimal: '260', + hexadecimal: '0x0104', + name: 'PLUS Locations Systems Pty Ltd' + }, + { + decimal: '261', + hexadecimal: '0x0105', + name: 'Ubiquitous Computing Technology Corporation' + }, + { + decimal: '262', + hexadecimal: '0x0106', + name: 'Innovative Yachtter Solutions' + }, + { + decimal: '263', + hexadecimal: '0x0107', + name: 'William Demant Holding A/S' + }, + { + decimal: '264', + hexadecimal: '0x0108', + name: 'Chicony Electronics Co., Ltd.' + }, + { + decimal: '265', + hexadecimal: '0x0109', + name: 'Atus BV' + }, + { + decimal: '266', + hexadecimal: '0x010A', + name: 'Codegate Ltd.' + }, + { + decimal: '267', + hexadecimal: '0x010B', + name: 'ERi, Inc.' + }, + { + decimal: '268', + hexadecimal: '0x010C', + name: 'Transducers Direct, LLC' + }, + { + decimal: '269', + hexadecimal: '0x010D', + name: 'Fujitsu Ten Limited' + }, + { + decimal: '270', + hexadecimal: '0x010E', + name: 'Audi AG' + }, + { + decimal: '271', + hexadecimal: '0x010F', + name: 'HiSilicon Technologies Co., Ltd.' + }, + { + decimal: '272', + hexadecimal: '0x0110', + name: 'Nippon Seiki Co., Ltd.' + }, + { + decimal: '273', + hexadecimal: '0x0111', + name: 'Steelseries ApS' + }, + { + decimal: '274', + hexadecimal: '0x0112', + name: 'vyzybl Inc.' + }, + { + decimal: '275', + hexadecimal: '0x0113', + name: 'Openbrain Technologies, Co., Ltd.' + }, + { + decimal: '276', + hexadecimal: '0x0114', + name: 'Xensr' + }, + { + decimal: '277', + hexadecimal: '0x0115', + name: 'e.solutions' + }, + { + decimal: '278', + hexadecimal: '0x0116', + name: '1OAK Technologies' + }, + { + decimal: '279', + hexadecimal: '0x0117', + name: 'Wimoto Technologies Inc' + }, + { + decimal: '280', + hexadecimal: '0x0118', + name: 'Radius Networks, Inc.' + }, + { + decimal: '281', + hexadecimal: '0x0119', + name: 'Wize Technology Co., Ltd.' + }, + { + decimal: '282', + hexadecimal: '0x011A', + name: 'Qualcomm Labs, Inc.' + }, + { + decimal: '283', + hexadecimal: '0x011B', + name: 'Aruba Networks' + }, + { + decimal: '284', + hexadecimal: '0x011C', + name: 'Baidu' + }, + { + decimal: '285', + hexadecimal: '0x011D', + name: 'Arendi AG' + }, + { + decimal: '286', + hexadecimal: '0x011E', + name: 'Skoda Auto a.s.' + }, + { + decimal: '287', + hexadecimal: '0x011F', + name: 'Volkswagon AG' + }, + { + decimal: '288', + hexadecimal: '0x0120', + name: 'Porsche AG' + }, + { + decimal: '289', + hexadecimal: '0x0121', + name: 'Sino Wealth Electronic Ltd.' + }, + { + decimal: '290', + hexadecimal: '0x0122', + name: 'AirTurn, Inc.' + }, + { + decimal: '291', + hexadecimal: '0x0123', + name: 'Kinsa, Inc.' + }, + { + decimal: '292', + hexadecimal: '0x0124', + name: 'HID Global' + }, + { + decimal: '293', + hexadecimal: '0x0125', + name: 'SEAT es' + }, + { + decimal: '294', + hexadecimal: '0x0126', + name: 'Promethean Ltd.' + }, + { + decimal: '295', + hexadecimal: '0x0127', + name: 'Salutica Allied Solutions' + }, + { + decimal: '296', + hexadecimal: '0x0128', + name: 'GPSI Group Pty Ltd' + }, + { + decimal: '297', + hexadecimal: '0x0129', + name: 'Nimble Devices Oy' + }, + { + decimal: '298', + hexadecimal: '0x012A', + name: 'Changzhou Yongse Infotech Co., Ltd' + }, + { + decimal: '299', + hexadecimal: '0x012B', + name: 'SportIQ' + }, + { + decimal: '300', + hexadecimal: '0x012C', + name: 'TEMEC Instruments B.V.' + }, + { + decimal: '301', + hexadecimal: '0x012D', + name: 'Sony Corporation' + }, + { + decimal: '302', + hexadecimal: '0x012E', + name: 'ASSA ABLOY' + }, + { + decimal: '303', + hexadecimal: '0x012F', + name: 'Clarion Co., Ltd.' + }, + { + decimal: '304', + hexadecimal: '0x0130', + name: 'Warehouse Innovations' + }, + { + decimal: '305', + hexadecimal: '0x0131', + name: 'Cypress Semiconductor Corporation' + }, + { + decimal: '306', + hexadecimal: '0x0132', + name: 'MADS Inc' + }, + { + decimal: '307', + hexadecimal: '0x0133', + name: 'Blue Maestro Limited' + }, + { + decimal: '308', + hexadecimal: '0x0134', + name: 'Resolution Products, Inc.' + }, + { + decimal: '309', + hexadecimal: '0x0135', + name: 'Airewear LLC' + }, + { + decimal: '310', + hexadecimal: '0x0136', + name: 'ETC sp. z.o.o.' + }, + { + decimal: '311', + hexadecimal: '0x0137', + name: 'Prestigio Plaza Ltd.' + }, + { + decimal: '312', + hexadecimal: '0x0138', + name: 'NTEO Inc.' + }, + { + decimal: '313', + hexadecimal: '0x0139', + name: 'Focus Systems Corporation' + }, + { + decimal: '314', + hexadecimal: '0x013A', + name: 'Tencent Holdings Limited' + }, + { + decimal: '315', + hexadecimal: '0x013B', + name: 'Allegion' + }, + { + decimal: '316', + hexadecimal: '0x013C', + name: 'Murata Manufacuring Co., Ltd.' + }, + { + decimal: '318', + hexadecimal: '0x013E', + name: 'Nod, Inc.' + }, + { + decimal: '319', + hexadecimal: '0x013F', + name: 'B&B Manufacturing Company' + }, + { + decimal: '320', + hexadecimal: '0x0140', + name: 'Alpine Electronics (China) Co., Ltd' + }, + { + decimal: '321', + hexadecimal: '0x0141', + name: 'FedEx Services' + }, + { + decimal: '322', + hexadecimal: '0x0142', + name: 'Grape Systems Inc.' + }, + { + decimal: '323', + hexadecimal: '0x0143', + name: 'Bkon Connect' + }, + { + decimal: '324', + hexadecimal: '0x0144', + name: 'Lintech GmbH' + }, + { + decimal: '325', + hexadecimal: '0x0145', + name: 'Novatel Wireless' + }, + { + decimal: '326', + hexadecimal: '0x0146', + name: 'Ciright' + }, + { + decimal: '327', + hexadecimal: '0x0147', + name: 'Mighty Cast, Inc.' + }, + { + decimal: '328', + hexadecimal: '0x0148', + name: 'Ambimat Electronics' + }, + { + decimal: '329', + hexadecimal: '0x0149', + name: 'Perytons Ltd.' + }, + { + decimal: '330', + hexadecimal: '0x014A', + name: 'Tivoli Audio, LLC' + }, + { + decimal: '331', + hexadecimal: '0x014B', + name: 'Master Lock' + }, + { + decimal: '332', + hexadecimal: '0x014C', + name: 'Mesh-Net Ltd' + }, + { + decimal: '333', + hexadecimal: '0x014D', + name: 'Huizhou Desay SV Automotive CO., LTD.' + }, + { + decimal: '334', + hexadecimal: '0x014E', + name: 'Tangerine, Inc.' + }, + { + decimal: '335', + hexadecimal: '0x014F', + name: 'B&W Group Ltd.' + }, + { + decimal: '336', + hexadecimal: '0x0150', + name: 'Pioneer Corporation' + }, + { + decimal: '337', + hexadecimal: '0x0151', + name: 'OnBeep' + }, + { + decimal: '338', + hexadecimal: '0x0152', + name: 'Vernier Software & Technology' + }, + { + decimal: '339', + hexadecimal: '0x0153', + name: 'ROL Ergo' + }, + { + decimal: '340', + hexadecimal: '0x0154', + name: 'Pebble Technology' + }, + { + decimal: '341', + hexadecimal: '0x0155', + name: 'NETATMO' + }, + { + decimal: '342', + hexadecimal: '0x0156', + name: 'Accumulate AB' + }, + { + decimal: '343', + hexadecimal: '0x0157', + name: 'Anhui Huami Information Technology Co., Ltd.' + }, + { + decimal: '344', + hexadecimal: '0x0158', + name: 'Inmite s.r.o.' + }, + { + decimal: '345', + hexadecimal: '0x0159', + name: 'ChefSteps, Inc.' + }, + { + decimal: '346', + hexadecimal: '0x015A', + name: 'micas AG' + }, + { + decimal: '347', + hexadecimal: '0x015B', + name: 'Biomedical Research Ltd.' + }, + { + decimal: '348', + hexadecimal: '0x015C', + name: 'Pitius Tec S.L.' + }, + { + decimal: '349', + hexadecimal: '0x015D', + name: 'Estimote, Inc.' + }, + { + decimal: '350', + hexadecimal: '0x015E', + name: 'Unikey Technologies, Inc.' + }, + { + decimal: '351', + hexadecimal: '0x015F', + name: 'Timer Cap Co.' + }, + { + decimal: '352', + hexadecimal: '0x0160', + name: 'AwoX' + }, + { + decimal: '353', + hexadecimal: '0x0161', + name: 'yikes' + }, + { + decimal: '354', + hexadecimal: '0x0162', + name: 'MADSGlobal NZ Ltd.' + }, + { + decimal: '355', + hexadecimal: '0x0163', + name: 'PCH International' + }, + { + decimal: '356', + hexadecimal: '0x0164', + name: 'Qingdao Yeelink Information Technology Co., Ltd.' + }, + { + decimal: '357', + hexadecimal: '0x0165', + name: 'Milwaukee Tool (formerly Milwaukee Electric Tools)' + }, + { + decimal: '358', + hexadecimal: '0x0166', + name: 'MISHIK Pte Ltd' + }, + { + decimal: '359', + hexadecimal: '0x0167', + name: 'Bayer HealthCare' + }, + { + decimal: '360', + hexadecimal: '0x0168', + name: 'Spicebox LLC' + }, + { + decimal: '361', + hexadecimal: '0x0169', + name: 'emberlight' + }, + { + decimal: '362', + hexadecimal: '0x016A', + name: 'Cooper-Atkins Corporation' + }, + { + decimal: '363', + hexadecimal: '0x016B', + name: 'Qblinks' + }, + { + decimal: '364', + hexadecimal: '0x016C', + name: 'MYSPHERA' + }, + { + decimal: '365', + hexadecimal: '​0x016D', + name: 'LifeScan Inc' + }, + { + decimal: '366', + hexadecimal: '​0x016E', + name: 'Volantic AB' + }, + { + decimal: '367', + hexadecimal: '0x016F', + name: 'Podo Labs, Inc' + }, + { + decimal: '368', + hexadecimal: '0x0170', + name: 'Roche Diabetes Care AG' + }, + { + decimal: '368', + hexadecimal: '0x0170', + name: 'Roche Diabetes Care AG' + }, + { + decimal: '369', + hexadecimal: '0x0171', + name: 'Amazon Fulfillment Service' + }, + { + decimal: '370', + hexadecimal: '0x0172', + name: 'Connovate Technology Private Limited' + }, + { + decimal: '371', + hexadecimal: '0x0173', + name: 'Kocomojo, LLC' + }, + { + decimal: '372', + hexadecimal: '0x0174', + name: 'Everykey LLC' + }, + { + decimal: '373', + hexadecimal: '0x0175', + name: 'Dynamic Controls' + }, + { + decimal: '374', + hexadecimal: '0x0176', + name: 'SentriLock' + }, + { + decimal: '375', + hexadecimal: '0x0177', + name: 'I-SYST inc.' + }, + { + decimal: '376', + hexadecimal: '0x0178', + name: 'CASIO COMPUTER CO., LTD.' + }, + { + decimal: '377', + hexadecimal: '0x0179', + name: 'LAPIS Semiconductor Co., Ltd.' + }, + { + decimal: '378', + hexadecimal: '0x017A', + name: 'Telemonitor, Inc.' + }, + { + decimal: '379', + hexadecimal: '0x017B', + name: 'taskit GmbH' + }, + { + decimal: '380', + hexadecimal: '0x017C', + name: 'Daimler AG' + }, + { + decimal: '381', + hexadecimal: '0x017D', + name: 'BatAndCat' + }, + { + decimal: '382', + hexadecimal: '0x017E', + name: 'BluDotz Ltd' + }, + { + decimal: '383', + hexadecimal: '0x017F', + name: 'XTel ApS' + }, + { + decimal: '384', + hexadecimal: '0x0180', + name: 'Gigaset Communications GmbH' + }, + { + decimal: '385', + hexadecimal: '0x0181', + name: 'Gecko Health Innovations, Inc.' + }, + { + decimal: '386', + hexadecimal: '0x0182', + name: 'HOP Ubiquitous' + }, + { + decimal: '387', + hexadecimal: '0x0183', + name: '### EMPTY ###' + }, + { + decimal: '388', + hexadecimal: '0x0184', + name: 'Nectar' + }, + { + decimal: '389', + hexadecimal: '0x0185', + name: 'bel\'apps LLC' + }, + { + decimal: '390', + hexadecimal: '0x0186', + name: 'CORE Lighting Ltd' + }, + { + decimal: '391', + hexadecimal: '0x0187', + name: 'Seraphim Sense Ltd' + }, + { + decimal: '392', + hexadecimal: '0x0188', + name: 'Unico RBC' + }, + { + decimal: '393', + hexadecimal: '0x0189', + name: 'Physical Enterprises Inc.' + }, + { + decimal: '394', + hexadecimal: '0x018A', + name: 'Able Trend Technology Limited' + }, + { + decimal: '395', + hexadecimal: '0x018B', + name: 'Konica Minolta, Inc.' + }, + { + decimal: '396', + hexadecimal: '0x018C', + name: 'Wilo SE' + }, + { + decimal: '397', + hexadecimal: '0x018D', + name: 'Extron Design Services' + }, + { + decimal: '398', + hexadecimal: '0x018E', + name: 'Fitbit, Inc.' + }, + { + decimal: '399', + hexadecimal: '0x018F', + name: 'Fireflies Systems' + }, + { + decimal: '400', + hexadecimal: '0x0190', + name: 'Intelletto Technologies Inc' + }, + { + decimal: '401', + hexadecimal: '0x0191', + name: 'FDK CORPORATION' + }, + { + decimal: '402', + hexadecimal: '0x0192', + name: 'Cloudleaf, Inc' + }, + { + decimal: '403', + hexadecimal: '0x0193', + name: 'Maveric Automation LLC' + }, + { + decimal: '404', + hexadecimal: '0x0194', + name: 'Acoustic Stream Corporation' + }, + { + decimal: '405', + hexadecimal: '0x0195', + name: 'Zuli' + }, + { + decimal: '406', + hexadecimal: '0x0196', + name: 'Paxton Access Ltd' + }, + { + decimal: '407', + hexadecimal: '0x0197', + name: 'WiSilica Inc' + }, + { + decimal: '408', + hexadecimal: '0x0198', + name: 'Vengit Limited' + }, + { + decimal: '409', + hexadecimal: '0x0199', + name: 'SALTO SYSTEMS S.L.' + }, + { + decimal: '410', + hexadecimal: '0x019A', + name: 'T-Engine Forum' + }, + { + decimal: '411', + hexadecimal: '0x019B', + name: 'CUBETECH s.r.o.' + }, + { + decimal: '412', + hexadecimal: '0x019C', + name: 'Cokiya Incorporated' + }, + { + decimal: '413', + hexadecimal: '0x019D', + name: 'CVS Health' + }, + { + decimal: '414', + hexadecimal: '0x019E', + name: 'Ceruus' + }, + { + decimal: '415', + hexadecimal: '0x019F', + name: 'Strainstall Ltd' + }, + { + decimal: '416', + hexadecimal: '0x01A0', + name: 'Channel Enterprises (HK) Ltd.' + }, + { + decimal: '417', + hexadecimal: '0x01A1', + name: 'FIAMM' + }, + { + decimal: '418', + hexadecimal: '0x01A2', + name: 'GIGALANE.CO.,LTD' + }, + { + decimal: '419', + hexadecimal: '0x01A3', + name: 'EROAD' + }, + { + decimal: '420', + hexadecimal: '0x01A4', + name: 'Mine Safety Appliances' + }, + { + decimal: '421', + hexadecimal: '0x01A5', + name: 'Icon Health and Fitness' + }, + { + decimal: '422', + hexadecimal: '0x01A6', + name: 'Asandoo GmbH' + }, + { + decimal: '423', + hexadecimal: '0x01A7', + name: 'ENERGOUS CORPORATION' + }, + { + decimal: '424', + hexadecimal: '0x01A8', + name: 'Taobao' + }, + { + decimal: '425', + hexadecimal: '0x01A9', + name: 'Canon Inc.' + }, + { + decimal: '426', + hexadecimal: '0x01AA', + name: 'Geophysical Technology Inc.' + }, + { + decimal: '427', + hexadecimal: '0x01AB', + name: 'Facebook, Inc.' + }, + { + decimal: '428', + hexadecimal: '0x01AC', + name: 'Nipro Diagnostics, Inc.' + }, + { + decimal: '429', + hexadecimal: '0x01AD', + name: 'FlightSafety International' + }, + { + decimal: '430', + hexadecimal: '0x01AE', + name: 'Earlens Corporation' + }, + { + decimal: '431', + hexadecimal: '0x01AF', + name: 'Sunrise Micro Devices, Inc.' + }, + { + decimal: '432', + hexadecimal: '0x01B0', + name: 'Star Micronics Co., Ltd.' + }, + { + decimal: '433', + hexadecimal: '0x01B1', + name: 'Netizens Sp. z o.o.' + }, + { + decimal: '434', + hexadecimal: '0x01B2', + name: 'Nymi Inc.' + }, + { + decimal: '435', + hexadecimal: '​0x01B3', + name: 'Nytec, Inc.' + }, + { + decimal: '436', + hexadecimal: '​0x01B4', + name: 'Trineo Sp. z o.o.' + }, + { + decimal: '437', + hexadecimal: '​0x01B5', + name: 'Nest Labs Inc.' + }, + { + decimal: '438', + hexadecimal: '​0x01B6', + name: 'LM Technologies Ltd' + }, + { + decimal: '439', + hexadecimal: '​0x01B7', + name: 'General Electric Company' + }, + { + decimal: '440', + hexadecimal: '​0x01B8', + name: 'i+D3 S.L.' + }, + { + decimal: '441', + hexadecimal: '​0x01B9', + name: 'HANA Micron' + }, + { + decimal: '442', + hexadecimal: '​0x01BA', + name: 'Stages Cycling LLC' + }, + { + decimal: '443', + hexadecimal: '​0x01BB', + name: 'Cochlear Bone Anchored Solutions AB' + }, + { + decimal: '444', + hexadecimal: '​0x01BC', + name: 'SenionLab AB' + }, + { + decimal: '445', + hexadecimal: '​0x01BD', + name: 'Syszone Co., Ltd' + }, + { + decimal: '446', + hexadecimal: '​0x01BE', + name: 'Pulsate Mobile Ltd.' + }, + { + decimal: '447', + hexadecimal: '​0x01BF', + name: 'Hong Kong HunterSun Electronic Limited' + }, + { + decimal: '448', + hexadecimal: '​​0x01C0', + name: 'pironex GmbH' + }, + { + decimal: '449', + hexadecimal: '​​0x01C1', + name: 'BRADATECH Corp.' + }, + { + decimal: '450', + hexadecimal: '​​0x01C2', + name: 'Transenergooil AG' + }, + { + decimal: '451', + hexadecimal: '​​0x01C3', + name: 'Bunch' + }, + { + decimal: '452', + hexadecimal: '​​0x01C4', + name: 'DME Microelectronics' + }, + { + decimal: '453', + hexadecimal: '​​0x01C5', + name: 'Bitcraze AB' + }, + { + decimal: '454', + hexadecimal: '​​0x01C6', + name: 'HASWARE Inc.' + }, + { + decimal: '455', + hexadecimal: '​​0x01C7', + name: 'Abiogenix Inc.' + }, + { + decimal: '456', + hexadecimal: '​​0x01C8', + name: 'Poly-Control ApS' + }, + { + decimal: '457', + hexadecimal: '​​0x01C9', + name: 'Avi-on' + }, + { + decimal: '458', + hexadecimal: '​​0x01CA', + name: 'Laerdal Medical AS' + }, + { + decimal: '459', + hexadecimal: '​​0x01CB', + name: 'Fetch My Pet' + }, + { + decimal: '460', + hexadecimal: '​​0x01CC', + name: 'Sam Labs Ltd.' + }, + { + decimal: '461', + hexadecimal: '​​0x01CD', + name: 'Chengdu Synwing Technology Ltd' + }, + { + decimal: '462', + hexadecimal: '​​0x01CE', + name: 'HOUWA SYSTEM DESIGN, k.k.' + }, + { + decimal: '463', + hexadecimal: '​​0x01CF', + name: 'BSH' + }, + { + decimal: '464', + hexadecimal: '​​0x01D0', + name: 'Primus Inter Pares Ltd' + }, + { + decimal: '465', + hexadecimal: '​​0x01D1', + name: 'August' + }, + { + decimal: '466', + hexadecimal: '​​0x01D2', + name: 'Gill Electronics' + }, + { + decimal: '467', + hexadecimal: '​​0x01D3', + name: 'Sky Wave Design' + }, + { + decimal: '468', + hexadecimal: '​​0x01D4', + name: 'Newlab S.r.l.' + }, + { + decimal: '469', + hexadecimal: '​​0x01D5', + name: 'ELAD srl' + }, + { + decimal: '470', + hexadecimal: '​​0x01D6', + name: 'G-wearables inc.' + }, + { + decimal: '471', + hexadecimal: '​​0x01D7', + name: 'Squadrone Systems Inc.' + }, + { + decimal: '472', + hexadecimal: '​​0x01D8', + name: 'Code Corporation' + }, + { + decimal: '473', + hexadecimal: '​​0x01D9', + name: 'Savant Systems LLC' + }, + { + decimal: '474', + hexadecimal: '​​0x01DA', + name: '​Logitech International SA' + }, + { + decimal: '475', + hexadecimal: '​​0x01DB', + name: '​Innblue Consulting' + }, + { + decimal: '476', + hexadecimal: '​​0x01DC', + name: 'iParking Ltd.' + }, + { + decimal: '477', + hexadecimal: '​​0x01DD', + name: '​Koninklijke Philips Electronics N.V.' + }, + { + decimal: '478', + hexadecimal: '​​0x01DE', + name: '​Minelab Electronics Pty Limited' + }, + { + decimal: '479', + hexadecimal: '​​0x01DF', + name: '​​Bison Group Ltd.' + }, + { + decimal: '480', + hexadecimal: '​​0x01E0', + name: '​​​Widex A/S' + }, + { + decimal: '481', + hexadecimal: '​​0x01E1', + name: '​Jolla Ltd' + }, + { + decimal: '482', + hexadecimal: '​​0x01E2', + name: '​​Lectronix, Inc.' + }, + { + decimal: '483', + hexadecimal: '​​0x01E3', + name: '​​Caterpillar Inc' + }, + { + decimal: '484', + hexadecimal: '​​0x01E4', + name: '​​Freedom Innovations' + }, + { + decimal: '485', + hexadecimal: '​​0x01E5', + name: '​​Dynamic Devices Ltd' + }, + { + decimal: '486', + hexadecimal: '​​0x01E6', + name: '​​Technology Solutions (UK) Ltd' + }, + { + decimal: '487', + hexadecimal: '​​0x01E7', + name: '​​IPS Group Inc.' + }, + { + decimal: '488', + hexadecimal: '​​0x01E8', + name: '​STIR' + }, + { + decimal: '489', + hexadecimal: '0x01E9', + name: '​Sano, Inc' + }, + { + decimal: '490', + hexadecimal: '0x01EA', + name: '​Advanced Application Design, Inc.​' + }, + { + decimal: '491', + hexadecimal: '0x01EB', + name: '​AutoMap LLC​​' + }, + { + decimal: '492', + hexadecimal: '0x01EC', + name: '​​Spreadtrum Communications Shanghai Ltd' + }, + { + decimal: '​493', + hexadecimal: '0x01ED', + name: '​​CuteCircuit LTD' + }, + { + decimal: '​494', + hexadecimal: '0x01EE', + name: '​​​Valeo Service' + }, + { + decimal: '​495', + hexadecimal: '0x01EF', + name: '​​​Fullpower Technologies, Inc.' + }, + { + decimal: '​496', + hexadecimal: '0x01F0', + name: 'KloudNation' + }, + { + decimal: '​497', + hexadecimal: '0x01F1', + name: '​Zebra Technologies Corporation' + }, + { + decimal: '​498', + hexadecimal: '0x01F2', + name: 'Itron, Inc.' + }, + { + decimal: '​499', + hexadecimal: '0x01F3', + name: 'The University of Tokyo' + }, + { + decimal: '​500', + hexadecimal: '0x01F4', + name: '​UTC Fire and Security' + }, + { + decimal: '​501', + hexadecimal: '0x01F5', + name: 'Cool Webthings Limited' + }, + { + decimal: '​502', + hexadecimal: '0x01F6', + name: 'DJO Global' + }, + { + decimal: '​503', + hexadecimal: '0x01F7', + name: '​Gelliner Limited' + }, + { + decimal: '​504', + hexadecimal: '0x01F8', + name: '​Anyka (Guangzhou) Microelectronics Technology Co, LTD' + }, + { + decimal: '​505', + hexadecimal: '0x01F9', + name: '​Medtronic, Inc.' + }, + { + decimal: '​506', + hexadecimal: '0x01FA', + name: '​Gozio, Inc.' + }, + { + decimal: '​507', + hexadecimal: '0x01FB', + name: '​Form Lifting, LLC' + }, + { + decimal: '​508', + hexadecimal: '0x01FC', + name: 'Wahoo Fitness, LLC' + }, + { + decimal: '​509', + hexadecimal: '0x01FD', + name: 'Kontakt Micro-Location Sp. z o.o.' + }, + { + decimal: '​510', + hexadecimal: '0x01FE', + name: 'Radio System Corporation' + }, + { + decimal: '​511', + hexadecimal: '0x01FF', + name: 'Freescale Semiconductor, Inc.' + }, + { + decimal: '​512', + hexadecimal: '0x0200', + name: 'Verifone Systems PTe Ltd. Taiwan Branch' + }, + { + decimal: '​513', + hexadecimal: '0x0201', + name: '​AR Timing' + }, + { + decimal: '​514', + hexadecimal: '0x0202', + name: '​​Rigado LLC' + }, + { + decimal: '​515', + hexadecimal: '0x0203', + name: '​​Kemppi Oy' + }, + { + decimal: '​516', + hexadecimal: '0x0204', + name: '​​Tapcentive Inc.' + }, + { + decimal: '​517', + hexadecimal: '0x0205', + name: '​​Smartbotics Inc.' + }, + { + decimal: '​518', + hexadecimal: '0x0206', + name: 'Otter Products, LLC​' + }, + { + decimal: '​519', + hexadecimal: '0x0207', + name: 'STEMP Inc.' + }, + { + decimal: '​520', + hexadecimal: '0x0208', + name: '​LumiGeek LLC' + }, + { + decimal: '​521', + hexadecimal: '0x0209', + name: '​InvisionHeart Inc.' + }, + { + decimal: '​522', + hexadecimal: '0x02​0A', + name: '​Macnica Inc.' + }, + { + decimal: '​523', + hexadecimal: '0x020B​', + name: 'Jaguar Land Rover Limited' + }, + { + decimal: '​524', + hexadecimal: '0x020C​', + name: '​CoroWare Technologies, Inc' + }, + { + decimal: '​525', + hexadecimal: '0x020D​', + name: '​Simplo Technology Co., LTD' + }, + { + decimal: '​526', + hexadecimal: '0x020E​', + name: 'Omron Healthcare Co., LTD' + }, + { + decimal: '​527', + hexadecimal: '0x020F​', + name: 'Comodule GMBH' + }, + { + decimal: '​528', + hexadecimal: '0x0210​', + name: 'ikeGPS' + }, + { + decimal: '​529', + hexadecimal: '0x0211​', + name: 'Telink Semiconductor Co. Ltd' + }, + { + decimal: '​530', + hexadecimal: '0x0212​', + name: '​Interplan Co., Ltd' + }, + { + decimal: '​531', + hexadecimal: '0x0213​', + name: '​Wyler AG' + }, + { + decimal: '​532', + hexadecimal: '0x0214​', + name: 'IK Multimedia Production srl' + }, + { + decimal: '​533', + hexadecimal: '0x0215​', + name: '​Lukoton Experience Oy' + }, + { + decimal: '​534', + hexadecimal: '0x0216​', + name: 'MTI Ltd' + }, + { + decimal: '​535', + hexadecimal: '0x0217​', + name: 'Tech4home, Lda' + }, + { + decimal: '​536', + hexadecimal: '0x0218', + name: '​Hiotech AB' + }, + { + decimal: '​537', + hexadecimal: '0x0219', + name: '​DOTT Limited' + }, + { + decimal: '​538', + hexadecimal: '0x021A', + name: '​Blue Speck Labs, LLC' + }, + { + decimal: '​539', + hexadecimal: '0x021B', + name: '​Cisco Systems Inc' + }, + { + decimal: '​540', + hexadecimal: '0x021C', + name: '​Mobicomm Inc' + }, + { + decimal: '​541', + hexadecimal: '0x021D', + name: '​​Edamic' + }, + { + decimal: '​542', + hexadecimal: '0x021E', + name: '​​Goodnet Ltd' + }, + { + decimal: '​543', + hexadecimal: '0x021F', + name: '​​Luster Leaf Products Inc' + }, + { + decimal: '​544', + hexadecimal: '0x0220', + name: '​​Manus Machina BV' + }, + { + decimal: '​545', + hexadecimal: '0x0221', + name: '​​Mobiquity Networks Inc' + }, + { + decimal: '​546', + hexadecimal: '0x0222', + name: '​​Praxis Dynamics' + }, + { + decimal: '​547', + hexadecimal: '0x0223', + name: '​​Philip Morris Products S.A.' + }, + { + decimal: '​548', + hexadecimal: '0x0224', + name: '​​Comarch SA' + }, + { + decimal: '​549', + hexadecimal: '0x0225', + name: 'Nestlé Nespresso S.A.' + }, + { + decimal: '​550', + hexadecimal: '0x0226', + name: '​Merlinia A/S' + }, + { + decimal: '​551', + hexadecimal: '0x0227', + name: '​LifeBEAM Technologies' + }, + { + decimal: '​552', + hexadecimal: '0x0228', + name: '​Twocanoes Labs, LLC' + }, + { + decimal: '​553', + hexadecimal: '0x0229', + name: '​​Muoverti Limited' + }, + { + decimal: '​554', + hexadecimal: '0X022A', + name: 'Stamer Musikanlagen GMBH' + }, + { + decimal: '​555', + hexadecimal: '0x022B', + name: 'Tesla Motors' + }, + { + decimal: '​556', + hexadecimal: '0x022C', + name: 'Pharynks Corporation' + }, + { + decimal: '​557', + hexadecimal: '0x022D', + name: 'Lupine' + }, + { + decimal: '​558', + hexadecimal: '0x022E', + name: 'Siemens AG' + }, + { + decimal: '​559', + hexadecimal: '0x022F', + name: 'Huami (Shanghai) Culture Communication CO., LTD' + }, + { + decimal: '​560', + hexadecimal: '0x0230', + name: 'Foster Electric Company, Ltd' + }, + { + decimal: '​561', + hexadecimal: '0x0231', + name: 'ETA SA' + }, + { + decimal: '​562', + hexadecimal: '0x0232', + name: 'x-Senso Solutions Kft' + }, + { + decimal: '​563', + hexadecimal: '0x0233', + name: 'Shenzhen SuLong Communication Ltd' + }, + { + decimal: '​564', + hexadecimal: '0x0234', + name: 'FengFan (BeiJing) Technology Co, Ltd' + }, + { + decimal: '​565', + hexadecimal: '0x0235', + name: 'Qrio Inc' + }, + { + decimal: '​566', + hexadecimal: '0x0236', + name: 'Pitpatpet Ltd' + }, + { + decimal: '​567', + hexadecimal: '0x0237', + name: 'MSHeli s.r.l.' + }, + { + decimal: '​568', + hexadecimal: '0x0238', + name: 'Trakm8 Ltd' + }, + { + decimal: '​569', + hexadecimal: '0x0239', + name: '​JIN CO, Ltd' + }, + { + decimal: '​570', + hexadecimal: '0x023A', + name: '​Alatech Technology' + }, + { + decimal: '​571', + hexadecimal: '0x023B', + name: 'Beijing CarePulse Electronic Technology Co, Ltd' + }, + { + decimal: '​572', + hexadecimal: '0x023C', + name: 'Awarepoint' + }, + { + decimal: '​573', + hexadecimal: '0x023D', + name: '​ViCentra B.V.' + }, + { + decimal: '​574', + hexadecimal: '0x023E', + name: '​Raven Industries' + }, + { + decimal: '​575', + hexadecimal: '0x023F', + name: '​WaveWare Technologies' + }, + { + decimal: '​576', + hexadecimal: '0x0240', + name: 'Argenox Technologies' + }, + { + decimal: '​577', + hexadecimal: '0x0241', + name: 'Bragi GmbH' + }, + { + decimal: '​578', + hexadecimal: '0x0242', + name: '16Lab Inc' + }, + { + decimal: '​579', + hexadecimal: '0x0243', + name: 'Masimo Corp' + }, + { + decimal: '​580', + hexadecimal: '0x0244', + name: 'Iotera Inc.' + }, + { + decimal: '​581', + hexadecimal: '0x0245', + name: 'Endress+Hauser' + }, + { + decimal: '​582', + hexadecimal: '0x0246', + name: '​ACKme Networks, Inc.' + }, + { + decimal: '​583', + hexadecimal: '0x0247', + name: '​FiftyThree Inc.' + }, + { + decimal: '​584', + hexadecimal: '0x0248', + name: '​Parker Hannifin Corp​' + }, + { + decimal: '​585', + hexadecimal: '0x0249', + name: '​Transcranial Ltd' + }, + { + decimal: '​586', + hexadecimal: '0x024A', + name: '​​Uwatec AG' + }, + { + decimal: '​587', + hexadecimal: '0x024B', + name: '​​Orlan LLC' + }, + { + decimal: '​588', + hexadecimal: '0x024C', + name: '​Blue Clover Devices' + }, + { + decimal: '​589', + hexadecimal: '0x024D', + name: '​M-Way Solutions GmbH' + }, + { + decimal: '​590', + hexadecimal: '0x024E', + name: '​Microtronics Engineering GmbH' + }, + { + decimal: '​591', + hexadecimal: '0x024F', + name: 'Schneider Schreibgeräte GmbH' + }, + { + decimal: '​592', + hexadecimal: '0x0250', + name: '​Sapphire Circuits LLC' + }, + { + decimal: '​593', + hexadecimal: '0x0251', + name: '​​Lumo Bodytech Inc.' + }, + { + decimal: '​594', + hexadecimal: '0x0252', + name: '​​UKC Technosolution' + }, + { + decimal: '​595', + hexadecimal: '0x0253', + name: 'Xicato Inc.' + }, + { + decimal: '​596', + hexadecimal: '0x0254', + name: 'Playbrush' + }, + { + decimal: '​597', + hexadecimal: '0x0255', + name: '​Dai Nippon Printing Co., Ltd.' + }, + { + decimal: '​598', + hexadecimal: '0x0256', + name: '​​G24 Power Limited' + }, + { + decimal: '​599', + hexadecimal: '0x0257', + name: '​​AdBabble Local Commerce Inc.' + }, + { + decimal: '​600', + hexadecimal: '0x0258', + name: 'Devialet SA' + }, + { + decimal: '​601', + hexadecimal: '​0x0259', + name: '​ALTYOR' + }, + { + decimal: '​602', + hexadecimal: '​0x025A', + name: '​University of Applied Sciences Valais/Haute Ecole Valaisanne' + }, + { + decimal: '​603', + hexadecimal: '​0x025B', + name: '​Five Interactive, LLC dba Zendo' + }, + { + decimal: '​604', + hexadecimal: '​0x025C', + name: '​NetEase (Hangzhou) Network co.Ltd.' + }, + { + decimal: '​605', + hexadecimal: '​0x025D', + name: '​Lexmark International Inc.' + }, + { + decimal: '​606', + hexadecimal: '​0x025E', + name: '​Fluke Corporation' + }, + { + decimal: '607​', + hexadecimal: '​0x025F', + name: 'Yardarm Technologies​' + }, + { + decimal: '​608', + hexadecimal: '​0x0260', + name: '​SensaRx' + }, + { + decimal: '​609', + hexadecimal: '​0x0261', + name: '​SECVRE GmbH' + }, + { + decimal: '610​', + hexadecimal: '​0x0262', + name: '​Glacial Ridge Technologies' + }, + { + decimal: '​611', + hexadecimal: '​0x0263', + name: '​Identiv, Inc.' + }, + { + decimal: '612​', + hexadecimal: '0x0264​', + name: 'DDS, Inc.' + }, + { + decimal: '​613', + hexadecimal: '​0x0265', + name: '​SMK Corporation' + }, + { + decimal: '​614', + hexadecimal: '​0x0266', + name: '​Schawbel Technologies LLC' + }, + { + decimal: '​615', + hexadecimal: '​0x0267', + name: '​XMI Systems SA' + }, + { + decimal: '​616', + hexadecimal: '​0x0268', + name: '​Cerevo' + }, + { + decimal: '​617', + hexadecimal: '​0x0269', + name: '​​Torrox GmbH & Co KG' + }, + { + decimal: '​618', + hexadecimal: '​0x026A', + name: '​​Gemalto' + }, + { + decimal: '​619', + hexadecimal: '​0x026B', + name: '​DEKA Research & Development Corp.​' + }, + { + decimal: '​620', + hexadecimal: '​0x026C', + name: '​Domster Tadeusz Szydlowski' + }, + { + decimal: '​621', + hexadecimal: '​0x026D', + name: '​Technogym SPA​' + }, + { + decimal: '​622', + hexadecimal: '​0x026E', + name: '​FLEURBAEY BVBA' + }, + { + decimal: '​623', + hexadecimal: '​0x026F', + name: '​Aptcode Solutions​' + }, + { + decimal: '​624', + hexadecimal: '​0x0270', + name: '​LSI ADL Technology​' + }, + { + decimal: '​625', + hexadecimal: '​0x0271', + name: '​Animas Corp' + }, + { + decimal: '​626', + hexadecimal: '​0x0272', + name: '​Alps Electric Co., Ltd.' + }, + { + decimal: '​627', + hexadecimal: '​0x0273', + name: '​OCEASOFT' + }, + { + decimal: '​628', + hexadecimal: '​0x0274', + name: '​Motsai Research' + }, + { + decimal: '​629', + hexadecimal: '​0x0275', + name: '​Geotab' + }, + { + decimal: '​630', + hexadecimal: '​0x0276', + name: '​E.G.O. Elektro-Gerätebau GmbH' + }, + { + decimal: '​631', + hexadecimal: '​0x0277', + name: '​​bewhere inc' + }, + { + decimal: '​632', + hexadecimal: '0x0278​', + name: '​Johnson Outdoors Inc' + }, + { + decimal: '​633', + hexadecimal: '​0x0279', + name: '​steute Schaltgerate GmbH & Co. KG' + }, + { + decimal: '​634', + hexadecimal: '​0x027A', + name: '​Ekomini in​c.​' + }, + { + decimal: '​635', + hexadecimal: '0x027B​', + name: '​​DEFA AS' + }, + { + decimal: '​636', + hexadecimal: '0x027C​', + name: 'Aseptika Ltd​' + }, + { + decimal: '​637', + hexadecimal: '​0x027D', + name: '​HUAWEI Technologies Co., Ltd. ( 华为技术有限公司 )​' + }, + { + decimal: '​638', + hexadecimal: '0x027E​', + name: '​HabitAware, LLC' + }, + { + decimal: '​639', + hexadecimal: '​0x027F', + name: '​ruwido austria gmbh​' + }, + { + decimal: '​640', + hexadecimal: '​0x0280', + name: '​ITEC corporation' + }, + { + decimal: '​641', + hexadecimal: '​0x0281', + name: '​StoneL​' + } +]}; diff --git a/www/js/standards/capability.js b/www/js/standards/capability.js index a1914d7..49147fd 100644 --- a/www/js/standards/capability.js +++ b/www/js/standards/capability.js @@ -18,6 +18,10 @@ var CAPABILITY = function(p) { this.deviceID = null; + this.data = []; + this.ctx = null; + this.first = false; + }; CAPABILITY.prototype.setInternalID = function() { @@ -64,7 +68,174 @@ CAPABILITY.prototype.onError = function(e) { console.error(e); }; +CAPABILITY.prototype.storeData = function(data, alt) { + if (!this.first) { + this.first = true; + return []; + } + var target = alt || this.data; + + if (target.length === 99) { + target = target.slice(1); + } + + target.push(data); + if (alt) { + return target; + } else { + this.data = target; + } + + +}; + +CAPABILITY.prototype.startGraph = function(id) { + + var c; + c = id[0].getContext('2d'); + this.ctx = c; + + this.ctx.fillStyle = '#ffffff'; + this.ctx.fillRect(0,0,300,150); +}; + +CAPABILITY.prototype.generateBlankGraph = function(subID) { + + var _subID = subID || ''; + var xmlns = 'http://www.w3.org/2000/svg'; + + var svgID = this.frameID + _subID + '-svg'; + var text1ID = this.frameID + _subID + '-txt1'; + var lineID = this.frameID + _subID + '-line'; + + var svg = document.createElementNS(xmlns,'svg'); + + svg.setAttributeNS(xmlns,'id',svgID); + svg.setAttributeNS(xmlns,'width',300); + svg.setAttributeNS(xmlns,'height',150); + svg.setAttributeNS(xmlns,'fill', 'blue'); + + var line = document.createElementNS(xmlns,'line'); + + line.setAttributeNS(null,'x1','46'); + line.setAttributeNS(null,'y1','12'); + line.setAttributeNS(null,'x2','280'); + line.setAttributeNS(null,'y2', '12'); + line.setAttributeNS(null,'style','stroke:#bad649;stroke-width:2;'); + + svg.appendChild(line); + + line = document.createElementNS(xmlns,'line'); + + line.setAttributeNS(null,'x1','46'); + line.setAttributeNS(null,'y1','136'); + line.setAttributeNS(null,'x2','280'); + line.setAttributeNS(null,'y2', '136'); + line.setAttributeNS(null,'style','stroke:#bad649;stroke-width:2;'); + + svg.appendChild(line); + + var text = document.createElementNS(xmlns,'text'); + + text.setAttributeNS(null,'id',text1ID); + text.setAttributeNS(null,'x','36'); + text.setAttributeNS(null,'y','15'); + text.setAttributeNS(null,'text-anchor', 'end'); + text.setAttributeNS(null,'style','font-family:"Ubuntu Condensed",sans-serif;font-size:12;fill: #bad649;text-align:right;'); + text.textContent = '000'; + + svg.appendChild(text); + + text = document.createElementNS(xmlns,'text'); + + text.setAttributeNS(null,'id','text2'); + text.setAttributeNS(null,'x','36'); + text.setAttributeNS(null,'y','140'); + text.setAttributeNS(null,'text-anchor', 'end'); + text.setAttributeNS(null,'style','font-family:"Ubuntu Condensed",sans-serif;font-size:12;fill: #bad649;text-align:right;'); + text.textContent = '0'; + + svg.appendChild(text); + + var polyline = document.createElementNS(xmlns,'polyline'); + + polyline.setAttributeNS(null,'id',lineID); + polyline.setAttributeNS(null,'fill','none'); + polyline.setAttributeNS(null,'stroke','#e5f7fd'); + //#e5f7fd + // old #B5C7FF + polyline.setAttributeNS(null,'text-anchor', 'end'); + polyline.setAttributeNS(null,'stroke-width','2'); + + svg.appendChild(polyline); + + return svg; + +}; + +CAPABILITY.prototype.animateGraph = function() { + //This.simpleGraph(this.data); +}; + + +CAPABILITY.prototype.simpleGraph = function(data, subID) { + + var _subID; + var _data; + var text1ID; + var lineID; + + _data = data || this.data; + + _subID = subID || ''; + + lineID = [this.frameID , _subID , '-line'].join(''); + text1ID = [this.frameID , _subID , '-txt1'].join(''); + + if (_data.length > 0) { + var ceiling = _data.reduce(function(p, v) { + return (p > v ? p : v); + }); + + /* Var floor = _data.reduce(function(p, v) { + return (p < v ? p : v); + }); + */ + + var calcArray = []; + + var ceilingLimit = Math.floor(ceiling / 10) * 10; + if (ceilingLimit < ceiling) { + ceilingLimit = Math.floor((ceiling + (ceiling * 0.25)) / 10) * 10; + } + + if (ceilingLimit < 30) { + ceilingLimit = 30; + } + + var scale = 124 / ceilingLimit; + // Var xstep = (280 - 46) / 100; + var xstep = 2.34; + var startX = 46 + (100 - _data.length) * xstep; + + for (var x = 0;x < _data.length;x++) { + + calcArray.push((startX + (x * xstep)).toFixed(2) + ',' + (136 - ((_data[x]) * scale)).toFixed(2)); + + } + + var elm = document.getElementById(lineID); + + elm.setAttribute('points',calcArray.join(' ')); + + elm = document.getElementById(text1ID); + + elm.textContent = ceilingLimit; + + } + +}; diff --git a/www/libs/smoothie.js b/www/libs/smoothie.js new file mode 100644 index 0000000..f50e4ed --- /dev/null +++ b/www/libs/smoothie.js @@ -0,0 +1,803 @@ +// MIT License: +// +// Copyright (c) 2010-2013, Joe Walnes +// 2013-2014, Drew Noakes +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +/** + * Smoothie Charts - http://smoothiecharts.org/ + * (c) 2010-2013, Joe Walnes + * 2013-2014, Drew Noakes + * + * v1.0: Main charting library, by Joe Walnes + * v1.1: Auto scaling of axis, by Neil Dunn + * v1.2: fps (frames per second) option, by Mathias Petterson + * v1.3: Fix for divide by zero, by Paul Nikitochkin + * v1.4: Set minimum, top-scale padding, remove timeseries, add optional timer to reset bounds, by Kelley Reynolds + * v1.5: Set default frames per second to 50... smoother. + * .start(), .stop() methods for conserving CPU, by Dmitry Vyal + * options.interpolation = 'bezier' or 'line', by Dmitry Vyal + * options.maxValue to fix scale, by Dmitry Vyal + * v1.6: minValue/maxValue will always get converted to floats, by Przemek Matylla + * v1.7: options.grid.fillStyle may be a transparent color, by Dmitry A. Shashkin + * Smooth rescaling, by Kostas Michalopoulos + * v1.8: Set max length to customize number of live points in the dataset with options.maxDataSetLength, by Krishna Narni + * v1.9: Display timestamps along the bottom, by Nick and Stev-io + * (https://groups.google.com/forum/?fromgroups#!topic/smoothie-charts/-Ywse8FCpKI%5B1-25%5D) + * Refactored by Krishna Narni, to support timestamp formatting function + * v1.10: Switch to requestAnimationFrame, removed the now obsoleted options.fps, by Gergely Imreh + * v1.11: options.grid.sharpLines option added, by @drewnoakes + * Addressed warning seen in Firefox when seriesOption.fillStyle undefined, by @drewnoakes + * v1.12: Support for horizontalLines added, by @drewnoakes + * Support for yRangeFunction callback added, by @drewnoakes + * v1.13: Fixed typo (#32), by @alnikitich + * v1.14: Timer cleared when last TimeSeries removed (#23), by @davidgaleano + * Fixed diagonal line on chart at start/end of data stream, by @drewnoakes + * v1.15: Support for npm package (#18), by @dominictarr + * Fixed broken removeTimeSeries function (#24) by @davidgaleano + * Minor performance and tidying, by @drewnoakes + * v1.16: Bug fix introduced in v1.14 relating to timer creation/clearance (#23), by @drewnoakes + * TimeSeries.append now deals with out-of-order timestamps, and can merge duplicates, by @zacwitte (#12) + * Documentation and some local variable renaming for clarity, by @drewnoakes + * v1.17: Allow control over font size (#10), by @drewnoakes + * Timestamp text won't overlap, by @drewnoakes + * v1.18: Allow control of max/min label precision, by @drewnoakes + * Added 'borderVisible' chart option, by @drewnoakes + * Allow drawing series with fill but no stroke (line), by @drewnoakes + * v1.19: Avoid unnecessary repaints, and fixed flicker in old browsers having multiple charts in document (#40), by @asbai + * v1.20: Add SmoothieChart.getTimeSeriesOptions and SmoothieChart.bringToFront functions, by @drewnoakes + * v1.21: Add 'step' interpolation mode, by @drewnoakes + * v1.22: Add support for different pixel ratios. Also add optional y limit formatters, by @copacetic + * v1.23: Fix bug introduced in v1.22 (#44), by @drewnoakes + * v1.24: Fix bug introduced in v1.23, re-adding parseFloat to y-axis formatter defaults, by @siggy_sf + * v1.25: Fix bug seen when adding a data point to TimeSeries which is older than the current data, by @Nking92 + * Draw time labels on top of series, by @comolosabia + * Add TimeSeries.clear function, by @drewnoakes + * v1.26: Add support for resizing on high device pixel ratio screens, by @copacetic + * v1.27: Fix bug introduced in v1.26 for non whole number devicePixelRatio values, by @zmbush + * v1.28: Add 'minValueScale' option, by @megawac + */ + +;(function(exports) { + + var Util = { + extend: function() { + arguments[0] = arguments[0] || {}; + for (var i = 1; i < arguments.length; i++) + { + for (var key in arguments[i]) + { + if (arguments[i].hasOwnProperty(key)) + { + if (typeof(arguments[i][key]) === 'object') { + if (arguments[i][key] instanceof Array) { + arguments[0][key] = arguments[i][key]; + } else { + arguments[0][key] = Util.extend(arguments[0][key], arguments[i][key]); + } + } else { + arguments[0][key] = arguments[i][key]; + } + } + } + } + return arguments[0]; + } + }; + + /** + * Initialises a new TimeSeries with optional data options. + * + * Options are of the form (defaults shown): + * + *
+   * {
+   *   resetBounds: true,        // enables/disables automatic scaling of the y-axis
+   *   resetBoundsInterval: 3000 // the period between scaling calculations, in millis
+   * }
+   * 
+ * + * Presentation options for TimeSeries are specified as an argument to SmoothieChart.addTimeSeries. + * + * @constructor + */ + function TimeSeries(options) { + this.options = Util.extend({}, TimeSeries.defaultOptions, options); + this.clear(); + } + + TimeSeries.defaultOptions = { + resetBoundsInterval: 3000, + resetBounds: true + }; + + /** + * Clears all data and state from this TimeSeries object. + */ + TimeSeries.prototype.clear = function() { + this.data = []; + this.maxValue = Number.NaN; // The maximum value ever seen in this TimeSeries. + this.minValue = Number.NaN; // The minimum value ever seen in this TimeSeries. + }; + + /** + * Recalculate the min/max values for this TimeSeries object. + * + * This causes the graph to scale itself in the y-axis. + */ + TimeSeries.prototype.resetBounds = function() { + if (this.data.length) { + // Walk through all data points, finding the min/max value + this.maxValue = this.data[0][1]; + this.minValue = this.data[0][1]; + for (var i = 1; i < this.data.length; i++) { + var value = this.data[i][1]; + if (value > this.maxValue) { + this.maxValue = value; + } + if (value < this.minValue) { + this.minValue = value; + } + } + } else { + // No data exists, so set min/max to NaN + this.maxValue = Number.NaN; + this.minValue = Number.NaN; + } + }; + + /** + * Adds a new data point to the TimeSeries, preserving chronological order. + * + * @param timestamp the position, in time, of this data point + * @param value the value of this data point + * @param sumRepeatedTimeStampValues if timestamp has an exact match in the series, this flag controls + * whether it is replaced, or the values summed (defaults to false.) + */ + TimeSeries.prototype.append = function(timestamp, value, sumRepeatedTimeStampValues) { + // Rewind until we hit an older timestamp + var i = this.data.length - 1; + while (i >= 0 && this.data[i][0] > timestamp) { + i--; + } + + if (i === -1) { + // This new item is the oldest data + this.data.splice(0, 0, [timestamp, value]); + } else if (this.data.length > 0 && this.data[i][0] === timestamp) { + // Update existing values in the array + if (sumRepeatedTimeStampValues) { + // Sum this value into the existing 'bucket' + this.data[i][1] += value; + value = this.data[i][1]; + } else { + // Replace the previous value + this.data[i][1] = value; + } + } else if (i < this.data.length - 1) { + // Splice into the correct position to keep timestamps in order + this.data.splice(i + 1, 0, [timestamp, value]); + } else { + // Add to the end of the array + this.data.push([timestamp, value]); + } + + this.maxValue = isNaN(this.maxValue) ? value : Math.max(this.maxValue, value); + this.minValue = isNaN(this.minValue) ? value : Math.min(this.minValue, value); + }; + + TimeSeries.prototype.dropOldData = function(oldestValidTime, maxDataSetLength) { + // We must always keep one expired data point as we need this to draw the + // line that comes into the chart from the left, but any points prior to that can be removed. + var removeCount = 0; + while (this.data.length - removeCount >= maxDataSetLength && this.data[removeCount + 1][0] < oldestValidTime) { + removeCount++; + } + if (removeCount !== 0) { + this.data.splice(0, removeCount); + } + }; + + /** + * Initialises a new SmoothieChart. + * + * Options are optional, and should be of the form below. Just specify the values you + * need and the rest will be given sensible defaults as shown: + * + *
+   * {
+   *   minValue: undefined,                      // specify to clamp the lower y-axis to a given value
+   *   maxValue: undefined,                      // specify to clamp the upper y-axis to a given value
+   *   maxValueScale: 1,                         // allows proportional padding to be added above the chart. for 10% padding, specify 1.1.
+   *   minValueScale: 1,                         // allows proportional padding to be added below the chart. for 10% padding, specify 1.1.
+   *   yRangeFunction: undefined,                // function({min: , max: }) { return {min: , max: }; }
+   *   scaleSmoothing: 0.125,                    // controls the rate at which y-value zoom animation occurs
+   *   millisPerPixel: 20,                       // sets the speed at which the chart pans by
+   *   enableDpiScaling: true,                   // support rendering at different DPI depending on the device
+   *   yMinFormatter: function(min, precision) { // callback function that formats the min y value label
+   *     return parseFloat(min).toFixed(precision);
+   *   },
+   *   yMaxFormatter: function(max, precision) { // callback function that formats the max y value label
+   *     return parseFloat(max).toFixed(precision);
+   *   },
+   *   maxDataSetLength: 2,
+   *   interpolation: 'bezier'                   // one of 'bezier', 'linear', or 'step'
+   *   timestampFormatter: null,                 // optional function to format time stamps for bottom of chart
+   *                                             // you may use SmoothieChart.timeFormatter, or your own: function(date) { return ''; }
+   *   scrollBackwards: false,                   // reverse the scroll direction of the chart
+   *   horizontalLines: [],                      // [ { value: 0, color: '#ffffff', lineWidth: 1 } ]
+   *   grid:
+   *   {
+   *     fillStyle: '#000000',                   // the background colour of the chart
+   *     lineWidth: 1,                           // the pixel width of grid lines
+   *     strokeStyle: '#777777',                 // colour of grid lines
+   *     millisPerLine: 1000,                    // distance between vertical grid lines
+   *     sharpLines: false,                      // controls whether grid lines are 1px sharp, or softened
+   *     verticalSections: 2,                    // number of vertical sections marked out by horizontal grid lines
+   *     borderVisible: true                     // whether the grid lines trace the border of the chart or not
+   *   },
+   *   labels
+   *   {
+   *     disabled: false,                        // enables/disables labels showing the min/max values
+   *     fillStyle: '#ffffff',                   // colour for text of labels,
+   *     fontSize: 15,
+   *     fontFamily: 'sans-serif',
+   *     precision: 2
+   *   }
+   * }
+   * 
+ * + * @constructor + */ + function SmoothieChart(options) { + this.options = Util.extend({}, SmoothieChart.defaultChartOptions, options); + this.seriesSet = []; + this.currentValueRange = 1; + this.currentVisMinValue = 0; + this.lastRenderTimeMillis = 0; + } + + SmoothieChart.defaultChartOptions = { + millisPerPixel: 20, + enableDpiScaling: true, + yMinFormatter: function(min, precision) { + return parseFloat(min).toFixed(precision); + }, + yMaxFormatter: function(max, precision) { + return parseFloat(max).toFixed(precision); + }, + maxValueScale: 1, + minValueScale: 1, + interpolation: 'bezier', + scaleSmoothing: 0.125, + maxDataSetLength: 2, + scrollBackwards: false, + grid: { + fillStyle: '#000000', + strokeStyle: '#777777', + lineWidth: 1, + sharpLines: false, + millisPerLine: 1000, + verticalSections: 2, + borderVisible: true + }, + labels: { + fillStyle: '#ffffff', + disabled: false, + fontSize: 10, + fontFamily: 'monospace', + precision: 2 + }, + horizontalLines: [] + }; + + // Based on http://inspirit.github.com/jsfeat/js/compatibility.js + SmoothieChart.AnimateCompatibility = (function() { + var requestAnimationFrame = function(callback, element) { + var requestAnimationFrame = + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(function() { + callback(new Date().getTime()); + }, 16); + }; + return requestAnimationFrame.call(window, callback, element); + }, + cancelAnimationFrame = function(id) { + var cancelAnimationFrame = + window.cancelAnimationFrame || + function(id) { + clearTimeout(id); + }; + return cancelAnimationFrame.call(window, id); + }; + + return { + requestAnimationFrame: requestAnimationFrame, + cancelAnimationFrame: cancelAnimationFrame + }; + })(); + + SmoothieChart.defaultSeriesPresentationOptions = { + lineWidth: 1, + strokeStyle: '#ffffff' + }; + + /** + * Adds a TimeSeries to this chart, with optional presentation options. + * + * Presentation options should be of the form (defaults shown): + * + *
+   * {
+   *   lineWidth: 1,
+   *   strokeStyle: '#ffffff',
+   *   fillStyle: undefined
+   * }
+   * 
+ */ + SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) { + this.seriesSet.push({timeSeries: timeSeries, options: Util.extend({}, SmoothieChart.defaultSeriesPresentationOptions, options)}); + if (timeSeries.options.resetBounds && timeSeries.options.resetBoundsInterval > 0) { + timeSeries.resetBoundsTimerId = setInterval( + function() { + timeSeries.resetBounds(); + }, + timeSeries.options.resetBoundsInterval + ); + } + }; + + /** + * Removes the specified TimeSeries from the chart. + */ + SmoothieChart.prototype.removeTimeSeries = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + this.seriesSet.splice(i, 1); + break; + } + } + // If a timer was operating for that timeseries, remove it + if (timeSeries.resetBoundsTimerId) { + // Stop resetting the bounds, if we were + clearInterval(timeSeries.resetBoundsTimerId); + } + }; + + /** + * Gets render options for the specified TimeSeries. + * + * As you may use a single TimeSeries in multiple charts with different formatting in each usage, + * these settings are stored in the chart. + */ + SmoothieChart.prototype.getTimeSeriesOptions = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + return this.seriesSet[i].options; + } + } + }; + + /** + * Brings the specified TimeSeries to the top of the chart. It will be rendered last. + */ + SmoothieChart.prototype.bringToFront = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + var set = this.seriesSet.splice(i, 1); + this.seriesSet.push(set[0]); + break; + } + } + }; + + /** + * Instructs the SmoothieChart to start rendering to the provided canvas, with specified delay. + * + * @param canvas the target canvas element + * @param delayMillis an amount of time to wait before a data point is shown. This can prevent the end of the series + * from appearing on screen, with new values flashing into view, at the expense of some latency. + */ + SmoothieChart.prototype.streamTo = function(canvas, delayMillis) { + this.canvas = canvas; + this.delay = delayMillis; + this.start(); + }; + + /** + * Make sure the canvas has the optimal resolution for the device's pixel ratio. + */ + SmoothieChart.prototype.resize = function() { + // TODO this function doesn't handle the value of enableDpiScaling changing during execution + if (!this.options.enableDpiScaling || !window || window.devicePixelRatio === 1) + return; + + var dpr = window.devicePixelRatio; + var width = parseInt(this.canvas.getAttribute('width')); + var height = parseInt(this.canvas.getAttribute('height')); + + if (!this.originalWidth || (Math.floor(this.originalWidth * dpr) !== width)) { + this.originalWidth = width; + this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString()); + this.canvas.style.width = width + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); + } + + if (!this.originalHeight || (Math.floor(this.originalHeight * dpr) !== height)) { + this.originalHeight = height; + this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString()); + this.canvas.style.height = height + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); + } + }; + + /** + * Starts the animation of this chart. + */ + SmoothieChart.prototype.start = function() { + if (this.frame) { + // We're already running, so just return + return; + } + + // Renders a frame, and queues the next frame for later rendering + var animate = function() { + this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(function() { + this.render(); + animate(); + }.bind(this)); + }.bind(this); + + animate(); + }; + + /** + * Stops the animation of this chart. + */ + SmoothieChart.prototype.stop = function() { + if (this.frame) { + SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame); + delete this.frame; + } + }; + + SmoothieChart.prototype.updateValueRange = function() { + // Calculate the current scale of the chart, from all time series. + var chartOptions = this.options, + chartMaxValue = Number.NaN, + chartMinValue = Number.NaN; + + for (var d = 0; d < this.seriesSet.length; d++) { + // TODO(ndunn): We could calculate / track these values as they stream in. + var timeSeries = this.seriesSet[d].timeSeries; + if (!isNaN(timeSeries.maxValue)) { + chartMaxValue = !isNaN(chartMaxValue) ? Math.max(chartMaxValue, timeSeries.maxValue) : timeSeries.maxValue; + } + + if (!isNaN(timeSeries.minValue)) { + chartMinValue = !isNaN(chartMinValue) ? Math.min(chartMinValue, timeSeries.minValue) : timeSeries.minValue; + } + } + + // Scale the chartMaxValue to add padding at the top if required + if (chartOptions.maxValue != null) { + chartMaxValue = chartOptions.maxValue; + } else { + chartMaxValue *= chartOptions.maxValueScale; + } + + // Set the minimum if we've specified one + if (chartOptions.minValue != null) { + chartMinValue = chartOptions.minValue; + } else { + chartMinValue -= Math.abs(chartMinValue * chartOptions.minValueScale - chartMinValue); + } + + // If a custom range function is set, call it + if (this.options.yRangeFunction) { + var range = this.options.yRangeFunction({min: chartMinValue, max: chartMaxValue}); + chartMinValue = range.min; + chartMaxValue = range.max; + } + + if (!isNaN(chartMaxValue) && !isNaN(chartMinValue)) { + var targetValueRange = chartMaxValue - chartMinValue; + var valueRangeDiff = (targetValueRange - this.currentValueRange); + var minValueDiff = (chartMinValue - this.currentVisMinValue); + this.isAnimatingScale = Math.abs(valueRangeDiff) > 0.1 || Math.abs(minValueDiff) > 0.1; + this.currentValueRange += chartOptions.scaleSmoothing * valueRangeDiff; + this.currentVisMinValue += chartOptions.scaleSmoothing * minValueDiff; + } + + this.valueRange = { min: chartMinValue, max: chartMaxValue }; + }; + + SmoothieChart.prototype.render = function(canvas, time) { + var nowMillis = new Date().getTime(); + + if (!this.isAnimatingScale) { + // We're not animating. We can use the last render time and the scroll speed to work out whether + // we actually need to paint anything yet. If not, we can return immediately. + + // Render at least every 1/6th of a second. The canvas may be resized, which there is + // no reliable way to detect. + var maxIdleMillis = Math.min(1000/6, this.options.millisPerPixel); + + if (nowMillis - this.lastRenderTimeMillis < maxIdleMillis) { + return; + } + } + + this.resize(); + + this.lastRenderTimeMillis = nowMillis; + + canvas = canvas || this.canvas; + time = time || nowMillis - (this.delay || 0); + + // Round time down to pixel granularity, so motion appears smoother. + time -= time % this.options.millisPerPixel; + + var context = canvas.getContext('2d'), + chartOptions = this.options, + dimensions = { top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight }, + // Calculate the threshold time for the oldest data points. + oldestValidTime = time - (dimensions.width * chartOptions.millisPerPixel), + valueToYPixel = function(value) { + var offset = value - this.currentVisMinValue; + return this.currentValueRange === 0 + ? dimensions.height + : dimensions.height - (Math.round((offset / this.currentValueRange) * dimensions.height)); + }.bind(this), + timeToXPixel = function(t) { + if(chartOptions.scrollBackwards) { + return Math.round((time - t) / chartOptions.millisPerPixel); + } + return Math.round(dimensions.width - ((time - t) / chartOptions.millisPerPixel)); + }; + + this.updateValueRange(); + + context.font = chartOptions.labels.fontSize + 'px ' + chartOptions.labels.fontFamily; + + // Save the state of the canvas context, any transformations applied in this method + // will get removed from the stack at the end of this method when .restore() is called. + context.save(); + + // Move the origin. + context.translate(dimensions.left, dimensions.top); + + // Create a clipped rectangle - anything we draw will be constrained to this rectangle. + // This prevents the occasional pixels from curves near the edges overrunning and creating + // screen cheese (that phrase should need no explanation). + context.beginPath(); + context.rect(0, 0, dimensions.width, dimensions.height); + context.clip(); + + // Clear the working area. + context.save(); + context.fillStyle = chartOptions.grid.fillStyle; + context.clearRect(0, 0, dimensions.width, dimensions.height); + context.fillRect(0, 0, dimensions.width, dimensions.height); + context.restore(); + + // Grid lines... + context.save(); + context.lineWidth = chartOptions.grid.lineWidth; + context.strokeStyle = chartOptions.grid.strokeStyle; + // Vertical (time) dividers. + if (chartOptions.grid.millisPerLine > 0) { + context.beginPath(); + for (var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine) { + var gx = timeToXPixel(t); + if (chartOptions.grid.sharpLines) { + gx -= 0.5; + } + context.moveTo(gx, 0); + context.lineTo(gx, dimensions.height); + } + context.stroke(); + context.closePath(); + } + + // Horizontal (value) dividers. + for (var v = 1; v < chartOptions.grid.verticalSections; v++) { + var gy = Math.round(v * dimensions.height / chartOptions.grid.verticalSections); + if (chartOptions.grid.sharpLines) { + gy -= 0.5; + } + context.beginPath(); + context.moveTo(0, gy); + context.lineTo(dimensions.width, gy); + context.stroke(); + context.closePath(); + } + // Bounding rectangle. + if (chartOptions.grid.borderVisible) { + context.beginPath(); + context.strokeRect(0, 0, dimensions.width, dimensions.height); + context.closePath(); + } + context.restore(); + + // Draw any horizontal lines... + if (chartOptions.horizontalLines && chartOptions.horizontalLines.length) { + for (var hl = 0; hl < chartOptions.horizontalLines.length; hl++) { + var line = chartOptions.horizontalLines[hl], + hly = Math.round(valueToYPixel(line.value)) - 0.5; + context.strokeStyle = line.color || '#ffffff'; + context.lineWidth = line.lineWidth || 1; + context.beginPath(); + context.moveTo(0, hly); + context.lineTo(dimensions.width, hly); + context.stroke(); + context.closePath(); + } + } + + // For each data set... + for (var d = 0; d < this.seriesSet.length; d++) { + context.save(); + var timeSeries = this.seriesSet[d].timeSeries, + dataSet = timeSeries.data, + seriesOptions = this.seriesSet[d].options; + + // Delete old data that's moved off the left of the chart. + timeSeries.dropOldData(oldestValidTime, chartOptions.maxDataSetLength); + + // Set style for this dataSet. + context.lineWidth = seriesOptions.lineWidth; + context.strokeStyle = seriesOptions.strokeStyle; + // Draw the line... + context.beginPath(); + // Retain lastX, lastY for calculating the control points of bezier curves. + var firstX = 0, lastX = 0, lastY = 0; + for (var i = 0; i < dataSet.length && dataSet.length !== 1; i++) { + var x = timeToXPixel(dataSet[i][0]), + y = valueToYPixel(dataSet[i][1]); + + if (i === 0) { + firstX = x; + context.moveTo(x, y); + } else { + switch (chartOptions.interpolation) { + case "linear": + case "line": { + context.lineTo(x,y); + break; + } + case "bezier": + default: { + // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves + // + // Assuming A was the last point in the line plotted and B is the new point, + // we draw a curve with control points P and Q as below. + // + // A---P + // | + // | + // | + // Q---B + // + // Importantly, A and P are at the same y coordinate, as are B and Q. This is + // so adjacent curves appear to flow as one. + // + context.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop + Math.round((lastX + x) / 2), lastY, // controlPoint1 (P) + Math.round((lastX + x)) / 2, y, // controlPoint2 (Q) + x, y); // endPoint (B) + break; + } + case "step": { + context.lineTo(x,lastY); + context.lineTo(x,y); + break; + } + } + } + + lastX = x; lastY = y; + } + + if (dataSet.length > 1) { + if (seriesOptions.fillStyle) { + // Close up the fill region. + context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY); + context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1); + context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth); + context.fillStyle = seriesOptions.fillStyle; + context.fill(); + } + + if (seriesOptions.strokeStyle && seriesOptions.strokeStyle !== 'none') { + context.stroke(); + } + context.closePath(); + } + context.restore(); + } + + // Draw the axis values on the chart. + if (!chartOptions.labels.disabled && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max)) { + var maxValueString = chartOptions.yMaxFormatter(this.valueRange.max, chartOptions.labels.precision), + minValueString = chartOptions.yMinFormatter(this.valueRange.min, chartOptions.labels.precision), + labelPos = chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(maxValueString).width - 2; + context.fillStyle = chartOptions.labels.fillStyle; + context.fillText(maxValueString, labelPos, chartOptions.labels.fontSize); + context.fillText(minValueString, labelPos, dimensions.height - 2); + } + + // Display timestamps along x-axis at the bottom of the chart. + if (chartOptions.timestampFormatter && chartOptions.grid.millisPerLine > 0) { + var textUntilX = chartOptions.scrollBackwards + ? context.measureText(minValueString).width + : dimensions.width - context.measureText(minValueString).width + 4; + for (var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine) { + var gx = timeToXPixel(t); + // Only draw the timestamp if it won't overlap with the previously drawn one. + if ((!chartOptions.scrollBackwards && gx < textUntilX) || (chartOptions.scrollBackwards && gx > textUntilX)) { + // Formats the timestamp based on user specified formatting function + // SmoothieChart.timeFormatter function above is one such formatting option + var tx = new Date(t), + ts = chartOptions.timestampFormatter(tx), + tsWidth = context.measureText(ts).width; + + textUntilX = chartOptions.scrollBackwards + ? gx + tsWidth + 2 + : gx - tsWidth - 2; + + context.fillStyle = chartOptions.labels.fillStyle; + if(chartOptions.scrollBackwards) { + context.fillText(ts, gx, dimensions.height - 2); + } else { + context.fillText(ts, gx - tsWidth, dimensions.height - 2); + } + } + } + } + + context.restore(); // See .save() above. + }; + + // Sample timestamp formatting function + SmoothieChart.timeFormatter = function(date) { + function pad2(number) { return (number < 10 ? '0' : '') + number } + return pad2(date.getHours()) + ':' + pad2(date.getMinutes()) + ':' + pad2(date.getSeconds()); + }; + + exports.TimeSeries = TimeSeries; + exports.SmoothieChart = SmoothieChart; + +})(typeof exports === 'undefined' ? this : exports);